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