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";
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 == JS_Terminated ||
166 jcr->previous_jr.JobStatus == JS_Warnings)) {
167 Jmsg(jcr, M_FATAL, 0, _("Last Job %d did not terminate normally. JobStatus=%c\n"),
168 verify_jobid, jcr->previous_jr.JobStatus);
171 Jmsg(jcr, M_INFO, 0, _("Verifying against JobId=%d Job=%s\n"),
172 jcr->previous_jr.JobId, jcr->previous_jr.Job);
176 * If we are verifying a Volume, we need the Storage
177 * daemon, so open a connection, otherwise, just
178 * create a dummy authorization key (passed to
179 * File daemon but not used).
181 if (jcr->get_JobLevel() == L_VERIFY_VOLUME_TO_CATALOG) {
182 if (!create_restore_bootstrap_file(jcr)) {
186 jcr->sd_auth_key = bstrdup("dummy"); /* dummy Storage daemon key */
189 if (jcr->get_JobLevel() == L_VERIFY_DISK_TO_CATALOG && jcr->verify_job) {
190 jcr->fileset = jcr->verify_job->fileset;
192 Dmsg2(100, "ClientId=%u JobLevel=%c\n", jcr->previous_jr.ClientId, jcr->get_JobLevel());
194 if (!db_update_job_start_record(jcr, jcr->db, &jcr->jr)) {
195 Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
199 /* Print Job Start message */
200 Jmsg(jcr, M_INFO, 0, _("Start Verify JobId=%s Level=%s Job=%s\n"),
201 edit_uint64(jcr->JobId, ed1), level_to_str(jcr->get_JobLevel()), jcr->Job);
203 if (jcr->get_JobLevel() == L_VERIFY_VOLUME_TO_CATALOG) {
205 * Start conversation with Storage daemon
207 set_jcr_job_status(jcr, JS_Blocked);
208 if (!connect_to_storage_daemon(jcr, 10, SDConnectTimeout, 1)) {
212 * Now start a job with the Storage daemon
214 if (!start_storage_daemon_job(jcr, jcr->rstorage, NULL)) {
217 if (!jcr->store_bsock->fsend("run")) {
221 * Now start a Storage daemon message thread
223 if (!start_storage_daemon_message_thread(jcr)) {
226 Dmsg0(50, "Storage daemon connection OK\n");
230 * OK, now connect to the File daemon
231 * and ask him for the files.
233 set_jcr_job_status(jcr, JS_Blocked);
234 if (!connect_to_file_daemon(jcr, 10, FDConnectTimeout, 1)) {
238 set_jcr_job_status(jcr, JS_Running);
239 fd = jcr->file_bsock;
242 Dmsg0(30, ">filed: Send include list\n");
243 if (!send_include_list(jcr)) {
247 Dmsg0(30, ">filed: Send exclude list\n");
248 if (!send_exclude_list(jcr)) {
253 * Send Level command to File daemon, as well
254 * as the Storage address if appropriate.
256 switch (jcr->get_JobLevel()) {
260 case L_VERIFY_CATALOG:
263 case L_VERIFY_VOLUME_TO_CATALOG:
265 * send Storage daemon address to the File daemon
267 if (jcr->rstore->SDDport == 0) {
268 jcr->rstore->SDDport = jcr->rstore->SDport;
270 bnet_fsend(fd, storaddr, jcr->rstore->address, jcr->rstore->SDDport);
271 if (!response(jcr, fd, OKstore, "Storage", DISPLAY_ERROR)) {
276 * Send the bootstrap file -- what Volumes/files to restore
278 if (!send_bootstrap_file(jcr, fd) ||
279 !response(jcr, fd, OKbootstrap, "Bootstrap", DISPLAY_ERROR)) {
283 if (!jcr->RestoreBootstrap) {
284 Jmsg0(jcr, M_FATAL, 0, _("Deprecated feature ... use bootstrap.\n"));
293 case L_VERIFY_DISK_TO_CATALOG:
294 level="disk_to_catalog";
297 Jmsg2(jcr, M_FATAL, 0, _("Unimplemented Verify level %d(%c)\n"), jcr->get_JobLevel(),
298 jcr->get_JobLevel());
302 if (!send_runscripts_commands(jcr)) {
307 * Send verify command/level to File daemon
309 fd->fsend(verifycmd, level);
310 if (!response(jcr, fd, OKverify, "Verify", DISPLAY_ERROR)) {
315 * Now get data back from File daemon and
316 * compare it to the catalog or store it in the
317 * catalog depending on the run type.
319 /* Compare to catalog */
320 switch (jcr->get_JobLevel()) {
321 case L_VERIFY_CATALOG:
322 Dmsg0(10, "Verify level=catalog\n");
323 jcr->sd_msg_thread_done = true; /* no SD msg thread, so it is done */
324 jcr->SDJobStatus = JS_Terminated;
325 get_attributes_and_compare_to_catalog(jcr, jcr->previous_jr.JobId);
328 case L_VERIFY_VOLUME_TO_CATALOG:
329 Dmsg0(10, "Verify level=volume\n");
330 get_attributes_and_compare_to_catalog(jcr, jcr->previous_jr.JobId);
333 case L_VERIFY_DISK_TO_CATALOG:
334 Dmsg0(10, "Verify level=disk_to_catalog\n");
335 jcr->sd_msg_thread_done = true; /* no SD msg thread, so it is done */
336 jcr->SDJobStatus = JS_Terminated;
337 get_attributes_and_compare_to_catalog(jcr, jcr->previous_jr.JobId);
342 Dmsg0(10, "Verify level=init\n");
343 jcr->sd_msg_thread_done = true; /* no SD msg thread, so it is done */
344 jcr->SDJobStatus = JS_Terminated;
345 get_attributes_and_put_in_catalog(jcr);
346 db_end_transaction(jcr, jcr->db); /* terminate any open transaction */
347 db_write_batch_file_records(jcr);
351 Jmsg1(jcr, M_FATAL, 0, _("Unimplemented verify level %d\n"), jcr->get_JobLevel());
355 stat = wait_for_job_termination(jcr);
356 verify_cleanup(jcr, stat);
365 * Release resources allocated during backup.
368 void verify_cleanup(JCR *jcr, int TermCode)
370 char sdt[50], edt[50];
371 char ec1[30], ec2[30];
372 char term_code[100], fd_term_msg[100], sd_term_msg[100];
373 const char *term_msg;
378 // Dmsg1(100, "Enter verify_cleanup() TermCod=%d\n", TermCode);
380 Dmsg3(900, "JobLevel=%c Expected=%u JobFiles=%u\n", jcr->get_JobLevel(),
381 jcr->ExpectedFiles, jcr->JobFiles);
382 if (jcr->get_JobLevel() == L_VERIFY_VOLUME_TO_CATALOG &&
383 jcr->ExpectedFiles != jcr->JobFiles) {
384 TermCode = JS_ErrorTerminated;
387 JobId = jcr->jr.JobId;
389 update_job_end(jcr, TermCode);
391 if (job_canceled(jcr)) {
392 cancel_storage_daemon_job(jcr);
395 if (jcr->unlink_bsr && jcr->RestoreBootstrap) {
396 unlink(jcr->RestoreBootstrap);
397 jcr->unlink_bsr = false;
400 msg_type = M_INFO; /* by default INFO message */
403 term_msg = _("Verify OK");
406 case JS_ErrorTerminated:
407 term_msg = _("*** Verify Error ***");
408 msg_type = M_ERROR; /* Generate error message */
411 term_msg = _("Verify warnings");
414 term_msg = _("Verify Canceled");
417 term_msg = _("Verify Differences");
420 term_msg = term_code;
421 bsnprintf(term_code, sizeof(term_code),
422 _("Inappropriate term code: %d %c\n"), TermCode, TermCode);
425 bstrftimes(sdt, sizeof(sdt), jcr->jr.StartTime);
426 bstrftimes(edt, sizeof(edt), jcr->jr.EndTime);
427 if (jcr->verify_job) {
428 Name = jcr->verify_job->hdr.name;
433 jobstatus_to_ascii(jcr->FDJobStatus, fd_term_msg, sizeof(fd_term_msg));
434 if (jcr->get_JobLevel() == L_VERIFY_VOLUME_TO_CATALOG) {
435 jobstatus_to_ascii(jcr->SDJobStatus, sd_term_msg, sizeof(sd_term_msg));
436 Jmsg(jcr, msg_type, 0, _("%s %s %s (%s): %s\n"
437 " Build OS: %s %s %s\n"
441 " Verify Level: %s\n"
443 " Verify JobId: %d\n"
447 " Files Expected: %s\n"
448 " Files Examined: %s\n"
449 " Non-fatal FD errors: %d\n"
450 " FD termination status: %s\n"
451 " SD termination status: %s\n"
452 " Termination: %s\n\n"),
453 BACULA, my_name, VERSION, LSMDATE, edt,
454 HOST_OS, DISTNAME, DISTVER,
457 jcr->fileset->hdr.name,
458 level_to_str(jcr->get_JobLevel()),
459 jcr->client->hdr.name,
460 jcr->previous_jr.JobId,
464 edit_uint64_with_commas(jcr->ExpectedFiles, ec1),
465 edit_uint64_with_commas(jcr->JobFiles, ec2),
471 Jmsg(jcr, msg_type, 0, _("%s %s %s (%s): %s\n"
476 " Verify Level: %s\n"
478 " Verify JobId: %d\n"
482 " Files Examined: %s\n"
483 " Non-fatal FD errors: %d\n"
484 " FD termination status: %s\n"
485 " Termination: %s\n\n"),
486 BACULA, my_name, VERSION, LSMDATE, edt,
487 HOST_OS, DISTNAME, DISTVER,
490 jcr->fileset->hdr.name,
491 level_to_str(jcr->get_JobLevel()),
493 jcr->previous_jr.JobId,
497 edit_uint64_with_commas(jcr->JobFiles, ec1),
502 Dmsg0(100, "Leave verify_cleanup()\n");
506 * This routine is called only during a Verify
508 void get_attributes_and_compare_to_catalog(JCR *jcr, JobId_t JobId)
513 struct stat statf; /* file stat */
514 struct stat statc; /* catalog stat */
516 POOLMEM *fname = get_pool_memory(PM_MESSAGE);
517 int do_Digest = CRYPTO_DIGEST_NONE;
518 int32_t file_index = 0;
520 memset(&fdbr, 0, sizeof(FILE_DBR));
521 fd = jcr->file_bsock;
525 Dmsg0(20, "bdird: waiting to receive file attributes\n");
527 * Get Attributes and Signature from File daemon
531 * Options or Digest (MD5/SHA1)
536 while ((n=bget_dirmsg(fd)) >= 0 && !job_canceled(jcr)) {
539 char Opts_Digest[MAXSTRING]; /* Verify Opts or MD5/SHA1 digest */
541 if (job_canceled(jcr)) {
544 fname = check_pool_memory_size(fname, fd->msglen);
545 jcr->fname = check_pool_memory_size(jcr->fname, fd->msglen);
546 Dmsg1(200, "Atts+Digest=%s\n", fd->msg);
547 if ((len = sscanf(fd->msg, "%ld %d %100s", &file_index, &stream,
549 Jmsg3(jcr, M_FATAL, 0, _("bird<filed: bad attributes, expected 3 fields got %d\n"
550 " mslen=%d msg=%s\n"), len, fd->msglen, fd->msg);
554 * We read the Options or Signature into fname
555 * to prevent overrun, now copy it to proper location.
557 bstrncpy(Opts_Digest, fname, sizeof(Opts_Digest));
559 skip_nonspaces(&p); /* skip FileIndex */
561 skip_nonspaces(&p); /* skip Stream */
563 skip_nonspaces(&p); /* skip Opts_Digest */
564 p++; /* skip space */
567 *fn++ = *p++; /* copy filename */
569 *fn = *p++; /* term filename and point to attribs */
572 * Got attributes stream, decode it
574 if (stream == STREAM_UNIX_ATTRIBUTES || stream == STREAM_UNIX_ATTRIBUTES_EX) {
575 int32_t LinkFIf, LinkFIc;
576 Dmsg2(400, "file_index=%d attr=%s\n", file_index, attr);
578 jcr->FileIndex = file_index; /* remember attribute file_index */
579 jcr->previous_jr.FileIndex = file_index;
580 decode_stat(attr, &statf, &LinkFIf); /* decode file stat packet */
581 do_Digest = CRYPTO_DIGEST_NONE;
582 jcr->fn_printed = false;
583 pm_strcpy(jcr->fname, fname); /* move filename into JCR */
585 Dmsg2(040, "dird<filed: stream=%d %s\n", stream, jcr->fname);
586 Dmsg1(020, "dird<filed: attr=%s\n", attr);
589 * Find equivalent record in the database
592 if (!db_get_file_attributes_record(jcr, jcr->db, jcr->fname,
593 &jcr->previous_jr, &fdbr)) {
594 Jmsg(jcr, M_INFO, 0, _("New file: %s\n"), jcr->fname);
595 Dmsg1(020, _("File not in catalog: %s\n"), jcr->fname);
596 set_jcr_job_status(jcr, JS_Differences);
600 * mark file record as visited by stuffing the
601 * current JobId, which is unique, into the MarkId field.
603 db_mark_file_record(jcr, jcr->db, fdbr.FileId, jcr->JobId);
606 Dmsg3(400, "Found %s in catalog. inx=%d Opts=%s\n", jcr->fname,
607 file_index, Opts_Digest);
608 decode_stat(fdbr.LStat, &statc, &LinkFIc); /* decode catalog stat */
610 * Loop over options supplied by user and verify the
611 * fields he requests.
613 for (p=Opts_Digest; *p; p++) {
614 char ed1[30], ed2[30];
616 case 'i': /* compare INODEs */
617 if (statc.st_ino != statf.st_ino) {
619 Jmsg(jcr, M_INFO, 0, _(" st_ino differ. Cat: %s File: %s\n"),
620 edit_uint64((uint64_t)statc.st_ino, ed1),
621 edit_uint64((uint64_t)statf.st_ino, ed2));
622 set_jcr_job_status(jcr, JS_Differences);
625 case 'p': /* permissions bits */
626 if (statc.st_mode != statf.st_mode) {
628 Jmsg(jcr, M_INFO, 0, _(" st_mode differ. Cat: %x File: %x\n"),
629 (uint32_t)statc.st_mode, (uint32_t)statf.st_mode);
630 set_jcr_job_status(jcr, JS_Differences);
633 case 'n': /* number of links */
634 if (statc.st_nlink != statf.st_nlink) {
636 Jmsg(jcr, M_INFO, 0, _(" st_nlink differ. Cat: %d File: %d\n"),
637 (uint32_t)statc.st_nlink, (uint32_t)statf.st_nlink);
638 set_jcr_job_status(jcr, JS_Differences);
641 case 'u': /* user id */
642 if (statc.st_uid != statf.st_uid) {
644 Jmsg(jcr, M_INFO, 0, _(" st_uid differ. Cat: %u File: %u\n"),
645 (uint32_t)statc.st_uid, (uint32_t)statf.st_uid);
646 set_jcr_job_status(jcr, JS_Differences);
649 case 'g': /* group id */
650 if (statc.st_gid != statf.st_gid) {
652 Jmsg(jcr, M_INFO, 0, _(" st_gid differ. Cat: %u File: %u\n"),
653 (uint32_t)statc.st_gid, (uint32_t)statf.st_gid);
654 set_jcr_job_status(jcr, JS_Differences);
658 if (statc.st_size != statf.st_size) {
660 Jmsg(jcr, M_INFO, 0, _(" st_size differ. Cat: %s File: %s\n"),
661 edit_uint64((uint64_t)statc.st_size, ed1),
662 edit_uint64((uint64_t)statf.st_size, ed2));
663 set_jcr_job_status(jcr, JS_Differences);
666 case 'a': /* access time */
667 if (statc.st_atime != statf.st_atime) {
669 Jmsg(jcr, M_INFO, 0, _(" st_atime differs\n"));
670 set_jcr_job_status(jcr, JS_Differences);
674 if (statc.st_mtime != statf.st_mtime) {
676 Jmsg(jcr, M_INFO, 0, _(" st_mtime differs\n"));
677 set_jcr_job_status(jcr, JS_Differences);
680 case 'c': /* ctime */
681 if (statc.st_ctime != statf.st_ctime) {
683 Jmsg(jcr, M_INFO, 0, _(" st_ctime differs\n"));
684 set_jcr_job_status(jcr, JS_Differences);
687 case 'd': /* file size decrease */
688 if (statc.st_size > statf.st_size) {
690 Jmsg(jcr, M_INFO, 0, _(" st_size decrease. Cat: %s File: %s\n"),
691 edit_uint64((uint64_t)statc.st_size, ed1),
692 edit_uint64((uint64_t)statf.st_size, ed2));
693 set_jcr_job_status(jcr, JS_Differences);
696 case '5': /* compare MD5 */
697 Dmsg1(500, "set Do_MD5 for %s\n", jcr->fname);
698 do_Digest = CRYPTO_DIGEST_MD5;
700 case '1': /* compare SHA1 */
701 do_Digest = CRYPTO_DIGEST_SHA1;
710 * Got Digest Signature from Storage daemon
711 * It came across in the Opts_Digest field.
713 } else if (crypto_digest_stream_type(stream) != CRYPTO_DIGEST_NONE) {
714 Dmsg2(400, "stream=Digest inx=%d Digest=%s\n", file_index, Opts_Digest);
716 * When ever we get a digest it MUST have been
717 * preceded by an attributes record, which sets attr_file_index
719 if (jcr->FileIndex != (uint32_t)file_index) {
720 Jmsg2(jcr, M_FATAL, 0, _("MD5/SHA1 index %d not same as attributes %d\n"),
721 file_index, jcr->FileIndex);
724 if (do_Digest != CRYPTO_DIGEST_NONE) {
725 db_escape_string(jcr, jcr->db, buf, Opts_Digest, strlen(Opts_Digest));
726 if (strcmp(buf, fdbr.Digest) != 0) {
728 Jmsg(jcr, M_INFO, 0, _(" %s differs. File=%s Cat=%s\n"),
729 stream_to_ascii(stream), buf, fdbr.Digest);
730 set_jcr_job_status(jcr, JS_Differences);
732 do_Digest = CRYPTO_DIGEST_NONE;
735 jcr->JobFiles = file_index;
737 if (is_bnet_error(fd)) {
739 Jmsg2(jcr, M_FATAL, 0, _("bdird<filed: bad attributes from filed n=%d : %s\n"),
744 /* Now find all the files that are missing -- i.e. all files in
745 * the database where the MarkId != current JobId
747 jcr->fn_printed = false;
748 bsnprintf(buf, sizeof(buf),
749 "SELECT Path.Path,Filename.Name FROM File,Path,Filename "
750 "WHERE File.JobId=%d "
751 "AND File.MarkId!=%d AND File.PathId=Path.PathId "
752 "AND File.FilenameId=Filename.FilenameId",
754 /* missing_handler is called for each file found */
755 db_sql_query(jcr->db, buf, missing_handler, (void *)jcr);
756 if (jcr->fn_printed) {
757 set_jcr_job_status(jcr, JS_Differences);
759 free_pool_memory(fname);
763 * We are called here for each record that matches the above
764 * SQL query -- that is for each file contained in the Catalog
765 * that was not marked earlier. This means that the file in
766 * question is a missing file (in the Catalog but not on Disk).
768 static int missing_handler(void *ctx, int num_fields, char **row)
770 JCR *jcr = (JCR *)ctx;
772 if (job_canceled(jcr)) {
775 if (!jcr->fn_printed) {
776 Qmsg(jcr, M_WARNING, 0, _("The following files are in the Catalog but not on %s:\n"),
777 jcr->get_JobLevel() == L_VERIFY_VOLUME_TO_CATALOG ? "the Volume(s)" : "disk");
778 jcr->fn_printed = true;
780 Qmsg(jcr, M_INFO, 0, " %s%s\n", row[0]?row[0]:"", row[1]?row[1]:"");
786 * Print filename for verify
788 static void prt_fname(JCR *jcr)
790 if (!jcr->fn_printed) {
791 Jmsg(jcr, M_INFO, 0, _("File: %s\n"), jcr->fname);
792 jcr->fn_printed = true;