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