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\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) {
184 if (!create_restore_bootstrap_file(jcr)) {
188 jcr->sd_auth_key = bstrdup("dummy"); /* dummy Storage daemon key */
191 if (jcr->get_JobLevel() == L_VERIFY_DISK_TO_CATALOG && jcr->verify_job) {
192 jcr->fileset = jcr->verify_job->fileset;
194 Dmsg2(100, "ClientId=%u JobLevel=%c\n", jcr->previous_jr.ClientId, jcr->get_JobLevel());
196 if (!db_update_job_start_record(jcr, jcr->db, &jcr->jr)) {
197 Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
201 /* Print Job Start message */
202 Jmsg(jcr, M_INFO, 0, _("Start Verify JobId=%s Level=%s Job=%s\n"),
203 edit_uint64(jcr->JobId, ed1), level_to_str(jcr->get_JobLevel()), jcr->Job);
205 if (jcr->get_JobLevel() == L_VERIFY_VOLUME_TO_CATALOG) {
208 * Start conversation with Storage daemon
210 set_jcr_job_status(jcr, JS_Blocked);
211 if (!connect_to_storage_daemon(jcr, 10, SDConnectTimeout, 1)) {
215 * Now start a job with the Storage daemon
217 if (!start_storage_daemon_job(jcr, jcr->rstorage, NULL)) {
220 sd = jcr->store_bsock;
222 * Send the bootstrap file -- what Volumes/files to restore
224 if (!send_bootstrap_file(jcr, sd) ||
225 !response(jcr, sd, OKbootstrap, "Bootstrap", DISPLAY_ERROR)) {
228 if (!sd->fsend("run")) {
232 * Now start a Storage daemon message thread
234 if (!start_storage_daemon_message_thread(jcr)) {
237 Dmsg0(50, "Storage daemon connection OK\n");
241 * OK, now connect to the File daemon
242 * and ask him for the files.
244 set_jcr_job_status(jcr, JS_Blocked);
245 if (!connect_to_file_daemon(jcr, 10, FDConnectTimeout, 1)) {
249 set_jcr_job_status(jcr, JS_Running);
250 fd = jcr->file_bsock;
253 Dmsg0(30, ">filed: Send include list\n");
254 if (!send_include_list(jcr)) {
258 Dmsg0(30, ">filed: Send exclude list\n");
259 if (!send_exclude_list(jcr)) {
264 * Send Level command to File daemon, as well
265 * as the Storage address if appropriate.
267 switch (jcr->get_JobLevel()) {
271 case L_VERIFY_CATALOG:
274 case L_VERIFY_VOLUME_TO_CATALOG:
276 * send Storage daemon address to the File daemon
278 if (jcr->rstore->SDDport == 0) {
279 jcr->rstore->SDDport = jcr->rstore->SDport;
281 bnet_fsend(fd, storaddr, jcr->rstore->address, jcr->rstore->SDDport);
282 if (!response(jcr, fd, OKstore, "Storage", DISPLAY_ERROR)) {
286 if (!jcr->RestoreBootstrap) {
287 Jmsg0(jcr, M_FATAL, 0, _("Deprecated feature ... use bootstrap.\n"));
296 case L_VERIFY_DISK_TO_CATALOG:
297 level="disk_to_catalog";
300 Jmsg2(jcr, M_FATAL, 0, _("Unimplemented Verify level %d(%c)\n"), jcr->get_JobLevel(),
301 jcr->get_JobLevel());
305 if (!send_runscripts_commands(jcr)) {
310 * Send verify command/level to File daemon
312 fd->fsend(verifycmd, level);
313 if (!response(jcr, fd, OKverify, "Verify", DISPLAY_ERROR)) {
318 * Now get data back from File daemon and
319 * compare it to the catalog or store it in the
320 * catalog depending on the run type.
322 /* Compare to catalog */
323 switch (jcr->get_JobLevel()) {
324 case L_VERIFY_CATALOG:
325 Dmsg0(10, "Verify level=catalog\n");
326 jcr->sd_msg_thread_done = true; /* no SD msg thread, so it is done */
327 jcr->SDJobStatus = JS_Terminated;
328 get_attributes_and_compare_to_catalog(jcr, jcr->previous_jr.JobId);
331 case L_VERIFY_VOLUME_TO_CATALOG:
332 Dmsg0(10, "Verify level=volume\n");
333 get_attributes_and_compare_to_catalog(jcr, jcr->previous_jr.JobId);
336 case L_VERIFY_DISK_TO_CATALOG:
337 Dmsg0(10, "Verify level=disk_to_catalog\n");
338 jcr->sd_msg_thread_done = true; /* no SD msg thread, so it is done */
339 jcr->SDJobStatus = JS_Terminated;
340 get_attributes_and_compare_to_catalog(jcr, jcr->previous_jr.JobId);
345 Dmsg0(10, "Verify level=init\n");
346 jcr->sd_msg_thread_done = true; /* no SD msg thread, so it is done */
347 jcr->SDJobStatus = JS_Terminated;
348 get_attributes_and_put_in_catalog(jcr);
349 db_end_transaction(jcr, jcr->db); /* terminate any open transaction */
350 db_write_batch_file_records(jcr);
354 Jmsg1(jcr, M_FATAL, 0, _("Unimplemented verify level %d\n"), jcr->get_JobLevel());
358 stat = wait_for_job_termination(jcr);
359 verify_cleanup(jcr, stat);
368 * Release resources allocated during backup.
371 void verify_cleanup(JCR *jcr, int TermCode)
373 char sdt[50], edt[50];
374 char ec1[30], ec2[30];
375 char term_code[100], fd_term_msg[100], sd_term_msg[100];
376 const char *term_msg;
381 // Dmsg1(100, "Enter verify_cleanup() TermCod=%d\n", TermCode);
383 Dmsg3(900, "JobLevel=%c Expected=%u JobFiles=%u\n", jcr->get_JobLevel(),
384 jcr->ExpectedFiles, jcr->JobFiles);
385 if (jcr->get_JobLevel() == L_VERIFY_VOLUME_TO_CATALOG &&
386 jcr->ExpectedFiles != jcr->JobFiles) {
387 TermCode = JS_ErrorTerminated;
390 JobId = jcr->jr.JobId;
392 update_job_end(jcr, TermCode);
394 if (job_canceled(jcr)) {
395 cancel_storage_daemon_job(jcr);
398 if (jcr->unlink_bsr && jcr->RestoreBootstrap) {
399 unlink(jcr->RestoreBootstrap);
400 jcr->unlink_bsr = false;
403 msg_type = M_INFO; /* by default INFO message */
406 term_msg = _("Verify OK");
409 case JS_ErrorTerminated:
410 term_msg = _("*** Verify Error ***");
411 msg_type = M_ERROR; /* Generate error message */
414 term_msg = _("Verify warnings");
417 term_msg = _("Verify Canceled");
420 term_msg = _("Verify Differences");
423 term_msg = term_code;
424 bsnprintf(term_code, sizeof(term_code),
425 _("Inappropriate term code: %d %c\n"), TermCode, TermCode);
428 bstrftimes(sdt, sizeof(sdt), jcr->jr.StartTime);
429 bstrftimes(edt, sizeof(edt), jcr->jr.EndTime);
430 if (jcr->verify_job) {
431 Name = jcr->verify_job->hdr.name;
436 jobstatus_to_ascii(jcr->FDJobStatus, fd_term_msg, sizeof(fd_term_msg));
437 if (jcr->get_JobLevel() == L_VERIFY_VOLUME_TO_CATALOG) {
438 jobstatus_to_ascii(jcr->SDJobStatus, sd_term_msg, sizeof(sd_term_msg));
439 Jmsg(jcr, msg_type, 0, _("%s %s %s (%s): %s\n"
440 " Build OS: %s %s %s\n"
444 " Verify Level: %s\n"
446 " Verify JobId: %d\n"
450 " Files Expected: %s\n"
451 " Files Examined: %s\n"
452 " Non-fatal FD errors: %d\n"
453 " FD termination status: %s\n"
454 " SD termination status: %s\n"
455 " Termination: %s\n\n"),
456 BACULA, my_name, VERSION, LSMDATE, edt,
457 HOST_OS, DISTNAME, DISTVER,
460 jcr->fileset->hdr.name,
461 level_to_str(jcr->get_JobLevel()),
462 jcr->client->hdr.name,
463 jcr->previous_jr.JobId,
467 edit_uint64_with_commas(jcr->ExpectedFiles, ec1),
468 edit_uint64_with_commas(jcr->JobFiles, ec2),
474 Jmsg(jcr, msg_type, 0, _("%s %s %s (%s): %s\n"
479 " Verify Level: %s\n"
481 " Verify JobId: %d\n"
485 " Files Examined: %s\n"
486 " Non-fatal FD errors: %d\n"
487 " FD termination status: %s\n"
488 " Termination: %s\n\n"),
489 BACULA, my_name, VERSION, LSMDATE, edt,
490 HOST_OS, DISTNAME, DISTVER,
493 jcr->fileset->hdr.name,
494 level_to_str(jcr->get_JobLevel()),
496 jcr->previous_jr.JobId,
500 edit_uint64_with_commas(jcr->JobFiles, ec1),
505 Dmsg0(100, "Leave verify_cleanup()\n");
509 * This routine is called only during a Verify
511 void get_attributes_and_compare_to_catalog(JCR *jcr, JobId_t JobId)
516 struct stat statf; /* file stat */
517 struct stat statc; /* catalog stat */
519 POOLMEM *fname = get_pool_memory(PM_MESSAGE);
520 int do_Digest = CRYPTO_DIGEST_NONE;
521 int32_t file_index = 0;
523 memset(&fdbr, 0, sizeof(FILE_DBR));
524 fd = jcr->file_bsock;
528 Dmsg0(20, "bdird: waiting to receive file attributes\n");
530 * Get Attributes and Signature from File daemon
534 * Options or Digest (MD5/SHA1)
539 while ((n=bget_dirmsg(fd)) >= 0 && !job_canceled(jcr)) {
542 char Opts_Digest[MAXSTRING]; /* Verify Opts or MD5/SHA1 digest */
544 if (job_canceled(jcr)) {
547 fname = check_pool_memory_size(fname, fd->msglen);
548 jcr->fname = check_pool_memory_size(jcr->fname, fd->msglen);
549 Dmsg1(200, "Atts+Digest=%s\n", fd->msg);
550 if ((len = sscanf(fd->msg, "%ld %d %100s", &file_index, &stream,
552 Jmsg3(jcr, M_FATAL, 0, _("bird<filed: bad attributes, expected 3 fields got %d\n"
553 " mslen=%d msg=%s\n"), len, fd->msglen, fd->msg);
557 * We read the Options or Signature into fname
558 * to prevent overrun, now copy it to proper location.
560 bstrncpy(Opts_Digest, fname, sizeof(Opts_Digest));
562 skip_nonspaces(&p); /* skip FileIndex */
564 skip_nonspaces(&p); /* skip Stream */
566 skip_nonspaces(&p); /* skip Opts_Digest */
567 p++; /* skip space */
570 *fn++ = *p++; /* copy filename */
572 *fn = *p++; /* term filename and point to attribs */
575 * Got attributes stream, decode it
577 if (stream == STREAM_UNIX_ATTRIBUTES || stream == STREAM_UNIX_ATTRIBUTES_EX) {
578 int32_t LinkFIf, LinkFIc;
579 Dmsg2(400, "file_index=%d attr=%s\n", file_index, attr);
581 jcr->FileIndex = file_index; /* remember attribute file_index */
582 jcr->previous_jr.FileIndex = file_index;
583 decode_stat(attr, &statf, &LinkFIf); /* decode file stat packet */
584 do_Digest = CRYPTO_DIGEST_NONE;
585 jcr->fn_printed = false;
586 pm_strcpy(jcr->fname, fname); /* move filename into JCR */
588 Dmsg2(040, "dird<filed: stream=%d %s\n", stream, jcr->fname);
589 Dmsg1(020, "dird<filed: attr=%s\n", attr);
592 * Find equivalent record in the database
595 if (!db_get_file_attributes_record(jcr, jcr->db, jcr->fname,
596 &jcr->previous_jr, &fdbr)) {
597 Jmsg(jcr, M_INFO, 0, _("New file: %s\n"), jcr->fname);
598 Dmsg1(020, _("File not in catalog: %s\n"), jcr->fname);
599 set_jcr_job_status(jcr, JS_Differences);
603 * mark file record as visited by stuffing the
604 * current JobId, which is unique, into the MarkId field.
606 db_mark_file_record(jcr, jcr->db, fdbr.FileId, jcr->JobId);
609 Dmsg3(400, "Found %s in catalog. inx=%d Opts=%s\n", jcr->fname,
610 file_index, Opts_Digest);
611 decode_stat(fdbr.LStat, &statc, &LinkFIc); /* decode catalog stat */
613 * Loop over options supplied by user and verify the
614 * fields he requests.
616 for (p=Opts_Digest; *p; p++) {
617 char ed1[30], ed2[30];
619 case 'i': /* compare INODEs */
620 if (statc.st_ino != statf.st_ino) {
622 Jmsg(jcr, M_INFO, 0, _(" st_ino differ. Cat: %s File: %s\n"),
623 edit_uint64((uint64_t)statc.st_ino, ed1),
624 edit_uint64((uint64_t)statf.st_ino, ed2));
625 set_jcr_job_status(jcr, JS_Differences);
628 case 'p': /* permissions bits */
629 if (statc.st_mode != statf.st_mode) {
631 Jmsg(jcr, M_INFO, 0, _(" st_mode differ. Cat: %x File: %x\n"),
632 (uint32_t)statc.st_mode, (uint32_t)statf.st_mode);
633 set_jcr_job_status(jcr, JS_Differences);
636 case 'n': /* number of links */
637 if (statc.st_nlink != statf.st_nlink) {
639 Jmsg(jcr, M_INFO, 0, _(" st_nlink differ. Cat: %d File: %d\n"),
640 (uint32_t)statc.st_nlink, (uint32_t)statf.st_nlink);
641 set_jcr_job_status(jcr, JS_Differences);
644 case 'u': /* user id */
645 if (statc.st_uid != statf.st_uid) {
647 Jmsg(jcr, M_INFO, 0, _(" st_uid differ. Cat: %u File: %u\n"),
648 (uint32_t)statc.st_uid, (uint32_t)statf.st_uid);
649 set_jcr_job_status(jcr, JS_Differences);
652 case 'g': /* group id */
653 if (statc.st_gid != statf.st_gid) {
655 Jmsg(jcr, M_INFO, 0, _(" st_gid differ. Cat: %u File: %u\n"),
656 (uint32_t)statc.st_gid, (uint32_t)statf.st_gid);
657 set_jcr_job_status(jcr, JS_Differences);
661 if (statc.st_size != statf.st_size) {
663 Jmsg(jcr, M_INFO, 0, _(" st_size differ. Cat: %s File: %s\n"),
664 edit_uint64((uint64_t)statc.st_size, ed1),
665 edit_uint64((uint64_t)statf.st_size, ed2));
666 set_jcr_job_status(jcr, JS_Differences);
669 case 'a': /* access time */
670 if (statc.st_atime != statf.st_atime) {
672 Jmsg(jcr, M_INFO, 0, _(" st_atime differs\n"));
673 set_jcr_job_status(jcr, JS_Differences);
677 if (statc.st_mtime != statf.st_mtime) {
679 Jmsg(jcr, M_INFO, 0, _(" st_mtime differs\n"));
680 set_jcr_job_status(jcr, JS_Differences);
683 case 'c': /* ctime */
684 if (statc.st_ctime != statf.st_ctime) {
686 Jmsg(jcr, M_INFO, 0, _(" st_ctime differs\n"));
687 set_jcr_job_status(jcr, JS_Differences);
690 case 'd': /* file size decrease */
691 if (statc.st_size > statf.st_size) {
693 Jmsg(jcr, M_INFO, 0, _(" st_size decrease. Cat: %s File: %s\n"),
694 edit_uint64((uint64_t)statc.st_size, ed1),
695 edit_uint64((uint64_t)statf.st_size, ed2));
696 set_jcr_job_status(jcr, JS_Differences);
699 case '5': /* compare MD5 */
700 Dmsg1(500, "set Do_MD5 for %s\n", jcr->fname);
701 do_Digest = CRYPTO_DIGEST_MD5;
703 case '1': /* compare SHA1 */
704 do_Digest = CRYPTO_DIGEST_SHA1;
713 * Got Digest Signature from Storage daemon
714 * It came across in the Opts_Digest field.
716 } else if (crypto_digest_stream_type(stream) != CRYPTO_DIGEST_NONE) {
717 Dmsg2(400, "stream=Digest inx=%d Digest=%s\n", file_index, Opts_Digest);
719 * When ever we get a digest it MUST have been
720 * preceded by an attributes record, which sets attr_file_index
722 if (jcr->FileIndex != (uint32_t)file_index) {
723 Jmsg2(jcr, M_FATAL, 0, _("MD5/SHA1 index %d not same as attributes %d\n"),
724 file_index, jcr->FileIndex);
727 if (do_Digest != CRYPTO_DIGEST_NONE) {
728 db_escape_string(jcr, jcr->db, buf, Opts_Digest, strlen(Opts_Digest));
729 if (strcmp(buf, fdbr.Digest) != 0) {
731 Jmsg(jcr, M_INFO, 0, _(" %s differs. File=%s Cat=%s\n"),
732 stream_to_ascii(stream), buf, fdbr.Digest);
733 set_jcr_job_status(jcr, JS_Differences);
735 do_Digest = CRYPTO_DIGEST_NONE;
738 jcr->JobFiles = file_index;
740 if (is_bnet_error(fd)) {
742 Jmsg2(jcr, M_FATAL, 0, _("bdird<filed: bad attributes from filed n=%d : %s\n"),
747 /* Now find all the files that are missing -- i.e. all files in
748 * the database where the MarkId != current JobId
750 jcr->fn_printed = false;
751 bsnprintf(buf, sizeof(buf),
752 "SELECT Path.Path,Filename.Name FROM File,Path,Filename "
753 "WHERE File.JobId=%d AND File.FileIndex > 0 "
754 "AND File.MarkId!=%d AND File.PathId=Path.PathId "
755 "AND File.FilenameId=Filename.FilenameId",
757 /* missing_handler is called for each file found */
758 db_sql_query(jcr->db, buf, missing_handler, (void *)jcr);
759 if (jcr->fn_printed) {
760 set_jcr_job_status(jcr, JS_Differences);
762 free_pool_memory(fname);
766 * We are called here for each record that matches the above
767 * SQL query -- that is for each file contained in the Catalog
768 * that was not marked earlier. This means that the file in
769 * question is a missing file (in the Catalog but not on Disk).
771 static int missing_handler(void *ctx, int num_fields, char **row)
773 JCR *jcr = (JCR *)ctx;
775 if (job_canceled(jcr)) {
778 if (!jcr->fn_printed) {
779 Qmsg(jcr, M_WARNING, 0, _("The following files are in the Catalog but not on %s:\n"),
780 jcr->get_JobLevel() == L_VERIFY_VOLUME_TO_CATALOG ? "the Volume(s)" : "disk");
781 jcr->fn_printed = true;
783 Qmsg(jcr, M_INFO, 0, " %s%s\n", row[0]?row[0]:"", row[1]?row[1]:"");
789 * Print filename for verify
791 static void prt_fname(JCR *jcr)
793 if (!jcr->fn_printed) {
794 Jmsg(jcr, M_INFO, 0, _("File: %s\n"), jcr->fname);
795 jcr->fn_printed = true;