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