]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/dird/fd_cmds.c
Backport from BEE
[bacula/bacula] / bacula / src / dird / fd_cmds.c
1 /*
2    Bacula® - The Network Backup Solution
3
4    Copyright (C) 2000-2014 Free Software Foundation Europe e.V.
5
6    The main author of Bacula is Kern Sibbald, with contributions from many
7    others, a complete list can be found in the file AUTHORS.
8
9    You may use this file and others of this release according to the
10    license defined in the LICENSE file, which includes the Affero General
11    Public License, v3.0 ("AGPLv3") and some additional permissions and
12    terms pursuant to its AGPLv3 Section 7.
13
14    Bacula® is a registered trademark of Kern Sibbald.
15 */
16 /*
17  *
18  *   Bacula Director -- fd_cmds.c -- send commands to File daemon
19  *
20  *     Kern Sibbald, October MM
21  *
22  *    This routine is run as a separate thread.  There may be more
23  *    work to be done to make it totally reentrant!!!!
24  *
25  *  Utility functions for sending info to File Daemon.
26  *   These functions are used by both backup and verify.
27  *
28  */
29
30 #include "bacula.h"
31 #include "dird.h"
32 #include "findlib/find.h"
33
34 const int dbglvl = 400;
35
36 /* Commands sent to File daemon */
37 static char filesetcmd[]  = "fileset%s\n"; /* set full fileset */
38 static char jobcmd[]      = "JobId=%s Job=%s SDid=%u SDtime=%u Authorization=%s\n";
39 /* Note, mtime_only is not used here -- implemented as file option */
40 static char levelcmd[]    = "level = %s%s%s mtime_only=%d %s%s\n";
41 static char runscript[]   = "Run OnSuccess=%u OnFailure=%u AbortOnError=%u When=%u Command=%s\n";
42 static char runbeforenow[]= "RunBeforeNow\n";
43 static char bandwidthcmd[] = "setbandwidth=%lld Job=%s\n";
44
45 /* Responses received from File daemon */
46 static char OKinc[]          = "2000 OK include\n";
47 static char OKjob[]          = "2000 OK Job";
48 static char OKlevel[]        = "2000 OK level\n";
49 static char OKRunScript[]    = "2000 OK RunScript\n";
50 static char OKRunBeforeNow[] = "2000 OK RunBeforeNow\n";
51 static char OKRestoreObject[] = "2000 OK ObjectRestored\n";
52 static char OKBandwidth[]    = "2000 OK Bandwidth\n";
53
54 /* Forward referenced functions */
55 static bool send_list_item(JCR *jcr, const char *code, char *item, BSOCK *fd);
56
57 /* External functions */
58 extern DIRRES *director;
59 extern int FDConnectTimeout;
60
61 #define INC_LIST 0
62 #define EXC_LIST 1
63
64 /*
65  * Open connection with File daemon.
66  * Try connecting every retry_interval (default 10 sec), and
67  *   give up after max_retry_time (default 30 mins).
68  */
69
70 int connect_to_file_daemon(JCR *jcr, int retry_interval, int max_retry_time,
71                            int verbose)
72 {
73    BSOCK   *fd = jcr->file_bsock;
74    char ed1[30];
75    utime_t heart_beat;
76
77    if (jcr->client->heartbeat_interval) {
78       heart_beat = jcr->client->heartbeat_interval;
79    } else {
80       heart_beat = director->heartbeat_interval;
81    }
82
83    if (!is_bsock_open(jcr->file_bsock)) {
84       char name[MAX_NAME_LENGTH + 100];
85
86       if (!fd) {
87          fd = jcr->file_bsock = new_bsock();
88       }
89       bstrncpy(name, _("Client: "), sizeof(name));
90       bstrncat(name, jcr->client->name(), sizeof(name));
91
92       fd->set_source_address(director->DIRsrc_addr);
93       if (!fd->connect(jcr,retry_interval,max_retry_time, heart_beat, name, jcr->client->address,
94            NULL, jcr->client->FDport, verbose)) {
95          fd->close();
96          jcr->setJobStatus(JS_ErrorTerminated);
97          return 0;
98       }
99       Dmsg0(10, "Opened connection with File daemon\n");
100    }
101    fd->res = (RES *)jcr->client;      /* save resource in BSOCK */
102    jcr->setJobStatus(JS_Running);
103
104    if (!authenticate_file_daemon(jcr)) {
105       jcr->setJobStatus(JS_ErrorTerminated);
106       Dmsg0(10, "Authentication error with FD.\n");
107       return 0;
108    }
109
110    /*
111     * Now send JobId and authorization key
112     */
113    if (jcr->sd_auth_key == NULL) {
114       jcr->sd_auth_key = bstrdup("dummy");
115    }
116    fd->fsend(jobcmd, edit_int64(jcr->JobId, ed1), jcr->Job, jcr->VolSessionId,
117              jcr->VolSessionTime, jcr->sd_auth_key);
118    if (!jcr->keep_sd_auth_key && strcmp(jcr->sd_auth_key, "dummy")) {
119       memset(jcr->sd_auth_key, 0, strlen(jcr->sd_auth_key));
120    }
121    Dmsg1(100, ">filed: %s", fd->msg);
122    if (bget_dirmsg(fd) > 0) {
123        Dmsg1(110, "<filed: %s", fd->msg);
124        if (strncmp(fd->msg, OKjob, strlen(OKjob)) != 0) {
125           Jmsg(jcr, M_FATAL, 0, _("File daemon \"%s\" rejected Job command: %s\n"),
126              jcr->client->hdr.name, fd->msg);
127           jcr->setJobStatus(JS_ErrorTerminated);
128           return 0;
129        } else if (jcr->db) {
130           CLIENT_DBR cr;
131           memset(&cr, 0, sizeof(cr));
132           bstrncpy(cr.Name, jcr->client->hdr.name, sizeof(cr.Name));
133           cr.AutoPrune = jcr->client->AutoPrune;
134           cr.FileRetention = jcr->client->FileRetention;
135           cr.JobRetention = jcr->client->JobRetention;
136           bstrncpy(cr.Uname, fd->msg+strlen(OKjob)+1, sizeof(cr.Uname));
137           if (!db_update_client_record(jcr, jcr->db, &cr)) {
138              Jmsg(jcr, M_WARNING, 0, _("Error updating Client record. ERR=%s\n"),
139                 db_strerror(jcr->db));
140           }
141        }
142    } else {
143       Jmsg(jcr, M_FATAL, 0, _("FD gave bad response to JobId command: %s\n"),
144          fd->bstrerror());
145       jcr->setJobStatus(JS_ErrorTerminated);
146       return 0;
147    }
148    return 1;
149 }
150
151 /*
152  * This subroutine edits the last job start time into a
153  *   "since=date/time" buffer that is returned in the
154  *   variable since.  This is used for display purposes in
155  *   the job report.  The time in jcr->stime is later
156  *   passed to tell the File daemon what to do.
157  */
158 void get_level_since_time(JCR *jcr, char *since, int since_len)
159 {
160    int JobLevel;
161    bool have_full;
162    bool do_full = false;
163    bool do_diff = false;
164    utime_t now;
165    utime_t last_full_time = 0;
166    utime_t last_diff_time;
167    char prev_job[MAX_NAME_LENGTH];
168
169    since[0] = 0;
170    /* If job cloned and a since time already given, use it */
171    if (jcr->cloned && jcr->stime && jcr->stime[0]) {
172       bstrncpy(since, _(", since="), since_len);
173       bstrncat(since, jcr->stime, since_len);
174       return;
175    }
176    /* Make sure stime buffer is allocated */
177    if (!jcr->stime) {
178       jcr->stime = get_pool_memory(PM_MESSAGE);
179    }
180    jcr->PrevJob[0] = jcr->stime[0] = 0;
181    /*
182     * Lookup the last FULL backup job to get the time/date for a
183     * differential or incremental save.
184     */
185    switch (jcr->getJobLevel()) {
186    case L_DIFFERENTIAL:
187    case L_INCREMENTAL:
188       POOLMEM *stime = get_pool_memory(PM_MESSAGE);
189       /* Look up start time of last Full job */
190       now = (utime_t)time(NULL);
191       jcr->jr.JobId = 0;     /* flag to return since time */
192       /*
193        * This is probably redundant, but some of the code below
194        * uses jcr->stime, so don't remove unless you are sure.
195        */
196       if (!db_find_job_start_time(jcr,jcr->db, &jcr->jr, &jcr->stime, jcr->PrevJob)) {
197          do_full = true;
198       }
199       have_full = db_find_last_job_start_time(jcr, jcr->db, &jcr->jr,
200                                               &stime, prev_job, L_FULL);
201       if (have_full) {
202          last_full_time = str_to_utime(stime);
203       } else {
204          do_full = true;               /* No full, upgrade to one */
205       }
206       Dmsg4(50, "have_full=%d do_full=%d now=%lld full_time=%lld\n", have_full,
207             do_full, now, last_full_time);
208       /* Make sure the last diff is recent enough */
209       if (have_full && jcr->getJobLevel() == L_INCREMENTAL && jcr->job->MaxDiffInterval > 0) {
210          /* Lookup last diff job */
211          if (db_find_last_job_start_time(jcr, jcr->db, &jcr->jr,
212                                          &stime, prev_job, L_DIFFERENTIAL)) {
213             last_diff_time = str_to_utime(stime);
214             /* If no Diff since Full, use Full time */
215             if (last_diff_time < last_full_time) {
216                last_diff_time = last_full_time;
217             }
218             Dmsg2(50, "last_diff_time=%lld last_full_time=%lld\n", last_diff_time,
219                   last_full_time);
220          } else {
221             /* No last differential, so use last full time */
222             last_diff_time = last_full_time;
223             Dmsg1(50, "No last_diff_time setting to full_time=%lld\n", last_full_time);
224          }
225          do_diff = ((now - last_diff_time) >= jcr->job->MaxDiffInterval);
226          Dmsg2(50, "do_diff=%d diffInter=%lld\n", do_diff, jcr->job->MaxDiffInterval);
227       }
228       /* Note, do_full takes precedence over do_diff */
229       if (have_full && jcr->job->MaxFullInterval > 0) {
230          do_full = ((now - last_full_time) >= jcr->job->MaxFullInterval);
231       }
232       free_pool_memory(stime);
233
234       if (do_full) {
235          /* No recent Full job found, so upgrade this one to Full */
236          Jmsg(jcr, M_INFO, 0, "%s", db_strerror(jcr->db));
237          Jmsg(jcr, M_INFO, 0, _("No prior or suitable Full backup found in catalog. Doing FULL backup.\n"));
238          bsnprintf(since, since_len, _(" (upgraded from %s)"),
239             level_to_str(jcr->getJobLevel()));
240          jcr->setJobLevel(jcr->jr.JobLevel = L_FULL);
241        } else if (do_diff) {
242          /* No recent diff job found, so upgrade this one to Diff */
243          Jmsg(jcr, M_INFO, 0, _("No prior or suitable Differential backup found in catalog. Doing Differential backup.\n"));
244          bsnprintf(since, since_len, _(" (upgraded from %s)"),
245             level_to_str(jcr->getJobLevel()));
246          jcr->setJobLevel(jcr->jr.JobLevel = L_DIFFERENTIAL);
247       } else {
248          if (jcr->job->rerun_failed_levels) {
249             if (db_find_failed_job_since(jcr, jcr->db, &jcr->jr,
250                                          jcr->stime, JobLevel)) {
251                Jmsg(jcr, M_INFO, 0, _("Prior failed job found in catalog. Upgrading to %s.\n"),
252                   level_to_str(JobLevel));
253                bsnprintf(since, since_len, _(" (upgraded from %s)"),
254                   level_to_str(jcr->getJobLevel()));
255                jcr->setJobLevel(jcr->jr.JobLevel = JobLevel);
256                jcr->jr.JobId = jcr->JobId;
257                break;
258             }
259          }
260          bstrncpy(since, _(", since="), since_len);
261          bstrncat(since, jcr->stime, since_len);
262       }
263       jcr->jr.JobId = jcr->JobId;
264       break;
265    }
266    Dmsg3(100, "Level=%c last start time=%s job=%s\n",
267          jcr->getJobLevel(), jcr->stime, jcr->PrevJob);
268 }
269
270 static void send_since_time(JCR *jcr)
271 {
272    BSOCK   *fd = jcr->file_bsock;
273    utime_t stime;
274    char ed1[50];
275
276    stime = str_to_utime(jcr->stime);
277    fd->fsend(levelcmd, "", NT_("since_utime "), edit_uint64(stime, ed1), 0,
278              NT_("prev_job="), jcr->PrevJob);
279    while (bget_dirmsg(fd) >= 0) {  /* allow him to poll us to sync clocks */
280       Jmsg(jcr, M_INFO, 0, "%s\n", fd->msg);
281    }
282 }
283
284 bool send_bwlimit(JCR *jcr, const char *Job)
285 {
286    BSOCK *fd = jcr->file_bsock;
287    if (jcr->FDVersion >= 4) {
288       fd->fsend(bandwidthcmd, jcr->max_bandwidth, Job);
289       if (!response(jcr, fd, OKBandwidth, "Bandwidth", DISPLAY_ERROR)) {
290          jcr->max_bandwidth = 0;      /* can't set bandwidth limit */
291          return false;
292       }
293    }
294    return true;
295 }
296
297 /*
298  * Send level command to FD.
299  * Used for backup jobs and estimate command.
300  */
301 bool send_level_command(JCR *jcr)
302 {
303    BSOCK   *fd = jcr->file_bsock;
304    const char *accurate = jcr->accurate?"accurate_":"";
305    const char *not_accurate = "";
306    const char *rerunning = jcr->rerunning?" rerunning ":" ";
307    /*
308     * Send Level command to File daemon
309     */
310    switch (jcr->getJobLevel()) {
311    case L_BASE:
312       fd->fsend(levelcmd, not_accurate, "base", rerunning, 0, "", "");
313       break;
314    /* L_NONE is the console, sending something off to the FD */
315    case L_NONE:
316    case L_FULL:
317       fd->fsend(levelcmd, not_accurate, "full", rerunning, 0, "", "");
318       break;
319    case L_DIFFERENTIAL:
320       fd->fsend(levelcmd, accurate, "differential", rerunning, 0, "", "");
321       send_since_time(jcr);
322       break;
323    case L_INCREMENTAL:
324       fd->fsend(levelcmd, accurate, "incremental", rerunning, 0, "", "");
325       send_since_time(jcr);
326       break;
327    case L_SINCE:
328    default:
329       Jmsg2(jcr, M_FATAL, 0, _("Unimplemented backup level %d %c\n"),
330          jcr->getJobLevel(), jcr->getJobLevel());
331       return 0;
332    }
333    Dmsg1(120, ">filed: %s", fd->msg);
334    if (!response(jcr, fd, OKlevel, "Level", DISPLAY_ERROR)) {
335       return false;
336    }
337    return true;
338 }
339
340 /*
341  * Send either an Included or an Excluded list to FD
342  */
343 static bool send_fileset(JCR *jcr)
344 {
345    FILESET *fileset = jcr->fileset;
346    BSOCK   *fd = jcr->file_bsock;
347    STORE   *store = jcr->wstore;
348    int num;
349    bool include = true;
350
351    for ( ;; ) {
352       if (include) {
353          num = fileset->num_includes;
354       } else {
355          num = fileset->num_excludes;
356       }
357       for (int i=0; i<num; i++) {
358          char *item;
359          INCEXE *ie;
360          int j, k;
361
362          if (include) {
363             ie = fileset->include_items[i];
364             fd->fsend("I\n");
365          } else {
366             ie = fileset->exclude_items[i];
367             fd->fsend("E\n");
368          }
369          if (ie->ignoredir) {
370             fd->fsend("Z %s\n", ie->ignoredir);
371          }
372          for (j=0; j<ie->num_opts; j++) {
373             FOPTS *fo = ie->opts_list[j];
374
375             bool enhanced_wild = false;
376             for (k=0; fo->opts[k]!='\0'; k++) {
377                if (fo->opts[k]=='W') {
378                   enhanced_wild = true;
379                   break;
380                }
381             }
382
383             /* Strip out compression option Zn if disallowed for this Storage */
384             if (store && !store->AllowCompress) {
385                char newopts[MAX_FOPTS];
386                bool done=false;         /* print warning only if compression enabled in FS */
387                int j = 0;
388                for (k=0; fo->opts[k]!='\0'; k++) {
389                  /* Z compress option is followed by the single-digit compress level or 'o' */
390                  if (fo->opts[k]=='Z') {
391                     done=true;
392                     k++;                /* skip option and level */
393                  } else {
394                     newopts[j] = fo->opts[k];
395                     j++;
396                  }
397                }
398                newopts[j] = '\0';
399
400                if (done) {
401                   Jmsg(jcr, M_INFO, 0,
402                       _("FD compression disabled for this Job because AllowCompress=No in Storage resource.\n") );
403                }
404                /* Send the new trimmed option set without overwriting fo->opts */
405                fd->fsend("O %s\n", newopts);
406             } else {
407                /* Send the original options */
408                fd->fsend("O %s\n", fo->opts);
409             }
410
411             for (k=0; k<fo->regex.size(); k++) {
412                fd->fsend("R %s\n", fo->regex.get(k));
413             }
414             for (k=0; k<fo->regexdir.size(); k++) {
415                fd->fsend("RD %s\n", fo->regexdir.get(k));
416             }
417             for (k=0; k<fo->regexfile.size(); k++) {
418                fd->fsend("RF %s\n", fo->regexfile.get(k));
419             }
420             for (k=0; k<fo->wild.size(); k++) {
421                fd->fsend("W %s\n", fo->wild.get(k));
422             }
423             for (k=0; k<fo->wilddir.size(); k++) {
424                fd->fsend("WD %s\n", fo->wilddir.get(k));
425             }
426             for (k=0; k<fo->wildfile.size(); k++) {
427                fd->fsend("WF %s\n", fo->wildfile.get(k));
428             }
429             for (k=0; k<fo->wildbase.size(); k++) {
430                fd->fsend("W%c %s\n", enhanced_wild ? 'B' : 'F', fo->wildbase.get(k));
431             }
432             for (k=0; k<fo->base.size(); k++) {
433                fd->fsend("B %s\n", fo->base.get(k));
434             }
435             for (k=0; k<fo->fstype.size(); k++) {
436                fd->fsend("X %s\n", fo->fstype.get(k));
437             }
438             for (k=0; k<fo->drivetype.size(); k++) {
439                fd->fsend("XD %s\n", fo->drivetype.get(k));
440             }
441             if (fo->plugin) {
442                fd->fsend("G %s\n", fo->plugin);
443             }
444             if (fo->reader) {
445                fd->fsend("D %s\n", fo->reader);
446             }
447             if (fo->writer) {
448                fd->fsend("T %s\n", fo->writer);
449             }
450             fd->fsend("N\n");
451          }
452
453          for (j=0; j<ie->name_list.size(); j++) {
454             item = (char *)ie->name_list.get(j);
455             if (!send_list_item(jcr, "F ", item, fd)) {
456                goto bail_out;
457             }
458          }
459          fd->fsend("N\n");
460          for (j=0; j<ie->plugin_list.size(); j++) {
461             item = (char *)ie->plugin_list.get(j);
462             if (!send_list_item(jcr, "P ", item, fd)) {
463                goto bail_out;
464             }
465          }
466          fd->fsend("N\n");
467       }
468       if (!include) {                 /* If we just did excludes */
469          break;                       /*   all done */
470       }
471       include = false;                /* Now do excludes */
472    }
473
474    fd->signal(BNET_EOD);              /* end of data */
475    if (!response(jcr, fd, OKinc, "Include", DISPLAY_ERROR)) {
476       goto bail_out;
477    }
478    return true;
479
480 bail_out:
481    jcr->setJobStatus(JS_ErrorTerminated);
482    return false;
483
484 }
485
486 static bool send_list_item(JCR *jcr, const char *code, char *item, BSOCK *fd)
487 {
488    BPIPE *bpipe;
489    FILE *ffd;
490    char buf[2000];
491    int optlen, stat;
492    char *p = item;
493
494    switch (*p) {
495    case '|':
496       p++;                      /* skip over the | */
497       fd->msg = edit_job_codes(jcr, fd->msg, p, "");
498       bpipe = open_bpipe(fd->msg, 0, "r");
499       if (!bpipe) {
500          berrno be;
501          Jmsg(jcr, M_FATAL, 0, _("Cannot run program: %s. ERR=%s\n"),
502             p, be.bstrerror());
503          return false;
504       }
505       bstrncpy(buf, code, sizeof(buf));
506       Dmsg1(500, "code=%s\n", buf);
507       optlen = strlen(buf);
508       while (fgets(buf+optlen, sizeof(buf)-optlen, bpipe->rfd)) {
509          fd->msglen = Mmsg(fd->msg, "%s", buf);
510          Dmsg2(500, "Inc/exc len=%d: %s", fd->msglen, fd->msg);
511          if (!fd->send()) {
512             Jmsg(jcr, M_FATAL, 0, _(">filed: write error on socket\n"));
513             return false;
514          }
515       }
516       if ((stat=close_bpipe(bpipe)) != 0) {
517          berrno be;
518          Jmsg(jcr, M_FATAL, 0, _("Error running program: %s. ERR=%s\n"),
519             p, be.bstrerror(stat));
520          return false;
521       }
522       break;
523    case '<':
524       p++;                      /* skip over < */
525       if ((ffd = fopen(p, "rb")) == NULL) {
526          berrno be;
527          Jmsg(jcr, M_FATAL, 0, _("Cannot open included file: %s. ERR=%s\n"),
528             p, be.bstrerror());
529          return false;
530       }
531       bstrncpy(buf, code, sizeof(buf));
532       Dmsg1(500, "code=%s\n", buf);
533       optlen = strlen(buf);
534       while (fgets(buf+optlen, sizeof(buf)-optlen, ffd)) {
535          fd->msglen = Mmsg(fd->msg, "%s", buf);
536          if (!fd->send()) {
537             Jmsg(jcr, M_FATAL, 0, _(">filed: write error on socket\n"));
538             return false;
539          }
540       }
541       fclose(ffd);
542       break;
543    case '\\':
544       p++;                      /* skip over \ */
545       /* Note, fall through wanted */
546    default:
547       pm_strcpy(fd->msg, code);
548       fd->msglen = pm_strcat(fd->msg, p);
549       Dmsg1(500, "Inc/Exc name=%s\n", fd->msg);
550       if (!fd->send()) {
551          Jmsg(jcr, M_FATAL, 0, _(">filed: write error on socket\n"));
552          return false;
553       }
554       break;
555    }
556    return true;
557 }
558
559
560 /*
561  * Send include list to File daemon
562  */
563 bool send_include_list(JCR *jcr)
564 {
565    BSOCK *fd = jcr->file_bsock;
566    if (jcr->fileset->new_include) {
567       fd->fsend(filesetcmd, jcr->fileset->enable_vss ? " vss=1" : "");
568       return send_fileset(jcr);
569    }
570    return true;
571 }
572
573 /*
574  * Send exclude list to File daemon
575  *   Under the new scheme, the Exclude list
576  *   is part of the FileSet sent with the
577  *   "include_list" above.
578  */
579 bool send_exclude_list(JCR *jcr)
580 {
581    return true;
582 }
583
584 /* TODO: drop this with runscript.old_proto in bacula 1.42 */
585 static char runbefore[]   = "RunBeforeJob %s\n";
586 static char runafter[]    = "RunAfterJob %s\n";
587 static char OKRunBefore[] = "2000 OK RunBefore\n";
588 static char OKRunAfter[]  = "2000 OK RunAfter\n";
589
590 int send_runscript_with_old_proto(JCR *jcr, int when, POOLMEM *msg)
591 {
592    int ret;
593    Dmsg1(120, "bdird: sending old runcommand to fd '%s'\n",msg);
594    if (when & SCRIPT_Before) {
595       jcr->file_bsock->fsend(runbefore, msg);
596       ret = response(jcr, jcr->file_bsock, OKRunBefore, "ClientRunBeforeJob", DISPLAY_ERROR);
597    } else {
598       jcr->file_bsock->fsend(runafter, msg);
599       ret = response(jcr, jcr->file_bsock, OKRunAfter, "ClientRunAfterJob", DISPLAY_ERROR);
600    }
601    return ret;
602 } /* END OF TODO */
603
604 /*
605  * Send RunScripts to File daemon
606  * 1) We send all runscript to FD, they can be executed Before, After, or twice
607  * 2) Then, we send a "RunBeforeNow" command to the FD to tell him to do the
608  *    first run_script() call. (ie ClientRunBeforeJob)
609  */
610 int send_runscripts_commands(JCR *jcr)
611 {
612    POOLMEM *msg = get_pool_memory(PM_FNAME);
613    BSOCK *fd = jcr->file_bsock;
614    RUNSCRIPT *cmd;
615    bool launch_before_cmd = false;
616    POOLMEM *ehost = get_pool_memory(PM_FNAME);
617    int result;
618
619    Dmsg0(120, "bdird: sending runscripts to fd\n");
620
621    foreach_alist(cmd, jcr->job->RunScripts) {
622       if (cmd->can_run_at_level(jcr->getJobLevel()) && cmd->target) {
623          ehost = edit_job_codes(jcr, ehost, cmd->target, "");
624          Dmsg2(200, "bdird: runscript %s -> %s\n", cmd->target, ehost);
625
626          if (strcmp(ehost, jcr->client->name()) == 0) {
627             pm_strcpy(msg, cmd->command);
628             bash_spaces(msg);
629
630             Dmsg1(120, "bdird: sending runscripts to fd '%s'\n", cmd->command);
631
632             /* TODO: remove this with bacula 1.42 */
633             if (cmd->old_proto) {
634                result = send_runscript_with_old_proto(jcr, cmd->when, msg);
635
636             } else {
637                fd->fsend(runscript, cmd->on_success,
638                                     cmd->on_failure,
639                                     cmd->fail_on_error,
640                                     cmd->when,
641                                     msg);
642
643                result = response(jcr, fd, OKRunScript, "RunScript", DISPLAY_ERROR);
644                launch_before_cmd = true;
645             }
646
647             if (!result) {
648                goto bail_out;
649             }
650          }
651          /* TODO : we have to play with other client */
652          /*
653            else {
654            send command to an other client
655            }
656          */
657       }
658    }
659
660    /* Tell the FD to execute the ClientRunBeforeJob */
661    if (launch_before_cmd) {
662       fd->fsend(runbeforenow);
663       if (!response(jcr, fd, OKRunBeforeNow, "RunBeforeNow", DISPLAY_ERROR)) {
664         goto bail_out;
665       }
666    }
667    free_pool_memory(msg);
668    free_pool_memory(ehost);
669    return 1;
670
671 bail_out:
672    Jmsg(jcr, M_FATAL, 0, _("Client \"%s\" RunScript failed.\n"), ehost);
673    free_pool_memory(msg);
674    free_pool_memory(ehost);
675    return 0;
676 }
677
678 struct OBJ_CTX {
679    JCR *jcr;
680    int count;
681 };
682
683 static int restore_object_handler(void *ctx, int num_fields, char **row)
684 {
685    OBJ_CTX *octx = (OBJ_CTX *)ctx;
686    JCR *jcr = octx->jcr;
687    BSOCK *fd;
688
689    fd = jcr->file_bsock;
690    if (jcr->is_job_canceled()) {
691       return 1;
692    }
693    /* Old File Daemon doesn't handle restore objects */
694    if (jcr->FDVersion < 3) {
695       Jmsg(jcr, M_WARNING, 0, _("Client \"%s\" may not be used to restore "
696                                 "this job. Please upgrade your client.\n"),
697            jcr->client->name());
698       return 1;
699    }
700
701    if (jcr->FDVersion < 5) {    /* Old version without PluginName */
702       fd->fsend("restoreobject JobId=%s %s,%s,%s,%s,%s,%s\n",
703                 row[0], row[1], row[2], row[3], row[4], row[5], row[6]);
704    } else {
705       /* bash spaces from PluginName */
706       bash_spaces(row[9]);
707       fd->fsend("restoreobject JobId=%s %s,%s,%s,%s,%s,%s,%s\n",
708                 row[0], row[1], row[2], row[3], row[4], row[5], row[6], row[9]);
709    }
710    Dmsg1(010, "Send obj hdr=%s", fd->msg);
711
712    fd->msglen = pm_strcpy(fd->msg, row[7]);
713    fd->send();                            /* send Object name */
714
715    Dmsg1(010, "Send obj: %s\n", fd->msg);
716
717 //   fd->msglen = str_to_uint64(row[1]);   /* object length */
718 //   Dmsg1(000, "obj size: %lld\n", (uint64_t)fd->msglen);
719
720    /* object */
721    db_unescape_object(jcr, jcr->db,
722                       row[8],                /* Object  */
723                       str_to_uint64(row[1]), /* Object length */
724                       &fd->msg, &fd->msglen);
725    fd->send();                           /* send object */
726    octx->count++;
727
728    if (debug_level > 100) {
729       for (int i=0; i < fd->msglen; i++)
730          if (!fd->msg[i])
731             fd->msg[i] = ' ';
732       Dmsg1(000, "Send obj: %s\n", fd->msg);
733    }
734
735    return 0;
736 }
737
738 /*
739  * Send the plugin Restore Objects, which allow the
740  *  plugin to get information early in the restore
741  *  process.  The RestoreObjects were created during
742  *  the backup by the plugin.
743  */
744 bool send_restore_objects(JCR *jcr)
745 {
746    char ed1[50];
747    POOL_MEM query(PM_MESSAGE);
748    BSOCK *fd;
749    OBJ_CTX octx;
750
751    if (!jcr->JobIds || !jcr->JobIds[0]) {
752       return true;
753    }
754    octx.jcr = jcr;
755    octx.count = 0;
756
757    /* restore_object_handler is called for each file found */
758
759    /* send restore objects for all jobs involved  */
760    Mmsg(query, get_restore_objects, jcr->JobIds, FT_RESTORE_FIRST);
761    db_sql_query(jcr->db, query.c_str(), restore_object_handler, (void *)&octx);
762
763    /* send config objects for the current restore job */
764    Mmsg(query, get_restore_objects,
765         edit_uint64(jcr->JobId, ed1), FT_PLUGIN_CONFIG_FILLED);
766    db_sql_query(jcr->db, query.c_str(), restore_object_handler, (void *)&octx);
767
768    /*
769     * Send to FD only if we have at least one restore object.
770     * This permits backward compatibility with older FDs.
771     */
772    if (octx.count > 0) {
773       fd = jcr->file_bsock;
774       fd->fsend("restoreobject end\n");
775       if (!response(jcr, fd, OKRestoreObject, "RestoreObject", DISPLAY_ERROR)) {
776          Jmsg(jcr, M_FATAL, 0, _("RestoreObject failed.\n"));
777          return false;
778       }
779    }
780    return true;
781 }
782
783 /*
784  * Read the attributes from the File daemon for
785  * a Verify job and store them in the catalog.
786  */
787 int get_attributes_and_put_in_catalog(JCR *jcr)
788 {
789    BSOCK   *fd;
790    int n = 0;
791    ATTR_DBR *ar = NULL;
792    char digest[MAXSTRING];
793
794    fd = jcr->file_bsock;
795    jcr->jr.FirstIndex = 1;
796    jcr->FileIndex = 0;
797    /* Start transaction allocates jcr->attr and jcr->ar if needed */
798    db_start_transaction(jcr, jcr->db);     /* start transaction if not already open */
799    ar = jcr->ar;
800
801    Dmsg0(120, "bdird: waiting to receive file attributes\n");
802    /* Pickup file attributes and digest */
803    while (!fd->errors && (n = bget_dirmsg(fd)) > 0) {
804       uint32_t file_index;
805       int stream, len;
806       char *p, *fn;
807       char Digest[MAXSTRING];      /* either Verify opts or MD5/SHA1 digest */
808
809       /* Stop here if canceled */
810       if (jcr->is_job_canceled()) {
811          jcr->cached_attribute = false;
812          return 0;
813       }
814
815       if ((len = sscanf(fd->msg, "%ld %d %s", &file_index, &stream, Digest)) != 3) {
816          Jmsg(jcr, M_FATAL, 0, _("<filed: bad attributes, expected 3 fields got %d\n"
817 "msglen=%d msg=%s\n"), len, fd->msglen, fd->msg);
818          jcr->setJobStatus(JS_ErrorTerminated);
819          jcr->cached_attribute = false;
820          return 0;
821       }
822       p = fd->msg;
823       /* The following three fields were sscanf'ed above so skip them */
824       skip_nonspaces(&p);             /* skip FileIndex */
825       skip_spaces(&p);
826       skip_nonspaces(&p);             /* skip Stream */
827       skip_spaces(&p);
828       skip_nonspaces(&p);             /* skip Opts_Digest */
829       p++;                            /* skip space */
830       Dmsg1(dbglvl, "Stream=%d\n", stream);
831       if (stream == STREAM_UNIX_ATTRIBUTES || stream == STREAM_UNIX_ATTRIBUTES_EX) {
832          if (jcr->cached_attribute) {
833             Dmsg3(dbglvl, "Cached attr. Stream=%d fname=%s\n", ar->Stream, ar->fname,
834                ar->attr);
835             if (!db_create_file_attributes_record(jcr, jcr->db, ar)) {
836                Jmsg1(jcr, M_FATAL, 0, _("Attribute create error. %s"), db_strerror(jcr->db));
837             }
838             jcr->cached_attribute = false;
839          }
840          /* Any cached attr is flushed so we can reuse jcr->attr and jcr->ar */
841          fn = jcr->fname = check_pool_memory_size(jcr->fname, fd->msglen);
842          while (*p != 0) {
843             *fn++ = *p++;                /* copy filename */
844          }
845          *fn = *p++;                     /* term filename and point p to attribs */
846          pm_strcpy(jcr->attr, p);        /* save attributes */
847          jcr->JobFiles++;
848          jcr->FileIndex = file_index;
849          ar->attr = jcr->attr;
850          ar->fname = jcr->fname;
851          ar->FileIndex = file_index;
852          ar->Stream = stream;
853          ar->link = NULL;
854          ar->JobId = jcr->JobId;
855          ar->ClientId = jcr->ClientId;
856          ar->PathId = 0;
857          ar->Digest = NULL;
858          ar->DigestType = CRYPTO_DIGEST_NONE;
859          ar->DeltaSeq = 0;
860          jcr->cached_attribute = true;
861
862          Dmsg2(dbglvl, "dird<filed: stream=%d %s\n", stream, jcr->fname);
863          Dmsg1(dbglvl, "dird<filed: attr=%s\n", ar->attr);
864          jcr->FileId = ar->FileId;
865       /*
866        * First, get STREAM_UNIX_ATTRIBUTES and fill ATTR_DBR structure
867        * Next, we CAN have a CRYPTO_DIGEST, so we fill ATTR_DBR with it (or not)
868        * When we get a new STREAM_UNIX_ATTRIBUTES, we known that we can add file to the catalog
869        * At the end, we have to add the last file
870        */
871       } else if (crypto_digest_stream_type(stream) != CRYPTO_DIGEST_NONE) {
872          if (jcr->FileIndex != (uint32_t)file_index) {
873             Jmsg3(jcr, M_ERROR, 0, _("%s index %d not same as attributes %d\n"),
874                stream_to_ascii(stream), file_index, jcr->FileIndex);
875             continue;
876          }
877          ar->Digest = digest;
878          ar->DigestType = crypto_digest_stream_type(stream);
879          db_escape_string(jcr, jcr->db, digest, Digest, strlen(Digest));
880          Dmsg4(dbglvl, "stream=%d DigestLen=%d Digest=%s type=%d\n", stream,
881                strlen(digest), digest, ar->DigestType);
882       }
883       jcr->jr.JobFiles = jcr->JobFiles = file_index;
884       jcr->jr.LastIndex = file_index;
885    }
886    if (fd->is_error()) {
887       Jmsg1(jcr, M_FATAL, 0, _("<filed: Network error getting attributes. ERR=%s\n"),
888             fd->bstrerror());
889       jcr->cached_attribute = false;
890       return 0;
891    }
892    if (jcr->cached_attribute) {
893       Dmsg3(dbglvl, "Cached attr with digest. Stream=%d fname=%s attr=%s\n", ar->Stream,
894          ar->fname, ar->attr);
895       if (!db_create_file_attributes_record(jcr, jcr->db, ar)) {
896          Jmsg1(jcr, M_FATAL, 0, _("Attribute create error. %s"), db_strerror(jcr->db));
897       }
898       jcr->cached_attribute = false;
899    }
900    jcr->setJobStatus(JS_Terminated);
901    return 1;
902 }