]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/dird/fd_cmds.c
c6b621fb4d35ebaef18d0b47d29285f44b9b986c
[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 (!jcr->keep_sd_auth_key && strcmp(jcr->sd_auth_key, "dummy")) {
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->getJobLevel()) {
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->getJobLevel() == 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->getJobLevel()));
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->getJobLevel()));
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->getJobLevel()));
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->getJobLevel(), 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->getJobLevel()) {
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->getJobLevel(), jcr->getJobLevel());
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    STORE   *store = jcr->wstore;
337    int num;
338    bool include = true;
339
340    for ( ;; ) {
341       if (include) {
342          num = fileset->num_includes;
343       } else {
344          num = fileset->num_excludes;
345       }
346       for (int i=0; i<num; i++) {
347          char *item;
348          INCEXE *ie;
349          int j, k;
350
351          if (include) {
352             ie = fileset->include_items[i];
353             fd->fsend("I\n");
354          } else {
355             ie = fileset->exclude_items[i];
356             fd->fsend("E\n");
357          }
358          if (ie->ignoredir) {
359             bnet_fsend(fd, "Z %s\n", ie->ignoredir);
360          }
361          for (j=0; j<ie->num_opts; j++) {
362             FOPTS *fo = ie->opts_list[j];
363
364             bool enhanced_wild = false;
365             for (k=0; fo->opts[k]!='\0'; k++) {
366                if (fo->opts[k]=='W') {
367                   enhanced_wild = true;
368                   break;
369                }
370             }
371
372             /* Strip out compression option Zn if disallowed for this Storage */
373             if (store && !store->AllowCompress) {
374                char newopts[MAX_FOPTS];
375                int j = 0;
376                for (k=0; fo->opts[k]!='\0'; k++) {                   
377                  /* Z compress option is followed by the single-digit compress level */
378                  if (fo->opts[k]=='Z') {
379                     k++;                /* skip option and level */
380                  } else {
381                     newopts[j] = fo->opts[k];
382                    j++;
383                  }
384                }
385                newopts[j] = '\0';
386
387                Jmsg(jcr, M_INFO, 0,
388                    _("FD compression disabled for this Job because AllowCompress=No in Storage resource.\n") );
389
390                /* Send the new trimmed option set without overwriting fo->opts */
391                fd->fsend("O %s\n", newopts);
392             } else {
393                /* Send the original options */
394                fd->fsend("O %s\n", fo->opts);
395             }
396
397             for (k=0; k<fo->regex.size(); k++) {
398                fd->fsend("R %s\n", fo->regex.get(k));
399             }
400             for (k=0; k<fo->regexdir.size(); k++) {
401                fd->fsend("RD %s\n", fo->regexdir.get(k));
402             }
403             for (k=0; k<fo->regexfile.size(); k++) {
404                fd->fsend("RF %s\n", fo->regexfile.get(k));
405             }
406             for (k=0; k<fo->wild.size(); k++) {
407                fd->fsend("W %s\n", fo->wild.get(k));
408             }
409             for (k=0; k<fo->wilddir.size(); k++) {
410                fd->fsend("WD %s\n", fo->wilddir.get(k));
411             }
412             for (k=0; k<fo->wildfile.size(); k++) {
413                fd->fsend("WF %s\n", fo->wildfile.get(k));
414             }
415             for (k=0; k<fo->wildbase.size(); k++) {
416                fd->fsend("W%c %s\n", enhanced_wild ? 'B' : 'F', fo->wildbase.get(k));
417             }
418             for (k=0; k<fo->base.size(); k++) {
419                fd->fsend("B %s\n", fo->base.get(k));
420             }
421             for (k=0; k<fo->fstype.size(); k++) {
422                fd->fsend("X %s\n", fo->fstype.get(k));
423             }
424             for (k=0; k<fo->drivetype.size(); k++) {
425                fd->fsend("XD %s\n", fo->drivetype.get(k));
426             }
427             if (fo->plugin) {
428                fd->fsend("G %s\n", fo->plugin);
429             }
430             if (fo->reader) {
431                fd->fsend("D %s\n", fo->reader);
432             }
433             if (fo->writer) {
434                fd->fsend("T %s\n", fo->writer);
435             }
436             fd->fsend("N\n");
437          }
438
439          for (j=0; j<ie->name_list.size(); j++) {
440             item = (char *)ie->name_list.get(j);
441             if (!send_list_item(jcr, "F ", item, fd)) {
442                goto bail_out;
443             }
444          }
445          fd->fsend("N\n");
446          for (j=0; j<ie->plugin_list.size(); j++) {
447             item = (char *)ie->plugin_list.get(j);
448             if (!send_list_item(jcr, "P ", item, fd)) {
449                goto bail_out;
450             }
451          }
452          fd->fsend("N\n");
453       }
454       if (!include) {                 /* If we just did excludes */
455          break;                       /*   all done */
456       }
457       include = false;                /* Now do excludes */
458    }
459
460    fd->signal(BNET_EOD);              /* end of data */
461    if (!response(jcr, fd, OKinc, "Include", DISPLAY_ERROR)) {
462       goto bail_out;
463    }
464    return true;
465
466 bail_out:
467    set_jcr_job_status(jcr, JS_ErrorTerminated);
468    return false;
469
470 }
471
472 static bool send_list_item(JCR *jcr, const char *code, char *item, BSOCK *fd)
473 {
474    BPIPE *bpipe;
475    FILE *ffd;
476    char buf[2000];
477    int optlen, stat;
478    char *p = item;
479
480    switch (*p) {
481    case '|':
482       p++;                      /* skip over the | */
483       fd->msg = edit_job_codes(jcr, fd->msg, p, "");
484       bpipe = open_bpipe(fd->msg, 0, "r");
485       if (!bpipe) {
486          berrno be;
487          Jmsg(jcr, M_FATAL, 0, _("Cannot run program: %s. ERR=%s\n"),
488             p, be.bstrerror());
489          return false;
490       }
491       bstrncpy(buf, code, sizeof(buf));
492       Dmsg1(500, "code=%s\n", buf);
493       optlen = strlen(buf);
494       while (fgets(buf+optlen, sizeof(buf)-optlen, bpipe->rfd)) {
495          fd->msglen = Mmsg(fd->msg, "%s", buf);
496          Dmsg2(500, "Inc/exc len=%d: %s", fd->msglen, fd->msg);
497          if (!bnet_send(fd)) {
498             Jmsg(jcr, M_FATAL, 0, _(">filed: write error on socket\n"));
499             return false;
500          }
501       }
502       if ((stat=close_bpipe(bpipe)) != 0) {
503          berrno be;
504          Jmsg(jcr, M_FATAL, 0, _("Error running program: %s. ERR=%s\n"),
505             p, be.bstrerror(stat));
506          return false;
507       }
508       break;
509    case '<':
510       p++;                      /* skip over < */
511       if ((ffd = fopen(p, "rb")) == NULL) {
512          berrno be;
513          Jmsg(jcr, M_FATAL, 0, _("Cannot open included file: %s. ERR=%s\n"),
514             p, be.bstrerror());
515          return false;
516       }
517       bstrncpy(buf, code, sizeof(buf));
518       Dmsg1(500, "code=%s\n", buf);
519       optlen = strlen(buf);
520       while (fgets(buf+optlen, sizeof(buf)-optlen, ffd)) {
521          fd->msglen = Mmsg(fd->msg, "%s", buf);
522          if (!bnet_send(fd)) {
523             Jmsg(jcr, M_FATAL, 0, _(">filed: write error on socket\n"));
524             return false;
525          }
526       }
527       fclose(ffd);
528       break;
529    case '\\':
530       p++;                      /* skip over \ */
531       /* Note, fall through wanted */
532    default:
533       pm_strcpy(fd->msg, code);
534       fd->msglen = pm_strcat(fd->msg, p);
535       Dmsg1(500, "Inc/Exc name=%s\n", fd->msg);
536       if (!fd->send()) {
537          Jmsg(jcr, M_FATAL, 0, _(">filed: write error on socket\n"));
538          return false;
539       }
540       break;
541    }
542    return true;
543 }            
544
545
546 /*
547  * Send include list to File daemon
548  */
549 bool send_include_list(JCR *jcr)
550 {
551    BSOCK *fd = jcr->file_bsock;
552    if (jcr->fileset->new_include) {
553       fd->fsend(filesetcmd, jcr->fileset->enable_vss ? " vss=1" : "");
554       return send_fileset(jcr);
555    }
556    return true;
557 }
558
559
560 /*
561  * Send exclude list to File daemon
562  *   Under the new scheme, the Exclude list
563  *   is part of the FileSet sent with the
564  *   "include_list" above.
565  */
566 bool send_exclude_list(JCR *jcr)
567 {
568    return true;
569 }
570
571 /* TODO: drop this with runscript.old_proto in bacula 1.42 */
572 static char runbefore[]   = "RunBeforeJob %s\n";
573 static char runafter[]    = "RunAfterJob %s\n";
574 static char OKRunBefore[] = "2000 OK RunBefore\n";
575 static char OKRunAfter[]  = "2000 OK RunAfter\n";
576
577 int send_runscript_with_old_proto(JCR *jcr, int when, POOLMEM *msg)
578 {
579    int ret;
580    Dmsg1(120, "bdird: sending old runcommand to fd '%s'\n",msg);
581    if (when & SCRIPT_Before) {
582       bnet_fsend(jcr->file_bsock, runbefore, msg);
583       ret = response(jcr, jcr->file_bsock, OKRunBefore, "ClientRunBeforeJob", DISPLAY_ERROR);
584    } else {
585       bnet_fsend(jcr->file_bsock, runafter, msg);
586       ret = response(jcr, jcr->file_bsock, OKRunAfter, "ClientRunAfterJob", DISPLAY_ERROR);
587    }
588    return ret;
589 } /* END OF TODO */
590
591 /*
592  * Send RunScripts to File daemon
593  * 1) We send all runscript to FD, they can be executed Before, After, or twice
594  * 2) Then, we send a "RunBeforeNow" command to the FD to tell him to do the
595  *    first run_script() call. (ie ClientRunBeforeJob)
596  */
597 int send_runscripts_commands(JCR *jcr)
598 {
599    POOLMEM *msg = get_pool_memory(PM_FNAME);
600    BSOCK *fd = jcr->file_bsock;
601    RUNSCRIPT *cmd;
602    bool launch_before_cmd = false;
603    POOLMEM *ehost = get_pool_memory(PM_FNAME);
604    int result;
605
606    Dmsg0(120, "bdird: sending runscripts to fd\n");
607    
608    foreach_alist(cmd, jcr->job->RunScripts) {
609       if (cmd->can_run_at_level(jcr->getJobLevel()) && cmd->target) {
610          ehost = edit_job_codes(jcr, ehost, cmd->target, "");
611          Dmsg2(200, "bdird: runscript %s -> %s\n", cmd->target, ehost);
612
613          if (strcmp(ehost, jcr->client->name()) == 0) {
614             pm_strcpy(msg, cmd->command);
615             bash_spaces(msg);
616
617             Dmsg1(120, "bdird: sending runscripts to fd '%s'\n", cmd->command);
618             
619             /* TODO: remove this with bacula 1.42 */
620             if (cmd->old_proto) {
621                result = send_runscript_with_old_proto(jcr, cmd->when, msg);
622
623             } else {
624                fd->fsend(runscript, cmd->on_success, 
625                                     cmd->on_failure,
626                                     cmd->fail_on_error,
627                                     cmd->when,
628                                     msg);
629
630                result = response(jcr, fd, OKRunScript, "RunScript", DISPLAY_ERROR);
631                launch_before_cmd = true;
632             }
633             
634             if (!result) {
635                goto bail_out;
636             }
637          }
638          /* TODO : we have to play with other client */
639          /*
640            else {
641            send command to an other client
642            }
643          */
644       }        
645    } 
646
647    /* Tell the FD to execute the ClientRunBeforeJob */
648    if (launch_before_cmd) {
649       fd->fsend(runbeforenow);
650       if (!response(jcr, fd, OKRunBeforeNow, "RunBeforeNow", DISPLAY_ERROR)) {
651         goto bail_out;
652       }
653    }
654    free_pool_memory(msg);
655    free_pool_memory(ehost);
656    return 1;
657
658 bail_out:
659    Jmsg(jcr, M_FATAL, 0, _("Client \"%s\" RunScript failed.\n"), ehost);
660    free_pool_memory(msg);
661    free_pool_memory(ehost);
662    return 0;
663 }
664
665
666
667 /*
668  * Read the attributes from the File daemon for
669  * a Verify job and store them in the catalog.
670  */
671 int get_attributes_and_put_in_catalog(JCR *jcr)
672 {
673    BSOCK   *fd;
674    int n = 0;
675    ATTR_DBR *ar = NULL;
676    char digest[MAXSTRING];
677
678    fd = jcr->file_bsock;
679    jcr->jr.FirstIndex = 1;
680    jcr->FileIndex = 0;
681    /* Start transaction allocates jcr->attr and jcr->ar if needed */
682    db_start_transaction(jcr, jcr->db);     /* start transaction if not already open */
683    ar = jcr->ar;
684
685    Dmsg0(120, "bdird: waiting to receive file attributes\n");
686    /* Pickup file attributes and digest */
687    while (!fd->errors && (n = bget_dirmsg(fd)) > 0) {
688       uint32_t file_index;
689       int stream, len;
690       char *p, *fn;
691       char Digest[MAXSTRING];      /* either Verify opts or MD5/SHA1 digest */
692
693       if ((len = sscanf(fd->msg, "%ld %d %s", &file_index, &stream, Digest)) != 3) {
694          Jmsg(jcr, M_FATAL, 0, _("<filed: bad attributes, expected 3 fields got %d\n"
695 "msglen=%d msg=%s\n"), len, fd->msglen, fd->msg);
696          set_jcr_job_status(jcr, JS_ErrorTerminated);
697          return 0;
698       }
699       p = fd->msg;
700       /* The following three fields were sscanf'ed above so skip them */
701       skip_nonspaces(&p);             /* skip FileIndex */
702       skip_spaces(&p);
703       skip_nonspaces(&p);             /* skip Stream */
704       skip_spaces(&p);
705       skip_nonspaces(&p);             /* skip Opts_Digest */
706       p++;                            /* skip space */
707       Dmsg1(dbglvl, "Stream=%d\n", stream);
708       if (stream == STREAM_UNIX_ATTRIBUTES || stream == STREAM_UNIX_ATTRIBUTES_EX) {
709          if (jcr->cached_attribute) {
710             Dmsg3(dbglvl, "Cached attr. Stream=%d fname=%s\n", ar->Stream, ar->fname,
711                ar->attr);
712             if (!db_create_file_attributes_record(jcr, jcr->db, ar)) {
713                Jmsg1(jcr, M_FATAL, 0, _("Attribute create error. %s"), db_strerror(jcr->db));
714             }
715          }
716          /* Any cached attr is flushed so we can reuse jcr->attr and jcr->ar */
717          fn = jcr->fname = check_pool_memory_size(jcr->fname, fd->msglen);
718          while (*p != 0) {
719             *fn++ = *p++;                /* copy filename */
720          }
721          *fn = *p++;                     /* term filename and point p to attribs */
722          pm_strcpy(jcr->attr, p);        /* save attributes */
723          jcr->JobFiles++;
724          jcr->FileIndex = file_index;
725          ar->attr = jcr->attr;
726          ar->fname = jcr->fname;
727          ar->FileIndex = file_index;
728          ar->Stream = stream;
729          ar->link = NULL;
730          ar->JobId = jcr->JobId;
731          ar->ClientId = jcr->ClientId;
732          ar->PathId = 0;
733          ar->FilenameId = 0;
734          ar->Digest = NULL;
735          ar->DigestType = CRYPTO_DIGEST_NONE;
736          jcr->cached_attribute = true;
737
738          Dmsg2(dbglvl, "dird<filed: stream=%d %s\n", stream, jcr->fname);
739          Dmsg1(dbglvl, "dird<filed: attr=%s\n", ar->attr);
740          jcr->FileId = ar->FileId;
741       /*
742        * First, get STREAM_UNIX_ATTRIBUTES and fill ATTR_DBR structure
743        * Next, we CAN have a CRYPTO_DIGEST, so we fill ATTR_DBR with it (or not)
744        * When we get a new STREAM_UNIX_ATTRIBUTES, we known that we can add file to the catalog
745        * At the end, we have to add the last file
746        */
747       } else if (crypto_digest_stream_type(stream) != CRYPTO_DIGEST_NONE) {
748          if (jcr->FileIndex != (uint32_t)file_index) {
749             Jmsg3(jcr, M_ERROR, 0, _("%s index %d not same as attributes %d\n"),
750                stream_to_ascii(stream), file_index, jcr->FileIndex);
751             continue;
752          }
753          ar->Digest = digest;
754          ar->DigestType = crypto_digest_stream_type(stream);
755          db_escape_string(jcr, jcr->db, digest, Digest, strlen(Digest));
756          Dmsg4(dbglvl, "stream=%d DigestLen=%d Digest=%s type=%d\n", stream,
757                strlen(digest), digest, ar->DigestType);
758       }
759       jcr->jr.JobFiles = jcr->JobFiles = file_index;
760       jcr->jr.LastIndex = file_index;
761    }
762    if (is_bnet_error(fd)) {
763       Jmsg1(jcr, M_FATAL, 0, _("<filed: Network error getting attributes. ERR=%s\n"),
764             fd->bstrerror());
765       return 0;
766    }
767    if (jcr->cached_attribute) {
768       Dmsg3(dbglvl, "Cached attr with digest. Stream=%d fname=%s attr=%s\n", ar->Stream,            
769          ar->fname, ar->attr);
770       if (!db_create_file_attributes_record(jcr, jcr->db, ar)) {
771          Jmsg1(jcr, M_FATAL, 0, _("Attribute create error. %s"), db_strerror(jcr->db));
772       }
773       jcr->cached_attribute = false; 
774    }
775    set_jcr_job_status(jcr, JS_Terminated);
776    return 1;
777 }