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