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-2005 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);
54 /* Global variables */
55 #if defined(HAVE_CYGWIN) || defined(HAVE_WIN32)
63 static DEVICE *dev = NULL;
65 static JCR *bjcr; /* jcr for bscan */
66 static BSR *bsr = NULL;
71 static FILESET_DBR fsr;
74 static SESSION_LABEL label;
75 static SESSION_LABEL elabel;
78 static time_t lasttime = 0;
80 static const char *db_name = "bacula";
81 static const char *db_user = "bacula";
82 static const char *db_password = "";
83 static const char *db_host = NULL;
84 static const char *wd = NULL;
85 static bool update_db = false;
86 static bool update_vol_info = false;
87 static bool list_records = false;
88 static int ignored_msgs = 0;
90 static uint64_t currentVolumeSize;
91 static int last_pct = -1;
92 static bool showProgress = false;
93 static int num_jobs = 0;
94 static int num_pools = 0;
95 static int num_media = 0;
96 static int num_files = 0;
98 #define CONFIG_FILE "bacula-sd.conf"
99 char *configfile = NULL;
100 STORES *me = NULL; /* our Global resource */
101 bool forge_on = false; /* proceed inspite of I/O errors */
102 pthread_mutex_t device_release_mutex = PTHREAD_MUTEX_INITIALIZER;
103 pthread_cond_t wait_device_release = PTHREAD_COND_INITIALIZER;
109 "Copyright (C) 2001-2005 Kern Sibbald.\n"
110 "\nVersion: %s (%s)\n\n"
111 "Usage: bscan [ options ] <bacula-archive>\n"
112 " -b bootstrap specify a bootstrap file\n"
113 " -c <file> specify configuration file\n"
114 " -d <nn> set debug level to nn\n"
115 " -m update media info in database\n"
116 " -n <name> specify the database name (default bacula)\n"
117 " -u <user> specify database user name (default bacula)\n"
118 " -P <password specify database password (default none)\n"
119 " -h <host> specify database host (default NULL)\n"
120 " -p proceed inspite of I/O errors\n"
122 " -s synchronize or store in database\n"
123 " -S show scan progress periodically\n"
125 " -V <Volumes> specify Volume names (separated by |)\n"
126 " -w <dir> specify working directory (default from conf file)\n"
127 " -? print this message\n\n"), VERSION, BDATE);
131 int main (int argc, char *argv[])
134 struct stat stat_buf;
135 char *VolumeName = NULL;
137 setlocale(LC_ALL, "");
138 bindtextdomain("bacula", LOCALEDIR);
139 textdomain("bacula");
141 my_name_is(argc, argv, "bscan");
142 init_msg(NULL, NULL);
145 while ((ch = getopt(argc, argv, "b:c:d:h:mn:pP:rsSu:vV:w:?")) != -1) {
151 bsr = parse_bsr(NULL, optarg);
154 case 'c': /* specify config file */
155 if (configfile != NULL) {
158 configfile = bstrdup(optarg);
161 case 'd': /* debug level */
162 debug_level = atoi(optarg);
163 if (debug_level <= 0)
172 update_vol_info = true;
184 db_password = optarg;
203 case 'V': /* Volume name */
221 Pmsg0(0, _("Wrong number of arguments: \n"));
225 if (configfile == NULL) {
226 configfile = bstrdup(CONFIG_FILE);
229 parse_config(configfile);
231 me = (STORES *)GetNextRes(R_STORAGE, NULL);
234 Emsg1(M_ERROR_TERM, 0, _("No Storage resource defined in %s. Cannot continue.\n"),
238 /* Check if -w option given, otherwise use resource for working directory */
240 working_directory = wd;
241 } else if (!me->working_directory) {
242 Emsg1(M_ERROR_TERM, 0, _("No Working Directory defined in %s. Cannot continue.\n"),
245 working_directory = me->working_directory;
248 /* Check that working directory is good */
249 if (stat(working_directory, &stat_buf) != 0) {
250 Emsg1(M_ERROR_TERM, 0, _("Working Directory: %s not found. Cannot continue.\n"),
253 if (!S_ISDIR(stat_buf.st_mode)) {
254 Emsg1(M_ERROR_TERM, 0, _("Working Directory: %s is not a directory. Cannot continue.\n"),
258 bjcr = setup_jcr("bscan", argv[0], bsr, VolumeName, 1); /* read device */
262 dev = bjcr->read_dcr->dev;
267 currentVolumeSize = sb.st_size;
268 Pmsg1(000, _("First Volume Size = %sn"),
269 edit_uint64(currentVolumeSize, ed1));
272 if ((db=db_init_database(NULL, db_name, db_user, db_password,
273 db_host, 0, NULL, 0)) == NULL) {
274 Emsg0(M_ERROR_TERM, 0, _("Could not init Bacula database\n"));
276 if (!db_open_database(NULL, db)) {
277 Emsg0(M_ERROR_TERM, 0, db_strerror(db));
279 Dmsg0(200, "Database opened\n");
281 Pmsg2(000, _("Using Database: %s, User: %s\n"), db_name, db_user);
286 printf("Records added or updated in the catalog:\n%7d Media\n%7d Pool\n%7d Job\n%7d File\n",
287 num_media, num_pools, num_jobs, num_files);
290 printf("Records would have been added or updated in the catalog:\n%7d Media\n%7d Pool\n%7d Job\n%7d File\n",
291 num_media, num_pools, num_jobs, num_files);
300 * We are at the end of reading a tape. Now, we simulate handling
301 * the end of writing a tape by wiffling through the attached
302 * jcrs creating jobmedia records.
304 static bool bscan_mount_next_read_volume(DCR *dcr)
306 DEVICE *dev = dcr->dev;
308 Dmsg1(100, "Walk attached jcrs. Volume=%s\n", dev->VolCatInfo.VolCatName);
309 foreach_dlist(mdcr, dev->attached_dcrs) {
310 JCR *mjcr = mdcr->jcr;
311 if (mjcr->JobId == 0) {
315 Pmsg1(000, _("Create JobMedia for Job %s\n"), mjcr->Job);
317 if (dev->is_tape()) {
318 mdcr->EndBlock = dcr->EndBlock;
319 mdcr->EndFile = dcr->EndFile;
321 // mdcr->EndBlock = (uint32_t)dcr->file_addr;
322 // mdcr->EndFile = (uint32_t)(dcr->file_addr >> 32);
324 if (!create_jobmedia_record(db, mjcr)) {
325 Pmsg2(000, _("Could not create JobMedia record for Volume=%s Job=%s\n"),
326 dev->VolCatInfo.VolCatName, mjcr->Job);
329 /* Now let common read routine get up next tape. Note,
330 * we call mount_next... with bscan's jcr because that is where we
331 * have the Volume list, but we get attached.
333 bool stat = mount_next_read_volume(dcr);
339 currentVolumeSize = sb.st_size;
340 Pmsg1(000, _("First Volume Size = %sn"),
341 edit_uint64(currentVolumeSize, ed1));
346 static void do_scan()
350 memset(&ar, 0, sizeof(ar));
351 memset(&pr, 0, sizeof(pr));
352 memset(&jr, 0, sizeof(jr));
353 memset(&cr, 0, sizeof(cr));
354 memset(&fsr, 0, sizeof(fsr));
355 memset(&fr, 0, sizeof(fr));
357 /* Detach bscan's jcr as we are not a real Job on the tape */
359 read_records(bjcr->read_dcr, record_cb, bscan_mount_next_read_volume);
365 * Returns: true if OK
368 static bool record_cb(DCR *dcr, DEV_RECORD *rec)
372 DEVICE *dev = dcr->dev;
373 JCR *bjcr = dcr->jcr;
374 DEV_BLOCK *block = dcr->block;
375 char digest[BASE64_SIZE(CRYPTO_DIGEST_MAX_SIZE)];
377 if (rec->data_len > 0) {
378 mr.VolBytes += rec->data_len + WRITE_RECHDR_LENGTH; /* Accumulate Volume bytes */
380 int pct = (mr.VolBytes * 100) / currentVolumeSize;
381 if (pct != last_pct) {
382 fprintf(stdout, _("done: %d%%\n"), pct);
390 Pmsg5(000, _("Record: SessId=%u SessTim=%u FileIndex=%d Stream=%d len=%u\n"),
391 rec->VolSessionId, rec->VolSessionTime, rec->FileIndex,
392 rec->Stream, rec->data_len);
395 * Check for Start or End of Session Record
398 if (rec->FileIndex < 0) {
399 bool save_update_db = update_db;
402 dump_label_record(dev, rec, 1);
404 switch (rec->FileIndex) {
406 Pmsg0(000, _("Volume is prelabeled. This tape cannot be scanned.\n"));
411 unser_volume_label(dev, rec);
412 /* Check Pool info */
413 bstrncpy(pr.Name, dev->VolHdr.PoolName, sizeof(pr.Name));
414 bstrncpy(pr.PoolType, dev->VolHdr.PoolType, sizeof(pr.PoolType));
416 if (db_get_pool_record(bjcr, db, &pr)) {
418 Pmsg1(000, _("Pool record for %s found in DB.\n"), pr.Name);
422 Pmsg1(000, _("VOL_LABEL: Pool record not found for Pool: %s\n"),
425 create_pool_record(db, &pr);
427 if (strcmp(pr.PoolType, dev->VolHdr.PoolType) != 0) {
428 Pmsg2(000, _("VOL_LABEL: PoolType mismatch. DB=%s Vol=%s\n"),
429 pr.PoolType, dev->VolHdr.PoolType);
431 } else if (verbose) {
432 Pmsg1(000, _("Pool type \"%s\" is OK.\n"), pr.PoolType);
435 /* Check Media Info */
436 memset(&mr, 0, sizeof(mr));
437 bstrncpy(mr.VolumeName, dev->VolHdr.VolumeName, sizeof(mr.VolumeName));
438 mr.PoolId = pr.PoolId;
440 if (db_get_media_record(bjcr, db, &mr)) {
442 Pmsg1(000, _("Media record for %s found in DB.\n"), mr.VolumeName);
444 /* Clear out some volume statistics that will be updated */
445 mr.VolJobs = mr.VolFiles = mr.VolBlocks = 0;
446 mr.VolBytes = rec->data_len + 20;
449 Pmsg1(000, _("VOL_LABEL: Media record not found for Volume: %s\n"),
452 bstrncpy(mr.MediaType, dev->VolHdr.MediaType, sizeof(mr.MediaType));
453 create_media_record(db, &mr, &dev->VolHdr);
455 if (strcmp(mr.MediaType, dev->VolHdr.MediaType) != 0) {
456 Pmsg2(000, _("VOL_LABEL: MediaType mismatch. DB=%s Vol=%s\n"),
457 mr.MediaType, dev->VolHdr.MediaType);
458 return true; /* ignore error */
459 } else if (verbose) {
460 Pmsg1(000, _("Media type \"%s\" is OK.\n"), mr.MediaType);
462 /* Reset some DCR variables */
463 foreach_dlist(dcr, dev->attached_dcrs) {
464 dcr->VolFirstIndex = dcr->FileIndex = 0;
465 dcr->StartBlock = dcr->EndBlock = 0;
466 dcr->StartFile = dcr->EndFile = 0;
469 Pmsg1(000, _("VOL_LABEL: OK for Volume: %s\n"), mr.VolumeName);
475 if (ignored_msgs > 0) {
476 Pmsg1(000, _("%d \"errors\" ignored before first Start of Session record.\n"),
480 unser_session_label(&label, rec);
481 memset(&jr, 0, sizeof(jr));
482 bstrncpy(jr.Job, label.Job, sizeof(jr.Job));
483 if (db_get_job_record(bjcr, db, &jr)) {
484 /* Job record already exists in DB */
485 update_db = false; /* don't change db in create_job_record */
487 Pmsg1(000, _("SOS_LABEL: Found Job record for JobId: %d\n"), jr.JobId);
490 /* Must create a Job record in DB */
492 Pmsg1(000, _("SOS_LABEL: Job record not found for JobId: %d\n"),
496 /* Create Client record if not already there */
497 bstrncpy(cr.Name, label.ClientName, sizeof(cr.Name));
498 create_client_record(db, &cr);
499 jr.ClientId = cr.ClientId;
501 /* process label, if Job record exists don't update db */
502 mjcr = create_job_record(db, &jr, &label, rec);
503 dcr = mjcr->read_dcr;
504 update_db = save_update_db;
506 jr.PoolId = pr.PoolId;
508 /* Set start positions into JCR */
509 if (dev->is_tape()) {
511 * Note, we have already advanced past current block,
512 * so the correct number is block_num - 1
514 dcr->StartBlock = dev->block_num - 1;
515 dcr->StartFile = dev->file;
517 dcr->StartBlock = (uint32_t)dev->file_addr;
518 dcr->StartFile = (uint32_t)(dev->file_addr >> 32);
521 mjcr->start_time = jr.StartTime;
522 mjcr->JobLevel = jr.JobLevel;
524 mjcr->client_name = get_pool_memory(PM_FNAME);
525 pm_strcpy(mjcr->client_name, label.ClientName);
526 mjcr->fileset_name = get_pool_memory(PM_FNAME);
527 pm_strcpy(mjcr->fileset_name, label.FileSetName);
528 bstrncpy(dcr->pool_type, label.PoolType, sizeof(dcr->pool_type));
529 bstrncpy(dcr->pool_name, label.PoolName, sizeof(dcr->pool_name));
531 if (rec->VolSessionId != jr.VolSessionId) {
532 Pmsg3(000, _("SOS_LABEL: VolSessId mismatch for JobId=%u. DB=%d Vol=%d\n"),
534 jr.VolSessionId, rec->VolSessionId);
535 return true; /* ignore error */
537 if (rec->VolSessionTime != jr.VolSessionTime) {
538 Pmsg3(000, _("SOS_LABEL: VolSessTime mismatch for JobId=%u. DB=%d Vol=%d\n"),
540 jr.VolSessionTime, rec->VolSessionTime);
541 return true; /* ignore error */
543 if (jr.PoolId != pr.PoolId) {
544 Pmsg3(000, _("SOS_LABEL: PoolId mismatch for JobId=%u. DB=%d Vol=%d\n"),
546 jr.PoolId, pr.PoolId);
547 return true; /* ignore error */
552 unser_session_label(&elabel, rec);
554 /* Create FileSet record */
555 bstrncpy(fsr.FileSet, label.FileSetName, sizeof(fsr.FileSet));
556 bstrncpy(fsr.MD5, label.FileSetMD5, sizeof(fsr.MD5));
557 create_fileset_record(db, &fsr);
558 jr.FileSetId = fsr.FileSetId;
560 mjcr = get_jcr_by_session(rec->VolSessionId, rec->VolSessionTime);
562 Pmsg2(000, _("Could not find SessId=%d SessTime=%d for EOS record.\n"),
563 rec->VolSessionId, rec->VolSessionTime);
567 /* Do the final update to the Job record */
568 update_job_record(db, &jr, &elabel, rec);
570 mjcr->end_time = jr.EndTime;
571 mjcr->JobStatus = JS_Terminated;
573 /* Create JobMedia record */
574 create_jobmedia_record(db, mjcr);
575 dev->attached_dcrs->remove(mjcr->read_dcr);
583 case EOT_LABEL: /* end of all tapes */
585 * Wiffle through all jobs still open and close
590 foreach_dlist(mdcr, dev->attached_dcrs) {
591 JCR *mjcr = mdcr->jcr;
592 if (!mjcr || mjcr->JobId == 0) {
595 jr.JobId = mjcr->JobId;
596 /* Mark Job as Error Terimined */
597 jr.JobStatus = JS_ErrorTerminated;
598 jr.JobFiles = mjcr->JobFiles;
599 jr.JobBytes = mjcr->JobBytes;
600 jr.VolSessionId = mjcr->VolSessionId;
601 jr.VolSessionTime = mjcr->VolSessionTime;
602 jr.JobTDate = (utime_t)mjcr->start_time;
603 jr.ClientId = mjcr->ClientId;
604 if (!db_update_job_end_record(bjcr, db, &jr)) {
605 Pmsg1(0, _("Could not update job record. ERR=%s\n"), db_strerror(db));
607 mjcr->read_dcr = NULL;
611 mr.VolFiles = rec->File;
612 mr.VolBlocks = rec->Block;
613 mr.VolBytes += mr.VolBlocks * WRITE_BLKHDR_LENGTH; /* approx. */
615 update_media_record(db, &mr);
616 Pmsg3(0, _("End of all Volumes. VolFiles=%u VolBlocks=%u VolBytes=%s\n"), mr.VolFiles,
617 mr.VolBlocks, edit_uint64_with_commas(mr.VolBytes, ec1));
625 mjcr = get_jcr_by_session(rec->VolSessionId, rec->VolSessionTime);
627 if (mr.VolJobs > 0) {
628 Pmsg2(000, _("Could not find Job for SessId=%d SessTime=%d record.\n"),
629 rec->VolSessionId, rec->VolSessionTime);
635 dcr = mjcr->read_dcr;
636 if (dcr->VolFirstIndex == 0) {
637 dcr->VolFirstIndex = block->FirstIndex;
640 /* File Attributes stream */
641 switch (rec->Stream) {
642 case STREAM_UNIX_ATTRIBUTES:
643 case STREAM_UNIX_ATTRIBUTES_EX:
645 if (!unpack_attributes_record(bjcr, rec->Stream, rec->data, attr)) {
646 Emsg0(M_ERROR_TERM, 0, _("Cannot continue.\n"));
649 if (attr->file_index != rec->FileIndex) {
650 Emsg2(M_ERROR_TERM, 0, _("Record header file index %ld not equal record index %ld\n"),
651 rec->FileIndex, attr->file_index);
655 decode_stat(attr->attr, &attr->statp, &attr->LinkFI);
656 build_attr_output_fnames(bjcr, attr);
657 print_ls_output(bjcr, attr);
659 fr.JobId = mjcr->JobId;
662 if (verbose && (num_files & 0x7FFF) == 0) {
663 char ed1[30], ed2[30], ed3[30], ed4[30];
664 Pmsg4(000, _("%s file records. At file:blk=%s:%s bytes=%s\n"),
665 edit_uint64_with_commas(num_files, ed1),
666 edit_uint64_with_commas(rec->File, ed2),
667 edit_uint64_with_commas(rec->Block, ed3),
668 edit_uint64_with_commas(mr.VolBytes, ed4));
670 create_file_attributes_record(db, mjcr, attr->fname, attr->lname,
671 attr->type, attr->attr, rec);
676 case STREAM_WIN32_DATA:
677 case STREAM_FILE_DATA:
678 case STREAM_SPARSE_DATA:
679 mjcr->JobBytes += rec->data_len;
680 if (rec->Stream == STREAM_SPARSE_DATA) {
681 mjcr->JobBytes -= sizeof(uint64_t);
684 free_jcr(mjcr); /* done using JCR */
687 case STREAM_GZIP_DATA:
688 mjcr->JobBytes += rec->data_len; /* No correct, we should expand it */
689 free_jcr(mjcr); /* done using JCR */
692 case STREAM_SPARSE_GZIP_DATA:
693 mjcr->JobBytes += rec->data_len - sizeof(uint64_t); /* No correct, we should expand it */
694 free_jcr(mjcr); /* done using JCR */
697 /* Win32 GZIP stream */
698 case STREAM_WIN32_GZIP_DATA:
699 mjcr->JobBytes += rec->data_len;
700 free_jcr(mjcr); /* done using JCR */
703 case STREAM_MD5_DIGEST:
704 bin_to_base64(digest, (char *)rec->data, CRYPTO_DIGEST_MD5_SIZE);
706 Pmsg1(000, _("Got MD5 record: %s\n"), digest);
708 update_digest_record(db, digest, rec, CRYPTO_DIGEST_MD5);
711 case STREAM_SHA1_DIGEST:
712 bin_to_base64(digest, (char *)rec->data, CRYPTO_DIGEST_SHA1_SIZE);
714 Pmsg1(000, _("Got SHA1 record: %s\n"), digest);
716 update_digest_record(db, digest, rec, CRYPTO_DIGEST_SHA1);
719 case STREAM_SHA256_DIGEST:
720 bin_to_base64(digest, (char *)rec->data, CRYPTO_DIGEST_SHA256_SIZE);
722 Pmsg1(000, _("Got SHA256 record: %s\n"), digest);
724 update_digest_record(db, digest, rec, CRYPTO_DIGEST_SHA256);
727 case STREAM_SHA512_DIGEST:
728 bin_to_base64(digest, (char *)rec->data, CRYPTO_DIGEST_SHA512_SIZE);
730 Pmsg1(000, _("Got SHA512 record: %s\n"), digest);
732 update_digest_record(db, digest, rec, CRYPTO_DIGEST_SHA512);
735 case STREAM_SIGNED_DIGEST:
736 // TODO landonf: Investigate signed digest support in bscan
738 Pmsg0(000, _("Got signed digest record\n"));
742 case STREAM_PROGRAM_NAMES:
744 Pmsg1(000, _("Got Prog Names Stream: %s\n"), rec->data);
748 case STREAM_PROGRAM_DATA:
750 Pmsg0(000, _("Got Prog Data Stream record.\n"));
754 Pmsg2(0, _("Unknown stream type!!! stream=%d data=%s\n"), rec->Stream, rec->data);
761 * Free the Job Control Record if no one is still using it.
762 * Called from main free_jcr() routine in src/lib/jcr.c so
763 * that we can do our Director specific cleanup of the jcr.
765 static void bscan_free_jcr(JCR *jcr)
767 Dmsg0(200, "Start bscan free_jcr\n");
769 if (jcr->file_bsock) {
770 Dmsg0(200, "Close File bsock\n");
771 bnet_close(jcr->file_bsock);
773 if (jcr->store_bsock) {
774 Dmsg0(200, "Close Store bsock\n");
775 bnet_close(jcr->store_bsock);
777 if (jcr->RestoreBootstrap) {
778 free(jcr->RestoreBootstrap);
785 free_dcr(jcr->read_dcr);
786 jcr->read_dcr = NULL;
788 Dmsg0(200, "End bscan free_jcr\n");
792 * We got a File Attributes record on the tape. Now, lookup the Job
793 * record, and then create the attributes record.
795 static int create_file_attributes_record(B_DB *db, JCR *mjcr,
796 char *fname, char *lname, int type,
797 char *ap, DEV_RECORD *rec)
799 DCR *dcr = mjcr->read_dcr;
802 ar.ClientId = mjcr->ClientId;
803 ar.JobId = mjcr->JobId;
804 ar.Stream = rec->Stream;
805 ar.FileIndex = rec->FileIndex;
807 if (dcr->VolFirstIndex == 0) {
808 dcr->VolFirstIndex = rec->FileIndex;
810 dcr->FileIndex = rec->FileIndex;
817 if (!db_create_file_attributes_record(bjcr, db, &ar)) {
818 Pmsg1(0, _("Could not create File Attributes record. ERR=%s\n"), db_strerror(db));
821 mjcr->FileId = ar.FileId;
824 Pmsg1(000, _("Created File record: %s\n"), fname);
830 * For each Volume we see, we create a Medium record
832 static int create_media_record(B_DB *db, MEDIA_DBR *mr, VOLUME_LABEL *vl)
837 /* We mark Vols as Archive to keep them from being re-written */
838 bstrncpy(mr->VolStatus, "Archive", sizeof(mr->VolStatus));
839 mr->VolRetention = 365 * 3600 * 24; /* 1 year */
840 if (vl->VerNum >= 11) {
841 mr->FirstWritten = btime_to_utime(vl->write_btime);
842 mr->LabelDate = btime_to_utime(vl->label_btime);
844 /* DEPRECATED DO NOT USE */
845 dt.julian_day_number = vl->write_date;
846 dt.julian_day_fraction = vl->write_time;
848 mr->FirstWritten = mktime(&tm);
849 dt.julian_day_number = vl->label_date;
850 dt.julian_day_fraction = vl->label_time;
852 mr->LabelDate = mktime(&tm);
854 lasttime = mr->LabelDate;
860 if (!db_create_media_record(bjcr, db, mr)) {
861 Pmsg1(0, _("Could not create media record. ERR=%s\n"), db_strerror(db));
864 if (!db_update_media_record(bjcr, db, mr)) {
865 Pmsg1(0, _("Could not update media record. ERR=%s\n"), db_strerror(db));
869 Pmsg1(000, _("Created Media record for Volume: %s\n"), mr->VolumeName);
876 * Called at end of media to update it
878 static bool update_media_record(B_DB *db, MEDIA_DBR *mr)
880 if (!update_db && !update_vol_info) {
884 mr->LastWritten = lasttime;
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, _("Updated Media record at end of Volume: %s\n"), mr->VolumeName);
897 static int create_pool_record(B_DB *db, POOL_DBR *pr)
901 pr->VolRetention = 355 * 3600 * 24; /* 1 year */
906 if (!db_create_pool_record(bjcr, db, pr)) {
907 Pmsg1(0, _("Could not create pool record. ERR=%s\n"), db_strerror(db));
911 Pmsg1(000, _("Created Pool record for Pool: %s\n"), pr->Name);
919 * Called from SOS to create a client for the current Job
921 static int create_client_record(B_DB *db, CLIENT_DBR *cr)
926 if (!db_create_client_record(bjcr, db, cr)) {
927 Pmsg1(0, _("Could not create Client record. ERR=%s\n"), db_strerror(db));
931 Pmsg1(000, _("Created Client record for Client: %s\n"), cr->Name);
936 static int create_fileset_record(B_DB *db, FILESET_DBR *fsr)
942 if (fsr->MD5[0] == 0) {
943 fsr->MD5[0] = ' '; /* Equivalent to nothing */
946 if (db_get_fileset_record(bjcr, db, fsr)) {
948 Pmsg1(000, _("Fileset \"%s\" already exists.\n"), fsr->FileSet);
951 if (!db_create_fileset_record(bjcr, db, fsr)) {
952 Pmsg2(0, _("Could not create FileSet record \"%s\". ERR=%s\n"),
953 fsr->FileSet, db_strerror(db));
957 Pmsg1(000, _("Created FileSet record \"%s\"\n"), fsr->FileSet);
964 * Simulate the two calls on the database to create
965 * the Job record and to update it when the Job actually
968 static JCR *create_job_record(B_DB *db, JOB_DBR *jr, SESSION_LABEL *label,
975 jr->JobId = label->JobId;
976 jr->JobType = label->JobType;
977 jr->JobLevel = label->JobLevel;
978 jr->JobStatus = JS_Created;
979 bstrncpy(jr->Name, label->JobName, sizeof(jr->Name));
980 bstrncpy(jr->Job, label->Job, sizeof(jr->Job));
981 if (label->VerNum >= 11) {
982 jr->SchedTime = btime_to_unix(label->write_btime);
984 dt.julian_day_number = label->write_date;
985 dt.julian_day_fraction = label->write_time;
987 jr->SchedTime = mktime(&tm);
990 jr->StartTime = jr->SchedTime;
991 jr->JobTDate = (utime_t)jr->SchedTime;
992 jr->VolSessionId = rec->VolSessionId;
993 jr->VolSessionTime = rec->VolSessionTime;
995 /* Now create a JCR as if starting the Job */
996 mjcr = create_jcr(jr, rec, label->JobId);
1002 /* This creates the bare essentials */
1003 if (!db_create_job_record(bjcr, db, jr)) {
1004 Pmsg1(0, _("Could not create JobId record. ERR=%s\n"), db_strerror(db));
1008 /* This adds the client, StartTime, JobTDate, ... */
1009 if (!db_update_job_start_record(bjcr, db, jr)) {
1010 Pmsg1(0, _("Could not update job start record. ERR=%s\n"), db_strerror(db));
1013 Pmsg2(000, _("Created new JobId=%u record for original JobId=%u\n"), jr->JobId,
1015 mjcr->JobId = jr->JobId; /* set new JobId */
1020 * Simulate the database call that updates the Job
1021 * at Job termination time.
1023 static int update_job_record(B_DB *db, JOB_DBR *jr, SESSION_LABEL *elabel,
1026 struct date_time dt;
1030 mjcr = get_jcr_by_session(rec->VolSessionId, rec->VolSessionTime);
1032 Pmsg2(000, _("Could not find SessId=%d SessTime=%d for EOS record.\n"),
1033 rec->VolSessionId, rec->VolSessionTime);
1036 if (elabel->VerNum >= 11) {
1037 jr->EndTime = btime_to_unix(elabel->write_btime);
1039 dt.julian_day_number = elabel->write_date;
1040 dt.julian_day_fraction = elabel->write_time;
1041 tm_decode(&dt, &tm);
1042 jr->EndTime = mktime(&tm);
1044 lasttime = jr->EndTime;
1045 mjcr->end_time = jr->EndTime;
1047 jr->JobId = mjcr->JobId;
1048 jr->JobStatus = elabel->JobStatus;
1049 mjcr->JobStatus = elabel->JobStatus;
1050 jr->JobFiles = elabel->JobFiles;
1051 jr->JobBytes = elabel->JobBytes;
1052 jr->VolSessionId = rec->VolSessionId;
1053 jr->VolSessionTime = rec->VolSessionTime;
1054 jr->JobTDate = (utime_t)mjcr->start_time;
1055 jr->ClientId = mjcr->ClientId;
1062 if (!db_update_job_end_record(bjcr, db, jr)) {
1063 Pmsg2(0, _("Could not update JobId=%u record. ERR=%s\n"), jr->JobId, db_strerror(db));
1068 Pmsg2(000, _("Updated Job termination record for JobId=%u TermStat=%c\n"), jr->JobId,
1072 const char *term_msg;
1073 static char term_code[70];
1074 char sdt[50], edt[50];
1075 char ec1[30], ec2[30], ec3[30];
1077 switch (mjcr->JobStatus) {
1079 term_msg = _("Backup OK");
1082 case JS_ErrorTerminated:
1083 term_msg = _("*** Backup Error ***");
1086 term_msg = _("Backup Canceled");
1089 term_msg = term_code;
1090 sprintf(term_code, _("Job Termination code: %d"), mjcr->JobStatus);
1093 bstrftime(sdt, sizeof(sdt), mjcr->start_time);
1094 bstrftime(edt, sizeof(edt), mjcr->end_time);
1095 Pmsg14(000, _("%s\n"
1099 "Backup Level: %s\n"
1103 "Files Written: %s\n"
1104 "Bytes Written: %s\n"
1105 "Volume Session Id: %d\n"
1106 "Volume Session Time: %d\n"
1107 "Last Volume Bytes: %s\n"
1108 "Termination: %s\n\n"),
1113 job_level_to_str(mjcr->JobLevel),
1117 edit_uint64_with_commas(mjcr->JobFiles, ec1),
1118 edit_uint64_with_commas(mjcr->JobBytes, ec2),
1120 mjcr->VolSessionTime,
1121 edit_uint64_with_commas(mr.VolBytes, ec3),
1128 static int create_jobmedia_record(B_DB *db, JCR *mjcr)
1131 DCR *dcr = mjcr->read_dcr;
1133 if (dev->is_tape()) {
1134 dcr->EndBlock = dev->EndBlock;
1135 dcr->EndFile = dev->EndFile;
1138 dcr->EndBlock = (uint32_t)dev->file_addr;
1139 dcr->EndFile = (uint32_t)(dev->file_addr >> 32);
1143 memset(&jmr, 0, sizeof(jmr));
1144 jmr.JobId = mjcr->JobId;
1145 jmr.MediaId = mr.MediaId;
1146 jmr.FirstIndex = dcr->VolFirstIndex;
1147 jmr.LastIndex = dcr->FileIndex;
1148 jmr.StartFile = dcr->StartFile;
1149 jmr.EndFile = dcr->EndFile;
1150 jmr.StartBlock = dcr->StartBlock;
1151 jmr.EndBlock = dcr->EndBlock;
1158 if (!db_create_jobmedia_record(bjcr, db, &jmr)) {
1159 Pmsg1(0, _("Could not create JobMedia record. ERR=%s\n"), db_strerror(db));
1163 Pmsg2(000, _("Created JobMedia record JobId %d, MediaId %d\n"),
1164 jmr.JobId, jmr.MediaId);
1170 * Simulate the database call that updates the MD5/SHA1 record
1172 static int update_digest_record(B_DB *db, char *digest, DEV_RECORD *rec, int type)
1176 mjcr = get_jcr_by_session(rec->VolSessionId, rec->VolSessionTime);
1178 if (mr.VolJobs > 0) {
1179 Pmsg2(000, _("Could not find SessId=%d SessTime=%d for MD5/SHA1 record.\n"),
1180 rec->VolSessionId, rec->VolSessionTime);
1187 if (!update_db || mjcr->FileId == 0) {
1192 if (!db_add_digest_to_file_record(bjcr, db, mjcr->FileId, digest, type)) {
1193 Pmsg1(0, _("Could not add MD5/SHA1 to File record. ERR=%s\n"), db_strerror(db));
1198 Pmsg0(000, _("Updated MD5/SHA1 record\n"));
1206 * Create a JCR as if we are really starting the job
1208 static JCR *create_jcr(JOB_DBR *jr, DEV_RECORD *rec, uint32_t JobId)
1212 * Transfer as much as possible to the Job JCR. Most important is
1213 * the JobId and the ClientId.
1215 jobjcr = new_jcr(sizeof(JCR), bscan_free_jcr);
1216 jobjcr->JobType = jr->JobType;
1217 jobjcr->JobLevel = jr->JobLevel;
1218 jobjcr->JobStatus = jr->JobStatus;
1219 bstrncpy(jobjcr->Job, jr->Job, sizeof(jobjcr->Job));
1220 jobjcr->JobId = JobId; /* this is JobId on tape */
1221 jobjcr->sched_time = jr->SchedTime;
1222 jobjcr->start_time = jr->StartTime;
1223 jobjcr->VolSessionId = rec->VolSessionId;
1224 jobjcr->VolSessionTime = rec->VolSessionTime;
1225 jobjcr->ClientId = jr->ClientId;
1226 jobjcr->read_dcr = new_dcr(jobjcr, dev);
1231 /* Dummies to replace askdir.c */
1232 bool dir_get_volume_info(DCR *dcr, enum get_vol_info_rw writing) { return 1;}
1233 bool dir_find_next_appendable_volume(DCR *dcr) { return 1;}
1234 bool dir_update_volume_info(DCR *dcr, bool relabel) { return 1; }
1235 bool dir_create_jobmedia_record(DCR *dcr) { return 1; }
1236 bool dir_ask_sysop_to_create_appendable_volume(DCR *dcr) { return 1; }
1237 bool dir_update_file_attributes(DCR *dcr, DEV_RECORD *rec) { return 1;}
1238 bool dir_send_job_status(JCR *jcr) {return 1;}
1239 int generate_job_event(JCR *jcr, const char *event) { return 1; }
1240 VOLRES *new_volume(DCR *dcr, const char *VolumeName) { return NULL; }
1241 bool free_volume(DEVICE *dev) { return true; }
1242 void free_unused_volume(DCR *dcr) { }
1244 bool dir_ask_sysop_to_mount_volume(DCR *dcr)
1246 DEVICE *dev = dcr->dev;
1247 Dmsg0(20, "Enter dir_ask_sysop_to_mount_volume\n");
1248 /* Close device so user can use autochanger if desired */
1249 if (dev_cap(dev, CAP_OFFLINEUNMOUNT)) {
1252 force_close_device(dev);
1253 fprintf(stderr, _("Mount Volume \"%s\" on device %s and press return when ready: "),
1254 dcr->VolumeName, dev->print_name());