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