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