3 * Program to scan a Bacula Volume and compare it with
4 * the catalog and optionally synchronize the catalog
7 * Kern E. Sibbald, December 2001
13 Copyright (C) 2001-2006 Kern Sibbald
15 This program is free software; you can redistribute it and/or
16 modify it under the terms of the GNU General Public License
17 version 2 as amended with additional clauses defined in the
18 file LICENSE in the main source directory.
20 This program is distributed in the hope that it will be useful,
21 but WITHOUT ANY WARRANTY; without even the implied warranty of
22 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 the file LICENSE for additional details.
29 #include "findlib/find.h"
30 #include "cats/cats.h"
33 int generate_daemon_event(JCR *jcr, const char *event) { return 1; }
35 /* Forward referenced functions */
36 static void do_scan(void);
37 static bool record_cb(DCR *dcr, DEV_RECORD *rec);
38 static int create_file_attributes_record(B_DB *db, JCR *mjcr,
39 char *fname, char *lname, int type,
40 char *ap, DEV_RECORD *rec);
41 static int create_media_record(B_DB *db, MEDIA_DBR *mr, VOLUME_LABEL *vl);
42 static bool update_media_record(B_DB *db, MEDIA_DBR *mr);
43 static int create_pool_record(B_DB *db, POOL_DBR *pr);
44 static JCR *create_job_record(B_DB *db, JOB_DBR *mr, SESSION_LABEL *label, DEV_RECORD *rec);
45 static int update_job_record(B_DB *db, JOB_DBR *mr, SESSION_LABEL *elabel,
47 static int create_client_record(B_DB *db, CLIENT_DBR *cr);
48 static int create_fileset_record(B_DB *db, FILESET_DBR *fsr);
49 static int create_jobmedia_record(B_DB *db, JCR *jcr);
50 static JCR *create_jcr(JOB_DBR *jr, DEV_RECORD *rec, uint32_t JobId);
51 static int update_digest_record(B_DB *db, char *digest, DEV_RECORD *rec, int type);
55 static DEVICE *dev = NULL;
57 static JCR *bjcr; /* jcr for bscan */
58 static BSR *bsr = NULL;
63 static FILESET_DBR fsr;
66 static SESSION_LABEL label;
67 static SESSION_LABEL elabel;
70 static time_t lasttime = 0;
72 static const char *db_name = "bacula";
73 static const char *db_user = "bacula";
74 static const char *db_password = "";
75 static const char *db_host = NULL;
76 static const char *wd = NULL;
77 static bool update_db = false;
78 static bool update_vol_info = false;
79 static bool list_records = false;
80 static int ignored_msgs = 0;
82 static uint64_t currentVolumeSize;
83 static int last_pct = -1;
84 static bool showProgress = false;
85 static int num_jobs = 0;
86 static int num_pools = 0;
87 static int num_media = 0;
88 static int num_files = 0;
90 #define CONFIG_FILE "bacula-sd.conf"
91 char *configfile = NULL;
92 STORES *me = NULL; /* our Global resource */
93 bool forge_on = false; /* proceed inspite of I/O errors */
94 pthread_mutex_t device_release_mutex = PTHREAD_MUTEX_INITIALIZER;
95 pthread_cond_t wait_device_release = PTHREAD_COND_INITIALIZER;
101 "Copyright (C) 2001-%s Kern Sibbald.\n"
102 "\nVersion: %s (%s)\n\n"
103 "Usage: bscan [ options ] <bacula-archive>\n"
104 " -b bootstrap specify a bootstrap file\n"
105 " -c <file> specify configuration file\n"
106 " -d <nn> set debug level to nn\n"
107 " -m update media info in database\n"
108 " -n <name> specify the database name (default bacula)\n"
109 " -u <user> specify database user name (default bacula)\n"
110 " -P <password specify database password (default none)\n"
111 " -h <host> specify database host (default NULL)\n"
112 " -p proceed inspite of I/O errors\n"
114 " -s synchronize or store in database\n"
115 " -S show scan progress periodically\n"
117 " -V <Volumes> specify Volume names (separated by |)\n"
118 " -w <dir> specify working directory (default from conf file)\n"
119 " -? print this message\n\n"), BYEAR, VERSION, BDATE);
123 int main (int argc, char *argv[])
126 struct stat stat_buf;
127 char *VolumeName = NULL;
129 setlocale(LC_ALL, "");
130 bindtextdomain("bacula", LOCALEDIR);
131 textdomain("bacula");
134 my_name_is(argc, argv, "bscan");
135 init_msg(NULL, NULL);
138 while ((ch = getopt(argc, argv, "b:c:d:h:mn:pP:rsSu:vV:w:?")) != -1) {
144 bsr = parse_bsr(NULL, optarg);
147 case 'c': /* specify config file */
148 if (configfile != NULL) {
151 configfile = bstrdup(optarg);
154 case 'd': /* debug level */
155 debug_level = atoi(optarg);
156 if (debug_level <= 0)
165 update_vol_info = true;
177 db_password = optarg;
196 case 'V': /* Volume name */
214 Pmsg0(0, _("Wrong number of arguments: \n"));
218 if (configfile == NULL) {
219 configfile = bstrdup(CONFIG_FILE);
222 parse_config(configfile);
224 me = (STORES *)GetNextRes(R_STORAGE, NULL);
227 Emsg1(M_ERROR_TERM, 0, _("No Storage resource defined in %s. Cannot continue.\n"),
231 /* Check if -w option given, otherwise use resource for working directory */
233 working_directory = wd;
234 } else if (!me->working_directory) {
235 Emsg1(M_ERROR_TERM, 0, _("No Working Directory defined in %s. Cannot continue.\n"),
238 working_directory = me->working_directory;
241 /* Check that working directory is good */
242 if (stat(working_directory, &stat_buf) != 0) {
243 Emsg1(M_ERROR_TERM, 0, _("Working Directory: %s not found. Cannot continue.\n"),
246 if (!S_ISDIR(stat_buf.st_mode)) {
247 Emsg1(M_ERROR_TERM, 0, _("Working Directory: %s is not a directory. Cannot continue.\n"),
251 bjcr = setup_jcr("bscan", argv[0], bsr, VolumeName, 1); /* read device */
255 dev = bjcr->read_dcr->dev;
260 currentVolumeSize = sb.st_size;
261 Pmsg1(000, _("First Volume Size = %sn"),
262 edit_uint64(currentVolumeSize, ed1));
265 if ((db=db_init_database(NULL, db_name, db_user, db_password,
266 db_host, 0, NULL, 0)) == NULL) {
267 Emsg0(M_ERROR_TERM, 0, _("Could not init Bacula database\n"));
269 if (!db_open_database(NULL, db)) {
270 Emsg0(M_ERROR_TERM, 0, db_strerror(db));
272 Dmsg0(200, "Database opened\n");
274 Pmsg2(000, _("Using Database: %s, User: %s\n"), db_name, db_user);
279 printf("Records added or updated in the catalog:\n%7d Media\n%7d Pool\n%7d Job\n%7d File\n",
280 num_media, num_pools, num_jobs, num_files);
283 printf("Records would have been added or updated in the catalog:\n%7d Media\n%7d Pool\n%7d Job\n%7d File\n",
284 num_media, num_pools, num_jobs, num_files);
293 * We are at the end of reading a tape. Now, we simulate handling
294 * the end of writing a tape by wiffling through the attached
295 * jcrs creating jobmedia records.
297 static bool bscan_mount_next_read_volume(DCR *dcr)
299 DEVICE *dev = dcr->dev;
301 Dmsg1(100, "Walk attached jcrs. Volume=%s\n", dev->VolCatInfo.VolCatName);
302 foreach_dlist(mdcr, dev->attached_dcrs) {
303 JCR *mjcr = mdcr->jcr;
304 if (mjcr->JobId == 0) {
308 Pmsg1(000, _("Create JobMedia for Job %s\n"), mjcr->Job);
310 if (dev->is_tape()) {
311 mdcr->EndBlock = dcr->EndBlock;
312 mdcr->EndFile = dcr->EndFile;
314 // mdcr->EndBlock = (uint32_t)dcr->file_addr;
315 // mdcr->EndFile = (uint32_t)(dcr->file_addr >> 32);
317 mjcr->read_dcr->VolLastIndex = dcr->VolLastIndex;
318 if (!create_jobmedia_record(db, mjcr)) {
319 Pmsg2(000, _("Could not create JobMedia record for Volume=%s Job=%s\n"),
320 dev->VolCatInfo.VolCatName, mjcr->Job);
323 /* Now let common read routine get up next tape. Note,
324 * we call mount_next... with bscan's jcr because that is where we
325 * have the Volume list, but we get attached.
327 bool stat = mount_next_read_volume(dcr);
333 currentVolumeSize = sb.st_size;
334 Pmsg1(000, _("First Volume Size = %sn"),
335 edit_uint64(currentVolumeSize, ed1));
340 static void do_scan()
344 memset(&ar, 0, sizeof(ar));
345 memset(&pr, 0, sizeof(pr));
346 memset(&jr, 0, sizeof(jr));
347 memset(&cr, 0, sizeof(cr));
348 memset(&fsr, 0, sizeof(fsr));
349 memset(&fr, 0, sizeof(fr));
351 /* Detach bscan's jcr as we are not a real Job on the tape */
353 read_records(bjcr->read_dcr, record_cb, bscan_mount_next_read_volume);
359 * Returns: true if OK
362 static bool record_cb(DCR *dcr, DEV_RECORD *rec)
366 DEVICE *dev = dcr->dev;
367 JCR *bjcr = dcr->jcr;
368 DEV_BLOCK *block = dcr->block;
369 char digest[BASE64_SIZE(CRYPTO_DIGEST_MAX_SIZE)];
371 if (rec->data_len > 0) {
372 mr.VolBytes += rec->data_len + WRITE_RECHDR_LENGTH; /* Accumulate Volume bytes */
373 if (showProgress && currentVolumeSize > 0) {
374 int pct = (mr.VolBytes * 100) / currentVolumeSize;
375 if (pct != last_pct) {
376 fprintf(stdout, _("done: %d%%\n"), pct);
384 Pmsg5(000, _("Record: SessId=%u SessTim=%u FileIndex=%d Stream=%d len=%u\n"),
385 rec->VolSessionId, rec->VolSessionTime, rec->FileIndex,
386 rec->Stream, rec->data_len);
389 * Check for Start or End of Session Record
392 if (rec->FileIndex < 0) {
393 bool save_update_db = update_db;
396 dump_label_record(dev, rec, 1);
398 switch (rec->FileIndex) {
400 Pmsg0(000, _("Volume is prelabeled. This tape cannot be scanned.\n"));
405 unser_volume_label(dev, rec);
406 /* Check Pool info */
407 bstrncpy(pr.Name, dev->VolHdr.PoolName, sizeof(pr.Name));
408 bstrncpy(pr.PoolType, dev->VolHdr.PoolType, sizeof(pr.PoolType));
410 if (db_get_pool_record(bjcr, db, &pr)) {
412 Pmsg1(000, _("Pool record for %s found in DB.\n"), pr.Name);
416 Pmsg1(000, _("VOL_LABEL: Pool record not found for Pool: %s\n"),
419 create_pool_record(db, &pr);
421 if (strcmp(pr.PoolType, dev->VolHdr.PoolType) != 0) {
422 Pmsg2(000, _("VOL_LABEL: PoolType mismatch. DB=%s Vol=%s\n"),
423 pr.PoolType, dev->VolHdr.PoolType);
425 } else if (verbose) {
426 Pmsg1(000, _("Pool type \"%s\" is OK.\n"), pr.PoolType);
429 /* Check Media Info */
430 memset(&mr, 0, sizeof(mr));
431 bstrncpy(mr.VolumeName, dev->VolHdr.VolumeName, sizeof(mr.VolumeName));
432 mr.PoolId = pr.PoolId;
434 if (db_get_media_record(bjcr, db, &mr)) {
436 Pmsg1(000, _("Media record for %s found in DB.\n"), mr.VolumeName);
438 /* Clear out some volume statistics that will be updated */
439 mr.VolJobs = mr.VolFiles = mr.VolBlocks = 0;
440 mr.VolBytes = rec->data_len + 20;
443 Pmsg1(000, _("VOL_LABEL: Media record not found for Volume: %s\n"),
446 bstrncpy(mr.MediaType, dev->VolHdr.MediaType, sizeof(mr.MediaType));
447 create_media_record(db, &mr, &dev->VolHdr);
449 if (strcmp(mr.MediaType, dev->VolHdr.MediaType) != 0) {
450 Pmsg2(000, _("VOL_LABEL: MediaType mismatch. DB=%s Vol=%s\n"),
451 mr.MediaType, dev->VolHdr.MediaType);
452 return true; /* ignore error */
453 } else if (verbose) {
454 Pmsg1(000, _("Media type \"%s\" is OK.\n"), mr.MediaType);
456 /* Reset some DCR variables */
457 foreach_dlist(dcr, dev->attached_dcrs) {
458 dcr->VolFirstIndex = dcr->FileIndex = 0;
459 dcr->StartBlock = dcr->EndBlock = 0;
460 dcr->StartFile = dcr->EndFile = 0;
463 Pmsg1(000, _("VOL_LABEL: OK for Volume: %s\n"), mr.VolumeName);
469 if (ignored_msgs > 0) {
470 Pmsg1(000, _("%d \"errors\" ignored before first Start of Session record.\n"),
474 unser_session_label(&label, rec);
475 memset(&jr, 0, sizeof(jr));
476 bstrncpy(jr.Job, label.Job, sizeof(jr.Job));
477 if (db_get_job_record(bjcr, db, &jr)) {
478 /* Job record already exists in DB */
479 update_db = false; /* don't change db in create_job_record */
481 Pmsg1(000, _("SOS_LABEL: Found Job record for JobId: %d\n"), jr.JobId);
484 /* Must create a Job record in DB */
486 Pmsg1(000, _("SOS_LABEL: Job record not found for JobId: %d\n"),
490 /* Create Client record if not already there */
491 bstrncpy(cr.Name, label.ClientName, sizeof(cr.Name));
492 create_client_record(db, &cr);
493 jr.ClientId = cr.ClientId;
495 /* process label, if Job record exists don't update db */
496 mjcr = create_job_record(db, &jr, &label, rec);
497 dcr = mjcr->read_dcr;
498 update_db = save_update_db;
500 jr.PoolId = pr.PoolId;
502 /* Set start positions into JCR */
503 if (dev->is_tape()) {
505 * Note, we have already advanced past current block,
506 * so the correct number is block_num - 1
508 dcr->StartBlock = dev->block_num - 1;
509 dcr->StartFile = dev->file;
511 dcr->StartBlock = (uint32_t)dev->file_addr;
512 dcr->StartFile = (uint32_t)(dev->file_addr >> 32);
515 mjcr->start_time = jr.StartTime;
516 mjcr->JobLevel = jr.JobLevel;
518 mjcr->client_name = get_pool_memory(PM_FNAME);
519 pm_strcpy(mjcr->client_name, label.ClientName);
520 mjcr->fileset_name = get_pool_memory(PM_FNAME);
521 pm_strcpy(mjcr->fileset_name, label.FileSetName);
522 bstrncpy(dcr->pool_type, label.PoolType, sizeof(dcr->pool_type));
523 bstrncpy(dcr->pool_name, label.PoolName, sizeof(dcr->pool_name));
525 if (rec->VolSessionId != jr.VolSessionId) {
526 Pmsg3(000, _("SOS_LABEL: VolSessId mismatch for JobId=%u. DB=%d Vol=%d\n"),
528 jr.VolSessionId, rec->VolSessionId);
529 return true; /* ignore error */
531 if (rec->VolSessionTime != jr.VolSessionTime) {
532 Pmsg3(000, _("SOS_LABEL: VolSessTime mismatch for JobId=%u. DB=%d Vol=%d\n"),
534 jr.VolSessionTime, rec->VolSessionTime);
535 return true; /* ignore error */
537 if (jr.PoolId != pr.PoolId) {
538 Pmsg3(000, _("SOS_LABEL: PoolId mismatch for JobId=%u. DB=%d Vol=%d\n"),
540 jr.PoolId, pr.PoolId);
541 return true; /* ignore error */
546 unser_session_label(&elabel, rec);
548 /* Create FileSet record */
549 bstrncpy(fsr.FileSet, label.FileSetName, sizeof(fsr.FileSet));
550 bstrncpy(fsr.MD5, label.FileSetMD5, sizeof(fsr.MD5));
551 create_fileset_record(db, &fsr);
552 jr.FileSetId = fsr.FileSetId;
554 mjcr = get_jcr_by_session(rec->VolSessionId, rec->VolSessionTime);
556 Pmsg2(000, _("Could not find SessId=%d SessTime=%d for EOS record.\n"),
557 rec->VolSessionId, rec->VolSessionTime);
561 /* Do the final update to the Job record */
562 update_job_record(db, &jr, &elabel, rec);
564 mjcr->end_time = jr.EndTime;
565 mjcr->JobStatus = JS_Terminated;
567 /* Create JobMedia record */
568 mjcr->read_dcr->VolLastIndex = dcr->VolLastIndex;
569 create_jobmedia_record(db, mjcr);
570 detach_dcr_from_dev(mjcr->read_dcr);
578 case EOT_LABEL: /* end of all tapes */
580 * Wiffle through all jobs still open and close
585 foreach_dlist(mdcr, dev->attached_dcrs) {
586 JCR *mjcr = mdcr->jcr;
587 if (!mjcr || mjcr->JobId == 0) {
590 jr.JobId = mjcr->JobId;
591 /* Mark Job as Error Terimined */
592 jr.JobStatus = JS_ErrorTerminated;
593 jr.JobFiles = mjcr->JobFiles;
594 jr.JobBytes = mjcr->JobBytes;
595 jr.VolSessionId = mjcr->VolSessionId;
596 jr.VolSessionTime = mjcr->VolSessionTime;
597 jr.JobTDate = (utime_t)mjcr->start_time;
598 jr.ClientId = mjcr->ClientId;
599 if (!db_update_job_end_record(bjcr, db, &jr)) {
600 Pmsg1(0, _("Could not update job record. ERR=%s\n"), db_strerror(db));
602 mjcr->read_dcr = NULL;
606 mr.VolFiles = rec->File;
607 mr.VolBlocks = rec->Block;
608 mr.VolBytes += mr.VolBlocks * WRITE_BLKHDR_LENGTH; /* approx. */
610 update_media_record(db, &mr);
611 Pmsg3(0, _("End of all Volumes. VolFiles=%u VolBlocks=%u VolBytes=%s\n"), mr.VolFiles,
612 mr.VolBlocks, edit_uint64_with_commas(mr.VolBytes, ec1));
620 mjcr = get_jcr_by_session(rec->VolSessionId, rec->VolSessionTime);
622 if (mr.VolJobs > 0) {
623 Pmsg2(000, _("Could not find Job for SessId=%d SessTime=%d record.\n"),
624 rec->VolSessionId, rec->VolSessionTime);
630 dcr = mjcr->read_dcr;
631 if (dcr->VolFirstIndex == 0) {
632 dcr->VolFirstIndex = block->FirstIndex;
635 /* File Attributes stream */
636 switch (rec->Stream) {
637 case STREAM_UNIX_ATTRIBUTES:
638 case STREAM_UNIX_ATTRIBUTES_EX:
640 if (!unpack_attributes_record(bjcr, rec->Stream, rec->data, attr)) {
641 Emsg0(M_ERROR_TERM, 0, _("Cannot continue.\n"));
644 if (attr->file_index != rec->FileIndex) {
645 Emsg2(M_ERROR_TERM, 0, _("Record header file index %ld not equal record index %ld\n"),
646 rec->FileIndex, attr->file_index);
650 decode_stat(attr->attr, &attr->statp, &attr->LinkFI);
651 build_attr_output_fnames(bjcr, attr);
652 print_ls_output(bjcr, attr);
654 fr.JobId = mjcr->JobId;
657 if (verbose && (num_files & 0x7FFF) == 0) {
658 char ed1[30], ed2[30], ed3[30], ed4[30];
659 Pmsg4(000, _("%s file records. At file:blk=%s:%s bytes=%s\n"),
660 edit_uint64_with_commas(num_files, ed1),
661 edit_uint64_with_commas(rec->File, ed2),
662 edit_uint64_with_commas(rec->Block, ed3),
663 edit_uint64_with_commas(mr.VolBytes, ed4));
665 create_file_attributes_record(db, mjcr, attr->fname, attr->lname,
666 attr->type, attr->attr, rec);
671 case STREAM_WIN32_DATA:
672 case STREAM_FILE_DATA:
673 case STREAM_SPARSE_DATA:
674 case STREAM_ENCRYPTED_FILE_DATA:
675 case STREAM_ENCRYPTED_WIN32_DATA:
676 case STREAM_ENCRYPTED_MACOS_FORK_DATA:
678 * For encrypted stream, this is an approximation.
679 * The data must be decrypted to know the correct length.
681 mjcr->JobBytes += rec->data_len;
682 if (rec->Stream == STREAM_SPARSE_DATA) {
683 mjcr->JobBytes -= sizeof(uint64_t);
686 free_jcr(mjcr); /* done using JCR */
689 case STREAM_GZIP_DATA:
690 case STREAM_ENCRYPTED_FILE_GZIP_DATA:
691 case STREAM_ENCRYPTED_WIN32_GZIP_DATA:
692 /* No correct, we should (decrypt and) expand it
695 mjcr->JobBytes += rec->data_len;
699 case STREAM_SPARSE_GZIP_DATA:
700 mjcr->JobBytes += rec->data_len - sizeof(uint64_t); /* No correct, we should expand it */
701 free_jcr(mjcr); /* done using JCR */
704 /* Win32 GZIP stream */
705 case STREAM_WIN32_GZIP_DATA:
706 mjcr->JobBytes += rec->data_len;
707 free_jcr(mjcr); /* done using JCR */
710 case STREAM_MD5_DIGEST:
711 bin_to_base64(digest, sizeof(digest), (char *)rec->data, CRYPTO_DIGEST_MD5_SIZE, true);
713 Pmsg1(000, _("Got MD5 record: %s\n"), digest);
715 update_digest_record(db, digest, rec, CRYPTO_DIGEST_MD5);
718 case STREAM_SHA1_DIGEST:
719 bin_to_base64(digest, sizeof(digest), (char *)rec->data, CRYPTO_DIGEST_SHA1_SIZE, true);
721 Pmsg1(000, _("Got SHA1 record: %s\n"), digest);
723 update_digest_record(db, digest, rec, CRYPTO_DIGEST_SHA1);
726 case STREAM_SHA256_DIGEST:
727 bin_to_base64(digest, sizeof(digest), (char *)rec->data, CRYPTO_DIGEST_SHA256_SIZE, true);
729 Pmsg1(000, _("Got SHA256 record: %s\n"), digest);
731 update_digest_record(db, digest, rec, CRYPTO_DIGEST_SHA256);
734 case STREAM_SHA512_DIGEST:
735 bin_to_base64(digest, sizeof(digest), (char *)rec->data, CRYPTO_DIGEST_SHA512_SIZE, true);
737 Pmsg1(000, _("Got SHA512 record: %s\n"), digest);
739 update_digest_record(db, digest, rec, CRYPTO_DIGEST_SHA512);
742 case STREAM_ENCRYPTED_SESSION_DATA:
743 // TODO landonf: Investigate crypto support in bscan
745 Pmsg0(000, _("Got signed digest record\n"));
749 case STREAM_SIGNED_DIGEST:
750 // TODO landonf: Investigate crypto support in bscan
752 Pmsg0(000, _("Got signed digest record\n"));
756 case STREAM_PROGRAM_NAMES:
758 Pmsg1(000, _("Got Prog Names Stream: %s\n"), rec->data);
762 case STREAM_PROGRAM_DATA:
764 Pmsg0(000, _("Got Prog Data Stream record.\n"));
768 case STREAM_UNIX_ATTRIBUTES_ACCESS_ACL: /* Standard ACL attributes on UNIX */
769 case STREAM_UNIX_ATTRIBUTES_DEFAULT_ACL: /* Default ACL attributes on UNIX */
770 /* Ignore Unix attributes */
774 Pmsg2(0, _("Unknown stream type!!! stream=%d len=%i\n"), rec->Stream, rec->data_len);
781 * Free the Job Control Record if no one is still using it.
782 * Called from main free_jcr() routine in src/lib/jcr.c so
783 * that we can do our Director specific cleanup of the jcr.
785 static void bscan_free_jcr(JCR *jcr)
787 Dmsg0(200, "Start bscan free_jcr\n");
789 if (jcr->file_bsock) {
790 Dmsg0(200, "Close File bsock\n");
791 bnet_close(jcr->file_bsock);
793 if (jcr->store_bsock) {
794 Dmsg0(200, "Close Store bsock\n");
795 bnet_close(jcr->store_bsock);
797 if (jcr->RestoreBootstrap) {
798 free(jcr->RestoreBootstrap);
805 free_dcr(jcr->read_dcr);
806 jcr->read_dcr = NULL;
808 Dmsg0(200, "End bscan free_jcr\n");
812 * We got a File Attributes record on the tape. Now, lookup the Job
813 * record, and then create the attributes record.
815 static int create_file_attributes_record(B_DB *db, JCR *mjcr,
816 char *fname, char *lname, int type,
817 char *ap, DEV_RECORD *rec)
819 DCR *dcr = mjcr->read_dcr;
822 ar.ClientId = mjcr->ClientId;
823 ar.JobId = mjcr->JobId;
824 ar.Stream = rec->Stream;
825 ar.FileIndex = rec->FileIndex;
827 if (dcr->VolFirstIndex == 0) {
828 dcr->VolFirstIndex = rec->FileIndex;
830 dcr->FileIndex = rec->FileIndex;
837 if (!db_create_file_attributes_record(bjcr, db, &ar)) {
838 Pmsg1(0, _("Could not create File Attributes record. ERR=%s\n"), db_strerror(db));
841 mjcr->FileId = ar.FileId;
844 Pmsg1(000, _("Created File record: %s\n"), fname);
850 * For each Volume we see, we create a Medium record
852 static int create_media_record(B_DB *db, MEDIA_DBR *mr, VOLUME_LABEL *vl)
857 /* We mark Vols as Archive to keep them from being re-written */
858 bstrncpy(mr->VolStatus, "Archive", sizeof(mr->VolStatus));
859 mr->VolRetention = 365 * 3600 * 24; /* 1 year */
861 if (vl->VerNum >= 11) {
862 mr->FirstWritten = btime_to_utime(vl->write_btime);
863 mr->LabelDate = btime_to_utime(vl->label_btime);
865 /* DEPRECATED DO NOT USE */
866 dt.julian_day_number = vl->write_date;
867 dt.julian_day_fraction = vl->write_time;
869 mr->FirstWritten = mktime(&tm);
870 dt.julian_day_number = vl->label_date;
871 dt.julian_day_fraction = vl->label_time;
873 mr->LabelDate = mktime(&tm);
875 lasttime = mr->LabelDate;
881 if (!db_create_media_record(bjcr, db, mr)) {
882 Pmsg1(0, _("Could not create media record. ERR=%s\n"), db_strerror(db));
885 if (!db_update_media_record(bjcr, db, mr)) {
886 Pmsg1(0, _("Could not update media record. ERR=%s\n"), db_strerror(db));
890 Pmsg1(000, _("Created Media record for Volume: %s\n"), mr->VolumeName);
897 * Called at end of media to update it
899 static bool update_media_record(B_DB *db, MEDIA_DBR *mr)
901 if (!update_db && !update_vol_info) {
905 mr->LastWritten = lasttime;
906 if (!db_update_media_record(bjcr, db, mr)) {
907 Pmsg1(0, _("Could not update media record. ERR=%s\n"), db_strerror(db));
911 Pmsg1(000, _("Updated Media record at end of Volume: %s\n"), mr->VolumeName);
918 static int create_pool_record(B_DB *db, POOL_DBR *pr)
922 pr->VolRetention = 355 * 3600 * 24; /* 1 year */
927 if (!db_create_pool_record(bjcr, db, pr)) {
928 Pmsg1(0, _("Could not create pool record. ERR=%s\n"), db_strerror(db));
932 Pmsg1(000, _("Created Pool record for Pool: %s\n"), pr->Name);
940 * Called from SOS to create a client for the current Job
942 static int create_client_record(B_DB *db, CLIENT_DBR *cr)
947 if (!db_create_client_record(bjcr, db, cr)) {
948 Pmsg1(0, _("Could not create Client record. ERR=%s\n"), db_strerror(db));
952 Pmsg1(000, _("Created Client record for Client: %s\n"), cr->Name);
957 static int create_fileset_record(B_DB *db, FILESET_DBR *fsr)
963 if (fsr->MD5[0] == 0) {
964 fsr->MD5[0] = ' '; /* Equivalent to nothing */
967 if (db_get_fileset_record(bjcr, db, fsr)) {
969 Pmsg1(000, _("Fileset \"%s\" already exists.\n"), fsr->FileSet);
972 if (!db_create_fileset_record(bjcr, db, fsr)) {
973 Pmsg2(0, _("Could not create FileSet record \"%s\". ERR=%s\n"),
974 fsr->FileSet, db_strerror(db));
978 Pmsg1(000, _("Created FileSet record \"%s\"\n"), fsr->FileSet);
985 * Simulate the two calls on the database to create
986 * the Job record and to update it when the Job actually
989 static JCR *create_job_record(B_DB *db, JOB_DBR *jr, SESSION_LABEL *label,
996 jr->JobId = label->JobId;
997 jr->JobType = label->JobType;
998 jr->JobLevel = label->JobLevel;
999 jr->JobStatus = JS_Created;
1000 bstrncpy(jr->Name, label->JobName, sizeof(jr->Name));
1001 bstrncpy(jr->Job, label->Job, sizeof(jr->Job));
1002 if (label->VerNum >= 11) {
1003 jr->SchedTime = btime_to_unix(label->write_btime);
1005 dt.julian_day_number = label->write_date;
1006 dt.julian_day_fraction = label->write_time;
1007 tm_decode(&dt, &tm);
1008 jr->SchedTime = mktime(&tm);
1011 jr->StartTime = jr->SchedTime;
1012 jr->JobTDate = (utime_t)jr->SchedTime;
1013 jr->VolSessionId = rec->VolSessionId;
1014 jr->VolSessionTime = rec->VolSessionTime;
1016 /* Now create a JCR as if starting the Job */
1017 mjcr = create_jcr(jr, rec, label->JobId);
1023 /* This creates the bare essentials */
1024 if (!db_create_job_record(bjcr, db, jr)) {
1025 Pmsg1(0, _("Could not create JobId record. ERR=%s\n"), db_strerror(db));
1029 /* This adds the client, StartTime, JobTDate, ... */
1030 if (!db_update_job_start_record(bjcr, db, jr)) {
1031 Pmsg1(0, _("Could not update job start record. ERR=%s\n"), db_strerror(db));
1034 Pmsg2(000, _("Created new JobId=%u record for original JobId=%u\n"), jr->JobId,
1036 mjcr->JobId = jr->JobId; /* set new JobId */
1041 * Simulate the database call that updates the Job
1042 * at Job termination time.
1044 static int update_job_record(B_DB *db, JOB_DBR *jr, SESSION_LABEL *elabel,
1047 struct date_time dt;
1051 mjcr = get_jcr_by_session(rec->VolSessionId, rec->VolSessionTime);
1053 Pmsg2(000, _("Could not find SessId=%d SessTime=%d for EOS record.\n"),
1054 rec->VolSessionId, rec->VolSessionTime);
1057 if (elabel->VerNum >= 11) {
1058 jr->EndTime = btime_to_unix(elabel->write_btime);
1060 dt.julian_day_number = elabel->write_date;
1061 dt.julian_day_fraction = elabel->write_time;
1062 tm_decode(&dt, &tm);
1063 jr->EndTime = mktime(&tm);
1065 lasttime = jr->EndTime;
1066 mjcr->end_time = jr->EndTime;
1068 jr->JobId = mjcr->JobId;
1069 jr->JobStatus = elabel->JobStatus;
1070 mjcr->JobStatus = elabel->JobStatus;
1071 jr->JobFiles = elabel->JobFiles;
1072 jr->JobBytes = elabel->JobBytes;
1073 jr->VolSessionId = rec->VolSessionId;
1074 jr->VolSessionTime = rec->VolSessionTime;
1075 jr->JobTDate = (utime_t)mjcr->start_time;
1076 jr->ClientId = mjcr->ClientId;
1083 if (!db_update_job_end_record(bjcr, db, jr)) {
1084 Pmsg2(0, _("Could not update JobId=%u record. ERR=%s\n"), jr->JobId, db_strerror(db));
1089 Pmsg3(000, _("Updated Job termination record for JobId=%u Level=%s TermStat=%c\n"),
1090 jr->JobId, job_level_to_str(mjcr->JobLevel), jr->JobStatus);
1093 const char *term_msg;
1094 static char term_code[70];
1095 char sdt[50], edt[50];
1096 char ec1[30], ec2[30], ec3[30];
1098 switch (mjcr->JobStatus) {
1100 term_msg = _("Backup OK");
1103 case JS_ErrorTerminated:
1104 term_msg = _("*** Backup Error ***");
1107 term_msg = _("Backup Canceled");
1110 term_msg = term_code;
1111 sprintf(term_code, _("Job Termination code: %d"), mjcr->JobStatus);
1114 bstrftime(sdt, sizeof(sdt), mjcr->start_time);
1115 bstrftime(edt, sizeof(edt), mjcr->end_time);
1116 Pmsg14(000, _("%s\n"
1120 "Backup Level: %s\n"
1124 "Files Written: %s\n"
1125 "Bytes Written: %s\n"
1126 "Volume Session Id: %d\n"
1127 "Volume Session Time: %d\n"
1128 "Last Volume Bytes: %s\n"
1129 "Termination: %s\n\n"),
1134 job_level_to_str(mjcr->JobLevel),
1138 edit_uint64_with_commas(mjcr->JobFiles, ec1),
1139 edit_uint64_with_commas(mjcr->JobBytes, ec2),
1141 mjcr->VolSessionTime,
1142 edit_uint64_with_commas(mr.VolBytes, ec3),
1149 static int create_jobmedia_record(B_DB *db, JCR *mjcr)
1152 DCR *dcr = mjcr->read_dcr;
1154 if (dev->is_tape()) {
1155 dcr->EndBlock = dev->EndBlock;
1156 dcr->EndFile = dev->EndFile;
1159 dcr->EndBlock = (uint32_t)dev->file_addr;
1160 dcr->EndFile = (uint32_t)(dev->file_addr >> 32);
1164 memset(&jmr, 0, sizeof(jmr));
1165 jmr.JobId = mjcr->JobId;
1166 jmr.MediaId = mr.MediaId;
1167 jmr.FirstIndex = dcr->VolFirstIndex;
1168 jmr.LastIndex = dcr->VolLastIndex;
1169 jmr.StartFile = dcr->StartFile;
1170 jmr.EndFile = dcr->EndFile;
1171 jmr.StartBlock = dcr->StartBlock;
1172 jmr.EndBlock = dcr->EndBlock;
1179 if (!db_create_jobmedia_record(bjcr, db, &jmr)) {
1180 Pmsg1(0, _("Could not create JobMedia record. ERR=%s\n"), db_strerror(db));
1184 Pmsg2(000, _("Created JobMedia record JobId %d, MediaId %d\n"),
1185 jmr.JobId, jmr.MediaId);
1191 * Simulate the database call that updates the MD5/SHA1 record
1193 static int update_digest_record(B_DB *db, char *digest, DEV_RECORD *rec, int type)
1197 mjcr = get_jcr_by_session(rec->VolSessionId, rec->VolSessionTime);
1199 if (mr.VolJobs > 0) {
1200 Pmsg2(000, _("Could not find SessId=%d SessTime=%d for MD5/SHA1 record.\n"),
1201 rec->VolSessionId, rec->VolSessionTime);
1208 if (!update_db || mjcr->FileId == 0) {
1213 if (!db_add_digest_to_file_record(bjcr, db, mjcr->FileId, digest, type)) {
1214 Pmsg1(0, _("Could not add MD5/SHA1 to File record. ERR=%s\n"), db_strerror(db));
1219 Pmsg0(000, _("Updated MD5/SHA1 record\n"));
1227 * Create a JCR as if we are really starting the job
1229 static JCR *create_jcr(JOB_DBR *jr, DEV_RECORD *rec, uint32_t JobId)
1233 * Transfer as much as possible to the Job JCR. Most important is
1234 * the JobId and the ClientId.
1236 jobjcr = new_jcr(sizeof(JCR), bscan_free_jcr);
1237 jobjcr->JobType = jr->JobType;
1238 jobjcr->JobLevel = jr->JobLevel;
1239 jobjcr->JobStatus = jr->JobStatus;
1240 bstrncpy(jobjcr->Job, jr->Job, sizeof(jobjcr->Job));
1241 jobjcr->JobId = JobId; /* this is JobId on tape */
1242 jobjcr->sched_time = jr->SchedTime;
1243 jobjcr->start_time = jr->StartTime;
1244 jobjcr->VolSessionId = rec->VolSessionId;
1245 jobjcr->VolSessionTime = rec->VolSessionTime;
1246 jobjcr->ClientId = jr->ClientId;
1247 jobjcr->read_dcr = new_dcr(jobjcr, dev);
1252 /* Dummies to replace askdir.c */
1253 bool dir_find_next_appendable_volume(DCR *dcr) { return 1;}
1254 bool dir_update_volume_info(DCR *dcr, bool relabel) { return 1; }
1255 bool dir_create_jobmedia_record(DCR *dcr) { return 1; }
1256 bool dir_ask_sysop_to_create_appendable_volume(DCR *dcr) { return 1; }
1257 bool dir_update_file_attributes(DCR *dcr, DEV_RECORD *rec) { return 1;}
1258 bool dir_send_job_status(JCR *jcr) {return 1;}
1259 int generate_job_event(JCR *jcr, const char *event) { return 1; }
1261 bool dir_ask_sysop_to_mount_volume(DCR *dcr)
1263 DEVICE *dev = dcr->dev;
1264 Dmsg0(20, "Enter dir_ask_sysop_to_mount_volume\n");
1265 /* Close device so user can use autochanger if desired */
1266 fprintf(stderr, _("Mount Volume \"%s\" on device %s and press return when ready: "),
1267 dcr->VolumeName, dev->print_name());
1273 bool dir_get_volume_info(DCR *dcr, enum get_vol_info_rw writing)
1275 Dmsg0(100, "Fake dir_get_volume_info\n");
1276 bstrncpy(dcr->VolCatInfo.VolCatName, dcr->VolumeName, sizeof(dcr->VolCatInfo.VolCatName));
1277 dcr->VolCatInfo.VolCatParts = find_num_dvd_parts(dcr);
1278 Dmsg2(500, "Vol=%s num_parts=%d\n", dcr->VolCatInfo.VolCatName, dcr->VolCatInfo.VolCatParts);