]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/dird/verify.c
50828f40bdfd5f80e434e3416e4d71221e7b0e6e
[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    /* If no files were expected, there can be no error */
387    if (jcr->get_JobLevel() == L_VERIFY_VOLUME_TO_CATALOG &&
388        jcr->ExpectedFiles == 0) {
389       TermCode = JS_Terminated;
390    }
391
392    JobId = jcr->jr.JobId;
393
394    update_job_end(jcr, TermCode);
395
396    if (job_canceled(jcr)) {
397       cancel_storage_daemon_job(jcr);
398    }
399
400    if (jcr->unlink_bsr && jcr->RestoreBootstrap) {
401       unlink(jcr->RestoreBootstrap);
402       jcr->unlink_bsr = false;
403    }
404
405    msg_type = M_INFO;                 /* by default INFO message */
406    switch (TermCode) {
407    case JS_Terminated:
408       term_msg = _("Verify OK");
409       break;
410    case JS_FatalError:
411    case JS_ErrorTerminated:
412       term_msg = _("*** Verify Error ***");
413       msg_type = M_ERROR;          /* Generate error message */
414       break;
415    case JS_Error:
416       term_msg = _("Verify warnings");
417       break;
418    case JS_Canceled:
419       term_msg = _("Verify Canceled");
420       break;
421    case JS_Differences:
422       term_msg = _("Verify Differences");
423       break;
424    default:
425       term_msg = term_code;
426       bsnprintf(term_code, sizeof(term_code),
427                 _("Inappropriate term code: %d %c\n"), TermCode, TermCode);
428       break;
429    }
430    bstrftimes(sdt, sizeof(sdt), jcr->jr.StartTime);
431    bstrftimes(edt, sizeof(edt), jcr->jr.EndTime);
432    if (jcr->verify_job) {
433       Name = jcr->verify_job->hdr.name;
434    } else {
435       Name = "";
436    }
437
438    jobstatus_to_ascii(jcr->FDJobStatus, fd_term_msg, sizeof(fd_term_msg));
439    if (jcr->get_JobLevel() == L_VERIFY_VOLUME_TO_CATALOG) {
440       jobstatus_to_ascii(jcr->SDJobStatus, sd_term_msg, sizeof(sd_term_msg));
441       Jmsg(jcr, msg_type, 0, _("%s %s %s (%s): %s\n"
442 "  Build OS:               %s %s %s\n"
443 "  JobId:                  %d\n"
444 "  Job:                    %s\n"
445 "  FileSet:                %s\n"
446 "  Verify Level:           %s\n"
447 "  Client:                 %s\n"
448 "  Verify JobId:           %d\n"
449 "  Verify Job:             %s\n"
450 "  Start time:             %s\n"
451 "  End time:               %s\n"
452 "  Files Expected:         %s\n"
453 "  Files Examined:         %s\n"
454 "  Non-fatal FD errors:    %d\n"
455 "  FD termination status:  %s\n"
456 "  SD termination status:  %s\n"
457 "  Termination:            %s\n\n"),
458            BACULA, my_name, VERSION, LSMDATE, edt,
459            HOST_OS, DISTNAME, DISTVER,
460            jcr->jr.JobId,
461            jcr->jr.Job,
462            jcr->fileset->hdr.name,
463            level_to_str(jcr->get_JobLevel()),
464            jcr->client->hdr.name,
465            jcr->previous_jr.JobId,
466            Name,
467            sdt,
468            edt,
469            edit_uint64_with_commas(jcr->ExpectedFiles, ec1),
470            edit_uint64_with_commas(jcr->JobFiles, ec2),
471            jcr->Errors,
472            fd_term_msg,
473            sd_term_msg,
474            term_msg);
475    } else {
476       Jmsg(jcr, msg_type, 0, _("%s %s %s (%s): %s\n"
477 "  Build:                  %s %s %s\n"
478 "  JobId:                  %d\n"
479 "  Job:                    %s\n"
480 "  FileSet:                %s\n"
481 "  Verify Level:           %s\n"
482 "  Client:                 %s\n"
483 "  Verify JobId:           %d\n"
484 "  Verify Job:             %s\n"
485 "  Start time:             %s\n"
486 "  End time:               %s\n"
487 "  Files Examined:         %s\n"
488 "  Non-fatal FD errors:    %d\n"
489 "  FD termination status:  %s\n"
490 "  Termination:            %s\n\n"),
491            BACULA, my_name, VERSION, LSMDATE, edt,
492            HOST_OS, DISTNAME, DISTVER,
493            jcr->jr.JobId,
494            jcr->jr.Job,
495            jcr->fileset->hdr.name,
496            level_to_str(jcr->get_JobLevel()),
497            jcr->client->name(),
498            jcr->previous_jr.JobId,
499            Name,
500            sdt,
501            edt,
502            edit_uint64_with_commas(jcr->JobFiles, ec1),
503            jcr->Errors,
504            fd_term_msg,
505            term_msg);
506    }
507    Dmsg0(100, "Leave verify_cleanup()\n");
508 }
509
510 /*
511  * This routine is called only during a Verify
512  */
513 int get_attributes_and_compare_to_catalog(JCR *jcr, JobId_t JobId)
514 {
515    BSOCK   *fd;
516    int n, len;
517    FILE_DBR fdbr;
518    struct stat statf;                 /* file stat */
519    struct stat statc;                 /* catalog stat */
520    int stat = JS_Terminated;
521    char buf[MAXSTRING];
522    POOLMEM *fname = get_pool_memory(PM_MESSAGE);
523    int do_Digest = CRYPTO_DIGEST_NONE;
524    int32_t file_index = 0;
525
526    memset(&fdbr, 0, sizeof(FILE_DBR));
527    fd = jcr->file_bsock;
528    fdbr.JobId = JobId;
529    jcr->FileIndex = 0;
530
531    Dmsg0(20, "bdird: waiting to receive file attributes\n");
532    /*
533     * Get Attributes and Signature from File daemon
534     * We expect:
535     *   FileIndex
536     *   Stream
537     *   Options or Digest (MD5/SHA1)
538     *   Filename
539     *   Attributes
540     *   Link name  ???
541     */
542    while ((n=bget_dirmsg(fd)) >= 0 && !job_canceled(jcr)) {
543       int stream;
544       char *attr, *p, *fn;
545       char Opts_Digest[MAXSTRING];        /* Verify Opts or MD5/SHA1 digest */
546
547       if (job_canceled(jcr)) {
548          return false;
549       }
550       fname = check_pool_memory_size(fname, fd->msglen);
551       jcr->fname = check_pool_memory_size(jcr->fname, fd->msglen);
552       Dmsg1(200, "Atts+Digest=%s\n", fd->msg);
553       if ((len = sscanf(fd->msg, "%ld %d %100s", &file_index, &stream,
554             fname)) != 3) {
555          Jmsg3(jcr, M_FATAL, 0, _("bird<filed: bad attributes, expected 3 fields got %d\n"
556 " mslen=%d msg=%s\n"), len, fd->msglen, fd->msg);
557          return false;
558       }
559       /*
560        * We read the Options or Signature into fname
561        *  to prevent overrun, now copy it to proper location.
562        */
563       bstrncpy(Opts_Digest, fname, sizeof(Opts_Digest));
564       p = fd->msg;
565       skip_nonspaces(&p);             /* skip FileIndex */
566       skip_spaces(&p);
567       skip_nonspaces(&p);             /* skip Stream */
568       skip_spaces(&p);
569       skip_nonspaces(&p);             /* skip Opts_Digest */
570       p++;                            /* skip space */
571       fn = fname;
572       while (*p != 0) {
573          *fn++ = *p++;                /* copy filename */
574       }
575       *fn = *p++;                     /* term filename and point to attribs */
576       attr = p;
577       /*
578        * Got attributes stream, decode it
579        */
580       if (stream == STREAM_UNIX_ATTRIBUTES || stream == STREAM_UNIX_ATTRIBUTES_EX) {
581          int32_t LinkFIf, LinkFIc;
582          Dmsg2(400, "file_index=%d attr=%s\n", file_index, attr);
583          jcr->JobFiles++;
584          jcr->FileIndex = file_index;    /* remember attribute file_index */
585          decode_stat(attr, &statf, &LinkFIf);  /* decode file stat packet */
586          do_Digest = CRYPTO_DIGEST_NONE;
587          jcr->fn_printed = false;
588          pm_strcpy(jcr->fname, fname);  /* move filename into JCR */
589
590          Dmsg2(040, "dird<filed: stream=%d %s\n", stream, jcr->fname);
591          Dmsg1(020, "dird<filed: attr=%s\n", attr);
592
593          /*
594           * Find equivalent record in the database
595           */
596          fdbr.FileId = 0;
597          if (!db_get_file_attributes_record(jcr, jcr->db, jcr->fname,
598               &jcr->previous_jr, &fdbr)) {
599             Jmsg(jcr, M_INFO, 0, _("New file: %s\n"), jcr->fname);
600             Dmsg1(020, _("File not in catalog: %s\n"), jcr->fname);
601             stat = JS_Differences;
602             continue;
603          } else {
604             /*
605              * mark file record as visited by stuffing the
606              * current JobId, which is unique, into the MarkId field.
607              */
608             db_mark_file_record(jcr, jcr->db, fdbr.FileId, jcr->JobId);
609          }
610
611          Dmsg3(400, "Found %s in catalog. inx=%d Opts=%s\n", jcr->fname,
612             file_index, Opts_Digest);
613          decode_stat(fdbr.LStat, &statc, &LinkFIc); /* decode catalog stat */
614          /*
615           * Loop over options supplied by user and verify the
616           * fields he requests.
617           */
618          for (p=Opts_Digest; *p; p++) {
619             char ed1[30], ed2[30];
620             switch (*p) {
621             case 'i':                /* compare INODEs */
622                if (statc.st_ino != statf.st_ino) {
623                   prt_fname(jcr);
624                   Jmsg(jcr, M_INFO, 0, _("      st_ino   differ. Cat: %s File: %s\n"),
625                      edit_uint64((uint64_t)statc.st_ino, ed1),
626                      edit_uint64((uint64_t)statf.st_ino, ed2));
627                   stat = JS_Differences;
628                }
629                break;
630             case 'p':                /* permissions bits */
631                if (statc.st_mode != statf.st_mode) {
632                   prt_fname(jcr);
633                   Jmsg(jcr, M_INFO, 0, _("      st_mode  differ. Cat: %x File: %x\n"),
634                      (uint32_t)statc.st_mode, (uint32_t)statf.st_mode);
635                   stat = JS_Differences;
636                }
637                break;
638             case 'n':                /* number of links */
639                if (statc.st_nlink != statf.st_nlink) {
640                   prt_fname(jcr);
641                   Jmsg(jcr, M_INFO, 0, _("      st_nlink differ. Cat: %d File: %d\n"),
642                      (uint32_t)statc.st_nlink, (uint32_t)statf.st_nlink);
643                   stat = JS_Differences;
644                }
645                break;
646             case 'u':                /* user id */
647                if (statc.st_uid != statf.st_uid) {
648                   prt_fname(jcr);
649                   Jmsg(jcr, M_INFO, 0, _("      st_uid   differ. Cat: %u File: %u\n"),
650                      (uint32_t)statc.st_uid, (uint32_t)statf.st_uid);
651                   stat = JS_Differences;
652                }
653                break;
654             case 'g':                /* group id */
655                if (statc.st_gid != statf.st_gid) {
656                   prt_fname(jcr);
657                   Jmsg(jcr, M_INFO, 0, _("      st_gid   differ. Cat: %u File: %u\n"),
658                      (uint32_t)statc.st_gid, (uint32_t)statf.st_gid);
659                   stat = JS_Differences;
660                }
661                break;
662             case 's':                /* size */
663                if (statc.st_size != statf.st_size) {
664                   prt_fname(jcr);
665                   Jmsg(jcr, M_INFO, 0, _("      st_size  differ. Cat: %s File: %s\n"),
666                      edit_uint64((uint64_t)statc.st_size, ed1),
667                      edit_uint64((uint64_t)statf.st_size, ed2));
668                   stat = JS_Differences;
669                }
670                break;
671             case 'a':                /* access time */
672                if (statc.st_atime != statf.st_atime) {
673                   prt_fname(jcr);
674                   Jmsg(jcr, M_INFO, 0, _("      st_atime differs\n"));
675                   stat = JS_Differences;
676                }
677                break;
678             case 'm':
679                if (statc.st_mtime != statf.st_mtime) {
680                   prt_fname(jcr);
681                   Jmsg(jcr, M_INFO, 0, _("      st_mtime differs\n"));
682                   stat = JS_Differences;
683                }
684                break;
685             case 'c':                /* ctime */
686                if (statc.st_ctime != statf.st_ctime) {
687                   prt_fname(jcr);
688                   Jmsg(jcr, M_INFO, 0, _("      st_ctime differs\n"));
689                   stat = JS_Differences;
690                }
691                break;
692             case 'd':                /* file size decrease */
693                if (statc.st_size > statf.st_size) {
694                   prt_fname(jcr);
695                   Jmsg(jcr, M_INFO, 0, _("      st_size  decrease. Cat: %s File: %s\n"),
696                      edit_uint64((uint64_t)statc.st_size, ed1),
697                      edit_uint64((uint64_t)statf.st_size, ed2));
698                   stat = JS_Differences;
699                }
700                break;
701             case '5':                /* compare MD5 */
702                Dmsg1(500, "set Do_MD5 for %s\n", jcr->fname);
703                do_Digest = CRYPTO_DIGEST_MD5;
704                break;
705             case '1':                 /* compare SHA1 */
706                do_Digest = CRYPTO_DIGEST_SHA1;
707                break;
708             case ':':
709             case 'V':
710             default:
711                break;
712             }
713          }
714       /*
715        * Got Digest Signature from Storage daemon
716        *  It came across in the Opts_Digest field.
717        */
718       } else if (crypto_digest_stream_type(stream) != CRYPTO_DIGEST_NONE) {
719          Dmsg2(400, "stream=Digest inx=%d Digest=%s\n", file_index, Opts_Digest);
720          /*
721           * When ever we get a digest it MUST have been
722           * preceded by an attributes record, which sets attr_file_index
723           */
724          if (jcr->FileIndex != (uint32_t)file_index) {
725             Jmsg2(jcr, M_FATAL, 0, _("MD5/SHA1 index %d not same as attributes %d\n"),
726                file_index, jcr->FileIndex);
727             return false;
728          }
729          if (do_Digest != CRYPTO_DIGEST_NONE) {
730             db_escape_string(jcr, jcr->db, buf, Opts_Digest, strlen(Opts_Digest));
731             if (strcmp(buf, fdbr.Digest) != 0) {
732                prt_fname(jcr);
733                Jmsg(jcr, M_INFO, 0, _("      %s differs. File=%s Cat=%s\n"),
734                     stream_to_ascii(stream), buf, fdbr.Digest);
735                stat = JS_Differences;
736             }
737             do_Digest = CRYPTO_DIGEST_NONE;
738          }
739       }
740       jcr->JobFiles = file_index;
741    }
742    if (is_bnet_error(fd)) {
743       berrno be;
744       Jmsg2(jcr, M_FATAL, 0, _("bdird<filed: bad attributes from filed n=%d : %s\n"),
745                         n, be.bstrerror());
746       return false;
747    }
748
749    /* Now find all the files that are missing -- i.e. all files in
750     *  the database where the MarkId != current JobId
751     */
752    jcr->fn_printed = false;
753    bsnprintf(buf, sizeof(buf),
754       "SELECT Path.Path,Filename.Name FROM File,Path,Filename "
755       "WHERE File.JobId=%d "
756       "AND File.MarkId!=%d AND File.PathId=Path.PathId "
757       "AND File.FilenameId=Filename.FilenameId",
758          JobId, jcr->JobId);
759    /* missing_handler is called for each file found */
760    db_sql_query(jcr->db, buf, missing_handler, (void *)jcr);
761    if (jcr->fn_printed) {
762       stat = JS_Differences;
763    }
764    free_pool_memory(fname);
765    if (!job_canceled(jcr)) {
766       jcr->JobStatus = stat;
767    }
768    return stat == JS_Terminated;
769 }
770
771 /*
772  * We are called here for each record that matches the above
773  *  SQL query -- that is for each file contained in the Catalog
774  *  that was not marked earlier. This means that the file in
775  *  question is a missing file (in the Catalog but not on Disk).
776  */
777 static int missing_handler(void *ctx, int num_fields, char **row)
778 {
779    JCR *jcr = (JCR *)ctx;
780
781    if (job_canceled(jcr)) {
782       return 1;
783    }
784    if (!jcr->fn_printed) {
785       Qmsg(jcr, M_INFO, 0, _("\nThe following files are in the Catalog but not on %s:\n"),
786        jcr->get_JobLevel() == L_VERIFY_VOLUME_TO_CATALOG ? "the Volume(s)" : "disk");
787       jcr->fn_printed = true;
788    }
789    Qmsg(jcr, M_INFO, 0, "      %s%s\n", row[0]?row[0]:"", row[1]?row[1]:"");
790    return 0;
791 }
792
793
794 /*
795  * Print filename for verify
796  */
797 static void prt_fname(JCR *jcr)
798 {
799    if (!jcr->fn_printed) {
800       Jmsg(jcr, M_INFO, 0, _("File: %s\n"), jcr->fname);
801       jcr->fn_printed = true;
802    }
803 }