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