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