]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/dird/fd_cmds.c
kes Implement 'MaxFullInterval' and start 'MaxDiffInterval' based on
[bacula/bacula] / bacula / src / dird / fd_cmds.c
1 /*
2    Bacula® - The Network Backup Solution
3
4    Copyright (C) 2000-2008 Free Software Foundation Europe e.V.
5
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
11    in the file LICENSE.
12
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.
17
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
21    02110-1301, USA.
22
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.
27 */
28 /*
29  *
30  *   Bacula Director -- fd_cmds.c -- send commands to File daemon
31  *
32  *     Kern Sibbald, October MM
33  *
34  *    This routine is run as a separate thread.  There may be more
35  *    work to be done to make it totally reentrant!!!!
36  *
37  *  Utility functions for sending info to File Daemon.
38  *   These functions are used by both backup and verify.
39  *
40  *   Version $Id$
41  */
42
43 #include "bacula.h"
44 #include "dird.h"
45 #include "findlib/find.h"
46
47 const int dbglvl = 400;
48
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";
56
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";
63
64 /* Forward referenced functions */
65 static bool send_list_item(JCR *jcr, const char *code, char *item, BSOCK *fd);
66
67 /* External functions */
68 extern DIRRES *director;
69 extern int FDConnectTimeout;
70
71 #define INC_LIST 0
72 #define EXC_LIST 1
73
74 /*
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).
78  */
79
80 int connect_to_file_daemon(JCR *jcr, int retry_interval, int max_retry_time,
81                            int verbose)
82 {
83    BSOCK   *fd;
84    char ed1[30];
85    utime_t heart_beat;
86
87    if (jcr->client->heartbeat_interval) {
88       heart_beat = jcr->client->heartbeat_interval;
89    } else {           
90       heart_beat = director->heartbeat_interval;
91    }
92
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);
99       if (fd == NULL) {
100          set_jcr_job_status(jcr, JS_ErrorTerminated);
101          return 0;
102       }
103       Dmsg0(10, "Opened connection with File daemon\n");
104    } else {
105       fd = jcr->file_bsock;           /* use existing connection */
106    }
107    fd->res = (RES *)jcr->client;      /* save resource in BSOCK */
108    jcr->file_bsock = fd;
109    set_jcr_job_status(jcr, JS_Running);
110
111    if (!authenticate_file_daemon(jcr)) {
112       set_jcr_job_status(jcr, JS_ErrorTerminated);
113       return 0;
114    }
115
116    /*
117     * Now send JobId and authorization key
118     */
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));
123    }
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);
131           return 0;
132        } else if (jcr->db) {
133           CLIENT_DBR cr;
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));
143           }
144        }
145    } else {
146       Jmsg(jcr, M_FATAL, 0, _("FD gave bad response to JobId command: %s\n"),
147          bnet_strerror(fd));
148       set_jcr_job_status(jcr, JS_ErrorTerminated);
149       return 0;
150    }
151    return 1;
152 }
153
154 /*
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.
160  */
161 void get_level_since_time(JCR *jcr, char *since, int since_len)
162 {
163    int JobLevel;
164    bool have_full;
165    bool do_full = false;
166    bool do_diff = false;
167    time_t now;
168    utime_t full_time, diff_time;
169
170    since[0] = 0;
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);
175       return;
176    }
177    /* Make sure stime buffer is allocated */
178    if (!jcr->stime) {
179       jcr->stime = get_pool_memory(PM_MESSAGE);
180    } 
181    jcr->stime[0] = 0;
182    /*
183     * Lookup the last FULL backup job to get the time/date for a
184     * differential or incremental save.
185     */
186    switch (jcr->JobLevel) {
187    case L_DIFFERENTIAL:
188    case L_INCREMENTAL:
189       /* Look up start time of last Full job */
190       now = time(NULL);
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);
193 #ifdef xxx
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 */
197          jcr->jr.JobId = 0;
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);
202          }
203       }
204 #endif
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);
208       }
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;
222       } else {
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;
231                break;
232             }
233          }
234          bstrncpy(since, _(", since="), since_len);
235          bstrncat(since, jcr->stime, since_len);
236       }
237       jcr->jr.JobId = jcr->JobId;
238       break;
239    }
240    Dmsg2(100, "Level=%c last start time=%s\n", jcr->JobLevel, jcr->stime);
241 }
242
243 static void send_since_time(JCR *jcr)
244 {
245    BSOCK   *fd = jcr->file_bsock;
246    utime_t stime;
247    char ed1[50];
248
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);
253    }
254 }
255
256 /*
257  * Send level command to FD.
258  * Used for backup jobs and estimate command.
259  */
260 bool send_level_command(JCR *jcr)
261 {
262    BSOCK   *fd = jcr->file_bsock;
263    const char *accurate=jcr->job->accurate?"accurate_":"";
264    const char *not_accurate="";
265    /*
266     * Send Level command to File daemon
267     */
268    switch (jcr->JobLevel) {
269    case L_BASE:
270       fd->fsend(levelcmd, not_accurate, "base", " ", 0);
271       break;
272    /* L_NONE is the console, sending something off to the FD */
273    case L_NONE:
274    case L_FULL:
275       fd->fsend(levelcmd, not_accurate, "full", " ", 0);
276       break;
277    case L_DIFFERENTIAL:
278       fd->fsend(levelcmd, accurate, "differential", " ", 0);
279       send_since_time(jcr);
280       break;
281    case L_INCREMENTAL:
282       fd->fsend(levelcmd, accurate, "incremental", " ", 0);
283       send_since_time(jcr);
284       break;
285    case L_SINCE:
286    default:
287       Jmsg2(jcr, M_FATAL, 0, _("Unimplemented backup level %d %c\n"),
288          jcr->JobLevel, jcr->JobLevel);
289       return 0;
290    }
291    Dmsg1(120, ">filed: %s", fd->msg);
292    if (!response(jcr, fd, OKlevel, "Level", DISPLAY_ERROR)) {
293       return 0;
294    }
295    return 1;
296 }
297
298 /*
299  * Send either an Included or an Excluded list to FD
300  */
301 static bool send_fileset(JCR *jcr)
302 {
303    FILESET *fileset = jcr->fileset;
304    BSOCK   *fd = jcr->file_bsock;
305    int num;
306    bool include = true;
307
308    for ( ;; ) {
309       if (include) {
310          num = fileset->num_includes;
311       } else {
312          num = fileset->num_excludes;
313       }
314       for (int i=0; i<num; i++) {
315          char *item;
316          INCEXE *ie;
317          int j, k;
318
319          if (include) {
320             ie = fileset->include_items[i];
321             fd->fsend("I\n");
322          } else {
323             ie = fileset->exclude_items[i];
324             fd->fsend("E\n");
325          }
326          for (j=0; j<ie->num_opts; j++) {
327             FOPTS *fo = ie->opts_list[j];
328             fd->fsend("O %s\n", fo->opts);
329
330             bool enhanced_wild = false;
331             for (k=0; fo->opts[k]!='\0'; k++) {
332                if (fo->opts[k]=='W') {
333                   enhanced_wild = true;
334                   break;
335                }
336             }
337
338             for (k=0; k<fo->regex.size(); k++) {
339                fd->fsend("R %s\n", fo->regex.get(k));
340             }
341             for (k=0; k<fo->regexdir.size(); k++) {
342                fd->fsend("RD %s\n", fo->regexdir.get(k));
343             }
344             for (k=0; k<fo->regexfile.size(); k++) {
345                fd->fsend("RF %s\n", fo->regexfile.get(k));
346             }
347             for (k=0; k<fo->wild.size(); k++) {
348                fd->fsend("W %s\n", fo->wild.get(k));
349             }
350             for (k=0; k<fo->wilddir.size(); k++) {
351                fd->fsend("WD %s\n", fo->wilddir.get(k));
352             }
353             for (k=0; k<fo->wildfile.size(); k++) {
354                fd->fsend("WF %s\n", fo->wildfile.get(k));
355             }
356             for (k=0; k<fo->wildbase.size(); k++) {
357                fd->fsend("W%c %s\n", enhanced_wild ? 'B' : 'F', fo->wildbase.get(k));
358             }
359             for (k=0; k<fo->base.size(); k++) {
360                fd->fsend("B %s\n", fo->base.get(k));
361             }
362             for (k=0; k<fo->fstype.size(); k++) {
363                fd->fsend("X %s\n", fo->fstype.get(k));
364             }
365             for (k=0; k<fo->drivetype.size(); k++) {
366                fd->fsend("XD %s\n", fo->drivetype.get(k));
367             }
368             if (fo->plugin) {
369                fd->fsend("G %s\n", fo->plugin);
370             }
371             if (fo->reader) {
372                fd->fsend("D %s\n", fo->reader);
373             }
374             if (fo->writer) {
375                fd->fsend("T %s\n", fo->writer);
376             }
377             fd->fsend("N\n");
378          }
379
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)) {
383                goto bail_out;
384             }
385          }
386          fd->fsend("N\n");
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)) {
390                goto bail_out;
391             }
392          }
393          fd->fsend("N\n");
394       }
395       if (!include) {                 /* If we just did excludes */
396          break;                       /*   all done */
397       }
398       include = false;                /* Now do excludes */
399    }
400
401    fd->signal(BNET_EOD);              /* end of data */
402    if (!response(jcr, fd, OKinc, "Include", DISPLAY_ERROR)) {
403       goto bail_out;
404    }
405    return true;
406
407 bail_out:
408    set_jcr_job_status(jcr, JS_ErrorTerminated);
409    return false;
410
411 }
412
413 static bool send_list_item(JCR *jcr, const char *code, char *item, BSOCK *fd)
414 {
415    BPIPE *bpipe;
416    FILE *ffd;
417    char buf[2000];
418    int optlen, stat;
419    char *p = item;
420
421    switch (*p) {
422    case '|':
423       p++;                      /* skip over the | */
424       fd->msg = edit_job_codes(jcr, fd->msg, p, "");
425       bpipe = open_bpipe(fd->msg, 0, "r");
426       if (!bpipe) {
427          berrno be;
428          Jmsg(jcr, M_FATAL, 0, _("Cannot run program: %s. ERR=%s\n"),
429             p, be.bstrerror());
430          return false;
431       }
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"));
440             return false;
441          }
442       }
443       if ((stat=close_bpipe(bpipe)) != 0) {
444          berrno be;
445          Jmsg(jcr, M_FATAL, 0, _("Error running program: %s. ERR=%s\n"),
446             p, be.bstrerror(stat));
447          return false;
448       }
449       break;
450    case '<':
451       p++;                      /* skip over < */
452       if ((ffd = fopen(p, "rb")) == NULL) {
453          berrno be;
454          Jmsg(jcr, M_FATAL, 0, _("Cannot open included file: %s. ERR=%s\n"),
455             p, be.bstrerror());
456          return false;
457       }
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"));
465             return false;
466          }
467       }
468       fclose(ffd);
469       break;
470    case '\\':
471       p++;                      /* skip over \ */
472       /* Note, fall through wanted */
473    default:
474       pm_strcpy(fd->msg, code);
475       fd->msglen = pm_strcat(fd->msg, p);
476       Dmsg1(500, "Inc/Exc name=%s\n", fd->msg);
477       if (!fd->send()) {
478          Jmsg(jcr, M_FATAL, 0, _(">filed: write error on socket\n"));
479          return false;
480       }
481       break;
482    }
483    return true;
484 }            
485
486
487 /*
488  * Send include list to File daemon
489  */
490 bool send_include_list(JCR *jcr)
491 {
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);
496    }
497    return true;
498 }
499
500
501 /*
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.
506  */
507 bool send_exclude_list(JCR *jcr)
508 {
509    return true;
510 }
511
512
513 /*
514  * Send bootstrap file if any to the socket given (FD or SD).
515  *  This is used for restore, verify VolumeToCatalog, and
516  *  for migration.
517  */
518 bool send_bootstrap_file(JCR *jcr, BSOCK *sock)
519 {
520    FILE *bs;
521    char buf[1000];
522    const char *bootstrap = "bootstrap\n";
523
524    Dmsg1(400, "send_bootstrap_file: %s\n", jcr->RestoreBootstrap);
525    if (!jcr->RestoreBootstrap) {
526       return true;
527    }
528    bs = fopen(jcr->RestoreBootstrap, "rb");
529    if (!bs) {
530       berrno be;
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);
534       return false;
535    }
536    sock->fsend(bootstrap);
537    while (fgets(buf, sizeof(buf), bs)) {
538       sock->fsend("%s", buf);
539    }
540    sock->signal(BNET_EOD);
541    fclose(bs);
542    if (jcr->unlink_bsr) {
543       unlink(jcr->RestoreBootstrap);
544       jcr->unlink_bsr = false;
545    }                         
546    return true;
547 }
548
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";
554
555 int send_runscript_with_old_proto(JCR *jcr, int when, POOLMEM *msg)
556 {
557    int ret;
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);
562    } else {
563       bnet_fsend(jcr->file_bsock, runafter, msg);
564       ret = response(jcr, jcr->file_bsock, OKRunAfter, "ClientRunAfterJob", DISPLAY_ERROR);
565    }
566    return ret;
567 } /* END OF TODO */
568
569 /*
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)
574  */
575 int send_runscripts_commands(JCR *jcr)
576 {
577    POOLMEM *msg = get_pool_memory(PM_FNAME);
578    BSOCK *fd = jcr->file_bsock;
579    RUNSCRIPT *cmd;
580    bool launch_before_cmd = false;
581    POOLMEM *ehost = get_pool_memory(PM_FNAME);
582    int result;
583
584    Dmsg0(120, "bdird: sending runscripts to fd\n");
585    
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);
590
591          if (strcmp(ehost, jcr->client->name()) == 0) {
592             pm_strcpy(msg, cmd->command);
593             bash_spaces(msg);
594
595             Dmsg1(120, "bdird: sending runscripts to fd '%s'\n", cmd->command);
596             
597             /* TODO: remove this with bacula 1.42 */
598             if (cmd->old_proto) {
599                result = send_runscript_with_old_proto(jcr, cmd->when, msg);
600
601             } else {
602                fd->fsend(runscript, cmd->on_success, 
603                                     cmd->on_failure,
604                                     cmd->fail_on_error,
605                                     cmd->when,
606                                     msg);
607
608                result = response(jcr, fd, OKRunScript, "RunScript", DISPLAY_ERROR);
609                launch_before_cmd = true;
610             }
611             
612             if (!result) {
613                goto bail_out;
614             }
615          }
616          /* TODO : we have to play with other client */
617          /*
618            else {
619            send command to an other client
620            }
621          */
622       }        
623    } 
624
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)) {
629         goto bail_out;
630       }
631    }
632    free_pool_memory(msg);
633    free_pool_memory(ehost);
634    return 1;
635
636 bail_out:
637    Jmsg(jcr, M_FATAL, 0, _("Client \"%s\" RunScript failed.\n"), ehost);
638    free_pool_memory(msg);
639    free_pool_memory(ehost);
640    return 0;
641 }
642
643
644
645 /*
646  * Read the attributes from the File daemon for
647  * a Verify job and store them in the catalog.
648  */
649 int get_attributes_and_put_in_catalog(JCR *jcr)
650 {
651    BSOCK   *fd;
652    int n = 0;
653    ATTR_DBR *ar = NULL;
654    char digest[MAXSTRING];
655
656    fd = jcr->file_bsock;
657    jcr->jr.FirstIndex = 1;
658    jcr->FileIndex = 0;
659    /* Start transaction allocates jcr->attr and jcr->ar if needed */
660    db_start_transaction(jcr, jcr->db);     /* start transaction if not already open */
661    ar = jcr->ar;
662
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) {
666       uint32_t file_index;
667       int stream, len;
668       char *p, *fn;
669       char Digest[MAXSTRING];      /* either Verify opts or MD5/SHA1 digest */
670
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);
676          return 0;
677       }
678       p = fd->msg;
679       /* The following three fields were sscanf'ed above so skip them */
680       skip_nonspaces(&p);             /* skip FileIndex */
681       skip_spaces(&p);
682       skip_nonspaces(&p);             /* skip Stream */
683       skip_spaces(&p);
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,
690                ar->attr);
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));
693             }
694          }
695          /* Any cached attr is flushed so we can reuse jcr->attr and jcr->ar */
696          fn = jcr->fname;
697          while (*p != 0) {
698             *fn++ = *p++;                /* copy filename */
699          }
700          *fn = *p++;                     /* term filename and point p to attribs */
701          pm_strcpy(jcr->attr, p);        /* save attributes */
702          jcr->JobFiles++;
703          jcr->FileIndex = file_index;
704          ar->attr = jcr->attr;
705          ar->fname = jcr->fname;
706          ar->FileIndex = file_index;
707          ar->Stream = stream;
708          ar->link = NULL;
709          ar->JobId = jcr->JobId;
710          ar->ClientId = jcr->ClientId;
711          ar->PathId = 0;
712          ar->FilenameId = 0;
713          ar->Digest = NULL;
714          ar->DigestType = CRYPTO_DIGEST_NONE;
715          jcr->cached_attribute = true;
716
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;
720       /*
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
725        */
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);
730             continue;
731          }
732          ar->Digest = digest;
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);
737       }
738       jcr->jr.JobFiles = jcr->JobFiles = file_index;
739       jcr->jr.LastIndex = file_index;
740    }
741    if (is_bnet_error(fd)) {
742       Jmsg1(jcr, M_FATAL, 0, _("<filed: Network error getting attributes. ERR=%s\n"),
743             fd->bstrerror());
744       return 0;
745    }
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));
751       }
752       jcr->cached_attribute = false; 
753    }
754    set_jcr_job_status(jcr, JS_Terminated);
755    return 1;
756 }