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 JobId = jcr->jr.JobId;
388 update_job_end(jcr, TermCode);
390 if (job_canceled(jcr)) {
391 cancel_storage_daemon_job(jcr);
394 if (jcr->unlink_bsr && jcr->RestoreBootstrap) {
395 unlink(jcr->RestoreBootstrap);
396 jcr->unlink_bsr = false;
399 msg_type = M_INFO; /* by default INFO message */
402 term_msg = _("Verify OK");
405 case JS_ErrorTerminated:
406 term_msg = _("*** Verify Error ***");
407 msg_type = M_ERROR; /* Generate error message */
410 term_msg = _("Verify warnings");
413 term_msg = _("Verify Canceled");
416 term_msg = _("Verify Differences");
419 term_msg = term_code;
420 bsnprintf(term_code, sizeof(term_code),
421 _("Inappropriate term code: %d %c\n"), TermCode, TermCode);
424 bstrftimes(sdt, sizeof(sdt), jcr->jr.StartTime);
425 bstrftimes(edt, sizeof(edt), jcr->jr.EndTime);
426 if (jcr->verify_job) {
427 Name = jcr->verify_job->hdr.name;
432 jobstatus_to_ascii(jcr->FDJobStatus, fd_term_msg, sizeof(fd_term_msg));
433 if (jcr->get_JobLevel() == L_VERIFY_VOLUME_TO_CATALOG) {
434 jobstatus_to_ascii(jcr->SDJobStatus, sd_term_msg, sizeof(sd_term_msg));
435 Jmsg(jcr, msg_type, 0, _("%s %s %s (%s): %s\n"
436 " Build OS: %s %s %s\n"
440 " Verify Level: %s\n"
442 " Verify JobId: %d\n"
446 " Files Expected: %s\n"
447 " Files Examined: %s\n"
448 " Non-fatal FD errors: %d\n"
449 " FD termination status: %s\n"
450 " SD termination status: %s\n"
451 " Termination: %s\n\n"),
452 BACULA, my_name, VERSION, LSMDATE, edt,
453 HOST_OS, DISTNAME, DISTVER,
456 jcr->fileset->hdr.name,
457 level_to_str(jcr->get_JobLevel()),
458 jcr->client->hdr.name,
459 jcr->previous_jr.JobId,
463 edit_uint64_with_commas(jcr->ExpectedFiles, ec1),
464 edit_uint64_with_commas(jcr->JobFiles, ec2),
470 Jmsg(jcr, msg_type, 0, _("%s %s %s (%s): %s\n"
475 " Verify Level: %s\n"
477 " Verify JobId: %d\n"
481 " Files Examined: %s\n"
482 " Non-fatal FD errors: %d\n"
483 " FD termination status: %s\n"
484 " Termination: %s\n\n"),
485 BACULA, my_name, VERSION, LSMDATE, edt,
486 HOST_OS, DISTNAME, DISTVER,
489 jcr->fileset->hdr.name,
490 level_to_str(jcr->get_JobLevel()),
492 jcr->previous_jr.JobId,
496 edit_uint64_with_commas(jcr->JobFiles, ec1),
501 Dmsg0(100, "Leave verify_cleanup()\n");
505 * This routine is called only during a Verify
507 void get_attributes_and_compare_to_catalog(JCR *jcr, JobId_t JobId)
512 struct stat statf; /* file stat */
513 struct stat statc; /* catalog stat */
515 POOLMEM *fname = get_pool_memory(PM_MESSAGE);
516 int do_Digest = CRYPTO_DIGEST_NONE;
517 int32_t file_index = 0;
519 memset(&fdbr, 0, sizeof(FILE_DBR));
520 fd = jcr->file_bsock;
524 Dmsg0(20, "bdird: waiting to receive file attributes\n");
526 * Get Attributes and Signature from File daemon
530 * Options or Digest (MD5/SHA1)
535 while ((n=bget_dirmsg(fd)) >= 0 && !job_canceled(jcr)) {
538 char Opts_Digest[MAXSTRING]; /* Verify Opts or MD5/SHA1 digest */
540 if (job_canceled(jcr)) {
543 fname = check_pool_memory_size(fname, fd->msglen);
544 jcr->fname = check_pool_memory_size(jcr->fname, fd->msglen);
545 Dmsg1(200, "Atts+Digest=%s\n", fd->msg);
546 if ((len = sscanf(fd->msg, "%ld %d %100s", &file_index, &stream,
548 Jmsg3(jcr, M_FATAL, 0, _("bird<filed: bad attributes, expected 3 fields got %d\n"
549 " mslen=%d msg=%s\n"), len, fd->msglen, fd->msg);
553 * We read the Options or Signature into fname
554 * to prevent overrun, now copy it to proper location.
556 bstrncpy(Opts_Digest, fname, sizeof(Opts_Digest));
558 skip_nonspaces(&p); /* skip FileIndex */
560 skip_nonspaces(&p); /* skip Stream */
562 skip_nonspaces(&p); /* skip Opts_Digest */
563 p++; /* skip space */
566 *fn++ = *p++; /* copy filename */
568 *fn = *p++; /* term filename and point to attribs */
571 * Got attributes stream, decode it
573 if (stream == STREAM_UNIX_ATTRIBUTES || stream == STREAM_UNIX_ATTRIBUTES_EX) {
574 int32_t LinkFIf, LinkFIc;
575 Dmsg2(400, "file_index=%d attr=%s\n", file_index, attr);
577 jcr->FileIndex = file_index; /* remember attribute file_index */
578 jcr->previous_jr.FileIndex = file_index;
579 decode_stat(attr, &statf, &LinkFIf); /* decode file stat packet */
580 do_Digest = CRYPTO_DIGEST_NONE;
581 jcr->fn_printed = false;
582 pm_strcpy(jcr->fname, fname); /* move filename into JCR */
584 Dmsg2(040, "dird<filed: stream=%d %s\n", stream, jcr->fname);
585 Dmsg1(020, "dird<filed: attr=%s\n", attr);
588 * Find equivalent record in the database
591 if (!db_get_file_attributes_record(jcr, jcr->db, jcr->fname,
592 &jcr->previous_jr, &fdbr)) {
593 Jmsg(jcr, M_INFO, 0, _("New file: %s\n"), jcr->fname);
594 Dmsg1(020, _("File not in catalog: %s\n"), jcr->fname);
595 set_jcr_job_status(jcr, JS_Differences);
599 * mark file record as visited by stuffing the
600 * current JobId, which is unique, into the MarkId field.
602 db_mark_file_record(jcr, jcr->db, fdbr.FileId, jcr->JobId);
605 Dmsg3(400, "Found %s in catalog. inx=%d Opts=%s\n", jcr->fname,
606 file_index, Opts_Digest);
607 decode_stat(fdbr.LStat, &statc, &LinkFIc); /* decode catalog stat */
609 * Loop over options supplied by user and verify the
610 * fields he requests.
612 for (p=Opts_Digest; *p; p++) {
613 char ed1[30], ed2[30];
615 case 'i': /* compare INODEs */
616 if (statc.st_ino != statf.st_ino) {
618 Jmsg(jcr, M_INFO, 0, _(" st_ino differ. Cat: %s File: %s\n"),
619 edit_uint64((uint64_t)statc.st_ino, ed1),
620 edit_uint64((uint64_t)statf.st_ino, ed2));
621 set_jcr_job_status(jcr, JS_Differences);
624 case 'p': /* permissions bits */
625 if (statc.st_mode != statf.st_mode) {
627 Jmsg(jcr, M_INFO, 0, _(" st_mode differ. Cat: %x File: %x\n"),
628 (uint32_t)statc.st_mode, (uint32_t)statf.st_mode);
629 set_jcr_job_status(jcr, JS_Differences);
632 case 'n': /* number of links */
633 if (statc.st_nlink != statf.st_nlink) {
635 Jmsg(jcr, M_INFO, 0, _(" st_nlink differ. Cat: %d File: %d\n"),
636 (uint32_t)statc.st_nlink, (uint32_t)statf.st_nlink);
637 set_jcr_job_status(jcr, JS_Differences);
640 case 'u': /* user id */
641 if (statc.st_uid != statf.st_uid) {
643 Jmsg(jcr, M_INFO, 0, _(" st_uid differ. Cat: %u File: %u\n"),
644 (uint32_t)statc.st_uid, (uint32_t)statf.st_uid);
645 set_jcr_job_status(jcr, JS_Differences);
648 case 'g': /* group id */
649 if (statc.st_gid != statf.st_gid) {
651 Jmsg(jcr, M_INFO, 0, _(" st_gid differ. Cat: %u File: %u\n"),
652 (uint32_t)statc.st_gid, (uint32_t)statf.st_gid);
653 set_jcr_job_status(jcr, JS_Differences);
657 if (statc.st_size != statf.st_size) {
659 Jmsg(jcr, M_INFO, 0, _(" st_size differ. Cat: %s File: %s\n"),
660 edit_uint64((uint64_t)statc.st_size, ed1),
661 edit_uint64((uint64_t)statf.st_size, ed2));
662 set_jcr_job_status(jcr, JS_Differences);
665 case 'a': /* access time */
666 if (statc.st_atime != statf.st_atime) {
668 Jmsg(jcr, M_INFO, 0, _(" st_atime differs\n"));
669 set_jcr_job_status(jcr, JS_Differences);
673 if (statc.st_mtime != statf.st_mtime) {
675 Jmsg(jcr, M_INFO, 0, _(" st_mtime differs\n"));
676 set_jcr_job_status(jcr, JS_Differences);
679 case 'c': /* ctime */
680 if (statc.st_ctime != statf.st_ctime) {
682 Jmsg(jcr, M_INFO, 0, _(" st_ctime differs\n"));
683 set_jcr_job_status(jcr, JS_Differences);
686 case 'd': /* file size decrease */
687 if (statc.st_size > statf.st_size) {
689 Jmsg(jcr, M_INFO, 0, _(" st_size decrease. Cat: %s File: %s\n"),
690 edit_uint64((uint64_t)statc.st_size, ed1),
691 edit_uint64((uint64_t)statf.st_size, ed2));
692 set_jcr_job_status(jcr, JS_Differences);
695 case '5': /* compare MD5 */
696 Dmsg1(500, "set Do_MD5 for %s\n", jcr->fname);
697 do_Digest = CRYPTO_DIGEST_MD5;
699 case '1': /* compare SHA1 */
700 do_Digest = CRYPTO_DIGEST_SHA1;
709 * Got Digest Signature from Storage daemon
710 * It came across in the Opts_Digest field.
712 } else if (crypto_digest_stream_type(stream) != CRYPTO_DIGEST_NONE) {
713 Dmsg2(400, "stream=Digest inx=%d Digest=%s\n", file_index, Opts_Digest);
715 * When ever we get a digest it MUST have been
716 * preceded by an attributes record, which sets attr_file_index
718 if (jcr->FileIndex != (uint32_t)file_index) {
719 Jmsg2(jcr, M_FATAL, 0, _("MD5/SHA1 index %d not same as attributes %d\n"),
720 file_index, jcr->FileIndex);
723 if (do_Digest != CRYPTO_DIGEST_NONE) {
724 db_escape_string(jcr, jcr->db, buf, Opts_Digest, strlen(Opts_Digest));
725 if (strcmp(buf, fdbr.Digest) != 0) {
727 Jmsg(jcr, M_INFO, 0, _(" %s differs. File=%s Cat=%s\n"),
728 stream_to_ascii(stream), buf, fdbr.Digest);
729 set_jcr_job_status(jcr, JS_Differences);
731 do_Digest = CRYPTO_DIGEST_NONE;
734 jcr->JobFiles = file_index;
736 if (is_bnet_error(fd)) {
738 Jmsg2(jcr, M_FATAL, 0, _("bdird<filed: bad attributes from filed n=%d : %s\n"),
743 /* Now find all the files that are missing -- i.e. all files in
744 * the database where the MarkId != current JobId
746 jcr->fn_printed = false;
747 bsnprintf(buf, sizeof(buf),
748 "SELECT Path.Path,Filename.Name FROM File,Path,Filename "
749 "WHERE File.JobId=%d "
750 "AND File.MarkId!=%d AND File.PathId=Path.PathId "
751 "AND File.FilenameId=Filename.FilenameId",
753 /* missing_handler is called for each file found */
754 db_sql_query(jcr->db, buf, missing_handler, (void *)jcr);
755 if (jcr->fn_printed) {
756 set_jcr_job_status(jcr, JS_Differences);
758 free_pool_memory(fname);
762 * We are called here for each record that matches the above
763 * SQL query -- that is for each file contained in the Catalog
764 * that was not marked earlier. This means that the file in
765 * question is a missing file (in the Catalog but not on Disk).
767 static int missing_handler(void *ctx, int num_fields, char **row)
769 JCR *jcr = (JCR *)ctx;
771 if (job_canceled(jcr)) {
774 if (!jcr->fn_printed) {
775 Qmsg(jcr, M_WARNING, 0, _("The following files are in the Catalog but not on %s:\n"),
776 jcr->get_JobLevel() == L_VERIFY_VOLUME_TO_CATALOG ? "the Volume(s)" : "disk");
777 jcr->fn_printed = true;
779 Qmsg(jcr, M_INFO, 0, " %s%s\n", row[0]?row[0]:"", row[1]?row[1]:"");
785 * Print filename for verify
787 static void prt_fname(JCR *jcr)
789 if (!jcr->fn_printed) {
790 Jmsg(jcr, M_INFO, 0, _("File: %s\n"), jcr->fname);
791 jcr->fn_printed = true;