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