2 Bacula® - The Network Backup Solution
4 Copyright (C) 2000-2011 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 three of the GNU Affero 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 Affero 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
46 #include "findlib/find.h"
48 /* Commands sent to File daemon */
49 static char verifycmd[] = "verify level=%s\n";
50 static char storaddr[] = "storage address=%s port=%d ssl=0 Authorization=%s\n";
52 /* Responses received from File daemon */
53 static char OKverify[] = "2000 OK verify\n";
54 static char OKstore[] = "2000 OK storage\n";
56 /* Responses received from the Storage daemon */
57 static char OKbootstrap[] = "3000 OK bootstrap\n";
59 /* Forward referenced functions */
60 static void prt_fname(JCR *jcr);
61 static int missing_handler(void *ctx, int num_fields, char **row);
65 * Called here before the job is run to do the job
68 bool do_verify_init(JCR *jcr)
70 if (!allow_duplicate_job(jcr)) {
73 switch (jcr->getJobLevel()) {
75 case L_VERIFY_CATALOG:
76 case L_VERIFY_DISK_TO_CATALOG:
80 case L_VERIFY_VOLUME_TO_CATALOG:
86 Jmsg2(jcr, M_FATAL, 0, _("Unimplemented Verify level %d(%c)\n"), jcr->getJobLevel(),
95 * Do a verification of the specified files against the Catlaog
97 * Returns: false on failure
100 bool do_verify(JCR *jcr)
107 JobId_t verify_jobid = 0;
110 free_wstorage(jcr); /* we don't write */
112 memset(&jcr->previous_jr, 0, sizeof(jcr->previous_jr));
115 * Find JobId of last job that ran. Note, we do this when
116 * the job actually starts running, not at schedule time,
117 * so that we find the last job that terminated before
118 * this job runs rather than before it is scheduled. This
119 * permits scheduling a Backup and Verify at the same time,
120 * but with the Verify at a lower priority.
122 * For VERIFY_CATALOG we want the JobId of the last INIT.
123 * For VERIFY_VOLUME_TO_CATALOG, we want the JobId of the
126 if (jcr->getJobLevel() == L_VERIFY_CATALOG ||
127 jcr->getJobLevel() == L_VERIFY_VOLUME_TO_CATALOG ||
128 jcr->getJobLevel() == L_VERIFY_DISK_TO_CATALOG) {
129 memcpy(&jr, &jcr->jr, sizeof(jr));
130 if (jcr->verify_job &&
131 (jcr->getJobLevel() == L_VERIFY_VOLUME_TO_CATALOG ||
132 jcr->getJobLevel() == L_VERIFY_DISK_TO_CATALOG)) {
133 Name = jcr->verify_job->name();
137 Dmsg1(100, "find last jobid for: %s\n", NPRT(Name));
139 /* see if user supplied a jobid= as run argument or from menu */
140 if (jcr->RestoreJobId) {
141 verify_jobid = jcr->RestoreJobId;
142 Dmsg1(100, "Supplied jobid=%d\n", verify_jobid);
145 if (!db_find_last_jobid(jcr, jcr->db, Name, &jr)) {
146 if (jcr->getJobLevel() == L_VERIFY_CATALOG) {
147 Jmsg(jcr, M_FATAL, 0, _(
148 "Unable to find JobId of previous InitCatalog Job.\n"
149 "Please run a Verify with Level=InitCatalog before\n"
150 "running the current Job.\n"));
152 Jmsg(jcr, M_FATAL, 0, _(
153 "Unable to find JobId of previous Job for this client.\n"));
157 verify_jobid = jr.JobId;
159 Dmsg1(100, "Last full jobid=%d\n", verify_jobid);
162 * Now get the job record for the previous backup that interests
163 * us. We use the verify_jobid that we found above.
165 if (jcr->getJobLevel() == L_VERIFY_CATALOG ||
166 jcr->getJobLevel() == L_VERIFY_VOLUME_TO_CATALOG ||
167 jcr->getJobLevel() == L_VERIFY_DISK_TO_CATALOG) {
168 jcr->previous_jr.JobId = verify_jobid;
169 if (!db_get_job_record(jcr, jcr->db, &jcr->previous_jr)) {
170 Jmsg(jcr, M_FATAL, 0, _("Could not get job record for previous Job. ERR=%s"),
171 db_strerror(jcr->db));
174 if (!(jcr->previous_jr.JobStatus == JS_Terminated ||
175 jcr->previous_jr.JobStatus == JS_Warnings)) {
176 Jmsg(jcr, M_FATAL, 0, _("Last Job %d did not terminate normally. JobStatus=%c\n"),
177 verify_jobid, jcr->previous_jr.JobStatus);
180 Jmsg(jcr, M_INFO, 0, _("Verifying against JobId=%d Job=%s\n"),
181 jcr->previous_jr.JobId, jcr->previous_jr.Job);
185 * If we are verifying a Volume, we need the Storage
186 * daemon, so open a connection, otherwise, just
187 * create a dummy authorization key (passed to
188 * File daemon but not used).
190 if (jcr->getJobLevel() == L_VERIFY_VOLUME_TO_CATALOG) {
193 * Note: negative status is an error, zero status, means
194 * no files were backed up, so skip calling SD and
197 stat = create_restore_bootstrap_file(jcr);
198 if (stat < 0) { /* error */
200 } else if (stat == 0) { /* No files, nothing to do */
201 verify_cleanup(jcr, JS_Terminated); /* clean up */
202 return true; /* get out */
205 jcr->sd_auth_key = bstrdup("dummy"); /* dummy Storage daemon key */
208 if (jcr->getJobLevel() == L_VERIFY_DISK_TO_CATALOG && jcr->verify_job) {
209 jcr->fileset = jcr->verify_job->fileset;
211 Dmsg2(100, "ClientId=%u JobLevel=%c\n", jcr->previous_jr.ClientId, jcr->getJobLevel());
213 if (!db_update_job_start_record(jcr, jcr->db, &jcr->jr)) {
214 Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
218 /* Print Job Start message */
219 Jmsg(jcr, M_INFO, 0, _("Start Verify JobId=%s Level=%s Job=%s\n"),
220 edit_uint64(jcr->JobId, ed1), level_to_str(jcr->getJobLevel()), jcr->Job);
222 if (jcr->getJobLevel() == L_VERIFY_VOLUME_TO_CATALOG) {
225 * Start conversation with Storage daemon
227 jcr->setJobStatus(JS_Blocked);
228 if (!connect_to_storage_daemon(jcr, 10, SDConnectTimeout, 1)) {
232 * Now start a job with the Storage daemon
234 if (!start_storage_daemon_job(jcr, jcr->rstorage, NULL)) {
237 sd = jcr->store_bsock;
239 * Send the bootstrap file -- what Volumes/files to restore
241 if (!send_bootstrap_file(jcr, sd) ||
242 !response(jcr, sd, OKbootstrap, "Bootstrap", DISPLAY_ERROR)) {
245 if (!sd->fsend("run")) {
249 * Now start a Storage daemon message thread
251 if (!start_storage_daemon_message_thread(jcr)) {
254 Dmsg0(50, "Storage daemon connection OK\n");
258 * OK, now connect to the File daemon
259 * and ask him for the files.
261 jcr->setJobStatus(JS_Blocked);
262 if (!connect_to_file_daemon(jcr, 10, FDConnectTimeout, 1)) {
266 jcr->setJobStatus(JS_Running);
267 fd = jcr->file_bsock;
270 Dmsg0(30, ">filed: Send include list\n");
271 if (!send_include_list(jcr)) {
275 Dmsg0(30, ">filed: Send exclude list\n");
276 if (!send_exclude_list(jcr)) {
281 * Send Level command to File daemon, as well
282 * as the Storage address if appropriate.
284 switch (jcr->getJobLevel()) {
288 case L_VERIFY_CATALOG:
291 case L_VERIFY_VOLUME_TO_CATALOG:
293 * send Storage daemon address to the File daemon
295 if (jcr->rstore->SDDport == 0) {
296 jcr->rstore->SDDport = jcr->rstore->SDport;
298 bnet_fsend(fd, storaddr, jcr->rstore->address,
299 jcr->rstore->SDDport, jcr->sd_auth_key);
300 if (!response(jcr, fd, OKstore, "Storage", DISPLAY_ERROR)) {
304 if (!jcr->RestoreBootstrap) {
305 Jmsg0(jcr, M_FATAL, 0, _("Deprecated feature ... use bootstrap.\n"));
314 case L_VERIFY_DISK_TO_CATALOG:
315 level="disk_to_catalog";
318 Jmsg2(jcr, M_FATAL, 0, _("Unimplemented Verify level %d(%c)\n"), jcr->getJobLevel(),
323 if (!send_runscripts_commands(jcr)) {
328 * Send verify command/level to File daemon
330 fd->fsend(verifycmd, level);
331 if (!response(jcr, fd, OKverify, "Verify", DISPLAY_ERROR)) {
336 * Now get data back from File daemon and
337 * compare it to the catalog or store it in the
338 * catalog depending on the run type.
340 /* Compare to catalog */
341 switch (jcr->getJobLevel()) {
342 case L_VERIFY_CATALOG:
343 Dmsg0(10, "Verify level=catalog\n");
344 jcr->sd_msg_thread_done = true; /* no SD msg thread, so it is done */
345 jcr->SDJobStatus = JS_Terminated;
346 get_attributes_and_compare_to_catalog(jcr, jcr->previous_jr.JobId);
349 case L_VERIFY_VOLUME_TO_CATALOG:
350 Dmsg0(10, "Verify level=volume\n");
351 get_attributes_and_compare_to_catalog(jcr, jcr->previous_jr.JobId);
354 case L_VERIFY_DISK_TO_CATALOG:
355 Dmsg0(10, "Verify level=disk_to_catalog\n");
356 jcr->sd_msg_thread_done = true; /* no SD msg thread, so it is done */
357 jcr->SDJobStatus = JS_Terminated;
358 get_attributes_and_compare_to_catalog(jcr, jcr->previous_jr.JobId);
363 Dmsg0(10, "Verify level=init\n");
364 jcr->sd_msg_thread_done = true; /* no SD msg thread, so it is done */
365 jcr->SDJobStatus = JS_Terminated;
366 get_attributes_and_put_in_catalog(jcr);
367 db_end_transaction(jcr, jcr->db); /* terminate any open transaction */
368 db_write_batch_file_records(jcr);
372 Jmsg1(jcr, M_FATAL, 0, _("Unimplemented verify level %d\n"), jcr->getJobLevel());
376 stat = wait_for_job_termination(jcr);
377 verify_cleanup(jcr, stat);
386 * Release resources allocated during backup.
389 void verify_cleanup(JCR *jcr, int TermCode)
391 char sdt[50], edt[50];
392 char ec1[30], ec2[30];
393 char term_code[100], fd_term_msg[100], sd_term_msg[100];
394 const char *term_msg;
398 // Dmsg1(100, "Enter verify_cleanup() TermCod=%d\n", TermCode);
400 Dmsg3(900, "JobLevel=%c Expected=%u JobFiles=%u\n", jcr->getJobLevel(),
401 jcr->ExpectedFiles, jcr->JobFiles);
402 if (jcr->getJobLevel() == L_VERIFY_VOLUME_TO_CATALOG &&
403 jcr->ExpectedFiles != jcr->JobFiles) {
404 TermCode = JS_ErrorTerminated;
407 update_job_end(jcr, TermCode);
409 if (job_canceled(jcr)) {
410 cancel_storage_daemon_job(jcr);
413 if (jcr->unlink_bsr && jcr->RestoreBootstrap) {
414 unlink(jcr->RestoreBootstrap);
415 jcr->unlink_bsr = false;
418 msg_type = M_INFO; /* by default INFO message */
421 term_msg = _("Verify OK");
424 case JS_ErrorTerminated:
425 term_msg = _("*** Verify Error ***");
426 msg_type = M_ERROR; /* Generate error message */
429 term_msg = _("Verify warnings");
432 term_msg = _("Verify Canceled");
435 term_msg = _("Verify Differences");
438 term_msg = term_code;
439 bsnprintf(term_code, sizeof(term_code),
440 _("Inappropriate term code: %d %c\n"), TermCode, TermCode);
443 bstrftimes(sdt, sizeof(sdt), jcr->jr.StartTime);
444 bstrftimes(edt, sizeof(edt), jcr->jr.EndTime);
445 if (jcr->verify_job) {
446 Name = jcr->verify_job->hdr.name;
451 jobstatus_to_ascii(jcr->FDJobStatus, fd_term_msg, sizeof(fd_term_msg));
452 if (jcr->getJobLevel() == L_VERIFY_VOLUME_TO_CATALOG) {
453 jobstatus_to_ascii(jcr->SDJobStatus, sd_term_msg, sizeof(sd_term_msg));
454 Jmsg(jcr, msg_type, 0, _("%s %s %s (%s):\n"
455 " Build OS: %s %s %s\n"
459 " Verify Level: %s\n"
461 " Verify JobId: %d\n"
465 " Files Expected: %s\n"
466 " Files Examined: %s\n"
467 " Non-fatal FD errors: %d\n"
468 " FD termination status: %s\n"
469 " SD termination status: %s\n"
470 " Termination: %s\n\n"),
471 BACULA, my_name, VERSION, LSMDATE,
472 HOST_OS, DISTNAME, DISTVER,
475 jcr->fileset->hdr.name,
476 level_to_str(jcr->getJobLevel()),
477 jcr->client->hdr.name,
478 jcr->previous_jr.JobId,
482 edit_uint64_with_commas(jcr->ExpectedFiles, ec1),
483 edit_uint64_with_commas(jcr->JobFiles, ec2),
489 Jmsg(jcr, msg_type, 0, _("%s %s %s (%s):\n"
494 " Verify Level: %s\n"
496 " Verify JobId: %d\n"
500 " Files Examined: %s\n"
501 " Non-fatal FD errors: %d\n"
502 " FD termination status: %s\n"
503 " Termination: %s\n\n"),
504 BACULA, my_name, VERSION, LSMDATE,
505 HOST_OS, DISTNAME, DISTVER,
508 jcr->fileset->hdr.name,
509 level_to_str(jcr->getJobLevel()),
511 jcr->previous_jr.JobId,
515 edit_uint64_with_commas(jcr->JobFiles, ec1),
520 Dmsg0(100, "Leave verify_cleanup()\n");
524 * This routine is called only during a Verify
526 void get_attributes_and_compare_to_catalog(JCR *jcr, JobId_t JobId)
531 struct stat statf; /* file stat */
532 struct stat statc; /* catalog stat */
534 POOLMEM *fname = get_pool_memory(PM_MESSAGE);
535 int do_Digest = CRYPTO_DIGEST_NONE;
536 int32_t file_index = 0;
538 memset(&fdbr, 0, sizeof(FILE_DBR));
539 fd = jcr->file_bsock;
543 Dmsg0(20, "bdird: waiting to receive file attributes\n");
545 * Get Attributes and Signature from File daemon
549 * Options or Digest (MD5/SHA1)
554 while ((n=bget_dirmsg(fd)) >= 0 && !job_canceled(jcr)) {
557 char Opts_Digest[MAXSTRING]; /* Verify Opts or MD5/SHA1 digest */
559 if (job_canceled(jcr)) {
562 fname = check_pool_memory_size(fname, fd->msglen);
563 jcr->fname = check_pool_memory_size(jcr->fname, fd->msglen);
564 Dmsg1(200, "Atts+Digest=%s\n", fd->msg);
565 if ((len = sscanf(fd->msg, "%ld %d %100s", &file_index, &stream,
567 Jmsg3(jcr, M_FATAL, 0, _("bird<filed: bad attributes, expected 3 fields got %d\n"
568 " mslen=%d msg=%s\n"), len, fd->msglen, fd->msg);
572 * We read the Options or Signature into fname
573 * to prevent overrun, now copy it to proper location.
575 bstrncpy(Opts_Digest, fname, sizeof(Opts_Digest));
577 skip_nonspaces(&p); /* skip FileIndex */
579 skip_nonspaces(&p); /* skip Stream */
581 skip_nonspaces(&p); /* skip Opts_Digest */
582 p++; /* skip space */
585 *fn++ = *p++; /* copy filename */
587 *fn = *p++; /* term filename and point to attribs */
590 * Got attributes stream, decode it
592 if (stream == STREAM_UNIX_ATTRIBUTES || stream == STREAM_UNIX_ATTRIBUTES_EX) {
593 int32_t LinkFIf, LinkFIc;
594 Dmsg2(400, "file_index=%d attr=%s\n", file_index, attr);
596 jcr->FileIndex = file_index; /* remember attribute file_index */
597 jcr->previous_jr.FileIndex = file_index;
598 decode_stat(attr, &statf, sizeof(statf), &LinkFIf); /* decode file stat packet */
599 do_Digest = CRYPTO_DIGEST_NONE;
600 jcr->fn_printed = false;
601 pm_strcpy(jcr->fname, fname); /* move filename into JCR */
603 Dmsg2(040, "dird<filed: stream=%d %s\n", stream, jcr->fname);
604 Dmsg1(020, "dird<filed: attr=%s\n", attr);
607 * Find equivalent record in the database
610 if (!db_get_file_attributes_record(jcr, jcr->db, jcr->fname,
611 &jcr->previous_jr, &fdbr)) {
612 Jmsg(jcr, M_INFO, 0, _("New file: %s\n"), jcr->fname);
613 Dmsg1(020, _("File not in catalog: %s\n"), jcr->fname);
614 jcr->setJobStatus(JS_Differences);
618 * mark file record as visited by stuffing the
619 * current JobId, which is unique, into the MarkId field.
621 db_mark_file_record(jcr, jcr->db, fdbr.FileId, jcr->JobId);
624 Dmsg3(400, "Found %s in catalog. inx=%d Opts=%s\n", jcr->fname,
625 file_index, Opts_Digest);
626 decode_stat(fdbr.LStat, &statc, sizeof(statc), &LinkFIc); /* decode catalog stat */
628 * Loop over options supplied by user and verify the
629 * fields he requests.
631 for (p=Opts_Digest; *p; p++) {
632 char ed1[30], ed2[30];
634 case 'i': /* compare INODEs */
635 if (statc.st_ino != statf.st_ino) {
637 Jmsg(jcr, M_INFO, 0, _(" st_ino differ. Cat: %s File: %s\n"),
638 edit_uint64((uint64_t)statc.st_ino, ed1),
639 edit_uint64((uint64_t)statf.st_ino, ed2));
640 jcr->setJobStatus(JS_Differences);
643 case 'p': /* permissions bits */
644 if (statc.st_mode != statf.st_mode) {
646 Jmsg(jcr, M_INFO, 0, _(" st_mode differ. Cat: %x File: %x\n"),
647 (uint32_t)statc.st_mode, (uint32_t)statf.st_mode);
648 jcr->setJobStatus(JS_Differences);
651 case 'n': /* number of links */
652 if (statc.st_nlink != statf.st_nlink) {
654 Jmsg(jcr, M_INFO, 0, _(" st_nlink differ. Cat: %d File: %d\n"),
655 (uint32_t)statc.st_nlink, (uint32_t)statf.st_nlink);
656 jcr->setJobStatus(JS_Differences);
659 case 'u': /* user id */
660 if (statc.st_uid != statf.st_uid) {
662 Jmsg(jcr, M_INFO, 0, _(" st_uid differ. Cat: %u File: %u\n"),
663 (uint32_t)statc.st_uid, (uint32_t)statf.st_uid);
664 jcr->setJobStatus(JS_Differences);
667 case 'g': /* group id */
668 if (statc.st_gid != statf.st_gid) {
670 Jmsg(jcr, M_INFO, 0, _(" st_gid differ. Cat: %u File: %u\n"),
671 (uint32_t)statc.st_gid, (uint32_t)statf.st_gid);
672 jcr->setJobStatus(JS_Differences);
676 if (statc.st_size != statf.st_size) {
678 Jmsg(jcr, M_INFO, 0, _(" st_size differ. Cat: %s File: %s\n"),
679 edit_uint64((uint64_t)statc.st_size, ed1),
680 edit_uint64((uint64_t)statf.st_size, ed2));
681 jcr->setJobStatus(JS_Differences);
684 case 'a': /* access time */
685 if (statc.st_atime != statf.st_atime) {
687 Jmsg(jcr, M_INFO, 0, _(" st_atime differs\n"));
688 jcr->setJobStatus(JS_Differences);
692 if (statc.st_mtime != statf.st_mtime) {
694 Jmsg(jcr, M_INFO, 0, _(" st_mtime differs\n"));
695 jcr->setJobStatus(JS_Differences);
698 case 'c': /* ctime */
699 if (statc.st_ctime != statf.st_ctime) {
701 Jmsg(jcr, M_INFO, 0, _(" st_ctime differs\n"));
702 jcr->setJobStatus(JS_Differences);
705 case 'd': /* file size decrease */
706 if (statc.st_size > statf.st_size) {
708 Jmsg(jcr, M_INFO, 0, _(" st_size decrease. Cat: %s File: %s\n"),
709 edit_uint64((uint64_t)statc.st_size, ed1),
710 edit_uint64((uint64_t)statf.st_size, ed2));
711 jcr->setJobStatus(JS_Differences);
714 case '5': /* compare MD5 */
715 Dmsg1(500, "set Do_MD5 for %s\n", jcr->fname);
716 do_Digest = CRYPTO_DIGEST_MD5;
718 case '1': /* compare SHA1 */
719 do_Digest = CRYPTO_DIGEST_SHA1;
728 * Got Digest Signature from Storage daemon
729 * It came across in the Opts_Digest field.
731 } else if (crypto_digest_stream_type(stream) != CRYPTO_DIGEST_NONE) {
732 Dmsg2(400, "stream=Digest inx=%d Digest=%s\n", file_index, Opts_Digest);
734 * When ever we get a digest it MUST have been
735 * preceded by an attributes record, which sets attr_file_index
737 if (jcr->FileIndex != (uint32_t)file_index) {
738 Jmsg2(jcr, M_FATAL, 0, _("MD5/SHA1 index %d not same as attributes %d\n"),
739 file_index, jcr->FileIndex);
742 if (do_Digest != CRYPTO_DIGEST_NONE) {
743 db_escape_string(jcr, jcr->db, buf, Opts_Digest, strlen(Opts_Digest));
744 if (strcmp(buf, fdbr.Digest) != 0) {
746 Jmsg(jcr, M_INFO, 0, _(" %s differs. File=%s Cat=%s\n"),
747 stream_to_ascii(stream), buf, fdbr.Digest);
748 jcr->setJobStatus(JS_Differences);
750 do_Digest = CRYPTO_DIGEST_NONE;
753 jcr->JobFiles = file_index;
755 if (is_bnet_error(fd)) {
757 Jmsg2(jcr, M_FATAL, 0, _("bdird<filed: bad attributes from filed n=%d : %s\n"),
762 /* Now find all the files that are missing -- i.e. all files in
763 * the database where the MarkId != current JobId
765 jcr->fn_printed = false;
766 bsnprintf(buf, sizeof(buf),
767 "SELECT Path.Path,Filename.Name FROM File,Path,Filename "
768 "WHERE File.JobId=%d AND File.FileIndex > 0 "
769 "AND File.MarkId!=%d AND File.PathId=Path.PathId "
770 "AND File.FilenameId=Filename.FilenameId",
772 /* missing_handler is called for each file found */
773 db_sql_query(jcr->db, buf, missing_handler, (void *)jcr);
774 if (jcr->fn_printed) {
775 jcr->setJobStatus(JS_Differences);
779 free_pool_memory(fname);
783 * We are called here for each record that matches the above
784 * SQL query -- that is for each file contained in the Catalog
785 * that was not marked earlier. This means that the file in
786 * question is a missing file (in the Catalog but not on Disk).
788 static int missing_handler(void *ctx, int num_fields, char **row)
790 JCR *jcr = (JCR *)ctx;
792 if (job_canceled(jcr)) {
795 if (!jcr->fn_printed) {
796 Qmsg(jcr, M_WARNING, 0, _("The following files are in the Catalog but not on %s:\n"),
797 jcr->getJobLevel() == L_VERIFY_VOLUME_TO_CATALOG ? "the Volume(s)" : "disk");
798 jcr->fn_printed = true;
800 Qmsg(jcr, M_INFO, 0, " %s%s\n", row[0]?row[0]:"", row[1]?row[1]:"");
806 * Print filename for verify
808 static void prt_fname(JCR *jcr)
810 if (!jcr->fn_printed) {
811 Jmsg(jcr, M_INFO, 0, _("File: %s\n"), jcr->fname);
812 jcr->fn_printed = true;