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 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,
72 if (!jcr->file_bsock) {
73 fd = bnet_connect(jcr, retry_interval, max_retry_time,
74 _("File daemon"), jcr->client->address,
75 NULL, jcr->client->FDport, verbose);
77 set_jcr_job_status(jcr, JS_ErrorTerminated);
80 Dmsg0(10, "Opened connection with File daemon\n");
82 fd = jcr->file_bsock; /* use existing connection */
84 fd->res = (RES *)jcr->client; /* save resource in BSOCK */
86 set_jcr_job_status(jcr, JS_Running);
88 if (!authenticate_file_daemon(jcr)) {
89 set_jcr_job_status(jcr, JS_ErrorTerminated);
94 * Now send JobId and authorization key
96 bnet_fsend(fd, jobcmd, edit_int64(jcr->JobId, ed1), jcr->Job, jcr->VolSessionId,
97 jcr->VolSessionTime, jcr->sd_auth_key);
98 if (strcmp(jcr->sd_auth_key, "dummy") != 0) {
99 memset(jcr->sd_auth_key, 0, strlen(jcr->sd_auth_key));
101 Dmsg1(100, ">filed: %s", fd->msg);
102 if (bget_dirmsg(fd) > 0) {
103 Dmsg1(110, "<filed: %s", fd->msg);
104 if (strncmp(fd->msg, OKjob, strlen(OKjob)) != 0) {
105 Jmsg(jcr, M_FATAL, 0, _("File daemon \"%s\" rejected Job command: %s\n"),
106 jcr->client->hdr.name, fd->msg);
107 set_jcr_job_status(jcr, JS_ErrorTerminated);
109 } else if (jcr->db) {
111 memset(&cr, 0, sizeof(cr));
112 bstrncpy(cr.Name, jcr->client->hdr.name, sizeof(cr.Name));
113 cr.AutoPrune = jcr->client->AutoPrune;
114 cr.FileRetention = jcr->client->FileRetention;
115 cr.JobRetention = jcr->client->JobRetention;
116 bstrncpy(cr.Uname, fd->msg+strlen(OKjob)+1, sizeof(cr.Uname));
117 if (!db_update_client_record(jcr, jcr->db, &cr)) {
118 Jmsg(jcr, M_WARNING, 0, _("Error updating Client record. ERR=%s\n"),
119 db_strerror(jcr->db));
123 Jmsg(jcr, M_FATAL, 0, _("FD gave bad response to JobId command: %s\n"),
125 set_jcr_job_status(jcr, JS_ErrorTerminated);
132 * This subroutine edits the last job start time into a
133 * "since=date/time" buffer that is returned in the
134 * variable since. This is used for display purposes in
135 * the job report. The time in jcr->stime is later
136 * passed to tell the File daemon what to do.
138 void get_level_since_time(JCR *jcr, char *since, int since_len)
144 if (jcr->stime && jcr->stime[0]) {
145 bstrncpy(since, _(", since="), since_len);
146 bstrncat(since, jcr->stime, since_len);
151 jcr->stime = get_pool_memory(PM_MESSAGE);
154 /* Lookup the last FULL backup job to get the time/date for a
155 * differential or incremental save.
157 switch (jcr->JobLevel) {
160 /* Look up start time of last job */
161 jcr->jr.JobId = 0; /* flag for db_find_job_start time */
162 if (!db_find_job_start_time(jcr, jcr->db, &jcr->jr, &jcr->stime)) {
163 /* No job found, so upgrade this one to Full */
164 Jmsg(jcr, M_INFO, 0, "%s", db_strerror(jcr->db));
165 Jmsg(jcr, M_INFO, 0, _("No prior or suitable Full backup found. Doing FULL backup.\n"));
166 bsnprintf(since, since_len, _(" (upgraded from %s)"),
167 level_to_str(jcr->JobLevel));
168 jcr->JobLevel = jcr->jr.JobLevel = L_FULL;
170 if (jcr->job->rerun_failed_levels) {
171 if (db_find_failed_job_since(jcr, jcr->db, &jcr->jr, jcr->stime, JobLevel)) {
172 Jmsg(jcr, M_INFO, 0, _("Prior failed job found. Upgrading to %s.\n"),
173 level_to_str(JobLevel));
174 bsnprintf(since, since_len, _(" (upgraded from %s)"),
175 level_to_str(jcr->JobLevel));
176 jcr->JobLevel = jcr->jr.JobLevel = JobLevel;
177 jcr->jr.JobId = jcr->JobId;
181 bstrncpy(since, _(", since="), since_len);
182 bstrncat(since, jcr->stime, since_len);
184 jcr->jr.JobId = jcr->JobId;
187 Dmsg2(100, "Level=%c last start time=%s\n", jcr->JobLevel, jcr->stime);
190 static void send_since_time(JCR *jcr)
192 BSOCK *fd = jcr->file_bsock;
196 stime = str_to_utime(jcr->stime);
197 bnet_fsend(fd, levelcmd, NT_("since_utime "), edit_uint64(stime, ed1), 0);
198 while (bget_dirmsg(fd) >= 0) { /* allow him to poll us to sync clocks */
199 Jmsg(jcr, M_INFO, 0, "%s\n", fd->msg);
205 * Send level command to FD.
206 * Used for backup jobs and estimate command.
208 bool send_level_command(JCR *jcr)
210 BSOCK *fd = jcr->file_bsock;
212 * Send Level command to File daemon
214 switch (jcr->JobLevel) {
216 bnet_fsend(fd, levelcmd, "base", " ", 0);
218 /* L_NONE is the console, sending something off to the FD */
221 bnet_fsend(fd, levelcmd, "full", " ", 0);
224 bnet_fsend(fd, levelcmd, "differential", " ", 0);
225 send_since_time(jcr);
228 bnet_fsend(fd, levelcmd, "incremental", " ", 0);
229 send_since_time(jcr);
233 Jmsg2(jcr, M_FATAL, 0, _("Unimplemented backup level %d %c\n"),
234 jcr->JobLevel, jcr->JobLevel);
237 Dmsg1(120, ">filed: %s", fd->msg);
238 if (!response(jcr, fd, OKlevel, "Level", DISPLAY_ERROR)) {
245 * Send either an Included or an Excluded list to FD
247 static bool send_fileset(JCR *jcr)
249 FILESET *fileset = jcr->fileset;
250 BSOCK *fd = jcr->file_bsock;
256 num = fileset->num_includes;
258 num = fileset->num_excludes;
260 for (int i=0; i<num; i++) {
270 ie = fileset->include_items[i];
271 bnet_fsend(fd, "I\n");
273 ie = fileset->exclude_items[i];
274 bnet_fsend(fd, "E\n");
276 for (j=0; j<ie->num_opts; j++) {
277 FOPTS *fo = ie->opts_list[j];
278 bnet_fsend(fd, "O %s\n", fo->opts);
279 for (k=0; k<fo->regex.size(); k++) {
280 bnet_fsend(fd, "R %s\n", fo->regex.get(k));
282 for (k=0; k<fo->regexdir.size(); k++) {
283 bnet_fsend(fd, "RD %s\n", fo->regexdir.get(k));
285 for (k=0; k<fo->regexfile.size(); k++) {
286 bnet_fsend(fd, "RF %s\n", fo->regexfile.get(k));
288 for (k=0; k<fo->wild.size(); k++) {
289 bnet_fsend(fd, "W %s\n", fo->wild.get(k));
291 for (k=0; k<fo->wilddir.size(); k++) {
292 bnet_fsend(fd, "WD %s\n", fo->wilddir.get(k));
294 for (k=0; k<fo->wildfile.size(); k++) {
295 bnet_fsend(fd, "WF %s\n", fo->wildfile.get(k));
297 for (k=0; k<fo->base.size(); k++) {
298 bnet_fsend(fd, "B %s\n", fo->base.get(k));
300 for (k=0; k<fo->fstype.size(); k++) {
301 bnet_fsend(fd, "X %s\n", fo->fstype.get(k));
304 bnet_fsend(fd, "D %s\n", fo->reader);
307 bnet_fsend(fd, "T %s\n", fo->writer);
309 bnet_fsend(fd, "N\n");
312 for (j=0; j<ie->name_list.size(); j++) {
313 p = (char *)ie->name_list.get(j);
316 p++; /* skip over the | */
317 fd->msg = edit_job_codes(jcr, fd->msg, p, "");
318 bpipe = open_bpipe(fd->msg, 0, "r");
321 Jmsg(jcr, M_FATAL, 0, _("Cannot run program: %s. ERR=%s\n"),
325 bstrncpy(buf, "F ", sizeof(buf));
326 Dmsg1(500, "Opts=%s\n", buf);
327 optlen = strlen(buf);
328 while (fgets(buf+optlen, sizeof(buf)-optlen, bpipe->rfd)) {
329 fd->msglen = Mmsg(fd->msg, "%s", buf);
330 Dmsg2(500, "Inc/exc len=%d: %s", fd->msglen, fd->msg);
331 if (!bnet_send(fd)) {
332 Jmsg(jcr, M_FATAL, 0, _(">filed: write error on socket\n"));
336 if ((stat=close_bpipe(bpipe)) != 0) {
338 Jmsg(jcr, M_FATAL, 0, _("Error running program: %s. ERR=%s\n"),
339 p, be.strerror(stat));
344 p++; /* skip over < */
345 if ((ffd = fopen(p, "r")) == NULL) {
347 Jmsg(jcr, M_FATAL, 0, _("Cannot open included file: %s. ERR=%s\n"),
351 bstrncpy(buf, "F ", sizeof(buf));
352 Dmsg1(500, "Opts=%s\n", buf);
353 optlen = strlen(buf);
354 while (fgets(buf+optlen, sizeof(buf)-optlen, ffd)) {
355 fd->msglen = Mmsg(fd->msg, "%s", buf);
356 if (!bnet_send(fd)) {
357 Jmsg(jcr, M_FATAL, 0, _(">filed: write error on socket\n"));
364 p++; /* skip over \ */
365 /* Note, fall through wanted */
367 pm_strcpy(fd->msg, "F ");
368 fd->msglen = pm_strcat(fd->msg, p);
369 Dmsg1(500, "Inc/Exc name=%s\n", fd->msg);
370 if (!bnet_send(fd)) {
371 Jmsg(jcr, M_FATAL, 0, _(">filed: write error on socket\n"));
377 bnet_fsend(fd, "N\n");
379 if (!include) { /* If we just did excludes */
380 break; /* all done */
382 include = false; /* Now do excludes */
385 bnet_sig(fd, BNET_EOD); /* end of data */
386 if (!response(jcr, fd, OKinc, "Include", DISPLAY_ERROR)) {
392 set_jcr_job_status(jcr, JS_ErrorTerminated);
399 * Send include list to File daemon
401 bool send_include_list(JCR *jcr)
403 BSOCK *fd = jcr->file_bsock;
404 if (jcr->fileset->new_include) {
405 bnet_fsend(fd, filesetcmd, jcr->fileset->enable_vss ? " vss=1" : "");
406 return send_fileset(jcr);
413 * Send exclude list to File daemon
414 * Under the new scheme, the Exclude list
415 * is part of the FileSet sent with the
416 * "include_list" above.
418 bool send_exclude_list(JCR *jcr)
425 * Send bootstrap file if any to the socket given (FD or SD).
426 * This is used for restore, verify VolumeToCatalog, and
429 bool send_bootstrap_file(JCR *jcr, BSOCK *sock)
433 const char *bootstrap = "bootstrap\n";
435 Dmsg1(400, "send_bootstrap_file: %s\n", jcr->RestoreBootstrap);
436 if (!jcr->RestoreBootstrap) {
439 bs = fopen(jcr->RestoreBootstrap, "r");
442 Jmsg(jcr, M_FATAL, 0, _("Could not open bootstrap file %s: ERR=%s\n"),
443 jcr->RestoreBootstrap, be.strerror());
444 set_jcr_job_status(jcr, JS_ErrorTerminated);
447 bnet_fsend(sock, bootstrap);
448 while (fgets(buf, sizeof(buf), bs)) {
449 bnet_fsend(sock, "%s", buf);
451 bnet_sig(sock, BNET_EOD);
453 if (jcr->unlink_bsr) {
454 unlink(jcr->RestoreBootstrap);
455 jcr->unlink_bsr = false;
461 * Send ClientRunBeforeJob and ClientRunAfterJob to File daemon
463 int send_run_before_and_after_commands(JCR *jcr)
465 POOLMEM *msg = get_pool_memory(PM_FNAME);
466 BSOCK *fd = jcr->file_bsock;
467 if (jcr->job->ClientRunBeforeJob) {
468 pm_strcpy(msg, jcr->job->ClientRunBeforeJob);
470 bnet_fsend(fd, runbefore, msg);
471 if (!response(jcr, fd, OKRunBefore, "ClientRunBeforeJob", DISPLAY_ERROR)) {
472 set_jcr_job_status(jcr, JS_ErrorTerminated);
473 free_pool_memory(msg);
477 if (jcr->job->ClientRunAfterJob) {
478 fd->msglen = pm_strcpy(msg, jcr->job->ClientRunAfterJob);
480 bnet_fsend(fd, runafter, msg);
481 if (!response(jcr, fd, OKRunAfter, "ClientRunAfterJob", DISPLAY_ERROR)) {
482 set_jcr_job_status(jcr, JS_ErrorTerminated);
483 free_pool_memory(msg);
487 free_pool_memory(msg);
493 * Read the attributes from the File daemon for
494 * a Verify job and store them in the catalog.
496 int get_attributes_and_put_in_catalog(JCR *jcr)
502 fd = jcr->file_bsock;
503 jcr->jr.FirstIndex = 1;
504 memset(&ar, 0, sizeof(ar));
507 Dmsg0(120, "bdird: waiting to receive file attributes\n");
508 /* Pickup file attributes and digest */
509 while (!fd->errors && (n = bget_dirmsg(fd)) > 0) {
511 /*****FIXME****** improve error handling to stop only on
512 * really fatal problems, or the number of errors is too
518 char Opts_Digest[MAXSTRING]; /* either Verify opts or MD5/SHA1 digest */
519 char digest[CRYPTO_DIGEST_MAX_SIZE];
521 jcr->fname = check_pool_memory_size(jcr->fname, fd->msglen);
522 if ((len = sscanf(fd->msg, "%ld %d %s", &file_index, &stream, Opts_Digest)) != 3) {
523 Jmsg(jcr, M_FATAL, 0, _("<filed: bad attributes, expected 3 fields got %d\n"
524 "msglen=%d msg=%s\n"), len, fd->msglen, fd->msg);
525 set_jcr_job_status(jcr, JS_ErrorTerminated);
529 skip_nonspaces(&p); /* skip FileIndex */
531 skip_nonspaces(&p); /* skip Stream */
533 skip_nonspaces(&p); /* skip Opts_SHA1 */
534 p++; /* skip space */
537 *fn++ = *p++; /* copy filename */
539 *fn = *p++; /* term filename and point to attribs */
542 if (stream == STREAM_UNIX_ATTRIBUTES || stream == STREAM_UNIX_ATTRIBUTES_EX) {
544 jcr->FileIndex = file_index;
546 ar.fname = jcr->fname;
547 ar.FileIndex = file_index;
550 ar.JobId = jcr->JobId;
551 ar.ClientId = jcr->ClientId;
555 ar.DigestType = CRYPTO_DIGEST_NONE;
557 Dmsg2(111, "dird<filed: stream=%d %s\n", stream, jcr->fname);
558 Dmsg1(120, "dird<filed: attr=%s\n", attr);
560 if (!db_create_file_attributes_record(jcr, jcr->db, &ar)) {
561 Jmsg1(jcr, M_ERROR, 0, "%s", db_strerror(jcr->db));
562 set_jcr_job_status(jcr, JS_Error);
565 jcr->FileId = ar.FileId;
566 } else if (crypto_digest_stream_type(stream) != CRYPTO_DIGEST_NONE) {
567 if (jcr->FileIndex != (uint32_t)file_index) {
568 Jmsg3(jcr, M_ERROR, 0, _("%s index %d not same as attributes %d\n"),
569 stream_to_ascii(stream), file_index, jcr->FileIndex);
570 set_jcr_job_status(jcr, JS_Error);
573 db_escape_string(digest, Opts_Digest, strlen(Opts_Digest));
574 Dmsg2(120, "DigestLen=%d Digest=%s\n", strlen(digest), digest);
575 if (!db_add_digest_to_file_record(jcr, jcr->db, jcr->FileId, digest,
576 crypto_digest_stream_type(stream))) {
577 Jmsg1(jcr, M_ERROR, 0, "%s", db_strerror(jcr->db));
578 set_jcr_job_status(jcr, JS_Error);
581 jcr->jr.JobFiles = jcr->JobFiles = file_index;
582 jcr->jr.LastIndex = file_index;
584 if (is_bnet_error(fd)) {
585 Jmsg1(jcr, M_FATAL, 0, _("<filed: Network error getting attributes. ERR=%s\n"),
587 set_jcr_job_status(jcr, JS_ErrorTerminated);
591 set_jcr_job_status(jcr, JS_Terminated);