3 * Bacula Director -- fd_cmds.c -- send commands to File daemon
5 * Kern Sibbald, October MM
7 * This routine is run as a separate thread. There may be more
8 * work to be done to make it totally reentrant!!!!
10 * Utility functions for sending info to File Daemon.
11 * These functions are used by both backup and verify.
16 Copyright (C) 2000-2006 Kern Sibbald
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.
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.
32 #include "findlib/find.h"
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";
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";
49 /* Forward referenced functions */
51 /* External functions */
52 extern DIRRES *director;
53 extern int FDConnectTimeout;
59 * Open connection with File daemon.
60 * Try connecting every retry_interval (default 10 sec), and
61 * give up after max_retry_time (default 30 mins).
64 int connect_to_file_daemon(JCR *jcr, int retry_interval, int max_retry_time,
70 if (!jcr->file_bsock) {
71 fd = bnet_connect(jcr, retry_interval, max_retry_time,
72 _("File daemon"), jcr->client->address,
73 NULL, jcr->client->FDport, verbose);
75 set_jcr_job_status(jcr, JS_ErrorTerminated);
78 Dmsg0(10, "Opened connection with File daemon\n");
80 fd = jcr->file_bsock; /* use existing connection */
82 fd->res = (RES *)jcr->client; /* save resource in BSOCK */
84 set_jcr_job_status(jcr, JS_Running);
86 if (!authenticate_file_daemon(jcr)) {
87 set_jcr_job_status(jcr, JS_ErrorTerminated);
92 * Now send JobId and authorization key
94 bnet_fsend(fd, jobcmd, edit_int64(jcr->JobId, ed1), jcr->Job, jcr->VolSessionId,
95 jcr->VolSessionTime, jcr->sd_auth_key);
96 if (strcmp(jcr->sd_auth_key, "dummy") != 0) {
97 memset(jcr->sd_auth_key, 0, strlen(jcr->sd_auth_key));
99 Dmsg1(100, ">filed: %s", fd->msg);
100 if (bget_dirmsg(fd) > 0) {
101 Dmsg1(110, "<filed: %s", fd->msg);
102 if (strncmp(fd->msg, OKjob, strlen(OKjob)) != 0) {
103 Jmsg(jcr, M_FATAL, 0, _("File daemon \"%s\" rejected Job command: %s\n"),
104 jcr->client->hdr.name, fd->msg);
105 set_jcr_job_status(jcr, JS_ErrorTerminated);
107 } else if (jcr->db) {
109 memset(&cr, 0, sizeof(cr));
110 bstrncpy(cr.Name, jcr->client->hdr.name, sizeof(cr.Name));
111 cr.AutoPrune = jcr->client->AutoPrune;
112 cr.FileRetention = jcr->client->FileRetention;
113 cr.JobRetention = jcr->client->JobRetention;
114 bstrncpy(cr.Uname, fd->msg+strlen(OKjob)+1, sizeof(cr.Uname));
115 if (!db_update_client_record(jcr, jcr->db, &cr)) {
116 Jmsg(jcr, M_WARNING, 0, _("Error updating Client record. ERR=%s\n"),
117 db_strerror(jcr->db));
121 Jmsg(jcr, M_FATAL, 0, _("FD gave bad response to JobId command: %s\n"),
123 set_jcr_job_status(jcr, JS_ErrorTerminated);
130 * This subroutine edits the last job start time into a
131 * "since=date/time" buffer that is returned in the
132 * variable since. This is used for display purposes in
133 * the job report. The time in jcr->stime is later
134 * passed to tell the File daemon what to do.
136 void get_level_since_time(JCR *jcr, char *since, int since_len)
142 if (jcr->stime && jcr->stime[0]) {
143 bstrncpy(since, _(", since="), since_len);
144 bstrncat(since, jcr->stime, since_len);
149 jcr->stime = get_pool_memory(PM_MESSAGE);
152 /* Lookup the last FULL backup job to get the time/date for a
153 * differential or incremental save.
155 switch (jcr->JobLevel) {
158 /* Look up start time of last job */
159 jcr->jr.JobId = 0; /* flag for db_find_job_start time */
160 if (!db_find_job_start_time(jcr, jcr->db, &jcr->jr, &jcr->stime)) {
161 /* No job found, so upgrade this one to Full */
162 Jmsg(jcr, M_INFO, 0, "%s", db_strerror(jcr->db));
163 Jmsg(jcr, M_INFO, 0, _("No prior or suitable Full backup found in catalog. Doing FULL backup.\n"));
164 bsnprintf(since, since_len, _(" (upgraded from %s)"),
165 level_to_str(jcr->JobLevel));
166 jcr->JobLevel = jcr->jr.JobLevel = L_FULL;
168 if (jcr->job->rerun_failed_levels) {
169 if (db_find_failed_job_since(jcr, jcr->db, &jcr->jr, jcr->stime, JobLevel)) {
170 Jmsg(jcr, M_INFO, 0, _("Prior failed job found in catalog. Upgrading to %s.\n"),
171 level_to_str(JobLevel));
172 bsnprintf(since, since_len, _(" (upgraded from %s)"),
173 level_to_str(jcr->JobLevel));
174 jcr->JobLevel = jcr->jr.JobLevel = JobLevel;
175 jcr->jr.JobId = jcr->JobId;
179 bstrncpy(since, _(", since="), since_len);
180 bstrncat(since, jcr->stime, since_len);
182 jcr->jr.JobId = jcr->JobId;
185 Dmsg2(100, "Level=%c last start time=%s\n", jcr->JobLevel, jcr->stime);
188 static void send_since_time(JCR *jcr)
190 BSOCK *fd = jcr->file_bsock;
194 stime = str_to_utime(jcr->stime);
195 bnet_fsend(fd, levelcmd, NT_("since_utime "), edit_uint64(stime, ed1), 0);
196 while (bget_dirmsg(fd) >= 0) { /* allow him to poll us to sync clocks */
197 Jmsg(jcr, M_INFO, 0, "%s\n", fd->msg);
203 * Send level command to FD.
204 * Used for backup jobs and estimate command.
206 bool send_level_command(JCR *jcr)
208 BSOCK *fd = jcr->file_bsock;
210 * Send Level command to File daemon
212 switch (jcr->JobLevel) {
214 bnet_fsend(fd, levelcmd, "base", " ", 0);
216 /* L_NONE is the console, sending something off to the FD */
219 bnet_fsend(fd, levelcmd, "full", " ", 0);
222 bnet_fsend(fd, levelcmd, "differential", " ", 0);
223 send_since_time(jcr);
226 bnet_fsend(fd, levelcmd, "incremental", " ", 0);
227 send_since_time(jcr);
231 Jmsg2(jcr, M_FATAL, 0, _("Unimplemented backup level %d %c\n"),
232 jcr->JobLevel, jcr->JobLevel);
235 Dmsg1(120, ">filed: %s", fd->msg);
236 if (!response(jcr, fd, OKlevel, "Level", DISPLAY_ERROR)) {
243 * Send either an Included or an Excluded list to FD
245 static bool send_fileset(JCR *jcr)
247 FILESET *fileset = jcr->fileset;
248 BSOCK *fd = jcr->file_bsock;
254 num = fileset->num_includes;
256 num = fileset->num_excludes;
258 for (int i=0; i<num; i++) {
268 ie = fileset->include_items[i];
269 bnet_fsend(fd, "I\n");
271 ie = fileset->exclude_items[i];
272 bnet_fsend(fd, "E\n");
274 for (j=0; j<ie->num_opts; j++) {
275 FOPTS *fo = ie->opts_list[j];
276 bnet_fsend(fd, "O %s\n", fo->opts);
278 bool enhanced_wild = false;
279 for (k=0; fo->opts[k]!='\0'; k++) {
280 if (fo->opts[k]=='W') {
281 enhanced_wild = true;
286 for (k=0; k<fo->regex.size(); k++) {
287 bnet_fsend(fd, "R %s\n", fo->regex.get(k));
289 for (k=0; k<fo->regexdir.size(); k++) {
290 bnet_fsend(fd, "RD %s\n", fo->regexdir.get(k));
292 for (k=0; k<fo->regexfile.size(); k++) {
293 bnet_fsend(fd, "RF %s\n", fo->regexfile.get(k));
295 for (k=0; k<fo->wild.size(); k++) {
296 bnet_fsend(fd, "W %s\n", fo->wild.get(k));
298 for (k=0; k<fo->wilddir.size(); k++) {
299 bnet_fsend(fd, "WD %s\n", fo->wilddir.get(k));
301 for (k=0; k<fo->wildfile.size(); k++) {
302 bnet_fsend(fd, "WF %s\n", fo->wildfile.get(k));
304 for (k=0; k<fo->wildbase.size(); k++) {
305 bnet_fsend(fd, "W%c %s\n", enhanced_wild ? 'B' : 'F', fo->wildbase.get(k));
307 for (k=0; k<fo->base.size(); k++) {
308 bnet_fsend(fd, "B %s\n", fo->base.get(k));
310 for (k=0; k<fo->fstype.size(); k++) {
311 bnet_fsend(fd, "X %s\n", fo->fstype.get(k));
313 for (k=0; k<fo->drivetype.size(); k++) {
314 bnet_fsend(fd, "XD %s\n", fo->drivetype.get(k));
317 bnet_fsend(fd, "D %s\n", fo->reader);
320 bnet_fsend(fd, "T %s\n", fo->writer);
322 bnet_fsend(fd, "N\n");
325 for (j=0; j<ie->name_list.size(); j++) {
326 p = (char *)ie->name_list.get(j);
329 p++; /* skip over the | */
330 fd->msg = edit_job_codes(jcr, fd->msg, p, "");
331 bpipe = open_bpipe(fd->msg, 0, "r");
334 Jmsg(jcr, M_FATAL, 0, _("Cannot run program: %s. ERR=%s\n"),
338 bstrncpy(buf, "F ", sizeof(buf));
339 Dmsg1(500, "Opts=%s\n", buf);
340 optlen = strlen(buf);
341 while (fgets(buf+optlen, sizeof(buf)-optlen, bpipe->rfd)) {
342 fd->msglen = Mmsg(fd->msg, "%s", buf);
343 Dmsg2(500, "Inc/exc len=%d: %s", fd->msglen, fd->msg);
344 if (!bnet_send(fd)) {
345 Jmsg(jcr, M_FATAL, 0, _(">filed: write error on socket\n"));
349 if ((stat=close_bpipe(bpipe)) != 0) {
351 Jmsg(jcr, M_FATAL, 0, _("Error running program: %s. ERR=%s\n"),
352 p, be.strerror(stat));
357 p++; /* skip over < */
358 if ((ffd = fopen(p, "rb")) == NULL) {
360 Jmsg(jcr, M_FATAL, 0, _("Cannot open included file: %s. ERR=%s\n"),
364 bstrncpy(buf, "F ", sizeof(buf));
365 Dmsg1(500, "Opts=%s\n", buf);
366 optlen = strlen(buf);
367 while (fgets(buf+optlen, sizeof(buf)-optlen, ffd)) {
368 fd->msglen = Mmsg(fd->msg, "%s", buf);
369 if (!bnet_send(fd)) {
370 Jmsg(jcr, M_FATAL, 0, _(">filed: write error on socket\n"));
377 p++; /* skip over \ */
378 /* Note, fall through wanted */
380 pm_strcpy(fd->msg, "F ");
381 fd->msglen = pm_strcat(fd->msg, p);
382 Dmsg1(500, "Inc/Exc name=%s\n", fd->msg);
383 if (!bnet_send(fd)) {
384 Jmsg(jcr, M_FATAL, 0, _(">filed: write error on socket\n"));
390 bnet_fsend(fd, "N\n");
392 if (!include) { /* If we just did excludes */
393 break; /* all done */
395 include = false; /* Now do excludes */
398 bnet_sig(fd, BNET_EOD); /* end of data */
399 if (!response(jcr, fd, OKinc, "Include", DISPLAY_ERROR)) {
405 set_jcr_job_status(jcr, JS_ErrorTerminated);
412 * Send include list to File daemon
414 bool send_include_list(JCR *jcr)
416 BSOCK *fd = jcr->file_bsock;
417 if (jcr->fileset->new_include) {
418 bnet_fsend(fd, filesetcmd, jcr->fileset->enable_vss ? " vss=1" : "");
419 return send_fileset(jcr);
426 * Send exclude list to File daemon
427 * Under the new scheme, the Exclude list
428 * is part of the FileSet sent with the
429 * "include_list" above.
431 bool send_exclude_list(JCR *jcr)
438 * Send bootstrap file if any to the socket given (FD or SD).
439 * This is used for restore, verify VolumeToCatalog, and
442 bool send_bootstrap_file(JCR *jcr, BSOCK *sock)
446 const char *bootstrap = "bootstrap\n";
448 Dmsg1(400, "send_bootstrap_file: %s\n", jcr->RestoreBootstrap);
449 if (!jcr->RestoreBootstrap) {
452 bs = fopen(jcr->RestoreBootstrap, "rb");
455 Jmsg(jcr, M_FATAL, 0, _("Could not open bootstrap file %s: ERR=%s\n"),
456 jcr->RestoreBootstrap, be.strerror());
457 set_jcr_job_status(jcr, JS_ErrorTerminated);
460 bnet_fsend(sock, bootstrap);
461 while (fgets(buf, sizeof(buf), bs)) {
462 bnet_fsend(sock, "%s", buf);
464 bnet_sig(sock, BNET_EOD);
466 if (jcr->unlink_bsr) {
467 unlink(jcr->RestoreBootstrap);
468 jcr->unlink_bsr = false;
473 /* TODO: drop this with runscript.old_proto in bacula 1.42 */
474 static char runbefore[] = "RunBeforeJob %s\n";
475 static char runafter[] = "RunAfterJob %s\n";
476 static char OKRunBefore[] = "2000 OK RunBefore\n";
477 static char OKRunAfter[] = "2000 OK RunAfter\n";
479 int send_runscript_with_old_proto(JCR *jcr, int when, POOLMEM *msg)
482 Dmsg1(120, "bdird: sending old runcommand to fd '%s'\n",msg);
483 if (when & SCRIPT_Before) {
484 bnet_fsend(jcr->file_bsock, runbefore, msg);
485 ret = response(jcr, jcr->file_bsock, OKRunBefore, "ClientRunBeforeJob", DISPLAY_ERROR);
487 bnet_fsend(jcr->file_bsock, runafter, msg);
488 ret = response(jcr, jcr->file_bsock, OKRunAfter, "ClientRunAfterJob", DISPLAY_ERROR);
494 * Send RunScripts to File daemon
496 int send_runscripts_commands(JCR *jcr)
498 POOLMEM *msg = get_pool_memory(PM_FNAME);
499 BSOCK *fd = jcr->file_bsock;
501 bool launch_before_cmd = false;
502 POOLMEM *ehost = get_pool_memory(PM_FNAME);
505 Dmsg0(120, "bdird: sending runscripts to fd\n");
507 foreach_alist(cmd, jcr->job->RunScripts) {
509 if (cmd->can_run_at_level(jcr->JobLevel) && cmd->target) {
511 ehost = edit_job_codes(jcr, ehost, cmd->target, "");
512 Dmsg2(200, "bdird: runscript %s -> %s\n", cmd->target, ehost);
514 if (strcmp(ehost, jcr->client->hdr.name) == 0) {
515 pm_strcpy(msg, cmd->command);
518 Dmsg1(120, "bdird: sending runscripts to fd '%s'\n", cmd->command);
520 /* TODO: remove this with bacula 1.42 */
521 if (cmd->old_proto) {
522 result = send_runscript_with_old_proto(jcr, cmd->when, msg);
525 bnet_fsend(fd, runscript, cmd->on_success,
531 result = response(jcr, fd, OKRunScript, "RunScript", DISPLAY_ERROR);
532 launch_before_cmd=true;
536 set_jcr_job_status(jcr, JS_ErrorTerminated);
537 free_pool_memory(msg);
538 free_pool_memory(ehost);
544 send command to an other client
550 /* TODO : we have to play with other client */
551 if (launch_before_cmd) {
552 bnet_fsend(fd, runbeforenow);
553 if (!response(jcr, fd, OKRunBeforeNow, "RunBeforeNow", DISPLAY_ERROR)) {
554 set_jcr_job_status(jcr, JS_ErrorTerminated);
555 free_pool_memory(msg);
556 free_pool_memory(ehost);
560 free_pool_memory(msg);
561 free_pool_memory(ehost);
567 * Read the attributes from the File daemon for
568 * a Verify job and store them in the catalog.
570 int get_attributes_and_put_in_catalog(JCR *jcr)
576 fd = jcr->file_bsock;
577 jcr->jr.FirstIndex = 1;
578 memset(&ar, 0, sizeof(ar));
581 Dmsg0(120, "bdird: waiting to receive file attributes\n");
582 /* Pickup file attributes and digest */
583 while (!fd->errors && (n = bget_dirmsg(fd)) > 0) {
585 /*****FIXME****** improve error handling to stop only on
586 * really fatal problems, or the number of errors is too
592 char Opts_Digest[MAXSTRING]; /* either Verify opts or MD5/SHA1 digest */
593 char digest[CRYPTO_DIGEST_MAX_SIZE];
595 jcr->fname = check_pool_memory_size(jcr->fname, fd->msglen);
596 if ((len = sscanf(fd->msg, "%ld %d %s", &file_index, &stream, Opts_Digest)) != 3) {
597 Jmsg(jcr, M_FATAL, 0, _("<filed: bad attributes, expected 3 fields got %d\n"
598 "msglen=%d msg=%s\n"), len, fd->msglen, fd->msg);
599 set_jcr_job_status(jcr, JS_ErrorTerminated);
603 skip_nonspaces(&p); /* skip FileIndex */
605 skip_nonspaces(&p); /* skip Stream */
607 skip_nonspaces(&p); /* skip Opts_SHA1 */
608 p++; /* skip space */
611 *fn++ = *p++; /* copy filename */
613 *fn = *p++; /* term filename and point to attribs */
616 if (stream == STREAM_UNIX_ATTRIBUTES || stream == STREAM_UNIX_ATTRIBUTES_EX) {
618 jcr->FileIndex = file_index;
620 ar.fname = jcr->fname;
621 ar.FileIndex = file_index;
624 ar.JobId = jcr->JobId;
625 ar.ClientId = jcr->ClientId;
629 ar.DigestType = CRYPTO_DIGEST_NONE;
631 Dmsg2(111, "dird<filed: stream=%d %s\n", stream, jcr->fname);
632 Dmsg1(120, "dird<filed: attr=%s\n", attr);
634 if (!db_create_file_attributes_record(jcr, jcr->db, &ar)) {
635 Jmsg1(jcr, M_ERROR, 0, "%s", db_strerror(jcr->db));
636 set_jcr_job_status(jcr, JS_Error);
639 jcr->FileId = ar.FileId;
640 } else if (crypto_digest_stream_type(stream) != CRYPTO_DIGEST_NONE) {
641 if (jcr->FileIndex != (uint32_t)file_index) {
642 Jmsg3(jcr, M_ERROR, 0, _("%s index %d not same as attributes %d\n"),
643 stream_to_ascii(stream), file_index, jcr->FileIndex);
644 set_jcr_job_status(jcr, JS_Error);
647 db_escape_string(digest, Opts_Digest, strlen(Opts_Digest));
648 Dmsg2(120, "DigestLen=%d Digest=%s\n", strlen(digest), digest);
649 if (!db_add_digest_to_file_record(jcr, jcr->db, jcr->FileId, digest,
650 crypto_digest_stream_type(stream))) {
651 Jmsg1(jcr, M_ERROR, 0, "%s", db_strerror(jcr->db));
652 set_jcr_job_status(jcr, JS_Error);
655 jcr->jr.JobFiles = jcr->JobFiles = file_index;
656 jcr->jr.LastIndex = file_index;
658 if (is_bnet_error(fd)) {
659 Jmsg1(jcr, M_FATAL, 0, _("<filed: Network error getting attributes. ERR=%s\n"),
661 set_jcr_job_status(jcr, JS_ErrorTerminated);
665 set_jcr_job_status(jcr, JS_Terminated);