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