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