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) {
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,
282 jcr->rstore->SDDport, jcr->sd_auth_key);
283 if (!response(jcr, fd, OKstore, "Storage", DISPLAY_ERROR)) {
287 if (!jcr->RestoreBootstrap) {
288 Jmsg0(jcr, M_FATAL, 0, _("Deprecated feature ... use bootstrap.\n"));
297 case L_VERIFY_DISK_TO_CATALOG:
298 level="disk_to_catalog";
301 Jmsg2(jcr, M_FATAL, 0, _("Unimplemented Verify level %d(%c)\n"), jcr->get_JobLevel(),
302 jcr->get_JobLevel());
306 if (!send_runscripts_commands(jcr)) {
311 * Send verify command/level to File daemon
313 fd->fsend(verifycmd, level);
314 if (!response(jcr, fd, OKverify, "Verify", DISPLAY_ERROR)) {
319 * Now get data back from File daemon and
320 * compare it to the catalog or store it in the
321 * catalog depending on the run type.
323 /* Compare to catalog */
324 switch (jcr->get_JobLevel()) {
325 case L_VERIFY_CATALOG:
326 Dmsg0(10, "Verify level=catalog\n");
327 jcr->sd_msg_thread_done = true; /* no SD msg thread, so it is done */
328 jcr->SDJobStatus = JS_Terminated;
329 get_attributes_and_compare_to_catalog(jcr, jcr->previous_jr.JobId);
332 case L_VERIFY_VOLUME_TO_CATALOG:
333 Dmsg0(10, "Verify level=volume\n");
334 get_attributes_and_compare_to_catalog(jcr, jcr->previous_jr.JobId);
337 case L_VERIFY_DISK_TO_CATALOG:
338 Dmsg0(10, "Verify level=disk_to_catalog\n");
339 jcr->sd_msg_thread_done = true; /* no SD msg thread, so it is done */
340 jcr->SDJobStatus = JS_Terminated;
341 get_attributes_and_compare_to_catalog(jcr, jcr->previous_jr.JobId);
346 Dmsg0(10, "Verify level=init\n");
347 jcr->sd_msg_thread_done = true; /* no SD msg thread, so it is done */
348 jcr->SDJobStatus = JS_Terminated;
349 get_attributes_and_put_in_catalog(jcr);
350 db_end_transaction(jcr, jcr->db); /* terminate any open transaction */
351 db_write_batch_file_records(jcr);
355 Jmsg1(jcr, M_FATAL, 0, _("Unimplemented verify level %d\n"), jcr->get_JobLevel());
359 stat = wait_for_job_termination(jcr);
360 verify_cleanup(jcr, stat);
369 * Release resources allocated during backup.
372 void verify_cleanup(JCR *jcr, int TermCode)
374 char sdt[50], edt[50];
375 char ec1[30], ec2[30];
376 char term_code[100], fd_term_msg[100], sd_term_msg[100];
377 const char *term_msg;
382 // Dmsg1(100, "Enter verify_cleanup() TermCod=%d\n", TermCode);
384 Dmsg3(900, "JobLevel=%c Expected=%u JobFiles=%u\n", jcr->get_JobLevel(),
385 jcr->ExpectedFiles, jcr->JobFiles);
386 if (jcr->get_JobLevel() == L_VERIFY_VOLUME_TO_CATALOG &&
387 jcr->ExpectedFiles != jcr->JobFiles) {
388 TermCode = JS_ErrorTerminated;
391 JobId = jcr->jr.JobId;
393 update_job_end(jcr, TermCode);
395 if (job_canceled(jcr)) {
396 cancel_storage_daemon_job(jcr);
399 if (jcr->unlink_bsr && jcr->RestoreBootstrap) {
400 unlink(jcr->RestoreBootstrap);
401 jcr->unlink_bsr = false;
404 msg_type = M_INFO; /* by default INFO message */
407 term_msg = _("Verify OK");
410 case JS_ErrorTerminated:
411 term_msg = _("*** Verify Error ***");
412 msg_type = M_ERROR; /* Generate error message */
415 term_msg = _("Verify warnings");
418 term_msg = _("Verify Canceled");
421 term_msg = _("Verify Differences");
424 term_msg = term_code;
425 bsnprintf(term_code, sizeof(term_code),
426 _("Inappropriate term code: %d %c\n"), TermCode, TermCode);
429 bstrftimes(sdt, sizeof(sdt), jcr->jr.StartTime);
430 bstrftimes(edt, sizeof(edt), jcr->jr.EndTime);
431 if (jcr->verify_job) {
432 Name = jcr->verify_job->hdr.name;
437 jobstatus_to_ascii(jcr->FDJobStatus, fd_term_msg, sizeof(fd_term_msg));
438 if (jcr->get_JobLevel() == L_VERIFY_VOLUME_TO_CATALOG) {
439 jobstatus_to_ascii(jcr->SDJobStatus, sd_term_msg, sizeof(sd_term_msg));
440 Jmsg(jcr, msg_type, 0, _("%s %s %s (%s): %s\n"
441 " Build OS: %s %s %s\n"
445 " Verify Level: %s\n"
447 " Verify JobId: %d\n"
451 " Files Expected: %s\n"
452 " Files Examined: %s\n"
453 " Non-fatal FD errors: %d\n"
454 " FD termination status: %s\n"
455 " SD termination status: %s\n"
456 " Termination: %s\n\n"),
457 BACULA, my_name, VERSION, LSMDATE, edt,
458 HOST_OS, DISTNAME, DISTVER,
461 jcr->fileset->hdr.name,
462 level_to_str(jcr->get_JobLevel()),
463 jcr->client->hdr.name,
464 jcr->previous_jr.JobId,
468 edit_uint64_with_commas(jcr->ExpectedFiles, ec1),
469 edit_uint64_with_commas(jcr->JobFiles, ec2),
475 Jmsg(jcr, msg_type, 0, _("%s %s %s (%s): %s\n"
480 " Verify Level: %s\n"
482 " Verify JobId: %d\n"
486 " Files Examined: %s\n"
487 " Non-fatal FD errors: %d\n"
488 " FD termination status: %s\n"
489 " Termination: %s\n\n"),
490 BACULA, my_name, VERSION, LSMDATE, edt,
491 HOST_OS, DISTNAME, DISTVER,
494 jcr->fileset->hdr.name,
495 level_to_str(jcr->get_JobLevel()),
497 jcr->previous_jr.JobId,
501 edit_uint64_with_commas(jcr->JobFiles, ec1),
506 Dmsg0(100, "Leave verify_cleanup()\n");
510 * This routine is called only during a Verify
512 void get_attributes_and_compare_to_catalog(JCR *jcr, JobId_t JobId)
517 struct stat statf; /* file stat */
518 struct stat statc; /* catalog stat */
520 POOLMEM *fname = get_pool_memory(PM_MESSAGE);
521 int do_Digest = CRYPTO_DIGEST_NONE;
522 int32_t file_index = 0;
524 memset(&fdbr, 0, sizeof(FILE_DBR));
525 fd = jcr->file_bsock;
529 Dmsg0(20, "bdird: waiting to receive file attributes\n");
531 * Get Attributes and Signature from File daemon
535 * Options or Digest (MD5/SHA1)
540 while ((n=bget_dirmsg(fd)) >= 0 && !job_canceled(jcr)) {
543 char Opts_Digest[MAXSTRING]; /* Verify Opts or MD5/SHA1 digest */
545 if (job_canceled(jcr)) {
548 fname = check_pool_memory_size(fname, fd->msglen);
549 jcr->fname = check_pool_memory_size(jcr->fname, fd->msglen);
550 Dmsg1(200, "Atts+Digest=%s\n", fd->msg);
551 if ((len = sscanf(fd->msg, "%ld %d %100s", &file_index, &stream,
553 Jmsg3(jcr, M_FATAL, 0, _("bird<filed: bad attributes, expected 3 fields got %d\n"
554 " mslen=%d msg=%s\n"), len, fd->msglen, fd->msg);
558 * We read the Options or Signature into fname
559 * to prevent overrun, now copy it to proper location.
561 bstrncpy(Opts_Digest, fname, sizeof(Opts_Digest));
563 skip_nonspaces(&p); /* skip FileIndex */
565 skip_nonspaces(&p); /* skip Stream */
567 skip_nonspaces(&p); /* skip Opts_Digest */
568 p++; /* skip space */
571 *fn++ = *p++; /* copy filename */
573 *fn = *p++; /* term filename and point to attribs */
576 * Got attributes stream, decode it
578 if (stream == STREAM_UNIX_ATTRIBUTES || stream == STREAM_UNIX_ATTRIBUTES_EX) {
579 int32_t LinkFIf, LinkFIc;
580 Dmsg2(400, "file_index=%d attr=%s\n", file_index, attr);
582 jcr->FileIndex = file_index; /* remember attribute file_index */
583 jcr->previous_jr.FileIndex = file_index;
584 decode_stat(attr, &statf, &LinkFIf); /* decode file stat packet */
585 do_Digest = CRYPTO_DIGEST_NONE;
586 jcr->fn_printed = false;
587 pm_strcpy(jcr->fname, fname); /* move filename into JCR */
589 Dmsg2(040, "dird<filed: stream=%d %s\n", stream, jcr->fname);
590 Dmsg1(020, "dird<filed: attr=%s\n", attr);
593 * Find equivalent record in the database
596 if (!db_get_file_attributes_record(jcr, jcr->db, jcr->fname,
597 &jcr->previous_jr, &fdbr)) {
598 Jmsg(jcr, M_INFO, 0, _("New file: %s\n"), jcr->fname);
599 Dmsg1(020, _("File not in catalog: %s\n"), jcr->fname);
600 set_jcr_job_status(jcr, JS_Differences);
604 * mark file record as visited by stuffing the
605 * current JobId, which is unique, into the MarkId field.
607 db_mark_file_record(jcr, jcr->db, fdbr.FileId, jcr->JobId);
610 Dmsg3(400, "Found %s in catalog. inx=%d Opts=%s\n", jcr->fname,
611 file_index, Opts_Digest);
612 decode_stat(fdbr.LStat, &statc, &LinkFIc); /* decode catalog stat */
614 * Loop over options supplied by user and verify the
615 * fields he requests.
617 for (p=Opts_Digest; *p; p++) {
618 char ed1[30], ed2[30];
620 case 'i': /* compare INODEs */
621 if (statc.st_ino != statf.st_ino) {
623 Jmsg(jcr, M_INFO, 0, _(" st_ino differ. Cat: %s File: %s\n"),
624 edit_uint64((uint64_t)statc.st_ino, ed1),
625 edit_uint64((uint64_t)statf.st_ino, ed2));
626 set_jcr_job_status(jcr, JS_Differences);
629 case 'p': /* permissions bits */
630 if (statc.st_mode != statf.st_mode) {
632 Jmsg(jcr, M_INFO, 0, _(" st_mode differ. Cat: %x File: %x\n"),
633 (uint32_t)statc.st_mode, (uint32_t)statf.st_mode);
634 set_jcr_job_status(jcr, JS_Differences);
637 case 'n': /* number of links */
638 if (statc.st_nlink != statf.st_nlink) {
640 Jmsg(jcr, M_INFO, 0, _(" st_nlink differ. Cat: %d File: %d\n"),
641 (uint32_t)statc.st_nlink, (uint32_t)statf.st_nlink);
642 set_jcr_job_status(jcr, JS_Differences);
645 case 'u': /* user id */
646 if (statc.st_uid != statf.st_uid) {
648 Jmsg(jcr, M_INFO, 0, _(" st_uid differ. Cat: %u File: %u\n"),
649 (uint32_t)statc.st_uid, (uint32_t)statf.st_uid);
650 set_jcr_job_status(jcr, JS_Differences);
653 case 'g': /* group id */
654 if (statc.st_gid != statf.st_gid) {
656 Jmsg(jcr, M_INFO, 0, _(" st_gid differ. Cat: %u File: %u\n"),
657 (uint32_t)statc.st_gid, (uint32_t)statf.st_gid);
658 set_jcr_job_status(jcr, JS_Differences);
662 if (statc.st_size != statf.st_size) {
664 Jmsg(jcr, M_INFO, 0, _(" st_size differ. Cat: %s File: %s\n"),
665 edit_uint64((uint64_t)statc.st_size, ed1),
666 edit_uint64((uint64_t)statf.st_size, ed2));
667 set_jcr_job_status(jcr, JS_Differences);
670 case 'a': /* access time */
671 if (statc.st_atime != statf.st_atime) {
673 Jmsg(jcr, M_INFO, 0, _(" st_atime differs\n"));
674 set_jcr_job_status(jcr, JS_Differences);
678 if (statc.st_mtime != statf.st_mtime) {
680 Jmsg(jcr, M_INFO, 0, _(" st_mtime differs\n"));
681 set_jcr_job_status(jcr, JS_Differences);
684 case 'c': /* ctime */
685 if (statc.st_ctime != statf.st_ctime) {
687 Jmsg(jcr, M_INFO, 0, _(" st_ctime differs\n"));
688 set_jcr_job_status(jcr, JS_Differences);
691 case 'd': /* file size decrease */
692 if (statc.st_size > statf.st_size) {
694 Jmsg(jcr, M_INFO, 0, _(" st_size decrease. Cat: %s File: %s\n"),
695 edit_uint64((uint64_t)statc.st_size, ed1),
696 edit_uint64((uint64_t)statf.st_size, ed2));
697 set_jcr_job_status(jcr, JS_Differences);
700 case '5': /* compare MD5 */
701 Dmsg1(500, "set Do_MD5 for %s\n", jcr->fname);
702 do_Digest = CRYPTO_DIGEST_MD5;
704 case '1': /* compare SHA1 */
705 do_Digest = CRYPTO_DIGEST_SHA1;
714 * Got Digest Signature from Storage daemon
715 * It came across in the Opts_Digest field.
717 } else if (crypto_digest_stream_type(stream) != CRYPTO_DIGEST_NONE) {
718 Dmsg2(400, "stream=Digest inx=%d Digest=%s\n", file_index, Opts_Digest);
720 * When ever we get a digest it MUST have been
721 * preceded by an attributes record, which sets attr_file_index
723 if (jcr->FileIndex != (uint32_t)file_index) {
724 Jmsg2(jcr, M_FATAL, 0, _("MD5/SHA1 index %d not same as attributes %d\n"),
725 file_index, jcr->FileIndex);
728 if (do_Digest != CRYPTO_DIGEST_NONE) {
729 db_escape_string(jcr, jcr->db, buf, Opts_Digest, strlen(Opts_Digest));
730 if (strcmp(buf, fdbr.Digest) != 0) {
732 Jmsg(jcr, M_INFO, 0, _(" %s differs. File=%s Cat=%s\n"),
733 stream_to_ascii(stream), buf, fdbr.Digest);
734 set_jcr_job_status(jcr, JS_Differences);
736 do_Digest = CRYPTO_DIGEST_NONE;
739 jcr->JobFiles = file_index;
741 if (is_bnet_error(fd)) {
743 Jmsg2(jcr, M_FATAL, 0, _("bdird<filed: bad attributes from filed n=%d : %s\n"),
748 /* Now find all the files that are missing -- i.e. all files in
749 * the database where the MarkId != current JobId
751 jcr->fn_printed = false;
752 bsnprintf(buf, sizeof(buf),
753 "SELECT Path.Path,Filename.Name FROM File,Path,Filename "
754 "WHERE File.JobId=%d AND File.FileIndex > 0 "
755 "AND File.MarkId!=%d AND File.PathId=Path.PathId "
756 "AND File.FilenameId=Filename.FilenameId",
758 /* missing_handler is called for each file found */
759 db_sql_query(jcr->db, buf, missing_handler, (void *)jcr);
760 if (jcr->fn_printed) {
761 set_jcr_job_status(jcr, JS_Differences);
763 free_pool_memory(fname);
767 * We are called here for each record that matches the above
768 * SQL query -- that is for each file contained in the Catalog
769 * that was not marked earlier. This means that the file in
770 * question is a missing file (in the Catalog but not on Disk).
772 static int missing_handler(void *ctx, int num_fields, char **row)
774 JCR *jcr = (JCR *)ctx;
776 if (job_canceled(jcr)) {
779 if (!jcr->fn_printed) {
780 Qmsg(jcr, M_WARNING, 0, _("The following files are in the Catalog but not on %s:\n"),
781 jcr->get_JobLevel() == L_VERIFY_VOLUME_TO_CATALOG ? "the Volume(s)" : "disk");
782 jcr->fn_printed = true;
784 Qmsg(jcr, M_INFO, 0, " %s%s\n", row[0]?row[0]:"", row[1]?row[1]:"");
790 * Print filename for verify
792 static void prt_fname(JCR *jcr)
794 if (!jcr->fn_printed) {
795 Jmsg(jcr, M_INFO, 0, _("File: %s\n"), jcr->fname);
796 jcr->fn_printed = true;