]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/dird/verify.c
- Put Dmsg() on inside if() to avoid calling subroutine.
[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 ammended 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    JobId = jcr->jr.JobId;
365    set_jcr_job_status(jcr, TermCode);
366
367    update_job_end_record(jcr);
368    if (jcr->unlink_bsr && jcr->RestoreBootstrap) {
369       unlink(jcr->RestoreBootstrap);
370       jcr->unlink_bsr = false;
371    }
372
373    msg_type = M_INFO;                 /* by default INFO message */
374    switch (TermCode) {
375    case JS_Terminated:
376       term_msg = _("Verify OK");
377       break;
378    case JS_FatalError:
379    case JS_ErrorTerminated:
380       term_msg = _("*** Verify Error ***");
381       msg_type = M_ERROR;          /* Generate error message */
382       break;
383    case JS_Error:
384       term_msg = _("Verify warnings");
385       break;
386    case JS_Canceled:
387       term_msg = _("Verify Canceled");
388       break;
389    case JS_Differences:
390       term_msg = _("Verify Differences");
391       break;
392    default:
393       term_msg = term_code;
394       bsnprintf(term_code, sizeof(term_code),
395                 _("Inappropriate term code: %d %c\n"), TermCode, TermCode);
396       break;
397    }
398    bstrftimes(sdt, sizeof(sdt), jcr->jr.StartTime);
399    bstrftimes(edt, sizeof(edt), jcr->jr.EndTime);
400    if (jcr->verify_job) {
401       Name = jcr->verify_job->hdr.name;
402    } else {
403       Name = "";
404    }
405
406    jobstatus_to_ascii(jcr->FDJobStatus, fd_term_msg, sizeof(fd_term_msg));
407    if (jcr->JobLevel == L_VERIFY_VOLUME_TO_CATALOG) {
408       jobstatus_to_ascii(jcr->SDJobStatus, sd_term_msg, sizeof(sd_term_msg));
409       Jmsg(jcr, msg_type, 0, _("Bacula " VERSION " (" LSMDATE "): %s\n"
410 "  JobId:                  %d\n"
411 "  Job:                    %s\n"
412 "  FileSet:                %s\n"
413 "  Verify Level:           %s\n"
414 "  Client:                 %s\n"
415 "  Verify JobId:           %d\n"
416 "  Verify Job:             %s\n"
417 "  Start time:             %s\n"
418 "  End time:               %s\n"
419 "  Files Expected:         %s\n"
420 "  Files Examined:         %s\n"
421 "  Non-fatal FD errors:    %d\n"
422 "  FD termination status:  %s\n"
423 "  SD termination status:  %s\n"
424 "  Termination:            %s\n\n"),
425          edt,
426          jcr->jr.JobId,
427          jcr->jr.Job,
428          jcr->fileset->hdr.name,
429          level_to_str(jcr->JobLevel),
430          jcr->client->hdr.name,
431          jcr->target_jr.JobId,
432          Name,
433          sdt,
434          edt,
435          edit_uint64_with_commas(jcr->ExpectedFiles, ec1),
436          edit_uint64_with_commas(jcr->JobFiles, ec2),
437          jcr->Errors,
438          fd_term_msg,
439          sd_term_msg,
440          term_msg);
441    } else {
442       Jmsg(jcr, msg_type, 0, _("Bacula " VERSION " (" LSMDATE "): %s\n"
443 "  JobId:                  %d\n"
444 "  Job:                    %s\n"
445 "  FileSet:                %s\n"
446 "  Verify Level:           %s\n"
447 "  Client:                 %s\n"
448 "  Verify JobId:           %d\n"
449 "  Verify Job:             %s\n"
450 "  Start time:             %s\n"
451 "  End time:               %s\n"
452 "  Files Examined:         %s\n"
453 "  Non-fatal FD errors:    %d\n"
454 "  FD termination status:  %s\n"
455 "  Termination:            %s\n\n"),
456          edt,
457          jcr->jr.JobId,
458          jcr->jr.Job,
459          jcr->fileset->hdr.name,
460          level_to_str(jcr->JobLevel),
461          jcr->client->hdr.name,
462          jcr->target_jr.JobId,
463          Name,
464          sdt,
465          edt,
466          edit_uint64_with_commas(jcr->JobFiles, ec1),
467          jcr->Errors,
468          fd_term_msg,
469          term_msg);
470    }
471    Dmsg0(100, "Leave verify_cleanup()\n");
472 }
473
474 /*
475  * This routine is called only during a Verify
476  */
477 int get_attributes_and_compare_to_catalog(JCR *jcr, JobId_t JobId)
478 {
479    BSOCK   *fd;
480    int n, len;
481    FILE_DBR fdbr;
482    struct stat statf;                 /* file stat */
483    struct stat statc;                 /* catalog stat */
484    int stat = JS_Terminated;
485    char buf[MAXSTRING];
486    POOLMEM *fname = get_pool_memory(PM_MESSAGE);
487    int do_SIG = NO_SIG;
488    int32_t file_index = 0;
489
490    memset(&fdbr, 0, sizeof(FILE_DBR));
491    fd = jcr->file_bsock;
492    fdbr.JobId = JobId;
493    jcr->FileIndex = 0;
494
495    Dmsg0(20, "bdird: waiting to receive file attributes\n");
496    /*
497     * Get Attributes and Signature from File daemon
498     * We expect:
499     *   FileIndex
500     *   Stream
501     *   Options or SIG (MD5/SHA1)
502     *   Filename
503     *   Attributes
504     *   Link name  ???
505     */
506    while ((n=bget_dirmsg(fd)) >= 0 && !job_canceled(jcr)) {
507       int stream;
508       char *attr, *p, *fn;
509       char Opts_SIG[MAXSTRING];        /* Verify Opts or MD5/SHA1 signature */
510
511       fname = check_pool_memory_size(fname, fd->msglen);
512       jcr->fname = check_pool_memory_size(jcr->fname, fd->msglen);
513       Dmsg1(200, "Atts+SIG=%s\n", fd->msg);
514       if ((len = sscanf(fd->msg, "%ld %d %100s", &file_index, &stream,
515             fname)) != 3) {
516          Jmsg3(jcr, M_FATAL, 0, _("bird<filed: bad attributes, expected 3 fields got %d\n"
517 " mslen=%d msg=%s\n"), len, fd->msglen, fd->msg);
518          return false;
519       }
520       /*
521        * We read the Options or Signature into fname
522        *  to prevent overrun, now copy it to proper location.
523        */
524       bstrncpy(Opts_SIG, fname, sizeof(Opts_SIG));
525       p = fd->msg;
526       skip_nonspaces(&p);             /* skip FileIndex */
527       skip_spaces(&p);
528       skip_nonspaces(&p);             /* skip Stream */
529       skip_spaces(&p);
530       skip_nonspaces(&p);             /* skip Opts_SIG */
531       p++;                            /* skip space */
532       fn = fname;
533       while (*p != 0) {
534          *fn++ = *p++;                /* copy filename */
535       }
536       *fn = *p++;                     /* term filename and point to attribs */
537       attr = p;
538       /*
539        * Got attributes stream, decode it
540        */
541       if (stream == STREAM_UNIX_ATTRIBUTES || stream == STREAM_UNIX_ATTRIBUTES_EX) {
542          int32_t LinkFIf, LinkFIc;
543          Dmsg2(400, "file_index=%d attr=%s\n", file_index, attr);
544          jcr->JobFiles++;
545          jcr->FileIndex = file_index;    /* remember attribute file_index */
546          decode_stat(attr, &statf, &LinkFIf);  /* decode file stat packet */
547          do_SIG = NO_SIG;
548          jcr->fn_printed = false;
549          pm_strcpy(jcr->fname, fname);  /* move filename into JCR */
550
551          Dmsg2(040, "dird<filed: stream=%d %s\n", stream, jcr->fname);
552          Dmsg1(020, "dird<filed: attr=%s\n", attr);
553
554          /*
555           * Find equivalent record in the database
556           */
557          fdbr.FileId = 0;
558          if (!db_get_file_attributes_record(jcr, jcr->db, jcr->fname,
559               &jcr->target_jr, &fdbr)) {
560             Jmsg(jcr, M_INFO, 0, _("New file: %s\n"), jcr->fname);
561             Dmsg1(020, _("File not in catalog: %s\n"), jcr->fname);
562             stat = JS_Differences;
563             continue;
564          } else {
565             /*
566              * mark file record as visited by stuffing the
567              * current JobId, which is unique, into the MarkId field.
568              */
569             db_mark_file_record(jcr, jcr->db, fdbr.FileId, jcr->JobId);
570          }
571
572          Dmsg3(400, "Found %s in catalog. inx=%d Opts=%s\n", jcr->fname,
573             file_index, Opts_SIG);
574          decode_stat(fdbr.LStat, &statc, &LinkFIc); /* decode catalog stat */
575          /*
576           * Loop over options supplied by user and verify the
577           * fields he requests.
578           */
579          for (p=Opts_SIG; *p; p++) {
580             char ed1[30], ed2[30];
581             switch (*p) {
582             case 'i':                /* compare INODEs */
583                if (statc.st_ino != statf.st_ino) {
584                   prt_fname(jcr);
585                   Jmsg(jcr, M_INFO, 0, _("      st_ino   differ. Cat: %s File: %s\n"),
586                      edit_uint64((uint64_t)statc.st_ino, ed1),
587                      edit_uint64((uint64_t)statf.st_ino, ed2));
588                   stat = JS_Differences;
589                }
590                break;
591             case 'p':                /* permissions bits */
592                if (statc.st_mode != statf.st_mode) {
593                   prt_fname(jcr);
594                   Jmsg(jcr, M_INFO, 0, _("      st_mode  differ. Cat: %x File: %x\n"),
595                      (uint32_t)statc.st_mode, (uint32_t)statf.st_mode);
596                   stat = JS_Differences;
597                }
598                break;
599             case 'n':                /* number of links */
600                if (statc.st_nlink != statf.st_nlink) {
601                   prt_fname(jcr);
602                   Jmsg(jcr, M_INFO, 0, _("      st_nlink differ. Cat: %d File: %d\n"),
603                      (uint32_t)statc.st_nlink, (uint32_t)statf.st_nlink);
604                   stat = JS_Differences;
605                }
606                break;
607             case 'u':                /* user id */
608                if (statc.st_uid != statf.st_uid) {
609                   prt_fname(jcr);
610                   Jmsg(jcr, M_INFO, 0, _("      st_uid   differ. Cat: %u File: %u\n"),
611                      (uint32_t)statc.st_uid, (uint32_t)statf.st_uid);
612                   stat = JS_Differences;
613                }
614                break;
615             case 'g':                /* group id */
616                if (statc.st_gid != statf.st_gid) {
617                   prt_fname(jcr);
618                   Jmsg(jcr, M_INFO, 0, _("      st_gid   differ. Cat: %u File: %u\n"),
619                      (uint32_t)statc.st_gid, (uint32_t)statf.st_gid);
620                   stat = JS_Differences;
621                }
622                break;
623             case 's':                /* size */
624                if (statc.st_size != statf.st_size) {
625                   prt_fname(jcr);
626                   Jmsg(jcr, M_INFO, 0, _("      st_size  differ. Cat: %s File: %s\n"),
627                      edit_uint64((uint64_t)statc.st_size, ed1),
628                      edit_uint64((uint64_t)statf.st_size, ed2));
629                   stat = JS_Differences;
630                }
631                break;
632             case 'a':                /* access time */
633                if (statc.st_atime != statf.st_atime) {
634                   prt_fname(jcr);
635                   Jmsg(jcr, M_INFO, 0, _("      st_atime differs\n"));
636                   stat = JS_Differences;
637                }
638                break;
639             case 'm':
640                if (statc.st_mtime != statf.st_mtime) {
641                   prt_fname(jcr);
642                   Jmsg(jcr, M_INFO, 0, _("      st_mtime differs\n"));
643                   stat = JS_Differences;
644                }
645                break;
646             case 'c':                /* ctime */
647                if (statc.st_ctime != statf.st_ctime) {
648                   prt_fname(jcr);
649                   Jmsg(jcr, M_INFO, 0, _("      st_ctime differs\n"));
650                   stat = JS_Differences;
651                }
652                break;
653             case 'd':                /* file size decrease */
654                if (statc.st_size > statf.st_size) {
655                   prt_fname(jcr);
656                   Jmsg(jcr, M_INFO, 0, _("      st_size  decrease. Cat: %s File: %s\n"),
657                      edit_uint64((uint64_t)statc.st_size, ed1),
658                      edit_uint64((uint64_t)statf.st_size, ed2));
659                   stat = JS_Differences;
660                }
661                break;
662             case '5':                /* compare MD5 */
663                Dmsg1(500, "set Do_MD5 for %s\n", jcr->fname);
664                do_SIG = MD5_SIG;
665                break;
666             case '1':                 /* compare SHA1 */
667                do_SIG = SHA1_SIG;
668                break;
669             case ':':
670             case 'V':
671             default:
672                break;
673             }
674          }
675       /*
676        * Got SIG Signature from Storage daemon
677        *  It came across in the Opts_SIG field.
678        */
679       } else if (stream == STREAM_MD5_SIGNATURE || stream == STREAM_SHA1_SIGNATURE) {
680          Dmsg2(400, "stream=SIG inx=%d SIG=%s\n", file_index, Opts_SIG);
681          /*
682           * When ever we get a signature is MUST have been
683           * preceded by an attributes record, which sets attr_file_index
684           */
685          if (jcr->FileIndex != (uint32_t)file_index) {
686             Jmsg2(jcr, M_FATAL, 0, _("MD5/SHA1 index %d not same as attributes %d\n"),
687                file_index, jcr->FileIndex);
688             return false;
689          }
690          if (do_SIG) {
691             db_escape_string(buf, Opts_SIG, strlen(Opts_SIG));
692             if (strcmp(buf, fdbr.SIG) != 0) {
693                prt_fname(jcr);
694                if (debug_level >= 10) {
695                   Jmsg(jcr, M_INFO, 0, _("      %s not same. File=%s Cat=%s\n"),
696                        stream==STREAM_MD5_SIGNATURE?"MD5":"SHA1", buf, fdbr.SIG);
697                } else {
698                   Jmsg(jcr, M_INFO, 0, _("      %s differs.\n"),
699                        stream==STREAM_MD5_SIGNATURE?"MD5":"SHA1");
700                }
701                stat = JS_Differences;
702             }
703             do_SIG = FALSE;
704          }
705       }
706       jcr->JobFiles = file_index;
707    }
708    if (is_bnet_error(fd)) {
709       berrno be;
710       Jmsg2(jcr, M_FATAL, 0, _("bdird<filed: bad attributes from filed n=%d : %s\n"),
711                         n, be.strerror());
712       return false;
713    }
714
715    /* Now find all the files that are missing -- i.e. all files in
716     *  the database where the MarkedId != current JobId
717     */
718    jcr->fn_printed = false;
719    bsnprintf(buf, sizeof(buf),
720 "SELECT Path.Path,Filename.Name FROM File,Path,Filename "
721 "WHERE File.JobId=%d "
722 "AND File.MarkedId!=%d AND File.PathId=Path.PathId "
723 "AND File.FilenameId=Filename.FilenameId",
724       JobId, jcr->JobId);
725    /* missing_handler is called for each file found */
726    db_sql_query(jcr->db, buf, missing_handler, (void *)jcr);
727    if (jcr->fn_printed) {
728       stat = JS_Differences;
729    }
730    free_pool_memory(fname);
731    set_jcr_job_status(jcr, stat);
732    return stat == JS_Terminated;
733 }
734
735 /*
736  * We are called here for each record that matches the above
737  *  SQL query -- that is for each file contained in the Catalog
738  *  that was not marked earlier. This means that the file in
739  *  question is a missing file (in the Catalog but not on Disk).
740  */
741 static int missing_handler(void *ctx, int num_fields, char **row)
742 {
743    JCR *jcr = (JCR *)ctx;
744
745    if (!jcr->fn_printed) {
746       Jmsg(jcr, M_INFO, 0, "\n");
747       Jmsg(jcr, M_INFO, 0, _("The following files are missing:\n"));
748       jcr->fn_printed = true;
749    }
750    Jmsg(jcr, M_INFO, 0, "      %s%s\n", row[0]?row[0]:"", row[1]?row[1]:"");
751    return 0;
752 }
753
754
755 /*
756  * Print filename for verify
757  */
758 static void prt_fname(JCR *jcr)
759 {
760    if (!jcr->fn_printed) {
761       Jmsg(jcr, M_INFO, 0, _("File: %s\n"), jcr->fname);
762       jcr->fn_printed = TRUE;
763    }
764 }