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 Bacula® - The Network Backup Solution
18 Copyright (C) 2000-2006 Free Software Foundation Europe e.V.
20 The main author of Bacula is Kern Sibbald, with contributions from
21 many others, a complete list can be found in the file AUTHORS.
22 This program is Free Software; you can redistribute it and/or
23 modify it under the terms of version two of the GNU General Public
24 License as published by the Free Software Foundation plus additions
25 that are listed in the file LICENSE.
27 This program is distributed in the hope that it will be useful, but
28 WITHOUT ANY WARRANTY; without even the implied warranty of
29 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
30 General Public License for more details.
32 You should have received a copy of the GNU General Public License
33 along with this program; if not, write to the Free Software
34 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
37 Bacula® is a registered trademark of John Walker.
38 The licensor of Bacula is the Free Software Foundation Europe
39 (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zürich,
40 Switzerland, email:ftf@fsfeurope.org.
45 #include "findlib/find.h"
47 /* Commands sent to File daemon */
48 static char filesetcmd[] = "fileset%s\n"; /* set full fileset */
49 static char jobcmd[] = "JobId=%s Job=%s SDid=%u SDtime=%u Authorization=%s\n";
50 /* Note, mtime_only is not used here -- implemented as file option */
51 static char levelcmd[] = "level = %s%s mtime_only=%d\n";
52 static char runscript[] = "Run OnSuccess=%u OnFailure=%u AbortOnError=%u When=%u Command=%s\n";
53 static char runbeforenow[]= "RunBeforeNow\n";
55 /* Responses received from File daemon */
56 static char OKinc[] = "2000 OK include\n";
57 static char OKjob[] = "2000 OK Job";
58 static char OKlevel[] = "2000 OK level\n";
59 static char OKRunScript[] = "2000 OK RunScript\n";
60 static char OKRunBeforeNow[] = "2000 OK RunBeforeNow\n";
62 /* Forward referenced functions */
64 /* External functions */
65 extern DIRRES *director;
66 extern int FDConnectTimeout;
72 * Open connection with File daemon.
73 * Try connecting every retry_interval (default 10 sec), and
74 * give up after max_retry_time (default 30 mins).
77 int connect_to_file_daemon(JCR *jcr, int retry_interval, int max_retry_time,
83 if (!jcr->file_bsock) {
84 fd = bnet_connect(jcr, retry_interval, max_retry_time,
85 _("File daemon"), jcr->client->address,
86 NULL, jcr->client->FDport, verbose);
88 set_jcr_job_status(jcr, JS_ErrorTerminated);
91 Dmsg0(10, "Opened connection with File daemon\n");
93 fd = jcr->file_bsock; /* use existing connection */
95 fd->res = (RES *)jcr->client; /* save resource in BSOCK */
97 set_jcr_job_status(jcr, JS_Running);
99 if (!authenticate_file_daemon(jcr)) {
100 set_jcr_job_status(jcr, JS_ErrorTerminated);
105 * Now send JobId and authorization key
107 bnet_fsend(fd, jobcmd, edit_int64(jcr->JobId, ed1), jcr->Job, jcr->VolSessionId,
108 jcr->VolSessionTime, jcr->sd_auth_key);
109 if (strcmp(jcr->sd_auth_key, "dummy") != 0) {
110 memset(jcr->sd_auth_key, 0, strlen(jcr->sd_auth_key));
112 Dmsg1(100, ">filed: %s", fd->msg);
113 if (bget_dirmsg(fd) > 0) {
114 Dmsg1(110, "<filed: %s", fd->msg);
115 if (strncmp(fd->msg, OKjob, strlen(OKjob)) != 0) {
116 Jmsg(jcr, M_FATAL, 0, _("File daemon \"%s\" rejected Job command: %s\n"),
117 jcr->client->hdr.name, fd->msg);
118 set_jcr_job_status(jcr, JS_ErrorTerminated);
120 } else if (jcr->db) {
122 memset(&cr, 0, sizeof(cr));
123 bstrncpy(cr.Name, jcr->client->hdr.name, sizeof(cr.Name));
124 cr.AutoPrune = jcr->client->AutoPrune;
125 cr.FileRetention = jcr->client->FileRetention;
126 cr.JobRetention = jcr->client->JobRetention;
127 bstrncpy(cr.Uname, fd->msg+strlen(OKjob)+1, sizeof(cr.Uname));
128 if (!db_update_client_record(jcr, jcr->db, &cr)) {
129 Jmsg(jcr, M_WARNING, 0, _("Error updating Client record. ERR=%s\n"),
130 db_strerror(jcr->db));
134 Jmsg(jcr, M_FATAL, 0, _("FD gave bad response to JobId command: %s\n"),
136 set_jcr_job_status(jcr, JS_ErrorTerminated);
143 * This subroutine edits the last job start time into a
144 * "since=date/time" buffer that is returned in the
145 * variable since. This is used for display purposes in
146 * the job report. The time in jcr->stime is later
147 * passed to tell the File daemon what to do.
149 void get_level_since_time(JCR *jcr, char *since, int since_len)
155 if (jcr->stime && jcr->stime[0]) {
156 bstrncpy(since, _(", since="), since_len);
157 bstrncat(since, jcr->stime, since_len);
162 jcr->stime = get_pool_memory(PM_MESSAGE);
165 /* Lookup the last FULL backup job to get the time/date for a
166 * differential or incremental save.
168 switch (jcr->JobLevel) {
171 /* Look up start time of last job */
172 jcr->jr.JobId = 0; /* flag for db_find_job_start time */
173 if (!db_find_job_start_time(jcr, jcr->db, &jcr->jr, &jcr->stime)) {
174 /* No job found, so upgrade this one to Full */
175 Jmsg(jcr, M_INFO, 0, "%s", db_strerror(jcr->db));
176 Jmsg(jcr, M_INFO, 0, _("No prior or suitable Full backup found in catalog. Doing FULL backup.\n"));
177 bsnprintf(since, since_len, _(" (upgraded from %s)"),
178 level_to_str(jcr->JobLevel));
179 jcr->JobLevel = jcr->jr.JobLevel = L_FULL;
181 if (jcr->job->rerun_failed_levels) {
182 if (db_find_failed_job_since(jcr, jcr->db, &jcr->jr, jcr->stime, JobLevel)) {
183 Jmsg(jcr, M_INFO, 0, _("Prior failed job found in catalog. Upgrading to %s.\n"),
184 level_to_str(JobLevel));
185 bsnprintf(since, since_len, _(" (upgraded from %s)"),
186 level_to_str(jcr->JobLevel));
187 jcr->JobLevel = jcr->jr.JobLevel = JobLevel;
188 jcr->jr.JobId = jcr->JobId;
192 bstrncpy(since, _(", since="), since_len);
193 bstrncat(since, jcr->stime, since_len);
195 jcr->jr.JobId = jcr->JobId;
198 Dmsg2(100, "Level=%c last start time=%s\n", jcr->JobLevel, jcr->stime);
201 static void send_since_time(JCR *jcr)
203 BSOCK *fd = jcr->file_bsock;
207 stime = str_to_utime(jcr->stime);
208 bnet_fsend(fd, levelcmd, NT_("since_utime "), edit_uint64(stime, ed1), 0);
209 while (bget_dirmsg(fd) >= 0) { /* allow him to poll us to sync clocks */
210 Jmsg(jcr, M_INFO, 0, "%s\n", fd->msg);
216 * Send level command to FD.
217 * Used for backup jobs and estimate command.
219 bool send_level_command(JCR *jcr)
221 BSOCK *fd = jcr->file_bsock;
223 * Send Level command to File daemon
225 switch (jcr->JobLevel) {
227 bnet_fsend(fd, levelcmd, "base", " ", 0);
229 /* L_NONE is the console, sending something off to the FD */
232 bnet_fsend(fd, levelcmd, "full", " ", 0);
235 bnet_fsend(fd, levelcmd, "differential", " ", 0);
236 send_since_time(jcr);
239 bnet_fsend(fd, levelcmd, "incremental", " ", 0);
240 send_since_time(jcr);
244 Jmsg2(jcr, M_FATAL, 0, _("Unimplemented backup level %d %c\n"),
245 jcr->JobLevel, jcr->JobLevel);
248 Dmsg1(120, ">filed: %s", fd->msg);
249 if (!response(jcr, fd, OKlevel, "Level", DISPLAY_ERROR)) {
256 * Send either an Included or an Excluded list to FD
258 static bool send_fileset(JCR *jcr)
260 FILESET *fileset = jcr->fileset;
261 BSOCK *fd = jcr->file_bsock;
267 num = fileset->num_includes;
269 num = fileset->num_excludes;
271 for (int i=0; i<num; i++) {
281 ie = fileset->include_items[i];
282 bnet_fsend(fd, "I\n");
284 ie = fileset->exclude_items[i];
285 bnet_fsend(fd, "E\n");
287 for (j=0; j<ie->num_opts; j++) {
288 FOPTS *fo = ie->opts_list[j];
289 bnet_fsend(fd, "O %s\n", fo->opts);
291 bool enhanced_wild = false;
292 for (k=0; fo->opts[k]!='\0'; k++) {
293 if (fo->opts[k]=='W') {
294 enhanced_wild = true;
299 for (k=0; k<fo->regex.size(); k++) {
300 bnet_fsend(fd, "R %s\n", fo->regex.get(k));
302 for (k=0; k<fo->regexdir.size(); k++) {
303 bnet_fsend(fd, "RD %s\n", fo->regexdir.get(k));
305 for (k=0; k<fo->regexfile.size(); k++) {
306 bnet_fsend(fd, "RF %s\n", fo->regexfile.get(k));
308 for (k=0; k<fo->wild.size(); k++) {
309 bnet_fsend(fd, "W %s\n", fo->wild.get(k));
311 for (k=0; k<fo->wilddir.size(); k++) {
312 bnet_fsend(fd, "WD %s\n", fo->wilddir.get(k));
314 for (k=0; k<fo->wildfile.size(); k++) {
315 bnet_fsend(fd, "WF %s\n", fo->wildfile.get(k));
317 for (k=0; k<fo->wildbase.size(); k++) {
318 bnet_fsend(fd, "W%c %s\n", enhanced_wild ? 'B' : 'F', fo->wildbase.get(k));
320 for (k=0; k<fo->base.size(); k++) {
321 bnet_fsend(fd, "B %s\n", fo->base.get(k));
323 for (k=0; k<fo->fstype.size(); k++) {
324 bnet_fsend(fd, "X %s\n", fo->fstype.get(k));
326 for (k=0; k<fo->drivetype.size(); k++) {
327 bnet_fsend(fd, "XD %s\n", fo->drivetype.get(k));
330 bnet_fsend(fd, "D %s\n", fo->reader);
333 bnet_fsend(fd, "T %s\n", fo->writer);
335 bnet_fsend(fd, "N\n");
338 for (j=0; j<ie->name_list.size(); j++) {
339 p = (char *)ie->name_list.get(j);
342 p++; /* skip over the | */
343 fd->msg = edit_job_codes(jcr, fd->msg, p, "");
344 bpipe = open_bpipe(fd->msg, 0, "r");
347 Jmsg(jcr, M_FATAL, 0, _("Cannot run program: %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, bpipe->rfd)) {
355 fd->msglen = Mmsg(fd->msg, "%s", buf);
356 Dmsg2(500, "Inc/exc len=%d: %s", fd->msglen, fd->msg);
357 if (!bnet_send(fd)) {
358 Jmsg(jcr, M_FATAL, 0, _(">filed: write error on socket\n"));
362 if ((stat=close_bpipe(bpipe)) != 0) {
364 Jmsg(jcr, M_FATAL, 0, _("Error running program: %s. ERR=%s\n"),
365 p, be.strerror(stat));
370 p++; /* skip over < */
371 if ((ffd = fopen(p, "rb")) == NULL) {
373 Jmsg(jcr, M_FATAL, 0, _("Cannot open included file: %s. ERR=%s\n"),
377 bstrncpy(buf, "F ", sizeof(buf));
378 Dmsg1(500, "Opts=%s\n", buf);
379 optlen = strlen(buf);
380 while (fgets(buf+optlen, sizeof(buf)-optlen, ffd)) {
381 fd->msglen = Mmsg(fd->msg, "%s", buf);
382 if (!bnet_send(fd)) {
383 Jmsg(jcr, M_FATAL, 0, _(">filed: write error on socket\n"));
390 p++; /* skip over \ */
391 /* Note, fall through wanted */
393 pm_strcpy(fd->msg, "F ");
394 fd->msglen = pm_strcat(fd->msg, p);
395 Dmsg1(500, "Inc/Exc name=%s\n", fd->msg);
396 if (!bnet_send(fd)) {
397 Jmsg(jcr, M_FATAL, 0, _(">filed: write error on socket\n"));
403 bnet_fsend(fd, "N\n");
405 if (!include) { /* If we just did excludes */
406 break; /* all done */
408 include = false; /* Now do excludes */
411 bnet_sig(fd, BNET_EOD); /* end of data */
412 if (!response(jcr, fd, OKinc, "Include", DISPLAY_ERROR)) {
418 set_jcr_job_status(jcr, JS_ErrorTerminated);
425 * Send include list to File daemon
427 bool send_include_list(JCR *jcr)
429 BSOCK *fd = jcr->file_bsock;
430 if (jcr->fileset->new_include) {
431 bnet_fsend(fd, filesetcmd, jcr->fileset->enable_vss ? " vss=1" : "");
432 return send_fileset(jcr);
439 * Send exclude list to File daemon
440 * Under the new scheme, the Exclude list
441 * is part of the FileSet sent with the
442 * "include_list" above.
444 bool send_exclude_list(JCR *jcr)
451 * Send bootstrap file if any to the socket given (FD or SD).
452 * This is used for restore, verify VolumeToCatalog, and
455 bool send_bootstrap_file(JCR *jcr, BSOCK *sock)
459 const char *bootstrap = "bootstrap\n";
461 Dmsg1(400, "send_bootstrap_file: %s\n", jcr->RestoreBootstrap);
462 if (!jcr->RestoreBootstrap) {
465 bs = fopen(jcr->RestoreBootstrap, "rb");
468 Jmsg(jcr, M_FATAL, 0, _("Could not open bootstrap file %s: ERR=%s\n"),
469 jcr->RestoreBootstrap, be.strerror());
470 set_jcr_job_status(jcr, JS_ErrorTerminated);
473 bnet_fsend(sock, bootstrap);
474 while (fgets(buf, sizeof(buf), bs)) {
475 bnet_fsend(sock, "%s", buf);
477 bnet_sig(sock, BNET_EOD);
479 if (jcr->unlink_bsr) {
480 unlink(jcr->RestoreBootstrap);
481 jcr->unlink_bsr = false;
486 /* TODO: drop this with runscript.old_proto in bacula 1.42 */
487 static char runbefore[] = "RunBeforeJob %s\n";
488 static char runafter[] = "RunAfterJob %s\n";
489 static char OKRunBefore[] = "2000 OK RunBefore\n";
490 static char OKRunAfter[] = "2000 OK RunAfter\n";
492 int send_runscript_with_old_proto(JCR *jcr, int when, POOLMEM *msg)
495 Dmsg1(120, "bdird: sending old runcommand to fd '%s'\n",msg);
496 if (when & SCRIPT_Before) {
497 bnet_fsend(jcr->file_bsock, runbefore, msg);
498 ret = response(jcr, jcr->file_bsock, OKRunBefore, "ClientRunBeforeJob", DISPLAY_ERROR);
500 bnet_fsend(jcr->file_bsock, runafter, msg);
501 ret = response(jcr, jcr->file_bsock, OKRunAfter, "ClientRunAfterJob", DISPLAY_ERROR);
507 * Send RunScripts to File daemon
509 int send_runscripts_commands(JCR *jcr)
511 POOLMEM *msg = get_pool_memory(PM_FNAME);
512 BSOCK *fd = jcr->file_bsock;
514 bool launch_before_cmd = false;
515 POOLMEM *ehost = get_pool_memory(PM_FNAME);
518 Dmsg0(120, "bdird: sending runscripts to fd\n");
520 foreach_alist(cmd, jcr->job->RunScripts) {
522 if (cmd->can_run_at_level(jcr->JobLevel) && cmd->target) {
524 ehost = edit_job_codes(jcr, ehost, cmd->target, "");
525 Dmsg2(200, "bdird: runscript %s -> %s\n", cmd->target, ehost);
527 if (strcmp(ehost, jcr->client->hdr.name) == 0) {
528 pm_strcpy(msg, cmd->command);
531 Dmsg1(120, "bdird: sending runscripts to fd '%s'\n", cmd->command);
533 /* TODO: remove this with bacula 1.42 */
534 if (cmd->old_proto) {
535 result = send_runscript_with_old_proto(jcr, cmd->when, msg);
538 bnet_fsend(fd, runscript, cmd->on_success,
544 result = response(jcr, fd, OKRunScript, "RunScript", DISPLAY_ERROR);
545 launch_before_cmd=true;
549 set_jcr_job_status(jcr, JS_ErrorTerminated);
550 free_pool_memory(msg);
551 free_pool_memory(ehost);
557 send command to an other client
563 /* TODO : we have to play with other client */
564 if (launch_before_cmd) {
565 bnet_fsend(fd, runbeforenow);
566 if (!response(jcr, fd, OKRunBeforeNow, "RunBeforeNow", DISPLAY_ERROR)) {
567 set_jcr_job_status(jcr, JS_ErrorTerminated);
568 free_pool_memory(msg);
569 free_pool_memory(ehost);
573 free_pool_memory(msg);
574 free_pool_memory(ehost);
580 * Read the attributes from the File daemon for
581 * a Verify job and store them in the catalog.
583 int get_attributes_and_put_in_catalog(JCR *jcr)
589 fd = jcr->file_bsock;
590 jcr->jr.FirstIndex = 1;
591 memset(&ar, 0, sizeof(ar));
594 Dmsg0(120, "bdird: waiting to receive file attributes\n");
595 /* Pickup file attributes and digest */
596 while (!fd->errors && (n = bget_dirmsg(fd)) > 0) {
598 /*****FIXME****** improve error handling to stop only on
599 * really fatal problems, or the number of errors is too
605 char Opts_Digest[MAXSTRING]; /* either Verify opts or MD5/SHA1 digest */
606 char digest[CRYPTO_DIGEST_MAX_SIZE];
608 jcr->fname = check_pool_memory_size(jcr->fname, fd->msglen);
609 if ((len = sscanf(fd->msg, "%ld %d %s", &file_index, &stream, Opts_Digest)) != 3) {
610 Jmsg(jcr, M_FATAL, 0, _("<filed: bad attributes, expected 3 fields got %d\n"
611 "msglen=%d msg=%s\n"), len, fd->msglen, fd->msg);
612 set_jcr_job_status(jcr, JS_ErrorTerminated);
616 skip_nonspaces(&p); /* skip FileIndex */
618 skip_nonspaces(&p); /* skip Stream */
620 skip_nonspaces(&p); /* skip Opts_SHA1 */
621 p++; /* skip space */
624 *fn++ = *p++; /* copy filename */
626 *fn = *p++; /* term filename and point to attribs */
629 if (stream == STREAM_UNIX_ATTRIBUTES || stream == STREAM_UNIX_ATTRIBUTES_EX) {
631 jcr->FileIndex = file_index;
633 ar.fname = jcr->fname;
634 ar.FileIndex = file_index;
637 ar.JobId = jcr->JobId;
638 ar.ClientId = jcr->ClientId;
642 ar.DigestType = CRYPTO_DIGEST_NONE;
644 Dmsg2(111, "dird<filed: stream=%d %s\n", stream, jcr->fname);
645 Dmsg1(120, "dird<filed: attr=%s\n", attr);
647 if (!db_create_file_attributes_record(jcr, jcr->db, &ar)) {
648 Jmsg1(jcr, M_ERROR, 0, "%s", db_strerror(jcr->db));
649 set_jcr_job_status(jcr, JS_Error);
652 jcr->FileId = ar.FileId;
653 } else if (crypto_digest_stream_type(stream) != CRYPTO_DIGEST_NONE) {
654 if (jcr->FileIndex != (uint32_t)file_index) {
655 Jmsg3(jcr, M_ERROR, 0, _("%s index %d not same as attributes %d\n"),
656 stream_to_ascii(stream), file_index, jcr->FileIndex);
657 set_jcr_job_status(jcr, JS_Error);
660 db_escape_string(digest, Opts_Digest, strlen(Opts_Digest));
661 Dmsg2(120, "DigestLen=%d Digest=%s\n", strlen(digest), digest);
662 if (!db_add_digest_to_file_record(jcr, jcr->db, jcr->FileId, digest,
663 crypto_digest_stream_type(stream))) {
664 Jmsg1(jcr, M_ERROR, 0, "%s", db_strerror(jcr->db));
665 set_jcr_job_status(jcr, JS_Error);
668 jcr->jr.JobFiles = jcr->JobFiles = file_index;
669 jcr->jr.LastIndex = file_index;
671 if (is_bnet_error(fd)) {
672 Jmsg1(jcr, M_FATAL, 0, _("<filed: Network error getting attributes. ERR=%s\n"),
674 set_jcr_job_status(jcr, JS_ErrorTerminated);
678 set_jcr_job_status(jcr, JS_Terminated);