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