2 Bacula® - The Network Backup Solution
4 Copyright (C) 2000-2008 Free Software Foundation Europe e.V.
6 The main author of Bacula is Kern Sibbald, with contributions from
7 many others, a complete list can be found in the file AUTHORS.
8 This program is Free Software; you can redistribute it and/or
9 modify it under the terms of version two of the GNU General Public
10 License as published by the Free Software Foundation and included
13 This program is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 General Public License for more details.
18 You should have received a copy of the GNU General Public License
19 along with this program; if not, write to the Free Software
20 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
23 Bacula® is a registered trademark of John Walker.
24 The licensor of Bacula is the Free Software Foundation Europe
25 (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zürich,
26 Switzerland, email:ftf@fsfeurope.org.
30 * Bacula Director -- fd_cmds.c -- send commands to File daemon
32 * Kern Sibbald, October MM
34 * This routine is run as a separate thread. There may be more
35 * work to be done to make it totally reentrant!!!!
37 * Utility functions for sending info to File Daemon.
38 * These functions are used by both backup and verify.
45 #include "findlib/find.h"
47 const int dbglvl = 400;
49 /* Commands sent to File daemon */
50 static char filesetcmd[] = "fileset%s\n"; /* set full fileset */
51 static char jobcmd[] = "JobId=%s Job=%s SDid=%u SDtime=%u Authorization=%s\n";
52 /* Note, mtime_only is not used here -- implemented as file option */
53 static char levelcmd[] = "level = %s%s%s mtime_only=%d\n";
54 static char runscript[] = "Run OnSuccess=%u OnFailure=%u AbortOnError=%u When=%u Command=%s\n";
55 static char runbeforenow[]= "RunBeforeNow\n";
57 /* Responses received from File daemon */
58 static char OKinc[] = "2000 OK include\n";
59 static char OKjob[] = "2000 OK Job";
60 static char OKlevel[] = "2000 OK level\n";
61 static char OKRunScript[] = "2000 OK RunScript\n";
62 static char OKRunBeforeNow[] = "2000 OK RunBeforeNow\n";
64 /* Forward referenced functions */
65 static bool send_list_item(JCR *jcr, const char *code, char *item, BSOCK *fd);
67 /* External functions */
68 extern DIRRES *director;
69 extern int FDConnectTimeout;
75 * Open connection with File daemon.
76 * Try connecting every retry_interval (default 10 sec), and
77 * give up after max_retry_time (default 30 mins).
80 int connect_to_file_daemon(JCR *jcr, int retry_interval, int max_retry_time,
87 if (jcr->client->heartbeat_interval) {
88 heart_beat = jcr->client->heartbeat_interval;
90 heart_beat = director->heartbeat_interval;
93 if (!jcr->file_bsock) {
94 char name[MAX_NAME_LENGTH + 100];
95 bstrncpy(name, _("Client: "), sizeof(name));
96 bstrncat(name, jcr->client->name(), sizeof(name));
97 fd = bnet_connect(jcr, retry_interval, max_retry_time, heart_beat,
98 name, jcr->client->address, NULL, jcr->client->FDport, verbose);
100 set_jcr_job_status(jcr, JS_ErrorTerminated);
103 Dmsg0(10, "Opened connection with File daemon\n");
105 fd = jcr->file_bsock; /* use existing connection */
107 fd->res = (RES *)jcr->client; /* save resource in BSOCK */
108 jcr->file_bsock = fd;
109 set_jcr_job_status(jcr, JS_Running);
111 if (!authenticate_file_daemon(jcr)) {
112 set_jcr_job_status(jcr, JS_ErrorTerminated);
117 * Now send JobId and authorization key
119 fd->fsend(jobcmd, edit_int64(jcr->JobId, ed1), jcr->Job, jcr->VolSessionId,
120 jcr->VolSessionTime, jcr->sd_auth_key);
121 if (strcmp(jcr->sd_auth_key, "dummy") != 0) {
122 memset(jcr->sd_auth_key, 0, strlen(jcr->sd_auth_key));
124 Dmsg1(100, ">filed: %s", fd->msg);
125 if (bget_dirmsg(fd) > 0) {
126 Dmsg1(110, "<filed: %s", fd->msg);
127 if (strncmp(fd->msg, OKjob, strlen(OKjob)) != 0) {
128 Jmsg(jcr, M_FATAL, 0, _("File daemon \"%s\" rejected Job command: %s\n"),
129 jcr->client->hdr.name, fd->msg);
130 set_jcr_job_status(jcr, JS_ErrorTerminated);
132 } else if (jcr->db) {
134 memset(&cr, 0, sizeof(cr));
135 bstrncpy(cr.Name, jcr->client->hdr.name, sizeof(cr.Name));
136 cr.AutoPrune = jcr->client->AutoPrune;
137 cr.FileRetention = jcr->client->FileRetention;
138 cr.JobRetention = jcr->client->JobRetention;
139 bstrncpy(cr.Uname, fd->msg+strlen(OKjob)+1, sizeof(cr.Uname));
140 if (!db_update_client_record(jcr, jcr->db, &cr)) {
141 Jmsg(jcr, M_WARNING, 0, _("Error updating Client record. ERR=%s\n"),
142 db_strerror(jcr->db));
146 Jmsg(jcr, M_FATAL, 0, _("FD gave bad response to JobId command: %s\n"),
148 set_jcr_job_status(jcr, JS_ErrorTerminated);
155 * This subroutine edits the last job start time into a
156 * "since=date/time" buffer that is returned in the
157 * variable since. This is used for display purposes in
158 * the job report. The time in jcr->stime is later
159 * passed to tell the File daemon what to do.
161 void get_level_since_time(JCR *jcr, char *since, int since_len)
165 bool do_full = false;
166 bool do_diff = false;
168 utime_t full_time, diff_time;
171 /* If job cloned and a since time already given, use it */
172 if (jcr->cloned && jcr->stime && jcr->stime[0]) {
173 bstrncpy(since, _(", since="), since_len);
174 bstrncat(since, jcr->stime, since_len);
177 /* Make sure stime buffer is allocated */
179 jcr->stime = get_pool_memory(PM_MESSAGE);
183 * Lookup the last FULL backup job to get the time/date for a
184 * differential or incremental save.
186 switch (jcr->JobLevel) {
189 /* Look up start time of last Full job */
191 jcr->jr.JobId = 0; /* flag for db_find_job_start time */
192 have_full = db_find_job_start_time(jcr, jcr->db, &jcr->jr, &jcr->stime);
194 /* If there was a successful job, make sure it is recent enough */
195 if (jcr->JobLevel == L_INCREMENTAL && have_full && jcr->job->MaxDiffInterval > 0) {
196 /* Lookup last diff job */
198 /* ***FIXME*** must find diff start time and not destroy jcr->stime */
199 if (db_find_job_start_time(jcr, jcr->db, &jcr->jr, &jcr->stime)) {
200 diff_time = str_to_utime(jcr->stime);
201 do_diff = ((now - diff_time) <= jcr->job->MaxDiffInterval);
205 if (have_full && jcr->job->MaxFullInterval > 0) {
206 full_time = str_to_utime(jcr->stime);
207 do_full = ((now - full_time) <= jcr->job->MaxFullInterval);
209 if (!have_full || do_full) {
210 /* No recent Full job found, so upgrade this one to Full */
211 Jmsg(jcr, M_INFO, 0, "%s", db_strerror(jcr->db));
212 Jmsg(jcr, M_INFO, 0, _("No prior or suitable Full backup found in catalog. Doing FULL backup.\n"));
213 bsnprintf(since, since_len, _(" (upgraded from %s)"),
214 level_to_str(jcr->JobLevel));
215 jcr->JobLevel = jcr->jr.JobLevel = L_FULL;
216 } else if (do_diff) {
217 /* No recent diff job found, so upgrade this one to Full */
218 Jmsg(jcr, M_INFO, 0, _("No prior or suitable Differential backup found in catalog. Doing Differential backup.\n"));
219 bsnprintf(since, since_len, _(" (upgraded from %s)"),
220 level_to_str(jcr->JobLevel));
221 jcr->JobLevel = jcr->jr.JobLevel = L_DIFFERENTIAL;
223 if (jcr->job->rerun_failed_levels) {
224 if (db_find_failed_job_since(jcr, jcr->db, &jcr->jr, jcr->stime, JobLevel)) {
225 Jmsg(jcr, M_INFO, 0, _("Prior failed job found in catalog. Upgrading to %s.\n"),
226 level_to_str(JobLevel));
227 bsnprintf(since, since_len, _(" (upgraded from %s)"),
228 level_to_str(jcr->JobLevel));
229 jcr->JobLevel = jcr->jr.JobLevel = JobLevel;
230 jcr->jr.JobId = jcr->JobId;
234 bstrncpy(since, _(", since="), since_len);
235 bstrncat(since, jcr->stime, since_len);
237 jcr->jr.JobId = jcr->JobId;
240 Dmsg2(100, "Level=%c last start time=%s\n", jcr->JobLevel, jcr->stime);
243 static void send_since_time(JCR *jcr)
245 BSOCK *fd = jcr->file_bsock;
249 stime = str_to_utime(jcr->stime);
250 fd->fsend(levelcmd, "", NT_("since_utime "), edit_uint64(stime, ed1), 0);
251 while (bget_dirmsg(fd) >= 0) { /* allow him to poll us to sync clocks */
252 Jmsg(jcr, M_INFO, 0, "%s\n", fd->msg);
257 * Send level command to FD.
258 * Used for backup jobs and estimate command.
260 bool send_level_command(JCR *jcr)
262 BSOCK *fd = jcr->file_bsock;
263 const char *accurate=jcr->job->accurate?"accurate_":"";
264 const char *not_accurate="";
266 * Send Level command to File daemon
268 switch (jcr->JobLevel) {
270 fd->fsend(levelcmd, not_accurate, "base", " ", 0);
272 /* L_NONE is the console, sending something off to the FD */
275 fd->fsend(levelcmd, not_accurate, "full", " ", 0);
278 fd->fsend(levelcmd, accurate, "differential", " ", 0);
279 send_since_time(jcr);
282 fd->fsend(levelcmd, accurate, "incremental", " ", 0);
283 send_since_time(jcr);
287 Jmsg2(jcr, M_FATAL, 0, _("Unimplemented backup level %d %c\n"),
288 jcr->JobLevel, jcr->JobLevel);
291 Dmsg1(120, ">filed: %s", fd->msg);
292 if (!response(jcr, fd, OKlevel, "Level", DISPLAY_ERROR)) {
299 * Send either an Included or an Excluded list to FD
301 static bool send_fileset(JCR *jcr)
303 FILESET *fileset = jcr->fileset;
304 BSOCK *fd = jcr->file_bsock;
310 num = fileset->num_includes;
312 num = fileset->num_excludes;
314 for (int i=0; i<num; i++) {
320 ie = fileset->include_items[i];
323 ie = fileset->exclude_items[i];
326 for (j=0; j<ie->num_opts; j++) {
327 FOPTS *fo = ie->opts_list[j];
328 fd->fsend("O %s\n", fo->opts);
330 bool enhanced_wild = false;
331 for (k=0; fo->opts[k]!='\0'; k++) {
332 if (fo->opts[k]=='W') {
333 enhanced_wild = true;
338 for (k=0; k<fo->regex.size(); k++) {
339 fd->fsend("R %s\n", fo->regex.get(k));
341 for (k=0; k<fo->regexdir.size(); k++) {
342 fd->fsend("RD %s\n", fo->regexdir.get(k));
344 for (k=0; k<fo->regexfile.size(); k++) {
345 fd->fsend("RF %s\n", fo->regexfile.get(k));
347 for (k=0; k<fo->wild.size(); k++) {
348 fd->fsend("W %s\n", fo->wild.get(k));
350 for (k=0; k<fo->wilddir.size(); k++) {
351 fd->fsend("WD %s\n", fo->wilddir.get(k));
353 for (k=0; k<fo->wildfile.size(); k++) {
354 fd->fsend("WF %s\n", fo->wildfile.get(k));
356 for (k=0; k<fo->wildbase.size(); k++) {
357 fd->fsend("W%c %s\n", enhanced_wild ? 'B' : 'F', fo->wildbase.get(k));
359 for (k=0; k<fo->base.size(); k++) {
360 fd->fsend("B %s\n", fo->base.get(k));
362 for (k=0; k<fo->fstype.size(); k++) {
363 fd->fsend("X %s\n", fo->fstype.get(k));
365 for (k=0; k<fo->drivetype.size(); k++) {
366 fd->fsend("XD %s\n", fo->drivetype.get(k));
369 fd->fsend("G %s\n", fo->plugin);
372 fd->fsend("D %s\n", fo->reader);
375 fd->fsend("T %s\n", fo->writer);
380 for (j=0; j<ie->name_list.size(); j++) {
381 item = (char *)ie->name_list.get(j);
382 if (!send_list_item(jcr, "F ", item, fd)) {
387 for (j=0; j<ie->plugin_list.size(); j++) {
388 item = (char *)ie->plugin_list.get(j);
389 if (!send_list_item(jcr, "P ", item, fd)) {
395 if (!include) { /* If we just did excludes */
396 break; /* all done */
398 include = false; /* Now do excludes */
401 fd->signal(BNET_EOD); /* end of data */
402 if (!response(jcr, fd, OKinc, "Include", DISPLAY_ERROR)) {
408 set_jcr_job_status(jcr, JS_ErrorTerminated);
413 static bool send_list_item(JCR *jcr, const char *code, char *item, BSOCK *fd)
423 p++; /* skip over the | */
424 fd->msg = edit_job_codes(jcr, fd->msg, p, "");
425 bpipe = open_bpipe(fd->msg, 0, "r");
428 Jmsg(jcr, M_FATAL, 0, _("Cannot run program: %s. ERR=%s\n"),
432 bstrncpy(buf, code, sizeof(buf));
433 Dmsg1(500, "code=%s\n", buf);
434 optlen = strlen(buf);
435 while (fgets(buf+optlen, sizeof(buf)-optlen, bpipe->rfd)) {
436 fd->msglen = Mmsg(fd->msg, "%s", buf);
437 Dmsg2(500, "Inc/exc len=%d: %s", fd->msglen, fd->msg);
438 if (!bnet_send(fd)) {
439 Jmsg(jcr, M_FATAL, 0, _(">filed: write error on socket\n"));
443 if ((stat=close_bpipe(bpipe)) != 0) {
445 Jmsg(jcr, M_FATAL, 0, _("Error running program: %s. ERR=%s\n"),
446 p, be.bstrerror(stat));
451 p++; /* skip over < */
452 if ((ffd = fopen(p, "rb")) == NULL) {
454 Jmsg(jcr, M_FATAL, 0, _("Cannot open included file: %s. ERR=%s\n"),
458 bstrncpy(buf, code, sizeof(buf));
459 Dmsg1(500, "code=%s\n", buf);
460 optlen = strlen(buf);
461 while (fgets(buf+optlen, sizeof(buf)-optlen, ffd)) {
462 fd->msglen = Mmsg(fd->msg, "%s", buf);
463 if (!bnet_send(fd)) {
464 Jmsg(jcr, M_FATAL, 0, _(">filed: write error on socket\n"));
471 p++; /* skip over \ */
472 /* Note, fall through wanted */
474 pm_strcpy(fd->msg, code);
475 fd->msglen = pm_strcat(fd->msg, p);
476 Dmsg1(500, "Inc/Exc name=%s\n", fd->msg);
478 Jmsg(jcr, M_FATAL, 0, _(">filed: write error on socket\n"));
488 * Send include list to File daemon
490 bool send_include_list(JCR *jcr)
492 BSOCK *fd = jcr->file_bsock;
493 if (jcr->fileset->new_include) {
494 fd->fsend(filesetcmd, jcr->fileset->enable_vss ? " vss=1" : "");
495 return send_fileset(jcr);
502 * Send exclude list to File daemon
503 * Under the new scheme, the Exclude list
504 * is part of the FileSet sent with the
505 * "include_list" above.
507 bool send_exclude_list(JCR *jcr)
514 * Send bootstrap file if any to the socket given (FD or SD).
515 * This is used for restore, verify VolumeToCatalog, and
518 bool send_bootstrap_file(JCR *jcr, BSOCK *sock)
522 const char *bootstrap = "bootstrap\n";
524 Dmsg1(400, "send_bootstrap_file: %s\n", jcr->RestoreBootstrap);
525 if (!jcr->RestoreBootstrap) {
528 bs = fopen(jcr->RestoreBootstrap, "rb");
531 Jmsg(jcr, M_FATAL, 0, _("Could not open bootstrap file %s: ERR=%s\n"),
532 jcr->RestoreBootstrap, be.bstrerror());
533 set_jcr_job_status(jcr, JS_ErrorTerminated);
536 sock->fsend(bootstrap);
537 while (fgets(buf, sizeof(buf), bs)) {
538 sock->fsend("%s", buf);
540 sock->signal(BNET_EOD);
542 if (jcr->unlink_bsr) {
543 unlink(jcr->RestoreBootstrap);
544 jcr->unlink_bsr = false;
549 /* TODO: drop this with runscript.old_proto in bacula 1.42 */
550 static char runbefore[] = "RunBeforeJob %s\n";
551 static char runafter[] = "RunAfterJob %s\n";
552 static char OKRunBefore[] = "2000 OK RunBefore\n";
553 static char OKRunAfter[] = "2000 OK RunAfter\n";
555 int send_runscript_with_old_proto(JCR *jcr, int when, POOLMEM *msg)
558 Dmsg1(120, "bdird: sending old runcommand to fd '%s'\n",msg);
559 if (when & SCRIPT_Before) {
560 bnet_fsend(jcr->file_bsock, runbefore, msg);
561 ret = response(jcr, jcr->file_bsock, OKRunBefore, "ClientRunBeforeJob", DISPLAY_ERROR);
563 bnet_fsend(jcr->file_bsock, runafter, msg);
564 ret = response(jcr, jcr->file_bsock, OKRunAfter, "ClientRunAfterJob", DISPLAY_ERROR);
570 * Send RunScripts to File daemon
571 * 1) We send all runscript to FD, they can be executed Before, After, or twice
572 * 2) Then, we send a "RunBeforeNow" command to the FD to tell him to do the
573 * first run_script() call. (ie ClientRunBeforeJob)
575 int send_runscripts_commands(JCR *jcr)
577 POOLMEM *msg = get_pool_memory(PM_FNAME);
578 BSOCK *fd = jcr->file_bsock;
580 bool launch_before_cmd = false;
581 POOLMEM *ehost = get_pool_memory(PM_FNAME);
584 Dmsg0(120, "bdird: sending runscripts to fd\n");
586 foreach_alist(cmd, jcr->job->RunScripts) {
587 if (cmd->can_run_at_level(jcr->JobLevel) && cmd->target) {
588 ehost = edit_job_codes(jcr, ehost, cmd->target, "");
589 Dmsg2(200, "bdird: runscript %s -> %s\n", cmd->target, ehost);
591 if (strcmp(ehost, jcr->client->name()) == 0) {
592 pm_strcpy(msg, cmd->command);
595 Dmsg1(120, "bdird: sending runscripts to fd '%s'\n", cmd->command);
597 /* TODO: remove this with bacula 1.42 */
598 if (cmd->old_proto) {
599 result = send_runscript_with_old_proto(jcr, cmd->when, msg);
602 fd->fsend(runscript, cmd->on_success,
608 result = response(jcr, fd, OKRunScript, "RunScript", DISPLAY_ERROR);
609 launch_before_cmd = true;
616 /* TODO : we have to play with other client */
619 send command to an other client
625 /* Tell the FD to execute the ClientRunBeforeJob */
626 if (launch_before_cmd) {
627 fd->fsend(runbeforenow);
628 if (!response(jcr, fd, OKRunBeforeNow, "RunBeforeNow", DISPLAY_ERROR)) {
632 free_pool_memory(msg);
633 free_pool_memory(ehost);
637 Jmsg(jcr, M_FATAL, 0, _("Client \"%s\" RunScript failed.\n"), ehost);
638 free_pool_memory(msg);
639 free_pool_memory(ehost);
646 * Read the attributes from the File daemon for
647 * a Verify job and store them in the catalog.
649 int get_attributes_and_put_in_catalog(JCR *jcr)
654 char digest[MAXSTRING];
656 fd = jcr->file_bsock;
657 jcr->jr.FirstIndex = 1;
659 /* Start transaction allocates jcr->attr and jcr->ar if needed */
660 db_start_transaction(jcr, jcr->db); /* start transaction if not already open */
663 Dmsg0(120, "bdird: waiting to receive file attributes\n");
664 /* Pickup file attributes and digest */
665 while (!fd->errors && (n = bget_dirmsg(fd)) > 0) {
669 char Digest[MAXSTRING]; /* either Verify opts or MD5/SHA1 digest */
671 jcr->fname = check_pool_memory_size(jcr->fname, fd->msglen);
672 if ((len = sscanf(fd->msg, "%ld %d %s", &file_index, &stream, Digest)) != 3) {
673 Jmsg(jcr, M_FATAL, 0, _("<filed: bad attributes, expected 3 fields got %d\n"
674 "msglen=%d msg=%s\n"), len, fd->msglen, fd->msg);
675 set_jcr_job_status(jcr, JS_ErrorTerminated);
679 /* The following three fields were sscanf'ed above so skip them */
680 skip_nonspaces(&p); /* skip FileIndex */
682 skip_nonspaces(&p); /* skip Stream */
684 skip_nonspaces(&p); /* skip Opts_Digest */
685 p++; /* skip space */
686 Dmsg1(dbglvl, "Stream=%d\n", stream);
687 if (stream == STREAM_UNIX_ATTRIBUTES || stream == STREAM_UNIX_ATTRIBUTES_EX) {
688 if (jcr->cached_attribute) {
689 Dmsg3(dbglvl, "Cached attr. Stream=%d fname=%s\n", ar->Stream, ar->fname,
691 if (!db_create_file_attributes_record(jcr, jcr->db, ar)) {
692 Jmsg1(jcr, M_FATAL, 0, _("Attribute create error. %s"), db_strerror(jcr->db));
695 /* Any cached attr is flushed so we can reuse jcr->attr and jcr->ar */
698 *fn++ = *p++; /* copy filename */
700 *fn = *p++; /* term filename and point p to attribs */
701 pm_strcpy(jcr->attr, p); /* save attributes */
703 jcr->FileIndex = file_index;
704 ar->attr = jcr->attr;
705 ar->fname = jcr->fname;
706 ar->FileIndex = file_index;
709 ar->JobId = jcr->JobId;
710 ar->ClientId = jcr->ClientId;
714 ar->DigestType = CRYPTO_DIGEST_NONE;
715 jcr->cached_attribute = true;
717 Dmsg2(dbglvl, "dird<filed: stream=%d %s\n", stream, jcr->fname);
718 Dmsg1(dbglvl, "dird<filed: attr=%s\n", ar->attr);
719 jcr->FileId = ar->FileId;
721 * First, get STREAM_UNIX_ATTRIBUTES and fill ATTR_DBR structure
722 * Next, we CAN have a CRYPTO_DIGEST, so we fill ATTR_DBR with it (or not)
723 * When we get a new STREAM_UNIX_ATTRIBUTES, we known that we can add file to the catalog
724 * At the end, we have to add the last file
726 } else if (crypto_digest_stream_type(stream) != CRYPTO_DIGEST_NONE) {
727 if (jcr->FileIndex != (uint32_t)file_index) {
728 Jmsg3(jcr, M_ERROR, 0, _("%s index %d not same as attributes %d\n"),
729 stream_to_ascii(stream), file_index, jcr->FileIndex);
733 ar->DigestType = crypto_digest_stream_type(stream);
734 db_escape_string(jcr, jcr->db, digest, Digest, strlen(Digest));
735 Dmsg4(dbglvl, "stream=%d DigestLen=%d Digest=%s type=%d\n", stream,
736 strlen(digest), digest, ar->DigestType);
738 jcr->jr.JobFiles = jcr->JobFiles = file_index;
739 jcr->jr.LastIndex = file_index;
741 if (is_bnet_error(fd)) {
742 Jmsg1(jcr, M_FATAL, 0, _("<filed: Network error getting attributes. ERR=%s\n"),
746 if (jcr->cached_attribute) {
747 Dmsg3(dbglvl, "Cached attr with digest. Stream=%d fname=%s attr=%s\n", ar->Stream,
748 ar->fname, ar->attr);
749 if (!db_create_file_attributes_record(jcr, jcr->db, ar)) {
750 Jmsg1(jcr, M_FATAL, 0, _("Attribute create error. %s"), db_strerror(jcr->db));
752 jcr->cached_attribute = false;
754 set_jcr_job_status(jcr, JS_Terminated);