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-2005 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.
33 /* Commands sent to File daemon */
34 static char filesetcmd[] = "fileset%s\n"; /* set full fileset */
35 static char jobcmd[] = "JobId=%d Job=%s SDid=%u SDtime=%u Authorization=%s\n";
36 /* Note, mtime_only is not used here -- implemented as file option */
37 static char levelcmd[] = "level = %s%s mtime_only=%d\n";
38 static char runbefore[] = "RunBeforeJob %s\n";
39 static char runafter[] = "RunAfterJob %s\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 OKbootstrap[] = "2000 OK bootstrap\n";
46 static char OKlevel[] = "2000 OK level\n";
47 static char OKRunBefore[] = "2000 OK RunBefore\n";
48 static char OKRunAfter[] = "2000 OK RunAfter\n";
50 /* Forward referenced functions */
52 /* External functions */
53 extern int debug_level;
54 extern DIRRES *director;
55 extern int FDConnectTimeout;
61 * Open connection with File daemon.
62 * Try connecting every retry_interval (default 10 sec), and
63 * give up after max_retry_time (default 30 mins).
66 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, jcr->JobId, 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, "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 int 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, "r")) == 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 File daemon.
425 * This is used for restore and verify VolumeToCatalog
427 bool send_bootstrap_file(JCR *jcr)
431 BSOCK *fd = jcr->file_bsock;
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, "r");
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(fd, bootstrap);
447 while (fgets(buf, sizeof(buf), bs)) {
448 bnet_fsend(fd, "%s", buf);
450 bnet_sig(fd, BNET_EOD);
452 if (jcr->unlink_bsr) {
453 unlink(jcr->RestoreBootstrap);
454 jcr->unlink_bsr = false;
456 if (!response(jcr, fd, OKbootstrap, "Bootstrap", DISPLAY_ERROR)) {
457 set_jcr_job_status(jcr, JS_ErrorTerminated);
464 * Send ClientRunBeforeJob and ClientRunAfterJob to File daemon
466 int send_run_before_and_after_commands(JCR *jcr)
468 POOLMEM *msg = get_pool_memory(PM_FNAME);
469 BSOCK *fd = jcr->file_bsock;
470 if (jcr->job->ClientRunBeforeJob) {
471 pm_strcpy(msg, jcr->job->ClientRunBeforeJob);
473 bnet_fsend(fd, runbefore, msg);
474 if (!response(jcr, fd, OKRunBefore, "ClientRunBeforeJob", DISPLAY_ERROR)) {
475 set_jcr_job_status(jcr, JS_ErrorTerminated);
476 free_pool_memory(msg);
480 if (jcr->job->ClientRunAfterJob) {
481 fd->msglen = pm_strcpy(msg, jcr->job->ClientRunAfterJob);
483 bnet_fsend(fd, runafter, msg);
484 if (!response(jcr, fd, OKRunAfter, "ClientRunAfterJob", DISPLAY_ERROR)) {
485 set_jcr_job_status(jcr, JS_ErrorTerminated);
486 free_pool_memory(msg);
490 free_pool_memory(msg);
496 * Read the attributes from the File daemon for
497 * a Verify job and store them in the catalog.
499 int get_attributes_and_put_in_catalog(JCR *jcr)
505 fd = jcr->file_bsock;
506 jcr->jr.FirstIndex = 1;
507 memset(&ar, 0, sizeof(ar));
510 Dmsg0(120, "bdird: waiting to receive file attributes\n");
511 /* Pickup file attributes and signature */
512 while (!fd->errors && (n = bget_dirmsg(fd)) > 0) {
514 /*****FIXME****** improve error handling to stop only on
515 * really fatal problems, or the number of errors is too
521 char Opts_SIG[MAXSTRING]; /* either Verify opts or MD5/SHA1 signature */
524 jcr->fname = check_pool_memory_size(jcr->fname, fd->msglen);
525 if ((len = sscanf(fd->msg, "%ld %d %s", &file_index, &stream, Opts_SIG)) != 3) {
526 Jmsg(jcr, M_FATAL, 0, _("<filed: bad attributes, expected 3 fields got %d\n"
527 "msglen=%d msg=%s\n"), len, fd->msglen, fd->msg);
528 set_jcr_job_status(jcr, JS_ErrorTerminated);
532 skip_nonspaces(&p); /* skip FileIndex */
534 skip_nonspaces(&p); /* skip Stream */
536 skip_nonspaces(&p); /* skip Opts_SHA1 */
537 p++; /* skip space */
540 *fn++ = *p++; /* copy filename */
542 *fn = *p++; /* term filename and point to attribs */
545 if (stream == STREAM_UNIX_ATTRIBUTES || stream == STREAM_UNIX_ATTRIBUTES_EX) {
547 jcr->FileIndex = file_index;
549 ar.fname = jcr->fname;
550 ar.FileIndex = file_index;
553 ar.JobId = jcr->JobId;
554 ar.ClientId = jcr->ClientId;
560 Dmsg2(111, "dird<filed: stream=%d %s\n", stream, jcr->fname);
561 Dmsg1(120, "dird<filed: attr=%s\n", attr);
563 if (!db_create_file_attributes_record(jcr, jcr->db, &ar)) {
564 Jmsg1(jcr, M_ERROR, 0, "%s", db_strerror(jcr->db));
565 set_jcr_job_status(jcr, JS_Error);
568 jcr->FileId = ar.FileId;
569 } else if (stream == STREAM_MD5_SIGNATURE || stream == STREAM_SHA1_SIGNATURE) {
570 if (jcr->FileIndex != (uint32_t)file_index) {
571 Jmsg2(jcr, M_ERROR, 0, _("MD5/SHA1 index %d not same as attributes %d\n"),
572 file_index, jcr->FileIndex);
573 set_jcr_job_status(jcr, JS_Error);
576 db_escape_string(SIG, Opts_SIG, strlen(Opts_SIG));
577 Dmsg2(120, "SIGlen=%d SIG=%s\n", strlen(SIG), SIG);
578 if (!db_add_SIG_to_file_record(jcr, jcr->db, jcr->FileId, SIG,
579 stream==STREAM_MD5_SIGNATURE?MD5_SIG:SHA1_SIG)) {
580 Jmsg1(jcr, M_ERROR, 0, "%s", db_strerror(jcr->db));
581 set_jcr_job_status(jcr, JS_Error);
584 jcr->jr.JobFiles = jcr->JobFiles = file_index;
585 jcr->jr.LastIndex = file_index;
587 if (is_bnet_error(fd)) {
588 Jmsg1(jcr, M_FATAL, 0, _("<filed: Network error getting attributes. ERR=%s\n"),
590 set_jcr_job_status(jcr, JS_ErrorTerminated);
594 set_jcr_job_status(jcr, JS_Terminated);