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 int debug_level;
53 extern DIRRES *director;
54 extern int FDConnectTimeout;
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).
65 int connect_to_file_daemon(JCR *jcr, int retry_interval, int max_retry_time,
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);
76 set_jcr_job_status(jcr, JS_ErrorTerminated);
79 Dmsg0(10, "Opened connection with File daemon\n");
81 fd = jcr->file_bsock; /* use existing connection */
83 fd->res = (RES *)jcr->client; /* save resource in BSOCK */
85 set_jcr_job_status(jcr, JS_Running);
87 if (!authenticate_file_daemon(jcr)) {
88 set_jcr_job_status(jcr, JS_ErrorTerminated);
93 * Now send JobId and authorization key
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));
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);
108 } else if (jcr->db) {
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));
122 Jmsg(jcr, M_FATAL, 0, _("FD gave bad response to JobId command: %s\n"),
124 set_jcr_job_status(jcr, JS_ErrorTerminated);
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.
137 void get_level_since_time(JCR *jcr, char *since, int since_len)
143 if (jcr->stime && jcr->stime[0]) {
144 bstrncpy(since, _(", since="), since_len);
145 bstrncat(since, jcr->stime, since_len);
150 jcr->stime = get_pool_memory(PM_MESSAGE);
153 /* Lookup the last FULL backup job to get the time/date for a
154 * differential or incremental save.
156 switch (jcr->JobLevel) {
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;
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;
180 bstrncpy(since, _(", since="), since_len);
181 bstrncat(since, jcr->stime, since_len);
183 jcr->jr.JobId = jcr->JobId;
186 Dmsg2(100, "Level=%c last start time=%s\n", jcr->JobLevel, jcr->stime);
189 static void send_since_time(JCR *jcr)
191 BSOCK *fd = jcr->file_bsock;
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);
204 * Send level command to FD.
205 * Used for backup jobs and estimate command.
207 bool send_level_command(JCR *jcr)
209 BSOCK *fd = jcr->file_bsock;
211 * Send Level command to File daemon
213 switch (jcr->JobLevel) {
215 bnet_fsend(fd, levelcmd, "base", " ", 0);
217 /* L_NONE is the console, sending something off to the FD */
220 bnet_fsend(fd, levelcmd, "full", " ", 0);
223 bnet_fsend(fd, levelcmd, "differential", " ", 0);
224 send_since_time(jcr);
227 bnet_fsend(fd, levelcmd, "incremental", " ", 0);
228 send_since_time(jcr);
232 Jmsg2(jcr, M_FATAL, 0, _("Unimplemented backup level %d %c\n"),
233 jcr->JobLevel, jcr->JobLevel);
236 Dmsg1(120, ">filed: %s", fd->msg);
237 if (!response(jcr, fd, OKlevel, "Level", DISPLAY_ERROR)) {
244 * Send either an Included or an Excluded list to FD
246 static bool send_fileset(JCR *jcr)
248 FILESET *fileset = jcr->fileset;
249 BSOCK *fd = jcr->file_bsock;
255 num = fileset->num_includes;
257 num = fileset->num_excludes;
259 for (int i=0; i<num; i++) {
269 ie = fileset->include_items[i];
270 bnet_fsend(fd, "I\n");
272 ie = fileset->exclude_items[i];
273 bnet_fsend(fd, "E\n");
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));
281 for (k=0; k<fo->regexdir.size(); k++) {
282 bnet_fsend(fd, "RD %s\n", fo->regexdir.get(k));
284 for (k=0; k<fo->regexfile.size(); k++) {
285 bnet_fsend(fd, "RF %s\n", fo->regexfile.get(k));
287 for (k=0; k<fo->wild.size(); k++) {
288 bnet_fsend(fd, "W %s\n", fo->wild.get(k));
290 for (k=0; k<fo->wilddir.size(); k++) {
291 bnet_fsend(fd, "WD %s\n", fo->wilddir.get(k));
293 for (k=0; k<fo->wildfile.size(); k++) {
294 bnet_fsend(fd, "WF %s\n", fo->wildfile.get(k));
296 for (k=0; k<fo->base.size(); k++) {
297 bnet_fsend(fd, "B %s\n", fo->base.get(k));
299 for (k=0; k<fo->fstype.size(); k++) {
300 bnet_fsend(fd, "X %s\n", fo->fstype.get(k));
303 bnet_fsend(fd, "D %s\n", fo->reader);
306 bnet_fsend(fd, "T %s\n", fo->writer);
308 bnet_fsend(fd, "N\n");
311 for (j=0; j<ie->name_list.size(); j++) {
312 p = (char *)ie->name_list.get(j);
315 p++; /* skip over the | */
316 fd->msg = edit_job_codes(jcr, fd->msg, p, "");
317 bpipe = open_bpipe(fd->msg, 0, "r");
320 Jmsg(jcr, M_FATAL, 0, _("Cannot run program: %s. ERR=%s\n"),
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"));
335 if ((stat=close_bpipe(bpipe)) != 0) {
337 Jmsg(jcr, M_FATAL, 0, _("Error running program: %s. ERR=%s\n"),
338 p, be.strerror(stat));
343 p++; /* skip over < */
344 if ((ffd = fopen(p, "rb")) == NULL) {
346 Jmsg(jcr, M_FATAL, 0, _("Cannot open included file: %s. ERR=%s\n"),
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"));
363 p++; /* skip over \ */
364 /* Note, fall through wanted */
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"));
376 bnet_fsend(fd, "N\n");
378 if (!include) { /* If we just did excludes */
379 break; /* all done */
381 include = false; /* Now do excludes */
384 bnet_sig(fd, BNET_EOD); /* end of data */
385 if (!response(jcr, fd, OKinc, "Include", DISPLAY_ERROR)) {
391 set_jcr_job_status(jcr, JS_ErrorTerminated);
398 * Send include list to File daemon
400 bool send_include_list(JCR *jcr)
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);
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.
417 bool send_exclude_list(JCR *jcr)
424 * Send bootstrap file if any to the socket given (FD or SD).
425 * This is used for restore, verify VolumeToCatalog, and
428 bool send_bootstrap_file(JCR *jcr, BSOCK *sock)
432 const char *bootstrap = "bootstrap\n";
434 Dmsg1(400, "send_bootstrap_file: %s\n", jcr->RestoreBootstrap);
435 if (!jcr->RestoreBootstrap) {
438 bs = fopen(jcr->RestoreBootstrap, "rb");
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);
446 bnet_fsend(sock, bootstrap);
447 while (fgets(buf, sizeof(buf), bs)) {
448 bnet_fsend(sock, "%s", buf);
450 bnet_sig(sock, BNET_EOD);
452 if (jcr->unlink_bsr) {
453 unlink(jcr->RestoreBootstrap);
454 jcr->unlink_bsr = false;
460 * Send RunScripts to File daemon
462 int send_runscripts_commands(JCR *jcr)
464 POOLMEM *msg = get_pool_memory(PM_FNAME);
465 BSOCK *fd = jcr->file_bsock;
467 bool launch_before_cmd = false;
468 POOLMEM *ehost = get_pool_memory(PM_FNAME);
470 Dmsg0(120, "bdird: sending runscripts to fd\n");
472 foreach_alist(cmd, jcr->job->RunScripts) {
474 if (cmd->can_run_at_level(jcr->JobLevel) && cmd->target) {
476 ehost = edit_job_codes(jcr, ehost, cmd->target, "");
477 Dmsg2(200, "bdird: runscript %s -> %s\n", cmd->target, ehost);
479 if (strcmp(ehost, jcr->client->hdr.name) == 0) {
480 pm_strcpy(msg, cmd->command);
482 bnet_fsend(fd, runscript, cmd->on_success,
488 Dmsg1(120, "bdird: sending runscripts to fd '%s'\n", cmd->command);
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);
496 launch_before_cmd=true;
500 send command to an other client
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);
516 free_pool_memory(msg);
517 free_pool_memory(ehost);
523 * Read the attributes from the File daemon for
524 * a Verify job and store them in the catalog.
526 int get_attributes_and_put_in_catalog(JCR *jcr)
532 fd = jcr->file_bsock;
533 jcr->jr.FirstIndex = 1;
534 memset(&ar, 0, sizeof(ar));
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) {
541 /*****FIXME****** improve error handling to stop only on
542 * really fatal problems, or the number of errors is too
548 char Opts_Digest[MAXSTRING]; /* either Verify opts or MD5/SHA1 digest */
549 char digest[CRYPTO_DIGEST_MAX_SIZE];
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);
559 skip_nonspaces(&p); /* skip FileIndex */
561 skip_nonspaces(&p); /* skip Stream */
563 skip_nonspaces(&p); /* skip Opts_SHA1 */
564 p++; /* skip space */
567 *fn++ = *p++; /* copy filename */
569 *fn = *p++; /* term filename and point to attribs */
572 if (stream == STREAM_UNIX_ATTRIBUTES || stream == STREAM_UNIX_ATTRIBUTES_EX) {
574 jcr->FileIndex = file_index;
576 ar.fname = jcr->fname;
577 ar.FileIndex = file_index;
580 ar.JobId = jcr->JobId;
581 ar.ClientId = jcr->ClientId;
585 ar.DigestType = CRYPTO_DIGEST_NONE;
587 Dmsg2(111, "dird<filed: stream=%d %s\n", stream, jcr->fname);
588 Dmsg1(120, "dird<filed: attr=%s\n", attr);
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);
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);
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);
611 jcr->jr.JobFiles = jcr->JobFiles = file_index;
612 jcr->jr.LastIndex = file_index;
614 if (is_bnet_error(fd)) {
615 Jmsg1(jcr, M_FATAL, 0, _("<filed: Network error getting attributes. ERR=%s\n"),
617 set_jcr_job_status(jcr, JS_ErrorTerminated);
621 set_jcr_job_status(jcr, JS_Terminated);