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