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