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;
474 * Send RunScripts to File daemon
476 int send_runscripts_commands(JCR *jcr)
478 POOLMEM *msg = get_pool_memory(PM_FNAME);
479 BSOCK *fd = jcr->file_bsock;
481 bool launch_before_cmd = false;
482 POOLMEM *ehost = get_pool_memory(PM_FNAME);
484 Dmsg0(120, "bdird: sending runscripts to fd\n");
486 foreach_alist(cmd, jcr->job->RunScripts) {
488 if (cmd->can_run_at_level(jcr->JobLevel) && cmd->target) {
490 ehost = edit_job_codes(jcr, ehost, cmd->target, "");
491 Dmsg2(200, "bdird: runscript %s -> %s\n", cmd->target, ehost);
493 if (strcmp(ehost, jcr->client->hdr.name) == 0) {
494 pm_strcpy(msg, cmd->command);
496 bnet_fsend(fd, runscript, cmd->on_success,
502 Dmsg1(120, "bdird: sending runscripts to fd '%s'\n", cmd->command);
504 if (!response(jcr, fd, OKRunScript, "RunScript", DISPLAY_ERROR)) {
505 set_jcr_job_status(jcr, JS_ErrorTerminated);
506 free_pool_memory(msg);
507 free_pool_memory(ehost);
510 launch_before_cmd=true;
514 send command to an other client
520 /* TODO : we have to play with other client */
521 if (launch_before_cmd) {
522 bnet_fsend(fd, runbeforenow);
523 if (!response(jcr, fd, OKRunBeforeNow, "RunBeforeNow", DISPLAY_ERROR)) {
524 set_jcr_job_status(jcr, JS_ErrorTerminated);
525 free_pool_memory(msg);
526 free_pool_memory(ehost);
530 free_pool_memory(msg);
531 free_pool_memory(ehost);
537 * Read the attributes from the File daemon for
538 * a Verify job and store them in the catalog.
540 int get_attributes_and_put_in_catalog(JCR *jcr)
546 fd = jcr->file_bsock;
547 jcr->jr.FirstIndex = 1;
548 memset(&ar, 0, sizeof(ar));
551 Dmsg0(120, "bdird: waiting to receive file attributes\n");
552 /* Pickup file attributes and digest */
553 while (!fd->errors && (n = bget_dirmsg(fd)) > 0) {
555 /*****FIXME****** improve error handling to stop only on
556 * really fatal problems, or the number of errors is too
562 char Opts_Digest[MAXSTRING]; /* either Verify opts or MD5/SHA1 digest */
563 char digest[CRYPTO_DIGEST_MAX_SIZE];
565 jcr->fname = check_pool_memory_size(jcr->fname, fd->msglen);
566 if ((len = sscanf(fd->msg, "%ld %d %s", &file_index, &stream, Opts_Digest)) != 3) {
567 Jmsg(jcr, M_FATAL, 0, _("<filed: bad attributes, expected 3 fields got %d\n"
568 "msglen=%d msg=%s\n"), len, fd->msglen, fd->msg);
569 set_jcr_job_status(jcr, JS_ErrorTerminated);
573 skip_nonspaces(&p); /* skip FileIndex */
575 skip_nonspaces(&p); /* skip Stream */
577 skip_nonspaces(&p); /* skip Opts_SHA1 */
578 p++; /* skip space */
581 *fn++ = *p++; /* copy filename */
583 *fn = *p++; /* term filename and point to attribs */
586 if (stream == STREAM_UNIX_ATTRIBUTES || stream == STREAM_UNIX_ATTRIBUTES_EX) {
588 jcr->FileIndex = file_index;
590 ar.fname = jcr->fname;
591 ar.FileIndex = file_index;
594 ar.JobId = jcr->JobId;
595 ar.ClientId = jcr->ClientId;
599 ar.DigestType = CRYPTO_DIGEST_NONE;
601 Dmsg2(111, "dird<filed: stream=%d %s\n", stream, jcr->fname);
602 Dmsg1(120, "dird<filed: attr=%s\n", attr);
604 if (!db_create_file_attributes_record(jcr, jcr->db, &ar)) {
605 Jmsg1(jcr, M_ERROR, 0, "%s", db_strerror(jcr->db));
606 set_jcr_job_status(jcr, JS_Error);
609 jcr->FileId = ar.FileId;
610 } else if (crypto_digest_stream_type(stream) != CRYPTO_DIGEST_NONE) {
611 if (jcr->FileIndex != (uint32_t)file_index) {
612 Jmsg3(jcr, M_ERROR, 0, _("%s index %d not same as attributes %d\n"),
613 stream_to_ascii(stream), file_index, jcr->FileIndex);
614 set_jcr_job_status(jcr, JS_Error);
617 db_escape_string(digest, Opts_Digest, strlen(Opts_Digest));
618 Dmsg2(120, "DigestLen=%d Digest=%s\n", strlen(digest), digest);
619 if (!db_add_digest_to_file_record(jcr, jcr->db, jcr->FileId, digest,
620 crypto_digest_stream_type(stream))) {
621 Jmsg1(jcr, M_ERROR, 0, "%s", db_strerror(jcr->db));
622 set_jcr_job_status(jcr, JS_Error);
625 jcr->jr.JobFiles = jcr->JobFiles = file_index;
626 jcr->jr.LastIndex = file_index;
628 if (is_bnet_error(fd)) {
629 Jmsg1(jcr, M_FATAL, 0, _("<filed: Network error getting attributes. ERR=%s\n"),
631 set_jcr_job_status(jcr, JS_ErrorTerminated);
635 set_jcr_job_status(jcr, JS_Terminated);