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 Bacula® - The Network Backup Solution
19 Copyright (C) 2000-2006 Free Software Foundation Europe e.V.
21 The main author of Bacula is Kern Sibbald, with contributions from
22 many others, a complete list can be found in the file AUTHORS.
23 This program is Free Software; you can redistribute it and/or
24 modify it under the terms of version two of the GNU General Public
25 License as published by the Free Software Foundation plus additions
26 that are listed in the file LICENSE.
28 This program is distributed in the hope that it will be useful, but
29 WITHOUT ANY WARRANTY; without even the implied warranty of
30 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
31 General Public License for more details.
33 You should have received a copy of the GNU General Public License
34 along with this program; if not, write to the Free Software
35 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
38 Bacula® is a registered trademark of John Walker.
39 The licensor of Bacula is the Free Software Foundation Europe
40 (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zürich,
41 Switzerland, email:ftf@fsfeurope.org.
47 #include "findlib/find.h"
49 /* Commands sent to File daemon */
50 static char verifycmd[] = "verify level=%s\n";
51 static char storaddr[] = "storage address=%s port=%d ssl=0\n";
53 /* Responses received from File daemon */
54 static char OKverify[] = "2000 OK verify\n";
55 static char OKstore[] = "2000 OK storage\n";
56 static char OKbootstrap[] = "2000 OK bootstrap\n";
58 /* Forward referenced functions */
59 static void prt_fname(JCR *jcr);
60 static int missing_handler(void *ctx, int num_fields, char **row);
64 * Called here before the job is run to do the job
67 bool do_verify_init(JCR *jcr)
70 JobId_t verify_jobid = 0;
73 free_wstorage(jcr); /* we don't write */
75 memset(&jcr->previous_jr, 0, sizeof(jcr->previous_jr));
77 Dmsg1(9, "bdird: created client %s record\n", jcr->client->hdr.name);
80 * Find JobId of last job that ran. E.g.
81 * for VERIFY_CATALOG we want the JobId of the last INIT.
82 * for VERIFY_VOLUME_TO_CATALOG, we want the JobId of the
85 if (jcr->JobLevel == L_VERIFY_CATALOG ||
86 jcr->JobLevel == L_VERIFY_VOLUME_TO_CATALOG ||
87 jcr->JobLevel == L_VERIFY_DISK_TO_CATALOG) {
88 memcpy(&jr, &jcr->jr, sizeof(jr));
89 if (jcr->verify_job &&
90 (jcr->JobLevel == L_VERIFY_VOLUME_TO_CATALOG ||
91 jcr->JobLevel == L_VERIFY_DISK_TO_CATALOG)) {
92 Name = jcr->verify_job->hdr.name;
96 Dmsg1(100, "find last jobid for: %s\n", NPRT(Name));
97 if (!db_find_last_jobid(jcr, jcr->db, Name, &jr)) {
98 if (jcr->JobLevel == L_VERIFY_CATALOG) {
99 Jmsg(jcr, M_FATAL, 0, _(
100 "Unable to find JobId of previous InitCatalog Job.\n"
101 "Please run a Verify with Level=InitCatalog before\n"
102 "running the current Job.\n"));
104 Jmsg(jcr, M_FATAL, 0, _(
105 "Unable to find JobId of previous Job for this client.\n"));
109 verify_jobid = jr.JobId;
110 Dmsg1(100, "Last full jobid=%d\n", verify_jobid);
113 * Now get the job record for the previous backup that interests
114 * us. We use the verify_jobid that we found above.
116 if (jcr->JobLevel == L_VERIFY_CATALOG ||
117 jcr->JobLevel == L_VERIFY_VOLUME_TO_CATALOG ||
118 jcr->JobLevel == L_VERIFY_DISK_TO_CATALOG) {
119 jcr->previous_jr.JobId = verify_jobid;
120 if (!db_get_job_record(jcr, jcr->db, &jcr->previous_jr)) {
121 Jmsg(jcr, M_FATAL, 0, _("Could not get job record for previous Job. ERR=%s"),
122 db_strerror(jcr->db));
125 if (jcr->previous_jr.JobStatus != 'T') {
126 Jmsg(jcr, M_FATAL, 0, _("Last Job %d did not terminate normally. JobStatus=%c\n"),
127 verify_jobid, jcr->previous_jr.JobStatus);
130 Jmsg(jcr, M_INFO, 0, _("Verifying against JobId=%d Job=%s\n"),
131 jcr->previous_jr.JobId, jcr->previous_jr.Job);
135 * If we are verifying a Volume, we need the Storage
136 * daemon, so open a connection, otherwise, just
137 * create a dummy authorization key (passed to
138 * File daemon but not used).
140 if (jcr->JobLevel == L_VERIFY_VOLUME_TO_CATALOG) {
141 if (!create_restore_bootstrap_file(jcr)) {
145 jcr->sd_auth_key = bstrdup("dummy"); /* dummy Storage daemon key */
148 if (jcr->JobLevel == L_VERIFY_DISK_TO_CATALOG && jcr->verify_job) {
149 jcr->fileset = jcr->verify_job->fileset;
151 Dmsg2(100, "ClientId=%u JobLevel=%c\n", jcr->previous_jr.ClientId, jcr->JobLevel);
157 * Do a verification of the specified files against the Catlaog
159 * Returns: false on failure
162 bool do_verify(JCR *jcr)
169 if (!db_update_job_start_record(jcr, jcr->db, &jcr->jr)) {
170 Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
174 /* Print Job Start message */
175 Jmsg(jcr, M_INFO, 0, _("Start Verify JobId=%s Level=%s Job=%s\n"),
176 edit_uint64(jcr->JobId, ed1), level_to_str(jcr->JobLevel), jcr->Job);
178 if (jcr->JobLevel == L_VERIFY_VOLUME_TO_CATALOG) {
180 * Start conversation with Storage daemon
182 set_jcr_job_status(jcr, JS_Blocked);
183 if (!connect_to_storage_daemon(jcr, 10, SDConnectTimeout, 1)) {
187 * Now start a job with the Storage daemon
189 if (!start_storage_daemon_job(jcr, jcr->rstorage, NULL)) {
192 if (!bnet_fsend(jcr->store_bsock, "run")) {
196 * Now start a Storage daemon message thread
198 if (!start_storage_daemon_message_thread(jcr)) {
201 Dmsg0(50, "Storage daemon connection OK\n");
205 * OK, now connect to the File daemon
206 * and ask him for the files.
208 set_jcr_job_status(jcr, JS_Blocked);
209 if (!connect_to_file_daemon(jcr, 10, FDConnectTimeout, 1)) {
213 set_jcr_job_status(jcr, JS_Running);
214 fd = jcr->file_bsock;
217 Dmsg0(30, ">filed: Send include list\n");
218 if (!send_include_list(jcr)) {
222 Dmsg0(30, ">filed: Send exclude list\n");
223 if (!send_exclude_list(jcr)) {
228 * Send Level command to File daemon, as well
229 * as the Storage address if appropriate.
231 switch (jcr->JobLevel) {
235 case L_VERIFY_CATALOG:
238 case L_VERIFY_VOLUME_TO_CATALOG:
240 * send Storage daemon address to the File daemon
242 if (jcr->rstore->SDDport == 0) {
243 jcr->rstore->SDDport = jcr->rstore->SDport;
245 bnet_fsend(fd, storaddr, jcr->rstore->address, jcr->rstore->SDDport);
246 if (!response(jcr, fd, OKstore, "Storage", DISPLAY_ERROR)) {
251 * Send the bootstrap file -- what Volumes/files to restore
253 if (!send_bootstrap_file(jcr, fd) ||
254 !response(jcr, fd, OKbootstrap, "Bootstrap", DISPLAY_ERROR)) {
258 if (!jcr->RestoreBootstrap) {
259 Jmsg0(jcr, M_FATAL, 0, _("Deprecated feature ... use bootstrap.\n"));
268 case L_VERIFY_DISK_TO_CATALOG:
269 level="disk_to_catalog";
272 Jmsg2(jcr, M_FATAL, 0, _("Unimplemented Verify level %d(%c)\n"), jcr->JobLevel,
277 if (!send_runscripts_commands(jcr)) {
282 * Send verify command/level to File daemon
284 bnet_fsend(fd, verifycmd, level);
285 if (!response(jcr, fd, OKverify, "Verify", DISPLAY_ERROR)) {
290 * Now get data back from File daemon and
291 * compare it to the catalog or store it in the
292 * catalog depending on the run type.
294 /* Compare to catalog */
295 switch (jcr->JobLevel) {
296 case L_VERIFY_CATALOG:
297 Dmsg0(10, "Verify level=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);
303 case L_VERIFY_VOLUME_TO_CATALOG:
304 Dmsg0(10, "Verify level=volume\n");
305 get_attributes_and_compare_to_catalog(jcr, jcr->previous_jr.JobId);
308 case L_VERIFY_DISK_TO_CATALOG:
309 Dmsg0(10, "Verify level=disk_to_catalog\n");
310 jcr->sd_msg_thread_done = true; /* no SD msg thread, so it is done */
311 jcr->SDJobStatus = JS_Terminated;
312 get_attributes_and_compare_to_catalog(jcr, jcr->previous_jr.JobId);
317 Dmsg0(10, "Verify level=init\n");
318 jcr->sd_msg_thread_done = true; /* no SD msg thread, so it is done */
319 jcr->SDJobStatus = JS_Terminated;
320 get_attributes_and_put_in_catalog(jcr);
324 Jmsg1(jcr, M_FATAL, 0, _("Unimplemented verify level %d\n"), jcr->JobLevel);
328 stat = wait_for_job_termination(jcr);
329 if (stat == JS_Terminated) {
330 verify_cleanup(jcr, stat);
338 * Release resources allocated during backup.
341 void verify_cleanup(JCR *jcr, int TermCode)
343 char sdt[50], edt[50];
344 char ec1[30], ec2[30];
345 char term_code[100], fd_term_msg[100], sd_term_msg[100];
346 const char *term_msg;
351 // Dmsg1(100, "Enter verify_cleanup() TermCod=%d\n", TermCode);
352 dequeue_messages(jcr); /* display any queued messages */
354 Dmsg3(900, "JobLevel=%c Expected=%u JobFiles=%u\n", jcr->JobLevel,
355 jcr->ExpectedFiles, jcr->JobFiles);
356 if (jcr->JobLevel == L_VERIFY_VOLUME_TO_CATALOG &&
357 jcr->ExpectedFiles != jcr->JobFiles) {
358 TermCode = JS_ErrorTerminated;
361 /* If no files were expected, there can be no error */
362 if (jcr->JobLevel == L_VERIFY_VOLUME_TO_CATALOG &&
363 jcr->ExpectedFiles == 0) {
364 TermCode = JS_Terminated;
367 JobId = jcr->jr.JobId;
368 set_jcr_job_status(jcr, TermCode);
370 update_job_end_record(jcr);
371 if (jcr->unlink_bsr && jcr->RestoreBootstrap) {
372 unlink(jcr->RestoreBootstrap);
373 jcr->unlink_bsr = false;
376 msg_type = M_INFO; /* by default INFO message */
379 term_msg = _("Verify OK");
382 case JS_ErrorTerminated:
383 term_msg = _("*** Verify Error ***");
384 msg_type = M_ERROR; /* Generate error message */
387 term_msg = _("Verify warnings");
390 term_msg = _("Verify Canceled");
393 term_msg = _("Verify Differences");
396 term_msg = term_code;
397 bsnprintf(term_code, sizeof(term_code),
398 _("Inappropriate term code: %d %c\n"), TermCode, TermCode);
401 bstrftimes(sdt, sizeof(sdt), jcr->jr.StartTime);
402 bstrftimes(edt, sizeof(edt), jcr->jr.EndTime);
403 if (jcr->verify_job) {
404 Name = jcr->verify_job->hdr.name;
409 jobstatus_to_ascii(jcr->FDJobStatus, fd_term_msg, sizeof(fd_term_msg));
410 if (jcr->JobLevel == L_VERIFY_VOLUME_TO_CATALOG) {
411 jobstatus_to_ascii(jcr->SDJobStatus, sd_term_msg, sizeof(sd_term_msg));
412 Jmsg(jcr, msg_type, 0, _("Bacula %s (%s): %s\n"
416 " Verify Level: %s\n"
418 " Verify JobId: %d\n"
422 " Files Expected: %s\n"
423 " Files Examined: %s\n"
424 " Non-fatal FD errors: %d\n"
425 " FD termination status: %s\n"
426 " SD termination status: %s\n"
427 " Termination: %s\n\n"),
433 jcr->fileset->hdr.name,
434 level_to_str(jcr->JobLevel),
435 jcr->client->hdr.name,
436 jcr->previous_jr.JobId,
440 edit_uint64_with_commas(jcr->ExpectedFiles, ec1),
441 edit_uint64_with_commas(jcr->JobFiles, ec2),
447 Jmsg(jcr, msg_type, 0, _("Bacula %s (%s): %s\n"
451 " Verify Level: %s\n"
453 " Verify JobId: %d\n"
457 " Files Examined: %s\n"
458 " Non-fatal FD errors: %d\n"
459 " FD termination status: %s\n"
460 " Termination: %s\n\n"),
466 jcr->fileset->hdr.name,
467 level_to_str(jcr->JobLevel),
468 jcr->client->hdr.name,
469 jcr->previous_jr.JobId,
473 edit_uint64_with_commas(jcr->JobFiles, ec1),
478 Dmsg0(100, "Leave verify_cleanup()\n");
482 * This routine is called only during a Verify
484 int get_attributes_and_compare_to_catalog(JCR *jcr, JobId_t JobId)
489 struct stat statf; /* file stat */
490 struct stat statc; /* catalog stat */
491 int stat = JS_Terminated;
493 POOLMEM *fname = get_pool_memory(PM_MESSAGE);
494 int do_Digest = CRYPTO_DIGEST_NONE;
495 int32_t file_index = 0;
497 memset(&fdbr, 0, sizeof(FILE_DBR));
498 fd = jcr->file_bsock;
502 Dmsg0(20, "bdird: waiting to receive file attributes\n");
504 * Get Attributes and Signature from File daemon
508 * Options or Digest (MD5/SHA1)
513 while ((n=bget_dirmsg(fd)) >= 0 && !job_canceled(jcr)) {
516 char Opts_Digest[MAXSTRING]; /* Verify Opts or MD5/SHA1 digest */
518 if (job_canceled(jcr)) {
521 fname = check_pool_memory_size(fname, fd->msglen);
522 jcr->fname = check_pool_memory_size(jcr->fname, fd->msglen);
523 Dmsg1(200, "Atts+Digest=%s\n", fd->msg);
524 if ((len = sscanf(fd->msg, "%ld %d %100s", &file_index, &stream,
526 Jmsg3(jcr, M_FATAL, 0, _("bird<filed: bad attributes, expected 3 fields got %d\n"
527 " mslen=%d msg=%s\n"), len, fd->msglen, fd->msg);
531 * We read the Options or Signature into fname
532 * to prevent overrun, now copy it to proper location.
534 bstrncpy(Opts_Digest, fname, sizeof(Opts_Digest));
536 skip_nonspaces(&p); /* skip FileIndex */
538 skip_nonspaces(&p); /* skip Stream */
540 skip_nonspaces(&p); /* skip Opts_Digest */
541 p++; /* skip space */
544 *fn++ = *p++; /* copy filename */
546 *fn = *p++; /* term filename and point to attribs */
549 * Got attributes stream, decode it
551 if (stream == STREAM_UNIX_ATTRIBUTES || stream == STREAM_UNIX_ATTRIBUTES_EX) {
552 int32_t LinkFIf, LinkFIc;
553 Dmsg2(400, "file_index=%d attr=%s\n", file_index, attr);
555 jcr->FileIndex = file_index; /* remember attribute file_index */
556 decode_stat(attr, &statf, &LinkFIf); /* decode file stat packet */
557 do_Digest = CRYPTO_DIGEST_NONE;
558 jcr->fn_printed = false;
559 pm_strcpy(jcr->fname, fname); /* move filename into JCR */
561 Dmsg2(040, "dird<filed: stream=%d %s\n", stream, jcr->fname);
562 Dmsg1(020, "dird<filed: attr=%s\n", attr);
565 * Find equivalent record in the database
568 if (!db_get_file_attributes_record(jcr, jcr->db, jcr->fname,
569 &jcr->previous_jr, &fdbr)) {
570 Jmsg(jcr, M_INFO, 0, _("New file: %s\n"), jcr->fname);
571 Dmsg1(020, _("File not in catalog: %s\n"), jcr->fname);
572 stat = JS_Differences;
576 * mark file record as visited by stuffing the
577 * current JobId, which is unique, into the MarkId field.
579 db_mark_file_record(jcr, jcr->db, fdbr.FileId, jcr->JobId);
582 Dmsg3(400, "Found %s in catalog. inx=%d Opts=%s\n", jcr->fname,
583 file_index, Opts_Digest);
584 decode_stat(fdbr.LStat, &statc, &LinkFIc); /* decode catalog stat */
586 * Loop over options supplied by user and verify the
587 * fields he requests.
589 for (p=Opts_Digest; *p; p++) {
590 char ed1[30], ed2[30];
592 case 'i': /* compare INODEs */
593 if (statc.st_ino != statf.st_ino) {
595 Jmsg(jcr, M_INFO, 0, _(" st_ino differ. Cat: %s File: %s\n"),
596 edit_uint64((uint64_t)statc.st_ino, ed1),
597 edit_uint64((uint64_t)statf.st_ino, ed2));
598 stat = JS_Differences;
601 case 'p': /* permissions bits */
602 if (statc.st_mode != statf.st_mode) {
604 Jmsg(jcr, M_INFO, 0, _(" st_mode differ. Cat: %x File: %x\n"),
605 (uint32_t)statc.st_mode, (uint32_t)statf.st_mode);
606 stat = JS_Differences;
609 case 'n': /* number of links */
610 if (statc.st_nlink != statf.st_nlink) {
612 Jmsg(jcr, M_INFO, 0, _(" st_nlink differ. Cat: %d File: %d\n"),
613 (uint32_t)statc.st_nlink, (uint32_t)statf.st_nlink);
614 stat = JS_Differences;
617 case 'u': /* user id */
618 if (statc.st_uid != statf.st_uid) {
620 Jmsg(jcr, M_INFO, 0, _(" st_uid differ. Cat: %u File: %u\n"),
621 (uint32_t)statc.st_uid, (uint32_t)statf.st_uid);
622 stat = JS_Differences;
625 case 'g': /* group id */
626 if (statc.st_gid != statf.st_gid) {
628 Jmsg(jcr, M_INFO, 0, _(" st_gid differ. Cat: %u File: %u\n"),
629 (uint32_t)statc.st_gid, (uint32_t)statf.st_gid);
630 stat = JS_Differences;
634 if (statc.st_size != statf.st_size) {
636 Jmsg(jcr, M_INFO, 0, _(" st_size differ. Cat: %s File: %s\n"),
637 edit_uint64((uint64_t)statc.st_size, ed1),
638 edit_uint64((uint64_t)statf.st_size, ed2));
639 stat = JS_Differences;
642 case 'a': /* access time */
643 if (statc.st_atime != statf.st_atime) {
645 Jmsg(jcr, M_INFO, 0, _(" st_atime differs\n"));
646 stat = JS_Differences;
650 if (statc.st_mtime != statf.st_mtime) {
652 Jmsg(jcr, M_INFO, 0, _(" st_mtime differs\n"));
653 stat = JS_Differences;
656 case 'c': /* ctime */
657 if (statc.st_ctime != statf.st_ctime) {
659 Jmsg(jcr, M_INFO, 0, _(" st_ctime differs\n"));
660 stat = JS_Differences;
663 case 'd': /* file size decrease */
664 if (statc.st_size > statf.st_size) {
666 Jmsg(jcr, M_INFO, 0, _(" st_size decrease. Cat: %s File: %s\n"),
667 edit_uint64((uint64_t)statc.st_size, ed1),
668 edit_uint64((uint64_t)statf.st_size, ed2));
669 stat = JS_Differences;
672 case '5': /* compare MD5 */
673 Dmsg1(500, "set Do_MD5 for %s\n", jcr->fname);
674 do_Digest = CRYPTO_DIGEST_MD5;
676 case '1': /* compare SHA1 */
677 do_Digest = CRYPTO_DIGEST_SHA1;
686 * Got Digest Signature from Storage daemon
687 * It came across in the Opts_Digest field.
689 } else if (crypto_digest_stream_type(stream) != CRYPTO_DIGEST_NONE) {
690 Dmsg2(400, "stream=Digest inx=%d Digest=%s\n", file_index, Opts_Digest);
692 * When ever we get a digest is MUST have been
693 * preceded by an attributes record, which sets attr_file_index
695 if (jcr->FileIndex != (uint32_t)file_index) {
696 Jmsg2(jcr, M_FATAL, 0, _("MD5/SHA1 index %d not same as attributes %d\n"),
697 file_index, jcr->FileIndex);
700 if (do_Digest != CRYPTO_DIGEST_NONE) {
701 db_escape_string(buf, Opts_Digest, strlen(Opts_Digest));
702 if (strcmp(buf, fdbr.Digest) != 0) {
704 if (debug_level >= 10) {
705 Jmsg(jcr, M_INFO, 0, _(" %s not same. File=%s Cat=%s\n"),
706 stream_to_ascii(stream), buf, fdbr.Digest);
708 Jmsg(jcr, M_INFO, 0, _(" %s differs.\n"),
709 stream_to_ascii(stream));
711 stat = JS_Differences;
713 do_Digest = CRYPTO_DIGEST_NONE;
716 jcr->JobFiles = file_index;
718 if (is_bnet_error(fd)) {
720 Jmsg2(jcr, M_FATAL, 0, _("bdird<filed: bad attributes from filed n=%d : %s\n"),
725 /* Now find all the files that are missing -- i.e. all files in
726 * the database where the MarkId != current JobId
728 jcr->fn_printed = false;
729 bsnprintf(buf, sizeof(buf),
730 "SELECT Path.Path,Filename.Name FROM File,Path,Filename "
731 "WHERE File.JobId=%d "
732 "AND File.MarkId!=%d AND File.PathId=Path.PathId "
733 "AND File.FilenameId=Filename.FilenameId",
735 /* missing_handler is called for each file found */
736 db_sql_query(jcr->db, buf, missing_handler, (void *)jcr);
737 if (jcr->fn_printed) {
738 stat = JS_Differences;
740 free_pool_memory(fname);
741 set_jcr_job_status(jcr, stat);
742 return stat == JS_Terminated;
746 * We are called here for each record that matches the above
747 * SQL query -- that is for each file contained in the Catalog
748 * that was not marked earlier. This means that the file in
749 * question is a missing file (in the Catalog but not on Disk).
751 static int missing_handler(void *ctx, int num_fields, char **row)
753 JCR *jcr = (JCR *)ctx;
755 if (job_canceled(jcr)) {
758 if (!jcr->fn_printed) {
759 Jmsg(jcr, M_INFO, 0, "\n");
760 Jmsg(jcr, M_INFO, 0, _("The following files are missing:\n"));
761 jcr->fn_printed = true;
763 Jmsg(jcr, M_INFO, 0, " %s%s\n", row[0]?row[0]:"", row[1]?row[1]:"");
769 * Print filename for verify
771 static void prt_fname(JCR *jcr)
773 if (!jcr->fn_printed) {
774 Jmsg(jcr, M_INFO, 0, _("File: %s\n"), jcr->fname);
775 jcr->fn_printed = TRUE;