]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/dird/fd_cmds.c
kes Apply patch from Peter Much <pmc@citylink.dinoex.sub.org> that fixes
[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 static bool send_list_item(JCR *jcr, const char *code, char *item, BSOCK *fd);
66
67 /* External functions */
68 extern DIRRES *director;
69 extern int FDConnectTimeout;
70
71 #define INC_LIST 0
72 #define EXC_LIST 1
73
74 /*
75  * Open connection with File daemon.
76  * Try connecting every retry_interval (default 10 sec), and
77  *   give up after max_retry_time (default 30 mins).
78  */
79
80 int connect_to_file_daemon(JCR *jcr, int retry_interval, int max_retry_time,
81                            int verbose)
82 {
83    BSOCK   *fd;
84    char ed1[30];
85    utime_t heart_beat;
86
87    if (jcr->client->heartbeat_interval) {
88       heart_beat = jcr->client->heartbeat_interval;
89    } else {           
90       heart_beat = director->heartbeat_interval;
91    }
92
93    if (!jcr->file_bsock) {
94       char name[MAX_NAME_LENGTH + 100];
95       bstrncpy(name, _("Client: "), sizeof(name));
96       bstrncat(name, jcr->client->name(), sizeof(name));
97       fd = bnet_connect(jcr, retry_interval, max_retry_time, heart_beat,
98            name, jcr->client->address, NULL, jcr->client->FDport, verbose);
99       if (fd == NULL) {
100          set_jcr_job_status(jcr, JS_ErrorTerminated);
101          return 0;
102       }
103       Dmsg0(10, "Opened connection with File daemon\n");
104    } else {
105       fd = jcr->file_bsock;           /* use existing connection */
106    }
107    fd->res = (RES *)jcr->client;      /* save resource in BSOCK */
108    jcr->file_bsock = fd;
109    set_jcr_job_status(jcr, JS_Running);
110
111    if (!authenticate_file_daemon(jcr)) {
112       set_jcr_job_status(jcr, JS_ErrorTerminated);
113       return 0;
114    }
115
116    /*
117     * Now send JobId and authorization key
118     */
119    fd->fsend(jobcmd, edit_int64(jcr->JobId, ed1), jcr->Job, jcr->VolSessionId,
120       jcr->VolSessionTime, jcr->sd_auth_key);
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 job cloned and a since time already given, use it */
167    if (jcr->cloned && jcr->stime && jcr->stime[0]) {
168       bstrncpy(since, _(", since="), since_len);
169       bstrncat(since, jcr->stime, since_len);
170       return;
171    }
172    /* Make sure stime buffer is allocated */
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    fd->fsend(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       fd->fsend(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       fd->fsend(levelcmd, "full", " ", 0);
245       break;
246    case L_DIFFERENTIAL:
247       fd->fsend(levelcmd, "differential", " ", 0);
248       send_since_time(jcr);
249       break;
250    case L_INCREMENTAL:
251       fd->fsend(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          char *item;
285          INCEXE *ie;
286          int j, k;
287
288          if (include) {
289             ie = fileset->include_items[i];
290             fd->fsend("I\n");
291          } else {
292             ie = fileset->exclude_items[i];
293             fd->fsend("E\n");
294          }
295          for (j=0; j<ie->num_opts; j++) {
296             FOPTS *fo = ie->opts_list[j];
297             fd->fsend("O %s\n", fo->opts);
298
299             bool enhanced_wild = false;
300             for (k=0; fo->opts[k]!='\0'; k++) {
301                if (fo->opts[k]=='W') {
302                   enhanced_wild = true;
303                   break;
304                }
305             }
306
307             for (k=0; k<fo->regex.size(); k++) {
308                fd->fsend("R %s\n", fo->regex.get(k));
309             }
310             for (k=0; k<fo->regexdir.size(); k++) {
311                fd->fsend("RD %s\n", fo->regexdir.get(k));
312             }
313             for (k=0; k<fo->regexfile.size(); k++) {
314                fd->fsend("RF %s\n", fo->regexfile.get(k));
315             }
316             for (k=0; k<fo->wild.size(); k++) {
317                fd->fsend("W %s\n", fo->wild.get(k));
318             }
319             for (k=0; k<fo->wilddir.size(); k++) {
320                fd->fsend("WD %s\n", fo->wilddir.get(k));
321             }
322             for (k=0; k<fo->wildfile.size(); k++) {
323                fd->fsend("WF %s\n", fo->wildfile.get(k));
324             }
325             for (k=0; k<fo->wildbase.size(); k++) {
326                fd->fsend("W%c %s\n", enhanced_wild ? 'B' : 'F', fo->wildbase.get(k));
327             }
328             for (k=0; k<fo->base.size(); k++) {
329                fd->fsend("B %s\n", fo->base.get(k));
330             }
331             for (k=0; k<fo->fstype.size(); k++) {
332                fd->fsend("X %s\n", fo->fstype.get(k));
333             }
334             for (k=0; k<fo->drivetype.size(); k++) {
335                fd->fsend("XD %s\n", fo->drivetype.get(k));
336             }
337             if (fo->plugin) {
338                fd->fsend("G %s\n", fo->plugin);
339             }
340             if (fo->reader) {
341                fd->fsend("D %s\n", fo->reader);
342             }
343             if (fo->writer) {
344                fd->fsend("T %s\n", fo->writer);
345             }
346             fd->fsend("N\n");
347          }
348
349          for (j=0; j<ie->name_list.size(); j++) {
350             item = (char *)ie->name_list.get(j);
351             if (!send_list_item(jcr, "F ", item, fd)) {
352                goto bail_out;
353             }
354          }
355          fd->fsend("N\n");
356          for (j=0; j<ie->plugin_list.size(); j++) {
357             item = (char *)ie->plugin_list.get(j);
358             if (!send_list_item(jcr, "P ", item, fd)) {
359                goto bail_out;
360             }
361          }
362          fd->fsend("N\n");
363       }
364       if (!include) {                 /* If we just did excludes */
365          break;                       /*   all done */
366       }
367       include = false;                /* Now do excludes */
368    }
369
370    fd->signal(BNET_EOD);              /* end of data */
371    if (!response(jcr, fd, OKinc, "Include", DISPLAY_ERROR)) {
372       goto bail_out;
373    }
374    return true;
375
376 bail_out:
377    set_jcr_job_status(jcr, JS_ErrorTerminated);
378    return false;
379
380 }
381
382 static bool send_list_item(JCR *jcr, const char *code, char *item, BSOCK *fd)
383 {
384    BPIPE *bpipe;
385    FILE *ffd;
386    char buf[2000];
387    int optlen, stat;
388    char *p = item;
389
390    switch (*p) {
391    case '|':
392       p++;                      /* skip over the | */
393       fd->msg = edit_job_codes(jcr, fd->msg, p, "");
394       bpipe = open_bpipe(fd->msg, 0, "r");
395       if (!bpipe) {
396          berrno be;
397          Jmsg(jcr, M_FATAL, 0, _("Cannot run program: %s. ERR=%s\n"),
398             p, be.bstrerror());
399          return false;
400       }
401       bstrncpy(buf, code, sizeof(buf));
402       Dmsg1(500, "code=%s\n", buf);
403       optlen = strlen(buf);
404       while (fgets(buf+optlen, sizeof(buf)-optlen, bpipe->rfd)) {
405          fd->msglen = Mmsg(fd->msg, "%s", buf);
406          Dmsg2(500, "Inc/exc len=%d: %s", fd->msglen, fd->msg);
407          if (!bnet_send(fd)) {
408             Jmsg(jcr, M_FATAL, 0, _(">filed: write error on socket\n"));
409             return false;
410          }
411       }
412       if ((stat=close_bpipe(bpipe)) != 0) {
413          berrno be;
414          Jmsg(jcr, M_FATAL, 0, _("Error running program: %s. ERR=%s\n"),
415             p, be.bstrerror(stat));
416          return false;
417       }
418       break;
419    case '<':
420       p++;                      /* skip over < */
421       if ((ffd = fopen(p, "rb")) == NULL) {
422          berrno be;
423          Jmsg(jcr, M_FATAL, 0, _("Cannot open included file: %s. ERR=%s\n"),
424             p, be.bstrerror());
425          return false;
426       }
427       bstrncpy(buf, code, sizeof(buf));
428       Dmsg1(500, "code=%s\n", buf);
429       optlen = strlen(buf);
430       while (fgets(buf+optlen, sizeof(buf)-optlen, ffd)) {
431          fd->msglen = Mmsg(fd->msg, "%s", buf);
432          if (!bnet_send(fd)) {
433             Jmsg(jcr, M_FATAL, 0, _(">filed: write error on socket\n"));
434             return false;
435          }
436       }
437       fclose(ffd);
438       break;
439    case '\\':
440       p++;                      /* skip over \ */
441       /* Note, fall through wanted */
442    default:
443       pm_strcpy(fd->msg, code);
444       fd->msglen = pm_strcat(fd->msg, p);
445       Dmsg1(500, "Inc/Exc name=%s\n", fd->msg);
446       if (!fd->send()) {
447          Jmsg(jcr, M_FATAL, 0, _(">filed: write error on socket\n"));
448          return false;
449       }
450       break;
451    }
452    return true;
453 }            
454
455
456 /*
457  * Send include list to File daemon
458  */
459 bool send_include_list(JCR *jcr)
460 {
461    BSOCK *fd = jcr->file_bsock;
462    if (jcr->fileset->new_include) {
463       fd->fsend(filesetcmd, jcr->fileset->enable_vss ? " vss=1" : "");
464       return send_fileset(jcr);
465    }
466    return true;
467 }
468
469
470 /*
471  * Send exclude list to File daemon
472  *   Under the new scheme, the Exclude list
473  *   is part of the FileSet sent with the
474  *   "include_list" above.
475  */
476 bool send_exclude_list(JCR *jcr)
477 {
478    return true;
479 }
480
481
482 /*
483  * Send bootstrap file if any to the socket given (FD or SD).
484  *  This is used for restore, verify VolumeToCatalog, and
485  *  for migration.
486  */
487 bool send_bootstrap_file(JCR *jcr, BSOCK *sock)
488 {
489    FILE *bs;
490    char buf[1000];
491    const char *bootstrap = "bootstrap\n";
492
493    Dmsg1(400, "send_bootstrap_file: %s\n", jcr->RestoreBootstrap);
494    if (!jcr->RestoreBootstrap) {
495       return true;
496    }
497    bs = fopen(jcr->RestoreBootstrap, "rb");
498    if (!bs) {
499       berrno be;
500       Jmsg(jcr, M_FATAL, 0, _("Could not open bootstrap file %s: ERR=%s\n"),
501          jcr->RestoreBootstrap, be.bstrerror());
502       set_jcr_job_status(jcr, JS_ErrorTerminated);
503       return false;
504    }
505    sock->fsend(bootstrap);
506    while (fgets(buf, sizeof(buf), bs)) {
507       sock->fsend("%s", buf);
508    }
509    sock->signal(BNET_EOD);
510    fclose(bs);
511    if (jcr->unlink_bsr) {
512       unlink(jcr->RestoreBootstrap);
513       jcr->unlink_bsr = false;
514    }                         
515    return true;
516 }
517
518 /* TODO: drop this with runscript.old_proto in bacula 1.42 */
519 static char runbefore[]   = "RunBeforeJob %s\n";
520 static char runafter[]    = "RunAfterJob %s\n";
521 static char OKRunBefore[] = "2000 OK RunBefore\n";
522 static char OKRunAfter[]  = "2000 OK RunAfter\n";
523
524 int send_runscript_with_old_proto(JCR *jcr, int when, POOLMEM *msg)
525 {
526    int ret;
527    Dmsg1(120, "bdird: sending old runcommand to fd '%s'\n",msg);
528    if (when & SCRIPT_Before) {
529       bnet_fsend(jcr->file_bsock, runbefore, msg);
530       ret = response(jcr, jcr->file_bsock, OKRunBefore, "ClientRunBeforeJob", DISPLAY_ERROR);
531    } else {
532       bnet_fsend(jcr->file_bsock, runafter, msg);
533       ret = response(jcr, jcr->file_bsock, OKRunAfter, "ClientRunAfterJob", DISPLAY_ERROR);
534    }
535    return ret;
536 } /* END OF TODO */
537
538 /*
539  * Send RunScripts to File daemon
540  * 1) We send all runscript to FD, they can be executed Before, After, or twice
541  * 2) Then, we send a "RunBeforeNow" command to the FD to tell him to do the
542  *    first run_script() call. (ie ClientRunBeforeJob)
543  */
544 int send_runscripts_commands(JCR *jcr)
545 {
546    POOLMEM *msg = get_pool_memory(PM_FNAME);
547    BSOCK *fd = jcr->file_bsock;
548    RUNSCRIPT *cmd;
549    bool launch_before_cmd = false;
550    POOLMEM *ehost = get_pool_memory(PM_FNAME);
551    int result;
552
553    Dmsg0(120, "bdird: sending runscripts to fd\n");
554    
555    foreach_alist(cmd, jcr->job->RunScripts) {
556       if (cmd->can_run_at_level(jcr->JobLevel) && cmd->target) {
557          ehost = edit_job_codes(jcr, ehost, cmd->target, "");
558          Dmsg2(200, "bdird: runscript %s -> %s\n", cmd->target, ehost);
559
560          if (strcmp(ehost, jcr->client->name()) == 0) {
561             pm_strcpy(msg, cmd->command);
562             bash_spaces(msg);
563
564             Dmsg1(120, "bdird: sending runscripts to fd '%s'\n", cmd->command);
565             
566             /* TODO: remove this with bacula 1.42 */
567             if (cmd->old_proto) {
568                result = send_runscript_with_old_proto(jcr, cmd->when, msg);
569
570             } else {
571                fd->fsend(runscript, cmd->on_success, 
572                                     cmd->on_failure,
573                                     cmd->fail_on_error,
574                                     cmd->when,
575                                     msg);
576
577                result = response(jcr, fd, OKRunScript, "RunScript", DISPLAY_ERROR);
578                launch_before_cmd = true;
579             }
580             
581             if (!result) {
582                goto bail_out;
583             }
584          }
585          /* TODO : we have to play with other client */
586          /*
587            else {
588            send command to an other client
589            }
590          */
591       }        
592    } 
593
594    /* Tell the FD to execute the ClientRunBeforeJob */
595    if (launch_before_cmd) {
596       fd->fsend(runbeforenow);
597       if (!response(jcr, fd, OKRunBeforeNow, "RunBeforeNow", DISPLAY_ERROR)) {
598         goto bail_out;
599       }
600    }
601    free_pool_memory(msg);
602    free_pool_memory(ehost);
603    return 1;
604
605 bail_out:
606    Jmsg(jcr, M_FATAL, 0, _("Client \"%s\" RunScript failed.\n"), ehost);
607    free_pool_memory(msg);
608    free_pool_memory(ehost);
609    return 0;
610 }
611
612
613
614 /*
615  * Read the attributes from the File daemon for
616  * a Verify job and store them in the catalog.
617  */
618 int get_attributes_and_put_in_catalog(JCR *jcr)
619 {
620    BSOCK   *fd;
621    int n = 0;
622    ATTR_DBR *ar = NULL;
623    char digest[MAXSTRING];
624
625    fd = jcr->file_bsock;
626    jcr->jr.FirstIndex = 1;
627    jcr->FileIndex = 0;
628    /* Start transaction allocates jcr->attr and jcr->ar if needed */
629    db_start_transaction(jcr, jcr->db);     /* start transaction if not already open */
630    ar = jcr->ar;
631
632    Dmsg0(120, "bdird: waiting to receive file attributes\n");
633    /* Pickup file attributes and digest */
634    while (!fd->errors && (n = bget_dirmsg(fd)) > 0) {
635       uint32_t file_index;
636       int stream, len;
637       char *p, *fn;
638       char Digest[MAXSTRING];      /* either Verify opts or MD5/SHA1 digest */
639
640       jcr->fname = check_pool_memory_size(jcr->fname, fd->msglen);
641       if ((len = sscanf(fd->msg, "%ld %d %s", &file_index, &stream, Digest)) != 3) {
642          Jmsg(jcr, M_FATAL, 0, _("<filed: bad attributes, expected 3 fields got %d\n"
643 "msglen=%d msg=%s\n"), len, fd->msglen, fd->msg);
644          set_jcr_job_status(jcr, JS_ErrorTerminated);
645          return 0;
646       }
647       p = fd->msg;
648       /* The following three fields were sscanf'ed above so skip them */
649       skip_nonspaces(&p);             /* skip FileIndex */
650       skip_spaces(&p);
651       skip_nonspaces(&p);             /* skip Stream */
652       skip_spaces(&p);
653       skip_nonspaces(&p);             /* skip Opts_Digest */
654       p++;                            /* skip space */
655       Dmsg1(dbglvl, "Stream=%d\n", stream);
656       if (stream == STREAM_UNIX_ATTRIBUTES || stream == STREAM_UNIX_ATTRIBUTES_EX) {
657          if (jcr->cached_attribute) {
658             Dmsg3(dbglvl, "Cached attr. Stream=%d fname=%s\n", ar->Stream, ar->fname,
659                ar->attr);
660             if (!db_create_file_attributes_record(jcr, jcr->db, ar)) {
661                Jmsg1(jcr, M_FATAL, 0, _("Attribute create error. %s"), db_strerror(jcr->db));
662             }
663          }
664          /* Any cached attr is flushed so we can reuse jcr->attr and jcr->ar */
665          fn = jcr->fname;
666          while (*p != 0) {
667             *fn++ = *p++;                /* copy filename */
668          }
669          *fn = *p++;                     /* term filename and point p to attribs */
670          pm_strcpy(jcr->attr, p);        /* save attributes */
671          jcr->JobFiles++;
672          jcr->FileIndex = file_index;
673          ar->attr = jcr->attr;
674          ar->fname = jcr->fname;
675          ar->FileIndex = file_index;
676          ar->Stream = stream;
677          ar->link = NULL;
678          ar->JobId = jcr->JobId;
679          ar->ClientId = jcr->ClientId;
680          ar->PathId = 0;
681          ar->FilenameId = 0;
682          ar->Digest = NULL;
683          ar->DigestType = CRYPTO_DIGEST_NONE;
684          jcr->cached_attribute = true;
685
686          Dmsg2(dbglvl, "dird<filed: stream=%d %s\n", stream, jcr->fname);
687          Dmsg1(dbglvl, "dird<filed: attr=%s\n", ar->attr);
688          jcr->FileId = ar->FileId;
689       /*
690        * First, get STREAM_UNIX_ATTRIBUTES and fill ATTR_DBR structure
691        * Next, we CAN have a CRYPTO_DIGEST, so we fill ATTR_DBR with it (or not)
692        * When we get a new STREAM_UNIX_ATTRIBUTES, we known that we can add file to the catalog
693        * At the end, we have to add the last file
694        */
695       } else if (crypto_digest_stream_type(stream) != CRYPTO_DIGEST_NONE) {
696          if (jcr->FileIndex != (uint32_t)file_index) {
697             Jmsg3(jcr, M_ERROR, 0, _("%s index %d not same as attributes %d\n"),
698                stream_to_ascii(stream), file_index, jcr->FileIndex);
699             continue;
700          }
701          ar->Digest = digest;
702          ar->DigestType = crypto_digest_stream_type(stream);
703          db_escape_string(jcr, jcr->db, digest, Digest, strlen(Digest));
704          Dmsg4(dbglvl, "stream=%d DigestLen=%d Digest=%s type=%d\n", stream,
705                strlen(digest), digest, ar->DigestType);
706       }
707       jcr->jr.JobFiles = jcr->JobFiles = file_index;
708       jcr->jr.LastIndex = file_index;
709    }
710    if (is_bnet_error(fd)) {
711       Jmsg1(jcr, M_FATAL, 0, _("<filed: Network error getting attributes. ERR=%s\n"),
712             fd->bstrerror());
713       return 0;
714    }
715    if (jcr->cached_attribute) {
716       Dmsg3(dbglvl, "Cached attr with digest. Stream=%d fname=%s attr=%s\n", ar->Stream,            
717          ar->fname, ar->attr);
718       if (!db_create_file_attributes_record(jcr, jcr->db, ar)) {
719          Jmsg1(jcr, M_FATAL, 0, _("Attribute create error. %s"), db_strerror(jcr->db));
720       }
721       jcr->cached_attribute = false; 
722    }
723    set_jcr_job_status(jcr, JS_Terminated);
724    return 1;
725 }