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