2 Bacula® - The Network Backup Solution
4 Copyright (C) 2000-2009 Free Software Foundation Europe e.V.
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 two of the GNU General Public
10 License as published by the Free Software Foundation and included
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.
18 You should have received a copy of the GNU 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
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.
30 * Bacula Director -- verify.c -- responsible for running file verification
32 * Kern Sibbald, October MM
34 * Basic tasks done here:
36 * Open connection with File daemon and pass him commands
38 * When the File daemon sends the attributes, compare them to
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 Authorization=%s\n";
53 /* Responses received from File daemon */
54 static char OKverify[] = "2000 OK verify\n";
55 static char OKstore[] = "2000 OK storage\n";
57 /* Responses received from the Storage daemon */
58 static char OKbootstrap[] = "3000 OK bootstrap\n";
60 /* Forward referenced functions */
61 static void prt_fname(JCR *jcr);
62 static int missing_handler(void *ctx, int num_fields, char **row);
66 * Called here before the job is run to do the job
69 bool do_verify_init(JCR *jcr)
71 if (!allow_duplicate_job(jcr)) {
74 switch (jcr->get_JobLevel()) {
76 case L_VERIFY_CATALOG:
77 case L_VERIFY_DISK_TO_CATALOG:
81 case L_VERIFY_VOLUME_TO_CATALOG:
87 Jmsg2(jcr, M_FATAL, 0, _("Unimplemented Verify level %d(%c)\n"), jcr->get_JobLevel(),
96 * Do a verification of the specified files against the Catlaog
98 * Returns: false on failure
101 bool do_verify(JCR *jcr)
108 JobId_t verify_jobid = 0;
111 free_wstorage(jcr); /* we don't write */
113 memset(&jcr->previous_jr, 0, sizeof(jcr->previous_jr));
116 * Find JobId of last job that ran. Note, we do this when
117 * the job actually starts running, not at schedule time,
118 * so that we find the last job that terminated before
119 * this job runs rather than before it is scheduled. This
120 * permits scheduling a Backup and Verify at the same time,
121 * but with the Verify at a lower priority.
123 * For VERIFY_CATALOG we want the JobId of the last INIT.
124 * For VERIFY_VOLUME_TO_CATALOG, we want the JobId of the
127 if (jcr->get_JobLevel() == L_VERIFY_CATALOG ||
128 jcr->get_JobLevel() == L_VERIFY_VOLUME_TO_CATALOG ||
129 jcr->get_JobLevel() == L_VERIFY_DISK_TO_CATALOG) {
130 memcpy(&jr, &jcr->jr, sizeof(jr));
131 if (jcr->verify_job &&
132 (jcr->get_JobLevel() == L_VERIFY_VOLUME_TO_CATALOG ||
133 jcr->get_JobLevel() == L_VERIFY_DISK_TO_CATALOG)) {
134 Name = jcr->verify_job->name();
138 Dmsg1(100, "find last jobid for: %s\n", NPRT(Name));
139 if (!db_find_last_jobid(jcr, jcr->db, Name, &jr)) {
140 if (jcr->get_JobLevel() == L_VERIFY_CATALOG) {
141 Jmsg(jcr, M_FATAL, 0, _(
142 "Unable to find JobId of previous InitCatalog Job.\n"
143 "Please run a Verify with Level=InitCatalog before\n"
144 "running the current Job.\n"));
146 Jmsg(jcr, M_FATAL, 0, _(
147 "Unable to find JobId of previous Job for this client.\n"));
151 verify_jobid = jr.JobId;
152 Dmsg1(100, "Last full jobid=%d\n", verify_jobid);
155 * Now get the job record for the previous backup that interests
156 * us. We use the verify_jobid that we found above.
158 if (jcr->get_JobLevel() == L_VERIFY_CATALOG ||
159 jcr->get_JobLevel() == L_VERIFY_VOLUME_TO_CATALOG ||
160 jcr->get_JobLevel() == L_VERIFY_DISK_TO_CATALOG) {
161 jcr->previous_jr.JobId = verify_jobid;
162 if (!db_get_job_record(jcr, jcr->db, &jcr->previous_jr)) {
163 Jmsg(jcr, M_FATAL, 0, _("Could not get job record for previous Job. ERR=%s"),
164 db_strerror(jcr->db));
167 if (!(jcr->previous_jr.JobStatus == JS_Terminated ||
168 jcr->previous_jr.JobStatus == JS_Warnings)) {
169 Jmsg(jcr, M_FATAL, 0, _("Last Job %d did not terminate normally. JobStatus=%c\n"),
170 verify_jobid, jcr->previous_jr.JobStatus);
173 Jmsg(jcr, M_INFO, 0, _("Verifying against JobId=%d Job=%s\n"),
174 jcr->previous_jr.JobId, jcr->previous_jr.Job);
178 * If we are verifying a Volume, we need the Storage
179 * daemon, so open a connection, otherwise, just
180 * create a dummy authorization key (passed to
181 * File daemon but not used).
183 if (jcr->get_JobLevel() == L_VERIFY_VOLUME_TO_CATALOG) {
186 * Note: negative status is an error, zero status, means
187 * no files were backed up, so skip calling SD and
190 stat = create_restore_bootstrap_file(jcr);
191 if (stat < 0) { /* error */
193 } else if (stat == 0) { /* No files, nothing to do */
194 verify_cleanup(jcr, JS_Terminated); /* clean up */
195 return true; /* get out */
198 jcr->sd_auth_key = bstrdup("dummy"); /* dummy Storage daemon key */
201 if (jcr->get_JobLevel() == L_VERIFY_DISK_TO_CATALOG && jcr->verify_job) {
202 jcr->fileset = jcr->verify_job->fileset;
204 Dmsg2(100, "ClientId=%u JobLevel=%c\n", jcr->previous_jr.ClientId, jcr->get_JobLevel());
206 if (!db_update_job_start_record(jcr, jcr->db, &jcr->jr)) {
207 Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
211 /* Print Job Start message */
212 Jmsg(jcr, M_INFO, 0, _("Start Verify JobId=%s Level=%s Job=%s\n"),
213 edit_uint64(jcr->JobId, ed1), level_to_str(jcr->get_JobLevel()), jcr->Job);
215 if (jcr->get_JobLevel() == L_VERIFY_VOLUME_TO_CATALOG) {
218 * Start conversation with Storage daemon
220 set_jcr_job_status(jcr, JS_Blocked);
221 if (!connect_to_storage_daemon(jcr, 10, SDConnectTimeout, 1)) {
225 * Now start a job with the Storage daemon
227 if (!start_storage_daemon_job(jcr, jcr->rstorage, NULL)) {
230 sd = jcr->store_bsock;
232 * Send the bootstrap file -- what Volumes/files to restore
234 if (!send_bootstrap_file(jcr, sd) ||
235 !response(jcr, sd, OKbootstrap, "Bootstrap", DISPLAY_ERROR)) {
238 if (!sd->fsend("run")) {
242 * Now start a Storage daemon message thread
244 if (!start_storage_daemon_message_thread(jcr)) {
247 Dmsg0(50, "Storage daemon connection OK\n");
251 * OK, now connect to the File daemon
252 * and ask him for the files.
254 set_jcr_job_status(jcr, JS_Blocked);
255 if (!connect_to_file_daemon(jcr, 10, FDConnectTimeout, 1)) {
259 set_jcr_job_status(jcr, JS_Running);
260 fd = jcr->file_bsock;
263 Dmsg0(30, ">filed: Send include list\n");
264 if (!send_include_list(jcr)) {
268 Dmsg0(30, ">filed: Send exclude list\n");
269 if (!send_exclude_list(jcr)) {
274 * Send Level command to File daemon, as well
275 * as the Storage address if appropriate.
277 switch (jcr->get_JobLevel()) {
281 case L_VERIFY_CATALOG:
284 case L_VERIFY_VOLUME_TO_CATALOG:
286 * send Storage daemon address to the File daemon
288 if (jcr->rstore->SDDport == 0) {
289 jcr->rstore->SDDport = jcr->rstore->SDport;
291 bnet_fsend(fd, storaddr, jcr->rstore->address,
292 jcr->rstore->SDDport, jcr->sd_auth_key);
293 if (!response(jcr, fd, OKstore, "Storage", DISPLAY_ERROR)) {
297 if (!jcr->RestoreBootstrap) {
298 Jmsg0(jcr, M_FATAL, 0, _("Deprecated feature ... use bootstrap.\n"));
307 case L_VERIFY_DISK_TO_CATALOG:
308 level="disk_to_catalog";
311 Jmsg2(jcr, M_FATAL, 0, _("Unimplemented Verify level %d(%c)\n"), jcr->get_JobLevel(),
312 jcr->get_JobLevel());
316 if (!send_runscripts_commands(jcr)) {
321 * Send verify command/level to File daemon
323 fd->fsend(verifycmd, level);
324 if (!response(jcr, fd, OKverify, "Verify", DISPLAY_ERROR)) {
329 * Now get data back from File daemon and
330 * compare it to the catalog or store it in the
331 * catalog depending on the run type.
333 /* Compare to catalog */
334 switch (jcr->get_JobLevel()) {
335 case L_VERIFY_CATALOG:
336 Dmsg0(10, "Verify level=catalog\n");
337 jcr->sd_msg_thread_done = true; /* no SD msg thread, so it is done */
338 jcr->SDJobStatus = JS_Terminated;
339 get_attributes_and_compare_to_catalog(jcr, jcr->previous_jr.JobId);
342 case L_VERIFY_VOLUME_TO_CATALOG:
343 Dmsg0(10, "Verify level=volume\n");
344 get_attributes_and_compare_to_catalog(jcr, jcr->previous_jr.JobId);
347 case L_VERIFY_DISK_TO_CATALOG:
348 Dmsg0(10, "Verify level=disk_to_catalog\n");
349 jcr->sd_msg_thread_done = true; /* no SD msg thread, so it is done */
350 jcr->SDJobStatus = JS_Terminated;
351 get_attributes_and_compare_to_catalog(jcr, jcr->previous_jr.JobId);
356 Dmsg0(10, "Verify level=init\n");
357 jcr->sd_msg_thread_done = true; /* no SD msg thread, so it is done */
358 jcr->SDJobStatus = JS_Terminated;
359 get_attributes_and_put_in_catalog(jcr);
360 db_end_transaction(jcr, jcr->db); /* terminate any open transaction */
361 db_write_batch_file_records(jcr);
365 Jmsg1(jcr, M_FATAL, 0, _("Unimplemented verify level %d\n"), jcr->get_JobLevel());
369 stat = wait_for_job_termination(jcr);
370 verify_cleanup(jcr, stat);
379 * Release resources allocated during backup.
382 void verify_cleanup(JCR *jcr, int TermCode)
384 char sdt[50], edt[50];
385 char ec1[30], ec2[30];
386 char term_code[100], fd_term_msg[100], sd_term_msg[100];
387 const char *term_msg;
392 // Dmsg1(100, "Enter verify_cleanup() TermCod=%d\n", TermCode);
394 Dmsg3(900, "JobLevel=%c Expected=%u JobFiles=%u\n", jcr->get_JobLevel(),
395 jcr->ExpectedFiles, jcr->JobFiles);
396 if (jcr->get_JobLevel() == L_VERIFY_VOLUME_TO_CATALOG &&
397 jcr->ExpectedFiles != jcr->JobFiles) {
398 TermCode = JS_ErrorTerminated;
401 JobId = jcr->jr.JobId;
403 update_job_end(jcr, TermCode);
405 if (job_canceled(jcr)) {
406 cancel_storage_daemon_job(jcr);
409 if (jcr->unlink_bsr && jcr->RestoreBootstrap) {
410 unlink(jcr->RestoreBootstrap);
411 jcr->unlink_bsr = false;
414 msg_type = M_INFO; /* by default INFO message */
417 term_msg = _("Verify OK");
420 case JS_ErrorTerminated:
421 term_msg = _("*** Verify Error ***");
422 msg_type = M_ERROR; /* Generate error message */
425 term_msg = _("Verify warnings");
428 term_msg = _("Verify Canceled");
431 term_msg = _("Verify Differences");
434 term_msg = term_code;
435 bsnprintf(term_code, sizeof(term_code),
436 _("Inappropriate term code: %d %c\n"), TermCode, TermCode);
439 bstrftimes(sdt, sizeof(sdt), jcr->jr.StartTime);
440 bstrftimes(edt, sizeof(edt), jcr->jr.EndTime);
441 if (jcr->verify_job) {
442 Name = jcr->verify_job->hdr.name;
447 jobstatus_to_ascii(jcr->FDJobStatus, fd_term_msg, sizeof(fd_term_msg));
448 if (jcr->get_JobLevel() == L_VERIFY_VOLUME_TO_CATALOG) {
449 jobstatus_to_ascii(jcr->SDJobStatus, sd_term_msg, sizeof(sd_term_msg));
450 Jmsg(jcr, msg_type, 0, _("%s %s %s (%s): %s\n"
451 " Build OS: %s %s %s\n"
455 " Verify Level: %s\n"
457 " Verify JobId: %d\n"
461 " Files Expected: %s\n"
462 " Files Examined: %s\n"
463 " Non-fatal FD errors: %d\n"
464 " FD termination status: %s\n"
465 " SD termination status: %s\n"
466 " Termination: %s\n\n"),
467 BACULA, my_name, VERSION, LSMDATE, edt,
468 HOST_OS, DISTNAME, DISTVER,
471 jcr->fileset->hdr.name,
472 level_to_str(jcr->get_JobLevel()),
473 jcr->client->hdr.name,
474 jcr->previous_jr.JobId,
478 edit_uint64_with_commas(jcr->ExpectedFiles, ec1),
479 edit_uint64_with_commas(jcr->JobFiles, ec2),
485 Jmsg(jcr, msg_type, 0, _("%s %s %s (%s): %s\n"
490 " Verify Level: %s\n"
492 " Verify JobId: %d\n"
496 " Files Examined: %s\n"
497 " Non-fatal FD errors: %d\n"
498 " FD termination status: %s\n"
499 " Termination: %s\n\n"),
500 BACULA, my_name, VERSION, LSMDATE, edt,
501 HOST_OS, DISTNAME, DISTVER,
504 jcr->fileset->hdr.name,
505 level_to_str(jcr->get_JobLevel()),
507 jcr->previous_jr.JobId,
511 edit_uint64_with_commas(jcr->JobFiles, ec1),
516 Dmsg0(100, "Leave verify_cleanup()\n");
520 * This routine is called only during a Verify
522 void get_attributes_and_compare_to_catalog(JCR *jcr, JobId_t JobId)
527 struct stat statf; /* file stat */
528 struct stat statc; /* catalog stat */
530 POOLMEM *fname = get_pool_memory(PM_MESSAGE);
531 int do_Digest = CRYPTO_DIGEST_NONE;
532 int32_t file_index = 0;
534 memset(&fdbr, 0, sizeof(FILE_DBR));
535 fd = jcr->file_bsock;
539 Dmsg0(20, "bdird: waiting to receive file attributes\n");
541 * Get Attributes and Signature from File daemon
545 * Options or Digest (MD5/SHA1)
550 while ((n=bget_dirmsg(fd)) >= 0 && !job_canceled(jcr)) {
553 char Opts_Digest[MAXSTRING]; /* Verify Opts or MD5/SHA1 digest */
555 if (job_canceled(jcr)) {
558 fname = check_pool_memory_size(fname, fd->msglen);
559 jcr->fname = check_pool_memory_size(jcr->fname, fd->msglen);
560 Dmsg1(200, "Atts+Digest=%s\n", fd->msg);
561 if ((len = sscanf(fd->msg, "%ld %d %100s", &file_index, &stream,
563 Jmsg3(jcr, M_FATAL, 0, _("bird<filed: bad attributes, expected 3 fields got %d\n"
564 " mslen=%d msg=%s\n"), len, fd->msglen, fd->msg);
568 * We read the Options or Signature into fname
569 * to prevent overrun, now copy it to proper location.
571 bstrncpy(Opts_Digest, fname, sizeof(Opts_Digest));
573 skip_nonspaces(&p); /* skip FileIndex */
575 skip_nonspaces(&p); /* skip Stream */
577 skip_nonspaces(&p); /* skip Opts_Digest */
578 p++; /* skip space */
581 *fn++ = *p++; /* copy filename */
583 *fn = *p++; /* term filename and point to attribs */
586 * Got attributes stream, decode it
588 if (stream == STREAM_UNIX_ATTRIBUTES || stream == STREAM_UNIX_ATTRIBUTES_EX) {
589 int32_t LinkFIf, LinkFIc;
590 Dmsg2(400, "file_index=%d attr=%s\n", file_index, attr);
592 jcr->FileIndex = file_index; /* remember attribute file_index */
593 jcr->previous_jr.FileIndex = file_index;
594 decode_stat(attr, &statf, &LinkFIf); /* decode file stat packet */
595 do_Digest = CRYPTO_DIGEST_NONE;
596 jcr->fn_printed = false;
597 pm_strcpy(jcr->fname, fname); /* move filename into JCR */
599 Dmsg2(040, "dird<filed: stream=%d %s\n", stream, jcr->fname);
600 Dmsg1(020, "dird<filed: attr=%s\n", attr);
603 * Find equivalent record in the database
606 if (!db_get_file_attributes_record(jcr, jcr->db, jcr->fname,
607 &jcr->previous_jr, &fdbr)) {
608 Jmsg(jcr, M_INFO, 0, _("New file: %s\n"), jcr->fname);
609 Dmsg1(020, _("File not in catalog: %s\n"), jcr->fname);
610 set_jcr_job_status(jcr, JS_Differences);
614 * mark file record as visited by stuffing the
615 * current JobId, which is unique, into the MarkId field.
617 db_mark_file_record(jcr, jcr->db, fdbr.FileId, jcr->JobId);
620 Dmsg3(400, "Found %s in catalog. inx=%d Opts=%s\n", jcr->fname,
621 file_index, Opts_Digest);
622 decode_stat(fdbr.LStat, &statc, &LinkFIc); /* decode catalog stat */
624 * Loop over options supplied by user and verify the
625 * fields he requests.
627 for (p=Opts_Digest; *p; p++) {
628 char ed1[30], ed2[30];
630 case 'i': /* compare INODEs */
631 if (statc.st_ino != statf.st_ino) {
633 Jmsg(jcr, M_INFO, 0, _(" st_ino differ. Cat: %s File: %s\n"),
634 edit_uint64((uint64_t)statc.st_ino, ed1),
635 edit_uint64((uint64_t)statf.st_ino, ed2));
636 set_jcr_job_status(jcr, JS_Differences);
639 case 'p': /* permissions bits */
640 if (statc.st_mode != statf.st_mode) {
642 Jmsg(jcr, M_INFO, 0, _(" st_mode differ. Cat: %x File: %x\n"),
643 (uint32_t)statc.st_mode, (uint32_t)statf.st_mode);
644 set_jcr_job_status(jcr, JS_Differences);
647 case 'n': /* number of links */
648 if (statc.st_nlink != statf.st_nlink) {
650 Jmsg(jcr, M_INFO, 0, _(" st_nlink differ. Cat: %d File: %d\n"),
651 (uint32_t)statc.st_nlink, (uint32_t)statf.st_nlink);
652 set_jcr_job_status(jcr, JS_Differences);
655 case 'u': /* user id */
656 if (statc.st_uid != statf.st_uid) {
658 Jmsg(jcr, M_INFO, 0, _(" st_uid differ. Cat: %u File: %u\n"),
659 (uint32_t)statc.st_uid, (uint32_t)statf.st_uid);
660 set_jcr_job_status(jcr, JS_Differences);
663 case 'g': /* group id */
664 if (statc.st_gid != statf.st_gid) {
666 Jmsg(jcr, M_INFO, 0, _(" st_gid differ. Cat: %u File: %u\n"),
667 (uint32_t)statc.st_gid, (uint32_t)statf.st_gid);
668 set_jcr_job_status(jcr, JS_Differences);
672 if (statc.st_size != statf.st_size) {
674 Jmsg(jcr, M_INFO, 0, _(" st_size differ. Cat: %s File: %s\n"),
675 edit_uint64((uint64_t)statc.st_size, ed1),
676 edit_uint64((uint64_t)statf.st_size, ed2));
677 set_jcr_job_status(jcr, JS_Differences);
680 case 'a': /* access time */
681 if (statc.st_atime != statf.st_atime) {
683 Jmsg(jcr, M_INFO, 0, _(" st_atime differs\n"));
684 set_jcr_job_status(jcr, JS_Differences);
688 if (statc.st_mtime != statf.st_mtime) {
690 Jmsg(jcr, M_INFO, 0, _(" st_mtime differs\n"));
691 set_jcr_job_status(jcr, JS_Differences);
694 case 'c': /* ctime */
695 if (statc.st_ctime != statf.st_ctime) {
697 Jmsg(jcr, M_INFO, 0, _(" st_ctime differs\n"));
698 set_jcr_job_status(jcr, JS_Differences);
701 case 'd': /* file size decrease */
702 if (statc.st_size > statf.st_size) {
704 Jmsg(jcr, M_INFO, 0, _(" st_size decrease. Cat: %s File: %s\n"),
705 edit_uint64((uint64_t)statc.st_size, ed1),
706 edit_uint64((uint64_t)statf.st_size, ed2));
707 set_jcr_job_status(jcr, JS_Differences);
710 case '5': /* compare MD5 */
711 Dmsg1(500, "set Do_MD5 for %s\n", jcr->fname);
712 do_Digest = CRYPTO_DIGEST_MD5;
714 case '1': /* compare SHA1 */
715 do_Digest = CRYPTO_DIGEST_SHA1;
724 * Got Digest Signature from Storage daemon
725 * It came across in the Opts_Digest field.
727 } else if (crypto_digest_stream_type(stream) != CRYPTO_DIGEST_NONE) {
728 Dmsg2(400, "stream=Digest inx=%d Digest=%s\n", file_index, Opts_Digest);
730 * When ever we get a digest it MUST have been
731 * preceded by an attributes record, which sets attr_file_index
733 if (jcr->FileIndex != (uint32_t)file_index) {
734 Jmsg2(jcr, M_FATAL, 0, _("MD5/SHA1 index %d not same as attributes %d\n"),
735 file_index, jcr->FileIndex);
738 if (do_Digest != CRYPTO_DIGEST_NONE) {
739 db_escape_string(jcr, jcr->db, buf, Opts_Digest, strlen(Opts_Digest));
740 if (strcmp(buf, fdbr.Digest) != 0) {
742 Jmsg(jcr, M_INFO, 0, _(" %s differs. File=%s Cat=%s\n"),
743 stream_to_ascii(stream), buf, fdbr.Digest);
744 set_jcr_job_status(jcr, JS_Differences);
746 do_Digest = CRYPTO_DIGEST_NONE;
749 jcr->JobFiles = file_index;
751 if (is_bnet_error(fd)) {
753 Jmsg2(jcr, M_FATAL, 0, _("bdird<filed: bad attributes from filed n=%d : %s\n"),
758 /* Now find all the files that are missing -- i.e. all files in
759 * the database where the MarkId != current JobId
761 jcr->fn_printed = false;
762 bsnprintf(buf, sizeof(buf),
763 "SELECT Path.Path,Filename.Name FROM File,Path,Filename "
764 "WHERE File.JobId=%d AND File.FileIndex > 0 "
765 "AND File.MarkId!=%d AND File.PathId=Path.PathId "
766 "AND File.FilenameId=Filename.FilenameId",
768 /* missing_handler is called for each file found */
769 db_sql_query(jcr->db, buf, missing_handler, (void *)jcr);
770 if (jcr->fn_printed) {
771 set_jcr_job_status(jcr, JS_Differences);
773 free_pool_memory(fname);
777 * We are called here for each record that matches the above
778 * SQL query -- that is for each file contained in the Catalog
779 * that was not marked earlier. This means that the file in
780 * question is a missing file (in the Catalog but not on Disk).
782 static int missing_handler(void *ctx, int num_fields, char **row)
784 JCR *jcr = (JCR *)ctx;
786 if (job_canceled(jcr)) {
789 if (!jcr->fn_printed) {
790 Qmsg(jcr, M_WARNING, 0, _("The following files are in the Catalog but not on %s:\n"),
791 jcr->get_JobLevel() == L_VERIFY_VOLUME_TO_CATALOG ? "the Volume(s)" : "disk");
792 jcr->fn_printed = true;
794 Qmsg(jcr, M_INFO, 0, " %s%s\n", row[0]?row[0]:"", row[1]?row[1]:"");
800 * Print filename for verify
802 static void prt_fname(JCR *jcr)
804 if (!jcr->fn_printed) {
805 Jmsg(jcr, M_INFO, 0, _("File: %s\n"), jcr->fname);
806 jcr->fn_printed = true;