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