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 /* Commands sent to File daemon */
37 static char verifycmd[] = "verify level=%s\n";
38 static char storaddr[] = "storage address=%s port=%d ssl=0\n";
40 /* Responses received from File daemon */
41 static char OKverify[] = "2000 OK verify\n";
42 static char OKstore[] = "2000 OK storage\n";
43 static char OKbootstrap[] = "2000 OK bootstrap\n";
45 /* Forward referenced functions */
46 static void prt_fname(JCR *jcr);
47 static int missing_handler(void *ctx, int num_fields, char **row);
51 * Called here before the job is run to do the job
54 bool do_verify_init(JCR *jcr)
57 JobId_t verify_jobid = 0;
60 free_wstorage(jcr); /* we don't write */
62 memset(&jcr->previous_jr, 0, sizeof(jcr->previous_jr));
64 Dmsg1(9, "bdird: created client %s record\n", jcr->client->hdr.name);
67 * Find JobId of last job that ran. E.g.
68 * for VERIFY_CATALOG we want the JobId of the last INIT.
69 * for VERIFY_VOLUME_TO_CATALOG, we want the JobId of the
72 if (jcr->JobLevel == L_VERIFY_CATALOG ||
73 jcr->JobLevel == L_VERIFY_VOLUME_TO_CATALOG ||
74 jcr->JobLevel == L_VERIFY_DISK_TO_CATALOG) {
75 memcpy(&jr, &jcr->jr, sizeof(jr));
76 if (jcr->verify_job &&
77 (jcr->JobLevel == L_VERIFY_VOLUME_TO_CATALOG ||
78 jcr->JobLevel == L_VERIFY_DISK_TO_CATALOG)) {
79 Name = jcr->verify_job->hdr.name;
83 Dmsg1(100, "find last jobid for: %s\n", NPRT(Name));
84 if (!db_find_last_jobid(jcr, jcr->db, Name, &jr)) {
85 if (jcr->JobLevel == L_VERIFY_CATALOG) {
86 Jmsg(jcr, M_FATAL, 0, _(
87 "Unable to find JobId of previous InitCatalog Job.\n"
88 "Please run a Verify with Level=InitCatalog before\n"
89 "running the current Job.\n"));
91 Jmsg(jcr, M_FATAL, 0, _(
92 "Unable to find JobId of previous Job for this client.\n"));
96 verify_jobid = jr.JobId;
97 Dmsg1(100, "Last full jobid=%d\n", verify_jobid);
100 * Now get the job record for the previous backup that interests
101 * us. We use the verify_jobid that we found above.
103 if (jcr->JobLevel == L_VERIFY_CATALOG ||
104 jcr->JobLevel == L_VERIFY_VOLUME_TO_CATALOG ||
105 jcr->JobLevel == L_VERIFY_DISK_TO_CATALOG) {
106 jcr->previous_jr.JobId = verify_jobid;
107 if (!db_get_job_record(jcr, jcr->db, &jcr->previous_jr)) {
108 Jmsg(jcr, M_FATAL, 0, _("Could not get job record for previous Job. ERR=%s"),
109 db_strerror(jcr->db));
112 if (jcr->previous_jr.JobStatus != 'T') {
113 Jmsg(jcr, M_FATAL, 0, _("Last Job %d did not terminate normally. JobStatus=%c\n"),
114 verify_jobid, jcr->previous_jr.JobStatus);
117 Jmsg(jcr, M_INFO, 0, _("Verifying against JobId=%d Job=%s\n"),
118 jcr->previous_jr.JobId, jcr->previous_jr.Job);
122 * If we are verifying a Volume, we need the Storage
123 * daemon, so open a connection, otherwise, just
124 * create a dummy authorization key (passed to
125 * File daemon but not used).
127 if (jcr->JobLevel == L_VERIFY_VOLUME_TO_CATALOG) {
128 if (!create_restore_bootstrap_file(jcr)) {
132 jcr->sd_auth_key = bstrdup("dummy"); /* dummy Storage daemon key */
135 if (jcr->JobLevel == L_VERIFY_DISK_TO_CATALOG && jcr->verify_job) {
136 jcr->fileset = jcr->verify_job->fileset;
138 Dmsg2(100, "ClientId=%u JobLevel=%c\n", jcr->previous_jr.ClientId, jcr->JobLevel);
144 * Do a verification of the specified files against the Catlaog
146 * Returns: false on failure
149 bool do_verify(JCR *jcr)
156 if (!db_update_job_start_record(jcr, jcr->db, &jcr->jr)) {
157 Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
161 /* Print Job Start message */
162 Jmsg(jcr, M_INFO, 0, _("Start Verify JobId=%s Level=%s Job=%s\n"),
163 edit_uint64(jcr->JobId, ed1), level_to_str(jcr->JobLevel), jcr->Job);
165 if (jcr->JobLevel == L_VERIFY_VOLUME_TO_CATALOG) {
167 * Start conversation with Storage daemon
169 set_jcr_job_status(jcr, JS_Blocked);
170 if (!connect_to_storage_daemon(jcr, 10, SDConnectTimeout, 1)) {
174 * Now start a job with the Storage daemon
176 if (!start_storage_daemon_job(jcr, jcr->rstorage, NULL)) {
179 if (!bnet_fsend(jcr->store_bsock, "run")) {
183 * Now start a Storage daemon message thread
185 if (!start_storage_daemon_message_thread(jcr)) {
188 Dmsg0(50, "Storage daemon connection OK\n");
192 * OK, now connect to the File daemon
193 * and ask him for the files.
195 set_jcr_job_status(jcr, JS_Blocked);
196 if (!connect_to_file_daemon(jcr, 10, FDConnectTimeout, 1)) {
200 set_jcr_job_status(jcr, JS_Running);
201 fd = jcr->file_bsock;
204 Dmsg0(30, ">filed: Send include list\n");
205 if (!send_include_list(jcr)) {
209 Dmsg0(30, ">filed: Send exclude list\n");
210 if (!send_exclude_list(jcr)) {
215 * Send Level command to File daemon, as well
216 * as the Storage address if appropriate.
218 switch (jcr->JobLevel) {
222 case L_VERIFY_CATALOG:
225 case L_VERIFY_VOLUME_TO_CATALOG:
227 * send Storage daemon address to the File daemon
229 if (jcr->rstore->SDDport == 0) {
230 jcr->rstore->SDDport = jcr->rstore->SDport;
232 bnet_fsend(fd, storaddr, jcr->rstore->address, jcr->rstore->SDDport);
233 if (!response(jcr, fd, OKstore, "Storage", DISPLAY_ERROR)) {
238 * Send the bootstrap file -- what Volumes/files to restore
240 if (!send_bootstrap_file(jcr, fd) ||
241 !response(jcr, fd, OKbootstrap, "Bootstrap", DISPLAY_ERROR)) {
245 if (!jcr->RestoreBootstrap) {
246 Jmsg0(jcr, M_FATAL, 0, _("Deprecated feature ... use bootstrap.\n"));
255 case L_VERIFY_DISK_TO_CATALOG:
256 level="disk_to_catalog";
259 Jmsg2(jcr, M_FATAL, 0, _("Unimplemented Verify level %d(%c)\n"), jcr->JobLevel,
264 if (!send_runscripts_commands(jcr)) {
269 * Send verify command/level to File daemon
271 bnet_fsend(fd, verifycmd, level);
272 if (!response(jcr, fd, OKverify, "Verify", DISPLAY_ERROR)) {
277 * Now get data back from File daemon and
278 * compare it to the catalog or store it in the
279 * catalog depending on the run type.
281 /* Compare to catalog */
282 switch (jcr->JobLevel) {
283 case L_VERIFY_CATALOG:
284 Dmsg0(10, "Verify level=catalog\n");
285 jcr->sd_msg_thread_done = true; /* no SD msg thread, so it is done */
286 jcr->SDJobStatus = JS_Terminated;
287 get_attributes_and_compare_to_catalog(jcr, jcr->previous_jr.JobId);
290 case L_VERIFY_VOLUME_TO_CATALOG:
291 Dmsg0(10, "Verify level=volume\n");
292 get_attributes_and_compare_to_catalog(jcr, jcr->previous_jr.JobId);
295 case L_VERIFY_DISK_TO_CATALOG:
296 Dmsg0(10, "Verify level=disk_to_catalog\n");
297 jcr->sd_msg_thread_done = true; /* no SD msg thread, so it is done */
298 jcr->SDJobStatus = JS_Terminated;
299 get_attributes_and_compare_to_catalog(jcr, jcr->previous_jr.JobId);
304 Dmsg0(10, "Verify level=init\n");
305 jcr->sd_msg_thread_done = true; /* no SD msg thread, so it is done */
306 jcr->SDJobStatus = JS_Terminated;
307 get_attributes_and_put_in_catalog(jcr);
311 Jmsg1(jcr, M_FATAL, 0, _("Unimplemented verify level %d\n"), jcr->JobLevel);
315 stat = wait_for_job_termination(jcr);
316 if (stat == JS_Terminated) {
317 verify_cleanup(jcr, stat);
325 * Release resources allocated during backup.
328 void verify_cleanup(JCR *jcr, int TermCode)
330 char sdt[50], edt[50];
331 char ec1[30], ec2[30];
332 char term_code[100], fd_term_msg[100], sd_term_msg[100];
333 const char *term_msg;
338 // Dmsg1(100, "Enter verify_cleanup() TermCod=%d\n", TermCode);
339 dequeue_messages(jcr); /* display any queued messages */
341 Dmsg3(900, "JobLevel=%c Expected=%u JobFiles=%u\n", jcr->JobLevel,
342 jcr->ExpectedFiles, jcr->JobFiles);
343 if (jcr->JobLevel == L_VERIFY_VOLUME_TO_CATALOG &&
344 jcr->ExpectedFiles != jcr->JobFiles) {
345 TermCode = JS_ErrorTerminated;
348 /* If no files were expected, there can be no error */
349 if (jcr->JobLevel == L_VERIFY_VOLUME_TO_CATALOG &&
350 jcr->ExpectedFiles == 0) {
351 TermCode = JS_Terminated;
354 JobId = jcr->jr.JobId;
355 set_jcr_job_status(jcr, TermCode);
357 update_job_end_record(jcr);
358 if (jcr->unlink_bsr && jcr->RestoreBootstrap) {
359 unlink(jcr->RestoreBootstrap);
360 jcr->unlink_bsr = false;
363 msg_type = M_INFO; /* by default INFO message */
366 term_msg = _("Verify OK");
369 case JS_ErrorTerminated:
370 term_msg = _("*** Verify Error ***");
371 msg_type = M_ERROR; /* Generate error message */
374 term_msg = _("Verify warnings");
377 term_msg = _("Verify Canceled");
380 term_msg = _("Verify Differences");
383 term_msg = term_code;
384 bsnprintf(term_code, sizeof(term_code),
385 _("Inappropriate term code: %d %c\n"), TermCode, TermCode);
388 bstrftimes(sdt, sizeof(sdt), jcr->jr.StartTime);
389 bstrftimes(edt, sizeof(edt), jcr->jr.EndTime);
390 if (jcr->verify_job) {
391 Name = jcr->verify_job->hdr.name;
396 jobstatus_to_ascii(jcr->FDJobStatus, fd_term_msg, sizeof(fd_term_msg));
397 if (jcr->JobLevel == L_VERIFY_VOLUME_TO_CATALOG) {
398 jobstatus_to_ascii(jcr->SDJobStatus, sd_term_msg, sizeof(sd_term_msg));
399 Jmsg(jcr, msg_type, 0, _("Bacula %s (%s): %s\n"
403 " Verify Level: %s\n"
405 " Verify JobId: %d\n"
409 " Files Expected: %s\n"
410 " Files Examined: %s\n"
411 " Non-fatal FD errors: %d\n"
412 " FD termination status: %s\n"
413 " SD termination status: %s\n"
414 " Termination: %s\n\n"),
420 jcr->fileset->hdr.name,
421 level_to_str(jcr->JobLevel),
422 jcr->client->hdr.name,
423 jcr->previous_jr.JobId,
427 edit_uint64_with_commas(jcr->ExpectedFiles, ec1),
428 edit_uint64_with_commas(jcr->JobFiles, ec2),
434 Jmsg(jcr, msg_type, 0, _("Bacula %s (%s): %s\n"
438 " Verify Level: %s\n"
440 " Verify JobId: %d\n"
444 " Files Examined: %s\n"
445 " Non-fatal FD errors: %d\n"
446 " FD termination status: %s\n"
447 " Termination: %s\n\n"),
453 jcr->fileset->hdr.name,
454 level_to_str(jcr->JobLevel),
455 jcr->client->hdr.name,
456 jcr->previous_jr.JobId,
460 edit_uint64_with_commas(jcr->JobFiles, ec1),
465 Dmsg0(100, "Leave verify_cleanup()\n");
469 * This routine is called only during a Verify
471 int get_attributes_and_compare_to_catalog(JCR *jcr, JobId_t JobId)
476 struct stat statf; /* file stat */
477 struct stat statc; /* catalog stat */
478 int stat = JS_Terminated;
480 POOLMEM *fname = get_pool_memory(PM_MESSAGE);
481 int do_Digest = CRYPTO_DIGEST_NONE;
482 int32_t file_index = 0;
484 memset(&fdbr, 0, sizeof(FILE_DBR));
485 fd = jcr->file_bsock;
489 Dmsg0(20, "bdird: waiting to receive file attributes\n");
491 * Get Attributes and Signature from File daemon
495 * Options or Digest (MD5/SHA1)
500 while ((n=bget_dirmsg(fd)) >= 0 && !job_canceled(jcr)) {
503 char Opts_Digest[MAXSTRING]; /* Verify Opts or MD5/SHA1 digest */
505 if (job_canceled(jcr)) {
508 fname = check_pool_memory_size(fname, fd->msglen);
509 jcr->fname = check_pool_memory_size(jcr->fname, fd->msglen);
510 Dmsg1(200, "Atts+Digest=%s\n", fd->msg);
511 if ((len = sscanf(fd->msg, "%ld %d %100s", &file_index, &stream,
513 Jmsg3(jcr, M_FATAL, 0, _("bird<filed: bad attributes, expected 3 fields got %d\n"
514 " mslen=%d msg=%s\n"), len, fd->msglen, fd->msg);
518 * We read the Options or Signature into fname
519 * to prevent overrun, now copy it to proper location.
521 bstrncpy(Opts_Digest, fname, sizeof(Opts_Digest));
523 skip_nonspaces(&p); /* skip FileIndex */
525 skip_nonspaces(&p); /* skip Stream */
527 skip_nonspaces(&p); /* skip Opts_Digest */
528 p++; /* skip space */
531 *fn++ = *p++; /* copy filename */
533 *fn = *p++; /* term filename and point to attribs */
536 * Got attributes stream, decode it
538 if (stream == STREAM_UNIX_ATTRIBUTES || stream == STREAM_UNIX_ATTRIBUTES_EX) {
539 int32_t LinkFIf, LinkFIc;
540 Dmsg2(400, "file_index=%d attr=%s\n", file_index, attr);
542 jcr->FileIndex = file_index; /* remember attribute file_index */
543 decode_stat(attr, &statf, &LinkFIf); /* decode file stat packet */
544 do_Digest = CRYPTO_DIGEST_NONE;
545 jcr->fn_printed = false;
546 pm_strcpy(jcr->fname, fname); /* move filename into JCR */
548 Dmsg2(040, "dird<filed: stream=%d %s\n", stream, jcr->fname);
549 Dmsg1(020, "dird<filed: attr=%s\n", attr);
552 * Find equivalent record in the database
555 if (!db_get_file_attributes_record(jcr, jcr->db, jcr->fname,
556 &jcr->previous_jr, &fdbr)) {
557 Jmsg(jcr, M_INFO, 0, _("New file: %s\n"), jcr->fname);
558 Dmsg1(020, _("File not in catalog: %s\n"), jcr->fname);
559 stat = JS_Differences;
563 * mark file record as visited by stuffing the
564 * current JobId, which is unique, into the MarkId field.
566 db_mark_file_record(jcr, jcr->db, fdbr.FileId, jcr->JobId);
569 Dmsg3(400, "Found %s in catalog. inx=%d Opts=%s\n", jcr->fname,
570 file_index, Opts_Digest);
571 decode_stat(fdbr.LStat, &statc, &LinkFIc); /* decode catalog stat */
573 * Loop over options supplied by user and verify the
574 * fields he requests.
576 for (p=Opts_Digest; *p; p++) {
577 char ed1[30], ed2[30];
579 case 'i': /* compare INODEs */
580 if (statc.st_ino != statf.st_ino) {
582 Jmsg(jcr, M_INFO, 0, _(" st_ino differ. Cat: %s File: %s\n"),
583 edit_uint64((uint64_t)statc.st_ino, ed1),
584 edit_uint64((uint64_t)statf.st_ino, ed2));
585 stat = JS_Differences;
588 case 'p': /* permissions bits */
589 if (statc.st_mode != statf.st_mode) {
591 Jmsg(jcr, M_INFO, 0, _(" st_mode differ. Cat: %x File: %x\n"),
592 (uint32_t)statc.st_mode, (uint32_t)statf.st_mode);
593 stat = JS_Differences;
596 case 'n': /* number of links */
597 if (statc.st_nlink != statf.st_nlink) {
599 Jmsg(jcr, M_INFO, 0, _(" st_nlink differ. Cat: %d File: %d\n"),
600 (uint32_t)statc.st_nlink, (uint32_t)statf.st_nlink);
601 stat = JS_Differences;
604 case 'u': /* user id */
605 if (statc.st_uid != statf.st_uid) {
607 Jmsg(jcr, M_INFO, 0, _(" st_uid differ. Cat: %u File: %u\n"),
608 (uint32_t)statc.st_uid, (uint32_t)statf.st_uid);
609 stat = JS_Differences;
612 case 'g': /* group id */
613 if (statc.st_gid != statf.st_gid) {
615 Jmsg(jcr, M_INFO, 0, _(" st_gid differ. Cat: %u File: %u\n"),
616 (uint32_t)statc.st_gid, (uint32_t)statf.st_gid);
617 stat = JS_Differences;
621 if (statc.st_size != statf.st_size) {
623 Jmsg(jcr, M_INFO, 0, _(" st_size differ. Cat: %s File: %s\n"),
624 edit_uint64((uint64_t)statc.st_size, ed1),
625 edit_uint64((uint64_t)statf.st_size, ed2));
626 stat = JS_Differences;
629 case 'a': /* access time */
630 if (statc.st_atime != statf.st_atime) {
632 Jmsg(jcr, M_INFO, 0, _(" st_atime differs\n"));
633 stat = JS_Differences;
637 if (statc.st_mtime != statf.st_mtime) {
639 Jmsg(jcr, M_INFO, 0, _(" st_mtime differs\n"));
640 stat = JS_Differences;
643 case 'c': /* ctime */
644 if (statc.st_ctime != statf.st_ctime) {
646 Jmsg(jcr, M_INFO, 0, _(" st_ctime differs\n"));
647 stat = JS_Differences;
650 case 'd': /* file size decrease */
651 if (statc.st_size > statf.st_size) {
653 Jmsg(jcr, M_INFO, 0, _(" st_size decrease. Cat: %s File: %s\n"),
654 edit_uint64((uint64_t)statc.st_size, ed1),
655 edit_uint64((uint64_t)statf.st_size, ed2));
656 stat = JS_Differences;
659 case '5': /* compare MD5 */
660 Dmsg1(500, "set Do_MD5 for %s\n", jcr->fname);
661 do_Digest = CRYPTO_DIGEST_MD5;
663 case '1': /* compare SHA1 */
664 do_Digest = CRYPTO_DIGEST_SHA1;
673 * Got Digest Signature from Storage daemon
674 * It came across in the Opts_Digest field.
676 } else if (crypto_digest_stream_type(stream) != CRYPTO_DIGEST_NONE) {
677 Dmsg2(400, "stream=Digest inx=%d Digest=%s\n", file_index, Opts_Digest);
679 * When ever we get a digest is MUST have been
680 * preceded by an attributes record, which sets attr_file_index
682 if (jcr->FileIndex != (uint32_t)file_index) {
683 Jmsg2(jcr, M_FATAL, 0, _("MD5/SHA1 index %d not same as attributes %d\n"),
684 file_index, jcr->FileIndex);
687 if (do_Digest != CRYPTO_DIGEST_NONE) {
688 db_escape_string(buf, Opts_Digest, strlen(Opts_Digest));
689 if (strcmp(buf, fdbr.Digest) != 0) {
691 if (debug_level >= 10) {
692 Jmsg(jcr, M_INFO, 0, _(" %s not same. File=%s Cat=%s\n"),
693 stream_to_ascii(stream), buf, fdbr.Digest);
695 Jmsg(jcr, M_INFO, 0, _(" %s differs.\n"),
696 stream_to_ascii(stream));
698 stat = JS_Differences;
700 do_Digest = CRYPTO_DIGEST_NONE;
703 jcr->JobFiles = file_index;
705 if (is_bnet_error(fd)) {
707 Jmsg2(jcr, M_FATAL, 0, _("bdird<filed: bad attributes from filed n=%d : %s\n"),
712 /* Now find all the files that are missing -- i.e. all files in
713 * the database where the MarkId != current JobId
715 jcr->fn_printed = false;
716 bsnprintf(buf, sizeof(buf),
717 "SELECT Path.Path,Filename.Name FROM File,Path,Filename "
718 "WHERE File.JobId=%d "
719 "AND File.MarkId!=%d AND File.PathId=Path.PathId "
720 "AND File.FilenameId=Filename.FilenameId",
722 /* missing_handler is called for each file found */
723 db_sql_query(jcr->db, buf, missing_handler, (void *)jcr);
724 if (jcr->fn_printed) {
725 stat = JS_Differences;
727 free_pool_memory(fname);
728 set_jcr_job_status(jcr, stat);
729 return stat == JS_Terminated;
733 * We are called here for each record that matches the above
734 * SQL query -- that is for each file contained in the Catalog
735 * that was not marked earlier. This means that the file in
736 * question is a missing file (in the Catalog but not on Disk).
738 static int missing_handler(void *ctx, int num_fields, char **row)
740 JCR *jcr = (JCR *)ctx;
742 if (job_canceled(jcr)) {
745 if (!jcr->fn_printed) {
746 Jmsg(jcr, M_INFO, 0, "\n");
747 Jmsg(jcr, M_INFO, 0, _("The following files are missing:\n"));
748 jcr->fn_printed = true;
750 Jmsg(jcr, M_INFO, 0, " %s%s\n", row[0]?row[0]:"", row[1]?row[1]:"");
756 * Print filename for verify
758 static void prt_fname(JCR *jcr)
760 if (!jcr->fn_printed) {
761 Jmsg(jcr, M_INFO, 0, _("File: %s\n"), jcr->fname);
762 jcr->fn_printed = TRUE;