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