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