]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/dird/verify.c
Mark translatable strings in all source files.
[bacula/bacula] / bacula / src / dird / verify.c
1 /*
2  *
3  *   Bacula Director -- verify.c -- responsible for running file verification
4  *
5  *     Kern Sibbald, October MM
6  *
7  *  Basic tasks done here:
8  *     Open DB
9  *     Open connection with File daemon and pass him commands
10  *       to do the verify.
11  *     When the File daemon sends the attributes, compare them to
12  *       what is in the DB.
13  *
14  *   Version $Id$
15  */
16 /*
17    Copyright (C) 2000-2005 Kern Sibbald
18
19    This program is free software; you can redistribute it and/or
20    modify it under the terms of the GNU General Public License
21    version 2 as amended with additional clauses defined in the
22    file LICENSE in the main source directory.
23
24    This program is distributed in the hope that it will be useful,
25    but WITHOUT ANY WARRANTY; without even the implied warranty of
26    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 
27    the file LICENSE for additional details.
28
29  */
30
31
32 #include "bacula.h"
33 #include "dird.h"
34 #include "findlib/find.h"
35
36 /* Imported Global Variables */
37 extern int debug_level;
38
39 /* Commands sent to File daemon */
40 static char verifycmd[]    = "verify level=%s\n";
41 static char storaddr[]     = "storage address=%s port=%d ssl=0\n";
42
43 /* Responses received from File daemon */
44 static char OKverify[]    = "2000 OK verify\n";
45 static char OKstore[]     = "2000 OK storage\n";
46
47 /* Forward referenced functions */
48 static void prt_fname(JCR *jcr);
49 static int missing_handler(void *ctx, int num_fields, char **row);
50
51
52 /* 
53  * Called here before the job is run to do the job
54  *   specific setup.
55  */
56 bool do_verify_init(JCR *jcr) 
57 {
58    JOB_DBR jr;
59    JobId_t verify_jobid = 0;
60    const char *Name;
61
62    memset(&jcr->target_jr, 0, sizeof(jcr->target_jr));
63
64    Dmsg1(9, "bdird: created client %s record\n", jcr->client->hdr.name);
65
66    /*
67     * Find JobId of last job that ran.  E.g.
68     *   for VERIFY_CATALOG we want the JobId of the last INIT.
69     *   for VERIFY_VOLUME_TO_CATALOG, we want the JobId of the
70     *       last backup Job.
71     */
72    if (jcr->JobLevel == L_VERIFY_CATALOG ||
73        jcr->JobLevel == L_VERIFY_VOLUME_TO_CATALOG ||
74        jcr->JobLevel == L_VERIFY_DISK_TO_CATALOG) {
75       memcpy(&jr, &jcr->jr, sizeof(jr));
76       if (jcr->verify_job &&
77           (jcr->JobLevel == L_VERIFY_VOLUME_TO_CATALOG ||
78            jcr->JobLevel == L_VERIFY_DISK_TO_CATALOG)) {
79          Name = jcr->verify_job->hdr.name;
80       } else {
81          Name = NULL;
82       }
83       Dmsg1(100, "find last jobid for: %s\n", NPRT(Name));
84       if (!db_find_last_jobid(jcr, jcr->db, Name, &jr)) {
85          if (jcr->JobLevel == L_VERIFY_CATALOG) {
86             Jmsg(jcr, M_FATAL, 0, _(
87                  "Unable to find JobId of previous InitCatalog Job.\n"
88                  "Please run a Verify with Level=InitCatalog before\n"
89                  "running the current Job.\n"));
90           } else {
91             Jmsg(jcr, M_FATAL, 0, _(
92                  "Unable to find JobId of previous Job for this client.\n"));
93          }
94          return false;
95       }
96       verify_jobid = jr.JobId;
97       Dmsg1(100, "Last full jobid=%d\n", verify_jobid);
98    }
99    /*
100     * Now get the job record for the previous backup that interests
101     *   us. We use the verify_jobid that we found above.
102     */
103    if (jcr->JobLevel == L_VERIFY_CATALOG ||
104        jcr->JobLevel == L_VERIFY_VOLUME_TO_CATALOG ||
105        jcr->JobLevel == L_VERIFY_DISK_TO_CATALOG) {
106       jcr->target_jr.JobId = verify_jobid;
107       if (!db_get_job_record(jcr, jcr->db, &jcr->target_jr)) {
108          Jmsg(jcr, M_FATAL, 0, _("Could not get job record for previous Job. ERR=%s"),
109               db_strerror(jcr->db));
110          return false;
111       }
112       if (jcr->target_jr.JobStatus != 'T') {
113          Jmsg(jcr, M_FATAL, 0, _("Last Job %d did not terminate normally. JobStatus=%c\n"),
114             verify_jobid, jcr->target_jr.JobStatus);
115          return false;
116       }
117       Jmsg(jcr, M_INFO, 0, _("Verifying against JobId=%d Job=%s\n"),
118          jcr->target_jr.JobId, jcr->target_jr.Job);
119    }
120
121    /*
122     * If we are verifying a Volume, we need the Storage
123     *   daemon, so open a connection, otherwise, just
124     *   create a dummy authorization key (passed to
125     *   File daemon but not used).
126     */
127    if (jcr->JobLevel == L_VERIFY_VOLUME_TO_CATALOG) {
128       RBSR *bsr = new_bsr();
129       UAContext *ua;
130       bsr->JobId = jcr->target_jr.JobId;
131       ua = new_ua_context(jcr);
132       complete_bsr(ua, bsr);
133       bsr->fi = new_findex();
134       bsr->fi->findex = 1;
135       bsr->fi->findex2 = jcr->target_jr.JobFiles;
136       jcr->ExpectedFiles = write_bsr_file(ua, bsr);
137       if (jcr->ExpectedFiles == 0) {
138          free_ua_context(ua);
139          free_bsr(bsr);
140          return false;
141       }
142       if (jcr->RestoreBootstrap) {
143          free(jcr->RestoreBootstrap);
144       }
145       POOLMEM *fname = get_pool_memory(PM_MESSAGE);
146       make_unique_restore_filename(ua, &fname);
147       jcr->RestoreBootstrap = bstrdup(fname);
148       free_ua_context(ua);
149       free_bsr(bsr);
150       free_pool_memory(fname);
151       jcr->needs_sd = true;
152
153    } else {
154       jcr->sd_auth_key = bstrdup("dummy");    /* dummy Storage daemon key */
155    }
156
157    if (jcr->JobLevel == L_VERIFY_DISK_TO_CATALOG && jcr->verify_job) {
158       jcr->fileset = jcr->verify_job->fileset;
159    }
160    Dmsg2(100, "ClientId=%u JobLevel=%c\n", jcr->target_jr.ClientId, jcr->JobLevel);
161    return true;
162 }
163
164
165 /*
166  * Do a verification of the specified files against the Catlaog
167  *
168  *  Returns:  false on failure
169  *            true  on success
170  */
171 bool do_verify(JCR *jcr)
172 {
173    const char *level;
174    BSOCK   *fd;
175    int stat;
176
177    if (!db_update_job_start_record(jcr, jcr->db, &jcr->jr)) {
178       Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
179       return false;
180    }
181
182    /* Print Job Start message */
183    Jmsg(jcr, M_INFO, 0, _("Start Verify JobId=%d Level=%s Job=%s\n"),
184       jcr->JobId, level_to_str(jcr->JobLevel), jcr->Job);
185
186    if (jcr->JobLevel == L_VERIFY_VOLUME_TO_CATALOG) {
187       /*
188        * Start conversation with Storage daemon
189        */
190       set_jcr_job_status(jcr, JS_Blocked);
191       if (!connect_to_storage_daemon(jcr, 10, SDConnectTimeout, 1)) {
192          return false;
193       }
194       /*
195        * Now start a job with the Storage daemon
196        */
197       if (!start_storage_daemon_job(jcr, jcr->storage, SD_READ)) {
198          return false;
199       }
200       /*
201        * Now start a Storage daemon message thread
202        */
203       if (!start_storage_daemon_message_thread(jcr)) {
204          return false;
205       }
206       Dmsg0(50, "Storage daemon connection OK\n");
207    }
208    /*
209     * OK, now connect to the File daemon
210     *  and ask him for the files.
211     */
212    set_jcr_job_status(jcr, JS_Blocked);
213    if (!connect_to_file_daemon(jcr, 10, FDConnectTimeout, 1)) {
214       return false;
215    }
216
217    set_jcr_job_status(jcr, JS_Running);
218    fd = jcr->file_bsock;
219
220
221    Dmsg0(30, ">filed: Send include list\n");
222    if (!send_include_list(jcr)) {
223       return false;
224    }
225
226    Dmsg0(30, ">filed: Send exclude list\n");
227    if (!send_exclude_list(jcr)) {
228       return false;
229    }
230
231    /*
232     * Send Level command to File daemon, as well
233     *   as the Storage address if appropriate.
234     */
235    switch (jcr->JobLevel) {
236    case L_VERIFY_INIT:
237       level = "init";
238       break;
239    case L_VERIFY_CATALOG:
240       level = "catalog";
241       break;
242    case L_VERIFY_VOLUME_TO_CATALOG:
243       /*
244        * send Storage daemon address to the File daemon
245        */
246       if (jcr->store->SDDport == 0) {
247          jcr->store->SDDport = jcr->store->SDport;
248       }
249       bnet_fsend(fd, storaddr, jcr->store->address, jcr->store->SDDport);
250       if (!response(jcr, fd, OKstore, "Storage", DISPLAY_ERROR)) {
251          return false;
252       }
253
254       /*
255        * Send the bootstrap file -- what Volumes/files to restore
256        */
257       if (!send_bootstrap_file(jcr)) {
258          return false;
259       }
260
261       if (!jcr->RestoreBootstrap) {
262          Jmsg0(jcr, M_FATAL, 0, _("Deprecated feature ... use bootstrap.\n"));
263          return false;
264       }
265
266       level = "volume";
267       break;
268    case L_VERIFY_DATA:
269       level = "data";
270       break;
271    case L_VERIFY_DISK_TO_CATALOG:
272       level="disk_to_catalog";
273       break;
274    default:
275       Jmsg2(jcr, M_FATAL, 0, _("Unimplemented Verify level %d(%c)\n"), jcr->JobLevel,
276          jcr->JobLevel);
277       return false;
278    }
279
280    if (!send_run_before_and_after_commands(jcr)) {
281       return false;
282    }
283
284    /*
285     * Send verify command/level to File daemon
286     */
287    bnet_fsend(fd, verifycmd, level);
288    if (!response(jcr, fd, OKverify, "Verify", DISPLAY_ERROR)) {
289       return false;
290    }
291
292    /*
293     * Now get data back from File daemon and
294     *  compare it to the catalog or store it in the
295     *  catalog depending on the run type.
296     */
297    /* Compare to catalog */
298    switch (jcr->JobLevel) {
299    case L_VERIFY_CATALOG:
300       Dmsg0(10, "Verify level=catalog\n");
301       jcr->sd_msg_thread_done = true;   /* no SD msg thread, so it is done */
302       jcr->SDJobStatus = JS_Terminated;
303       get_attributes_and_compare_to_catalog(jcr, jcr->target_jr.JobId);
304       break;
305
306    case L_VERIFY_VOLUME_TO_CATALOG:
307       Dmsg0(10, "Verify level=volume\n");
308       get_attributes_and_compare_to_catalog(jcr, jcr->target_jr.JobId);
309       break;
310
311    case L_VERIFY_DISK_TO_CATALOG:
312       Dmsg0(10, "Verify level=disk_to_catalog\n");
313       jcr->sd_msg_thread_done = true;   /* no SD msg thread, so it is done */
314       jcr->SDJobStatus = JS_Terminated;
315       get_attributes_and_compare_to_catalog(jcr, jcr->target_jr.JobId);
316       break;
317
318    case L_VERIFY_INIT:
319       /* Build catalog */
320       Dmsg0(10, "Verify level=init\n");
321       jcr->sd_msg_thread_done = true;   /* no SD msg thread, so it is done */
322       jcr->SDJobStatus = JS_Terminated;
323       get_attributes_and_put_in_catalog(jcr);
324       break;
325
326    default:
327       Jmsg1(jcr, M_FATAL, 0, _("Unimplemented verify level %d\n"), jcr->JobLevel);
328       return false;
329    }
330
331    stat = wait_for_job_termination(jcr);
332    if (stat == JS_Terminated) {
333       verify_cleanup(jcr, stat);
334       return true;
335    }
336    return false;
337 }
338
339
340 /*
341  * Release resources allocated during backup.
342  *
343  */
344 void verify_cleanup(JCR *jcr, int TermCode)
345 {
346    char sdt[50], edt[50];
347    char ec1[30], ec2[30];
348    char term_code[100], fd_term_msg[100], sd_term_msg[100];
349    const char *term_msg;
350    int msg_type;
351    JobId_t JobId;
352    const char *Name;
353
354 // Dmsg1(100, "Enter verify_cleanup() TermCod=%d\n", TermCode);
355    dequeue_messages(jcr);             /* display any queued messages */
356
357    Dmsg3(900, "JobLevel=%c Expected=%u JobFiles=%u\n", jcr->JobLevel,
358       jcr->ExpectedFiles, jcr->JobFiles);
359    if (jcr->JobLevel == L_VERIFY_VOLUME_TO_CATALOG &&
360        jcr->ExpectedFiles != jcr->JobFiles) {
361       TermCode = JS_ErrorTerminated;
362    }
363
364    /* If no files were expected, there can be no error */
365    if (jcr->JobLevel == L_VERIFY_VOLUME_TO_CATALOG &&
366        jcr->ExpectedFiles == 0) {
367       TermCode = JS_Terminated;
368    }
369
370    JobId = jcr->jr.JobId;
371    set_jcr_job_status(jcr, TermCode);
372
373    update_job_end_record(jcr);
374    if (jcr->unlink_bsr && jcr->RestoreBootstrap) {
375       unlink(jcr->RestoreBootstrap);
376       jcr->unlink_bsr = false;
377    }
378
379    msg_type = M_INFO;                 /* by default INFO message */
380    switch (TermCode) {
381    case JS_Terminated:
382       term_msg = _("Verify OK");
383       break;
384    case JS_FatalError:
385    case JS_ErrorTerminated:
386       term_msg = _("*** Verify Error ***");
387       msg_type = M_ERROR;          /* Generate error message */
388       break;
389    case JS_Error:
390       term_msg = _("Verify warnings");
391       break;
392    case JS_Canceled:
393       term_msg = _("Verify Canceled");
394       break;
395    case JS_Differences:
396       term_msg = _("Verify Differences");
397       break;
398    default:
399       term_msg = term_code;
400       bsnprintf(term_code, sizeof(term_code),
401                 _("Inappropriate term code: %d %c\n"), TermCode, TermCode);
402       break;
403    }
404    bstrftimes(sdt, sizeof(sdt), jcr->jr.StartTime);
405    bstrftimes(edt, sizeof(edt), jcr->jr.EndTime);
406    if (jcr->verify_job) {
407       Name = jcr->verify_job->hdr.name;
408    } else {
409       Name = "";
410    }
411
412    jobstatus_to_ascii(jcr->FDJobStatus, fd_term_msg, sizeof(fd_term_msg));
413    if (jcr->JobLevel == L_VERIFY_VOLUME_TO_CATALOG) {
414       jobstatus_to_ascii(jcr->SDJobStatus, sd_term_msg, sizeof(sd_term_msg));
415       Jmsg(jcr, msg_type, 0, _("Bacula %s (%s): %s\n"
416 "  JobId:                  %d\n"
417 "  Job:                    %s\n"
418 "  FileSet:                %s\n"
419 "  Verify Level:           %s\n"
420 "  Client:                 %s\n"
421 "  Verify JobId:           %d\n"
422 "  Verify Job:             %s\n"
423 "  Start time:             %s\n"
424 "  End time:               %s\n"
425 "  Files Expected:         %s\n"
426 "  Files Examined:         %s\n"
427 "  Non-fatal FD errors:    %d\n"
428 "  FD termination status:  %s\n"
429 "  SD termination status:  %s\n"
430 "  Termination:            %s\n\n"),
431          VERSION,
432          LSMDATE,
433          edt,
434          jcr->jr.JobId,
435          jcr->jr.Job,
436          jcr->fileset->hdr.name,
437          level_to_str(jcr->JobLevel),
438          jcr->client->hdr.name,
439          jcr->target_jr.JobId,
440          Name,
441          sdt,
442          edt,
443          edit_uint64_with_commas(jcr->ExpectedFiles, ec1),
444          edit_uint64_with_commas(jcr->JobFiles, ec2),
445          jcr->Errors,
446          fd_term_msg,
447          sd_term_msg,
448          term_msg);
449    } else {
450       Jmsg(jcr, msg_type, 0, _("Bacula %s (%s): %s\n"
451 "  JobId:                  %d\n"
452 "  Job:                    %s\n"
453 "  FileSet:                %s\n"
454 "  Verify Level:           %s\n"
455 "  Client:                 %s\n"
456 "  Verify JobId:           %d\n"
457 "  Verify Job:             %s\n"
458 "  Start time:             %s\n"
459 "  End time:               %s\n"
460 "  Files Examined:         %s\n"
461 "  Non-fatal FD errors:    %d\n"
462 "  FD termination status:  %s\n"
463 "  Termination:            %s\n\n"),
464          VERSION,
465          LSMDATE,
466          edt,
467          jcr->jr.JobId,
468          jcr->jr.Job,
469          jcr->fileset->hdr.name,
470          level_to_str(jcr->JobLevel),
471          jcr->client->hdr.name,
472          jcr->target_jr.JobId,
473          Name,
474          sdt,
475          edt,
476          edit_uint64_with_commas(jcr->JobFiles, ec1),
477          jcr->Errors,
478          fd_term_msg,
479          term_msg);
480    }
481    Dmsg0(100, "Leave verify_cleanup()\n");
482 }
483
484 /*
485  * This routine is called only during a Verify
486  */
487 int get_attributes_and_compare_to_catalog(JCR *jcr, JobId_t JobId)
488 {
489    BSOCK   *fd;
490    int n, len;
491    FILE_DBR fdbr;
492    struct stat statf;                 /* file stat */
493    struct stat statc;                 /* catalog stat */
494    int stat = JS_Terminated;
495    char buf[MAXSTRING];
496    POOLMEM *fname = get_pool_memory(PM_MESSAGE);
497    int do_SIG = NO_SIG;
498    int32_t file_index = 0;
499
500    memset(&fdbr, 0, sizeof(FILE_DBR));
501    fd = jcr->file_bsock;
502    fdbr.JobId = JobId;
503    jcr->FileIndex = 0;
504
505    Dmsg0(20, "bdird: waiting to receive file attributes\n");
506    /*
507     * Get Attributes and Signature from File daemon
508     * We expect:
509     *   FileIndex
510     *   Stream
511     *   Options or SIG (MD5/SHA1)
512     *   Filename
513     *   Attributes
514     *   Link name  ???
515     */
516    while ((n=bget_dirmsg(fd)) >= 0 && !job_canceled(jcr)) {
517       int stream;
518       char *attr, *p, *fn;
519       char Opts_SIG[MAXSTRING];        /* Verify Opts or MD5/SHA1 signature */
520
521       fname = check_pool_memory_size(fname, fd->msglen);
522       jcr->fname = check_pool_memory_size(jcr->fname, fd->msglen);
523       Dmsg1(200, "Atts+SIG=%s\n", fd->msg);
524       if ((len = sscanf(fd->msg, "%ld %d %100s", &file_index, &stream,
525             fname)) != 3) {
526          Jmsg3(jcr, M_FATAL, 0, _("bird<filed: bad attributes, expected 3 fields got %d\n"
527 " mslen=%d msg=%s\n"), len, fd->msglen, fd->msg);
528          return false;
529       }
530       /*
531        * We read the Options or Signature into fname
532        *  to prevent overrun, now copy it to proper location.
533        */
534       bstrncpy(Opts_SIG, fname, sizeof(Opts_SIG));
535       p = fd->msg;
536       skip_nonspaces(&p);             /* skip FileIndex */
537       skip_spaces(&p);
538       skip_nonspaces(&p);             /* skip Stream */
539       skip_spaces(&p);
540       skip_nonspaces(&p);             /* skip Opts_SIG */
541       p++;                            /* skip space */
542       fn = fname;
543       while (*p != 0) {
544          *fn++ = *p++;                /* copy filename */
545       }
546       *fn = *p++;                     /* term filename and point to attribs */
547       attr = p;
548       /*
549        * Got attributes stream, decode it
550        */
551       if (stream == STREAM_UNIX_ATTRIBUTES || stream == STREAM_UNIX_ATTRIBUTES_EX) {
552          int32_t LinkFIf, LinkFIc;
553          Dmsg2(400, "file_index=%d attr=%s\n", file_index, attr);
554          jcr->JobFiles++;
555          jcr->FileIndex = file_index;    /* remember attribute file_index */
556          decode_stat(attr, &statf, &LinkFIf);  /* decode file stat packet */
557          do_SIG = NO_SIG;
558          jcr->fn_printed = false;
559          pm_strcpy(jcr->fname, fname);  /* move filename into JCR */
560
561          Dmsg2(040, "dird<filed: stream=%d %s\n", stream, jcr->fname);
562          Dmsg1(020, "dird<filed: attr=%s\n", attr);
563
564          /*
565           * Find equivalent record in the database
566           */
567          fdbr.FileId = 0;
568          if (!db_get_file_attributes_record(jcr, jcr->db, jcr->fname,
569               &jcr->target_jr, &fdbr)) {
570             Jmsg(jcr, M_INFO, 0, _("New file: %s\n"), jcr->fname);
571             Dmsg1(020, _("File not in catalog: %s\n"), jcr->fname);
572             stat = JS_Differences;
573             continue;
574          } else {
575             /*
576              * mark file record as visited by stuffing the
577              * current JobId, which is unique, into the MarkId field.
578              */
579             db_mark_file_record(jcr, jcr->db, fdbr.FileId, jcr->JobId);
580          }
581
582          Dmsg3(400, "Found %s in catalog. inx=%d Opts=%s\n", jcr->fname,
583             file_index, Opts_SIG);
584          decode_stat(fdbr.LStat, &statc, &LinkFIc); /* decode catalog stat */
585          /*
586           * Loop over options supplied by user and verify the
587           * fields he requests.
588           */
589          for (p=Opts_SIG; *p; p++) {
590             char ed1[30], ed2[30];
591             switch (*p) {
592             case 'i':                /* compare INODEs */
593                if (statc.st_ino != statf.st_ino) {
594                   prt_fname(jcr);
595                   Jmsg(jcr, M_INFO, 0, _("      st_ino   differ. Cat: %s File: %s\n"),
596                      edit_uint64((uint64_t)statc.st_ino, ed1),
597                      edit_uint64((uint64_t)statf.st_ino, ed2));
598                   stat = JS_Differences;
599                }
600                break;
601             case 'p':                /* permissions bits */
602                if (statc.st_mode != statf.st_mode) {
603                   prt_fname(jcr);
604                   Jmsg(jcr, M_INFO, 0, _("      st_mode  differ. Cat: %x File: %x\n"),
605                      (uint32_t)statc.st_mode, (uint32_t)statf.st_mode);
606                   stat = JS_Differences;
607                }
608                break;
609             case 'n':                /* number of links */
610                if (statc.st_nlink != statf.st_nlink) {
611                   prt_fname(jcr);
612                   Jmsg(jcr, M_INFO, 0, _("      st_nlink differ. Cat: %d File: %d\n"),
613                      (uint32_t)statc.st_nlink, (uint32_t)statf.st_nlink);
614                   stat = JS_Differences;
615                }
616                break;
617             case 'u':                /* user id */
618                if (statc.st_uid != statf.st_uid) {
619                   prt_fname(jcr);
620                   Jmsg(jcr, M_INFO, 0, _("      st_uid   differ. Cat: %u File: %u\n"),
621                      (uint32_t)statc.st_uid, (uint32_t)statf.st_uid);
622                   stat = JS_Differences;
623                }
624                break;
625             case 'g':                /* group id */
626                if (statc.st_gid != statf.st_gid) {
627                   prt_fname(jcr);
628                   Jmsg(jcr, M_INFO, 0, _("      st_gid   differ. Cat: %u File: %u\n"),
629                      (uint32_t)statc.st_gid, (uint32_t)statf.st_gid);
630                   stat = JS_Differences;
631                }
632                break;
633             case 's':                /* size */
634                if (statc.st_size != statf.st_size) {
635                   prt_fname(jcr);
636                   Jmsg(jcr, M_INFO, 0, _("      st_size  differ. Cat: %s File: %s\n"),
637                      edit_uint64((uint64_t)statc.st_size, ed1),
638                      edit_uint64((uint64_t)statf.st_size, ed2));
639                   stat = JS_Differences;
640                }
641                break;
642             case 'a':                /* access time */
643                if (statc.st_atime != statf.st_atime) {
644                   prt_fname(jcr);
645                   Jmsg(jcr, M_INFO, 0, _("      st_atime differs\n"));
646                   stat = JS_Differences;
647                }
648                break;
649             case 'm':
650                if (statc.st_mtime != statf.st_mtime) {
651                   prt_fname(jcr);
652                   Jmsg(jcr, M_INFO, 0, _("      st_mtime differs\n"));
653                   stat = JS_Differences;
654                }
655                break;
656             case 'c':                /* ctime */
657                if (statc.st_ctime != statf.st_ctime) {
658                   prt_fname(jcr);
659                   Jmsg(jcr, M_INFO, 0, _("      st_ctime differs\n"));
660                   stat = JS_Differences;
661                }
662                break;
663             case 'd':                /* file size decrease */
664                if (statc.st_size > statf.st_size) {
665                   prt_fname(jcr);
666                   Jmsg(jcr, M_INFO, 0, _("      st_size  decrease. Cat: %s File: %s\n"),
667                      edit_uint64((uint64_t)statc.st_size, ed1),
668                      edit_uint64((uint64_t)statf.st_size, ed2));
669                   stat = JS_Differences;
670                }
671                break;
672             case '5':                /* compare MD5 */
673                Dmsg1(500, "set Do_MD5 for %s\n", jcr->fname);
674                do_SIG = MD5_SIG;
675                break;
676             case '1':                 /* compare SHA1 */
677                do_SIG = SHA1_SIG;
678                break;
679             case ':':
680             case 'V':
681             default:
682                break;
683             }
684          }
685       /*
686        * Got SIG Signature from Storage daemon
687        *  It came across in the Opts_SIG field.
688        */
689       } else if (stream == STREAM_MD5_SIGNATURE || stream == STREAM_SHA1_SIGNATURE) {
690          Dmsg2(400, "stream=SIG inx=%d SIG=%s\n", file_index, Opts_SIG);
691          /*
692           * When ever we get a signature is MUST have been
693           * preceded by an attributes record, which sets attr_file_index
694           */
695          if (jcr->FileIndex != (uint32_t)file_index) {
696             Jmsg2(jcr, M_FATAL, 0, _("MD5/SHA1 index %d not same as attributes %d\n"),
697                file_index, jcr->FileIndex);
698             return false;
699          }
700          if (do_SIG) {
701             db_escape_string(buf, Opts_SIG, strlen(Opts_SIG));
702             if (strcmp(buf, fdbr.SIG) != 0) {
703                prt_fname(jcr);
704                if (debug_level >= 10) {
705                   Jmsg(jcr, M_INFO, 0, _("      %s not same. File=%s Cat=%s\n"),
706                        stream==STREAM_MD5_SIGNATURE?"MD5":"SHA1", buf, fdbr.SIG);
707                } else {
708                   Jmsg(jcr, M_INFO, 0, _("      %s differs.\n"),
709                        stream==STREAM_MD5_SIGNATURE?"MD5":"SHA1");
710                }
711                stat = JS_Differences;
712             }
713             do_SIG = FALSE;
714          }
715       }
716       jcr->JobFiles = file_index;
717    }
718    if (is_bnet_error(fd)) {
719       berrno be;
720       Jmsg2(jcr, M_FATAL, 0, _("bdird<filed: bad attributes from filed n=%d : %s\n"),
721                         n, be.strerror());
722       return false;
723    }
724
725    /* Now find all the files that are missing -- i.e. all files in
726     *  the database where the MarkedId != current JobId
727     */
728    jcr->fn_printed = false;
729    bsnprintf(buf, sizeof(buf),
730 "SELECT Path.Path,Filename.Name FROM File,Path,Filename "
731 "WHERE File.JobId=%d "
732 "AND File.MarkedId!=%d AND File.PathId=Path.PathId "
733 "AND File.FilenameId=Filename.FilenameId",
734       JobId, jcr->JobId);
735    /* missing_handler is called for each file found */
736    db_sql_query(jcr->db, buf, missing_handler, (void *)jcr);
737    if (jcr->fn_printed) {
738       stat = JS_Differences;
739    }
740    free_pool_memory(fname);
741    set_jcr_job_status(jcr, stat);
742    return stat == JS_Terminated;
743 }
744
745 /*
746  * We are called here for each record that matches the above
747  *  SQL query -- that is for each file contained in the Catalog
748  *  that was not marked earlier. This means that the file in
749  *  question is a missing file (in the Catalog but not on Disk).
750  */
751 static int missing_handler(void *ctx, int num_fields, char **row)
752 {
753    JCR *jcr = (JCR *)ctx;
754
755    if (!jcr->fn_printed) {
756       Jmsg(jcr, M_INFO, 0, "\n");
757       Jmsg(jcr, M_INFO, 0, _("The following files are missing:\n"));
758       jcr->fn_printed = true;
759    }
760    Jmsg(jcr, M_INFO, 0, "      %s%s\n", row[0]?row[0]:"", row[1]?row[1]:"");
761    return 0;
762 }
763
764
765 /*
766  * Print filename for verify
767  */
768 static void prt_fname(JCR *jcr)
769 {
770    if (!jcr->fn_printed) {
771       Jmsg(jcr, M_INFO, 0, _("File: %s\n"), jcr->fname);
772       jcr->fn_printed = TRUE;
773    }
774 }