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