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