]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/dird/fd_cmds.c
ebl add comments
[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
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  * 1) We send all runscript to FD, they can be executed Before, After, or twice
509  * 2) Then, we send a "RunBeforeNow" command to the FD to tell him to do the
510  *    first run_script() call. (ie ClientRunBeforeJob)
511  */
512 int send_runscripts_commands(JCR *jcr)
513 {
514    POOLMEM *msg = get_pool_memory(PM_FNAME);
515    BSOCK *fd = jcr->file_bsock;
516    RUNSCRIPT *cmd;
517    bool launch_before_cmd = false;
518    POOLMEM *ehost = get_pool_memory(PM_FNAME);
519    int result;
520
521    Dmsg0(120, "bdird: sending runscripts to fd\n");
522    
523    foreach_alist(cmd, jcr->job->RunScripts) {
524       
525       if (cmd->can_run_at_level(jcr->JobLevel) && cmd->target) {
526
527          ehost = edit_job_codes(jcr, ehost, cmd->target, "");
528          Dmsg2(200, "bdird: runscript %s -> %s\n", cmd->target, ehost);
529
530          if (strcmp(ehost, jcr->client->hdr.name) == 0) {
531             pm_strcpy(msg, cmd->command);
532             bash_spaces(msg);
533
534             Dmsg1(120, "bdird: sending runscripts to fd '%s'\n", cmd->command);
535             
536             /* TODO: remove this with bacula 1.42 */
537             if (cmd->old_proto) {
538                result = send_runscript_with_old_proto(jcr, cmd->when, msg);
539
540             } else {
541                bnet_fsend(fd, runscript, cmd->on_success, 
542                                          cmd->on_failure,
543                                          cmd->abort_on_error,
544                                          cmd->when,
545                                          msg);
546
547                result = response(jcr, fd, OKRunScript, "RunScript", DISPLAY_ERROR);
548                launch_before_cmd=true;
549             }
550             
551             if (!result) {
552                set_jcr_job_status(jcr, JS_ErrorTerminated);
553                free_pool_memory(msg);
554                free_pool_memory(ehost);
555                return 0;
556             }
557          }
558          /* TODO : we have to play with other client */
559          /*
560            else {
561            send command to an other client
562            }
563          */
564       }        
565    } 
566
567    /* We tell to the FD that i can execute commands (ie ClientRunBeforeJob) */
568    if (launch_before_cmd) {
569       bnet_fsend(fd, runbeforenow);
570       if (!response(jcr, fd, OKRunBeforeNow, "RunBeforeNow", DISPLAY_ERROR)) {
571         set_jcr_job_status(jcr, JS_ErrorTerminated);
572         free_pool_memory(msg);
573         free_pool_memory(ehost);
574         return 0;
575       }
576    }
577    free_pool_memory(msg);
578    free_pool_memory(ehost);
579    return 1;
580 }
581
582
583 /*
584  * Read the attributes from the File daemon for
585  * a Verify job and store them in the catalog.
586  */
587 int get_attributes_and_put_in_catalog(JCR *jcr)
588 {
589    BSOCK   *fd;
590    int n = 0;
591    ATTR_DBR ar;
592
593    fd = jcr->file_bsock;
594    jcr->jr.FirstIndex = 1;
595    memset(&ar, 0, sizeof(ar));
596    jcr->FileIndex = 0;
597
598    Dmsg0(120, "bdird: waiting to receive file attributes\n");
599    /* Pickup file attributes and digest */
600    while (!fd->errors && (n = bget_dirmsg(fd)) > 0) {
601
602    /*****FIXME****** improve error handling to stop only on
603     * really fatal problems, or the number of errors is too
604     * large.
605     */
606       long file_index;
607       int stream, len;
608       char *attr, *p, *fn;
609       char Opts_Digest[MAXSTRING];      /* either Verify opts or MD5/SHA1 digest */
610       char digest[CRYPTO_DIGEST_MAX_SIZE];
611
612       jcr->fname = check_pool_memory_size(jcr->fname, fd->msglen);
613       if ((len = sscanf(fd->msg, "%ld %d %s", &file_index, &stream, Opts_Digest)) != 3) {
614          Jmsg(jcr, M_FATAL, 0, _("<filed: bad attributes, expected 3 fields got %d\n"
615 "msglen=%d msg=%s\n"), len, fd->msglen, fd->msg);
616          set_jcr_job_status(jcr, JS_ErrorTerminated);
617          return 0;
618       }
619       p = fd->msg;
620       skip_nonspaces(&p);             /* skip FileIndex */
621       skip_spaces(&p);
622       skip_nonspaces(&p);             /* skip Stream */
623       skip_spaces(&p);
624       skip_nonspaces(&p);             /* skip Opts_SHA1 */
625       p++;                            /* skip space */
626       fn = jcr->fname;
627       while (*p != 0) {
628          *fn++ = *p++;                /* copy filename */
629       }
630       *fn = *p++;                     /* term filename and point to attribs */
631       attr = p;
632
633       if (stream == STREAM_UNIX_ATTRIBUTES || stream == STREAM_UNIX_ATTRIBUTES_EX) {
634          jcr->JobFiles++;
635          jcr->FileIndex = file_index;
636          ar.attr = attr;
637          ar.fname = jcr->fname;
638          ar.FileIndex = file_index;
639          ar.Stream = stream;
640          ar.link = NULL;
641          ar.JobId = jcr->JobId;
642          ar.ClientId = jcr->ClientId;
643          ar.PathId = 0;
644          ar.FilenameId = 0;
645          ar.Digest = NULL;
646          ar.DigestType = CRYPTO_DIGEST_NONE;
647
648          Dmsg2(111, "dird<filed: stream=%d %s\n", stream, jcr->fname);
649          Dmsg1(120, "dird<filed: attr=%s\n", attr);
650
651          if (!db_create_file_attributes_record(jcr, jcr->db, &ar)) {
652             Jmsg1(jcr, M_ERROR, 0, "%s", db_strerror(jcr->db));
653             set_jcr_job_status(jcr, JS_Error);
654             continue;
655          }
656          jcr->FileId = ar.FileId;
657       } else if (crypto_digest_stream_type(stream) != CRYPTO_DIGEST_NONE) {
658          if (jcr->FileIndex != (uint32_t)file_index) {
659             Jmsg3(jcr, M_ERROR, 0, _("%s index %d not same as attributes %d\n"),
660                stream_to_ascii(stream), file_index, jcr->FileIndex);
661             set_jcr_job_status(jcr, JS_Error);
662             continue;
663          }
664          db_escape_string(digest, Opts_Digest, strlen(Opts_Digest));
665          Dmsg2(120, "DigestLen=%d Digest=%s\n", strlen(digest), digest);
666          if (!db_add_digest_to_file_record(jcr, jcr->db, jcr->FileId, digest,
667                    crypto_digest_stream_type(stream))) {
668             Jmsg1(jcr, M_ERROR, 0, "%s", db_strerror(jcr->db));
669             set_jcr_job_status(jcr, JS_Error);
670          }
671       }
672       jcr->jr.JobFiles = jcr->JobFiles = file_index;
673       jcr->jr.LastIndex = file_index;
674    }
675    if (is_bnet_error(fd)) {
676       Jmsg1(jcr, M_FATAL, 0, _("<filed: Network error getting attributes. ERR=%s\n"),
677                         bnet_strerror(fd));
678       set_jcr_job_status(jcr, JS_ErrorTerminated);
679       return 0;
680    }
681
682    set_jcr_job_status(jcr, JS_Terminated);
683    return 1;
684 }