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