2 Bacula® - The Network Backup Solution
4 Copyright (C) 2000-2008 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";
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)
69 if (!allow_duplicate_job(jcr)) {
72 switch (jcr->get_JobLevel()) {
74 case L_VERIFY_CATALOG:
75 case L_VERIFY_DISK_TO_CATALOG:
79 case L_VERIFY_VOLUME_TO_CATALOG:
85 Jmsg2(jcr, M_FATAL, 0, _("Unimplemented Verify level %d(%c)\n"), jcr->get_JobLevel(),
94 * Do a verification of the specified files against the Catlaog
96 * Returns: false on failure
99 bool do_verify(JCR *jcr)
106 JobId_t verify_jobid = 0;
109 free_wstorage(jcr); /* we don't write */
111 memset(&jcr->previous_jr, 0, sizeof(jcr->previous_jr));
114 * Find JobId of last job that ran. Note, we do this when
115 * the job actually starts running, not at schedule time,
116 * so that we find the last job that terminated before
117 * this job runs rather than before it is scheduled. This
118 * permits scheduling a Backup and Verify at the same time,
119 * but with the Verify at a lower priority.
121 * For VERIFY_CATALOG we want the JobId of the last INIT.
122 * For VERIFY_VOLUME_TO_CATALOG, we want the JobId of the
125 if (jcr->get_JobLevel() == L_VERIFY_CATALOG ||
126 jcr->get_JobLevel() == L_VERIFY_VOLUME_TO_CATALOG ||
127 jcr->get_JobLevel() == L_VERIFY_DISK_TO_CATALOG) {
128 memcpy(&jr, &jcr->jr, sizeof(jr));
129 if (jcr->verify_job &&
130 (jcr->get_JobLevel() == L_VERIFY_VOLUME_TO_CATALOG ||
131 jcr->get_JobLevel() == L_VERIFY_DISK_TO_CATALOG)) {
132 Name = jcr->verify_job->name();
136 Dmsg1(100, "find last jobid for: %s\n", NPRT(Name));
137 if (!db_find_last_jobid(jcr, jcr->db, Name, &jr)) {
138 if (jcr->get_JobLevel() == L_VERIFY_CATALOG) {
139 Jmsg(jcr, M_FATAL, 0, _(
140 "Unable to find JobId of previous InitCatalog Job.\n"
141 "Please run a Verify with Level=InitCatalog before\n"
142 "running the current Job.\n"));
144 Jmsg(jcr, M_FATAL, 0, _(
145 "Unable to find JobId of previous Job for this client.\n"));
149 verify_jobid = jr.JobId;
150 Dmsg1(100, "Last full jobid=%d\n", verify_jobid);
153 * Now get the job record for the previous backup that interests
154 * us. We use the verify_jobid that we found above.
156 if (jcr->get_JobLevel() == L_VERIFY_CATALOG ||
157 jcr->get_JobLevel() == L_VERIFY_VOLUME_TO_CATALOG ||
158 jcr->get_JobLevel() == L_VERIFY_DISK_TO_CATALOG) {
159 jcr->previous_jr.JobId = verify_jobid;
160 if (!db_get_job_record(jcr, jcr->db, &jcr->previous_jr)) {
161 Jmsg(jcr, M_FATAL, 0, _("Could not get job record for previous Job. ERR=%s"),
162 db_strerror(jcr->db));
165 if (jcr->previous_jr.JobStatus != 'T') {
166 Jmsg(jcr, M_FATAL, 0, _("Last Job %d did not terminate normally. JobStatus=%c\n"),
167 verify_jobid, jcr->previous_jr.JobStatus);
170 Jmsg(jcr, M_INFO, 0, _("Verifying against JobId=%d Job=%s\n"),
171 jcr->previous_jr.JobId, jcr->previous_jr.Job);
175 * If we are verifying a Volume, we need the Storage
176 * daemon, so open a connection, otherwise, just
177 * create a dummy authorization key (passed to
178 * File daemon but not used).
180 if (jcr->get_JobLevel() == L_VERIFY_VOLUME_TO_CATALOG) {
181 if (!create_restore_bootstrap_file(jcr)) {
185 jcr->sd_auth_key = bstrdup("dummy"); /* dummy Storage daemon key */
188 if (jcr->get_JobLevel() == L_VERIFY_DISK_TO_CATALOG && jcr->verify_job) {
189 jcr->fileset = jcr->verify_job->fileset;
191 Dmsg2(100, "ClientId=%u JobLevel=%c\n", jcr->previous_jr.ClientId, jcr->get_JobLevel());
193 if (!db_update_job_start_record(jcr, jcr->db, &jcr->jr)) {
194 Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
198 /* Print Job Start message */
199 Jmsg(jcr, M_INFO, 0, _("Start Verify JobId=%s Level=%s Job=%s\n"),
200 edit_uint64(jcr->JobId, ed1), level_to_str(jcr->get_JobLevel()), jcr->Job);
202 if (jcr->get_JobLevel() == L_VERIFY_VOLUME_TO_CATALOG) {
204 * Start conversation with Storage daemon
206 set_jcr_job_status(jcr, JS_Blocked);
207 if (!connect_to_storage_daemon(jcr, 10, SDConnectTimeout, 1)) {
211 * Now start a job with the Storage daemon
213 if (!start_storage_daemon_job(jcr, jcr->rstorage, NULL)) {
216 if (!jcr->store_bsock->fsend("run")) {
220 * Now start a Storage daemon message thread
222 if (!start_storage_daemon_message_thread(jcr)) {
225 Dmsg0(50, "Storage daemon connection OK\n");
229 * OK, now connect to the File daemon
230 * and ask him for the files.
232 set_jcr_job_status(jcr, JS_Blocked);
233 if (!connect_to_file_daemon(jcr, 10, FDConnectTimeout, 1)) {
237 set_jcr_job_status(jcr, JS_Running);
238 fd = jcr->file_bsock;
241 Dmsg0(30, ">filed: Send include list\n");
242 if (!send_include_list(jcr)) {
246 Dmsg0(30, ">filed: Send exclude list\n");
247 if (!send_exclude_list(jcr)) {
252 * Send Level command to File daemon, as well
253 * as the Storage address if appropriate.
255 switch (jcr->get_JobLevel()) {
259 case L_VERIFY_CATALOG:
262 case L_VERIFY_VOLUME_TO_CATALOG:
264 * send Storage daemon address to the File daemon
266 if (jcr->rstore->SDDport == 0) {
267 jcr->rstore->SDDport = jcr->rstore->SDport;
269 bnet_fsend(fd, storaddr, jcr->rstore->address, jcr->rstore->SDDport);
270 if (!response(jcr, fd, OKstore, "Storage", DISPLAY_ERROR)) {
275 * Send the bootstrap file -- what Volumes/files to restore
277 if (!send_bootstrap_file(jcr, fd) ||
278 !response(jcr, fd, OKbootstrap, "Bootstrap", DISPLAY_ERROR)) {
282 if (!jcr->RestoreBootstrap) {
283 Jmsg0(jcr, M_FATAL, 0, _("Deprecated feature ... use bootstrap.\n"));
292 case L_VERIFY_DISK_TO_CATALOG:
293 level="disk_to_catalog";
296 Jmsg2(jcr, M_FATAL, 0, _("Unimplemented Verify level %d(%c)\n"), jcr->get_JobLevel(),
297 jcr->get_JobLevel());
301 if (!send_runscripts_commands(jcr)) {
306 * Send verify command/level to File daemon
308 fd->fsend(verifycmd, level);
309 if (!response(jcr, fd, OKverify, "Verify", DISPLAY_ERROR)) {
314 * Now get data back from File daemon and
315 * compare it to the catalog or store it in the
316 * catalog depending on the run type.
318 /* Compare to catalog */
319 switch (jcr->get_JobLevel()) {
320 case L_VERIFY_CATALOG:
321 Dmsg0(10, "Verify level=catalog\n");
322 jcr->sd_msg_thread_done = true; /* no SD msg thread, so it is done */
323 jcr->SDJobStatus = JS_Terminated;
324 get_attributes_and_compare_to_catalog(jcr, jcr->previous_jr.JobId);
327 case L_VERIFY_VOLUME_TO_CATALOG:
328 Dmsg0(10, "Verify level=volume\n");
329 get_attributes_and_compare_to_catalog(jcr, jcr->previous_jr.JobId);
332 case L_VERIFY_DISK_TO_CATALOG:
333 Dmsg0(10, "Verify level=disk_to_catalog\n");
334 jcr->sd_msg_thread_done = true; /* no SD msg thread, so it is done */
335 jcr->SDJobStatus = JS_Terminated;
336 get_attributes_and_compare_to_catalog(jcr, jcr->previous_jr.JobId);
341 Dmsg0(10, "Verify level=init\n");
342 jcr->sd_msg_thread_done = true; /* no SD msg thread, so it is done */
343 jcr->SDJobStatus = JS_Terminated;
344 get_attributes_and_put_in_catalog(jcr);
345 db_end_transaction(jcr, jcr->db); /* terminate any open transaction */
346 db_write_batch_file_records(jcr);
350 Jmsg1(jcr, M_FATAL, 0, _("Unimplemented verify level %d\n"), jcr->get_JobLevel());
354 stat = wait_for_job_termination(jcr);
355 verify_cleanup(jcr, stat);
364 * Release resources allocated during backup.
367 void verify_cleanup(JCR *jcr, int TermCode)
369 char sdt[50], edt[50];
370 char ec1[30], ec2[30];
371 char term_code[100], fd_term_msg[100], sd_term_msg[100];
372 const char *term_msg;
377 // Dmsg1(100, "Enter verify_cleanup() TermCod=%d\n", TermCode);
379 Dmsg3(900, "JobLevel=%c Expected=%u JobFiles=%u\n", jcr->get_JobLevel(),
380 jcr->ExpectedFiles, jcr->JobFiles);
381 if (jcr->get_JobLevel() == L_VERIFY_VOLUME_TO_CATALOG &&
382 jcr->ExpectedFiles != jcr->JobFiles) {
383 TermCode = JS_ErrorTerminated;
386 /* If no files were expected, there can be no error */
387 if (jcr->get_JobLevel() == L_VERIFY_VOLUME_TO_CATALOG &&
388 jcr->ExpectedFiles == 0) {
389 TermCode = JS_Terminated;
392 JobId = jcr->jr.JobId;
394 update_job_end(jcr, TermCode);
396 if (job_canceled(jcr)) {
397 cancel_storage_daemon_job(jcr);
400 if (jcr->unlink_bsr && jcr->RestoreBootstrap) {
401 unlink(jcr->RestoreBootstrap);
402 jcr->unlink_bsr = false;
405 msg_type = M_INFO; /* by default INFO message */
408 term_msg = _("Verify OK");
411 case JS_ErrorTerminated:
412 term_msg = _("*** Verify Error ***");
413 msg_type = M_ERROR; /* Generate error message */
416 term_msg = _("Verify warnings");
419 term_msg = _("Verify Canceled");
422 term_msg = _("Verify Differences");
425 term_msg = term_code;
426 bsnprintf(term_code, sizeof(term_code),
427 _("Inappropriate term code: %d %c\n"), TermCode, TermCode);
430 bstrftimes(sdt, sizeof(sdt), jcr->jr.StartTime);
431 bstrftimes(edt, sizeof(edt), jcr->jr.EndTime);
432 if (jcr->verify_job) {
433 Name = jcr->verify_job->hdr.name;
438 jobstatus_to_ascii(jcr->FDJobStatus, fd_term_msg, sizeof(fd_term_msg));
439 if (jcr->get_JobLevel() == L_VERIFY_VOLUME_TO_CATALOG) {
440 jobstatus_to_ascii(jcr->SDJobStatus, sd_term_msg, sizeof(sd_term_msg));
441 Jmsg(jcr, msg_type, 0, _("Bacula %s %s (%s): %s\n"
442 " Build OS: %s %s %s\n"
446 " Verify Level: %s\n"
448 " Verify JobId: %d\n"
452 " Files Expected: %s\n"
453 " Files Examined: %s\n"
454 " Non-fatal FD errors: %d\n"
455 " FD termination status: %s\n"
456 " SD termination status: %s\n"
457 " Termination: %s\n\n"),
458 my_name, VERSION, LSMDATE, edt,
459 HOST_OS, DISTNAME, DISTVER,
462 jcr->fileset->hdr.name,
463 level_to_str(jcr->get_JobLevel()),
464 jcr->client->hdr.name,
465 jcr->previous_jr.JobId,
469 edit_uint64_with_commas(jcr->ExpectedFiles, ec1),
470 edit_uint64_with_commas(jcr->JobFiles, ec2),
476 Jmsg(jcr, msg_type, 0, _("Bacula %s %s (%s): %s\n"
481 " Verify Level: %s\n"
483 " Verify JobId: %d\n"
487 " Files Examined: %s\n"
488 " Non-fatal FD errors: %d\n"
489 " FD termination status: %s\n"
490 " Termination: %s\n\n"),
491 my_name, VERSION, LSMDATE, edt,
492 HOST_OS, DISTNAME, DISTVER,
495 jcr->fileset->hdr.name,
496 level_to_str(jcr->get_JobLevel()),
498 jcr->previous_jr.JobId,
502 edit_uint64_with_commas(jcr->JobFiles, ec1),
507 Dmsg0(100, "Leave verify_cleanup()\n");
511 * This routine is called only during a Verify
513 int get_attributes_and_compare_to_catalog(JCR *jcr, JobId_t JobId)
518 struct stat statf; /* file stat */
519 struct stat statc; /* catalog stat */
520 int stat = JS_Terminated;
522 POOLMEM *fname = get_pool_memory(PM_MESSAGE);
523 int do_Digest = CRYPTO_DIGEST_NONE;
524 int32_t file_index = 0;
526 memset(&fdbr, 0, sizeof(FILE_DBR));
527 fd = jcr->file_bsock;
531 Dmsg0(20, "bdird: waiting to receive file attributes\n");
533 * Get Attributes and Signature from File daemon
537 * Options or Digest (MD5/SHA1)
542 while ((n=bget_dirmsg(fd)) >= 0 && !job_canceled(jcr)) {
545 char Opts_Digest[MAXSTRING]; /* Verify Opts or MD5/SHA1 digest */
547 if (job_canceled(jcr)) {
550 fname = check_pool_memory_size(fname, fd->msglen);
551 jcr->fname = check_pool_memory_size(jcr->fname, fd->msglen);
552 Dmsg1(200, "Atts+Digest=%s\n", fd->msg);
553 if ((len = sscanf(fd->msg, "%ld %d %100s", &file_index, &stream,
555 Jmsg3(jcr, M_FATAL, 0, _("bird<filed: bad attributes, expected 3 fields got %d\n"
556 " mslen=%d msg=%s\n"), len, fd->msglen, fd->msg);
560 * We read the Options or Signature into fname
561 * to prevent overrun, now copy it to proper location.
563 bstrncpy(Opts_Digest, fname, sizeof(Opts_Digest));
565 skip_nonspaces(&p); /* skip FileIndex */
567 skip_nonspaces(&p); /* skip Stream */
569 skip_nonspaces(&p); /* skip Opts_Digest */
570 p++; /* skip space */
573 *fn++ = *p++; /* copy filename */
575 *fn = *p++; /* term filename and point to attribs */
578 * Got attributes stream, decode it
580 if (stream == STREAM_UNIX_ATTRIBUTES || stream == STREAM_UNIX_ATTRIBUTES_EX) {
581 int32_t LinkFIf, LinkFIc;
582 Dmsg2(400, "file_index=%d attr=%s\n", file_index, attr);
584 jcr->FileIndex = file_index; /* remember attribute file_index */
585 decode_stat(attr, &statf, &LinkFIf); /* decode file stat packet */
586 do_Digest = CRYPTO_DIGEST_NONE;
587 jcr->fn_printed = false;
588 pm_strcpy(jcr->fname, fname); /* move filename into JCR */
590 Dmsg2(040, "dird<filed: stream=%d %s\n", stream, jcr->fname);
591 Dmsg1(020, "dird<filed: attr=%s\n", attr);
594 * Find equivalent record in the database
597 if (!db_get_file_attributes_record(jcr, jcr->db, jcr->fname,
598 &jcr->previous_jr, &fdbr)) {
599 Jmsg(jcr, M_INFO, 0, _("New file: %s\n"), jcr->fname);
600 Dmsg1(020, _("File not in catalog: %s\n"), jcr->fname);
601 stat = JS_Differences;
605 * mark file record as visited by stuffing the
606 * current JobId, which is unique, into the MarkId field.
608 db_mark_file_record(jcr, jcr->db, fdbr.FileId, jcr->JobId);
611 Dmsg3(400, "Found %s in catalog. inx=%d Opts=%s\n", jcr->fname,
612 file_index, Opts_Digest);
613 decode_stat(fdbr.LStat, &statc, &LinkFIc); /* decode catalog stat */
615 * Loop over options supplied by user and verify the
616 * fields he requests.
618 for (p=Opts_Digest; *p; p++) {
619 char ed1[30], ed2[30];
621 case 'i': /* compare INODEs */
622 if (statc.st_ino != statf.st_ino) {
624 Jmsg(jcr, M_INFO, 0, _(" st_ino differ. Cat: %s File: %s\n"),
625 edit_uint64((uint64_t)statc.st_ino, ed1),
626 edit_uint64((uint64_t)statf.st_ino, ed2));
627 stat = JS_Differences;
630 case 'p': /* permissions bits */
631 if (statc.st_mode != statf.st_mode) {
633 Jmsg(jcr, M_INFO, 0, _(" st_mode differ. Cat: %x File: %x\n"),
634 (uint32_t)statc.st_mode, (uint32_t)statf.st_mode);
635 stat = JS_Differences;
638 case 'n': /* number of links */
639 if (statc.st_nlink != statf.st_nlink) {
641 Jmsg(jcr, M_INFO, 0, _(" st_nlink differ. Cat: %d File: %d\n"),
642 (uint32_t)statc.st_nlink, (uint32_t)statf.st_nlink);
643 stat = JS_Differences;
646 case 'u': /* user id */
647 if (statc.st_uid != statf.st_uid) {
649 Jmsg(jcr, M_INFO, 0, _(" st_uid differ. Cat: %u File: %u\n"),
650 (uint32_t)statc.st_uid, (uint32_t)statf.st_uid);
651 stat = JS_Differences;
654 case 'g': /* group id */
655 if (statc.st_gid != statf.st_gid) {
657 Jmsg(jcr, M_INFO, 0, _(" st_gid differ. Cat: %u File: %u\n"),
658 (uint32_t)statc.st_gid, (uint32_t)statf.st_gid);
659 stat = JS_Differences;
663 if (statc.st_size != statf.st_size) {
665 Jmsg(jcr, M_INFO, 0, _(" st_size differ. Cat: %s File: %s\n"),
666 edit_uint64((uint64_t)statc.st_size, ed1),
667 edit_uint64((uint64_t)statf.st_size, ed2));
668 stat = JS_Differences;
671 case 'a': /* access time */
672 if (statc.st_atime != statf.st_atime) {
674 Jmsg(jcr, M_INFO, 0, _(" st_atime differs\n"));
675 stat = JS_Differences;
679 if (statc.st_mtime != statf.st_mtime) {
681 Jmsg(jcr, M_INFO, 0, _(" st_mtime differs\n"));
682 stat = JS_Differences;
685 case 'c': /* ctime */
686 if (statc.st_ctime != statf.st_ctime) {
688 Jmsg(jcr, M_INFO, 0, _(" st_ctime differs\n"));
689 stat = JS_Differences;
692 case 'd': /* file size decrease */
693 if (statc.st_size > statf.st_size) {
695 Jmsg(jcr, M_INFO, 0, _(" st_size decrease. Cat: %s File: %s\n"),
696 edit_uint64((uint64_t)statc.st_size, ed1),
697 edit_uint64((uint64_t)statf.st_size, ed2));
698 stat = JS_Differences;
701 case '5': /* compare MD5 */
702 Dmsg1(500, "set Do_MD5 for %s\n", jcr->fname);
703 do_Digest = CRYPTO_DIGEST_MD5;
705 case '1': /* compare SHA1 */
706 do_Digest = CRYPTO_DIGEST_SHA1;
715 * Got Digest Signature from Storage daemon
716 * It came across in the Opts_Digest field.
718 } else if (crypto_digest_stream_type(stream) != CRYPTO_DIGEST_NONE) {
719 Dmsg2(400, "stream=Digest inx=%d Digest=%s\n", file_index, Opts_Digest);
721 * When ever we get a digest it MUST have been
722 * preceded by an attributes record, which sets attr_file_index
724 if (jcr->FileIndex != (uint32_t)file_index) {
725 Jmsg2(jcr, M_FATAL, 0, _("MD5/SHA1 index %d not same as attributes %d\n"),
726 file_index, jcr->FileIndex);
729 if (do_Digest != CRYPTO_DIGEST_NONE) {
730 db_escape_string(jcr, jcr->db, buf, Opts_Digest, strlen(Opts_Digest));
731 if (strcmp(buf, fdbr.Digest) != 0) {
733 if (debug_level >= 10) {
734 Jmsg(jcr, M_INFO, 0, _(" %s not same. File=%s Cat=%s\n"),
735 stream_to_ascii(stream), buf, fdbr.Digest);
737 Jmsg(jcr, M_INFO, 0, _(" %s differs.\n"),
738 stream_to_ascii(stream));
740 stat = JS_Differences;
742 do_Digest = CRYPTO_DIGEST_NONE;
745 jcr->JobFiles = file_index;
747 if (is_bnet_error(fd)) {
749 Jmsg2(jcr, M_FATAL, 0, _("bdird<filed: bad attributes from filed n=%d : %s\n"),
754 /* Now find all the files that are missing -- i.e. all files in
755 * the database where the MarkId != current JobId
757 jcr->fn_printed = false;
758 bsnprintf(buf, sizeof(buf),
759 "SELECT Path.Path,Filename.Name FROM File,Path,Filename "
760 "WHERE File.JobId=%d "
761 "AND File.MarkId!=%d AND File.PathId=Path.PathId "
762 "AND File.FilenameId=Filename.FilenameId",
764 /* missing_handler is called for each file found */
765 db_sql_query(jcr->db, buf, missing_handler, (void *)jcr);
766 if (jcr->fn_printed) {
767 stat = JS_Differences;
769 free_pool_memory(fname);
770 if (!job_canceled(jcr)) {
771 jcr->JobStatus = stat;
773 return stat == JS_Terminated;
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_INFO, 0, _("\nThe 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;