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