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