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.
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 OKbootstrap[] = "2000 OK bootstrap\n";
47 static char OKlevel[] = "2000 OK level\n";
48 static char OKRunBefore[] = "2000 OK RunBefore\n";
49 static char OKRunAfter[] = "2000 OK RunAfter\n";
51 /* Forward referenced functions */
53 /* External functions */
54 extern int debug_level;
55 extern DIRRES *director;
56 extern int FDConnectTimeout;
62 * Open connection with File daemon.
63 * Try connecting every retry_interval (default 10 sec), and
64 * give up after max_retry_time (default 30 mins).
67 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, jcr->JobId, 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, _("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 int 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 File daemon.
426 * This is used for restore and verify VolumeToCatalog
428 bool send_bootstrap_file(JCR *jcr)
432 BSOCK *fd = jcr->file_bsock;
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(fd, bootstrap);
448 while (fgets(buf, sizeof(buf), bs)) {
449 bnet_fsend(fd, "%s", buf);
451 bnet_sig(fd, BNET_EOD);
453 if (jcr->unlink_bsr) {
454 unlink(jcr->RestoreBootstrap);
455 jcr->unlink_bsr = false;
457 if (!response(jcr, fd, OKbootstrap, "Bootstrap", DISPLAY_ERROR)) {
458 set_jcr_job_status(jcr, JS_ErrorTerminated);
465 * Send ClientRunBeforeJob and ClientRunAfterJob to File daemon
467 int send_run_before_and_after_commands(JCR *jcr)
469 POOLMEM *msg = get_pool_memory(PM_FNAME);
470 BSOCK *fd = jcr->file_bsock;
471 if (jcr->job->ClientRunBeforeJob) {
472 pm_strcpy(msg, jcr->job->ClientRunBeforeJob);
474 bnet_fsend(fd, runbefore, msg);
475 if (!response(jcr, fd, OKRunBefore, "ClientRunBeforeJob", DISPLAY_ERROR)) {
476 set_jcr_job_status(jcr, JS_ErrorTerminated);
477 free_pool_memory(msg);
481 if (jcr->job->ClientRunAfterJob) {
482 fd->msglen = pm_strcpy(msg, jcr->job->ClientRunAfterJob);
484 bnet_fsend(fd, runafter, msg);
485 if (!response(jcr, fd, OKRunAfter, "ClientRunAfterJob", DISPLAY_ERROR)) {
486 set_jcr_job_status(jcr, JS_ErrorTerminated);
487 free_pool_memory(msg);
491 free_pool_memory(msg);
497 * Read the attributes from the File daemon for
498 * a Verify job and store them in the catalog.
500 int get_attributes_and_put_in_catalog(JCR *jcr)
506 fd = jcr->file_bsock;
507 jcr->jr.FirstIndex = 1;
508 memset(&ar, 0, sizeof(ar));
511 Dmsg0(120, "bdird: waiting to receive file attributes\n");
512 /* Pickup file attributes and digest */
513 while (!fd->errors && (n = bget_dirmsg(fd)) > 0) {
515 /*****FIXME****** improve error handling to stop only on
516 * really fatal problems, or the number of errors is too
522 char Opts_Digest[MAXSTRING]; /* either Verify opts or MD5/SHA1 digest */
523 char digest[CRYPTO_DIGEST_MAX_SIZE];
525 jcr->fname = check_pool_memory_size(jcr->fname, fd->msglen);
526 if ((len = sscanf(fd->msg, "%ld %d %s", &file_index, &stream, Opts_Digest)) != 3) {
527 Jmsg(jcr, M_FATAL, 0, _("<filed: bad attributes, expected 3 fields got %d\n"
528 "msglen=%d msg=%s\n"), len, fd->msglen, fd->msg);
529 set_jcr_job_status(jcr, JS_ErrorTerminated);
533 skip_nonspaces(&p); /* skip FileIndex */
535 skip_nonspaces(&p); /* skip Stream */
537 skip_nonspaces(&p); /* skip Opts_SHA1 */
538 p++; /* skip space */
541 *fn++ = *p++; /* copy filename */
543 *fn = *p++; /* term filename and point to attribs */
546 if (stream == STREAM_UNIX_ATTRIBUTES || stream == STREAM_UNIX_ATTRIBUTES_EX) {
548 jcr->FileIndex = file_index;
550 ar.fname = jcr->fname;
551 ar.FileIndex = file_index;
554 ar.JobId = jcr->JobId;
555 ar.ClientId = jcr->ClientId;
559 ar.DigestType = CRYPTO_DIGEST_NONE;
561 Dmsg2(111, "dird<filed: stream=%d %s\n", stream, jcr->fname);
562 Dmsg1(120, "dird<filed: attr=%s\n", attr);
564 if (!db_create_file_attributes_record(jcr, jcr->db, &ar)) {
565 Jmsg1(jcr, M_ERROR, 0, "%s", db_strerror(jcr->db));
566 set_jcr_job_status(jcr, JS_Error);
569 jcr->FileId = ar.FileId;
570 } else if (crypto_digest_stream_type(stream) != CRYPTO_DIGEST_NONE) {
571 if (jcr->FileIndex != (uint32_t)file_index) {
572 Jmsg3(jcr, M_ERROR, 0, _("%s index %d not same as attributes %d\n"),
573 stream_to_ascii(stream), file_index, jcr->FileIndex);
574 set_jcr_job_status(jcr, JS_Error);
577 db_escape_string(digest, Opts_Digest, strlen(Opts_Digest));
578 Dmsg2(120, "DigestLen=%d Digest=%s\n", strlen(digest), digest);
579 if (!db_add_digest_to_file_record(jcr, jcr->db, jcr->FileId, digest,
580 crypto_digest_stream_type(stream))) {
581 Jmsg1(jcr, M_ERROR, 0, "%s", db_strerror(jcr->db));
582 set_jcr_job_status(jcr, JS_Error);
585 jcr->jr.JobFiles = jcr->JobFiles = file_index;
586 jcr->jr.LastIndex = file_index;
588 if (is_bnet_error(fd)) {
589 Jmsg1(jcr, M_FATAL, 0, _("<filed: Network error getting attributes. ERR=%s\n"),
591 set_jcr_job_status(jcr, JS_ErrorTerminated);
595 set_jcr_job_status(jcr, JS_Terminated);