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