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