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