2 Bacula® - The Network Backup Solution
4 Copyright (C) 2000-2010 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
46 #include "findlib/find.h"
48 /* Commands sent to File daemon */
49 static char verifycmd[] = "verify level=%s\n";
50 static char storaddr[] = "storage address=%s port=%d ssl=0 Authorization=%s\n";
52 /* Responses received from File daemon */
53 static char OKverify[] = "2000 OK verify\n";
54 static char OKstore[] = "2000 OK storage\n";
56 /* Responses received from the Storage daemon */
57 static char OKbootstrap[] = "3000 OK bootstrap\n";
59 /* Forward referenced functions */
60 static void prt_fname(JCR *jcr);
61 static int missing_handler(void *ctx, int num_fields, char **row);
65 * Called here before the job is run to do the job
68 bool do_verify_init(JCR *jcr)
70 if (!allow_duplicate_job(jcr)) {
73 switch (jcr->getJobLevel()) {
75 case L_VERIFY_CATALOG:
76 case L_VERIFY_DISK_TO_CATALOG:
80 case L_VERIFY_VOLUME_TO_CATALOG:
86 Jmsg2(jcr, M_FATAL, 0, _("Unimplemented Verify level %d(%c)\n"), jcr->getJobLevel(),
95 * Do a verification of the specified files against the Catlaog
97 * Returns: false on failure
100 bool do_verify(JCR *jcr)
107 JobId_t verify_jobid = 0;
110 free_wstorage(jcr); /* we don't write */
112 memset(&jcr->previous_jr, 0, sizeof(jcr->previous_jr));
115 * Find JobId of last job that ran. Note, we do this when
116 * the job actually starts running, not at schedule time,
117 * so that we find the last job that terminated before
118 * this job runs rather than before it is scheduled. This
119 * permits scheduling a Backup and Verify at the same time,
120 * but with the Verify at a lower priority.
122 * For VERIFY_CATALOG we want the JobId of the last INIT.
123 * For VERIFY_VOLUME_TO_CATALOG, we want the JobId of the
126 if (jcr->getJobLevel() == L_VERIFY_CATALOG ||
127 jcr->getJobLevel() == L_VERIFY_VOLUME_TO_CATALOG ||
128 jcr->getJobLevel() == L_VERIFY_DISK_TO_CATALOG) {
129 memcpy(&jr, &jcr->jr, sizeof(jr));
130 if (jcr->verify_job &&
131 (jcr->getJobLevel() == L_VERIFY_VOLUME_TO_CATALOG ||
132 jcr->getJobLevel() == L_VERIFY_DISK_TO_CATALOG)) {
133 Name = jcr->verify_job->name();
137 Dmsg1(100, "find last jobid for: %s\n", NPRT(Name));
138 if (!db_find_last_jobid(jcr, jcr->db, Name, &jr)) {
139 if (jcr->getJobLevel() == L_VERIFY_CATALOG) {
140 Jmsg(jcr, M_FATAL, 0, _(
141 "Unable to find JobId of previous InitCatalog Job.\n"
142 "Please run a Verify with Level=InitCatalog before\n"
143 "running the current Job.\n"));
145 Jmsg(jcr, M_FATAL, 0, _(
146 "Unable to find JobId of previous Job for this client.\n"));
150 verify_jobid = jr.JobId;
151 Dmsg1(100, "Last full jobid=%d\n", verify_jobid);
154 * Now get the job record for the previous backup that interests
155 * us. We use the verify_jobid that we found above.
157 if (jcr->getJobLevel() == L_VERIFY_CATALOG ||
158 jcr->getJobLevel() == L_VERIFY_VOLUME_TO_CATALOG ||
159 jcr->getJobLevel() == L_VERIFY_DISK_TO_CATALOG) {
160 jcr->previous_jr.JobId = verify_jobid;
161 if (!db_get_job_record(jcr, jcr->db, &jcr->previous_jr)) {
162 Jmsg(jcr, M_FATAL, 0, _("Could not get job record for previous Job. ERR=%s"),
163 db_strerror(jcr->db));
166 if (!(jcr->previous_jr.JobStatus == JS_Terminated ||
167 jcr->previous_jr.JobStatus == JS_Warnings)) {
168 Jmsg(jcr, M_FATAL, 0, _("Last Job %d did not terminate normally. JobStatus=%c\n"),
169 verify_jobid, jcr->previous_jr.JobStatus);
172 Jmsg(jcr, M_INFO, 0, _("Verifying against JobId=%d Job=%s\n"),
173 jcr->previous_jr.JobId, jcr->previous_jr.Job);
177 * If we are verifying a Volume, we need the Storage
178 * daemon, so open a connection, otherwise, just
179 * create a dummy authorization key (passed to
180 * File daemon but not used).
182 if (jcr->getJobLevel() == L_VERIFY_VOLUME_TO_CATALOG) {
185 * Note: negative status is an error, zero status, means
186 * no files were backed up, so skip calling SD and
189 stat = create_restore_bootstrap_file(jcr);
190 if (stat < 0) { /* error */
192 } else if (stat == 0) { /* No files, nothing to do */
193 verify_cleanup(jcr, JS_Terminated); /* clean up */
194 return true; /* get out */
197 jcr->sd_auth_key = bstrdup("dummy"); /* dummy Storage daemon key */
200 if (jcr->getJobLevel() == L_VERIFY_DISK_TO_CATALOG && jcr->verify_job) {
201 jcr->fileset = jcr->verify_job->fileset;
203 Dmsg2(100, "ClientId=%u JobLevel=%c\n", jcr->previous_jr.ClientId, jcr->getJobLevel());
205 if (!db_update_job_start_record(jcr, jcr->db, &jcr->jr)) {
206 Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
210 /* Print Job Start message */
211 Jmsg(jcr, M_INFO, 0, _("Start Verify JobId=%s Level=%s Job=%s\n"),
212 edit_uint64(jcr->JobId, ed1), level_to_str(jcr->getJobLevel()), jcr->Job);
214 if (jcr->getJobLevel() == L_VERIFY_VOLUME_TO_CATALOG) {
217 * Start conversation with Storage daemon
219 set_jcr_job_status(jcr, JS_Blocked);
220 if (!connect_to_storage_daemon(jcr, 10, SDConnectTimeout, 1)) {
224 * Now start a job with the Storage daemon
226 if (!start_storage_daemon_job(jcr, jcr->rstorage, NULL)) {
229 sd = jcr->store_bsock;
231 * Send the bootstrap file -- what Volumes/files to restore
233 if (!send_bootstrap_file(jcr, sd) ||
234 !response(jcr, sd, OKbootstrap, "Bootstrap", DISPLAY_ERROR)) {
237 if (!sd->fsend("run")) {
241 * Now start a Storage daemon message thread
243 if (!start_storage_daemon_message_thread(jcr)) {
246 Dmsg0(50, "Storage daemon connection OK\n");
250 * OK, now connect to the File daemon
251 * and ask him for the files.
253 set_jcr_job_status(jcr, JS_Blocked);
254 if (!connect_to_file_daemon(jcr, 10, FDConnectTimeout, 1)) {
258 set_jcr_job_status(jcr, JS_Running);
259 fd = jcr->file_bsock;
262 Dmsg0(30, ">filed: Send include list\n");
263 if (!send_include_list(jcr)) {
267 Dmsg0(30, ">filed: Send exclude list\n");
268 if (!send_exclude_list(jcr)) {
273 * Send Level command to File daemon, as well
274 * as the Storage address if appropriate.
276 switch (jcr->getJobLevel()) {
280 case L_VERIFY_CATALOG:
283 case L_VERIFY_VOLUME_TO_CATALOG:
285 * send Storage daemon address to the File daemon
287 if (jcr->rstore->SDDport == 0) {
288 jcr->rstore->SDDport = jcr->rstore->SDport;
290 bnet_fsend(fd, storaddr, jcr->rstore->address,
291 jcr->rstore->SDDport, jcr->sd_auth_key);
292 if (!response(jcr, fd, OKstore, "Storage", DISPLAY_ERROR)) {
296 if (!jcr->RestoreBootstrap) {
297 Jmsg0(jcr, M_FATAL, 0, _("Deprecated feature ... use bootstrap.\n"));
306 case L_VERIFY_DISK_TO_CATALOG:
307 level="disk_to_catalog";
310 Jmsg2(jcr, M_FATAL, 0, _("Unimplemented Verify level %d(%c)\n"), jcr->getJobLevel(),
315 if (!send_runscripts_commands(jcr)) {
320 * Send verify command/level to File daemon
322 fd->fsend(verifycmd, level);
323 if (!response(jcr, fd, OKverify, "Verify", DISPLAY_ERROR)) {
328 * Now get data back from File daemon and
329 * compare it to the catalog or store it in the
330 * catalog depending on the run type.
332 /* Compare to catalog */
333 switch (jcr->getJobLevel()) {
334 case L_VERIFY_CATALOG:
335 Dmsg0(10, "Verify level=catalog\n");
336 jcr->sd_msg_thread_done = true; /* no SD msg thread, so it is done */
337 jcr->SDJobStatus = JS_Terminated;
338 get_attributes_and_compare_to_catalog(jcr, jcr->previous_jr.JobId);
341 case L_VERIFY_VOLUME_TO_CATALOG:
342 Dmsg0(10, "Verify level=volume\n");
343 get_attributes_and_compare_to_catalog(jcr, jcr->previous_jr.JobId);
346 case L_VERIFY_DISK_TO_CATALOG:
347 Dmsg0(10, "Verify level=disk_to_catalog\n");
348 jcr->sd_msg_thread_done = true; /* no SD msg thread, so it is done */
349 jcr->SDJobStatus = JS_Terminated;
350 get_attributes_and_compare_to_catalog(jcr, jcr->previous_jr.JobId);
355 Dmsg0(10, "Verify level=init\n");
356 jcr->sd_msg_thread_done = true; /* no SD msg thread, so it is done */
357 jcr->SDJobStatus = JS_Terminated;
358 get_attributes_and_put_in_catalog(jcr);
359 db_end_transaction(jcr, jcr->db); /* terminate any open transaction */
360 db_write_batch_file_records(jcr);
364 Jmsg1(jcr, M_FATAL, 0, _("Unimplemented verify level %d\n"), jcr->getJobLevel());
368 stat = wait_for_job_termination(jcr);
369 verify_cleanup(jcr, stat);
378 * Release resources allocated during backup.
381 void verify_cleanup(JCR *jcr, int TermCode)
383 char sdt[50], edt[50];
384 char ec1[30], ec2[30];
385 char term_code[100], fd_term_msg[100], sd_term_msg[100];
386 const char *term_msg;
391 // Dmsg1(100, "Enter verify_cleanup() TermCod=%d\n", TermCode);
393 Dmsg3(900, "JobLevel=%c Expected=%u JobFiles=%u\n", jcr->getJobLevel(),
394 jcr->ExpectedFiles, jcr->JobFiles);
395 if (jcr->getJobLevel() == L_VERIFY_VOLUME_TO_CATALOG &&
396 jcr->ExpectedFiles != jcr->JobFiles) {
397 TermCode = JS_ErrorTerminated;
400 JobId = jcr->jr.JobId;
402 update_job_end(jcr, TermCode);
404 if (job_canceled(jcr)) {
405 cancel_storage_daemon_job(jcr);
408 if (jcr->unlink_bsr && jcr->RestoreBootstrap) {
409 unlink(jcr->RestoreBootstrap);
410 jcr->unlink_bsr = false;
413 msg_type = M_INFO; /* by default INFO message */
416 term_msg = _("Verify OK");
419 case JS_ErrorTerminated:
420 term_msg = _("*** Verify Error ***");
421 msg_type = M_ERROR; /* Generate error message */
424 term_msg = _("Verify warnings");
427 term_msg = _("Verify Canceled");
430 term_msg = _("Verify Differences");
433 term_msg = term_code;
434 bsnprintf(term_code, sizeof(term_code),
435 _("Inappropriate term code: %d %c\n"), TermCode, TermCode);
438 bstrftimes(sdt, sizeof(sdt), jcr->jr.StartTime);
439 bstrftimes(edt, sizeof(edt), jcr->jr.EndTime);
440 if (jcr->verify_job) {
441 Name = jcr->verify_job->hdr.name;
446 jobstatus_to_ascii(jcr->FDJobStatus, fd_term_msg, sizeof(fd_term_msg));
447 if (jcr->getJobLevel() == L_VERIFY_VOLUME_TO_CATALOG) {
448 jobstatus_to_ascii(jcr->SDJobStatus, sd_term_msg, sizeof(sd_term_msg));
449 Jmsg(jcr, msg_type, 0, _("%s %s %s (%s): %s\n"
450 " Build OS: %s %s %s\n"
454 " Verify Level: %s\n"
456 " Verify JobId: %d\n"
460 " Files Expected: %s\n"
461 " Files Examined: %s\n"
462 " Non-fatal FD errors: %d\n"
463 " FD termination status: %s\n"
464 " SD termination status: %s\n"
465 " Termination: %s\n\n"),
466 BACULA, my_name, VERSION, LSMDATE, edt,
467 HOST_OS, DISTNAME, DISTVER,
470 jcr->fileset->hdr.name,
471 level_to_str(jcr->getJobLevel()),
472 jcr->client->hdr.name,
473 jcr->previous_jr.JobId,
477 edit_uint64_with_commas(jcr->ExpectedFiles, ec1),
478 edit_uint64_with_commas(jcr->JobFiles, ec2),
484 Jmsg(jcr, msg_type, 0, _("%s %s %s (%s): %s\n"
489 " Verify Level: %s\n"
491 " Verify JobId: %d\n"
495 " Files Examined: %s\n"
496 " Non-fatal FD errors: %d\n"
497 " FD termination status: %s\n"
498 " Termination: %s\n\n"),
499 BACULA, my_name, VERSION, LSMDATE, edt,
500 HOST_OS, DISTNAME, DISTVER,
503 jcr->fileset->hdr.name,
504 level_to_str(jcr->getJobLevel()),
506 jcr->previous_jr.JobId,
510 edit_uint64_with_commas(jcr->JobFiles, ec1),
515 Dmsg0(100, "Leave verify_cleanup()\n");
519 * This routine is called only during a Verify
521 void get_attributes_and_compare_to_catalog(JCR *jcr, JobId_t JobId)
526 struct stat statf; /* file stat */
527 struct stat statc; /* catalog stat */
529 POOLMEM *fname = get_pool_memory(PM_MESSAGE);
530 int do_Digest = CRYPTO_DIGEST_NONE;
531 int32_t file_index = 0;
533 memset(&fdbr, 0, sizeof(FILE_DBR));
534 fd = jcr->file_bsock;
538 Dmsg0(20, "bdird: waiting to receive file attributes\n");
540 * Get Attributes and Signature from File daemon
544 * Options or Digest (MD5/SHA1)
549 while ((n=bget_dirmsg(fd)) >= 0 && !job_canceled(jcr)) {
552 char Opts_Digest[MAXSTRING]; /* Verify Opts or MD5/SHA1 digest */
554 if (job_canceled(jcr)) {
557 fname = check_pool_memory_size(fname, fd->msglen);
558 jcr->fname = check_pool_memory_size(jcr->fname, fd->msglen);
559 Dmsg1(200, "Atts+Digest=%s\n", fd->msg);
560 if ((len = sscanf(fd->msg, "%ld %d %100s", &file_index, &stream,
562 Jmsg3(jcr, M_FATAL, 0, _("bird<filed: bad attributes, expected 3 fields got %d\n"
563 " mslen=%d msg=%s\n"), len, fd->msglen, fd->msg);
567 * We read the Options or Signature into fname
568 * to prevent overrun, now copy it to proper location.
570 bstrncpy(Opts_Digest, fname, sizeof(Opts_Digest));
572 skip_nonspaces(&p); /* skip FileIndex */
574 skip_nonspaces(&p); /* skip Stream */
576 skip_nonspaces(&p); /* skip Opts_Digest */
577 p++; /* skip space */
580 *fn++ = *p++; /* copy filename */
582 *fn = *p++; /* term filename and point to attribs */
585 * Got attributes stream, decode it
587 if (stream == STREAM_UNIX_ATTRIBUTES || stream == STREAM_UNIX_ATTRIBUTES_EX) {
588 int32_t LinkFIf, LinkFIc;
589 Dmsg2(400, "file_index=%d attr=%s\n", file_index, attr);
591 jcr->FileIndex = file_index; /* remember attribute file_index */
592 jcr->previous_jr.FileIndex = file_index;
593 decode_stat(attr, &statf, &LinkFIf); /* decode file stat packet */
594 do_Digest = CRYPTO_DIGEST_NONE;
595 jcr->fn_printed = false;
596 pm_strcpy(jcr->fname, fname); /* move filename into JCR */
598 Dmsg2(040, "dird<filed: stream=%d %s\n", stream, jcr->fname);
599 Dmsg1(020, "dird<filed: attr=%s\n", attr);
602 * Find equivalent record in the database
605 if (!db_get_file_attributes_record(jcr, jcr->db, jcr->fname,
606 &jcr->previous_jr, &fdbr)) {
607 Jmsg(jcr, M_INFO, 0, _("New file: %s\n"), jcr->fname);
608 Dmsg1(020, _("File not in catalog: %s\n"), jcr->fname);
609 set_jcr_job_status(jcr, JS_Differences);
613 * mark file record as visited by stuffing the
614 * current JobId, which is unique, into the MarkId field.
616 db_mark_file_record(jcr, jcr->db, fdbr.FileId, jcr->JobId);
619 Dmsg3(400, "Found %s in catalog. inx=%d Opts=%s\n", jcr->fname,
620 file_index, Opts_Digest);
621 decode_stat(fdbr.LStat, &statc, &LinkFIc); /* decode catalog stat */
623 * Loop over options supplied by user and verify the
624 * fields he requests.
626 for (p=Opts_Digest; *p; p++) {
627 char ed1[30], ed2[30];
629 case 'i': /* compare INODEs */
630 if (statc.st_ino != statf.st_ino) {
632 Jmsg(jcr, M_INFO, 0, _(" st_ino differ. Cat: %s File: %s\n"),
633 edit_uint64((uint64_t)statc.st_ino, ed1),
634 edit_uint64((uint64_t)statf.st_ino, ed2));
635 set_jcr_job_status(jcr, JS_Differences);
638 case 'p': /* permissions bits */
639 if (statc.st_mode != statf.st_mode) {
641 Jmsg(jcr, M_INFO, 0, _(" st_mode differ. Cat: %x File: %x\n"),
642 (uint32_t)statc.st_mode, (uint32_t)statf.st_mode);
643 set_jcr_job_status(jcr, JS_Differences);
646 case 'n': /* number of links */
647 if (statc.st_nlink != statf.st_nlink) {
649 Jmsg(jcr, M_INFO, 0, _(" st_nlink differ. Cat: %d File: %d\n"),
650 (uint32_t)statc.st_nlink, (uint32_t)statf.st_nlink);
651 set_jcr_job_status(jcr, JS_Differences);
654 case 'u': /* user id */
655 if (statc.st_uid != statf.st_uid) {
657 Jmsg(jcr, M_INFO, 0, _(" st_uid differ. Cat: %u File: %u\n"),
658 (uint32_t)statc.st_uid, (uint32_t)statf.st_uid);
659 set_jcr_job_status(jcr, JS_Differences);
662 case 'g': /* group id */
663 if (statc.st_gid != statf.st_gid) {
665 Jmsg(jcr, M_INFO, 0, _(" st_gid differ. Cat: %u File: %u\n"),
666 (uint32_t)statc.st_gid, (uint32_t)statf.st_gid);
667 set_jcr_job_status(jcr, JS_Differences);
671 if (statc.st_size != statf.st_size) {
673 Jmsg(jcr, M_INFO, 0, _(" st_size differ. Cat: %s File: %s\n"),
674 edit_uint64((uint64_t)statc.st_size, ed1),
675 edit_uint64((uint64_t)statf.st_size, ed2));
676 set_jcr_job_status(jcr, JS_Differences);
679 case 'a': /* access time */
680 if (statc.st_atime != statf.st_atime) {
682 Jmsg(jcr, M_INFO, 0, _(" st_atime differs\n"));
683 set_jcr_job_status(jcr, JS_Differences);
687 if (statc.st_mtime != statf.st_mtime) {
689 Jmsg(jcr, M_INFO, 0, _(" st_mtime differs\n"));
690 set_jcr_job_status(jcr, JS_Differences);
693 case 'c': /* ctime */
694 if (statc.st_ctime != statf.st_ctime) {
696 Jmsg(jcr, M_INFO, 0, _(" st_ctime differs\n"));
697 set_jcr_job_status(jcr, JS_Differences);
700 case 'd': /* file size decrease */
701 if (statc.st_size > statf.st_size) {
703 Jmsg(jcr, M_INFO, 0, _(" st_size decrease. Cat: %s File: %s\n"),
704 edit_uint64((uint64_t)statc.st_size, ed1),
705 edit_uint64((uint64_t)statf.st_size, ed2));
706 set_jcr_job_status(jcr, JS_Differences);
709 case '5': /* compare MD5 */
710 Dmsg1(500, "set Do_MD5 for %s\n", jcr->fname);
711 do_Digest = CRYPTO_DIGEST_MD5;
713 case '1': /* compare SHA1 */
714 do_Digest = CRYPTO_DIGEST_SHA1;
723 * Got Digest Signature from Storage daemon
724 * It came across in the Opts_Digest field.
726 } else if (crypto_digest_stream_type(stream) != CRYPTO_DIGEST_NONE) {
727 Dmsg2(400, "stream=Digest inx=%d Digest=%s\n", file_index, Opts_Digest);
729 * When ever we get a digest it MUST have been
730 * preceded by an attributes record, which sets attr_file_index
732 if (jcr->FileIndex != (uint32_t)file_index) {
733 Jmsg2(jcr, M_FATAL, 0, _("MD5/SHA1 index %d not same as attributes %d\n"),
734 file_index, jcr->FileIndex);
737 if (do_Digest != CRYPTO_DIGEST_NONE) {
738 db_escape_string(jcr, jcr->db, buf, Opts_Digest, strlen(Opts_Digest));
739 if (strcmp(buf, fdbr.Digest) != 0) {
741 Jmsg(jcr, M_INFO, 0, _(" %s differs. File=%s Cat=%s\n"),
742 stream_to_ascii(stream), buf, fdbr.Digest);
743 set_jcr_job_status(jcr, JS_Differences);
745 do_Digest = CRYPTO_DIGEST_NONE;
748 jcr->JobFiles = file_index;
750 if (is_bnet_error(fd)) {
752 Jmsg2(jcr, M_FATAL, 0, _("bdird<filed: bad attributes from filed n=%d : %s\n"),
757 /* Now find all the files that are missing -- i.e. all files in
758 * the database where the MarkId != current JobId
760 jcr->fn_printed = false;
761 bsnprintf(buf, sizeof(buf),
762 "SELECT Path.Path,Filename.Name FROM File,Path,Filename "
763 "WHERE File.JobId=%d AND File.FileIndex > 0 "
764 "AND File.MarkId!=%d AND File.PathId=Path.PathId "
765 "AND File.FilenameId=Filename.FilenameId",
767 /* missing_handler is called for each file found */
768 db_sql_query(jcr->db, buf, missing_handler, (void *)jcr);
769 if (jcr->fn_printed) {
770 set_jcr_job_status(jcr, JS_Differences);
772 free_pool_memory(fname);
776 * We are called here for each record that matches the above
777 * SQL query -- that is for each file contained in the Catalog
778 * that was not marked earlier. This means that the file in
779 * question is a missing file (in the Catalog but not on Disk).
781 static int missing_handler(void *ctx, int num_fields, char **row)
783 JCR *jcr = (JCR *)ctx;
785 if (job_canceled(jcr)) {
788 if (!jcr->fn_printed) {
789 Qmsg(jcr, M_WARNING, 0, _("The following files are in the Catalog but not on %s:\n"),
790 jcr->getJobLevel() == L_VERIFY_VOLUME_TO_CATALOG ? "the Volume(s)" : "disk");
791 jcr->fn_printed = true;
793 Qmsg(jcr, M_INFO, 0, " %s%s\n", row[0]?row[0]:"", row[1]?row[1]:"");
799 * Print filename for verify
801 static void prt_fname(JCR *jcr)
803 if (!jcr->fn_printed) {
804 Jmsg(jcr, M_INFO, 0, _("File: %s\n"), jcr->fname);
805 jcr->fn_printed = true;