]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/dird/fd_cmds.c
Apply Max Full Age patch submitted by Scott Bailey
[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    bool FullOk;
165    utime_t now, LastFull;
166
167    since[0] = 0;
168    /* If job cloned and a since time already given, use it */
169    if (jcr->cloned && jcr->stime && jcr->stime[0]) {
170       bstrncpy(since, _(", since="), since_len);
171       bstrncat(since, jcr->stime, since_len);
172       return;
173    }
174    /* Make sure stime buffer is allocated */
175    if (!jcr->stime) {
176       jcr->stime = get_pool_memory(PM_MESSAGE);
177    } 
178    jcr->stime[0] = 0;
179    /* Lookup the last FULL backup job to get the time/date for a
180     * differential or incremental save.
181     */
182    switch (jcr->JobLevel) {
183    case L_DIFFERENTIAL:
184    case L_INCREMENTAL:
185       /* Look up start time of last job */
186       jcr->jr.JobId = 0;     /* flag for db_find_job_start time */
187       FullOk = db_find_job_start_time(jcr, jcr->db, &jcr->jr, &jcr->stime);
188       /* If there was a successful job, make sure it is recent enough */
189       if (FullOk && jcr->job->MaxFullAge > 0) {
190          now = btime_to_utime(get_current_btime());
191          LastFull = str_to_utime(jcr->stime);
192          FullOk = ((now - LastFull) < jcr->job->MaxFullAge);
193       }
194       if (!FullOk) {
195          /* No recent job found, so upgrade this one to Full */
196          Jmsg(jcr, M_INFO, 0, "%s", db_strerror(jcr->db));
197          Jmsg(jcr, M_INFO, 0, _("No prior or suitable Full backup found in catalog. Doing FULL backup.\n"));
198          bsnprintf(since, since_len, _(" (upgraded from %s)"),
199             level_to_str(jcr->JobLevel));
200          jcr->JobLevel = jcr->jr.JobLevel = L_FULL;
201       } else {
202          if (jcr->job->rerun_failed_levels) {
203             if (db_find_failed_job_since(jcr, jcr->db, &jcr->jr, jcr->stime, JobLevel)) {
204                Jmsg(jcr, M_INFO, 0, _("Prior failed job found in catalog. Upgrading to %s.\n"),
205                   level_to_str(JobLevel));
206                bsnprintf(since, since_len, _(" (upgraded from %s)"),
207                   level_to_str(jcr->JobLevel));
208                jcr->JobLevel = jcr->jr.JobLevel = JobLevel;
209                jcr->jr.JobId = jcr->JobId;
210                break;
211             }
212          }
213          bstrncpy(since, _(", since="), since_len);
214          bstrncat(since, jcr->stime, since_len);
215       }
216       jcr->jr.JobId = jcr->JobId;
217       break;
218    }
219    Dmsg2(100, "Level=%c last start time=%s\n", jcr->JobLevel, jcr->stime);
220 }
221
222 static void send_since_time(JCR *jcr)
223 {
224    BSOCK   *fd = jcr->file_bsock;
225    utime_t stime;
226    char ed1[50];
227
228    stime = str_to_utime(jcr->stime);
229    fd->fsend(levelcmd, NT_("since_utime "), edit_uint64(stime, ed1), 0);
230    while (bget_dirmsg(fd) >= 0) {  /* allow him to poll us to sync clocks */
231       Jmsg(jcr, M_INFO, 0, "%s\n", fd->msg);
232    }
233 }
234
235
236 /*
237  * Send level command to FD.
238  * Used for backup jobs and estimate command.
239  */
240 bool send_level_command(JCR *jcr)
241 {
242    BSOCK   *fd = jcr->file_bsock;
243    /*
244     * Send Level command to File daemon
245     */
246    switch (jcr->JobLevel) {
247    case L_BASE:
248       fd->fsend(levelcmd, "base", " ", 0);
249       break;
250    /* L_NONE is the console, sending something off to the FD */
251    case L_NONE:
252    case L_FULL:
253       fd->fsend(levelcmd, "full", " ", 0);
254       break;
255    case L_DIFFERENTIAL:
256       fd->fsend(levelcmd, "differential", " ", 0);
257       send_since_time(jcr);
258       break;
259    case L_INCREMENTAL:
260       fd->fsend(levelcmd, "incremental", " ", 0);
261       send_since_time(jcr);
262       break;
263    case L_SINCE:
264    default:
265       Jmsg2(jcr, M_FATAL, 0, _("Unimplemented backup level %d %c\n"),
266          jcr->JobLevel, jcr->JobLevel);
267       return 0;
268    }
269    Dmsg1(120, ">filed: %s", fd->msg);
270    if (!response(jcr, fd, OKlevel, "Level", DISPLAY_ERROR)) {
271       return 0;
272    }
273    return 1;
274 }
275
276 /*
277  * Send either an Included or an Excluded list to FD
278  */
279 static bool send_fileset(JCR *jcr)
280 {
281    FILESET *fileset = jcr->fileset;
282    BSOCK   *fd = jcr->file_bsock;
283    int num;
284    bool include = true;
285
286    for ( ;; ) {
287       if (include) {
288          num = fileset->num_includes;
289       } else {
290          num = fileset->num_excludes;
291       }
292       for (int i=0; i<num; i++) {
293          char *item;
294          INCEXE *ie;
295          int j, k;
296
297          if (include) {
298             ie = fileset->include_items[i];
299             fd->fsend("I\n");
300          } else {
301             ie = fileset->exclude_items[i];
302             fd->fsend("E\n");
303          }
304          for (j=0; j<ie->num_opts; j++) {
305             FOPTS *fo = ie->opts_list[j];
306             fd->fsend("O %s\n", fo->opts);
307
308             bool enhanced_wild = false;
309             for (k=0; fo->opts[k]!='\0'; k++) {
310                if (fo->opts[k]=='W') {
311                   enhanced_wild = true;
312                   break;
313                }
314             }
315
316             for (k=0; k<fo->regex.size(); k++) {
317                fd->fsend("R %s\n", fo->regex.get(k));
318             }
319             for (k=0; k<fo->regexdir.size(); k++) {
320                fd->fsend("RD %s\n", fo->regexdir.get(k));
321             }
322             for (k=0; k<fo->regexfile.size(); k++) {
323                fd->fsend("RF %s\n", fo->regexfile.get(k));
324             }
325             for (k=0; k<fo->wild.size(); k++) {
326                fd->fsend("W %s\n", fo->wild.get(k));
327             }
328             for (k=0; k<fo->wilddir.size(); k++) {
329                fd->fsend("WD %s\n", fo->wilddir.get(k));
330             }
331             for (k=0; k<fo->wildfile.size(); k++) {
332                fd->fsend("WF %s\n", fo->wildfile.get(k));
333             }
334             for (k=0; k<fo->wildbase.size(); k++) {
335                fd->fsend("W%c %s\n", enhanced_wild ? 'B' : 'F', fo->wildbase.get(k));
336             }
337             for (k=0; k<fo->base.size(); k++) {
338                fd->fsend("B %s\n", fo->base.get(k));
339             }
340             for (k=0; k<fo->fstype.size(); k++) {
341                fd->fsend("X %s\n", fo->fstype.get(k));
342             }
343             for (k=0; k<fo->drivetype.size(); k++) {
344                fd->fsend("XD %s\n", fo->drivetype.get(k));
345             }
346             if (fo->plugin) {
347                fd->fsend("G %s\n", fo->plugin);
348             }
349             if (fo->reader) {
350                fd->fsend("D %s\n", fo->reader);
351             }
352             if (fo->writer) {
353                fd->fsend("T %s\n", fo->writer);
354             }
355             fd->fsend("N\n");
356          }
357
358          for (j=0; j<ie->name_list.size(); j++) {
359             item = (char *)ie->name_list.get(j);
360             if (!send_list_item(jcr, "F ", item, fd)) {
361                goto bail_out;
362             }
363          }
364          fd->fsend("N\n");
365          for (j=0; j<ie->plugin_list.size(); j++) {
366             item = (char *)ie->plugin_list.get(j);
367             if (!send_list_item(jcr, "P ", item, fd)) {
368                goto bail_out;
369             }
370          }
371          fd->fsend("N\n");
372       }
373       if (!include) {                 /* If we just did excludes */
374          break;                       /*   all done */
375       }
376       include = false;                /* Now do excludes */
377    }
378
379    fd->signal(BNET_EOD);              /* end of data */
380    if (!response(jcr, fd, OKinc, "Include", DISPLAY_ERROR)) {
381       goto bail_out;
382    }
383    return true;
384
385 bail_out:
386    set_jcr_job_status(jcr, JS_ErrorTerminated);
387    return false;
388
389 }
390
391 static bool send_list_item(JCR *jcr, const char *code, char *item, BSOCK *fd)
392 {
393    BPIPE *bpipe;
394    FILE *ffd;
395    char buf[2000];
396    int optlen, stat;
397    char *p = item;
398
399    switch (*p) {
400    case '|':
401       p++;                      /* skip over the | */
402       fd->msg = edit_job_codes(jcr, fd->msg, p, "");
403       bpipe = open_bpipe(fd->msg, 0, "r");
404       if (!bpipe) {
405          berrno be;
406          Jmsg(jcr, M_FATAL, 0, _("Cannot run program: %s. ERR=%s\n"),
407             p, be.bstrerror());
408          return false;
409       }
410       bstrncpy(buf, code, sizeof(buf));
411       Dmsg1(500, "code=%s\n", buf);
412       optlen = strlen(buf);
413       while (fgets(buf+optlen, sizeof(buf)-optlen, bpipe->rfd)) {
414          fd->msglen = Mmsg(fd->msg, "%s", buf);
415          Dmsg2(500, "Inc/exc len=%d: %s", fd->msglen, fd->msg);
416          if (!bnet_send(fd)) {
417             Jmsg(jcr, M_FATAL, 0, _(">filed: write error on socket\n"));
418             return false;
419          }
420       }
421       if ((stat=close_bpipe(bpipe)) != 0) {
422          berrno be;
423          Jmsg(jcr, M_FATAL, 0, _("Error running program: %s. ERR=%s\n"),
424             p, be.bstrerror(stat));
425          return false;
426       }
427       break;
428    case '<':
429       p++;                      /* skip over < */
430       if ((ffd = fopen(p, "rb")) == NULL) {
431          berrno be;
432          Jmsg(jcr, M_FATAL, 0, _("Cannot open included file: %s. ERR=%s\n"),
433             p, be.bstrerror());
434          return false;
435       }
436       bstrncpy(buf, code, sizeof(buf));
437       Dmsg1(500, "code=%s\n", buf);
438       optlen = strlen(buf);
439       while (fgets(buf+optlen, sizeof(buf)-optlen, ffd)) {
440          fd->msglen = Mmsg(fd->msg, "%s", buf);
441          if (!bnet_send(fd)) {
442             Jmsg(jcr, M_FATAL, 0, _(">filed: write error on socket\n"));
443             return false;
444          }
445       }
446       fclose(ffd);
447       break;
448    case '\\':
449       p++;                      /* skip over \ */
450       /* Note, fall through wanted */
451    default:
452       pm_strcpy(fd->msg, code);
453       fd->msglen = pm_strcat(fd->msg, p);
454       Dmsg1(500, "Inc/Exc name=%s\n", fd->msg);
455       if (!fd->send()) {
456          Jmsg(jcr, M_FATAL, 0, _(">filed: write error on socket\n"));
457          return false;
458       }
459       break;
460    }
461    return true;
462 }            
463
464
465 /*
466  * Send include list to File daemon
467  */
468 bool send_include_list(JCR *jcr)
469 {
470    BSOCK *fd = jcr->file_bsock;
471    if (jcr->fileset->new_include) {
472       fd->fsend(filesetcmd, jcr->fileset->enable_vss ? " vss=1" : "");
473       return send_fileset(jcr);
474    }
475    return true;
476 }
477
478
479 /*
480  * Send exclude list to File daemon
481  *   Under the new scheme, the Exclude list
482  *   is part of the FileSet sent with the
483  *   "include_list" above.
484  */
485 bool send_exclude_list(JCR *jcr)
486 {
487    return true;
488 }
489
490
491 /*
492  * Send bootstrap file if any to the socket given (FD or SD).
493  *  This is used for restore, verify VolumeToCatalog, and
494  *  for migration.
495  */
496 bool send_bootstrap_file(JCR *jcr, BSOCK *sock)
497 {
498    FILE *bs;
499    char buf[1000];
500    const char *bootstrap = "bootstrap\n";
501
502    Dmsg1(400, "send_bootstrap_file: %s\n", jcr->RestoreBootstrap);
503    if (!jcr->RestoreBootstrap) {
504       return true;
505    }
506    bs = fopen(jcr->RestoreBootstrap, "rb");
507    if (!bs) {
508       berrno be;
509       Jmsg(jcr, M_FATAL, 0, _("Could not open bootstrap file %s: ERR=%s\n"),
510          jcr->RestoreBootstrap, be.bstrerror());
511       set_jcr_job_status(jcr, JS_ErrorTerminated);
512       return false;
513    }
514    sock->fsend(bootstrap);
515    while (fgets(buf, sizeof(buf), bs)) {
516       sock->fsend("%s", buf);
517    }
518    sock->signal(BNET_EOD);
519    fclose(bs);
520    if (jcr->unlink_bsr) {
521       unlink(jcr->RestoreBootstrap);
522       jcr->unlink_bsr = false;
523    }                         
524    return true;
525 }
526
527 /* TODO: drop this with runscript.old_proto in bacula 1.42 */
528 static char runbefore[]   = "RunBeforeJob %s\n";
529 static char runafter[]    = "RunAfterJob %s\n";
530 static char OKRunBefore[] = "2000 OK RunBefore\n";
531 static char OKRunAfter[]  = "2000 OK RunAfter\n";
532
533 int send_runscript_with_old_proto(JCR *jcr, int when, POOLMEM *msg)
534 {
535    int ret;
536    Dmsg1(120, "bdird: sending old runcommand to fd '%s'\n",msg);
537    if (when & SCRIPT_Before) {
538       bnet_fsend(jcr->file_bsock, runbefore, msg);
539       ret = response(jcr, jcr->file_bsock, OKRunBefore, "ClientRunBeforeJob", DISPLAY_ERROR);
540    } else {
541       bnet_fsend(jcr->file_bsock, runafter, msg);
542       ret = response(jcr, jcr->file_bsock, OKRunAfter, "ClientRunAfterJob", DISPLAY_ERROR);
543    }
544    return ret;
545 } /* END OF TODO */
546
547 /*
548  * Send RunScripts to File daemon
549  * 1) We send all runscript to FD, they can be executed Before, After, or twice
550  * 2) Then, we send a "RunBeforeNow" command to the FD to tell him to do the
551  *    first run_script() call. (ie ClientRunBeforeJob)
552  */
553 int send_runscripts_commands(JCR *jcr)
554 {
555    POOLMEM *msg = get_pool_memory(PM_FNAME);
556    BSOCK *fd = jcr->file_bsock;
557    RUNSCRIPT *cmd;
558    bool launch_before_cmd = false;
559    POOLMEM *ehost = get_pool_memory(PM_FNAME);
560    int result;
561
562    Dmsg0(120, "bdird: sending runscripts to fd\n");
563    
564    foreach_alist(cmd, jcr->job->RunScripts) {
565       if (cmd->can_run_at_level(jcr->JobLevel) && cmd->target) {
566          ehost = edit_job_codes(jcr, ehost, cmd->target, "");
567          Dmsg2(200, "bdird: runscript %s -> %s\n", cmd->target, ehost);
568
569          if (strcmp(ehost, jcr->client->name()) == 0) {
570             pm_strcpy(msg, cmd->command);
571             bash_spaces(msg);
572
573             Dmsg1(120, "bdird: sending runscripts to fd '%s'\n", cmd->command);
574             
575             /* TODO: remove this with bacula 1.42 */
576             if (cmd->old_proto) {
577                result = send_runscript_with_old_proto(jcr, cmd->when, msg);
578
579             } else {
580                fd->fsend(runscript, cmd->on_success, 
581                                     cmd->on_failure,
582                                     cmd->fail_on_error,
583                                     cmd->when,
584                                     msg);
585
586                result = response(jcr, fd, OKRunScript, "RunScript", DISPLAY_ERROR);
587                launch_before_cmd = true;
588             }
589             
590             if (!result) {
591                goto bail_out;
592             }
593          }
594          /* TODO : we have to play with other client */
595          /*
596            else {
597            send command to an other client
598            }
599          */
600       }        
601    } 
602
603    /* Tell the FD to execute the ClientRunBeforeJob */
604    if (launch_before_cmd) {
605       fd->fsend(runbeforenow);
606       if (!response(jcr, fd, OKRunBeforeNow, "RunBeforeNow", DISPLAY_ERROR)) {
607         goto bail_out;
608       }
609    }
610    free_pool_memory(msg);
611    free_pool_memory(ehost);
612    return 1;
613
614 bail_out:
615    Jmsg(jcr, M_FATAL, 0, _("Client \"%s\" RunScript failed.\n"), ehost);
616    free_pool_memory(msg);
617    free_pool_memory(ehost);
618    return 0;
619 }
620
621
622
623 /*
624  * Read the attributes from the File daemon for
625  * a Verify job and store them in the catalog.
626  */
627 int get_attributes_and_put_in_catalog(JCR *jcr)
628 {
629    BSOCK   *fd;
630    int n = 0;
631    ATTR_DBR *ar = NULL;
632    char digest[MAXSTRING];
633
634    fd = jcr->file_bsock;
635    jcr->jr.FirstIndex = 1;
636    jcr->FileIndex = 0;
637    /* Start transaction allocates jcr->attr and jcr->ar if needed */
638    db_start_transaction(jcr, jcr->db);     /* start transaction if not already open */
639    ar = jcr->ar;
640
641    Dmsg0(120, "bdird: waiting to receive file attributes\n");
642    /* Pickup file attributes and digest */
643    while (!fd->errors && (n = bget_dirmsg(fd)) > 0) {
644       uint32_t file_index;
645       int stream, len;
646       char *p, *fn;
647       char Digest[MAXSTRING];      /* either Verify opts or MD5/SHA1 digest */
648
649       jcr->fname = check_pool_memory_size(jcr->fname, fd->msglen);
650       if ((len = sscanf(fd->msg, "%ld %d %s", &file_index, &stream, Digest)) != 3) {
651          Jmsg(jcr, M_FATAL, 0, _("<filed: bad attributes, expected 3 fields got %d\n"
652 "msglen=%d msg=%s\n"), len, fd->msglen, fd->msg);
653          set_jcr_job_status(jcr, JS_ErrorTerminated);
654          return 0;
655       }
656       p = fd->msg;
657       /* The following three fields were sscanf'ed above so skip them */
658       skip_nonspaces(&p);             /* skip FileIndex */
659       skip_spaces(&p);
660       skip_nonspaces(&p);             /* skip Stream */
661       skip_spaces(&p);
662       skip_nonspaces(&p);             /* skip Opts_Digest */
663       p++;                            /* skip space */
664       Dmsg1(dbglvl, "Stream=%d\n", stream);
665       if (stream == STREAM_UNIX_ATTRIBUTES || stream == STREAM_UNIX_ATTRIBUTES_EX) {
666          if (jcr->cached_attribute) {
667             Dmsg3(dbglvl, "Cached attr. Stream=%d fname=%s\n", ar->Stream, ar->fname,
668                ar->attr);
669             if (!db_create_file_attributes_record(jcr, jcr->db, ar)) {
670                Jmsg1(jcr, M_FATAL, 0, _("Attribute create error. %s"), db_strerror(jcr->db));
671             }
672          }
673          /* Any cached attr is flushed so we can reuse jcr->attr and jcr->ar */
674          fn = jcr->fname;
675          while (*p != 0) {
676             *fn++ = *p++;                /* copy filename */
677          }
678          *fn = *p++;                     /* term filename and point p to attribs */
679          pm_strcpy(jcr->attr, p);        /* save attributes */
680          jcr->JobFiles++;
681          jcr->FileIndex = file_index;
682          ar->attr = jcr->attr;
683          ar->fname = jcr->fname;
684          ar->FileIndex = file_index;
685          ar->Stream = stream;
686          ar->link = NULL;
687          ar->JobId = jcr->JobId;
688          ar->ClientId = jcr->ClientId;
689          ar->PathId = 0;
690          ar->FilenameId = 0;
691          ar->Digest = NULL;
692          ar->DigestType = CRYPTO_DIGEST_NONE;
693          jcr->cached_attribute = true;
694
695          Dmsg2(dbglvl, "dird<filed: stream=%d %s\n", stream, jcr->fname);
696          Dmsg1(dbglvl, "dird<filed: attr=%s\n", ar->attr);
697          jcr->FileId = ar->FileId;
698       /*
699        * First, get STREAM_UNIX_ATTRIBUTES and fill ATTR_DBR structure
700        * Next, we CAN have a CRYPTO_DIGEST, so we fill ATTR_DBR with it (or not)
701        * When we get a new STREAM_UNIX_ATTRIBUTES, we known that we can add file to the catalog
702        * At the end, we have to add the last file
703        */
704       } else if (crypto_digest_stream_type(stream) != CRYPTO_DIGEST_NONE) {
705          if (jcr->FileIndex != (uint32_t)file_index) {
706             Jmsg3(jcr, M_ERROR, 0, _("%s index %d not same as attributes %d\n"),
707                stream_to_ascii(stream), file_index, jcr->FileIndex);
708             continue;
709          }
710          ar->Digest = digest;
711          ar->DigestType = crypto_digest_stream_type(stream);
712          db_escape_string(jcr, jcr->db, digest, Digest, strlen(Digest));
713          Dmsg4(dbglvl, "stream=%d DigestLen=%d Digest=%s type=%d\n", stream,
714                strlen(digest), digest, ar->DigestType);
715       }
716       jcr->jr.JobFiles = jcr->JobFiles = file_index;
717       jcr->jr.LastIndex = file_index;
718    }
719    if (is_bnet_error(fd)) {
720       Jmsg1(jcr, M_FATAL, 0, _("<filed: Network error getting attributes. ERR=%s\n"),
721             fd->bstrerror());
722       return 0;
723    }
724    if (jcr->cached_attribute) {
725       Dmsg3(dbglvl, "Cached attr with digest. Stream=%d fname=%s attr=%s\n", ar->Stream,            
726          ar->fname, ar->attr);
727       if (!db_create_file_attributes_record(jcr, jcr->db, ar)) {
728          Jmsg1(jcr, M_FATAL, 0, _("Attribute create error. %s"), db_strerror(jcr->db));
729       }
730       jcr->cached_attribute = false; 
731    }
732    set_jcr_job_status(jcr, JS_Terminated);
733    return 1;
734 }