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. 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. 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);
277 for (k=0; k<fo->regex.size(); k++) {
278 bnet_fsend(fd, "R %s\n", fo->regex.get(k));
280 for (k=0; k<fo->regexdir.size(); k++) {
281 bnet_fsend(fd, "RD %s\n", fo->regexdir.get(k));
283 for (k=0; k<fo->regexfile.size(); k++) {
284 bnet_fsend(fd, "RF %s\n", fo->regexfile.get(k));
286 for (k=0; k<fo->wild.size(); k++) {
287 bnet_fsend(fd, "W %s\n", fo->wild.get(k));
289 for (k=0; k<fo->wilddir.size(); k++) {
290 bnet_fsend(fd, "WD %s\n", fo->wilddir.get(k));
292 for (k=0; k<fo->wildfile.size(); k++) {
293 bnet_fsend(fd, "WF %s\n", fo->wildfile.get(k));
295 for (k=0; k<fo->base.size(); k++) {
296 bnet_fsend(fd, "B %s\n", fo->base.get(k));
298 for (k=0; k<fo->fstype.size(); k++) {
299 bnet_fsend(fd, "X %s\n", fo->fstype.get(k));
302 bnet_fsend(fd, "D %s\n", fo->reader);
305 bnet_fsend(fd, "T %s\n", fo->writer);
307 bnet_fsend(fd, "N\n");
310 for (j=0; j<ie->name_list.size(); j++) {
311 p = (char *)ie->name_list.get(j);
314 p++; /* skip over the | */
315 fd->msg = edit_job_codes(jcr, fd->msg, p, "");
316 bpipe = open_bpipe(fd->msg, 0, "r");
319 Jmsg(jcr, M_FATAL, 0, _("Cannot run program: %s. ERR=%s\n"),
323 bstrncpy(buf, "F ", sizeof(buf));
324 Dmsg1(500, "Opts=%s\n", buf);
325 optlen = strlen(buf);
326 while (fgets(buf+optlen, sizeof(buf)-optlen, bpipe->rfd)) {
327 fd->msglen = Mmsg(fd->msg, "%s", buf);
328 Dmsg2(500, "Inc/exc len=%d: %s", fd->msglen, fd->msg);
329 if (!bnet_send(fd)) {
330 Jmsg(jcr, M_FATAL, 0, _(">filed: write error on socket\n"));
334 if ((stat=close_bpipe(bpipe)) != 0) {
336 Jmsg(jcr, M_FATAL, 0, _("Error running program: %s. ERR=%s\n"),
337 p, be.strerror(stat));
342 p++; /* skip over < */
343 if ((ffd = fopen(p, "rb")) == NULL) {
345 Jmsg(jcr, M_FATAL, 0, _("Cannot open included file: %s. ERR=%s\n"),
349 bstrncpy(buf, "F ", sizeof(buf));
350 Dmsg1(500, "Opts=%s\n", buf);
351 optlen = strlen(buf);
352 while (fgets(buf+optlen, sizeof(buf)-optlen, ffd)) {
353 fd->msglen = Mmsg(fd->msg, "%s", buf);
354 if (!bnet_send(fd)) {
355 Jmsg(jcr, M_FATAL, 0, _(">filed: write error on socket\n"));
362 p++; /* skip over \ */
363 /* Note, fall through wanted */
365 pm_strcpy(fd->msg, "F ");
366 fd->msglen = pm_strcat(fd->msg, p);
367 Dmsg1(500, "Inc/Exc name=%s\n", fd->msg);
368 if (!bnet_send(fd)) {
369 Jmsg(jcr, M_FATAL, 0, _(">filed: write error on socket\n"));
375 bnet_fsend(fd, "N\n");
377 if (!include) { /* If we just did excludes */
378 break; /* all done */
380 include = false; /* Now do excludes */
383 bnet_sig(fd, BNET_EOD); /* end of data */
384 if (!response(jcr, fd, OKinc, "Include", DISPLAY_ERROR)) {
390 set_jcr_job_status(jcr, JS_ErrorTerminated);
397 * Send include list to File daemon
399 bool send_include_list(JCR *jcr)
401 BSOCK *fd = jcr->file_bsock;
402 if (jcr->fileset->new_include) {
403 bnet_fsend(fd, filesetcmd, jcr->fileset->enable_vss ? " vss=1" : "");
404 return send_fileset(jcr);
411 * Send exclude list to File daemon
412 * Under the new scheme, the Exclude list
413 * is part of the FileSet sent with the
414 * "include_list" above.
416 bool send_exclude_list(JCR *jcr)
423 * Send bootstrap file if any to the socket given (FD or SD).
424 * This is used for restore, verify VolumeToCatalog, and
427 bool send_bootstrap_file(JCR *jcr, BSOCK *sock)
431 const char *bootstrap = "bootstrap\n";
433 Dmsg1(400, "send_bootstrap_file: %s\n", jcr->RestoreBootstrap);
434 if (!jcr->RestoreBootstrap) {
437 bs = fopen(jcr->RestoreBootstrap, "rb");
440 Jmsg(jcr, M_FATAL, 0, _("Could not open bootstrap file %s: ERR=%s\n"),
441 jcr->RestoreBootstrap, be.strerror());
442 set_jcr_job_status(jcr, JS_ErrorTerminated);
445 bnet_fsend(sock, bootstrap);
446 while (fgets(buf, sizeof(buf), bs)) {
447 bnet_fsend(sock, "%s", buf);
449 bnet_sig(sock, BNET_EOD);
451 if (jcr->unlink_bsr) {
452 unlink(jcr->RestoreBootstrap);
453 jcr->unlink_bsr = false;
459 * Send RunScripts to File daemon
461 int send_runscripts_commands(JCR *jcr)
463 POOLMEM *msg = get_pool_memory(PM_FNAME);
464 BSOCK *fd = jcr->file_bsock;
466 bool launch_before_cmd = false;
467 POOLMEM *ehost = get_pool_memory(PM_FNAME);
469 Dmsg0(120, "bdird: sending runscripts to fd\n");
471 foreach_alist(cmd, jcr->job->RunScripts) {
473 if (cmd->can_run_at_level(jcr->JobLevel) && cmd->target) {
475 ehost = edit_job_codes(jcr, ehost, cmd->target, "");
476 Dmsg2(200, "bdird: runscript %s -> %s\n", cmd->target, ehost);
478 if (strcmp(ehost, jcr->client->hdr.name) == 0) {
479 pm_strcpy(msg, cmd->command);
481 bnet_fsend(fd, runscript, cmd->on_success,
487 Dmsg1(120, "bdird: sending runscripts to fd '%s'\n", cmd->command);
489 if (!response(jcr, fd, OKRunScript, "RunScript", DISPLAY_ERROR)) {
490 set_jcr_job_status(jcr, JS_ErrorTerminated);
491 free_pool_memory(msg);
492 free_pool_memory(ehost);
495 launch_before_cmd=true;
499 send command to an other client
505 /* TODO : we have to play with other client */
506 if (launch_before_cmd) {
507 bnet_fsend(fd, runbeforenow);
508 if (!response(jcr, fd, OKRunBeforeNow, "RunBeforeNow", DISPLAY_ERROR)) {
509 set_jcr_job_status(jcr, JS_ErrorTerminated);
510 free_pool_memory(msg);
511 free_pool_memory(ehost);
515 free_pool_memory(msg);
516 free_pool_memory(ehost);
522 * Read the attributes from the File daemon for
523 * a Verify job and store them in the catalog.
525 int get_attributes_and_put_in_catalog(JCR *jcr)
531 fd = jcr->file_bsock;
532 jcr->jr.FirstIndex = 1;
533 memset(&ar, 0, sizeof(ar));
536 Dmsg0(120, "bdird: waiting to receive file attributes\n");
537 /* Pickup file attributes and digest */
538 while (!fd->errors && (n = bget_dirmsg(fd)) > 0) {
540 /*****FIXME****** improve error handling to stop only on
541 * really fatal problems, or the number of errors is too
547 char Opts_Digest[MAXSTRING]; /* either Verify opts or MD5/SHA1 digest */
548 char digest[CRYPTO_DIGEST_MAX_SIZE];
550 jcr->fname = check_pool_memory_size(jcr->fname, fd->msglen);
551 if ((len = sscanf(fd->msg, "%ld %d %s", &file_index, &stream, Opts_Digest)) != 3) {
552 Jmsg(jcr, M_FATAL, 0, _("<filed: bad attributes, expected 3 fields got %d\n"
553 "msglen=%d msg=%s\n"), len, fd->msglen, fd->msg);
554 set_jcr_job_status(jcr, JS_ErrorTerminated);
558 skip_nonspaces(&p); /* skip FileIndex */
560 skip_nonspaces(&p); /* skip Stream */
562 skip_nonspaces(&p); /* skip Opts_SHA1 */
563 p++; /* skip space */
566 *fn++ = *p++; /* copy filename */
568 *fn = *p++; /* term filename and point to attribs */
571 if (stream == STREAM_UNIX_ATTRIBUTES || stream == STREAM_UNIX_ATTRIBUTES_EX) {
573 jcr->FileIndex = file_index;
575 ar.fname = jcr->fname;
576 ar.FileIndex = file_index;
579 ar.JobId = jcr->JobId;
580 ar.ClientId = jcr->ClientId;
584 ar.DigestType = CRYPTO_DIGEST_NONE;
586 Dmsg2(111, "dird<filed: stream=%d %s\n", stream, jcr->fname);
587 Dmsg1(120, "dird<filed: attr=%s\n", attr);
589 if (!db_create_file_attributes_record(jcr, jcr->db, &ar)) {
590 Jmsg1(jcr, M_ERROR, 0, "%s", db_strerror(jcr->db));
591 set_jcr_job_status(jcr, JS_Error);
594 jcr->FileId = ar.FileId;
595 } else if (crypto_digest_stream_type(stream) != CRYPTO_DIGEST_NONE) {
596 if (jcr->FileIndex != (uint32_t)file_index) {
597 Jmsg3(jcr, M_ERROR, 0, _("%s index %d not same as attributes %d\n"),
598 stream_to_ascii(stream), file_index, jcr->FileIndex);
599 set_jcr_job_status(jcr, JS_Error);
602 db_escape_string(digest, Opts_Digest, strlen(Opts_Digest));
603 Dmsg2(120, "DigestLen=%d Digest=%s\n", strlen(digest), digest);
604 if (!db_add_digest_to_file_record(jcr, jcr->db, jcr->FileId, digest,
605 crypto_digest_stream_type(stream))) {
606 Jmsg1(jcr, M_ERROR, 0, "%s", db_strerror(jcr->db));
607 set_jcr_job_status(jcr, JS_Error);
610 jcr->jr.JobFiles = jcr->JobFiles = file_index;
611 jcr->jr.LastIndex = file_index;
613 if (is_bnet_error(fd)) {
614 Jmsg1(jcr, M_FATAL, 0, _("<filed: Network error getting attributes. ERR=%s\n"),
616 set_jcr_job_status(jcr, JS_ErrorTerminated);
620 set_jcr_job_status(jcr, JS_Terminated);