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