]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/dird/fd_cmds.c
Send bootstrap directly from DIR to SD
[bacula/bacula] / bacula / src / dird / fd_cmds.c
1 /*
2    Bacula® - The Network Backup Solution
3
4    Copyright (C) 2000-2009 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 Kern Sibbald.
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 = new_bsock();
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
98       fd->set_source_address(director->DIRsrc_addr);
99       if (!fd->connect(jcr,retry_interval,max_retry_time, heart_beat, name, jcr->client->address,
100            NULL, jcr->client->FDport, verbose)) {
101         fd->destroy();
102         fd = NULL;
103       }
104
105       if (fd == NULL) {
106          set_jcr_job_status(jcr, JS_ErrorTerminated);
107          return 0;
108       }
109       Dmsg0(10, "Opened connection with File daemon\n");
110    } else {
111       fd = jcr->file_bsock;           /* use existing connection */
112    }
113    fd->res = (RES *)jcr->client;      /* save resource in BSOCK */
114    jcr->file_bsock = fd;
115    set_jcr_job_status(jcr, JS_Running);
116
117    if (!authenticate_file_daemon(jcr)) {
118       set_jcr_job_status(jcr, JS_ErrorTerminated);
119       return 0;
120    }
121
122    /*
123     * Now send JobId and authorization key
124     */
125    fd->fsend(jobcmd, edit_int64(jcr->JobId, ed1), jcr->Job, jcr->VolSessionId,
126       jcr->VolSessionTime, jcr->sd_auth_key);
127    if (strcmp(jcr->sd_auth_key, "dummy") != 0) {
128       memset(jcr->sd_auth_key, 0, strlen(jcr->sd_auth_key));
129    }
130    Dmsg1(100, ">filed: %s", fd->msg);
131    if (bget_dirmsg(fd) > 0) {
132        Dmsg1(110, "<filed: %s", fd->msg);
133        if (strncmp(fd->msg, OKjob, strlen(OKjob)) != 0) {
134           Jmsg(jcr, M_FATAL, 0, _("File daemon \"%s\" rejected Job command: %s\n"),
135              jcr->client->hdr.name, fd->msg);
136           set_jcr_job_status(jcr, JS_ErrorTerminated);
137           return 0;
138        } else if (jcr->db) {
139           CLIENT_DBR cr;
140           memset(&cr, 0, sizeof(cr));
141           bstrncpy(cr.Name, jcr->client->hdr.name, sizeof(cr.Name));
142           cr.AutoPrune = jcr->client->AutoPrune;
143           cr.FileRetention = jcr->client->FileRetention;
144           cr.JobRetention = jcr->client->JobRetention;
145           bstrncpy(cr.Uname, fd->msg+strlen(OKjob)+1, sizeof(cr.Uname));
146           if (!db_update_client_record(jcr, jcr->db, &cr)) {
147              Jmsg(jcr, M_WARNING, 0, _("Error updating Client record. ERR=%s\n"),
148                 db_strerror(jcr->db));
149           }
150        }
151    } else {
152       Jmsg(jcr, M_FATAL, 0, _("FD gave bad response to JobId command: %s\n"),
153          bnet_strerror(fd));
154       set_jcr_job_status(jcr, JS_ErrorTerminated);
155       return 0;
156    }
157    return 1;
158 }
159
160 /*
161  * This subroutine edits the last job start time into a
162  *   "since=date/time" buffer that is returned in the
163  *   variable since.  This is used for display purposes in
164  *   the job report.  The time in jcr->stime is later
165  *   passed to tell the File daemon what to do.
166  */
167 void get_level_since_time(JCR *jcr, char *since, int since_len)
168 {
169    int JobLevel;
170    bool have_full;
171    bool do_full = false;
172    bool do_diff = false;
173    utime_t now;
174    utime_t last_full_time = 0;
175    utime_t last_diff_time;
176
177    since[0] = 0;
178    /* If job cloned and a since time already given, use it */
179    if (jcr->cloned && jcr->stime && jcr->stime[0]) {
180       bstrncpy(since, _(", since="), since_len);
181       bstrncat(since, jcr->stime, since_len);
182       return;
183    }
184    /* Make sure stime buffer is allocated */
185    if (!jcr->stime) {
186       jcr->stime = get_pool_memory(PM_MESSAGE);
187    } 
188    jcr->stime[0] = 0;
189    /*
190     * Lookup the last FULL backup job to get the time/date for a
191     * differential or incremental save.
192     */
193    switch (jcr->get_JobLevel()) {
194    case L_DIFFERENTIAL:
195    case L_INCREMENTAL:
196       POOLMEM *stime = get_pool_memory(PM_MESSAGE);
197       /* Look up start time of last Full job */
198       now = (utime_t)time(NULL);
199       jcr->jr.JobId = 0;     /* flag to return since time */
200       /*
201        * This is probably redundant, but some of the code below
202        * uses jcr->stime, so don't remove unless you are sure.
203        */
204       if (!db_find_job_start_time(jcr, jcr->db, &jcr->jr, &jcr->stime)) {
205          do_full = true;
206       }
207       have_full = db_find_last_job_start_time(jcr, jcr->db, &jcr->jr, &stime, L_FULL);
208       if (have_full) {
209          last_full_time = str_to_utime(stime);
210       } else {
211          do_full = true;               /* No full, upgrade to one */
212       }
213       Dmsg4(50, "have_full=%d do_full=%d now=%lld full_time=%lld\n", have_full, 
214             do_full, now, last_full_time);
215       /* Make sure the last diff is recent enough */
216       if (have_full && jcr->get_JobLevel() == L_INCREMENTAL && jcr->job->MaxDiffInterval > 0) {
217          /* Lookup last diff job */
218          if (db_find_last_job_start_time(jcr, jcr->db, &jcr->jr, &stime, L_DIFFERENTIAL)) {
219             last_diff_time = str_to_utime(stime);
220             /* If no Diff since Full, use Full time */
221             if (last_diff_time < last_full_time) {
222                last_diff_time = last_full_time;
223             }
224             Dmsg2(50, "last_diff_time=%lld last_full_time=%lld\n", last_diff_time,
225                   last_full_time);
226          } else {
227             /* No last differential, so use last full time */
228             last_diff_time = last_full_time;
229             Dmsg1(50, "No last_diff_time setting to full_time=%lld\n", last_full_time);
230          }
231          do_diff = ((now - last_diff_time) >= jcr->job->MaxDiffInterval);
232          Dmsg2(50, "do_diff=%d diffInter=%lld\n", do_diff, jcr->job->MaxDiffInterval);
233       }
234       /* Note, do_full takes precedence over do_diff */
235       if (have_full && jcr->job->MaxFullInterval > 0) {
236          do_full = ((now - last_full_time) >= jcr->job->MaxFullInterval);
237       }
238       free_pool_memory(stime);
239
240       if (do_full) {
241          /* No recent Full job found, so upgrade this one to Full */
242          Jmsg(jcr, M_INFO, 0, "%s", db_strerror(jcr->db));
243          Jmsg(jcr, M_INFO, 0, _("No prior or suitable Full backup found in catalog. Doing FULL backup.\n"));
244          bsnprintf(since, since_len, _(" (upgraded from %s)"),
245             level_to_str(jcr->get_JobLevel()));
246          jcr->set_JobLevel(jcr->jr.JobLevel = L_FULL);
247        } else if (do_diff) {
248          /* No recent diff job found, so upgrade this one to Diff */
249          Jmsg(jcr, M_INFO, 0, _("No prior or suitable Differential backup found in catalog. Doing Differential backup.\n"));
250          bsnprintf(since, since_len, _(" (upgraded from %s)"),
251             level_to_str(jcr->get_JobLevel()));
252          jcr->set_JobLevel(jcr->jr.JobLevel = L_DIFFERENTIAL);
253       } else {
254          if (jcr->job->rerun_failed_levels) {
255             if (db_find_failed_job_since(jcr, jcr->db, &jcr->jr, jcr->stime, JobLevel)) {
256                Jmsg(jcr, M_INFO, 0, _("Prior failed job found in catalog. Upgrading to %s.\n"),
257                   level_to_str(JobLevel));
258                bsnprintf(since, since_len, _(" (upgraded from %s)"),
259                   level_to_str(jcr->get_JobLevel()));
260                jcr->set_JobLevel(jcr->jr.JobLevel = JobLevel);
261                jcr->jr.JobId = jcr->JobId;
262                break;
263             }
264          }
265          bstrncpy(since, _(", since="), since_len);
266          bstrncat(since, jcr->stime, since_len);
267       }
268       jcr->jr.JobId = jcr->JobId;
269       break;
270    }
271    Dmsg2(100, "Level=%c last start time=%s\n", jcr->get_JobLevel(), jcr->stime);
272 }
273
274 static void send_since_time(JCR *jcr)
275 {
276    BSOCK   *fd = jcr->file_bsock;
277    utime_t stime;
278    char ed1[50];
279
280    stime = str_to_utime(jcr->stime);
281    fd->fsend(levelcmd, "", NT_("since_utime "), edit_uint64(stime, ed1), 0);
282    while (bget_dirmsg(fd) >= 0) {  /* allow him to poll us to sync clocks */
283       Jmsg(jcr, M_INFO, 0, "%s\n", fd->msg);
284    }
285 }
286
287 /*
288  * Send level command to FD.
289  * Used for backup jobs and estimate command.
290  */
291 bool send_level_command(JCR *jcr)
292 {
293    BSOCK   *fd = jcr->file_bsock;
294    const char *accurate = jcr->accurate?"accurate_":"";
295    const char *not_accurate = "";
296    /*
297     * Send Level command to File daemon
298     */
299    switch (jcr->get_JobLevel()) {
300    case L_BASE:
301       fd->fsend(levelcmd, not_accurate, "base", " ", 0);
302       break;
303    /* L_NONE is the console, sending something off to the FD */
304    case L_NONE:
305    case L_FULL:
306       fd->fsend(levelcmd, not_accurate, "full", " ", 0);
307       break;
308    case L_DIFFERENTIAL:
309       fd->fsend(levelcmd, accurate, "differential", " ", 0);
310       send_since_time(jcr);
311       break;
312    case L_INCREMENTAL:
313       fd->fsend(levelcmd, accurate, "incremental", " ", 0);
314       send_since_time(jcr);
315       break;
316    case L_SINCE:
317    default:
318       Jmsg2(jcr, M_FATAL, 0, _("Unimplemented backup level %d %c\n"),
319          jcr->get_JobLevel(), jcr->get_JobLevel());
320       return 0;
321    }
322    Dmsg1(120, ">filed: %s", fd->msg);
323    if (!response(jcr, fd, OKlevel, "Level", DISPLAY_ERROR)) {
324       return 0;
325    }
326    return 1;
327 }
328
329 /*
330  * Send either an Included or an Excluded list to FD
331  */
332 static bool send_fileset(JCR *jcr)
333 {
334    FILESET *fileset = jcr->fileset;
335    BSOCK   *fd = jcr->file_bsock;
336    int num;
337    bool include = true;
338
339    for ( ;; ) {
340       if (include) {
341          num = fileset->num_includes;
342       } else {
343          num = fileset->num_excludes;
344       }
345       for (int i=0; i<num; i++) {
346          char *item;
347          INCEXE *ie;
348          int j, k;
349
350          if (include) {
351             ie = fileset->include_items[i];
352             fd->fsend("I\n");
353          } else {
354             ie = fileset->exclude_items[i];
355             fd->fsend("E\n");
356          }
357          for (j=0; j<ie->num_opts; j++) {
358             FOPTS *fo = ie->opts_list[j];
359             fd->fsend("O %s\n", fo->opts);
360
361             bool enhanced_wild = false;
362             for (k=0; fo->opts[k]!='\0'; k++) {
363                if (fo->opts[k]=='W') {
364                   enhanced_wild = true;
365                   break;
366                }
367             }
368
369             for (k=0; k<fo->regex.size(); k++) {
370                fd->fsend("R %s\n", fo->regex.get(k));
371             }
372             for (k=0; k<fo->regexdir.size(); k++) {
373                fd->fsend("RD %s\n", fo->regexdir.get(k));
374             }
375             for (k=0; k<fo->regexfile.size(); k++) {
376                fd->fsend("RF %s\n", fo->regexfile.get(k));
377             }
378             for (k=0; k<fo->wild.size(); k++) {
379                fd->fsend("W %s\n", fo->wild.get(k));
380             }
381             for (k=0; k<fo->wilddir.size(); k++) {
382                fd->fsend("WD %s\n", fo->wilddir.get(k));
383             }
384             for (k=0; k<fo->wildfile.size(); k++) {
385                fd->fsend("WF %s\n", fo->wildfile.get(k));
386             }
387             for (k=0; k<fo->wildbase.size(); k++) {
388                fd->fsend("W%c %s\n", enhanced_wild ? 'B' : 'F', fo->wildbase.get(k));
389             }
390             for (k=0; k<fo->base.size(); k++) {
391                fd->fsend("B %s\n", fo->base.get(k));
392             }
393             for (k=0; k<fo->fstype.size(); k++) {
394                fd->fsend("X %s\n", fo->fstype.get(k));
395             }
396             for (k=0; k<fo->drivetype.size(); k++) {
397                fd->fsend("XD %s\n", fo->drivetype.get(k));
398             }
399             if (fo->plugin) {
400                fd->fsend("G %s\n", fo->plugin);
401             }
402             if (fo->ignoredir) {
403                bnet_fsend(fd, "Z %s\n", fo->ignoredir);
404             }
405             if (fo->reader) {
406                fd->fsend("D %s\n", fo->reader);
407             }
408             if (fo->writer) {
409                fd->fsend("T %s\n", fo->writer);
410             }
411             fd->fsend("N\n");
412          }
413
414          for (j=0; j<ie->name_list.size(); j++) {
415             item = (char *)ie->name_list.get(j);
416             if (!send_list_item(jcr, "F ", item, fd)) {
417                goto bail_out;
418             }
419          }
420          fd->fsend("N\n");
421          for (j=0; j<ie->plugin_list.size(); j++) {
422             item = (char *)ie->plugin_list.get(j);
423             if (!send_list_item(jcr, "P ", item, fd)) {
424                goto bail_out;
425             }
426          }
427          fd->fsend("N\n");
428       }
429       if (!include) {                 /* If we just did excludes */
430          break;                       /*   all done */
431       }
432       include = false;                /* Now do excludes */
433    }
434
435    fd->signal(BNET_EOD);              /* end of data */
436    if (!response(jcr, fd, OKinc, "Include", DISPLAY_ERROR)) {
437       goto bail_out;
438    }
439    return true;
440
441 bail_out:
442    set_jcr_job_status(jcr, JS_ErrorTerminated);
443    return false;
444
445 }
446
447 static bool send_list_item(JCR *jcr, const char *code, char *item, BSOCK *fd)
448 {
449    BPIPE *bpipe;
450    FILE *ffd;
451    char buf[2000];
452    int optlen, stat;
453    char *p = item;
454
455    switch (*p) {
456    case '|':
457       p++;                      /* skip over the | */
458       fd->msg = edit_job_codes(jcr, fd->msg, p, "");
459       bpipe = open_bpipe(fd->msg, 0, "r");
460       if (!bpipe) {
461          berrno be;
462          Jmsg(jcr, M_FATAL, 0, _("Cannot run program: %s. ERR=%s\n"),
463             p, be.bstrerror());
464          return false;
465       }
466       bstrncpy(buf, code, sizeof(buf));
467       Dmsg1(500, "code=%s\n", buf);
468       optlen = strlen(buf);
469       while (fgets(buf+optlen, sizeof(buf)-optlen, bpipe->rfd)) {
470          fd->msglen = Mmsg(fd->msg, "%s", buf);
471          Dmsg2(500, "Inc/exc len=%d: %s", fd->msglen, fd->msg);
472          if (!bnet_send(fd)) {
473             Jmsg(jcr, M_FATAL, 0, _(">filed: write error on socket\n"));
474             return false;
475          }
476       }
477       if ((stat=close_bpipe(bpipe)) != 0) {
478          berrno be;
479          Jmsg(jcr, M_FATAL, 0, _("Error running program: %s. ERR=%s\n"),
480             p, be.bstrerror(stat));
481          return false;
482       }
483       break;
484    case '<':
485       p++;                      /* skip over < */
486       if ((ffd = fopen(p, "rb")) == NULL) {
487          berrno be;
488          Jmsg(jcr, M_FATAL, 0, _("Cannot open included file: %s. ERR=%s\n"),
489             p, be.bstrerror());
490          return false;
491       }
492       bstrncpy(buf, code, sizeof(buf));
493       Dmsg1(500, "code=%s\n", buf);
494       optlen = strlen(buf);
495       while (fgets(buf+optlen, sizeof(buf)-optlen, ffd)) {
496          fd->msglen = Mmsg(fd->msg, "%s", buf);
497          if (!bnet_send(fd)) {
498             Jmsg(jcr, M_FATAL, 0, _(">filed: write error on socket\n"));
499             return false;
500          }
501       }
502       fclose(ffd);
503       break;
504    case '\\':
505       p++;                      /* skip over \ */
506       /* Note, fall through wanted */
507    default:
508       pm_strcpy(fd->msg, code);
509       fd->msglen = pm_strcat(fd->msg, p);
510       Dmsg1(500, "Inc/Exc name=%s\n", fd->msg);
511       if (!fd->send()) {
512          Jmsg(jcr, M_FATAL, 0, _(">filed: write error on socket\n"));
513          return false;
514       }
515       break;
516    }
517    return true;
518 }            
519
520
521 /*
522  * Send include list to File daemon
523  */
524 bool send_include_list(JCR *jcr)
525 {
526    BSOCK *fd = jcr->file_bsock;
527    if (jcr->fileset->new_include) {
528       fd->fsend(filesetcmd, jcr->fileset->enable_vss ? " vss=1" : "");
529       return send_fileset(jcr);
530    }
531    return true;
532 }
533
534
535 /*
536  * Send exclude list to File daemon
537  *   Under the new scheme, the Exclude list
538  *   is part of the FileSet sent with the
539  *   "include_list" above.
540  */
541 bool send_exclude_list(JCR *jcr)
542 {
543    return true;
544 }
545
546 /* TODO: drop this with runscript.old_proto in bacula 1.42 */
547 static char runbefore[]   = "RunBeforeJob %s\n";
548 static char runafter[]    = "RunAfterJob %s\n";
549 static char OKRunBefore[] = "2000 OK RunBefore\n";
550 static char OKRunAfter[]  = "2000 OK RunAfter\n";
551
552 int send_runscript_with_old_proto(JCR *jcr, int when, POOLMEM *msg)
553 {
554    int ret;
555    Dmsg1(120, "bdird: sending old runcommand to fd '%s'\n",msg);
556    if (when & SCRIPT_Before) {
557       bnet_fsend(jcr->file_bsock, runbefore, msg);
558       ret = response(jcr, jcr->file_bsock, OKRunBefore, "ClientRunBeforeJob", DISPLAY_ERROR);
559    } else {
560       bnet_fsend(jcr->file_bsock, runafter, msg);
561       ret = response(jcr, jcr->file_bsock, OKRunAfter, "ClientRunAfterJob", DISPLAY_ERROR);
562    }
563    return ret;
564 } /* END OF TODO */
565
566 /*
567  * Send RunScripts to File daemon
568  * 1) We send all runscript to FD, they can be executed Before, After, or twice
569  * 2) Then, we send a "RunBeforeNow" command to the FD to tell him to do the
570  *    first run_script() call. (ie ClientRunBeforeJob)
571  */
572 int send_runscripts_commands(JCR *jcr)
573 {
574    POOLMEM *msg = get_pool_memory(PM_FNAME);
575    BSOCK *fd = jcr->file_bsock;
576    RUNSCRIPT *cmd;
577    bool launch_before_cmd = false;
578    POOLMEM *ehost = get_pool_memory(PM_FNAME);
579    int result;
580
581    Dmsg0(120, "bdird: sending runscripts to fd\n");
582    
583    foreach_alist(cmd, jcr->job->RunScripts) {
584       if (cmd->can_run_at_level(jcr->get_JobLevel()) && cmd->target) {
585          ehost = edit_job_codes(jcr, ehost, cmd->target, "");
586          Dmsg2(200, "bdird: runscript %s -> %s\n", cmd->target, ehost);
587
588          if (strcmp(ehost, jcr->client->name()) == 0) {
589             pm_strcpy(msg, cmd->command);
590             bash_spaces(msg);
591
592             Dmsg1(120, "bdird: sending runscripts to fd '%s'\n", cmd->command);
593             
594             /* TODO: remove this with bacula 1.42 */
595             if (cmd->old_proto) {
596                result = send_runscript_with_old_proto(jcr, cmd->when, msg);
597
598             } else {
599                fd->fsend(runscript, cmd->on_success, 
600                                     cmd->on_failure,
601                                     cmd->fail_on_error,
602                                     cmd->when,
603                                     msg);
604
605                result = response(jcr, fd, OKRunScript, "RunScript", DISPLAY_ERROR);
606                launch_before_cmd = true;
607             }
608             
609             if (!result) {
610                goto bail_out;
611             }
612          }
613          /* TODO : we have to play with other client */
614          /*
615            else {
616            send command to an other client
617            }
618          */
619       }        
620    } 
621
622    /* Tell the FD to execute the ClientRunBeforeJob */
623    if (launch_before_cmd) {
624       fd->fsend(runbeforenow);
625       if (!response(jcr, fd, OKRunBeforeNow, "RunBeforeNow", DISPLAY_ERROR)) {
626         goto bail_out;
627       }
628    }
629    free_pool_memory(msg);
630    free_pool_memory(ehost);
631    return 1;
632
633 bail_out:
634    Jmsg(jcr, M_FATAL, 0, _("Client \"%s\" RunScript failed.\n"), ehost);
635    free_pool_memory(msg);
636    free_pool_memory(ehost);
637    return 0;
638 }
639
640
641
642 /*
643  * Read the attributes from the File daemon for
644  * a Verify job and store them in the catalog.
645  */
646 int get_attributes_and_put_in_catalog(JCR *jcr)
647 {
648    BSOCK   *fd;
649    int n = 0;
650    ATTR_DBR *ar = NULL;
651    char digest[MAXSTRING];
652
653    fd = jcr->file_bsock;
654    jcr->jr.FirstIndex = 1;
655    jcr->FileIndex = 0;
656    /* Start transaction allocates jcr->attr and jcr->ar if needed */
657    db_start_transaction(jcr, jcr->db);     /* start transaction if not already open */
658    ar = jcr->ar;
659
660    Dmsg0(120, "bdird: waiting to receive file attributes\n");
661    /* Pickup file attributes and digest */
662    while (!fd->errors && (n = bget_dirmsg(fd)) > 0) {
663       uint32_t file_index;
664       int stream, len;
665       char *p, *fn;
666       char Digest[MAXSTRING];      /* either Verify opts or MD5/SHA1 digest */
667
668       if ((len = sscanf(fd->msg, "%ld %d %s", &file_index, &stream, Digest)) != 3) {
669          Jmsg(jcr, M_FATAL, 0, _("<filed: bad attributes, expected 3 fields got %d\n"
670 "msglen=%d msg=%s\n"), len, fd->msglen, fd->msg);
671          set_jcr_job_status(jcr, JS_ErrorTerminated);
672          return 0;
673       }
674       p = fd->msg;
675       /* The following three fields were sscanf'ed above so skip them */
676       skip_nonspaces(&p);             /* skip FileIndex */
677       skip_spaces(&p);
678       skip_nonspaces(&p);             /* skip Stream */
679       skip_spaces(&p);
680       skip_nonspaces(&p);             /* skip Opts_Digest */
681       p++;                            /* skip space */
682       Dmsg1(dbglvl, "Stream=%d\n", stream);
683       if (stream == STREAM_UNIX_ATTRIBUTES || stream == STREAM_UNIX_ATTRIBUTES_EX) {
684          if (jcr->cached_attribute) {
685             Dmsg3(dbglvl, "Cached attr. Stream=%d fname=%s\n", ar->Stream, ar->fname,
686                ar->attr);
687             if (!db_create_file_attributes_record(jcr, jcr->db, ar)) {
688                Jmsg1(jcr, M_FATAL, 0, _("Attribute create error. %s"), db_strerror(jcr->db));
689             }
690          }
691          /* Any cached attr is flushed so we can reuse jcr->attr and jcr->ar */
692          fn = jcr->fname = check_pool_memory_size(jcr->fname, fd->msglen);
693          while (*p != 0) {
694             *fn++ = *p++;                /* copy filename */
695          }
696          *fn = *p++;                     /* term filename and point p to attribs */
697          pm_strcpy(jcr->attr, p);        /* save attributes */
698          jcr->JobFiles++;
699          jcr->FileIndex = file_index;
700          ar->attr = jcr->attr;
701          ar->fname = jcr->fname;
702          ar->FileIndex = file_index;
703          ar->Stream = stream;
704          ar->link = NULL;
705          ar->JobId = jcr->JobId;
706          ar->ClientId = jcr->ClientId;
707          ar->PathId = 0;
708          ar->FilenameId = 0;
709          ar->Digest = NULL;
710          ar->DigestType = CRYPTO_DIGEST_NONE;
711          jcr->cached_attribute = true;
712
713          Dmsg2(dbglvl, "dird<filed: stream=%d %s\n", stream, jcr->fname);
714          Dmsg1(dbglvl, "dird<filed: attr=%s\n", ar->attr);
715          jcr->FileId = ar->FileId;
716       /*
717        * First, get STREAM_UNIX_ATTRIBUTES and fill ATTR_DBR structure
718        * Next, we CAN have a CRYPTO_DIGEST, so we fill ATTR_DBR with it (or not)
719        * When we get a new STREAM_UNIX_ATTRIBUTES, we known that we can add file to the catalog
720        * At the end, we have to add the last file
721        */
722       } else if (crypto_digest_stream_type(stream) != CRYPTO_DIGEST_NONE) {
723          if (jcr->FileIndex != (uint32_t)file_index) {
724             Jmsg3(jcr, M_ERROR, 0, _("%s index %d not same as attributes %d\n"),
725                stream_to_ascii(stream), file_index, jcr->FileIndex);
726             continue;
727          }
728          ar->Digest = digest;
729          ar->DigestType = crypto_digest_stream_type(stream);
730          db_escape_string(jcr, jcr->db, digest, Digest, strlen(Digest));
731          Dmsg4(dbglvl, "stream=%d DigestLen=%d Digest=%s type=%d\n", stream,
732                strlen(digest), digest, ar->DigestType);
733       }
734       jcr->jr.JobFiles = jcr->JobFiles = file_index;
735       jcr->jr.LastIndex = file_index;
736    }
737    if (is_bnet_error(fd)) {
738       Jmsg1(jcr, M_FATAL, 0, _("<filed: Network error getting attributes. ERR=%s\n"),
739             fd->bstrerror());
740       return 0;
741    }
742    if (jcr->cached_attribute) {
743       Dmsg3(dbglvl, "Cached attr with digest. Stream=%d fname=%s attr=%s\n", ar->Stream,            
744          ar->fname, ar->attr);
745       if (!db_create_file_attributes_record(jcr, jcr->db, ar)) {
746          Jmsg1(jcr, M_FATAL, 0, _("Attribute create error. %s"), db_strerror(jcr->db));
747       }
748       jcr->cached_attribute = false; 
749    }
750    set_jcr_job_status(jcr, JS_Terminated);
751    return 1;
752 }