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