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 three of the GNU Affero 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 Affero 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));
139 /* see if user supplied a jobid= as run argument or from menu */
140 if (jcr->RestoreJobId) {
141 verify_jobid = jcr->RestoreJobId;
142 Dmsg1(100, "Supplied jobid=%d\n", verify_jobid);
145 if (!db_find_last_jobid(jcr, jcr->db, Name, &jr)) {
146 if (jcr->getJobLevel() == L_VERIFY_CATALOG) {
147 Jmsg(jcr, M_FATAL, 0, _(
148 "Unable to find JobId of previous InitCatalog Job.\n"
149 "Please run a Verify with Level=InitCatalog before\n"
150 "running the current Job.\n"));
152 Jmsg(jcr, M_FATAL, 0, _(
153 "Unable to find JobId of previous Job for this client.\n"));
157 verify_jobid = jr.JobId;
159 Dmsg1(100, "Last full jobid=%d\n", verify_jobid);
162 * Now get the job record for the previous backup that interests
163 * us. We use the verify_jobid that we found above.
165 if (jcr->getJobLevel() == L_VERIFY_CATALOG ||
166 jcr->getJobLevel() == L_VERIFY_VOLUME_TO_CATALOG ||
167 jcr->getJobLevel() == L_VERIFY_DISK_TO_CATALOG) {
168 jcr->previous_jr.JobId = verify_jobid;
169 if (!db_get_job_record(jcr, jcr->db, &jcr->previous_jr)) {
170 Jmsg(jcr, M_FATAL, 0, _("Could not get job record for previous Job. ERR=%s"),
171 db_strerror(jcr->db));
174 if (!(jcr->previous_jr.JobStatus == JS_Terminated ||
175 jcr->previous_jr.JobStatus == JS_Warnings)) {
176 Jmsg(jcr, M_FATAL, 0, _("Last Job %d did not terminate normally. JobStatus=%c\n"),
177 verify_jobid, jcr->previous_jr.JobStatus);
180 Jmsg(jcr, M_INFO, 0, _("Verifying against JobId=%d Job=%s\n"),
181 jcr->previous_jr.JobId, jcr->previous_jr.Job);
185 * If we are verifying a Volume, we need the Storage
186 * daemon, so open a connection, otherwise, just
187 * create a dummy authorization key (passed to
188 * File daemon but not used).
190 if (jcr->getJobLevel() == L_VERIFY_VOLUME_TO_CATALOG) {
193 * Note: negative status is an error, zero status, means
194 * no files were backed up, so skip calling SD and
197 stat = create_restore_bootstrap_file(jcr);
198 if (stat < 0) { /* error */
200 } else if (stat == 0) { /* No files, nothing to do */
201 verify_cleanup(jcr, JS_Terminated); /* clean up */
202 return true; /* get out */
205 jcr->sd_auth_key = bstrdup("dummy"); /* dummy Storage daemon key */
208 if (jcr->getJobLevel() == L_VERIFY_DISK_TO_CATALOG && jcr->verify_job) {
209 jcr->fileset = jcr->verify_job->fileset;
211 Dmsg2(100, "ClientId=%u JobLevel=%c\n", jcr->previous_jr.ClientId, jcr->getJobLevel());
213 if (!db_update_job_start_record(jcr, jcr->db, &jcr->jr)) {
214 Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
218 /* Print Job Start message */
219 Jmsg(jcr, M_INFO, 0, _("Start Verify JobId=%s Level=%s Job=%s\n"),
220 edit_uint64(jcr->JobId, ed1), level_to_str(jcr->getJobLevel()), jcr->Job);
222 if (jcr->getJobLevel() == L_VERIFY_VOLUME_TO_CATALOG) {
225 * Start conversation with Storage daemon
227 set_jcr_job_status(jcr, JS_Blocked);
228 if (!connect_to_storage_daemon(jcr, 10, SDConnectTimeout, 1)) {
232 * Now start a job with the Storage daemon
234 if (!start_storage_daemon_job(jcr, jcr->rstorage, NULL)) {
237 sd = jcr->store_bsock;
239 * Send the bootstrap file -- what Volumes/files to restore
241 if (!send_bootstrap_file(jcr, sd) ||
242 !response(jcr, sd, OKbootstrap, "Bootstrap", DISPLAY_ERROR)) {
245 if (!sd->fsend("run")) {
249 * Now start a Storage daemon message thread
251 if (!start_storage_daemon_message_thread(jcr)) {
254 Dmsg0(50, "Storage daemon connection OK\n");
258 * OK, now connect to the File daemon
259 * and ask him for the files.
261 set_jcr_job_status(jcr, JS_Blocked);
262 if (!connect_to_file_daemon(jcr, 10, FDConnectTimeout, 1)) {
266 set_jcr_job_status(jcr, JS_Running);
267 fd = jcr->file_bsock;
270 Dmsg0(30, ">filed: Send include list\n");
271 if (!send_include_list(jcr)) {
275 Dmsg0(30, ">filed: Send exclude list\n");
276 if (!send_exclude_list(jcr)) {
281 * Send Level command to File daemon, as well
282 * as the Storage address if appropriate.
284 switch (jcr->getJobLevel()) {
288 case L_VERIFY_CATALOG:
291 case L_VERIFY_VOLUME_TO_CATALOG:
293 * send Storage daemon address to the File daemon
295 if (jcr->rstore->SDDport == 0) {
296 jcr->rstore->SDDport = jcr->rstore->SDport;
298 bnet_fsend(fd, storaddr, jcr->rstore->address,
299 jcr->rstore->SDDport, jcr->sd_auth_key);
300 if (!response(jcr, fd, OKstore, "Storage", DISPLAY_ERROR)) {
304 if (!jcr->RestoreBootstrap) {
305 Jmsg0(jcr, M_FATAL, 0, _("Deprecated feature ... use bootstrap.\n"));
314 case L_VERIFY_DISK_TO_CATALOG:
315 level="disk_to_catalog";
318 Jmsg2(jcr, M_FATAL, 0, _("Unimplemented Verify level %d(%c)\n"), jcr->getJobLevel(),
323 if (!send_runscripts_commands(jcr)) {
328 * Send verify command/level to File daemon
330 fd->fsend(verifycmd, level);
331 if (!response(jcr, fd, OKverify, "Verify", DISPLAY_ERROR)) {
336 * Now get data back from File daemon and
337 * compare it to the catalog or store it in the
338 * catalog depending on the run type.
340 /* Compare to catalog */
341 switch (jcr->getJobLevel()) {
342 case L_VERIFY_CATALOG:
343 Dmsg0(10, "Verify level=catalog\n");
344 jcr->sd_msg_thread_done = true; /* no SD msg thread, so it is done */
345 jcr->SDJobStatus = JS_Terminated;
346 get_attributes_and_compare_to_catalog(jcr, jcr->previous_jr.JobId);
349 case L_VERIFY_VOLUME_TO_CATALOG:
350 Dmsg0(10, "Verify level=volume\n");
351 get_attributes_and_compare_to_catalog(jcr, jcr->previous_jr.JobId);
354 case L_VERIFY_DISK_TO_CATALOG:
355 Dmsg0(10, "Verify level=disk_to_catalog\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_compare_to_catalog(jcr, jcr->previous_jr.JobId);
363 Dmsg0(10, "Verify level=init\n");
364 jcr->sd_msg_thread_done = true; /* no SD msg thread, so it is done */
365 jcr->SDJobStatus = JS_Terminated;
366 get_attributes_and_put_in_catalog(jcr);
367 db_end_transaction(jcr, jcr->db); /* terminate any open transaction */
368 db_write_batch_file_records(jcr);
372 Jmsg1(jcr, M_FATAL, 0, _("Unimplemented verify level %d\n"), jcr->getJobLevel());
376 stat = wait_for_job_termination(jcr);
377 verify_cleanup(jcr, stat);
386 * Release resources allocated during backup.
389 void verify_cleanup(JCR *jcr, int TermCode)
391 char sdt[50], edt[50];
392 char ec1[30], ec2[30];
393 char term_code[100], fd_term_msg[100], sd_term_msg[100];
394 const char *term_msg;
399 // Dmsg1(100, "Enter verify_cleanup() TermCod=%d\n", TermCode);
401 Dmsg3(900, "JobLevel=%c Expected=%u JobFiles=%u\n", jcr->getJobLevel(),
402 jcr->ExpectedFiles, jcr->JobFiles);
403 if (jcr->getJobLevel() == L_VERIFY_VOLUME_TO_CATALOG &&
404 jcr->ExpectedFiles != jcr->JobFiles) {
405 TermCode = JS_ErrorTerminated;
408 JobId = jcr->jr.JobId;
410 update_job_end(jcr, TermCode);
412 if (job_canceled(jcr)) {
413 cancel_storage_daemon_job(jcr);
416 if (jcr->unlink_bsr && jcr->RestoreBootstrap) {
417 unlink(jcr->RestoreBootstrap);
418 jcr->unlink_bsr = false;
421 msg_type = M_INFO; /* by default INFO message */
424 term_msg = _("Verify OK");
427 case JS_ErrorTerminated:
428 term_msg = _("*** Verify Error ***");
429 msg_type = M_ERROR; /* Generate error message */
432 term_msg = _("Verify warnings");
435 term_msg = _("Verify Canceled");
438 term_msg = _("Verify Differences");
441 term_msg = term_code;
442 bsnprintf(term_code, sizeof(term_code),
443 _("Inappropriate term code: %d %c\n"), TermCode, TermCode);
446 bstrftimes(sdt, sizeof(sdt), jcr->jr.StartTime);
447 bstrftimes(edt, sizeof(edt), jcr->jr.EndTime);
448 if (jcr->verify_job) {
449 Name = jcr->verify_job->hdr.name;
454 jobstatus_to_ascii(jcr->FDJobStatus, fd_term_msg, sizeof(fd_term_msg));
455 if (jcr->getJobLevel() == L_VERIFY_VOLUME_TO_CATALOG) {
456 jobstatus_to_ascii(jcr->SDJobStatus, sd_term_msg, sizeof(sd_term_msg));
457 Jmsg(jcr, msg_type, 0, _("%s %s %s (%s): %s\n"
458 " Build OS: %s %s %s\n"
462 " Verify Level: %s\n"
464 " Verify JobId: %d\n"
468 " Files Expected: %s\n"
469 " Files Examined: %s\n"
470 " Non-fatal FD errors: %d\n"
471 " FD termination status: %s\n"
472 " SD termination status: %s\n"
473 " Termination: %s\n\n"),
474 BACULA, my_name, VERSION, LSMDATE, edt,
475 HOST_OS, DISTNAME, DISTVER,
478 jcr->fileset->hdr.name,
479 level_to_str(jcr->getJobLevel()),
480 jcr->client->hdr.name,
481 jcr->previous_jr.JobId,
485 edit_uint64_with_commas(jcr->ExpectedFiles, ec1),
486 edit_uint64_with_commas(jcr->JobFiles, ec2),
492 Jmsg(jcr, msg_type, 0, _("%s %s %s (%s): %s\n"
497 " Verify Level: %s\n"
499 " Verify JobId: %d\n"
503 " Files Examined: %s\n"
504 " Non-fatal FD errors: %d\n"
505 " FD termination status: %s\n"
506 " Termination: %s\n\n"),
507 BACULA, my_name, VERSION, LSMDATE, edt,
508 HOST_OS, DISTNAME, DISTVER,
511 jcr->fileset->hdr.name,
512 level_to_str(jcr->getJobLevel()),
514 jcr->previous_jr.JobId,
518 edit_uint64_with_commas(jcr->JobFiles, ec1),
523 Dmsg0(100, "Leave verify_cleanup()\n");
527 * This routine is called only during a Verify
529 void get_attributes_and_compare_to_catalog(JCR *jcr, JobId_t JobId)
534 struct stat statf; /* file stat */
535 struct stat statc; /* catalog stat */
537 POOLMEM *fname = get_pool_memory(PM_MESSAGE);
538 int do_Digest = CRYPTO_DIGEST_NONE;
539 int32_t file_index = 0;
541 memset(&fdbr, 0, sizeof(FILE_DBR));
542 fd = jcr->file_bsock;
546 Dmsg0(20, "bdird: waiting to receive file attributes\n");
548 * Get Attributes and Signature from File daemon
552 * Options or Digest (MD5/SHA1)
557 while ((n=bget_dirmsg(fd)) >= 0 && !job_canceled(jcr)) {
560 char Opts_Digest[MAXSTRING]; /* Verify Opts or MD5/SHA1 digest */
562 if (job_canceled(jcr)) {
565 fname = check_pool_memory_size(fname, fd->msglen);
566 jcr->fname = check_pool_memory_size(jcr->fname, fd->msglen);
567 Dmsg1(200, "Atts+Digest=%s\n", fd->msg);
568 if ((len = sscanf(fd->msg, "%ld %d %100s", &file_index, &stream,
570 Jmsg3(jcr, M_FATAL, 0, _("bird<filed: bad attributes, expected 3 fields got %d\n"
571 " mslen=%d msg=%s\n"), len, fd->msglen, fd->msg);
575 * We read the Options or Signature into fname
576 * to prevent overrun, now copy it to proper location.
578 bstrncpy(Opts_Digest, fname, sizeof(Opts_Digest));
580 skip_nonspaces(&p); /* skip FileIndex */
582 skip_nonspaces(&p); /* skip Stream */
584 skip_nonspaces(&p); /* skip Opts_Digest */
585 p++; /* skip space */
588 *fn++ = *p++; /* copy filename */
590 *fn = *p++; /* term filename and point to attribs */
593 * Got attributes stream, decode it
595 if (stream == STREAM_UNIX_ATTRIBUTES || stream == STREAM_UNIX_ATTRIBUTES_EX) {
596 int32_t LinkFIf, LinkFIc;
597 Dmsg2(400, "file_index=%d attr=%s\n", file_index, attr);
599 jcr->FileIndex = file_index; /* remember attribute file_index */
600 jcr->previous_jr.FileIndex = file_index;
601 decode_stat(attr, &statf, &LinkFIf); /* decode file stat packet */
602 do_Digest = CRYPTO_DIGEST_NONE;
603 jcr->fn_printed = false;
604 pm_strcpy(jcr->fname, fname); /* move filename into JCR */
606 Dmsg2(040, "dird<filed: stream=%d %s\n", stream, jcr->fname);
607 Dmsg1(020, "dird<filed: attr=%s\n", attr);
610 * Find equivalent record in the database
613 if (!db_get_file_attributes_record(jcr, jcr->db, jcr->fname,
614 &jcr->previous_jr, &fdbr)) {
615 Jmsg(jcr, M_INFO, 0, _("New file: %s\n"), jcr->fname);
616 Dmsg1(020, _("File not in catalog: %s\n"), jcr->fname);
617 set_jcr_job_status(jcr, JS_Differences);
621 * mark file record as visited by stuffing the
622 * current JobId, which is unique, into the MarkId field.
624 db_mark_file_record(jcr, jcr->db, fdbr.FileId, jcr->JobId);
627 Dmsg3(400, "Found %s in catalog. inx=%d Opts=%s\n", jcr->fname,
628 file_index, Opts_Digest);
629 decode_stat(fdbr.LStat, &statc, &LinkFIc); /* decode catalog stat */
631 * Loop over options supplied by user and verify the
632 * fields he requests.
634 for (p=Opts_Digest; *p; p++) {
635 char ed1[30], ed2[30];
637 case 'i': /* compare INODEs */
638 if (statc.st_ino != statf.st_ino) {
640 Jmsg(jcr, M_INFO, 0, _(" st_ino differ. Cat: %s File: %s\n"),
641 edit_uint64((uint64_t)statc.st_ino, ed1),
642 edit_uint64((uint64_t)statf.st_ino, ed2));
643 set_jcr_job_status(jcr, JS_Differences);
646 case 'p': /* permissions bits */
647 if (statc.st_mode != statf.st_mode) {
649 Jmsg(jcr, M_INFO, 0, _(" st_mode differ. Cat: %x File: %x\n"),
650 (uint32_t)statc.st_mode, (uint32_t)statf.st_mode);
651 set_jcr_job_status(jcr, JS_Differences);
654 case 'n': /* number of links */
655 if (statc.st_nlink != statf.st_nlink) {
657 Jmsg(jcr, M_INFO, 0, _(" st_nlink differ. Cat: %d File: %d\n"),
658 (uint32_t)statc.st_nlink, (uint32_t)statf.st_nlink);
659 set_jcr_job_status(jcr, JS_Differences);
662 case 'u': /* user id */
663 if (statc.st_uid != statf.st_uid) {
665 Jmsg(jcr, M_INFO, 0, _(" st_uid differ. Cat: %u File: %u\n"),
666 (uint32_t)statc.st_uid, (uint32_t)statf.st_uid);
667 set_jcr_job_status(jcr, JS_Differences);
670 case 'g': /* group id */
671 if (statc.st_gid != statf.st_gid) {
673 Jmsg(jcr, M_INFO, 0, _(" st_gid differ. Cat: %u File: %u\n"),
674 (uint32_t)statc.st_gid, (uint32_t)statf.st_gid);
675 set_jcr_job_status(jcr, JS_Differences);
679 if (statc.st_size != statf.st_size) {
681 Jmsg(jcr, M_INFO, 0, _(" st_size differ. Cat: %s File: %s\n"),
682 edit_uint64((uint64_t)statc.st_size, ed1),
683 edit_uint64((uint64_t)statf.st_size, ed2));
684 set_jcr_job_status(jcr, JS_Differences);
687 case 'a': /* access time */
688 if (statc.st_atime != statf.st_atime) {
690 Jmsg(jcr, M_INFO, 0, _(" st_atime differs\n"));
691 set_jcr_job_status(jcr, JS_Differences);
695 if (statc.st_mtime != statf.st_mtime) {
697 Jmsg(jcr, M_INFO, 0, _(" st_mtime differs\n"));
698 set_jcr_job_status(jcr, JS_Differences);
701 case 'c': /* ctime */
702 if (statc.st_ctime != statf.st_ctime) {
704 Jmsg(jcr, M_INFO, 0, _(" st_ctime differs\n"));
705 set_jcr_job_status(jcr, JS_Differences);
708 case 'd': /* file size decrease */
709 if (statc.st_size > statf.st_size) {
711 Jmsg(jcr, M_INFO, 0, _(" st_size decrease. Cat: %s File: %s\n"),
712 edit_uint64((uint64_t)statc.st_size, ed1),
713 edit_uint64((uint64_t)statf.st_size, ed2));
714 set_jcr_job_status(jcr, JS_Differences);
717 case '5': /* compare MD5 */
718 Dmsg1(500, "set Do_MD5 for %s\n", jcr->fname);
719 do_Digest = CRYPTO_DIGEST_MD5;
721 case '1': /* compare SHA1 */
722 do_Digest = CRYPTO_DIGEST_SHA1;
731 * Got Digest Signature from Storage daemon
732 * It came across in the Opts_Digest field.
734 } else if (crypto_digest_stream_type(stream) != CRYPTO_DIGEST_NONE) {
735 Dmsg2(400, "stream=Digest inx=%d Digest=%s\n", file_index, Opts_Digest);
737 * When ever we get a digest it MUST have been
738 * preceded by an attributes record, which sets attr_file_index
740 if (jcr->FileIndex != (uint32_t)file_index) {
741 Jmsg2(jcr, M_FATAL, 0, _("MD5/SHA1 index %d not same as attributes %d\n"),
742 file_index, jcr->FileIndex);
745 if (do_Digest != CRYPTO_DIGEST_NONE) {
746 db_escape_string(jcr, jcr->db, buf, Opts_Digest, strlen(Opts_Digest));
747 if (strcmp(buf, fdbr.Digest) != 0) {
749 Jmsg(jcr, M_INFO, 0, _(" %s differs. File=%s Cat=%s\n"),
750 stream_to_ascii(stream), buf, fdbr.Digest);
751 set_jcr_job_status(jcr, JS_Differences);
753 do_Digest = CRYPTO_DIGEST_NONE;
756 jcr->JobFiles = file_index;
758 if (is_bnet_error(fd)) {
760 Jmsg2(jcr, M_FATAL, 0, _("bdird<filed: bad attributes from filed n=%d : %s\n"),
765 /* Now find all the files that are missing -- i.e. all files in
766 * the database where the MarkId != current JobId
768 jcr->fn_printed = false;
769 bsnprintf(buf, sizeof(buf),
770 "SELECT Path.Path,Filename.Name FROM File,Path,Filename "
771 "WHERE File.JobId=%d AND File.FileIndex > 0 "
772 "AND File.MarkId!=%d AND File.PathId=Path.PathId "
773 "AND File.FilenameId=Filename.FilenameId",
775 /* missing_handler is called for each file found */
776 db_sql_query(jcr->db, buf, missing_handler, (void *)jcr);
777 if (jcr->fn_printed) {
778 set_jcr_job_status(jcr, JS_Differences);
780 free_pool_memory(fname);
784 * We are called here for each record that matches the above
785 * SQL query -- that is for each file contained in the Catalog
786 * that was not marked earlier. This means that the file in
787 * question is a missing file (in the Catalog but not on Disk).
789 static int missing_handler(void *ctx, int num_fields, char **row)
791 JCR *jcr = (JCR *)ctx;
793 if (job_canceled(jcr)) {
796 if (!jcr->fn_printed) {
797 Qmsg(jcr, M_WARNING, 0, _("The following files are in the Catalog but not on %s:\n"),
798 jcr->getJobLevel() == L_VERIFY_VOLUME_TO_CATALOG ? "the Volume(s)" : "disk");
799 jcr->fn_printed = true;
801 Qmsg(jcr, M_INFO, 0, " %s%s\n", row[0]?row[0]:"", row[1]?row[1]:"");
807 * Print filename for verify
809 static void prt_fname(JCR *jcr)
811 if (!jcr->fn_printed) {
812 Jmsg(jcr, M_INFO, 0, _("File: %s\n"), jcr->fname);
813 jcr->fn_printed = true;