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