]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/dird/fd_cmds.c
kes Add code to help Dan debug 2drive-incremental-2disk test.
[bacula/bacula] / bacula / src / dird / fd_cmds.c
1 /*
2    Bacula® - The Network Backup Solution
3
4    Copyright (C) 2000-2007 Free Software Foundation Europe e.V.
5
6    The main author of Bacula is Kern Sibbald, with contributions from
7    many others, a complete list can be found in the file AUTHORS.
8    This program is Free Software; you can redistribute it and/or
9    modify it under the terms of version two of the GNU General Public
10    License as published by the Free Software Foundation and included
11    in the file LICENSE.
12
13    This program is distributed in the hope that it will be useful, but
14    WITHOUT ANY WARRANTY; without even the implied warranty of
15    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16    General Public License for more details.
17
18    You should have received a copy of the GNU General Public License
19    along with this program; if not, write to the Free Software
20    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21    02110-1301, USA.
22
23    Bacula® is a registered trademark of John Walker.
24    The licensor of Bacula is the Free Software Foundation Europe
25    (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zürich,
26    Switzerland, email:ftf@fsfeurope.org.
27 */
28 /*
29  *
30  *   Bacula Director -- fd_cmds.c -- send commands to File daemon
31  *
32  *     Kern Sibbald, October MM
33  *
34  *    This routine is run as a separate thread.  There may be more
35  *    work to be done to make it totally reentrant!!!!
36  *
37  *  Utility functions for sending info to File Daemon.
38  *   These functions are used by both backup and verify.
39  *
40  *   Version $Id$
41  */
42
43 #include "bacula.h"
44 #include "dird.h"
45 #include "findlib/find.h"
46
47 const int dbglvl = 400;
48
49 /* Commands sent to File daemon */
50 static char filesetcmd[]  = "fileset%s\n"; /* set full fileset */
51 static char jobcmd[]      = "JobId=%s Job=%s SDid=%u SDtime=%u Authorization=%s\n";
52 /* Note, mtime_only is not used here -- implemented as file option */
53 static char levelcmd[]    = "level = %s%s mtime_only=%d\n";
54 static char runscript[]   = "Run OnSuccess=%u OnFailure=%u AbortOnError=%u When=%u Command=%s\n";
55 static char runbeforenow[]= "RunBeforeNow\n";
56
57 /* Responses received from File daemon */
58 static char OKinc[]          = "2000 OK include\n";
59 static char OKjob[]          = "2000 OK Job";
60 static char OKlevel[]        = "2000 OK level\n";
61 static char OKRunScript[]    = "2000 OK RunScript\n";
62 static char OKRunBeforeNow[] = "2000 OK RunBeforeNow\n";
63
64 /* Forward referenced functions */
65
66 /* External functions */
67 extern DIRRES *director;
68 extern int FDConnectTimeout;
69
70 #define INC_LIST 0
71 #define EXC_LIST 1
72
73 /*
74  * Open connection with File daemon.
75  * Try connecting every retry_interval (default 10 sec), and
76  *   give up after max_retry_time (default 30 mins).
77  */
78
79 int connect_to_file_daemon(JCR *jcr, int retry_interval, int max_retry_time,
80                            int verbose)
81 {
82    BSOCK   *fd;
83    char ed1[30];
84    utime_t heart_beat;
85
86    if (jcr->client->heartbeat_interval) {
87       heart_beat = jcr->client->heartbeat_interval;
88    } else {           
89       heart_beat = director->heartbeat_interval;
90    }
91
92    if (!jcr->file_bsock) {
93       fd = bnet_connect(jcr, retry_interval, max_retry_time, heart_beat,
94            _("File daemon"), jcr->client->address,
95            NULL, jcr->client->FDport, verbose);
96       if (fd == NULL) {
97          set_jcr_job_status(jcr, JS_ErrorTerminated);
98          return 0;
99       }
100       Dmsg0(10, "Opened connection with File daemon\n");
101    } else {
102       fd = jcr->file_bsock;           /* use existing connection */
103    }
104    fd->res = (RES *)jcr->client;      /* save resource in BSOCK */
105    jcr->file_bsock = fd;
106    set_jcr_job_status(jcr, JS_Running);
107
108    if (!authenticate_file_daemon(jcr)) {
109       set_jcr_job_status(jcr, JS_ErrorTerminated);
110       return 0;
111    }
112
113    /*
114     * Now send JobId and authorization key
115     */
116    fd->fsend(jobcmd, edit_int64(jcr->JobId, ed1), jcr->Job, jcr->VolSessionId,
117       jcr->VolSessionTime, jcr->sd_auth_key);
118    if (debug_level == 3) {
119       Dmsg1(000, ">filed: %s", fd->msg);
120    }
121    if (strcmp(jcr->sd_auth_key, "dummy") != 0) {
122       memset(jcr->sd_auth_key, 0, strlen(jcr->sd_auth_key));
123    }
124    Dmsg1(100, ">filed: %s", fd->msg);
125    if (bget_dirmsg(fd) > 0) {
126        Dmsg1(110, "<filed: %s", fd->msg);
127        if (strncmp(fd->msg, OKjob, strlen(OKjob)) != 0) {
128           Jmsg(jcr, M_FATAL, 0, _("File daemon \"%s\" rejected Job command: %s\n"),
129              jcr->client->hdr.name, fd->msg);
130           set_jcr_job_status(jcr, JS_ErrorTerminated);
131           return 0;
132        } else if (jcr->db) {
133           CLIENT_DBR cr;
134           memset(&cr, 0, sizeof(cr));
135           bstrncpy(cr.Name, jcr->client->hdr.name, sizeof(cr.Name));
136           cr.AutoPrune = jcr->client->AutoPrune;
137           cr.FileRetention = jcr->client->FileRetention;
138           cr.JobRetention = jcr->client->JobRetention;
139           bstrncpy(cr.Uname, fd->msg+strlen(OKjob)+1, sizeof(cr.Uname));
140           if (!db_update_client_record(jcr, jcr->db, &cr)) {
141              Jmsg(jcr, M_WARNING, 0, _("Error updating Client record. ERR=%s\n"),
142                 db_strerror(jcr->db));
143           }
144        }
145    } else {
146       Jmsg(jcr, M_FATAL, 0, _("FD gave bad response to JobId command: %s\n"),
147          bnet_strerror(fd));
148       set_jcr_job_status(jcr, JS_ErrorTerminated);
149       return 0;
150    }
151    return 1;
152 }
153
154 /*
155  * This subroutine edits the last job start time into a
156  *   "since=date/time" buffer that is returned in the
157  *   variable since.  This is used for display purposes in
158  *   the job report.  The time in jcr->stime is later
159  *   passed to tell the File daemon what to do.
160  */
161 void get_level_since_time(JCR *jcr, char *since, int since_len)
162 {
163    int JobLevel;
164
165    since[0] = 0;
166    if (jcr->cloned) {
167       if (jcr->stime && jcr->stime[0]) {
168          bstrncpy(since, _(", since="), since_len);
169          bstrncat(since, jcr->stime, since_len);
170       }
171       return;
172    }
173    if (!jcr->stime) {
174       jcr->stime = get_pool_memory(PM_MESSAGE);
175    } 
176    jcr->stime[0] = 0;
177    /* Lookup the last FULL backup job to get the time/date for a
178     * differential or incremental save.
179     */
180    switch (jcr->JobLevel) {
181    case L_DIFFERENTIAL:
182    case L_INCREMENTAL:
183       /* Look up start time of last job */
184       jcr->jr.JobId = 0;     /* flag for db_find_job_start time */
185       if (!db_find_job_start_time(jcr, jcr->db, &jcr->jr, &jcr->stime)) {
186          /* No job found, so upgrade this one to Full */
187          Jmsg(jcr, M_INFO, 0, "%s", db_strerror(jcr->db));
188          Jmsg(jcr, M_INFO, 0, _("No prior or suitable Full backup found in catalog. Doing FULL backup.\n"));
189          bsnprintf(since, since_len, _(" (upgraded from %s)"),
190             level_to_str(jcr->JobLevel));
191          jcr->JobLevel = jcr->jr.JobLevel = L_FULL;
192       } else {
193          if (jcr->job->rerun_failed_levels) {
194             if (db_find_failed_job_since(jcr, jcr->db, &jcr->jr, jcr->stime, JobLevel)) {
195                Jmsg(jcr, M_INFO, 0, _("Prior failed job found in catalog. Upgrading to %s.\n"),
196                   level_to_str(JobLevel));
197                bsnprintf(since, since_len, _(" (upgraded from %s)"),
198                   level_to_str(jcr->JobLevel));
199                jcr->JobLevel = jcr->jr.JobLevel = JobLevel;
200                jcr->jr.JobId = jcr->JobId;
201                break;
202             }
203          }
204          bstrncpy(since, _(", since="), since_len);
205          bstrncat(since, jcr->stime, since_len);
206       }
207       jcr->jr.JobId = jcr->JobId;
208       break;
209    }
210    Dmsg2(100, "Level=%c last start time=%s\n", jcr->JobLevel, jcr->stime);
211 }
212
213 static void send_since_time(JCR *jcr)
214 {
215    BSOCK   *fd = jcr->file_bsock;
216    utime_t stime;
217    char ed1[50];
218
219    stime = str_to_utime(jcr->stime);
220    bnet_fsend(fd, levelcmd, NT_("since_utime "), edit_uint64(stime, ed1), 0);
221    while (bget_dirmsg(fd) >= 0) {  /* allow him to poll us to sync clocks */
222       Jmsg(jcr, M_INFO, 0, "%s\n", fd->msg);
223    }
224 }
225
226
227 /*
228  * Send level command to FD.
229  * Used for backup jobs and estimate command.
230  */
231 bool send_level_command(JCR *jcr)
232 {
233    BSOCK   *fd = jcr->file_bsock;
234    /*
235     * Send Level command to File daemon
236     */
237    switch (jcr->JobLevel) {
238    case L_BASE:
239       bnet_fsend(fd, levelcmd, "base", " ", 0);
240       break;
241    /* L_NONE is the console, sending something off to the FD */
242    case L_NONE:
243    case L_FULL:
244       bnet_fsend(fd, levelcmd, "full", " ", 0);
245       break;
246    case L_DIFFERENTIAL:
247       bnet_fsend(fd, levelcmd, "differential", " ", 0);
248       send_since_time(jcr);
249       break;
250    case L_INCREMENTAL:
251       bnet_fsend(fd, levelcmd, "incremental", " ", 0);
252       send_since_time(jcr);
253       break;
254    case L_SINCE:
255    default:
256       Jmsg2(jcr, M_FATAL, 0, _("Unimplemented backup level %d %c\n"),
257          jcr->JobLevel, jcr->JobLevel);
258       return 0;
259    }
260    Dmsg1(120, ">filed: %s", fd->msg);
261    if (!response(jcr, fd, OKlevel, "Level", DISPLAY_ERROR)) {
262       return 0;
263    }
264    return 1;
265 }
266
267 /*
268  * Send either an Included or an Excluded list to FD
269  */
270 static bool send_fileset(JCR *jcr)
271 {
272    FILESET *fileset = jcr->fileset;
273    BSOCK   *fd = jcr->file_bsock;
274    int num;
275    bool include = true;
276
277    for ( ;; ) {
278       if (include) {
279          num = fileset->num_includes;
280       } else {
281          num = fileset->num_excludes;
282       }
283       for (int i=0; i<num; i++) {
284          BPIPE *bpipe;
285          FILE *ffd;
286          char buf[2000];
287          char *p;
288          int optlen, stat;
289          INCEXE *ie;
290          int j, k;
291
292          if (include) {
293             ie = fileset->include_items[i];
294             bnet_fsend(fd, "I\n");
295          } else {
296             ie = fileset->exclude_items[i];
297             bnet_fsend(fd, "E\n");
298          }
299          for (j=0; j<ie->num_opts; j++) {
300             FOPTS *fo = ie->opts_list[j];
301             bnet_fsend(fd, "O %s\n", fo->opts);
302
303             bool enhanced_wild = false;
304             for (k=0; fo->opts[k]!='\0'; k++) {
305                if (fo->opts[k]=='W') {
306                   enhanced_wild = true;
307                   break;
308                }
309             }
310
311             for (k=0; k<fo->regex.size(); k++) {
312                bnet_fsend(fd, "R %s\n", fo->regex.get(k));
313             }
314             for (k=0; k<fo->regexdir.size(); k++) {
315                bnet_fsend(fd, "RD %s\n", fo->regexdir.get(k));
316             }
317             for (k=0; k<fo->regexfile.size(); k++) {
318                bnet_fsend(fd, "RF %s\n", fo->regexfile.get(k));
319             }
320             for (k=0; k<fo->wild.size(); k++) {
321                bnet_fsend(fd, "W %s\n", fo->wild.get(k));
322             }
323             for (k=0; k<fo->wilddir.size(); k++) {
324                bnet_fsend(fd, "WD %s\n", fo->wilddir.get(k));
325             }
326             for (k=0; k<fo->wildfile.size(); k++) {
327                bnet_fsend(fd, "WF %s\n", fo->wildfile.get(k));
328             }
329             for (k=0; k<fo->wildbase.size(); k++) {
330                bnet_fsend(fd, "W%c %s\n", enhanced_wild ? 'B' : 'F', fo->wildbase.get(k));
331             }
332             for (k=0; k<fo->base.size(); k++) {
333                bnet_fsend(fd, "B %s\n", fo->base.get(k));
334             }
335             for (k=0; k<fo->fstype.size(); k++) {
336                bnet_fsend(fd, "X %s\n", fo->fstype.get(k));
337             }
338             for (k=0; k<fo->drivetype.size(); k++) {
339                bnet_fsend(fd, "XD %s\n", fo->drivetype.get(k));
340             }
341             if (fo->reader) {
342                bnet_fsend(fd, "D %s\n", fo->reader);
343             }
344             if (fo->writer) {
345                bnet_fsend(fd, "T %s\n", fo->writer);
346             }
347             bnet_fsend(fd, "N\n");
348          }
349
350          for (j=0; j<ie->name_list.size(); j++) {
351             p = (char *)ie->name_list.get(j);
352             switch (*p) {
353             case '|':
354                p++;                      /* skip over the | */
355                fd->msg = edit_job_codes(jcr, fd->msg, p, "");
356                bpipe = open_bpipe(fd->msg, 0, "r");
357                if (!bpipe) {
358                   berrno be;
359                   Jmsg(jcr, M_FATAL, 0, _("Cannot run program: %s. ERR=%s\n"),
360                      p, be.bstrerror());
361                   goto bail_out;
362                }
363                bstrncpy(buf, "F ", sizeof(buf));
364                Dmsg1(500, "Opts=%s\n", buf);
365                optlen = strlen(buf);
366                while (fgets(buf+optlen, sizeof(buf)-optlen, bpipe->rfd)) {
367                   fd->msglen = Mmsg(fd->msg, "%s", buf);
368                   Dmsg2(500, "Inc/exc len=%d: %s", fd->msglen, fd->msg);
369                   if (!bnet_send(fd)) {
370                      Jmsg(jcr, M_FATAL, 0, _(">filed: write error on socket\n"));
371                      goto bail_out;
372                   }
373                }
374                if ((stat=close_bpipe(bpipe)) != 0) {
375                   berrno be;
376                   Jmsg(jcr, M_FATAL, 0, _("Error running program: %s. ERR=%s\n"),
377                      p, be.bstrerror(stat));
378                   goto bail_out;
379                }
380                break;
381             case '<':
382                p++;                      /* skip over < */
383                if ((ffd = fopen(p, "rb")) == NULL) {
384                   berrno be;
385                   Jmsg(jcr, M_FATAL, 0, _("Cannot open included file: %s. ERR=%s\n"),
386                      p, be.bstrerror());
387                   goto bail_out;
388                }
389                bstrncpy(buf, "F ", sizeof(buf));
390                Dmsg1(500, "Opts=%s\n", buf);
391                optlen = strlen(buf);
392                while (fgets(buf+optlen, sizeof(buf)-optlen, ffd)) {
393                   fd->msglen = Mmsg(fd->msg, "%s", buf);
394                   if (!bnet_send(fd)) {
395                      Jmsg(jcr, M_FATAL, 0, _(">filed: write error on socket\n"));
396                      goto bail_out;
397                   }
398                }
399                fclose(ffd);
400                break;
401             case '\\':
402                p++;                      /* skip over \ */
403                /* Note, fall through wanted */
404             default:
405                pm_strcpy(fd->msg, "F ");
406                fd->msglen = pm_strcat(fd->msg, p);
407                Dmsg1(500, "Inc/Exc name=%s\n", fd->msg);
408                if (!bnet_send(fd)) {
409                   Jmsg(jcr, M_FATAL, 0, _(">filed: write error on socket\n"));
410                   goto bail_out;
411                }
412                break;
413             }
414          }
415          bnet_fsend(fd, "N\n");
416       }
417       if (!include) {                 /* If we just did excludes */
418          break;                       /*   all done */
419       }
420       include = false;                /* Now do excludes */
421    }
422
423    bnet_sig(fd, BNET_EOD);            /* end of data */
424    if (!response(jcr, fd, OKinc, "Include", DISPLAY_ERROR)) {
425       goto bail_out;
426    }
427    return true;
428
429 bail_out:
430    set_jcr_job_status(jcr, JS_ErrorTerminated);
431    return false;
432
433 }
434
435
436 /*
437  * Send include list to File daemon
438  */
439 bool send_include_list(JCR *jcr)
440 {
441    BSOCK *fd = jcr->file_bsock;
442    if (jcr->fileset->new_include) {
443       bnet_fsend(fd, filesetcmd, jcr->fileset->enable_vss ? " vss=1" : "");
444       return send_fileset(jcr);
445    }
446    return true;
447 }
448
449
450 /*
451  * Send exclude list to File daemon
452  *   Under the new scheme, the Exclude list
453  *   is part of the FileSet sent with the
454  *   "include_list" above.
455  */
456 bool send_exclude_list(JCR *jcr)
457 {
458    return true;
459 }
460
461
462 /*
463  * Send bootstrap file if any to the socket given (FD or SD).
464  *  This is used for restore, verify VolumeToCatalog, and
465  *  for migration.
466  */
467 bool send_bootstrap_file(JCR *jcr, BSOCK *sock)
468 {
469    FILE *bs;
470    char buf[1000];
471    const char *bootstrap = "bootstrap\n";
472
473    Dmsg1(400, "send_bootstrap_file: %s\n", jcr->RestoreBootstrap);
474    if (!jcr->RestoreBootstrap) {
475       return true;
476    }
477    bs = fopen(jcr->RestoreBootstrap, "rb");
478    if (!bs) {
479       berrno be;
480       Jmsg(jcr, M_FATAL, 0, _("Could not open bootstrap file %s: ERR=%s\n"),
481          jcr->RestoreBootstrap, be.bstrerror());
482       set_jcr_job_status(jcr, JS_ErrorTerminated);
483       return false;
484    }
485    bnet_fsend(sock, bootstrap);
486    while (fgets(buf, sizeof(buf), bs)) {
487       bnet_fsend(sock, "%s", buf);
488    }
489    bnet_sig(sock, BNET_EOD);
490    fclose(bs);
491    if (jcr->unlink_bsr) {
492       unlink(jcr->RestoreBootstrap);
493       jcr->unlink_bsr = false;
494    }                         
495    return true;
496 }
497
498 /* TODO: drop this with runscript.old_proto in bacula 1.42 */
499 static char runbefore[]   = "RunBeforeJob %s\n";
500 static char runafter[]    = "RunAfterJob %s\n";
501 static char OKRunBefore[] = "2000 OK RunBefore\n";
502 static char OKRunAfter[]  = "2000 OK RunAfter\n";
503
504 int send_runscript_with_old_proto(JCR *jcr, int when, POOLMEM *msg)
505 {
506    int ret;
507    Dmsg1(120, "bdird: sending old runcommand to fd '%s'\n",msg);
508    if (when & SCRIPT_Before) {
509       bnet_fsend(jcr->file_bsock, runbefore, msg);
510       ret = response(jcr, jcr->file_bsock, OKRunBefore, "ClientRunBeforeJob", DISPLAY_ERROR);
511    } else {
512       bnet_fsend(jcr->file_bsock, runafter, msg);
513       ret = response(jcr, jcr->file_bsock, OKRunAfter, "ClientRunAfterJob", DISPLAY_ERROR);
514    }
515    return ret;
516 } /* END OF TODO */
517
518 /*
519  * Send RunScripts to File daemon
520  * 1) We send all runscript to FD, they can be executed Before, After, or twice
521  * 2) Then, we send a "RunBeforeNow" command to the FD to tell him to do the
522  *    first run_script() call. (ie ClientRunBeforeJob)
523  */
524 int send_runscripts_commands(JCR *jcr)
525 {
526    POOLMEM *msg = get_pool_memory(PM_FNAME);
527    BSOCK *fd = jcr->file_bsock;
528    RUNSCRIPT *cmd;
529    bool launch_before_cmd = false;
530    POOLMEM *ehost = get_pool_memory(PM_FNAME);
531    int result;
532
533    Dmsg0(120, "bdird: sending runscripts to fd\n");
534    
535    foreach_alist(cmd, jcr->job->RunScripts) {
536       if (cmd->can_run_at_level(jcr->JobLevel) && cmd->target) {
537          ehost = edit_job_codes(jcr, ehost, cmd->target, "");
538          Dmsg2(200, "bdird: runscript %s -> %s\n", cmd->target, ehost);
539
540          if (strcmp(ehost, jcr->client->name()) == 0) {
541             pm_strcpy(msg, cmd->command);
542             bash_spaces(msg);
543
544             Dmsg1(120, "bdird: sending runscripts to fd '%s'\n", cmd->command);
545             
546             /* TODO: remove this with bacula 1.42 */
547             if (cmd->old_proto) {
548                result = send_runscript_with_old_proto(jcr, cmd->when, msg);
549
550             } else {
551                bnet_fsend(fd, runscript, cmd->on_success, 
552                                          cmd->on_failure,
553                                          cmd->fail_on_error,
554                                          cmd->when,
555                                          msg);
556
557                result = response(jcr, fd, OKRunScript, "RunScript", DISPLAY_ERROR);
558                launch_before_cmd = true;
559             }
560             
561             if (!result) {
562                goto bail_out;
563             }
564          }
565          /* TODO : we have to play with other client */
566          /*
567            else {
568            send command to an other client
569            }
570          */
571       }        
572    } 
573
574    /* Tell the FD to execute the ClientRunBeforeJob */
575    if (launch_before_cmd) {
576       bnet_fsend(fd, runbeforenow);
577       if (!response(jcr, fd, OKRunBeforeNow, "RunBeforeNow", DISPLAY_ERROR)) {
578         goto bail_out;
579       }
580    }
581    free_pool_memory(msg);
582    free_pool_memory(ehost);
583    return 1;
584
585 bail_out:
586    Jmsg(jcr, M_FATAL, 0, _("Client \"%s\" RunScript failed.\n"), ehost);
587    free_pool_memory(msg);
588    free_pool_memory(ehost);
589    return 0;
590 }
591
592
593
594 /*
595  * Read the attributes from the File daemon for
596  * a Verify job and store them in the catalog.
597  */
598 int get_attributes_and_put_in_catalog(JCR *jcr)
599 {
600    BSOCK   *fd;
601    int n = 0;
602    ATTR_DBR *ar = NULL;
603    char digest[MAXSTRING];
604
605    fd = jcr->file_bsock;
606    jcr->jr.FirstIndex = 1;
607    jcr->FileIndex = 0;
608    /* Start transaction allocates jcr->attr and jcr->ar if needed */
609    db_start_transaction(jcr, jcr->db);     /* start transaction if not already open */
610    ar = jcr->ar;
611
612    Dmsg0(120, "bdird: waiting to receive file attributes\n");
613    /* Pickup file attributes and digest */
614    while (!fd->errors && (n = bget_dirmsg(fd)) > 0) {
615       uint32_t file_index;
616       int stream, len;
617       char *p, *fn;
618       char Digest[MAXSTRING];      /* either Verify opts or MD5/SHA1 digest */
619
620       jcr->fname = check_pool_memory_size(jcr->fname, fd->msglen);
621       if ((len = sscanf(fd->msg, "%ld %d %s", &file_index, &stream, Digest)) != 3) {
622          Jmsg(jcr, M_FATAL, 0, _("<filed: bad attributes, expected 3 fields got %d\n"
623 "msglen=%d msg=%s\n"), len, fd->msglen, fd->msg);
624          set_jcr_job_status(jcr, JS_ErrorTerminated);
625          return 0;
626       }
627       p = fd->msg;
628       /* The following three fields were sscanf'ed above so skip them */
629       skip_nonspaces(&p);             /* skip FileIndex */
630       skip_spaces(&p);
631       skip_nonspaces(&p);             /* skip Stream */
632       skip_spaces(&p);
633       skip_nonspaces(&p);             /* skip Opts_Digest */
634       p++;                            /* skip space */
635       Dmsg1(dbglvl, "Stream=%d\n", stream);
636       if (stream == STREAM_UNIX_ATTRIBUTES || stream == STREAM_UNIX_ATTRIBUTES_EX) {
637          if (jcr->cached_attribute) {
638             Dmsg3(dbglvl, "Cached attr. Stream=%d fname=%s\n", ar->Stream, ar->fname,
639                ar->attr);
640             if (!db_create_file_attributes_record(jcr, jcr->db, ar)) {
641                Jmsg1(jcr, M_FATAL, 0, _("Attribute create error. %s"), db_strerror(jcr->db));
642             }
643          }
644          /* Any cached attr is flushed so we can reuse jcr->attr and jcr->ar */
645          fn = jcr->fname;
646          while (*p != 0) {
647             *fn++ = *p++;                /* copy filename */
648          }
649          *fn = *p++;                     /* term filename and point p to attribs */
650          pm_strcpy(jcr->attr, p);        /* save attributes */
651          jcr->JobFiles++;
652          jcr->FileIndex = file_index;
653          ar->attr = jcr->attr;
654          ar->fname = jcr->fname;
655          ar->FileIndex = file_index;
656          ar->Stream = stream;
657          ar->link = NULL;
658          ar->JobId = jcr->JobId;
659          ar->ClientId = jcr->ClientId;
660          ar->PathId = 0;
661          ar->FilenameId = 0;
662          ar->Digest = NULL;
663          ar->DigestType = CRYPTO_DIGEST_NONE;
664          jcr->cached_attribute = true;
665
666          Dmsg2(dbglvl, "dird<filed: stream=%d %s\n", stream, jcr->fname);
667          Dmsg1(dbglvl, "dird<filed: attr=%s\n", ar->attr);
668          jcr->FileId = ar->FileId;
669       /*
670        * First, get STREAM_UNIX_ATTRIBUTES and fill ATTR_DBR structure
671        * Next, we CAN have a CRYPTO_DIGEST, so we fill ATTR_DBR with it (or not)
672        * When we get a new STREAM_UNIX_ATTRIBUTES, we known that we can add file to the catalog
673        * At the end, we have to add the last file
674        */
675       } else if (crypto_digest_stream_type(stream) != CRYPTO_DIGEST_NONE) {
676          if (jcr->FileIndex != (uint32_t)file_index) {
677             Jmsg3(jcr, M_ERROR, 0, _("%s index %d not same as attributes %d\n"),
678                stream_to_ascii(stream), file_index, jcr->FileIndex);
679             continue;
680          }
681          ar->Digest = digest;
682          ar->DigestType = crypto_digest_stream_type(stream);
683          db_escape_string(jcr, jcr->db, digest, Digest, strlen(Digest));
684          Dmsg4(dbglvl, "stream=%d DigestLen=%d Digest=%s type=%d\n", stream,
685                strlen(digest), digest, ar->DigestType);
686       }
687       jcr->jr.JobFiles = jcr->JobFiles = file_index;
688       jcr->jr.LastIndex = file_index;
689    }
690    if (is_bnet_error(fd)) {
691       Jmsg1(jcr, M_FATAL, 0, _("<filed: Network error getting attributes. ERR=%s\n"),
692             fd->bstrerror());
693       return 0;
694    }
695    if (jcr->cached_attribute) {
696       Dmsg3(dbglvl, "Cached attr with digest. Stream=%d fname=%s attr=%s\n", ar->Stream,            
697          ar->fname, ar->attr);
698       if (!db_create_file_attributes_record(jcr, jcr->db, ar)) {
699          Jmsg1(jcr, M_FATAL, 0, _("Attribute create error. %s"), db_strerror(jcr->db));
700       }
701       jcr->cached_attribute = false; 
702    }
703    set_jcr_job_status(jcr, JS_Terminated);
704    return 1;
705 }