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