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