1 /*
2 Bacula(R) - The Network Backup Solution
3
4 Copyright (C) 2000-2020 Kern Sibbald
5
6 The original author of Bacula is Kern Sibbald, with contributions
7 from many others, a complete list can be found in the file AUTHORS.
8
9 You may use this file and others of this release according to the
10 license defined in the LICENSE file, which includes the Affero General
11 Public License, v3.0 ("AGPLv3") and some additional permissions and
12 terms pursuant to its AGPLv3 Section 7.
13
14 This notice must be preserved when any source code is
15 conveyed and/or propagated.
16
17 Bacula(R) is a registered trademark of Kern Sibbald.
18 */
19 /*
20 *
21 * Bacula Director -- fd_cmds.c -- send commands to File daemon
22 *
23 * Kern Sibbald, October MM
24 *
25 * This routine is run as a separate thread. There may be more
26 * work to be done to make it totally reentrant!!!!
27 *
28 * Utility functions for sending info to File Daemon.
29 * These functions are used by both backup and verify.
30 *
31 */
32
33 #include "bacula.h"
34 #include "dird.h"
35 #include "findlib/find.h"
36
37 const int dbglvl = 400;
38
39 /* Commands sent to File daemon */
40 static char filesetcmd[] = "fileset%s%s\n"; /* set full fileset */
41 static char jobcmd[] = "JobId=%s Job=%s SDid=%u SDtime=%u Authorization=%s\n";
42 /* Note, mtime_only is not used here -- implemented as file option */
43 static char levelcmd[] = "level = %s%s%s mtime_only=%d %s%s\n";
44 static char runscript[] = "Run OnSuccess=%u OnFailure=%u AbortOnError=%u When=%u Command=%s\n";
45 static char runbeforenow[]= "RunBeforeNow\n";
46 static char bandwidthcmd[] = "setbandwidth=%lld Job=%s\n";
47 static char component_info[] = "component_info\n";
48
49 /* Responses received from File daemon */
50 static char OKinc[] = "2000 OK include\n";
51 static char OKjob[] = "2000 OK Job";
52 static char OKlevel[] = "2000 OK level\n";
53 static char OKRunScript[] = "2000 OK RunScript\n";
54 static char OKRunBeforeNow[] = "2000 OK RunBeforeNow\n";
55 static char OKRestoreObject[] = "2000 OK ObjectRestored\n";
56 static char OKComponentInfo[] = "2000 OK ComponentInfo\n";
57 static char OKBandwidth[] = "2000 OK Bandwidth\n";
58
59 /* Forward referenced functions */
60 static bool send_list_item(JCR *jcr, const char *code, char *item, BSOCK *fd);
61
62 /* External functions */
63 extern DIRRES *director;
64 extern int FDConnectTimeout;
65
66 #define INC_LIST 0
67 #define EXC_LIST 1
68
delete_bsock_end_cb(JCR * jcr,void * ctx)69 static void delete_bsock_end_cb(JCR *jcr, void *ctx)
70 {
71 BSOCK *socket = (BSOCK *)ctx;
72 free_bsock(socket);
73 }
74
75 /*
76 * Open connection with File daemon.
77 * Try connecting every retry_interval (default 10 sec), and
78 * give up after max_retry_time (default 30 mins).
79 */
80
connect_to_file_daemon(JCR * jcr,int retry_interval,int max_retry_time,int verbose)81 int connect_to_file_daemon(JCR *jcr, int retry_interval, int max_retry_time,
82 int verbose)
83 {
84 BSOCK *fd = jcr->file_bsock;
85 char ed1[30];
86 utime_t heart_beat;
87
88 if (!jcr->client) {
89 Jmsg(jcr, M_FATAL, 0, _("File daemon not defined for current Job\n"));
90 Dmsg0(10, "No Client defined for the job.\n");
91 return 0;
92 }
93
94 if (jcr->client->heartbeat_interval) {
95 heart_beat = jcr->client->heartbeat_interval;
96 } else {
97 heart_beat = director->heartbeat_interval;
98 }
99
100 if (!is_bsock_open(jcr->file_bsock)) {
101 char name[MAX_NAME_LENGTH + 100];
102 POOL_MEM buf;
103
104 bstrncpy(name, _("Client: "), sizeof(name));
105 bstrncat(name, jcr->client->name(), sizeof(name));
106
107 if (jcr->client->allow_fd_connections) {
108 Dmsg0(DT_NETWORK, "Try to use the existing socket if any\n");
109 fd = jcr->client->getBSOCK(max_retry_time);
110 /* Need to free the previous bsock, but without creating a race
111 * condition. We will replace the BSOCK
112 */
113 if (fd && jcr->file_bsock) {
114 Dmsg0(DT_NETWORK, "Found a socket, keep it!\n");
115 job_end_push(jcr, delete_bsock_end_cb, (void *)jcr->file_bsock);
116 }
117
118 /* if address == NULL forget it */
119 if (!fd) {
120 Dmsg0(DT_NETWORK, "No socket in client \n");
121 jcr->setJobStatus(JS_ErrorTerminated);
122 return 0;
123 }
124 jcr->file_bsock = fd;
125 fd->set_jcr(jcr);
126 /* TODO: Need to set TLS down */
127 if (fd->tls) {
128 fd->free_tls();
129 }
130 } else {
131
132 if (!fd) {
133 fd = jcr->file_bsock = new_bsock();
134 }
135
136 fd->set_source_address(director->DIRsrc_addr);
137 if (!fd->connect(jcr,retry_interval,
138 max_retry_time,
139 heart_beat, name,
140 get_client_address(jcr, jcr->client, buf.addr()),
141 NULL,
142 jcr->client->FDport,
143 verbose)) {
144 fd->close();
145 jcr->setJobStatus(JS_ErrorTerminated);
146 return 0;
147 }
148 Dmsg0(10, "Opened connection with File daemon\n");
149 }
150 }
151 fd->res = (RES *)jcr->client; /* save resource in BSOCK */
152 jcr->setJobStatus(JS_Running);
153
154 if (!authenticate_file_daemon(jcr)) {
155 jcr->setJobStatus(JS_ErrorTerminated);
156 Dmsg0(10, "Authentication error with FD.\n");
157 return 0;
158 }
159
160 /*
161 * Now send JobId and authorization key
162 */
163 if (jcr->sd_auth_key == NULL) {
164 jcr->sd_auth_key = bstrdup("dummy");
165 }
166 fd->fsend(jobcmd, edit_int64(jcr->JobId, ed1), jcr->Job, jcr->VolSessionId,
167 jcr->VolSessionTime, jcr->sd_auth_key);
168 if (!jcr->keep_sd_auth_key && strcmp(jcr->sd_auth_key, "dummy")) {
169 memset(jcr->sd_auth_key, 0, strlen(jcr->sd_auth_key));
170 }
171 Dmsg1(100, ">filed: %s", fd->msg);
172 if (bget_dirmsg(fd) > 0) {
173 Dmsg1(110, "<filed: %s", fd->msg);
174 if (strncmp(fd->msg, OKjob, strlen(OKjob)) != 0) {
175 Jmsg(jcr, M_FATAL, 0, _("File daemon \"%s\" rejected Job command: %s\n"),
176 jcr->client->hdr.name, fd->msg);
177 jcr->setJobStatus(JS_ErrorTerminated);
178 return 0;
179 } else if (jcr->db) {
180 CLIENT_DBR cr;
181 memset(&cr, 0, sizeof(cr));
182 bstrncpy(cr.Name, jcr->client->hdr.name, sizeof(cr.Name));
183 cr.AutoPrune = jcr->client->AutoPrune;
184 cr.FileRetention = jcr->client->FileRetention;
185 cr.JobRetention = jcr->client->JobRetention;
186 bstrncpy(cr.Uname, fd->msg+strlen(OKjob)+1, sizeof(cr.Uname));
187 if (!db_update_client_record(jcr, jcr->db, &cr)) {
188 Jmsg(jcr, M_WARNING, 0, _("Error updating Client record. ERR=%s\n"),
189 db_strerror(jcr->db));
190 }
191 }
192 } else {
193 Jmsg(jcr, M_FATAL, 0, _("FD gave bad response to JobId command: %s\n"),
194 fd->bstrerror());
195 jcr->setJobStatus(JS_ErrorTerminated);
196 return 0;
197 }
198 return 1;
199 }
200
201 /*
202 * This subroutine edits the last job start time into a
203 * "since=date/time" buffer that is returned in the
204 * variable since. This is used for display purposes in
205 * the job report. The time in jcr->stime is later
206 * passed to tell the File daemon what to do.
207 */
get_level_since_time(JCR * jcr,char * since,int since_len)208 void get_level_since_time(JCR *jcr, char *since, int since_len)
209 {
210 int JobLevel;
211 bool have_full;
212 bool do_full = false;
213 bool do_vfull = false;
214 bool do_diff = false;
215 bool print_reason2 = false;
216 utime_t now;
217 utime_t last_full_time = 0;
218 utime_t last_diff_time;
219 char prev_job[MAX_NAME_LENGTH], edl[50];
220 const char *reason = "";
221 POOL_MEM reason2;
222
223 since[0] = 0;
224 /* If job cloned and a since time already given, use it */
225 if (jcr->cloned && jcr->stime && jcr->stime[0]) {
226 bstrncpy(since, _(", since="), since_len);
227 bstrncat(since, jcr->stime, since_len);
228 return;
229 }
230 /* Make sure stime buffer is allocated */
231 if (!jcr->stime) {
232 jcr->stime = get_pool_memory(PM_MESSAGE);
233 }
234 jcr->PrevJob[0] = jcr->stime[0] = 0;
235 /*
236 * Lookup the last FULL backup job to get the time/date for a
237 * differential or incremental save.
238 */
239 switch (jcr->getJobLevel()) {
240 case L_DIFFERENTIAL:
241 case L_INCREMENTAL:
242 POOLMEM *stime = get_pool_memory(PM_MESSAGE);
243 /* Look up start time of last Full job */
244 now = (utime_t)time(NULL);
245 jcr->jr.JobId = 0; /* flag to return since time */
246 /*
247 * This is probably redundant, but some of the code below
248 * uses jcr->stime, so don't remove unless you are sure.
249 */
250 if (!db_find_job_start_time(jcr, jcr->db, &jcr->jr, &jcr->stime, jcr->PrevJob)) {
251 do_full = true;
252 reason = _("No prior or suitable Full backup found in catalog. ");
253 }
254 have_full = db_find_last_job_start_time(jcr, jcr->db, &jcr->jr,
255 &stime, prev_job, L_FULL);
256 if (have_full) {
257 last_full_time = str_to_utime(stime);
258 } else {
259 do_full = true; /* No full, upgrade to one */
260
261 /* We try to determine if we have a previous Full backup with an other FileSetId */
262 DBId_t id = jcr->jr.FileSetId;
263 jcr->jr.FileSetId = 0;
264 if (db_find_last_job_start_time(jcr, jcr->db, &jcr->jr, &stime, prev_job, L_FULL)) {
265 FILESET_DBR fs;
266 memset(&fs, 0, sizeof(fs));
267 fs.FileSetId = id;
268 /* Print more information about the last fileset */
269 if (db_get_fileset_record(jcr, jcr->db, &fs)) {
270 Mmsg(reason2, _("The FileSet \"%s\" was modified on %s, this is after the last successful backup on %s."),
271 fs.FileSet, fs.cCreateTime, stime);
272 print_reason2=true;
273 }
274 reason = _("No prior or suitable Full backup found in catalog for the current FileSet. ");
275 } else {
276 reason = _("No prior or suitable Full backup found in catalog. ");
277 }
278 jcr->jr.FileSetId = id;
279 }
280 Dmsg4(50, "have_full=%d do_full=%d now=%lld full_time=%lld\n", have_full,
281 do_full, now, last_full_time);
282 /* Make sure the last diff is recent enough */
283 if (have_full && jcr->getJobLevel() == L_INCREMENTAL && jcr->job->MaxDiffInterval > 0) {
284 /* Lookup last diff job */
285 if (db_find_last_job_start_time(jcr, jcr->db, &jcr->jr,
286 &stime, prev_job, L_DIFFERENTIAL)) {
287 last_diff_time = str_to_utime(stime);
288 /* If no Diff since Full, use Full time */
289 if (last_diff_time < last_full_time) {
290 last_diff_time = last_full_time;
291 }
292 Dmsg2(50, "last_diff_time=%lld last_full_time=%lld\n", last_diff_time,
293 last_full_time);
294 } else {
295 /* No last differential, so use last full time */
296 last_diff_time = last_full_time;
297 Dmsg1(50, "No last_diff_time setting to full_time=%lld\n", last_full_time);
298 }
299 do_diff = ((now - last_diff_time) >= jcr->job->MaxDiffInterval);
300 if (do_diff) {
301 reason = _("Max Diff Interval exceeded. ");
302 }
303 Dmsg2(50, "do_diff=%d diffInter=%lld\n", do_diff, jcr->job->MaxDiffInterval);
304 }
305 /* Note, do_full takes precedence over do_vfull and do_diff */
306 if (have_full && jcr->job->MaxFullInterval > 0) {
307 do_full = ((now - last_full_time) >= jcr->job->MaxFullInterval);
308 if (do_full) {
309 reason = _("Max Full Interval exceeded. ");
310 }
311 }
312 else
313 if (have_full && jcr->job->MaxVirtualFullInterval > 0) {
314 do_vfull = ((now - last_full_time) >= jcr->job->MaxVirtualFullInterval);
315 }
316
317 free_pool_memory(stime);
318
319 if (do_full) {
320 /* No recent Full job found, so upgrade this one to Full */
321 if (print_reason2) {
322 Jmsg(jcr, M_INFO, 0, "%s\n", reason2.c_str());
323 }
324 Jmsg(jcr, M_INFO, 0, _("%sDoing FULL backup.\n"), reason);
325 bsnprintf(since, since_len, _(" (upgraded from %s)"),
326 level_to_str(edl , sizeof(edl), jcr->getJobLevel()));
327 jcr->setJobLevel(jcr->jr.JobLevel = L_FULL);
328 } else if (do_vfull) {
329 /* No recent Full job found, and MaxVirtualFull is set so upgrade this one to Virtual Full */
330 Jmsg(jcr, M_INFO, 0, "%s", db_strerror(jcr->db));
331 Jmsg(jcr, M_INFO, 0, _("No prior or suitable Full backup found in catalog. Doing Virtual FULL backup.\n"));
332 bsnprintf(since, since_len, _(" (upgraded from %s)"),
333 level_to_str(edl, sizeof(edl), jcr->getJobLevel()));
334 jcr->setJobLevel(jcr->jr.JobLevel = L_VIRTUAL_FULL);
335 } else if (do_diff) {
336 /* No recent diff job found, so upgrade this one to Diff */
337 Jmsg(jcr, M_INFO, 0, _("%sDoing Differential backup.\n"), reason);
338 bsnprintf(since, since_len, _(" (upgraded from %s)"),
339 level_to_str(edl, sizeof(edl), jcr->getJobLevel()));
340 jcr->setJobLevel(jcr->jr.JobLevel = L_DIFFERENTIAL);
341 } else {
342 if (jcr->job->rerun_failed_levels) {
343
344 POOLMEM *etime = get_pool_memory(PM_MESSAGE);
345
346 /* Get the end time of our most recent successfull backup for this job */
347 /* This will be used to see if there have been any failures since then */
348 if (db_find_last_job_end_time(jcr, jcr->db, &jcr->jr, &etime, prev_job)) {
349
350 /* See if there are any failed Differential/Full backups since the completion */
351 /* of our last successful backup for this job */
352 if (db_find_failed_job_since(jcr, jcr->db, &jcr->jr,
353 etime, JobLevel)) {
354 /* If our job is an Incremental and we have a failed job then upgrade. */
355 /* If our job is a Differential and the failed job is a Full then upgrade. */
356 /* Otherwise there is no reason to upgrade. */
357 if ((jcr->getJobLevel() == L_INCREMENTAL) ||
358 ((jcr->getJobLevel() == L_DIFFERENTIAL) && (JobLevel == L_FULL))) {
359 Jmsg(jcr, M_INFO, 0, _("Prior failed job found in catalog. Upgrading to %s.\n"),
360 level_to_str(edl, sizeof(edl), JobLevel));
361 bsnprintf(since, since_len, _(" (upgraded from %s)"),
362 level_to_str(edl, sizeof(edl), jcr->getJobLevel()));
363 jcr->setJobLevel(jcr->jr.JobLevel = JobLevel);
364 jcr->jr.JobId = jcr->JobId;
365 break;
366 }
367 }
368 }
369 free_pool_memory(etime);
370 }
371 bstrncpy(since, _(", since="), since_len);
372 bstrncat(since, jcr->stime, since_len);
373 }
374 jcr->jr.JobId = jcr->JobId;
375 break;
376 }
377 Dmsg3(100, "Level=%c last start time=%s job=%s\n",
378 jcr->getJobLevel(), jcr->stime, jcr->PrevJob);
379 }
380
send_since_time(JCR * jcr)381 static void send_since_time(JCR *jcr)
382 {
383 BSOCK *fd = jcr->file_bsock;
384 utime_t stime;
385 char ed1[50];
386
387 stime = str_to_utime(jcr->stime);
388 fd->fsend(levelcmd, "", NT_("since_utime "), edit_uint64(stime, ed1), 0,
389 NT_("prev_job="), jcr->PrevJob);
390 while (bget_dirmsg(fd) >= 0) { /* allow him to poll us to sync clocks */
391 Jmsg(jcr, M_INFO, 0, "%s\n", fd->msg);
392 }
393 }
394
send_bwlimit(JCR * jcr,const char * Job)395 bool send_bwlimit(JCR *jcr, const char *Job)
396 {
397 BSOCK *fd = jcr->file_bsock;
398 if (jcr->FDVersion >= 4) {
399 fd->fsend(bandwidthcmd, jcr->max_bandwidth, Job);
400 if (!response(jcr, fd, OKBandwidth, "Bandwidth", DISPLAY_ERROR)) {
401 jcr->max_bandwidth = 0; /* can't set bandwidth limit */
402 return false;
403 }
404 }
405 return true;
406 }
407
408 /*
409 * Send level command to FD.
410 * Used for backup jobs and estimate command.
411 */
send_level_command(JCR * jcr)412 bool send_level_command(JCR *jcr)
413 {
414 BSOCK *fd = jcr->file_bsock;
415 const char *accurate = jcr->accurate?"accurate_":"";
416 const char *not_accurate = "";
417 const char *rerunning = jcr->rerunning?" rerunning ":" ";
418 /*
419 * Send Level command to File daemon
420 */
421 switch (jcr->getJobLevel()) {
422 case L_BASE:
423 fd->fsend(levelcmd, not_accurate, "base", rerunning, 0, "", "");
424 break;
425 /* L_NONE is the console, sending something off to the FD */
426 case L_NONE:
427 case L_FULL:
428 fd->fsend(levelcmd, not_accurate, "full", rerunning, 0, "", "");
429 break;
430 case L_DIFFERENTIAL:
431 fd->fsend(levelcmd, accurate, "differential", rerunning, 0, "", "");
432 send_since_time(jcr);
433 break;
434 case L_INCREMENTAL:
435 fd->fsend(levelcmd, accurate, "incremental", rerunning, 0, "", "");
436 send_since_time(jcr);
437 break;
438 case L_SINCE:
439 default:
440 Jmsg2(jcr, M_FATAL, 0, _("Unimplemented backup level %d %c\n"),
441 jcr->getJobLevel(), jcr->getJobLevel());
442 return 0;
443 }
444 Dmsg1(120, ">filed: %s", fd->msg);
445 if (!response(jcr, fd, OKlevel, "Level", DISPLAY_ERROR)) {
446 return false;
447 }
448 return true;
449 }
450
451 /*
452 * Send either an Included or an Excluded list to FD
453 */
send_fileset(JCR * jcr)454 static bool send_fileset(JCR *jcr)
455 {
456 FILESET *fileset = jcr->fileset;
457 BSOCK *fd = jcr->file_bsock;
458 STORE *store = jcr->wstore;
459 int num;
460 bool include = true;
461
462 for ( ;; ) {
463 if (include) {
464 num = fileset->num_includes;
465 } else {
466 num = fileset->num_excludes;
467 }
468 for (int i=0; i<num; i++) {
469 char *item;
470 INCEXE *ie;
471 int j, k;
472
473 if (include) {
474 ie = fileset->include_items[i];
475 fd->fsend("I\n");
476 } else {
477 ie = fileset->exclude_items[i];
478 fd->fsend("E\n");
479 }
480 if (ie->ignoredir) {
481 fd->fsend("Z %s\n", ie->ignoredir);
482 }
483 for (j=0; j<ie->num_opts; j++) {
484 FOPTS *fo = ie->opts_list[j];
485 bool enhanced_wild = false;
486 bool stripped_opts = false;
487 bool compress_disabled = false;
488 char newopts[MAX_FOPTS];
489
490 for (k=0; fo->opts[k]!='\0'; k++) {
491 if (fo->opts[k]=='W') {
492 enhanced_wild = true;
493 break;
494 }
495 }
496
497 /*
498 * Strip out compression option Zn if disallowed
499 * for this Storage.
500 * Strip out dedup option dn if old FD
501 */
502 bool strip_compress = store && !store->AllowCompress;
503 if (strip_compress || jcr->FDVersion >= 11) {
504 int j = 0;
505 for (k=0; fo->opts[k]!='\0'; k++) {
506 /* Z compress option is followed by the single-digit compress level or 'o' */
507 if (strip_compress && fo->opts[k]=='Z') {
508 stripped_opts = true;
509 compress_disabled = true;
510 k++; /* skip level */
511 } else if (jcr->FDVersion < 11 && fo->opts[k]=='d') {
512 stripped_opts = true;
513 k++; /* skip level */
514 } else {
515 newopts[j] = fo->opts[k];
516 j++;
517 }
518 }
519 newopts[j] = '\0';
520 if (compress_disabled) {
521 Jmsg(jcr, M_INFO, 0,
522 _("FD compression disabled for this Job because AllowCompression=No in Storage resource.\n") );
523 }
524 }
525 if (stripped_opts) {
526 /* Send the new trimmed option set without overwriting fo->opts */
527 fd->fsend("O %s\n", newopts);
528 } else {
529 /* Send the original options */
530 fd->fsend("O %s\n", fo->opts);
531 }
532 for (k=0; k<fo->regex.size(); k++) {
533 fd->fsend("R %s\n", fo->regex.get(k));
534 }
535 for (k=0; k<fo->regexdir.size(); k++) {
536 fd->fsend("RD %s\n", fo->regexdir.get(k));
537 }
538 for (k=0; k<fo->regexfile.size(); k++) {
539 fd->fsend("RF %s\n", fo->regexfile.get(k));
540 }
541 for (k=0; k<fo->wild.size(); k++) {
542 fd->fsend("W %s\n", fo->wild.get(k));
543 }
544 for (k=0; k<fo->wilddir.size(); k++) {
545 fd->fsend("WD %s\n", fo->wilddir.get(k));
546 }
547 for (k=0; k<fo->wildfile.size(); k++) {
548 fd->fsend("WF %s\n", fo->wildfile.get(k));
549 }
550 for (k=0; k<fo->wildbase.size(); k++) {
551 fd->fsend("W%c %s\n", enhanced_wild ? 'B' : 'F', fo->wildbase.get(k));
552 }
553 for (k=0; k<fo->base.size(); k++) {
554 fd->fsend("B %s\n", fo->base.get(k));
555 }
556 for (k=0; k<fo->fstype.size(); k++) {
557 fd->fsend("X %s\n", fo->fstype.get(k));
558 }
559 for (k=0; k<fo->drivetype.size(); k++) {
560 fd->fsend("XD %s\n", fo->drivetype.get(k));
561 }
562 if (fo->plugin) {
563 fd->fsend("G %s\n", fo->plugin);
564 }
565 if (fo->reader) {
566 fd->fsend("D %s\n", fo->reader);
567 }
568 if (fo->writer) {
569 fd->fsend("T %s\n", fo->writer);
570 }
571 fd->fsend("N\n");
572 }
573
574 for (j=0; j<ie->name_list.size(); j++) {
575 item = (char *)ie->name_list.get(j);
576 if (!send_list_item(jcr, "F ", item, fd)) {
577 goto bail_out;
578 }
579 }
580 fd->fsend("N\n");
581 for (j=0; j<ie->plugin_list.size(); j++) {
582 item = (char *)ie->plugin_list.get(j);
583 if (!send_list_item(jcr, "P ", item, fd)) {
584 goto bail_out;
585 }
586 }
587 fd->fsend("N\n");
588 }
589 if (!include) { /* If we just did excludes */
590 break; /* all done */
591 }
592 include = false; /* Now do excludes */
593 }
594
595 fd->signal(BNET_EOD); /* end of data */
596 if (!response(jcr, fd, OKinc, "Include", DISPLAY_ERROR)) {
597 goto bail_out;
598 }
599 return true;
600
601 bail_out:
602 jcr->setJobStatus(JS_ErrorTerminated);
603 return false;
604
605 }
606
send_list_item(JCR * jcr,const char * code,char * item,BSOCK * fd)607 static bool send_list_item(JCR *jcr, const char *code, char *item, BSOCK *fd)
608 {
609 BPIPE *bpipe;
610 FILE *ffd;
611 char buf[2000];
612 int optlen, stat;
613 char *p = item;
614
615 switch (*p) {
616 case '|':
617 p++; /* skip over the | */
618 fd->msg = edit_job_codes(jcr, fd->msg, p, "");
619 bpipe = open_bpipe(fd->msg, 0, "r");
620 if (!bpipe) {
621 berrno be;
622 Jmsg(jcr, M_FATAL, 0, _("Cannot run program: %s. ERR=%s\n"),
623 p, be.bstrerror());
624 return false;
625 }
626 bstrncpy(buf, code, sizeof(buf));
627 Dmsg1(500, "code=%s\n", buf);
628 optlen = strlen(buf);
629 while (fgets(buf+optlen, sizeof(buf)-optlen, bpipe->rfd)) {
630 fd->msglen = Mmsg(fd->msg, "%s", buf);
631 Dmsg2(500, "Inc/exc len=%d: %s", fd->msglen, fd->msg);
632 if (!fd->send()) {
633 close_bpipe(bpipe);
634 Jmsg(jcr, M_FATAL, 0, _(">filed: write error on socket\n"));
635 return false;
636 }
637 }
638 if ((stat=close_bpipe(bpipe)) != 0) {
639 berrno be;
640 Jmsg(jcr, M_FATAL, 0, _("Error running program: %s. ERR=%s\n"),
641 p, be.bstrerror(stat));
642 return false;
643 }
644 break;
645 case '<':
646 p++; /* skip over < */
647 if ((ffd = bfopen(p, "rb")) == NULL) {
648 berrno be;
649 Jmsg(jcr, M_FATAL, 0, _("Cannot open included file: %s. ERR=%s\n"),
650 p, be.bstrerror());
651 return false;
652 }
653 bstrncpy(buf, code, sizeof(buf));
654 Dmsg1(500, "code=%s\n", buf);
655 optlen = strlen(buf);
656 while (fgets(buf+optlen, sizeof(buf)-optlen, ffd)) {
657 fd->msglen = Mmsg(fd->msg, "%s", buf);
658 if (!fd->send()) {
659 fclose(ffd);
660 Jmsg(jcr, M_FATAL, 0, _(">filed: write error on socket\n"));
661 return false;
662 }
663 }
664 fclose(ffd);
665 break;
666 case '\\':
667 p++; /* skip over \ */
668 /* Note, fall through wanted */
669 default:
670 pm_strcpy(fd->msg, code);
671 fd->msglen = pm_strcat(fd->msg, p);
672 Dmsg1(500, "Inc/Exc name=%s\n", fd->msg);
673 if (!fd->send()) {
674 Jmsg(jcr, M_FATAL, 0, _(">filed: write error on socket\n"));
675 return false;
676 }
677 break;
678 }
679 return true;
680 }
681
682
683 /*
684 * Send include list to File daemon
685 */
send_include_list(JCR * jcr)686 bool send_include_list(JCR *jcr)
687 {
688 BSOCK *fd = jcr->file_bsock;
689 if (jcr->fileset->new_include) {
690 fd->fsend(filesetcmd,
691 jcr->fileset->enable_vss ? " vss=1" : "",
692 jcr->fileset->enable_snapshot ? " snap=1" : "");
693 return send_fileset(jcr);
694 }
695 return true;
696 }
697
698 /*
699 * Send an include list with a plugin and listing=<path> parameter
700 */
send_ls_plugin_fileset(JCR * jcr,const char * plugin,const char * path)701 bool send_ls_plugin_fileset(JCR *jcr, const char *plugin, const char *path)
702 {
703 BSOCK *fd = jcr->file_bsock;
704 fd->fsend(filesetcmd, "" /* no vss */, "" /* no snapshot */);
705
706 fd->fsend("I\n");
707 fd->fsend("O h\n"); /* is it required? */
708 fd->fsend("N\n");
709 fd->fsend("P %s%s listing=%s\n", plugin, strchr(plugin, ':') == NULL ? ":" : "", path);
710 fd->fsend("N\n");
711 fd->signal(BNET_EOD); /* end of data */
712
713 if (!response(jcr, fd, OKinc, "Include", DISPLAY_ERROR)) {
714 return false;
715 }
716 return true;
717 }
718
719 /*
720 * Send a include list with only one directory and recurse=no
721 */
send_ls_fileset(JCR * jcr,const char * path)722 bool send_ls_fileset(JCR *jcr, const char *path)
723 {
724 BSOCK *fd = jcr->file_bsock;
725 fd->fsend(filesetcmd, "" /* no vss */, "" /* no snapshot */);
726
727 fd->fsend("I\n");
728 fd->fsend("O h\n"); /* Limit recursion to one directory */
729 fd->fsend("N\n");
730 fd->fsend("F %s\n", path);
731 fd->fsend("N\n");
732 fd->signal(BNET_EOD); /* end of data */
733
734 if (!response(jcr, fd, OKinc, "Include", DISPLAY_ERROR)) {
735 return false;
736 }
737 return true;
738 }
739
740
741 /*
742 * Send exclude list to File daemon
743 * Under the new scheme, the Exclude list
744 * is part of the FileSet sent with the
745 * "include_list" above.
746 */
send_exclude_list(JCR * jcr)747 bool send_exclude_list(JCR *jcr)
748 {
749 return true;
750 }
751
752 /* TODO: drop this with runscript.old_proto in bacula 1.42 */
753 static char runbefore[] = "RunBeforeJob %s\n";
754 static char runafter[] = "RunAfterJob %s\n";
755 static char OKRunBefore[] = "2000 OK RunBefore\n";
756 static char OKRunAfter[] = "2000 OK RunAfter\n";
757
send_runscript_with_old_proto(JCR * jcr,int when,POOLMEM * msg)758 int send_runscript_with_old_proto(JCR *jcr, int when, POOLMEM *msg)
759 {
760 int ret;
761 Dmsg1(120, "bdird: sending old runcommand to fd '%s'\n",msg);
762 if (when & SCRIPT_Before) {
763 jcr->file_bsock->fsend(runbefore, msg);
764 ret = response(jcr, jcr->file_bsock, OKRunBefore, "ClientRunBeforeJob", DISPLAY_ERROR);
765 } else {
766 jcr->file_bsock->fsend(runafter, msg);
767 ret = response(jcr, jcr->file_bsock, OKRunAfter, "ClientRunAfterJob", DISPLAY_ERROR);
768 }
769 return ret;
770 } /* END OF TODO */
771
772 /*
773 * Send RunScripts to File daemon
774 * 1) We send all runscript to FD, they can be executed Before, After, or twice
775 * 2) Then, we send a "RunBeforeNow" command to the FD to tell him to do the
776 * first run_script() call. (ie ClientRunBeforeJob)
777 */
send_runscripts_commands(JCR * jcr)778 int send_runscripts_commands(JCR *jcr)
779 {
780 POOLMEM *msg = get_pool_memory(PM_FNAME);
781 BSOCK *fd = jcr->file_bsock;
782 RUNSCRIPT *cmd;
783 bool launch_before_cmd = false;
784 POOLMEM *ehost = get_pool_memory(PM_FNAME);
785 int result;
786
787 Dmsg0(120, "bdird: sending runscripts to fd\n");
788 if (!jcr->job->RunScripts) {
789 goto norunscript;
790 }
791 foreach_alist(cmd, jcr->job->RunScripts) {
792 if (cmd->can_run_at_level(jcr->getJobLevel()) && cmd->target) {
793 ehost = edit_job_codes(jcr, ehost, cmd->target, "");
794 Dmsg2(200, "bdird: runscript %s -> %s\n", cmd->target, ehost);
795
796 if (strcmp(ehost, jcr->client->name()) == 0) {
797 pm_strcpy(msg, cmd->command);
798 bash_spaces(msg);
799
800 Dmsg1(120, "bdird: sending runscripts to fd '%s'\n", cmd->command);
801
802 /* TODO: remove this with bacula 1.42 */
803 if (cmd->old_proto) {
804 result = send_runscript_with_old_proto(jcr, cmd->when, msg);
805
806 } else {
807 fd->fsend(runscript, cmd->on_success,
808 cmd->on_failure,
809 cmd->fail_on_error,
810 cmd->when,
811 msg);
812
813 result = response(jcr, fd, OKRunScript, "RunScript", DISPLAY_ERROR);
814 launch_before_cmd = true;
815 }
816
817 if (!result) {
818 goto bail_out;
819 }
820 }
821 /* TODO : we have to play with other client */
822 /*
823 else {
824 send command to an other client
825 }
826 */
827 }
828 }
829
830 /* Tell the FD to execute the ClientRunBeforeJob */
831 if (launch_before_cmd) {
832 fd->fsend(runbeforenow);
833 if (!response(jcr, fd, OKRunBeforeNow, "RunBeforeNow", DISPLAY_ERROR)) {
834 goto bail_out;
835 }
836 }
837 norunscript:
838 free_pool_memory(msg);
839 free_pool_memory(ehost);
840 return 1;
841
842 bail_out:
843 Jmsg(jcr, M_FATAL, 0, _("Client \"%s\" RunScript failed.\n"), ehost);
844 free_pool_memory(msg);
845 free_pool_memory(ehost);
846 return 0;
847 }
848
849 struct OBJ_CTX {
850 JCR *jcr;
851 int count;
852 };
853
restore_object_handler(void * ctx,int num_fields,char ** row)854 static int restore_object_handler(void *ctx, int num_fields, char **row)
855 {
856 OBJ_CTX *octx = (OBJ_CTX *)ctx;
857 JCR *jcr = octx->jcr;
858 BSOCK *fd;
859
860 fd = jcr->file_bsock;
861 if (jcr->is_job_canceled()) {
862 return 1;
863 }
864 /* Old File Daemon doesn't handle restore objects */
865 if (jcr->FDVersion < 3) {
866 Jmsg(jcr, M_WARNING, 0, _("Client \"%s\" may not be used to restore "
867 "this job. Please upgrade your client.\n"),
868 jcr->client->name());
869 return 1;
870 }
871
872 if (jcr->FDVersion < 5) { /* Old version without PluginName */
873 fd->fsend("restoreobject JobId=%s %s,%s,%s,%s,%s,%s\n",
874 row[0], row[1], row[2], row[3], row[4], row[5], row[6]);
875 } else {
876 /* bash spaces from PluginName */
877 bash_spaces(row[9]);
878 fd->fsend("restoreobject JobId=%s %s,%s,%s,%s,%s,%s,%s\n",
879 row[0], row[1], row[2], row[3], row[4], row[5], row[6], row[9]);
880 }
881 Dmsg1(010, "Send obj hdr=%s", fd->msg);
882
883 fd->msglen = pm_strcpy(fd->msg, row[7]);
884 fd->send(); /* send Object name */
885
886 Dmsg1(010, "Send obj: %s\n", fd->msg);
887
888 // fd->msglen = str_to_uint64(row[1]); /* object length */
889 // Dmsg1(000, "obj size: %lld\n", (uint64_t)fd->msglen);
890
891 /* object */
892 db_unescape_object(jcr, jcr->db,
893 row[8], /* Object */
894 str_to_uint64(row[1]), /* Object length */
895 &fd->msg, &fd->msglen);
896 fd->send(); /* send object */
897 octx->count++;
898
899 if (debug_level > 100) {
900 for (int i=0; i < fd->msglen; i++)
901 if (!fd->msg[i])
902 fd->msg[i] = ' ';
903 Dmsg1(000, "Send obj: %s\n", fd->msg);
904 }
905
906 return 0;
907 }
908
909 /* Send the restore file list to the plugin */
feature_send_restorefilelist(JCR * jcr,const char * plugin)910 void feature_send_restorefilelist(JCR *jcr, const char *plugin)
911 {
912 if (!jcr->bsr_list) {
913 Dmsg0(10, "Unable to send restore file list\n");
914 return;
915 }
916 jcr->file_bsock->fsend("restorefilelist plugin=%s\n", plugin);
917 scan_bsr(jcr);
918 jcr->file_bsock->signal(BNET_EOD);
919 }
920
921 typedef struct {
922 const char *name; /* Name of the feature */
923 alist *plugins; /* List of the plugins that can use the feature */
924 void (*handler)(JCR *, const char *plugin); /* handler for the feature */
925 } PluginFeatures;
926
927 /* List of all the supported features. This table is global and
928 * each job should do a copy.
929 */
930 const static PluginFeatures plugin_features[] = {
931 /* name plugins handler */
932 {PLUGIN_FEATURE_RESTORELISTFILES, NULL, feature_send_restorefilelist},
933 {NULL, NULL, NULL}
934 };
935
936 #define NB_FEATURES (sizeof(plugin_features) / sizeof(PluginFeatures))
937
938 /* Take a plugin and a feature name, and fill the Feature list */
fill_feature_list(JCR * jcr,PluginFeatures * features,char * plugin,char * name)939 static void fill_feature_list(JCR *jcr, PluginFeatures *features, char *plugin, char *name)
940 {
941 for (int i=0; features[i].name != NULL ; i++) {
942 if (strcasecmp(features[i].name, name) == 0) {
943 if (features[i].plugins == NULL) {
944 features[i].plugins = New(alist(10, owned_by_alist));
945 }
946 Dmsg2(10, "plugin=%s match feature %s\n", plugin, name);
947 features[i].plugins->append(bstrdup(plugin));
948 break;
949 }
950 }
951 }
952
953 /* Free the memory allocated by get_plugin_features() */
free_plugin_features(PluginFeatures * features)954 static void free_plugin_features(PluginFeatures *features)
955 {
956 if (!features) {
957 return;
958 }
959 for (int i=0; features[i].name != NULL ; i++) {
960 if (features[i].plugins != NULL) {
961 delete features[i].plugins;
962 }
963 }
964 free(features);
965 }
966
967 /* Can be used in Backup or Restore jobs to know what a plugin can do
968 * Need to call free_plugin_features() to release the PluginFeatures argument
969 */
get_plugin_features(JCR * jcr,PluginFeatures ** ret)970 static bool get_plugin_features(JCR *jcr, PluginFeatures **ret)
971 {
972 POOL_MEM buf;
973 char ed1[128];
974 BSOCK *fd = jcr->file_bsock;
975 PluginFeatures *features;
976 char *p, *start;
977
978 *ret = NULL;
979
980 if (jcr->FDVersion < 14 || jcr->FDVersion == 213 || jcr->FDVersion == 214) {
981 return false; /* File Daemon too old or not compatible */
982 }
983
984 /* Copy the features table */
985 features = (PluginFeatures *)malloc(sizeof(PluginFeatures) * NB_FEATURES);
986 memcpy(features, plugin_features, sizeof(plugin_features));
987
988 /* Get the list of the features that the plugins can request
989 * ex: plugin=ndmp features=files,feature1,feature2
990 */
991 fd->fsend("PluginFeatures\n");
992
993 while (bget_dirmsg(fd) > 0) {
994 buf.check_size(fd->msglen+1);
995 if (sscanf(fd->msg, "2000 plugin=%127s features=%s", ed1, buf.c_str()) == 2) {
996 /* We have buf=feature1,feature2,feature3 */
997 start = buf.c_str();
998 while ((p = next_name(&start)) != NULL) {
999 fill_feature_list(jcr, features, ed1, p);
1000 }
1001 } else {
1002 Dmsg1(10, "Something wrong with the protocol %s\n", fd->msg);
1003 free_plugin_features(features);
1004 return false;
1005 }
1006 }
1007 *ret = features;
1008 return true;
1009 }
1010
1011 /* See with the FD if we need to send the list of all the files
1012 * to be restored before the start of the job
1013 */
send_restore_file_list(JCR * jcr)1014 bool send_restore_file_list(JCR *jcr)
1015 {
1016 PluginFeatures *features=NULL;
1017
1018 /* TODO: If we have more features, we can store the features list in the JCR */
1019 if (!get_plugin_features(jcr, &features)) {
1020 return true; /* Not handled by FD */
1021 }
1022
1023 /* Now, we can deal with what the plugins want */
1024 for (int i=0; features[i].name != NULL ; i++) {
1025 if (strcmp(features[i].name, PLUGIN_FEATURE_RESTORELISTFILES) == 0) {
1026 if (features[i].plugins != NULL) {
1027 char *plug;
1028 foreach_alist(plug, features[i].plugins) {
1029 features[i].handler(jcr, plug);
1030 }
1031 }
1032 break;
1033 }
1034 }
1035
1036 free_plugin_features(features);
1037 return true;
1038 }
1039
1040 /*
1041 * Send the plugin Restore Objects, which allow the
1042 * plugin to get information early in the restore
1043 * process. The RestoreObjects were created during
1044 * the backup by the plugin.
1045 */
send_restore_objects(JCR * jcr)1046 bool send_restore_objects(JCR *jcr)
1047 {
1048 char ed1[50];
1049 POOL_MEM query(PM_MESSAGE);
1050 BSOCK *fd;
1051 OBJ_CTX octx;
1052
1053 if (!jcr->JobIds || !jcr->JobIds[0]) {
1054 return true;
1055 }
1056 octx.jcr = jcr;
1057 octx.count = 0;
1058
1059 /* restore_object_handler is called for each file found */
1060
1061 /* send restore objects for all jobs involved */
1062 Mmsg(query, get_restore_objects, jcr->JobIds, FT_RESTORE_FIRST);
1063 db_sql_query(jcr->db, query.c_str(), restore_object_handler, (void *)&octx);
1064
1065 /* send config objects for the current restore job */
1066 Mmsg(query, get_restore_objects,
1067 edit_uint64(jcr->JobId, ed1), FT_PLUGIN_CONFIG_FILLED);
1068 db_sql_query(jcr->db, query.c_str(), restore_object_handler, (void *)&octx);
1069
1070 /*
1071 * Send to FD only if we have at least one restore object.
1072 * This permits backward compatibility with older FDs.
1073 */
1074 if (octx.count > 0) {
1075 fd = jcr->file_bsock;
1076 fd->fsend("restoreobject end\n");
1077 if (!response(jcr, fd, OKRestoreObject, "RestoreObject", DISPLAY_ERROR)) {
1078 Jmsg(jcr, M_FATAL, 0, _("RestoreObject failed.\n"));
1079 return false;
1080 }
1081 }
1082 return true;
1083 }
1084
1085 /*
1086 * Send the plugin a list of component info files. These
1087 * were files that were created during the backup for
1088 * the VSS plugin. The list is a list of those component
1089 * files that have been chosen for restore. We
1090 * send them before the Restore Objects.
1091 */
send_component_info(JCR * jcr)1092 bool send_component_info(JCR *jcr)
1093 {
1094 BSOCK *fd;
1095 char buf[2000];
1096 bool ok = true;
1097
1098 if (!jcr->component_fd) {
1099 return true; /* nothing to send */
1100 }
1101 /* Don't send if old version FD */
1102 if (jcr->FDVersion < 6) {
1103 goto bail_out;
1104 }
1105
1106 rewind(jcr->component_fd);
1107 fd = jcr->file_bsock;
1108 fd->fsend(component_info);
1109 while (fgets(buf, sizeof(buf), jcr->component_fd)) {
1110 fd->fsend("%s", buf);
1111 Dmsg1(050, "Send component_info to FD: %s\n", buf);
1112 }
1113 fd->signal(BNET_EOD);
1114 if (!response(jcr, fd, OKComponentInfo, "ComponentInfo", DISPLAY_ERROR)) {
1115 Jmsg(jcr, M_FATAL, 0, _("ComponentInfo failed.\n"));
1116 ok = false;
1117 }
1118
1119 bail_out:
1120 fclose(jcr->component_fd);
1121 jcr->component_fd = NULL;
1122 unlink(jcr->component_fname);
1123 free_and_null_pool_memory(jcr->component_fname);
1124 return ok;
1125 }
1126
1127 /*
1128 * Read the attributes from the File daemon for
1129 * a Verify job and store them in the catalog.
1130 */
get_attributes_and_put_in_catalog(JCR * jcr)1131 int get_attributes_and_put_in_catalog(JCR *jcr)
1132 {
1133 BSOCK *fd;
1134 int n = 0;
1135 ATTR_DBR *ar = NULL;
1136 char digest[2*(MAXSTRING+1)+1]; /* escaped version of Digest */
1137
1138 fd = jcr->file_bsock;
1139 jcr->jr.FirstIndex = 1;
1140 jcr->FileIndex = 0;
1141 /* Start transaction allocates jcr->attr and jcr->ar if needed */
1142 db_start_transaction(jcr, jcr->db); /* start transaction if not already open */
1143 ar = jcr->ar;
1144
1145 Dmsg0(120, "bdird: waiting to receive file attributes\n");
1146 /* Pickup file attributes and digest */
1147 while (!fd->errors && (n = bget_dirmsg(fd)) > 0) {
1148 int32_t file_index;
1149 int stream, len;
1150 char *p, *fn;
1151 char Digest[MAXSTRING+1]; /* either Verify opts or MD5/SHA1 digest */
1152
1153 /* Stop here if canceled */
1154 if (jcr->is_job_canceled()) {
1155 jcr->cached_attribute = false;
1156 return 0;
1157 }
1158
1159 if ((len = sscanf(fd->msg, "%ld %d %500s", &file_index, &stream, Digest)) != 3) { /* MAXSTRING */
1160 Jmsg(jcr, M_FATAL, 0, _("<filed: bad attributes, expected 3 fields got %d\n"
1161 "msglen=%d msg=%s\n"), len, fd->msglen, fd->msg);
1162 jcr->setJobStatus(JS_ErrorTerminated);
1163 jcr->cached_attribute = false;
1164 return 0;
1165 }
1166 p = fd->msg;
1167 /* The following three fields were sscanf'ed above so skip them */
1168 skip_nonspaces(&p); /* skip FileIndex */
1169 skip_spaces(&p);
1170 skip_nonspaces(&p); /* skip Stream */
1171 skip_spaces(&p);
1172 skip_nonspaces(&p); /* skip Opts_Digest */
1173 p++; /* skip space */
1174 Dmsg1(dbglvl, "Stream=%d\n", stream);
1175 if (stream == STREAM_UNIX_ATTRIBUTES || stream == STREAM_UNIX_ATTRIBUTES_EX) {
1176 if (jcr->cached_attribute) {
1177 Dmsg3(dbglvl, "Cached attr. Stream=%d fname=%s\n", ar->Stream, ar->fname,
1178 ar->attr);
1179 if (!db_create_file_attributes_record(jcr, jcr->db, ar)) {
1180 Jmsg1(jcr, M_FATAL, 0, _("Attribute create error. %s"), db_strerror(jcr->db));
1181 }
1182 jcr->cached_attribute = false;
1183 }
1184 /* Any cached attr is flushed so we can reuse jcr->attr and jcr->ar */
1185 fn = jcr->fname = check_pool_memory_size(jcr->fname, fd->msglen);
1186 while (*p != 0) {
1187 *fn++ = *p++; /* copy filename */
1188 }
1189 *fn = *p++; /* term filename and point p to attribs */
1190 pm_strcpy(jcr->attr, p); /* save attributes */
1191 jcr->JobFiles++;
1192 jcr->FileIndex = file_index;
1193 ar->attr = jcr->attr;
1194 ar->fname = jcr->fname;
1195 ar->FileIndex = file_index;
1196 ar->Stream = stream;
1197 ar->link = NULL;
1198 ar->JobId = jcr->JobId;
1199 ar->ClientId = jcr->ClientId;
1200 ar->PathId = 0;
1201 ar->Filename = NULL;
1202 ar->Digest = NULL;
1203 ar->DigestType = CRYPTO_DIGEST_NONE;
1204 ar->DeltaSeq = 0;
1205 jcr->cached_attribute = true;
1206
1207 Dmsg2(dbglvl, "dird<filed: stream=%d %s\n", stream, jcr->fname);
1208 Dmsg1(dbglvl, "dird<filed: attr=%s\n", ar->attr);
1209 jcr->FileId = ar->FileId;
1210 /*
1211 * First, get STREAM_UNIX_ATTRIBUTES and fill ATTR_DBR structure
1212 * Next, we CAN have a CRYPTO_DIGEST, so we fill ATTR_DBR with it (or not)
1213 * When we get a new STREAM_UNIX_ATTRIBUTES, we known that we can add file to the catalog
1214 * At the end, we have to add the last file
1215 */
1216 } else if (crypto_digest_stream_type(stream) != CRYPTO_DIGEST_NONE) {
1217 if (jcr->FileIndex != file_index) {
1218 Jmsg3(jcr, M_ERROR, 0, _("%s index %d not same as attributes %d\n"),
1219 stream_to_ascii(stream), file_index, jcr->FileIndex);
1220 continue;
1221 }
1222 ar->Digest = digest;
1223 ar->DigestType = crypto_digest_stream_type(stream);
1224 db_escape_string(jcr, jcr->db, digest, Digest, strlen(Digest));
1225 Dmsg4(dbglvl, "stream=%d DigestLen=%d Digest=%s type=%d\n", stream,
1226 strlen(digest), digest, ar->DigestType);
1227 }
1228 jcr->jr.JobFiles = jcr->JobFiles = file_index;
1229 jcr->jr.LastIndex = file_index;
1230 }
1231 if (fd->is_error()) {
1232 Jmsg1(jcr, M_FATAL, 0, _("<filed: Network error getting attributes. ERR=%s\n"),
1233 fd->bstrerror());
1234 jcr->cached_attribute = false;
1235 return 0;
1236 }
1237 if (jcr->cached_attribute) {
1238 Dmsg3(dbglvl, "Cached attr with digest. Stream=%d fname=%s attr=%s\n", ar->Stream,
1239 ar->fname, ar->attr);
1240 if (!db_create_file_attributes_record(jcr, jcr->db, ar)) {
1241 Jmsg1(jcr, M_FATAL, 0, _("Attribute create error. %s"), db_strerror(jcr->db));
1242 }
1243 jcr->cached_attribute = false;
1244 }
1245 jcr->setJobStatus(JS_Terminated);
1246 return 1;
1247 }
1248