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