3 * Bacula Director -- verify.c -- responsible for running file verification
5 * Kern Sibbald, October MM
7 * Basic tasks done here:
9 * Open connection with File daemon and pass him commands
11 * When the File daemon sends the attributes, compare them to
17 Copyright (C) 2000-2006 Kern Sibbald
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.
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.
34 #include "findlib/find.h"
36 /* Imported Global Variables */
37 extern int debug_level;
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";
43 /* Responses received from File daemon */
44 static char OKverify[] = "2000 OK verify\n";
45 static char OKstore[] = "2000 OK storage\n";
46 static char OKbootstrap[] = "2000 OK bootstrap\n";
48 /* Forward referenced functions */
49 static void prt_fname(JCR *jcr);
50 static int missing_handler(void *ctx, int num_fields, char **row);
54 * Called here before the job is run to do the job
57 bool do_verify_init(JCR *jcr)
60 JobId_t verify_jobid = 0;
63 memset(&jcr->target_jr, 0, sizeof(jcr->target_jr));
65 Dmsg1(9, "bdird: created client %s record\n", jcr->client->hdr.name);
68 * Find JobId of last job that ran. E.g.
69 * for VERIFY_CATALOG we want the JobId of the last INIT.
70 * for VERIFY_VOLUME_TO_CATALOG, we want the JobId of the
73 if (jcr->JobLevel == L_VERIFY_CATALOG ||
74 jcr->JobLevel == L_VERIFY_VOLUME_TO_CATALOG ||
75 jcr->JobLevel == L_VERIFY_DISK_TO_CATALOG) {
76 memcpy(&jr, &jcr->jr, sizeof(jr));
77 if (jcr->verify_job &&
78 (jcr->JobLevel == L_VERIFY_VOLUME_TO_CATALOG ||
79 jcr->JobLevel == L_VERIFY_DISK_TO_CATALOG)) {
80 Name = jcr->verify_job->hdr.name;
84 Dmsg1(100, "find last jobid for: %s\n", NPRT(Name));
85 if (!db_find_last_jobid(jcr, jcr->db, Name, &jr)) {
86 if (jcr->JobLevel == L_VERIFY_CATALOG) {
87 Jmsg(jcr, M_FATAL, 0, _(
88 "Unable to find JobId of previous InitCatalog Job.\n"
89 "Please run a Verify with Level=InitCatalog before\n"
90 "running the current Job.\n"));
92 Jmsg(jcr, M_FATAL, 0, _(
93 "Unable to find JobId of previous Job for this client.\n"));
97 verify_jobid = jr.JobId;
98 Dmsg1(100, "Last full jobid=%d\n", verify_jobid);
101 * Now get the job record for the previous backup that interests
102 * us. We use the verify_jobid that we found above.
104 if (jcr->JobLevel == L_VERIFY_CATALOG ||
105 jcr->JobLevel == L_VERIFY_VOLUME_TO_CATALOG ||
106 jcr->JobLevel == L_VERIFY_DISK_TO_CATALOG) {
107 jcr->target_jr.JobId = verify_jobid;
108 if (!db_get_job_record(jcr, jcr->db, &jcr->target_jr)) {
109 Jmsg(jcr, M_FATAL, 0, _("Could not get job record for previous Job. ERR=%s"),
110 db_strerror(jcr->db));
113 if (jcr->target_jr.JobStatus != 'T') {
114 Jmsg(jcr, M_FATAL, 0, _("Last Job %d did not terminate normally. JobStatus=%c\n"),
115 verify_jobid, jcr->target_jr.JobStatus);
118 Jmsg(jcr, M_INFO, 0, _("Verifying against JobId=%d Job=%s\n"),
119 jcr->target_jr.JobId, jcr->target_jr.Job);
123 * If we are verifying a Volume, we need the Storage
124 * daemon, so open a connection, otherwise, just
125 * create a dummy authorization key (passed to
126 * File daemon but not used).
128 if (jcr->JobLevel == L_VERIFY_VOLUME_TO_CATALOG) {
131 memset(&rx, 0, sizeof(rx));
134 rx.bsr->JobId = jcr->target_jr.JobId;
135 ua = new_ua_context(jcr);
136 complete_bsr(ua, rx.bsr);
137 rx.bsr->fi = new_findex();
138 rx.bsr->fi->findex = 1;
139 rx.bsr->fi->findex2 = jcr->target_jr.JobFiles;
140 jcr->ExpectedFiles = write_bsr_file(ua, rx);
141 if (jcr->ExpectedFiles == 0) {
146 if (jcr->RestoreBootstrap) {
147 free(jcr->RestoreBootstrap);
149 POOLMEM *fname = get_pool_memory(PM_MESSAGE);
150 make_unique_restore_filename(ua, &fname);
151 jcr->RestoreBootstrap = bstrdup(fname);
154 free_pool_memory(fname);
155 jcr->needs_sd = true;
158 jcr->sd_auth_key = bstrdup("dummy"); /* dummy Storage daemon key */
161 if (jcr->JobLevel == L_VERIFY_DISK_TO_CATALOG && jcr->verify_job) {
162 jcr->fileset = jcr->verify_job->fileset;
164 Dmsg2(100, "ClientId=%u JobLevel=%c\n", jcr->target_jr.ClientId, jcr->JobLevel);
170 * Do a verification of the specified files against the Catlaog
172 * Returns: false on failure
175 bool do_verify(JCR *jcr)
182 if (!db_update_job_start_record(jcr, jcr->db, &jcr->jr)) {
183 Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
187 /* Print Job Start message */
188 Jmsg(jcr, M_INFO, 0, _("Start Verify JobId=%s Level=%s Job=%s\n"),
189 edit_uint64(jcr->JobId, ed1), level_to_str(jcr->JobLevel), jcr->Job);
191 if (jcr->JobLevel == L_VERIFY_VOLUME_TO_CATALOG) {
193 * Start conversation with Storage daemon
195 set_jcr_job_status(jcr, JS_Blocked);
196 if (!connect_to_storage_daemon(jcr, 10, SDConnectTimeout, 1)) {
200 * Now start a job with the Storage daemon
202 if (!start_storage_daemon_job(jcr, jcr->storage, NULL)) {
206 * Now start a Storage daemon message thread
208 if (!start_storage_daemon_message_thread(jcr)) {
211 Dmsg0(50, "Storage daemon connection OK\n");
213 if (!bnet_fsend(jcr->store_bsock, "run")) {
218 * OK, now connect to the File daemon
219 * and ask him for the files.
221 set_jcr_job_status(jcr, JS_Blocked);
222 if (!connect_to_file_daemon(jcr, 10, FDConnectTimeout, 1)) {
226 set_jcr_job_status(jcr, JS_Running);
227 fd = jcr->file_bsock;
230 Dmsg0(30, ">filed: Send include list\n");
231 if (!send_include_list(jcr)) {
235 Dmsg0(30, ">filed: Send exclude list\n");
236 if (!send_exclude_list(jcr)) {
241 * Send Level command to File daemon, as well
242 * as the Storage address if appropriate.
244 switch (jcr->JobLevel) {
248 case L_VERIFY_CATALOG:
251 case L_VERIFY_VOLUME_TO_CATALOG:
253 * send Storage daemon address to the File daemon
255 if (jcr->store->SDDport == 0) {
256 jcr->store->SDDport = jcr->store->SDport;
258 bnet_fsend(fd, storaddr, jcr->store->address, jcr->store->SDDport);
259 if (!response(jcr, fd, OKstore, "Storage", DISPLAY_ERROR)) {
264 * Send the bootstrap file -- what Volumes/files to restore
266 if (!send_bootstrap_file(jcr, fd) ||
267 !response(jcr, fd, OKbootstrap, "Bootstrap", DISPLAY_ERROR)) {
271 if (!jcr->RestoreBootstrap) {
272 Jmsg0(jcr, M_FATAL, 0, _("Deprecated feature ... use bootstrap.\n"));
281 case L_VERIFY_DISK_TO_CATALOG:
282 level="disk_to_catalog";
285 Jmsg2(jcr, M_FATAL, 0, _("Unimplemented Verify level %d(%c)\n"), jcr->JobLevel,
290 if (!send_run_before_and_after_commands(jcr)) {
295 * Send verify command/level to File daemon
297 bnet_fsend(fd, verifycmd, level);
298 if (!response(jcr, fd, OKverify, "Verify", DISPLAY_ERROR)) {
303 * Now get data back from File daemon and
304 * compare it to the catalog or store it in the
305 * catalog depending on the run type.
307 /* Compare to catalog */
308 switch (jcr->JobLevel) {
309 case L_VERIFY_CATALOG:
310 Dmsg0(10, "Verify level=catalog\n");
311 jcr->sd_msg_thread_done = true; /* no SD msg thread, so it is done */
312 jcr->SDJobStatus = JS_Terminated;
313 get_attributes_and_compare_to_catalog(jcr, jcr->target_jr.JobId);
316 case L_VERIFY_VOLUME_TO_CATALOG:
317 Dmsg0(10, "Verify level=volume\n");
318 get_attributes_and_compare_to_catalog(jcr, jcr->target_jr.JobId);
321 case L_VERIFY_DISK_TO_CATALOG:
322 Dmsg0(10, "Verify level=disk_to_catalog\n");
323 jcr->sd_msg_thread_done = true; /* no SD msg thread, so it is done */
324 jcr->SDJobStatus = JS_Terminated;
325 get_attributes_and_compare_to_catalog(jcr, jcr->target_jr.JobId);
330 Dmsg0(10, "Verify level=init\n");
331 jcr->sd_msg_thread_done = true; /* no SD msg thread, so it is done */
332 jcr->SDJobStatus = JS_Terminated;
333 get_attributes_and_put_in_catalog(jcr);
337 Jmsg1(jcr, M_FATAL, 0, _("Unimplemented verify level %d\n"), jcr->JobLevel);
341 stat = wait_for_job_termination(jcr);
342 if (stat == JS_Terminated) {
343 verify_cleanup(jcr, stat);
351 * Release resources allocated during backup.
354 void verify_cleanup(JCR *jcr, int TermCode)
356 char sdt[50], edt[50];
357 char ec1[30], ec2[30];
358 char term_code[100], fd_term_msg[100], sd_term_msg[100];
359 const char *term_msg;
364 // Dmsg1(100, "Enter verify_cleanup() TermCod=%d\n", TermCode);
365 dequeue_messages(jcr); /* display any queued messages */
367 Dmsg3(900, "JobLevel=%c Expected=%u JobFiles=%u\n", jcr->JobLevel,
368 jcr->ExpectedFiles, jcr->JobFiles);
369 if (jcr->JobLevel == L_VERIFY_VOLUME_TO_CATALOG &&
370 jcr->ExpectedFiles != jcr->JobFiles) {
371 TermCode = JS_ErrorTerminated;
374 /* If no files were expected, there can be no error */
375 if (jcr->JobLevel == L_VERIFY_VOLUME_TO_CATALOG &&
376 jcr->ExpectedFiles == 0) {
377 TermCode = JS_Terminated;
380 JobId = jcr->jr.JobId;
381 set_jcr_job_status(jcr, TermCode);
383 update_job_end_record(jcr);
384 if (jcr->unlink_bsr && jcr->RestoreBootstrap) {
385 unlink(jcr->RestoreBootstrap);
386 jcr->unlink_bsr = false;
389 msg_type = M_INFO; /* by default INFO message */
392 term_msg = _("Verify OK");
395 case JS_ErrorTerminated:
396 term_msg = _("*** Verify Error ***");
397 msg_type = M_ERROR; /* Generate error message */
400 term_msg = _("Verify warnings");
403 term_msg = _("Verify Canceled");
406 term_msg = _("Verify Differences");
409 term_msg = term_code;
410 bsnprintf(term_code, sizeof(term_code),
411 _("Inappropriate term code: %d %c\n"), TermCode, TermCode);
414 bstrftimes(sdt, sizeof(sdt), jcr->jr.StartTime);
415 bstrftimes(edt, sizeof(edt), jcr->jr.EndTime);
416 if (jcr->verify_job) {
417 Name = jcr->verify_job->hdr.name;
422 jobstatus_to_ascii(jcr->FDJobStatus, fd_term_msg, sizeof(fd_term_msg));
423 if (jcr->JobLevel == L_VERIFY_VOLUME_TO_CATALOG) {
424 jobstatus_to_ascii(jcr->SDJobStatus, sd_term_msg, sizeof(sd_term_msg));
425 Jmsg(jcr, msg_type, 0, _("Bacula %s (%s): %s\n"
429 " Verify Level: %s\n"
431 " Verify JobId: %d\n"
435 " Files Expected: %s\n"
436 " Files Examined: %s\n"
437 " Non-fatal FD errors: %d\n"
438 " FD termination status: %s\n"
439 " SD termination status: %s\n"
440 " Termination: %s\n\n"),
446 jcr->fileset->hdr.name,
447 level_to_str(jcr->JobLevel),
448 jcr->client->hdr.name,
449 jcr->target_jr.JobId,
453 edit_uint64_with_commas(jcr->ExpectedFiles, ec1),
454 edit_uint64_with_commas(jcr->JobFiles, ec2),
460 Jmsg(jcr, msg_type, 0, _("Bacula %s (%s): %s\n"
464 " Verify Level: %s\n"
466 " Verify JobId: %d\n"
470 " Files Examined: %s\n"
471 " Non-fatal FD errors: %d\n"
472 " FD termination status: %s\n"
473 " Termination: %s\n\n"),
479 jcr->fileset->hdr.name,
480 level_to_str(jcr->JobLevel),
481 jcr->client->hdr.name,
482 jcr->target_jr.JobId,
486 edit_uint64_with_commas(jcr->JobFiles, ec1),
491 Dmsg0(100, "Leave verify_cleanup()\n");
495 * This routine is called only during a Verify
497 int get_attributes_and_compare_to_catalog(JCR *jcr, JobId_t JobId)
502 struct stat statf; /* file stat */
503 struct stat statc; /* catalog stat */
504 int stat = JS_Terminated;
506 POOLMEM *fname = get_pool_memory(PM_MESSAGE);
507 int do_Digest = CRYPTO_DIGEST_NONE;
508 int32_t file_index = 0;
510 memset(&fdbr, 0, sizeof(FILE_DBR));
511 fd = jcr->file_bsock;
515 Dmsg0(20, "bdird: waiting to receive file attributes\n");
517 * Get Attributes and Signature from File daemon
521 * Options or Digest (MD5/SHA1)
526 while ((n=bget_dirmsg(fd)) >= 0 && !job_canceled(jcr)) {
529 char Opts_Digest[MAXSTRING]; /* Verify Opts or MD5/SHA1 digest */
531 fname = check_pool_memory_size(fname, fd->msglen);
532 jcr->fname = check_pool_memory_size(jcr->fname, fd->msglen);
533 Dmsg1(200, "Atts+Digest=%s\n", fd->msg);
534 if ((len = sscanf(fd->msg, "%ld %d %100s", &file_index, &stream,
536 Jmsg3(jcr, M_FATAL, 0, _("bird<filed: bad attributes, expected 3 fields got %d\n"
537 " mslen=%d msg=%s\n"), len, fd->msglen, fd->msg);
541 * We read the Options or Signature into fname
542 * to prevent overrun, now copy it to proper location.
544 bstrncpy(Opts_Digest, fname, sizeof(Opts_Digest));
546 skip_nonspaces(&p); /* skip FileIndex */
548 skip_nonspaces(&p); /* skip Stream */
550 skip_nonspaces(&p); /* skip Opts_Digest */
551 p++; /* skip space */
554 *fn++ = *p++; /* copy filename */
556 *fn = *p++; /* term filename and point to attribs */
559 * Got attributes stream, decode it
561 if (stream == STREAM_UNIX_ATTRIBUTES || stream == STREAM_UNIX_ATTRIBUTES_EX) {
562 int32_t LinkFIf, LinkFIc;
563 Dmsg2(400, "file_index=%d attr=%s\n", file_index, attr);
565 jcr->FileIndex = file_index; /* remember attribute file_index */
566 decode_stat(attr, &statf, &LinkFIf); /* decode file stat packet */
567 do_Digest = CRYPTO_DIGEST_NONE;
568 jcr->fn_printed = false;
569 pm_strcpy(jcr->fname, fname); /* move filename into JCR */
571 Dmsg2(040, "dird<filed: stream=%d %s\n", stream, jcr->fname);
572 Dmsg1(020, "dird<filed: attr=%s\n", attr);
575 * Find equivalent record in the database
578 if (!db_get_file_attributes_record(jcr, jcr->db, jcr->fname,
579 &jcr->target_jr, &fdbr)) {
580 Jmsg(jcr, M_INFO, 0, _("New file: %s\n"), jcr->fname);
581 Dmsg1(020, _("File not in catalog: %s\n"), jcr->fname);
582 stat = JS_Differences;
586 * mark file record as visited by stuffing the
587 * current JobId, which is unique, into the MarkId field.
589 db_mark_file_record(jcr, jcr->db, fdbr.FileId, jcr->JobId);
592 Dmsg3(400, "Found %s in catalog. inx=%d Opts=%s\n", jcr->fname,
593 file_index, Opts_Digest);
594 decode_stat(fdbr.LStat, &statc, &LinkFIc); /* decode catalog stat */
596 * Loop over options supplied by user and verify the
597 * fields he requests.
599 for (p=Opts_Digest; *p; p++) {
600 char ed1[30], ed2[30];
602 case 'i': /* compare INODEs */
603 if (statc.st_ino != statf.st_ino) {
605 Jmsg(jcr, M_INFO, 0, _(" st_ino differ. Cat: %s File: %s\n"),
606 edit_uint64((uint64_t)statc.st_ino, ed1),
607 edit_uint64((uint64_t)statf.st_ino, ed2));
608 stat = JS_Differences;
611 case 'p': /* permissions bits */
612 if (statc.st_mode != statf.st_mode) {
614 Jmsg(jcr, M_INFO, 0, _(" st_mode differ. Cat: %x File: %x\n"),
615 (uint32_t)statc.st_mode, (uint32_t)statf.st_mode);
616 stat = JS_Differences;
619 case 'n': /* number of links */
620 if (statc.st_nlink != statf.st_nlink) {
622 Jmsg(jcr, M_INFO, 0, _(" st_nlink differ. Cat: %d File: %d\n"),
623 (uint32_t)statc.st_nlink, (uint32_t)statf.st_nlink);
624 stat = JS_Differences;
627 case 'u': /* user id */
628 if (statc.st_uid != statf.st_uid) {
630 Jmsg(jcr, M_INFO, 0, _(" st_uid differ. Cat: %u File: %u\n"),
631 (uint32_t)statc.st_uid, (uint32_t)statf.st_uid);
632 stat = JS_Differences;
635 case 'g': /* group id */
636 if (statc.st_gid != statf.st_gid) {
638 Jmsg(jcr, M_INFO, 0, _(" st_gid differ. Cat: %u File: %u\n"),
639 (uint32_t)statc.st_gid, (uint32_t)statf.st_gid);
640 stat = JS_Differences;
644 if (statc.st_size != statf.st_size) {
646 Jmsg(jcr, M_INFO, 0, _(" st_size differ. Cat: %s File: %s\n"),
647 edit_uint64((uint64_t)statc.st_size, ed1),
648 edit_uint64((uint64_t)statf.st_size, ed2));
649 stat = JS_Differences;
652 case 'a': /* access time */
653 if (statc.st_atime != statf.st_atime) {
655 Jmsg(jcr, M_INFO, 0, _(" st_atime differs\n"));
656 stat = JS_Differences;
660 if (statc.st_mtime != statf.st_mtime) {
662 Jmsg(jcr, M_INFO, 0, _(" st_mtime differs\n"));
663 stat = JS_Differences;
666 case 'c': /* ctime */
667 if (statc.st_ctime != statf.st_ctime) {
669 Jmsg(jcr, M_INFO, 0, _(" st_ctime differs\n"));
670 stat = JS_Differences;
673 case 'd': /* file size decrease */
674 if (statc.st_size > statf.st_size) {
676 Jmsg(jcr, M_INFO, 0, _(" st_size decrease. Cat: %s File: %s\n"),
677 edit_uint64((uint64_t)statc.st_size, ed1),
678 edit_uint64((uint64_t)statf.st_size, ed2));
679 stat = JS_Differences;
682 case '5': /* compare MD5 */
683 Dmsg1(500, "set Do_MD5 for %s\n", jcr->fname);
684 do_Digest = CRYPTO_DIGEST_MD5;
686 case '1': /* compare SHA1 */
687 do_Digest = CRYPTO_DIGEST_SHA1;
696 * Got Digest Signature from Storage daemon
697 * It came across in the Opts_Digest field.
699 } else if (crypto_digest_stream_type(stream) != CRYPTO_DIGEST_NONE) {
700 Dmsg2(400, "stream=Digest inx=%d Digest=%s\n", file_index, Opts_Digest);
702 * When ever we get a digest is MUST have been
703 * preceded by an attributes record, which sets attr_file_index
705 if (jcr->FileIndex != (uint32_t)file_index) {
706 Jmsg2(jcr, M_FATAL, 0, _("MD5/SHA1 index %d not same as attributes %d\n"),
707 file_index, jcr->FileIndex);
710 if (do_Digest != CRYPTO_DIGEST_NONE) {
711 db_escape_string(buf, Opts_Digest, strlen(Opts_Digest));
712 if (strcmp(buf, fdbr.Digest) != 0) {
714 if (debug_level >= 10) {
715 Jmsg(jcr, M_INFO, 0, _(" %s not same. File=%s Cat=%s\n"),
716 stream_to_ascii(stream), buf, fdbr.Digest);
718 Jmsg(jcr, M_INFO, 0, _(" %s differs.\n"),
719 stream_to_ascii(stream));
721 stat = JS_Differences;
723 do_Digest = CRYPTO_DIGEST_NONE;
726 jcr->JobFiles = file_index;
728 if (is_bnet_error(fd)) {
730 Jmsg2(jcr, M_FATAL, 0, _("bdird<filed: bad attributes from filed n=%d : %s\n"),
735 /* Now find all the files that are missing -- i.e. all files in
736 * the database where the MarkedId != current JobId
738 jcr->fn_printed = false;
739 bsnprintf(buf, sizeof(buf),
740 "SELECT Path.Path,Filename.Name FROM File,Path,Filename "
741 "WHERE File.JobId=%d "
742 "AND File.MarkedId!=%d AND File.PathId=Path.PathId "
743 "AND File.FilenameId=Filename.FilenameId",
745 /* missing_handler is called for each file found */
746 db_sql_query(jcr->db, buf, missing_handler, (void *)jcr);
747 if (jcr->fn_printed) {
748 stat = JS_Differences;
750 free_pool_memory(fname);
751 set_jcr_job_status(jcr, stat);
752 return stat == JS_Terminated;
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 not on Disk).
761 static int missing_handler(void *ctx, int num_fields, char **row)
763 JCR *jcr = (JCR *)ctx;
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;
770 Jmsg(jcr, M_INFO, 0, " %s%s\n", row[0]?row[0]:"", row[1]?row[1]:"");
776 * Print filename for verify
778 static void prt_fname(JCR *jcr)
780 if (!jcr->fn_printed) {
781 Jmsg(jcr, M_INFO, 0, _("File: %s\n"), jcr->fname);
782 jcr->fn_printed = TRUE;