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