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