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