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->previous_jr, 0, sizeof(jcr->previous_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->previous_jr.JobId = verify_jobid;
108 if (!db_get_job_record(jcr, jcr->db, &jcr->previous_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->previous_jr.JobStatus != 'T') {
114 Jmsg(jcr, M_FATAL, 0, _("Last Job %d did not terminate normally. JobStatus=%c\n"),
115 verify_jobid, jcr->previous_jr.JobStatus);
118 Jmsg(jcr, M_INFO, 0, _("Verifying against JobId=%d Job=%s\n"),
119 jcr->previous_jr.JobId, jcr->previous_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) {
129 if (!create_restore_bootstrap_file(jcr)) {
133 jcr->sd_auth_key = bstrdup("dummy"); /* dummy Storage daemon key */
136 if (jcr->JobLevel == L_VERIFY_DISK_TO_CATALOG && jcr->verify_job) {
137 jcr->fileset = jcr->verify_job->fileset;
139 Dmsg2(100, "ClientId=%u JobLevel=%c\n", jcr->previous_jr.ClientId, jcr->JobLevel);
145 * Do a verification of the specified files against the Catlaog
147 * Returns: false on failure
150 bool do_verify(JCR *jcr)
157 if (!db_update_job_start_record(jcr, jcr->db, &jcr->jr)) {
158 Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
162 /* Print Job Start message */
163 Jmsg(jcr, M_INFO, 0, _("Start Verify JobId=%s Level=%s Job=%s\n"),
164 edit_uint64(jcr->JobId, ed1), level_to_str(jcr->JobLevel), jcr->Job);
166 if (jcr->JobLevel == L_VERIFY_VOLUME_TO_CATALOG) {
168 * Start conversation with Storage daemon
170 set_jcr_job_status(jcr, JS_Blocked);
171 if (!connect_to_storage_daemon(jcr, 10, SDConnectTimeout, 1)) {
175 * Now start a job with the Storage daemon
177 if (!start_storage_daemon_job(jcr, jcr->storage, NULL)) {
180 if (!bnet_fsend(jcr->store_bsock, "run")) {
184 * Now start a Storage daemon message thread
186 if (!start_storage_daemon_message_thread(jcr)) {
189 Dmsg0(50, "Storage daemon connection OK\n");
193 * OK, now connect to the File daemon
194 * and ask him for the files.
196 set_jcr_job_status(jcr, JS_Blocked);
197 if (!connect_to_file_daemon(jcr, 10, FDConnectTimeout, 1)) {
201 set_jcr_job_status(jcr, JS_Running);
202 fd = jcr->file_bsock;
205 Dmsg0(30, ">filed: Send include list\n");
206 if (!send_include_list(jcr)) {
210 Dmsg0(30, ">filed: Send exclude list\n");
211 if (!send_exclude_list(jcr)) {
216 * Send Level command to File daemon, as well
217 * as the Storage address if appropriate.
219 switch (jcr->JobLevel) {
223 case L_VERIFY_CATALOG:
226 case L_VERIFY_VOLUME_TO_CATALOG:
228 * send Storage daemon address to the File daemon
230 if (jcr->store->SDDport == 0) {
231 jcr->store->SDDport = jcr->store->SDport;
233 bnet_fsend(fd, storaddr, jcr->store->address, jcr->store->SDDport);
234 if (!response(jcr, fd, OKstore, "Storage", DISPLAY_ERROR)) {
239 * Send the bootstrap file -- what Volumes/files to restore
241 if (!send_bootstrap_file(jcr, fd) ||
242 !response(jcr, fd, OKbootstrap, "Bootstrap", DISPLAY_ERROR)) {
246 if (!jcr->RestoreBootstrap) {
247 Jmsg0(jcr, M_FATAL, 0, _("Deprecated feature ... use bootstrap.\n"));
256 case L_VERIFY_DISK_TO_CATALOG:
257 level="disk_to_catalog";
260 Jmsg2(jcr, M_FATAL, 0, _("Unimplemented Verify level %d(%c)\n"), jcr->JobLevel,
265 if (!send_runscripts_commands(jcr)) {
270 * Send verify command/level to File daemon
272 bnet_fsend(fd, verifycmd, level);
273 if (!response(jcr, fd, OKverify, "Verify", DISPLAY_ERROR)) {
278 * Now get data back from File daemon and
279 * compare it to the catalog or store it in the
280 * catalog depending on the run type.
282 /* Compare to catalog */
283 switch (jcr->JobLevel) {
284 case L_VERIFY_CATALOG:
285 Dmsg0(10, "Verify level=catalog\n");
286 jcr->sd_msg_thread_done = true; /* no SD msg thread, so it is done */
287 jcr->SDJobStatus = JS_Terminated;
288 get_attributes_and_compare_to_catalog(jcr, jcr->previous_jr.JobId);
291 case L_VERIFY_VOLUME_TO_CATALOG:
292 Dmsg0(10, "Verify level=volume\n");
293 get_attributes_and_compare_to_catalog(jcr, jcr->previous_jr.JobId);
296 case L_VERIFY_DISK_TO_CATALOG:
297 Dmsg0(10, "Verify level=disk_to_catalog\n");
298 jcr->sd_msg_thread_done = true; /* no SD msg thread, so it is done */
299 jcr->SDJobStatus = JS_Terminated;
300 get_attributes_and_compare_to_catalog(jcr, jcr->previous_jr.JobId);
305 Dmsg0(10, "Verify level=init\n");
306 jcr->sd_msg_thread_done = true; /* no SD msg thread, so it is done */
307 jcr->SDJobStatus = JS_Terminated;
308 get_attributes_and_put_in_catalog(jcr);
312 Jmsg1(jcr, M_FATAL, 0, _("Unimplemented verify level %d\n"), jcr->JobLevel);
316 stat = wait_for_job_termination(jcr);
317 if (stat == JS_Terminated) {
318 verify_cleanup(jcr, stat);
326 * Release resources allocated during backup.
329 void verify_cleanup(JCR *jcr, int TermCode)
331 char sdt[50], edt[50];
332 char ec1[30], ec2[30];
333 char term_code[100], fd_term_msg[100], sd_term_msg[100];
334 const char *term_msg;
339 // Dmsg1(100, "Enter verify_cleanup() TermCod=%d\n", TermCode);
340 dequeue_messages(jcr); /* display any queued messages */
342 Dmsg3(900, "JobLevel=%c Expected=%u JobFiles=%u\n", jcr->JobLevel,
343 jcr->ExpectedFiles, jcr->JobFiles);
344 if (jcr->JobLevel == L_VERIFY_VOLUME_TO_CATALOG &&
345 jcr->ExpectedFiles != jcr->JobFiles) {
346 TermCode = JS_ErrorTerminated;
349 /* If no files were expected, there can be no error */
350 if (jcr->JobLevel == L_VERIFY_VOLUME_TO_CATALOG &&
351 jcr->ExpectedFiles == 0) {
352 TermCode = JS_Terminated;
355 JobId = jcr->jr.JobId;
356 set_jcr_job_status(jcr, TermCode);
358 update_job_end_record(jcr);
359 if (jcr->unlink_bsr && jcr->RestoreBootstrap) {
360 unlink(jcr->RestoreBootstrap);
361 jcr->unlink_bsr = false;
364 msg_type = M_INFO; /* by default INFO message */
367 term_msg = _("Verify OK");
370 case JS_ErrorTerminated:
371 term_msg = _("*** Verify Error ***");
372 msg_type = M_ERROR; /* Generate error message */
375 term_msg = _("Verify warnings");
378 term_msg = _("Verify Canceled");
381 term_msg = _("Verify Differences");
384 term_msg = term_code;
385 bsnprintf(term_code, sizeof(term_code),
386 _("Inappropriate term code: %d %c\n"), TermCode, TermCode);
389 bstrftimes(sdt, sizeof(sdt), jcr->jr.StartTime);
390 bstrftimes(edt, sizeof(edt), jcr->jr.EndTime);
391 if (jcr->verify_job) {
392 Name = jcr->verify_job->hdr.name;
397 jobstatus_to_ascii(jcr->FDJobStatus, fd_term_msg, sizeof(fd_term_msg));
398 if (jcr->JobLevel == L_VERIFY_VOLUME_TO_CATALOG) {
399 jobstatus_to_ascii(jcr->SDJobStatus, sd_term_msg, sizeof(sd_term_msg));
400 Jmsg(jcr, msg_type, 0, _("Bacula %s (%s): %s\n"
404 " Verify Level: %s\n"
406 " Verify JobId: %d\n"
410 " Files Expected: %s\n"
411 " Files Examined: %s\n"
412 " Non-fatal FD errors: %d\n"
413 " FD termination status: %s\n"
414 " SD termination status: %s\n"
415 " Termination: %s\n\n"),
421 jcr->fileset->hdr.name,
422 level_to_str(jcr->JobLevel),
423 jcr->client->hdr.name,
424 jcr->previous_jr.JobId,
428 edit_uint64_with_commas(jcr->ExpectedFiles, ec1),
429 edit_uint64_with_commas(jcr->JobFiles, ec2),
435 Jmsg(jcr, msg_type, 0, _("Bacula %s (%s): %s\n"
439 " Verify Level: %s\n"
441 " Verify JobId: %d\n"
445 " Files Examined: %s\n"
446 " Non-fatal FD errors: %d\n"
447 " FD termination status: %s\n"
448 " Termination: %s\n\n"),
454 jcr->fileset->hdr.name,
455 level_to_str(jcr->JobLevel),
456 jcr->client->hdr.name,
457 jcr->previous_jr.JobId,
461 edit_uint64_with_commas(jcr->JobFiles, ec1),
466 Dmsg0(100, "Leave verify_cleanup()\n");
470 * This routine is called only during a Verify
472 int get_attributes_and_compare_to_catalog(JCR *jcr, JobId_t JobId)
477 struct stat statf; /* file stat */
478 struct stat statc; /* catalog stat */
479 int stat = JS_Terminated;
481 POOLMEM *fname = get_pool_memory(PM_MESSAGE);
482 int do_Digest = CRYPTO_DIGEST_NONE;
483 int32_t file_index = 0;
485 memset(&fdbr, 0, sizeof(FILE_DBR));
486 fd = jcr->file_bsock;
490 Dmsg0(20, "bdird: waiting to receive file attributes\n");
492 * Get Attributes and Signature from File daemon
496 * Options or Digest (MD5/SHA1)
501 while ((n=bget_dirmsg(fd)) >= 0 && !job_canceled(jcr)) {
504 char Opts_Digest[MAXSTRING]; /* Verify Opts or MD5/SHA1 digest */
506 fname = check_pool_memory_size(fname, fd->msglen);
507 jcr->fname = check_pool_memory_size(jcr->fname, fd->msglen);
508 Dmsg1(200, "Atts+Digest=%s\n", fd->msg);
509 if ((len = sscanf(fd->msg, "%ld %d %100s", &file_index, &stream,
511 Jmsg3(jcr, M_FATAL, 0, _("bird<filed: bad attributes, expected 3 fields got %d\n"
512 " mslen=%d msg=%s\n"), len, fd->msglen, fd->msg);
516 * We read the Options or Signature into fname
517 * to prevent overrun, now copy it to proper location.
519 bstrncpy(Opts_Digest, fname, sizeof(Opts_Digest));
521 skip_nonspaces(&p); /* skip FileIndex */
523 skip_nonspaces(&p); /* skip Stream */
525 skip_nonspaces(&p); /* skip Opts_Digest */
526 p++; /* skip space */
529 *fn++ = *p++; /* copy filename */
531 *fn = *p++; /* term filename and point to attribs */
534 * Got attributes stream, decode it
536 if (stream == STREAM_UNIX_ATTRIBUTES || stream == STREAM_UNIX_ATTRIBUTES_EX) {
537 int32_t LinkFIf, LinkFIc;
538 Dmsg2(400, "file_index=%d attr=%s\n", file_index, attr);
540 jcr->FileIndex = file_index; /* remember attribute file_index */
541 decode_stat(attr, &statf, &LinkFIf); /* decode file stat packet */
542 do_Digest = CRYPTO_DIGEST_NONE;
543 jcr->fn_printed = false;
544 pm_strcpy(jcr->fname, fname); /* move filename into JCR */
546 Dmsg2(040, "dird<filed: stream=%d %s\n", stream, jcr->fname);
547 Dmsg1(020, "dird<filed: attr=%s\n", attr);
550 * Find equivalent record in the database
553 if (!db_get_file_attributes_record(jcr, jcr->db, jcr->fname,
554 &jcr->previous_jr, &fdbr)) {
555 Jmsg(jcr, M_INFO, 0, _("New file: %s\n"), jcr->fname);
556 Dmsg1(020, _("File not in catalog: %s\n"), jcr->fname);
557 stat = JS_Differences;
561 * mark file record as visited by stuffing the
562 * current JobId, which is unique, into the MarkId field.
564 db_mark_file_record(jcr, jcr->db, fdbr.FileId, jcr->JobId);
567 Dmsg3(400, "Found %s in catalog. inx=%d Opts=%s\n", jcr->fname,
568 file_index, Opts_Digest);
569 decode_stat(fdbr.LStat, &statc, &LinkFIc); /* decode catalog stat */
571 * Loop over options supplied by user and verify the
572 * fields he requests.
574 for (p=Opts_Digest; *p; p++) {
575 char ed1[30], ed2[30];
577 case 'i': /* compare INODEs */
578 if (statc.st_ino != statf.st_ino) {
580 Jmsg(jcr, M_INFO, 0, _(" st_ino differ. Cat: %s File: %s\n"),
581 edit_uint64((uint64_t)statc.st_ino, ed1),
582 edit_uint64((uint64_t)statf.st_ino, ed2));
583 stat = JS_Differences;
586 case 'p': /* permissions bits */
587 if (statc.st_mode != statf.st_mode) {
589 Jmsg(jcr, M_INFO, 0, _(" st_mode differ. Cat: %x File: %x\n"),
590 (uint32_t)statc.st_mode, (uint32_t)statf.st_mode);
591 stat = JS_Differences;
594 case 'n': /* number of links */
595 if (statc.st_nlink != statf.st_nlink) {
597 Jmsg(jcr, M_INFO, 0, _(" st_nlink differ. Cat: %d File: %d\n"),
598 (uint32_t)statc.st_nlink, (uint32_t)statf.st_nlink);
599 stat = JS_Differences;
602 case 'u': /* user id */
603 if (statc.st_uid != statf.st_uid) {
605 Jmsg(jcr, M_INFO, 0, _(" st_uid differ. Cat: %u File: %u\n"),
606 (uint32_t)statc.st_uid, (uint32_t)statf.st_uid);
607 stat = JS_Differences;
610 case 'g': /* group id */
611 if (statc.st_gid != statf.st_gid) {
613 Jmsg(jcr, M_INFO, 0, _(" st_gid differ. Cat: %u File: %u\n"),
614 (uint32_t)statc.st_gid, (uint32_t)statf.st_gid);
615 stat = JS_Differences;
619 if (statc.st_size != statf.st_size) {
621 Jmsg(jcr, M_INFO, 0, _(" st_size differ. Cat: %s File: %s\n"),
622 edit_uint64((uint64_t)statc.st_size, ed1),
623 edit_uint64((uint64_t)statf.st_size, ed2));
624 stat = JS_Differences;
627 case 'a': /* access time */
628 if (statc.st_atime != statf.st_atime) {
630 Jmsg(jcr, M_INFO, 0, _(" st_atime differs\n"));
631 stat = JS_Differences;
635 if (statc.st_mtime != statf.st_mtime) {
637 Jmsg(jcr, M_INFO, 0, _(" st_mtime differs\n"));
638 stat = JS_Differences;
641 case 'c': /* ctime */
642 if (statc.st_ctime != statf.st_ctime) {
644 Jmsg(jcr, M_INFO, 0, _(" st_ctime differs\n"));
645 stat = JS_Differences;
648 case 'd': /* file size decrease */
649 if (statc.st_size > statf.st_size) {
651 Jmsg(jcr, M_INFO, 0, _(" st_size decrease. Cat: %s File: %s\n"),
652 edit_uint64((uint64_t)statc.st_size, ed1),
653 edit_uint64((uint64_t)statf.st_size, ed2));
654 stat = JS_Differences;
657 case '5': /* compare MD5 */
658 Dmsg1(500, "set Do_MD5 for %s\n", jcr->fname);
659 do_Digest = CRYPTO_DIGEST_MD5;
661 case '1': /* compare SHA1 */
662 do_Digest = CRYPTO_DIGEST_SHA1;
671 * Got Digest Signature from Storage daemon
672 * It came across in the Opts_Digest field.
674 } else if (crypto_digest_stream_type(stream) != CRYPTO_DIGEST_NONE) {
675 Dmsg2(400, "stream=Digest inx=%d Digest=%s\n", file_index, Opts_Digest);
677 * When ever we get a digest is MUST have been
678 * preceded by an attributes record, which sets attr_file_index
680 if (jcr->FileIndex != (uint32_t)file_index) {
681 Jmsg2(jcr, M_FATAL, 0, _("MD5/SHA1 index %d not same as attributes %d\n"),
682 file_index, jcr->FileIndex);
685 if (do_Digest != CRYPTO_DIGEST_NONE) {
686 db_escape_string(buf, Opts_Digest, strlen(Opts_Digest));
687 if (strcmp(buf, fdbr.Digest) != 0) {
689 if (debug_level >= 10) {
690 Jmsg(jcr, M_INFO, 0, _(" %s not same. File=%s Cat=%s\n"),
691 stream_to_ascii(stream), buf, fdbr.Digest);
693 Jmsg(jcr, M_INFO, 0, _(" %s differs.\n"),
694 stream_to_ascii(stream));
696 stat = JS_Differences;
698 do_Digest = CRYPTO_DIGEST_NONE;
701 jcr->JobFiles = file_index;
703 if (is_bnet_error(fd)) {
705 Jmsg2(jcr, M_FATAL, 0, _("bdird<filed: bad attributes from filed n=%d : %s\n"),
710 /* Now find all the files that are missing -- i.e. all files in
711 * the database where the MarkId != current JobId
713 jcr->fn_printed = false;
714 bsnprintf(buf, sizeof(buf),
715 "SELECT Path.Path,Filename.Name FROM File,Path,Filename "
716 "WHERE File.JobId=%d "
717 "AND File.MarkId!=%d AND File.PathId=Path.PathId "
718 "AND File.FilenameId=Filename.FilenameId",
720 /* missing_handler is called for each file found */
721 db_sql_query(jcr->db, buf, missing_handler, (void *)jcr);
722 if (jcr->fn_printed) {
723 stat = JS_Differences;
725 free_pool_memory(fname);
726 set_jcr_job_status(jcr, stat);
727 return stat == JS_Terminated;
731 * We are called here for each record that matches the above
732 * SQL query -- that is for each file contained in the Catalog
733 * that was not marked earlier. This means that the file in
734 * question is a missing file (in the Catalog but not on Disk).
736 static int missing_handler(void *ctx, int num_fields, char **row)
738 JCR *jcr = (JCR *)ctx;
740 if (!jcr->fn_printed) {
741 Jmsg(jcr, M_INFO, 0, "\n");
742 Jmsg(jcr, M_INFO, 0, _("The following files are missing:\n"));
743 jcr->fn_printed = true;
745 Jmsg(jcr, M_INFO, 0, " %s%s\n", row[0]?row[0]:"", row[1]?row[1]:"");
751 * Print filename for verify
753 static void prt_fname(JCR *jcr)
755 if (!jcr->fn_printed) {
756 Jmsg(jcr, M_INFO, 0, _("File: %s\n"), jcr->fname);
757 jcr->fn_printed = TRUE;