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