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=%d 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 runbefore[] = "RunBeforeJob %s\n";
40 static char runafter[] = "RunAfterJob %s\n";
43 /* Responses received from File daemon */
44 static char OKinc[] = "2000 OK include\n";
45 static char OKjob[] = "2000 OK Job";
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 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, "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 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, "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(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 ClientRunBeforeJob and ClientRunAfterJob to File daemon
462 int send_run_before_and_after_commands(JCR *jcr)
464 POOLMEM *msg = get_pool_memory(PM_FNAME);
465 BSOCK *fd = jcr->file_bsock;
466 if (jcr->job->ClientRunBeforeJob) {
467 pm_strcpy(msg, jcr->job->ClientRunBeforeJob);
469 bnet_fsend(fd, runbefore, msg);
470 if (!response(jcr, fd, OKRunBefore, "ClientRunBeforeJob", DISPLAY_ERROR)) {
471 set_jcr_job_status(jcr, JS_ErrorTerminated);
472 free_pool_memory(msg);
476 if (jcr->job->ClientRunAfterJob) {
477 fd->msglen = pm_strcpy(msg, jcr->job->ClientRunAfterJob);
479 bnet_fsend(fd, runafter, msg);
480 if (!response(jcr, fd, OKRunAfter, "ClientRunAfterJob", DISPLAY_ERROR)) {
481 set_jcr_job_status(jcr, JS_ErrorTerminated);
482 free_pool_memory(msg);
486 free_pool_memory(msg);
492 * Read the attributes from the File daemon for
493 * a Verify job and store them in the catalog.
495 int get_attributes_and_put_in_catalog(JCR *jcr)
501 fd = jcr->file_bsock;
502 jcr->jr.FirstIndex = 1;
503 memset(&ar, 0, sizeof(ar));
506 Dmsg0(120, "bdird: waiting to receive file attributes\n");
507 /* Pickup file attributes and digest */
508 while (!fd->errors && (n = bget_dirmsg(fd)) > 0) {
510 /*****FIXME****** improve error handling to stop only on
511 * really fatal problems, or the number of errors is too
517 char Opts_Digest[MAXSTRING]; /* either Verify opts or MD5/SHA1 digest */
518 char digest[CRYPTO_DIGEST_MAX_SIZE];
520 jcr->fname = check_pool_memory_size(jcr->fname, fd->msglen);
521 if ((len = sscanf(fd->msg, "%ld %d %s", &file_index, &stream, Opts_Digest)) != 3) {
522 Jmsg(jcr, M_FATAL, 0, _("<filed: bad attributes, expected 3 fields got %d\n"
523 "msglen=%d msg=%s\n"), len, fd->msglen, fd->msg);
524 set_jcr_job_status(jcr, JS_ErrorTerminated);
528 skip_nonspaces(&p); /* skip FileIndex */
530 skip_nonspaces(&p); /* skip Stream */
532 skip_nonspaces(&p); /* skip Opts_SHA1 */
533 p++; /* skip space */
536 *fn++ = *p++; /* copy filename */
538 *fn = *p++; /* term filename and point to attribs */
541 if (stream == STREAM_UNIX_ATTRIBUTES || stream == STREAM_UNIX_ATTRIBUTES_EX) {
543 jcr->FileIndex = file_index;
545 ar.fname = jcr->fname;
546 ar.FileIndex = file_index;
549 ar.JobId = jcr->JobId;
550 ar.ClientId = jcr->ClientId;
554 ar.DigestType = CRYPTO_DIGEST_NONE;
556 Dmsg2(111, "dird<filed: stream=%d %s\n", stream, jcr->fname);
557 Dmsg1(120, "dird<filed: attr=%s\n", attr);
559 if (!db_create_file_attributes_record(jcr, jcr->db, &ar)) {
560 Jmsg1(jcr, M_ERROR, 0, "%s", db_strerror(jcr->db));
561 set_jcr_job_status(jcr, JS_Error);
564 jcr->FileId = ar.FileId;
565 } else if (crypto_digest_stream_type(stream) != CRYPTO_DIGEST_NONE) {
566 if (jcr->FileIndex != (uint32_t)file_index) {
567 Jmsg3(jcr, M_ERROR, 0, _("%s index %d not same as attributes %d\n"),
568 stream_to_ascii(stream), file_index, jcr->FileIndex);
569 set_jcr_job_status(jcr, JS_Error);
572 db_escape_string(digest, Opts_Digest, strlen(Opts_Digest));
573 Dmsg2(120, "DigestLen=%d Digest=%s\n", strlen(digest), digest);
574 if (!db_add_digest_to_file_record(jcr, jcr->db, jcr->FileId, digest,
575 crypto_digest_stream_type(stream))) {
576 Jmsg1(jcr, M_ERROR, 0, "%s", db_strerror(jcr->db));
577 set_jcr_job_status(jcr, JS_Error);
580 jcr->jr.JobFiles = jcr->JobFiles = file_index;
581 jcr->jr.LastIndex = file_index;
583 if (is_bnet_error(fd)) {
584 Jmsg1(jcr, M_FATAL, 0, _("<filed: Network error getting attributes. ERR=%s\n"),
586 set_jcr_job_status(jcr, JS_ErrorTerminated);
590 set_jcr_job_status(jcr, JS_Terminated);