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