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