2 Bacula(R) - The Network Backup Solution
4 Copyright (C) 2000-2015 Kern Sibbald
5 Copyright (C) 2000-2014 Free Software Foundation Europe e.V.
7 The original author of Bacula is Kern Sibbald, with contributions
8 from many others, a complete list can be found in the file AUTHORS.
10 You may use this file and others of this release according to the
11 license defined in the LICENSE file, which includes the Affero General
12 Public License, v3.0 ("AGPLv3") and some additional permissions and
13 terms pursuant to its AGPLv3 Section 7.
15 This notice must be preserved when any source code is
16 conveyed and/or propagated.
18 Bacula(R) is a registered trademark of Kern Sibbald.
22 * Bacula Director -- verify.c -- responsible for running file verification
24 * Kern Sibbald, October MM
26 * Basic tasks done here:
28 * Open connection with File daemon and pass him commands
30 * When the File daemon sends the attributes, compare them to
38 #include "findlib/find.h"
40 /* Commands sent to File daemon */
41 static char verifycmd[] = "verify level=%s\n";
43 /* Responses received from File daemon */
44 static char OKverify[] = "2000 OK verify\n";
46 /* Commands received from Storage daemon */
47 static char OKbootstrap[] = "3000 OK bootstrap\n";
49 /* Forward referenced functions */
50 static void prt_fname(JCR *jcr);
51 static int missing_handler(void *ctx, int num_fields, char **row);
54 * Called here before the job is run to do the job
57 bool do_verify_init(JCR *jcr)
59 if (!allow_duplicate_job(jcr)) {
62 switch (jcr->getJobLevel()) {
64 case L_VERIFY_CATALOG:
65 case L_VERIFY_DISK_TO_CATALOG:
69 case L_VERIFY_VOLUME_TO_CATALOG:
75 Jmsg2(jcr, M_FATAL, 0, _("Unimplemented Verify level %d(%c)\n"), jcr->getJobLevel(),
84 * Do a verification of the specified files against the Catlaog
86 * Returns: false on failure
89 bool do_verify(JCR *jcr)
96 JobId_t verify_jobid = 0;
101 free_wstorage(jcr); /* we don't write */
103 memset(&jcr->previous_jr, 0, sizeof(jcr->previous_jr));
106 * Find JobId of last job that ran. Note, we do this when
107 * the job actually starts running, not at schedule time,
108 * so that we find the last job that terminated before
109 * this job runs rather than before it is scheduled. This
110 * permits scheduling a Backup and Verify at the same time,
111 * but with the Verify at a lower priority.
113 * For VERIFY_CATALOG we want the JobId of the last INIT.
114 * For VERIFY_VOLUME_TO_CATALOG, we want the JobId of the
117 if (jcr->getJobLevel() == L_VERIFY_CATALOG ||
118 jcr->getJobLevel() == L_VERIFY_VOLUME_TO_CATALOG ||
119 jcr->getJobLevel() == L_VERIFY_DISK_TO_CATALOG) {
120 memcpy(&jr, &jcr->jr, sizeof(jr));
121 if (jcr->verify_job &&
122 (jcr->getJobLevel() == L_VERIFY_VOLUME_TO_CATALOG ||
123 jcr->getJobLevel() == L_VERIFY_DISK_TO_CATALOG)) {
124 Name = jcr->verify_job->name();
128 Dmsg1(100, "find last jobid for: %s\n", NPRT(Name));
130 /* see if user supplied a jobid= as run argument or from menu */
131 if (jcr->RestoreJobId) {
132 verify_jobid = jcr->RestoreJobId;
133 Dmsg1(100, "Supplied jobid=%d\n", verify_jobid);
136 if (!db_find_last_jobid(jcr, jcr->db, Name, &jr)) {
137 if (jcr->getJobLevel() == L_VERIFY_CATALOG) {
138 Jmsg(jcr, M_FATAL, 0, _(
139 "Unable to find JobId of previous InitCatalog Job.\n"
140 "Please run a Verify with Level=InitCatalog before\n"
141 "running the current Job.\n"));
143 Jmsg(jcr, M_FATAL, 0, _(
144 "Unable to find JobId of previous Job for this client.\n"));
148 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->getJobLevel() == L_VERIFY_CATALOG ||
157 jcr->getJobLevel() == L_VERIFY_VOLUME_TO_CATALOG ||
158 jcr->getJobLevel() == 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->getJobLevel() == L_VERIFY_VOLUME_TO_CATALOG) {
184 * Note: negative status is an error, zero status, means
185 * no files were backed up, so skip calling SD and
188 stat = create_restore_bootstrap_file(jcr);
189 if (stat < 0) { /* error */
191 } else if (stat == 0) { /* No files, nothing to do */
192 verify_cleanup(jcr, JS_Terminated); /* clean up */
193 return true; /* get out */
196 jcr->sd_auth_key = bstrdup("dummy"); /* dummy Storage daemon key */
199 if (jcr->getJobLevel() == L_VERIFY_DISK_TO_CATALOG && jcr->verify_job) {
200 jcr->fileset = jcr->verify_job->fileset;
202 Dmsg2(100, "ClientId=%u JobLevel=%c\n", jcr->previous_jr.ClientId, jcr->getJobLevel());
204 if (!db_update_job_start_record(jcr, jcr->db, &jcr->jr)) {
205 Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
209 /* Print Job Start message */
210 Jmsg(jcr, M_INFO, 0, _("Start Verify JobId=%s Level=%s Job=%s\n"),
211 edit_uint64(jcr->JobId, ed1), level_to_str(jcr->getJobLevel()), jcr->Job);
213 if (jcr->getJobLevel() == L_VERIFY_VOLUME_TO_CATALOG) {
215 * Start conversation with Storage daemon
217 jcr->setJobStatus(JS_Blocked);
218 if (!connect_to_storage_daemon(jcr, 10, SDConnectTimeout, 1)) {
222 * Now start a job with the Storage daemon
224 if (!start_storage_daemon_job(jcr, jcr->rstorage, NULL)) {
227 sd = jcr->store_bsock;
228 jcr->sd_calls_client = jcr->client->sd_calls_client;
230 * Send the bootstrap file -- what Volumes/files to restore
232 if (!send_bootstrap_file(jcr, sd) ||
233 !response(jcr, sd, OKbootstrap, "Bootstrap", DISPLAY_ERROR)) {
236 if (!jcr->sd_calls_client) {
237 if (!run_storage_and_start_message_thread(jcr, sd)) {
243 * OK, now connect to the File daemon
244 * and ask him for the files.
246 jcr->setJobStatus(JS_Blocked);
247 if (!connect_to_file_daemon(jcr, 10, FDConnectTimeout, 1)) {
251 jcr->setJobStatus(JS_Running);
252 fd = jcr->file_bsock;
255 Dmsg0(30, ">filed: Send include list\n");
256 if (!send_include_list(jcr)) {
260 Dmsg0(30, ">filed: Send exclude list\n");
261 if (!send_exclude_list(jcr)) {
266 * Send Level command to File daemon, as well
267 * as the Storage address if appropriate.
269 switch (jcr->getJobLevel()) {
273 case L_VERIFY_CATALOG:
276 case L_VERIFY_VOLUME_TO_CATALOG:
277 if (jcr->sd_calls_client) {
278 if (jcr->FDVersion < 10) {
279 Jmsg(jcr, M_FATAL, 0, _("The File daemon does not support SDCallsClient.\n"));
283 if (!send_client_addr_to_sd(jcr)) {
287 if (!run_storage_and_start_message_thread(jcr, jcr->store_bsock)) {
290 store_address = jcr->rstore->address; /* dummy */
291 store_port = 0; /* flag that SD calls FD */
294 * send Storage daemon address to the File daemon
296 if (jcr->rstore->SDDport == 0) {
297 jcr->rstore->SDDport = jcr->rstore->SDport;
300 store_address = get_storage_address(jcr->client, jcr->rstore);
301 store_port = jcr->rstore->SDDport;
304 if (!send_store_addr_to_fd(jcr, jcr->rstore, store_address, store_port)) {
308 if (!jcr->RestoreBootstrap) {
309 Jmsg0(jcr, M_FATAL, 0, _("Deprecated feature ... use bootstrap.\n"));
318 case L_VERIFY_DISK_TO_CATALOG:
319 level="disk_to_catalog";
322 Jmsg2(jcr, M_FATAL, 0, _("Unimplemented Verify level %d(%c)\n"), jcr->getJobLevel(),
327 if (!send_runscripts_commands(jcr)) {
332 * Send verify command/level to File daemon
334 fd->fsend(verifycmd, level);
335 if (!response(jcr, fd, OKverify, "Verify", DISPLAY_ERROR)) {
340 * Now get data back from File daemon and
341 * compare it to the catalog or store it in the
342 * catalog depending on the run type.
344 /* Compare to catalog */
345 switch (jcr->getJobLevel()) {
346 case L_VERIFY_CATALOG:
347 Dmsg0(10, "Verify level=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);
353 case L_VERIFY_VOLUME_TO_CATALOG:
354 Dmsg0(10, "Verify level=volume\n");
355 get_attributes_and_compare_to_catalog(jcr, jcr->previous_jr.JobId);
358 case L_VERIFY_DISK_TO_CATALOG:
359 Dmsg0(10, "Verify level=disk_to_catalog\n");
360 jcr->sd_msg_thread_done = true; /* no SD msg thread, so it is done */
361 jcr->SDJobStatus = JS_Terminated;
362 get_attributes_and_compare_to_catalog(jcr, jcr->previous_jr.JobId);
367 Dmsg0(10, "Verify level=init\n");
368 jcr->sd_msg_thread_done = true; /* no SD msg thread, so it is done */
369 jcr->SDJobStatus = JS_Terminated;
370 get_attributes_and_put_in_catalog(jcr);
371 db_end_transaction(jcr, jcr->db); /* terminate any open transaction */
372 db_write_batch_file_records(jcr);
376 Jmsg1(jcr, M_FATAL, 0, _("Unimplemented verify level %d\n"), jcr->getJobLevel());
380 stat = wait_for_job_termination(jcr);
381 verify_cleanup(jcr, stat);
390 * Release resources allocated during backup.
393 void verify_cleanup(JCR *jcr, int TermCode)
395 char sdt[50], edt[50];
396 char ec1[30], ec2[30];
397 char term_code[100], fd_term_msg[100], sd_term_msg[100];
398 const char *term_msg;
402 // Dmsg1(100, "Enter verify_cleanup() TermCod=%d\n", TermCode);
404 Dmsg3(900, "JobLevel=%c Expected=%u JobFiles=%u\n", jcr->getJobLevel(),
405 jcr->ExpectedFiles, jcr->JobFiles);
406 if (jcr->getJobLevel() == L_VERIFY_VOLUME_TO_CATALOG &&
407 jcr->ExpectedFiles != jcr->JobFiles) {
408 TermCode = JS_ErrorTerminated;
411 update_job_end(jcr, TermCode);
413 if (job_canceled(jcr)) {
414 cancel_storage_daemon_job(jcr);
417 if (jcr->unlink_bsr && jcr->RestoreBootstrap) {
418 unlink(jcr->RestoreBootstrap);
419 jcr->unlink_bsr = false;
422 msg_type = M_INFO; /* by default INFO message */
425 term_msg = _("Verify OK");
428 case JS_ErrorTerminated:
429 term_msg = _("*** Verify Error ***");
430 msg_type = M_ERROR; /* Generate error message */
433 term_msg = _("Verify warnings");
436 term_msg = _("Verify Canceled");
439 term_msg = _("Verify Differences");
442 term_msg = term_code;
443 bsnprintf(term_code, sizeof(term_code),
444 _("Inappropriate term code: %d %c\n"), TermCode, TermCode);
447 bstrftimes(sdt, sizeof(sdt), jcr->jr.StartTime);
448 bstrftimes(edt, sizeof(edt), jcr->jr.EndTime);
449 if (jcr->verify_job) {
450 Name = jcr->verify_job->hdr.name;
455 jobstatus_to_ascii(jcr->FDJobStatus, fd_term_msg, sizeof(fd_term_msg));
456 if (jcr->getJobLevel() == L_VERIFY_VOLUME_TO_CATALOG) {
457 jobstatus_to_ascii(jcr->SDJobStatus, sd_term_msg, sizeof(sd_term_msg));
458 Jmsg(jcr, msg_type, 0, _("%s %s %s (%s):\n"
459 " Build OS: %s %s %s\n"
463 " Verify Level: %s\n"
465 " Verify JobId: %d\n"
469 " Files Expected: %s\n"
470 " Files Examined: %s\n"
471 " Non-fatal FD errors: %d\n"
472 " FD termination status: %s\n"
473 " SD termination status: %s\n"
474 " Termination: %s\n\n"),
475 BACULA, my_name, VERSION, LSMDATE,
476 HOST_OS, DISTNAME, DISTVER,
479 jcr->fileset->hdr.name,
480 level_to_str(jcr->getJobLevel()),
481 jcr->client->hdr.name,
482 jcr->previous_jr.JobId,
486 edit_uint64_with_commas(jcr->ExpectedFiles, ec1),
487 edit_uint64_with_commas(jcr->JobFiles, ec2),
493 Jmsg(jcr, msg_type, 0, _("%s %s %s (%s):\n"
498 " Verify Level: %s\n"
500 " Verify JobId: %d\n"
504 " Files Examined: %s\n"
505 " Non-fatal FD errors: %d\n"
506 " FD termination status: %s\n"
507 " Termination: %s\n\n"),
508 BACULA, my_name, VERSION, LSMDATE,
509 HOST_OS, DISTNAME, DISTVER,
512 jcr->fileset->hdr.name,
513 level_to_str(jcr->getJobLevel()),
515 jcr->previous_jr.JobId,
519 edit_uint64_with_commas(jcr->JobFiles, ec1),
524 Dmsg0(100, "Leave verify_cleanup()\n");
528 * This routine is called only during a Verify
530 void get_attributes_and_compare_to_catalog(JCR *jcr, JobId_t JobId)
535 struct stat statf; /* file stat */
536 struct stat statc; /* catalog stat */
538 POOLMEM *fname = get_pool_memory(PM_MESSAGE);
539 int do_Digest = CRYPTO_DIGEST_NONE;
540 int32_t file_index = 0;
542 memset(&fdbr, 0, sizeof(FILE_DBR));
543 fd = jcr->file_bsock;
547 Dmsg0(20, "bdird: waiting to receive file attributes\n");
549 * Get Attributes and Signature from File daemon
553 * Options or Digest (MD5/SHA1)
558 while ((n=bget_dirmsg(fd)) >= 0 && !job_canceled(jcr)) {
559 int32_t stream, full_stream;
561 char Opts_Digest[MAXSTRING]; /* Verify Opts or MD5/SHA1 digest */
563 if (job_canceled(jcr)) {
564 free_pool_memory(fname);
567 fname = check_pool_memory_size(fname, fd->msglen);
568 jcr->fname = check_pool_memory_size(jcr->fname, fd->msglen);
569 Dmsg1(200, "Atts+Digest=%s\n", fd->msg);
570 if ((len = sscanf(fd->msg, "%ld %d %100s", &file_index, &full_stream,
572 Jmsg3(jcr, M_FATAL, 0, _("bird<filed: bad attributes, expected 3 fields got %d\n"
573 " mslen=%d msg=%s\n"), len, fd->msglen, fd->msg);
574 free_pool_memory(fname);
577 stream = full_stream & STREAMMASK_TYPE;
578 Dmsg4(30, "Got hdr: FilInx=%d FullStream=%d Stream=%d fname=%s.\n", file_index, full_stream, stream, fname);
581 * We read the Options or Signature into fname
582 * to prevent overrun, now copy it to proper location.
584 bstrncpy(Opts_Digest, fname, sizeof(Opts_Digest));
586 skip_nonspaces(&p); /* skip FileIndex */
588 skip_nonspaces(&p); /* skip Stream */
590 skip_nonspaces(&p); /* skip Opts_Digest */
591 p++; /* skip space */
594 *fn++ = *p++; /* copy filename */
596 *fn = *p++; /* term filename and point to attribs */
599 * Got attributes stream, decode it
601 if (stream == STREAM_UNIX_ATTRIBUTES || stream == STREAM_UNIX_ATTRIBUTES_EX) {
602 int32_t LinkFIf, LinkFIc;
603 Dmsg2(400, "file_index=%d attr=%s\n", file_index, attr);
605 jcr->FileIndex = file_index; /* remember attribute file_index */
606 jcr->previous_jr.FileIndex = file_index;
607 decode_stat(attr, &statf, sizeof(statf), &LinkFIf); /* decode file stat packet */
608 do_Digest = CRYPTO_DIGEST_NONE;
609 jcr->fn_printed = false;
610 pm_strcpy(jcr->fname, fname); /* move filename into JCR */
612 Dmsg2(040, "dird<filed: stream=%d %s\n", stream, jcr->fname);
613 Dmsg1(020, "dird<filed: attr=%s\n", attr);
616 * Find equivalent record in the database
619 if (!db_get_file_attributes_record(jcr, jcr->db, jcr->fname,
620 &jcr->previous_jr, &fdbr)) {
621 Jmsg(jcr, M_INFO, 0, _("New file: %s\n"), jcr->fname);
622 Dmsg1(020, _("File not in catalog: %s\n"), jcr->fname);
623 jcr->setJobStatus(JS_Differences);
627 * mark file record as visited by stuffing the
628 * current JobId, which is unique, into the MarkId field.
630 db_mark_file_record(jcr, jcr->db, fdbr.FileId, jcr->JobId);
633 Dmsg3(400, "Found %s in catalog. inx=%d Opts=%s\n", jcr->fname,
634 file_index, Opts_Digest);
635 decode_stat(fdbr.LStat, &statc, sizeof(statc), &LinkFIc); /* decode catalog stat */
637 * Loop over options supplied by user and verify the
638 * fields he requests.
640 for (p=Opts_Digest; *p; p++) {
641 char ed1[30], ed2[30];
643 case 'i': /* compare INODEs */
644 if (statc.st_ino != statf.st_ino) {
646 Jmsg(jcr, M_INFO, 0, _(" st_ino differ. Cat: %s File: %s\n"),
647 edit_uint64((uint64_t)statc.st_ino, ed1),
648 edit_uint64((uint64_t)statf.st_ino, ed2));
649 jcr->setJobStatus(JS_Differences);
652 case 'p': /* permissions bits */
653 if (statc.st_mode != statf.st_mode) {
655 Jmsg(jcr, M_INFO, 0, _(" st_mode differ. Cat: %x File: %x\n"),
656 (uint32_t)statc.st_mode, (uint32_t)statf.st_mode);
657 jcr->setJobStatus(JS_Differences);
660 case 'n': /* number of links */
661 if (statc.st_nlink != statf.st_nlink) {
663 Jmsg(jcr, M_INFO, 0, _(" st_nlink differ. Cat: %d File: %d\n"),
664 (uint32_t)statc.st_nlink, (uint32_t)statf.st_nlink);
665 jcr->setJobStatus(JS_Differences);
668 case 'u': /* user id */
669 if (statc.st_uid != statf.st_uid) {
671 Jmsg(jcr, M_INFO, 0, _(" st_uid differ. Cat: %u File: %u\n"),
672 (uint32_t)statc.st_uid, (uint32_t)statf.st_uid);
673 jcr->setJobStatus(JS_Differences);
676 case 'g': /* group id */
677 if (statc.st_gid != statf.st_gid) {
679 Jmsg(jcr, M_INFO, 0, _(" st_gid differ. Cat: %u File: %u\n"),
680 (uint32_t)statc.st_gid, (uint32_t)statf.st_gid);
681 jcr->setJobStatus(JS_Differences);
685 if (statc.st_size != statf.st_size) {
687 Jmsg(jcr, M_INFO, 0, _(" st_size differ. Cat: %s File: %s\n"),
688 edit_uint64((uint64_t)statc.st_size, ed1),
689 edit_uint64((uint64_t)statf.st_size, ed2));
690 jcr->setJobStatus(JS_Differences);
693 case 'a': /* access time */
694 if (statc.st_atime != statf.st_atime) {
696 Jmsg(jcr, M_INFO, 0, _(" st_atime differs\n"));
697 jcr->setJobStatus(JS_Differences);
701 if (statc.st_mtime != statf.st_mtime) {
703 Jmsg(jcr, M_INFO, 0, _(" st_mtime differs\n"));
704 jcr->setJobStatus(JS_Differences);
707 case 'c': /* ctime */
708 if (statc.st_ctime != statf.st_ctime) {
710 Jmsg(jcr, M_INFO, 0, _(" st_ctime differs\n"));
711 jcr->setJobStatus(JS_Differences);
714 case 'd': /* file size decrease */
715 if (statc.st_size > statf.st_size) {
717 Jmsg(jcr, M_INFO, 0, _(" st_size decrease. Cat: %s File: %s\n"),
718 edit_uint64((uint64_t)statc.st_size, ed1),
719 edit_uint64((uint64_t)statf.st_size, ed2));
720 jcr->setJobStatus(JS_Differences);
723 case '5': /* compare MD5 */
724 Dmsg1(500, "set Do_MD5 for %s\n", jcr->fname);
725 do_Digest = CRYPTO_DIGEST_MD5;
727 case '1': /* compare SHA1 */
728 do_Digest = CRYPTO_DIGEST_SHA1;
737 * Got Digest Signature from Storage daemon
738 * It came across in the Opts_Digest field.
740 } else if (crypto_digest_stream_type(stream) != CRYPTO_DIGEST_NONE) {
741 Dmsg2(400, "stream=Digest inx=%d Digest=%s\n", file_index, Opts_Digest);
743 * When ever we get a digest it MUST have been
744 * preceded by an attributes record, which sets attr_file_index
746 if (jcr->FileIndex != (uint32_t)file_index) {
747 Jmsg2(jcr, M_FATAL, 0, _("MD5/SHA1 index %d not same as attributes %d\n"),
748 file_index, jcr->FileIndex);
749 free_pool_memory(fname);
752 if (do_Digest != CRYPTO_DIGEST_NONE) {
753 db_escape_string(jcr, jcr->db, buf, Opts_Digest, strlen(Opts_Digest));
754 if (strcmp(buf, fdbr.Digest) != 0) {
756 Jmsg(jcr, M_INFO, 0, _(" %s differs. File=%s Cat=%s\n"),
757 stream_to_ascii(stream), buf, fdbr.Digest);
758 jcr->setJobStatus(JS_Differences);
760 do_Digest = CRYPTO_DIGEST_NONE;
763 jcr->JobFiles = file_index;
765 if (fd->is_error()) {
767 Jmsg2(jcr, M_FATAL, 0, _("bdird<filed: bad attributes from filed n=%d : %s\n"),
769 free_pool_memory(fname);
773 /* Now find all the files that are missing -- i.e. all files in
774 * the database where the MarkId != current JobId
776 jcr->fn_printed = false;
777 bsnprintf(buf, sizeof(buf),
778 "SELECT Path.Path,Filename.Name FROM File,Path,Filename "
779 "WHERE File.JobId=%d AND File.FileIndex > 0 "
780 "AND File.MarkId!=%d AND File.PathId=Path.PathId "
781 "AND File.FilenameId=Filename.FilenameId",
783 /* missing_handler is called for each file found */
784 db_sql_query(jcr->db, buf, missing_handler, (void *)jcr);
785 if (jcr->fn_printed) {
786 jcr->setJobStatus(JS_Differences);
788 free_pool_memory(fname);
792 * We are called here for each record that matches the above
793 * SQL query -- that is for each file contained in the Catalog
794 * that was not marked earlier. This means that the file in
795 * question is a missing file (in the Catalog but not on Disk).
797 static int missing_handler(void *ctx, int num_fields, char **row)
799 JCR *jcr = (JCR *)ctx;
801 if (job_canceled(jcr)) {
804 if (!jcr->fn_printed) {
805 Qmsg(jcr, M_WARNING, 0, _("The following files are in the Catalog but not on %s:\n"),
806 jcr->getJobLevel() == L_VERIFY_VOLUME_TO_CATALOG ? "the Volume(s)" : "disk");
807 jcr->fn_printed = true;
809 Qmsg(jcr, M_INFO, 0, " %s%s\n", row[0]?row[0]:"", row[1]?row[1]:"");
815 * Print filename for verify
817 static void prt_fname(JCR *jcr)
819 if (!jcr->fn_printed) {
820 Jmsg(jcr, M_INFO, 0, _("File: %s\n"), jcr->fname);
821 jcr->fn_printed = true;