]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/dird/fd_cmds.c
- Start implementing Christopher's St.Bernard code.
[bacula/bacula] / bacula / src / dird / fd_cmds.c
1 /*
2  *
3  *   Bacula Director -- fd_cmds.c -- send commands to File daemon
4  *
5  *     Kern Sibbald, October MM
6  *
7  *    This routine is run as a separate thread.  There may be more
8  *    work to be done to make it totally reentrant!!!!
9  *
10  *  Utility functions for sending info to File Daemon.
11  *   These functions are used by both backup and verify.
12  *
13  *   Version $Id$
14  */
15 /*
16    Copyright (C) 2000-2006 Kern Sibbald
17
18    This program is free software; you can redistribute it and/or
19    modify it under the terms of the GNU General Public License
20    version 2 as amended with additional clauses defined in the
21    file LICENSE in the main source directory.
22
23    This program is distributed in the hope that it will be useful,
24    but WITHOUT ANY WARRANTY; without even the implied warranty of
25    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 
26    the file LICENSE for additional details.
27
28  */
29
30 #include "bacula.h"
31 #include "dird.h"
32 #include "findlib/find.h"
33
34 /* Commands sent to File daemon */
35 static char filesetcmd[]  = "fileset%s\n"; /* set full fileset */
36 static char jobcmd[]      = "JobId=%s Job=%s SDid=%u SDtime=%u Authorization=%s\n";
37 /* Note, mtime_only is not used here -- implemented as file option */
38 static char levelcmd[]    = "level = %s%s mtime_only=%d\n";
39 static char runbefore[]   = "RunBeforeJob %s\n";
40 static char runafter[]    = "RunAfterJob %s\n";
41
42
43 /* Responses received from File daemon */
44 static char OKinc[]       = "2000 OK include\n";
45 static char OKjob[]       = "2000 OK Job";
46 static char OKlevel[]     = "2000 OK level\n";
47 static char OKRunBefore[] = "2000 OK RunBefore\n";
48 static char OKRunAfter[]  = "2000 OK RunAfter\n";
49
50 /* Forward referenced functions */
51
52 /* External functions */
53 extern int debug_level;
54 extern DIRRES *director;
55 extern int FDConnectTimeout;
56
57 #define INC_LIST 0
58 #define EXC_LIST 1
59
60 /*
61  * Open connection with File daemon.
62  * Try connecting every retry_interval (default 10 sec), and
63  *   give up after max_retry_time (default 30 mins).
64  */
65
66 int connect_to_file_daemon(JCR *jcr, int retry_interval, int max_retry_time,
67                            int verbose)
68 {
69    BSOCK   *fd;
70    char ed1[30];
71
72    if (!jcr->file_bsock) {
73       fd = bnet_connect(jcr, retry_interval, max_retry_time,
74            _("File daemon"), jcr->client->address,
75            NULL, jcr->client->FDport, verbose);
76       if (fd == NULL) {
77          set_jcr_job_status(jcr, JS_ErrorTerminated);
78          return 0;
79       }
80       Dmsg0(10, "Opened connection with File daemon\n");
81    } else {
82       fd = jcr->file_bsock;           /* use existing connection */
83    }
84    fd->res = (RES *)jcr->client;      /* save resource in BSOCK */
85    jcr->file_bsock = fd;
86    set_jcr_job_status(jcr, JS_Running);
87
88    if (!authenticate_file_daemon(jcr)) {
89       set_jcr_job_status(jcr, JS_ErrorTerminated);
90       return 0;
91    }
92
93    /*
94     * Now send JobId and authorization key
95     */
96    bnet_fsend(fd, jobcmd, edit_int64(jcr->JobId, ed1), jcr->Job, jcr->VolSessionId,
97       jcr->VolSessionTime, jcr->sd_auth_key);
98    if (strcmp(jcr->sd_auth_key, "dummy") != 0) {
99       memset(jcr->sd_auth_key, 0, strlen(jcr->sd_auth_key));
100    }
101    Dmsg1(100, ">filed: %s", fd->msg);
102    if (bget_dirmsg(fd) > 0) {
103        Dmsg1(110, "<filed: %s", fd->msg);
104        if (strncmp(fd->msg, OKjob, strlen(OKjob)) != 0) {
105           Jmsg(jcr, M_FATAL, 0, _("File daemon \"%s\" rejected Job command: %s\n"),
106              jcr->client->hdr.name, fd->msg);
107           set_jcr_job_status(jcr, JS_ErrorTerminated);
108           return 0;
109        } else if (jcr->db) {
110           CLIENT_DBR cr;
111           memset(&cr, 0, sizeof(cr));
112           bstrncpy(cr.Name, jcr->client->hdr.name, sizeof(cr.Name));
113           cr.AutoPrune = jcr->client->AutoPrune;
114           cr.FileRetention = jcr->client->FileRetention;
115           cr.JobRetention = jcr->client->JobRetention;
116           bstrncpy(cr.Uname, fd->msg+strlen(OKjob)+1, sizeof(cr.Uname));
117           if (!db_update_client_record(jcr, jcr->db, &cr)) {
118              Jmsg(jcr, M_WARNING, 0, _("Error updating Client record. ERR=%s\n"),
119                 db_strerror(jcr->db));
120           }
121        }
122    } else {
123       Jmsg(jcr, M_FATAL, 0, _("FD gave bad response to JobId command: %s\n"),
124          bnet_strerror(fd));
125       set_jcr_job_status(jcr, JS_ErrorTerminated);
126       return 0;
127    }
128    return 1;
129 }
130
131 /*
132  * This subroutine edits the last job start time into a
133  *   "since=date/time" buffer that is returned in the
134  *   variable since.  This is used for display purposes in
135  *   the job report.  The time in jcr->stime is later
136  *   passed to tell the File daemon what to do.
137  */
138 void get_level_since_time(JCR *jcr, char *since, int since_len)
139 {
140    int JobLevel;
141
142    since[0] = 0;
143    if (jcr->cloned) {
144       if (jcr->stime && jcr->stime[0]) {
145          bstrncpy(since, _(", since="), since_len);
146          bstrncat(since, jcr->stime, since_len);
147       }
148       return;
149    }
150    if (!jcr->stime) {
151       jcr->stime = get_pool_memory(PM_MESSAGE);
152    } 
153    jcr->stime[0] = 0;
154    /* Lookup the last FULL backup job to get the time/date for a
155     * differential or incremental save.
156     */
157    switch (jcr->JobLevel) {
158    case L_DIFFERENTIAL:
159    case L_INCREMENTAL:
160       /* Look up start time of last job */
161       jcr->jr.JobId = 0;     /* flag for db_find_job_start time */
162       if (!db_find_job_start_time(jcr, jcr->db, &jcr->jr, &jcr->stime)) {
163          /* No job found, so upgrade this one to Full */
164          Jmsg(jcr, M_INFO, 0, "%s", db_strerror(jcr->db));
165          Jmsg(jcr, M_INFO, 0, _("No prior or suitable Full backup found. Doing FULL backup.\n"));
166          bsnprintf(since, since_len, _(" (upgraded from %s)"),
167             level_to_str(jcr->JobLevel));
168          jcr->JobLevel = jcr->jr.JobLevel = L_FULL;
169       } else {
170          if (jcr->job->rerun_failed_levels) {
171             if (db_find_failed_job_since(jcr, jcr->db, &jcr->jr, jcr->stime, JobLevel)) {
172                Jmsg(jcr, M_INFO, 0, _("Prior failed job found. Upgrading to %s.\n"),
173                   level_to_str(JobLevel));
174                bsnprintf(since, since_len, _(" (upgraded from %s)"),
175                   level_to_str(jcr->JobLevel));
176                jcr->JobLevel = jcr->jr.JobLevel = JobLevel;
177                jcr->jr.JobId = jcr->JobId;
178                break;
179             }
180          }
181          bstrncpy(since, _(", since="), since_len);
182          bstrncat(since, jcr->stime, since_len);
183       }
184       jcr->jr.JobId = jcr->JobId;
185       break;
186    }
187    Dmsg2(100, "Level=%c last start time=%s\n", jcr->JobLevel, jcr->stime);
188 }
189
190 static void send_since_time(JCR *jcr)
191 {
192    BSOCK   *fd = jcr->file_bsock;
193    utime_t stime;
194    char ed1[50];
195
196    stime = str_to_utime(jcr->stime);
197    bnet_fsend(fd, levelcmd, NT_("since_utime "), edit_uint64(stime, ed1), 0);
198    while (bget_dirmsg(fd) >= 0) {  /* allow him to poll us to sync clocks */
199       Jmsg(jcr, M_INFO, 0, "%s\n", fd->msg);
200    }
201 }
202
203
204 /*
205  * Send level command to FD.
206  * Used for backup jobs and estimate command.
207  */
208 bool send_level_command(JCR *jcr)
209 {
210    BSOCK   *fd = jcr->file_bsock;
211    /*
212     * Send Level command to File daemon
213     */
214    switch (jcr->JobLevel) {
215    case L_BASE:
216       bnet_fsend(fd, levelcmd, "base", " ", 0);
217       break;
218    /* L_NONE is the console, sending something off to the FD */
219    case L_NONE:
220    case L_FULL:
221       bnet_fsend(fd, levelcmd, "full", " ", 0);
222       break;
223    case L_DIFFERENTIAL:
224       bnet_fsend(fd, levelcmd, "differential", " ", 0);
225       send_since_time(jcr);
226       break;
227    case L_INCREMENTAL:
228       bnet_fsend(fd, levelcmd, "incremental", " ", 0);
229       send_since_time(jcr);
230       break;
231    case L_SINCE:
232    default:
233       Jmsg2(jcr, M_FATAL, 0, _("Unimplemented backup level %d %c\n"),
234          jcr->JobLevel, jcr->JobLevel);
235       return 0;
236    }
237    Dmsg1(120, ">filed: %s", fd->msg);
238    if (!response(jcr, fd, OKlevel, "Level", DISPLAY_ERROR)) {
239       return 0;
240    }
241    return 1;
242 }
243
244 /*
245  * Send either an Included or an Excluded list to FD
246  */
247 static bool send_fileset(JCR *jcr)
248 {
249    FILESET *fileset = jcr->fileset;
250    BSOCK   *fd = jcr->file_bsock;
251    int num;
252    bool include = true;
253
254    for ( ;; ) {
255       if (include) {
256          num = fileset->num_includes;
257       } else {
258          num = fileset->num_excludes;
259       }
260       for (int i=0; i<num; i++) {
261          BPIPE *bpipe;
262          FILE *ffd;
263          char buf[2000];
264          char *p;
265          int optlen, stat;
266          INCEXE *ie;
267          int j, k;
268
269          if (include) {
270             ie = fileset->include_items[i];
271             bnet_fsend(fd, "I\n");
272          } else {
273             ie = fileset->exclude_items[i];
274             bnet_fsend(fd, "E\n");
275          }
276          for (j=0; j<ie->num_opts; j++) {
277             FOPTS *fo = ie->opts_list[j];
278             bnet_fsend(fd, "O %s\n", fo->opts);
279             for (k=0; k<fo->regex.size(); k++) {
280                bnet_fsend(fd, "R %s\n", fo->regex.get(k));
281             }
282             for (k=0; k<fo->regexdir.size(); k++) {
283                bnet_fsend(fd, "RD %s\n", fo->regexdir.get(k));
284             }
285             for (k=0; k<fo->regexfile.size(); k++) {
286                bnet_fsend(fd, "RF %s\n", fo->regexfile.get(k));
287             }
288             for (k=0; k<fo->wild.size(); k++) {
289                bnet_fsend(fd, "W %s\n", fo->wild.get(k));
290             }
291             for (k=0; k<fo->wilddir.size(); k++) {
292                bnet_fsend(fd, "WD %s\n", fo->wilddir.get(k));
293             }
294             for (k=0; k<fo->wildfile.size(); k++) {
295                bnet_fsend(fd, "WF %s\n", fo->wildfile.get(k));
296             }
297             for (k=0; k<fo->base.size(); k++) {
298                bnet_fsend(fd, "B %s\n", fo->base.get(k));
299             }
300             for (k=0; k<fo->fstype.size(); k++) {
301                bnet_fsend(fd, "X %s\n", fo->fstype.get(k));
302             }
303             if (fo->reader) {
304                bnet_fsend(fd, "D %s\n", fo->reader);
305             }
306             if (fo->writer) {
307                bnet_fsend(fd, "T %s\n", fo->writer);
308             }
309             bnet_fsend(fd, "N\n");
310          }
311
312          for (j=0; j<ie->name_list.size(); j++) {
313             p = (char *)ie->name_list.get(j);
314             switch (*p) {
315             case '|':
316                p++;                      /* skip over the | */
317                fd->msg = edit_job_codes(jcr, fd->msg, p, "");
318                bpipe = open_bpipe(fd->msg, 0, "r");
319                if (!bpipe) {
320                   berrno be;
321                   Jmsg(jcr, M_FATAL, 0, _("Cannot run program: %s. ERR=%s\n"),
322                      p, be.strerror());
323                   goto bail_out;
324                }
325                bstrncpy(buf, "F ", sizeof(buf));
326                Dmsg1(500, "Opts=%s\n", buf);
327                optlen = strlen(buf);
328                while (fgets(buf+optlen, sizeof(buf)-optlen, bpipe->rfd)) {
329                   fd->msglen = Mmsg(fd->msg, "%s", buf);
330                   Dmsg2(500, "Inc/exc len=%d: %s", fd->msglen, fd->msg);
331                   if (!bnet_send(fd)) {
332                      Jmsg(jcr, M_FATAL, 0, _(">filed: write error on socket\n"));
333                      goto bail_out;
334                   }
335                }
336                if ((stat=close_bpipe(bpipe)) != 0) {
337                   berrno be;
338                   Jmsg(jcr, M_FATAL, 0, _("Error running program: %s. ERR=%s\n"),
339                      p, be.strerror(stat));
340                   goto bail_out;
341                }
342                break;
343             case '<':
344                p++;                      /* skip over < */
345                if ((ffd = fopen(p, "r")) == NULL) {
346                   berrno be;
347                   Jmsg(jcr, M_FATAL, 0, _("Cannot open included file: %s. ERR=%s\n"),
348                      p, be.strerror());
349                   goto bail_out;
350                }
351                bstrncpy(buf, "F ", sizeof(buf));
352                Dmsg1(500, "Opts=%s\n", buf);
353                optlen = strlen(buf);
354                while (fgets(buf+optlen, sizeof(buf)-optlen, ffd)) {
355                   fd->msglen = Mmsg(fd->msg, "%s", buf);
356                   if (!bnet_send(fd)) {
357                      Jmsg(jcr, M_FATAL, 0, _(">filed: write error on socket\n"));
358                      goto bail_out;
359                   }
360                }
361                fclose(ffd);
362                break;
363             case '\\':
364                p++;                      /* skip over \ */
365                /* Note, fall through wanted */
366             default:
367                pm_strcpy(fd->msg, "F ");
368                fd->msglen = pm_strcat(fd->msg, p);
369                Dmsg1(500, "Inc/Exc name=%s\n", fd->msg);
370                if (!bnet_send(fd)) {
371                   Jmsg(jcr, M_FATAL, 0, _(">filed: write error on socket\n"));
372                   goto bail_out;
373                }
374                break;
375             }
376          }
377          bnet_fsend(fd, "N\n");
378       }
379       if (!include) {                 /* If we just did excludes */
380          break;                       /*   all done */
381       }
382       include = false;                /* Now do excludes */
383    }
384
385    bnet_sig(fd, BNET_EOD);            /* end of data */
386    if (!response(jcr, fd, OKinc, "Include", DISPLAY_ERROR)) {
387       goto bail_out;
388    }
389    return true;
390
391 bail_out:
392    set_jcr_job_status(jcr, JS_ErrorTerminated);
393    return false;
394
395 }
396
397
398 /*
399  * Send include list to File daemon
400  */
401 bool send_include_list(JCR *jcr)
402 {
403    BSOCK *fd = jcr->file_bsock;
404    if (jcr->fileset->new_include) {
405       bnet_fsend(fd, filesetcmd, jcr->fileset->enable_vss ? " vss=1" : "");
406       return send_fileset(jcr);
407    }
408    return true;
409 }
410
411
412 /*
413  * Send exclude list to File daemon
414  *   Under the new scheme, the Exclude list
415  *   is part of the FileSet sent with the
416  *   "include_list" above.
417  */
418 bool send_exclude_list(JCR *jcr)
419 {
420    return true;
421 }
422
423
424 /*
425  * Send bootstrap file if any to the socket given (FD or SD).
426  *  This is used for restore, verify VolumeToCatalog, and
427  *  for migration.
428  */
429 bool send_bootstrap_file(JCR *jcr, BSOCK *sock)
430 {
431    FILE *bs;
432    char buf[1000];
433    const char *bootstrap = "bootstrap\n";
434
435    Dmsg1(400, "send_bootstrap_file: %s\n", jcr->RestoreBootstrap);
436    if (!jcr->RestoreBootstrap) {
437       return true;
438    }
439    bs = fopen(jcr->RestoreBootstrap, "r");
440    if (!bs) {
441       berrno be;
442       Jmsg(jcr, M_FATAL, 0, _("Could not open bootstrap file %s: ERR=%s\n"),
443          jcr->RestoreBootstrap, be.strerror());
444       set_jcr_job_status(jcr, JS_ErrorTerminated);
445       return false;
446    }
447    bnet_fsend(sock, bootstrap);
448    while (fgets(buf, sizeof(buf), bs)) {
449       bnet_fsend(sock, "%s", buf);
450    }
451    bnet_sig(sock, BNET_EOD);
452    fclose(bs);
453    if (jcr->unlink_bsr) {
454       unlink(jcr->RestoreBootstrap);
455       jcr->unlink_bsr = false;
456    }                         
457    return true;
458 }
459
460 /*
461  * Send ClientRunBeforeJob and ClientRunAfterJob to File daemon
462  */
463 int send_run_before_and_after_commands(JCR *jcr)
464 {
465    POOLMEM *msg = get_pool_memory(PM_FNAME);
466    BSOCK *fd = jcr->file_bsock;
467    if (jcr->job->ClientRunBeforeJob) {
468       pm_strcpy(msg, jcr->job->ClientRunBeforeJob);
469       bash_spaces(msg);
470       bnet_fsend(fd, runbefore, msg);
471       if (!response(jcr, fd, OKRunBefore, "ClientRunBeforeJob", DISPLAY_ERROR)) {
472          set_jcr_job_status(jcr, JS_ErrorTerminated);
473          free_pool_memory(msg);
474          return 0;
475       }
476    }
477    if (jcr->job->ClientRunAfterJob) {
478       fd->msglen = pm_strcpy(msg, jcr->job->ClientRunAfterJob);
479       bash_spaces(msg);
480       bnet_fsend(fd, runafter, msg);
481       if (!response(jcr, fd, OKRunAfter, "ClientRunAfterJob", DISPLAY_ERROR)) {
482          set_jcr_job_status(jcr, JS_ErrorTerminated);
483          free_pool_memory(msg);
484          return 0;
485       }
486    }
487    free_pool_memory(msg);
488    return 1;
489 }
490
491
492 /*
493  * Read the attributes from the File daemon for
494  * a Verify job and store them in the catalog.
495  */
496 int get_attributes_and_put_in_catalog(JCR *jcr)
497 {
498    BSOCK   *fd;
499    int n = 0;
500    ATTR_DBR ar;
501
502    fd = jcr->file_bsock;
503    jcr->jr.FirstIndex = 1;
504    memset(&ar, 0, sizeof(ar));
505    jcr->FileIndex = 0;
506
507    Dmsg0(120, "bdird: waiting to receive file attributes\n");
508    /* Pickup file attributes and digest */
509    while (!fd->errors && (n = bget_dirmsg(fd)) > 0) {
510
511    /*****FIXME****** improve error handling to stop only on
512     * really fatal problems, or the number of errors is too
513     * large.
514     */
515       long file_index;
516       int stream, len;
517       char *attr, *p, *fn;
518       char Opts_Digest[MAXSTRING];      /* either Verify opts or MD5/SHA1 digest */
519       char digest[CRYPTO_DIGEST_MAX_SIZE];
520
521       jcr->fname = check_pool_memory_size(jcr->fname, fd->msglen);
522       if ((len = sscanf(fd->msg, "%ld %d %s", &file_index, &stream, Opts_Digest)) != 3) {
523          Jmsg(jcr, M_FATAL, 0, _("<filed: bad attributes, expected 3 fields got %d\n"
524 "msglen=%d msg=%s\n"), len, fd->msglen, fd->msg);
525          set_jcr_job_status(jcr, JS_ErrorTerminated);
526          return 0;
527       }
528       p = fd->msg;
529       skip_nonspaces(&p);             /* skip FileIndex */
530       skip_spaces(&p);
531       skip_nonspaces(&p);             /* skip Stream */
532       skip_spaces(&p);
533       skip_nonspaces(&p);             /* skip Opts_SHA1 */
534       p++;                            /* skip space */
535       fn = jcr->fname;
536       while (*p != 0) {
537          *fn++ = *p++;                /* copy filename */
538       }
539       *fn = *p++;                     /* term filename and point to attribs */
540       attr = p;
541
542       if (stream == STREAM_UNIX_ATTRIBUTES || stream == STREAM_UNIX_ATTRIBUTES_EX) {
543          jcr->JobFiles++;
544          jcr->FileIndex = file_index;
545          ar.attr = attr;
546          ar.fname = jcr->fname;
547          ar.FileIndex = file_index;
548          ar.Stream = stream;
549          ar.link = NULL;
550          ar.JobId = jcr->JobId;
551          ar.ClientId = jcr->ClientId;
552          ar.PathId = 0;
553          ar.FilenameId = 0;
554          ar.Digest = NULL;
555          ar.DigestType = CRYPTO_DIGEST_NONE;
556
557          Dmsg2(111, "dird<filed: stream=%d %s\n", stream, jcr->fname);
558          Dmsg1(120, "dird<filed: attr=%s\n", attr);
559
560          if (!db_create_file_attributes_record(jcr, jcr->db, &ar)) {
561             Jmsg1(jcr, M_ERROR, 0, "%s", db_strerror(jcr->db));
562             set_jcr_job_status(jcr, JS_Error);
563             continue;
564          }
565          jcr->FileId = ar.FileId;
566       } else if (crypto_digest_stream_type(stream) != CRYPTO_DIGEST_NONE) {
567          if (jcr->FileIndex != (uint32_t)file_index) {
568             Jmsg3(jcr, M_ERROR, 0, _("%s index %d not same as attributes %d\n"),
569                stream_to_ascii(stream), file_index, jcr->FileIndex);
570             set_jcr_job_status(jcr, JS_Error);
571             continue;
572          }
573          db_escape_string(digest, Opts_Digest, strlen(Opts_Digest));
574          Dmsg2(120, "DigestLen=%d Digest=%s\n", strlen(digest), digest);
575          if (!db_add_digest_to_file_record(jcr, jcr->db, jcr->FileId, digest,
576                    crypto_digest_stream_type(stream))) {
577             Jmsg1(jcr, M_ERROR, 0, "%s", db_strerror(jcr->db));
578             set_jcr_job_status(jcr, JS_Error);
579          }
580       }
581       jcr->jr.JobFiles = jcr->JobFiles = file_index;
582       jcr->jr.LastIndex = file_index;
583    }
584    if (is_bnet_error(fd)) {
585       Jmsg1(jcr, M_FATAL, 0, _("<filed: Network error getting attributes. ERR=%s\n"),
586                         bnet_strerror(fd));
587       set_jcr_job_status(jcr, JS_ErrorTerminated);
588       return 0;
589    }
590
591    set_jcr_job_status(jcr, JS_Terminated);
592    return 1;
593 }