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_SIG_record(B_DB *db, char *SIGbuf, 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: " VERSION " (" BDATE ")\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"));
131 int main (int argc, char *argv[])
134 struct stat stat_buf;
135 char *VolumeName = NULL;
137 my_name_is(argc, argv, "bscan");
138 init_msg(NULL, NULL);
141 while ((ch = getopt(argc, argv, "b:c:d:h:mn:pP:rsSu:vV:w:?")) != -1) {
147 bsr = parse_bsr(NULL, optarg);
150 case 'c': /* specify config file */
151 if (configfile != NULL) {
154 configfile = bstrdup(optarg);
157 case 'd': /* debug level */
158 debug_level = atoi(optarg);
159 if (debug_level <= 0)
168 update_vol_info = true;
180 db_password = optarg;
199 case 'V': /* Volume name */
217 Pmsg0(0, _("Wrong number of arguments: \n"));
221 if (configfile == NULL) {
222 configfile = bstrdup(CONFIG_FILE);
225 parse_config(configfile);
227 me = (STORES *)GetNextRes(R_STORAGE, NULL);
230 Emsg1(M_ERROR_TERM, 0, _("No Storage resource defined in %s. Cannot continue.\n"),
234 /* Check if -w option given, otherwise use resource for working directory */
236 working_directory = wd;
237 } else if (!me->working_directory) {
238 Emsg1(M_ERROR_TERM, 0, _("No Working Directory defined in %s. Cannot continue.\n"),
241 working_directory = me->working_directory;
244 /* Check that working directory is good */
245 if (stat(working_directory, &stat_buf) != 0) {
246 Emsg1(M_ERROR_TERM, 0, _("Working Directory: %s not found. Cannot continue.\n"),
249 if (!S_ISDIR(stat_buf.st_mode)) {
250 Emsg1(M_ERROR_TERM, 0, _("Working Directory: %s is not a directory. Cannot continue.\n"),
254 bjcr = setup_jcr("bscan", argv[0], bsr, VolumeName, 1); /* read device */
258 dev = bjcr->dcr->dev;
263 currentVolumeSize = sb.st_size;
264 Pmsg1(000, _("First Volume Size = %sn"),
265 edit_uint64(currentVolumeSize, ed1));
268 if ((db=db_init_database(NULL, db_name, db_user, db_password,
269 db_host, 0, NULL, 0)) == NULL) {
270 Emsg0(M_ERROR_TERM, 0, _("Could not init Bacula database\n"));
272 if (!db_open_database(NULL, db)) {
273 Emsg0(M_ERROR_TERM, 0, db_strerror(db));
275 Dmsg0(200, "Database opened\n");
277 Pmsg2(000, _("Using Database: %s, User: %s\n"), db_name, db_user);
281 printf("Records %sadded or updated in the catalog:\n%7d Media\n%7d Pool\n%7d Job\n%7d File\n",
282 update_db?"":"would have been ",
283 num_media, num_pools, num_jobs, num_files);
291 * We are at the end of reading a tape. Now, we simulate handling
292 * the end of writing a tape by wiffling through the attached
293 * jcrs creating jobmedia records.
295 static bool bscan_mount_next_read_volume(DCR *dcr)
297 DEVICE *dev = dcr->dev;
299 Dmsg1(100, "Walk attached jcrs. Volume=%s\n", dev->VolCatInfo.VolCatName);
300 foreach_dlist(mdcr, dev->attached_dcrs) {
301 JCR *mjcr = mdcr->jcr;
302 if (mjcr->JobId == 0) {
306 Pmsg1(000, _("Create JobMedia for Job %s\n"), mjcr->Job);
308 if (dev->is_tape()) {
309 mdcr->EndBlock = dcr->EndBlock;
310 mdcr->EndFile = dcr->EndFile;
312 // mdcr->EndBlock = (uint32_t)dcr->file_addr;
313 // mdcr->EndFile = (uint32_t)(dcr->file_addr >> 32);
315 if (!create_jobmedia_record(db, mjcr)) {
316 Pmsg2(000, _("Could not create JobMedia record for Volume=%s Job=%s\n"),
317 dev->VolCatInfo.VolCatName, mjcr->Job);
320 /* Now let common read routine get up next tape. Note,
321 * we call mount_next... with bscan's jcr because that is where we
322 * have the Volume list, but we get attached.
324 bool stat = mount_next_read_volume(dcr);
330 currentVolumeSize = sb.st_size;
331 Pmsg1(000, _("First Volume Size = %sn"),
332 edit_uint64(currentVolumeSize, ed1));
337 static void do_scan()
341 memset(&ar, 0, sizeof(ar));
342 memset(&pr, 0, sizeof(pr));
343 memset(&jr, 0, sizeof(jr));
344 memset(&cr, 0, sizeof(cr));
345 memset(&fsr, 0, sizeof(fsr));
346 memset(&fr, 0, sizeof(fr));
348 /* Detach bscan's jcr as we are not a real Job on the tape */
350 read_records(bjcr->dcr, record_cb, bscan_mount_next_read_volume);
356 * Returns: true if OK
359 static bool record_cb(DCR *dcr, DEV_RECORD *rec)
363 DEVICE *dev = dcr->dev;
364 JCR *bjcr = dcr->jcr;
365 DEV_BLOCK *block = dcr->block;
367 if (rec->data_len > 0) {
368 mr.VolBytes += rec->data_len + WRITE_RECHDR_LENGTH; /* Accumulate Volume bytes */
370 int pct = (mr.VolBytes * 100) / currentVolumeSize;
371 if (pct != last_pct) {
372 fprintf(stdout, "done: %d%%\n", pct);
380 Pmsg5(000, _("Record: SessId=%u SessTim=%u FileIndex=%d Stream=%d len=%u\n"),
381 rec->VolSessionId, rec->VolSessionTime, rec->FileIndex,
382 rec->Stream, rec->data_len);
385 * Check for Start or End of Session Record
388 if (rec->FileIndex < 0) {
389 bool save_update_db = update_db;
392 dump_label_record(dev, rec, 1);
394 switch (rec->FileIndex) {
396 Pmsg0(000, _("Volume is prelabeled. This tape cannot be scanned.\n"));
401 unser_volume_label(dev, rec);
402 /* Check Pool info */
403 bstrncpy(pr.Name, dev->VolHdr.PoolName, sizeof(pr.Name));
404 bstrncpy(pr.PoolType, dev->VolHdr.PoolType, sizeof(pr.PoolType));
406 if (db_get_pool_record(bjcr, db, &pr)) {
408 Pmsg1(000, _("Pool record for %s found in DB.\n"), pr.Name);
412 Pmsg1(000, _("VOL_LABEL: Pool record not found for Pool: %s\n"),
415 create_pool_record(db, &pr);
417 if (strcmp(pr.PoolType, dev->VolHdr.PoolType) != 0) {
418 Pmsg2(000, _("VOL_LABEL: PoolType mismatch. DB=%s Vol=%s\n"),
419 pr.PoolType, dev->VolHdr.PoolType);
421 } else if (verbose) {
422 Pmsg1(000, _("Pool type \"%s\" is OK.\n"), pr.PoolType);
425 /* Check Media Info */
426 memset(&mr, 0, sizeof(mr));
427 bstrncpy(mr.VolumeName, dev->VolHdr.VolumeName, sizeof(mr.VolumeName));
428 mr.PoolId = pr.PoolId;
430 if (db_get_media_record(bjcr, db, &mr)) {
432 Pmsg1(000, _("Media record for %s found in DB.\n"), mr.VolumeName);
434 /* Clear out some volume statistics that will be updated */
435 mr.VolJobs = mr.VolFiles = mr.VolBlocks = 0;
436 mr.VolBytes = rec->data_len + 20;
439 Pmsg1(000, _("VOL_LABEL: Media record not found for Volume: %s\n"),
442 bstrncpy(mr.MediaType, dev->VolHdr.MediaType, sizeof(mr.MediaType));
443 create_media_record(db, &mr, &dev->VolHdr);
445 if (strcmp(mr.MediaType, dev->VolHdr.MediaType) != 0) {
446 Pmsg2(000, _("VOL_LABEL: MediaType mismatch. DB=%s Vol=%s\n"),
447 mr.MediaType, dev->VolHdr.MediaType);
448 return true; /* ignore error */
449 } else if (verbose) {
450 Pmsg1(000, _("Media type \"%s\" is OK.\n"), mr.MediaType);
452 /* Reset some DCR variables */
453 foreach_dlist(dcr, dev->attached_dcrs) {
454 dcr->VolFirstIndex = dcr->FileIndex = 0;
455 dcr->StartBlock = dcr->EndBlock = 0;
456 dcr->StartFile = dcr->EndFile = 0;
459 Pmsg1(000, _("VOL_LABEL: OK for Volume: %s\n"), mr.VolumeName);
465 if (ignored_msgs > 0) {
466 Pmsg1(000, _("%d \"errors\" ignored before first Start of Session record.\n"),
470 unser_session_label(&label, rec);
471 memset(&jr, 0, sizeof(jr));
472 bstrncpy(jr.Job, label.Job, sizeof(jr.Job));
473 if (db_get_job_record(bjcr, db, &jr)) {
474 /* Job record already exists in DB */
475 update_db = false; /* don't change db in create_job_record */
477 Pmsg1(000, _("SOS_LABEL: Found Job record for JobId: %d\n"), jr.JobId);
480 /* Must create a Job record in DB */
482 Pmsg1(000, _("SOS_LABEL: Job record not found for JobId: %d\n"),
486 /* Create Client record if not already there */
487 bstrncpy(cr.Name, label.ClientName, sizeof(cr.Name));
488 create_client_record(db, &cr);
489 jr.ClientId = cr.ClientId;
491 /* process label, if Job record exists don't update db */
492 mjcr = create_job_record(db, &jr, &label, rec);
494 update_db = save_update_db;
496 jr.PoolId = pr.PoolId;
498 /* Set start positions into JCR */
499 if (dev->is_tape()) {
501 * Note, we have already advanced past current block,
502 * so the correct number is block_num - 1
504 dcr->StartBlock = dev->block_num - 1;
505 dcr->StartFile = dev->file;
507 dcr->StartBlock = (uint32_t)dev->file_addr;
508 dcr->StartFile = (uint32_t)(dev->file_addr >> 32);
511 mjcr->start_time = jr.StartTime;
512 mjcr->JobLevel = jr.JobLevel;
514 mjcr->client_name = get_pool_memory(PM_FNAME);
515 pm_strcpy(mjcr->client_name, label.ClientName);
516 mjcr->fileset_name = get_pool_memory(PM_FNAME);
517 pm_strcpy(mjcr->fileset_name, label.FileSetName);
518 bstrncpy(dcr->pool_type, label.PoolType, sizeof(dcr->pool_type));
519 bstrncpy(dcr->pool_name, label.PoolName, sizeof(dcr->pool_name));
521 if (rec->VolSessionId != jr.VolSessionId) {
522 Pmsg3(000, _("SOS_LABEL: VolSessId mismatch for JobId=%u. DB=%d Vol=%d\n"),
524 jr.VolSessionId, rec->VolSessionId);
525 return true; /* ignore error */
527 if (rec->VolSessionTime != jr.VolSessionTime) {
528 Pmsg3(000, _("SOS_LABEL: VolSessTime mismatch for JobId=%u. DB=%d Vol=%d\n"),
530 jr.VolSessionTime, rec->VolSessionTime);
531 return true; /* ignore error */
533 if (jr.PoolId != pr.PoolId) {
534 Pmsg3(000, _("SOS_LABEL: PoolId mismatch for JobId=%u. DB=%d Vol=%d\n"),
536 jr.PoolId, pr.PoolId);
537 return true; /* ignore error */
542 unser_session_label(&elabel, rec);
544 /* Create FileSet record */
545 bstrncpy(fsr.FileSet, label.FileSetName, sizeof(fsr.FileSet));
546 bstrncpy(fsr.MD5, label.FileSetMD5, sizeof(fsr.MD5));
547 create_fileset_record(db, &fsr);
548 jr.FileSetId = fsr.FileSetId;
550 mjcr = get_jcr_by_session(rec->VolSessionId, rec->VolSessionTime);
552 Pmsg2(000, _("Could not find SessId=%d SessTime=%d for EOS record.\n"),
553 rec->VolSessionId, rec->VolSessionTime);
557 /* Do the final update to the Job record */
558 update_job_record(db, &jr, &elabel, rec);
560 mjcr->end_time = jr.EndTime;
561 mjcr->JobStatus = JS_Terminated;
563 /* Create JobMedia record */
564 create_jobmedia_record(db, mjcr);
565 dev->attached_dcrs->remove(mjcr->dcr);
573 case EOT_LABEL: /* end of all tapes */
575 * Wiffle through all jobs still open and close
580 foreach_dlist(mdcr, dev->attached_dcrs) {
581 JCR *mjcr = mdcr->jcr;
582 if (!mjcr || mjcr->JobId == 0) {
585 jr.JobId = mjcr->JobId;
586 /* Mark Job as Error Terimined */
587 jr.JobStatus = JS_ErrorTerminated;
588 jr.JobFiles = mjcr->JobFiles;
589 jr.JobBytes = mjcr->JobBytes;
590 jr.VolSessionId = mjcr->VolSessionId;
591 jr.VolSessionTime = mjcr->VolSessionTime;
592 jr.JobTDate = (utime_t)mjcr->start_time;
593 jr.ClientId = mjcr->ClientId;
594 if (!db_update_job_end_record(bjcr, db, &jr)) {
595 Pmsg1(0, _("Could not update job record. ERR=%s\n"), db_strerror(db));
601 mr.VolFiles = rec->File;
602 mr.VolBlocks = rec->Block;
603 mr.VolBytes += mr.VolBlocks * WRITE_BLKHDR_LENGTH; /* approx. */
605 update_media_record(db, &mr);
606 Pmsg3(0, _("End of all Volumes. VolFiles=%u VolBlocks=%u VolBytes=%s\n"), mr.VolFiles,
607 mr.VolBlocks, edit_uint64_with_commas(mr.VolBytes, ec1));
615 mjcr = get_jcr_by_session(rec->VolSessionId, rec->VolSessionTime);
617 if (mr.VolJobs > 0) {
618 Pmsg2(000, _("Could not find Job for SessId=%d SessTime=%d record.\n"),
619 rec->VolSessionId, rec->VolSessionTime);
626 if (dcr->VolFirstIndex == 0) {
627 dcr->VolFirstIndex = block->FirstIndex;
630 /* File Attributes stream */
631 switch (rec->Stream) {
632 case STREAM_UNIX_ATTRIBUTES:
633 case STREAM_UNIX_ATTRIBUTES_EX:
635 if (!unpack_attributes_record(bjcr, rec->Stream, rec->data, attr)) {
636 Emsg0(M_ERROR_TERM, 0, _("Cannot continue.\n"));
639 if (attr->file_index != rec->FileIndex) {
640 Emsg2(M_ERROR_TERM, 0, _("Record header file index %ld not equal record index %ld\n"),
641 rec->FileIndex, attr->file_index);
645 decode_stat(attr->attr, &attr->statp, &attr->LinkFI);
646 build_attr_output_fnames(bjcr, attr);
647 print_ls_output(bjcr, attr);
649 fr.JobId = mjcr->JobId;
652 if (verbose && (num_files & 0x7FFF) == 0) {
653 char ed1[30], ed2[30], ed3[30], ed4[30];
654 Pmsg4(000, _("%s file records. At file:blk=%s:%s bytes=%s\n"),
655 edit_uint64_with_commas(num_files, ed1),
656 edit_uint64_with_commas(rec->File, ed2),
657 edit_uint64_with_commas(rec->Block, ed3),
658 edit_uint64_with_commas(mr.VolBytes, ed4));
660 create_file_attributes_record(db, mjcr, attr->fname, attr->lname,
661 attr->type, attr->attr, rec);
666 case STREAM_WIN32_DATA:
667 case STREAM_FILE_DATA:
668 case STREAM_SPARSE_DATA:
669 mjcr->JobBytes += rec->data_len;
670 if (rec->Stream == STREAM_SPARSE_DATA) {
671 mjcr->JobBytes -= sizeof(uint64_t);
674 free_jcr(mjcr); /* done using JCR */
677 case STREAM_GZIP_DATA:
678 mjcr->JobBytes += rec->data_len; /* No correct, we should expand it */
679 free_jcr(mjcr); /* done using JCR */
682 case STREAM_SPARSE_GZIP_DATA:
683 mjcr->JobBytes += rec->data_len - sizeof(uint64_t); /* No correct, we should expand it */
684 free_jcr(mjcr); /* done using JCR */
687 /* Win32 GZIP stream */
688 case STREAM_WIN32_GZIP_DATA:
689 mjcr->JobBytes += rec->data_len;
690 free_jcr(mjcr); /* done using JCR */
693 case STREAM_MD5_SIGNATURE:
695 bin_to_base64(MD5buf, (char *)rec->data, 16); /* encode 16 bytes */
697 Pmsg1(000, _("Got MD5 record: %s\n"), MD5buf);
699 update_SIG_record(db, MD5buf, rec, MD5_SIG);
702 case STREAM_SHA1_SIGNATURE:
704 bin_to_base64(SIGbuf, (char *)rec->data, 20); /* encode 20 bytes */
706 Pmsg1(000, _("Got SHA1 record: %s\n"), SIGbuf);
708 update_SIG_record(db, SIGbuf, rec, SHA1_SIG);
712 case STREAM_PROGRAM_NAMES:
714 Pmsg1(000, _("Got Prog Names Stream: %s\n"), rec->data);
718 case STREAM_PROGRAM_DATA:
720 Pmsg0(000, _("Got Prog Data Stream record.\n"));
724 Pmsg2(0, _("Unknown stream type!!! stream=%d data=%s\n"), rec->Stream, rec->data);
731 * Free the Job Control Record if no one is still using it.
732 * Called from main free_jcr() routine in src/lib/jcr.c so
733 * that we can do our Director specific cleanup of the jcr.
735 static void bscan_free_jcr(JCR *jcr)
737 Dmsg0(200, "Start bscan free_jcr\n");
739 if (jcr->file_bsock) {
740 Dmsg0(200, "Close File bsock\n");
741 bnet_close(jcr->file_bsock);
743 if (jcr->store_bsock) {
744 Dmsg0(200, "Close Store bsock\n");
745 bnet_close(jcr->store_bsock);
747 if (jcr->RestoreBootstrap) {
748 free(jcr->RestoreBootstrap);
754 Dmsg0(200, "End bscan free_jcr\n");
758 * We got a File Attributes record on the tape. Now, lookup the Job
759 * record, and then create the attributes record.
761 static int create_file_attributes_record(B_DB *db, JCR *mjcr,
762 char *fname, char *lname, int type,
763 char *ap, DEV_RECORD *rec)
765 DCR *dcr = mjcr->dcr;
768 ar.ClientId = mjcr->ClientId;
769 ar.JobId = mjcr->JobId;
770 ar.Stream = rec->Stream;
771 ar.FileIndex = rec->FileIndex;
773 if (dcr->VolFirstIndex == 0) {
774 dcr->VolFirstIndex = rec->FileIndex;
776 dcr->FileIndex = rec->FileIndex;
783 if (!db_create_file_attributes_record(bjcr, db, &ar)) {
784 Pmsg1(0, _("Could not create File Attributes record. ERR=%s\n"), db_strerror(db));
787 mjcr->FileId = ar.FileId;
790 Pmsg1(000, _("Created File record: %s\n"), fname);
796 * For each Volume we see, we create a Medium record
798 static int create_media_record(B_DB *db, MEDIA_DBR *mr, VOLUME_LABEL *vl)
803 /* We mark Vols as Archive to keep them from being re-written */
804 bstrncpy(mr->VolStatus, "Archive", sizeof(mr->VolStatus));
805 mr->VolRetention = 365 * 3600 * 24; /* 1 year */
806 if (vl->VerNum >= 11) {
807 mr->FirstWritten = btime_to_utime(vl->write_btime);
808 mr->LabelDate = btime_to_utime(vl->label_btime);
810 /* DEPRECATED DO NOT USE */
811 dt.julian_day_number = vl->write_date;
812 dt.julian_day_fraction = vl->write_time;
814 mr->FirstWritten = mktime(&tm);
815 dt.julian_day_number = vl->label_date;
816 dt.julian_day_fraction = vl->label_time;
818 mr->LabelDate = mktime(&tm);
820 lasttime = mr->LabelDate;
826 if (!db_create_media_record(bjcr, db, mr)) {
827 Pmsg1(0, _("Could not create media record. ERR=%s\n"), db_strerror(db));
830 if (!db_update_media_record(bjcr, db, mr)) {
831 Pmsg1(0, _("Could not update media record. ERR=%s\n"), db_strerror(db));
835 Pmsg1(000, _("Created Media record for Volume: %s\n"), mr->VolumeName);
842 * Called at end of media to update it
844 static bool update_media_record(B_DB *db, MEDIA_DBR *mr)
846 if (!update_db && !update_vol_info) {
850 mr->LastWritten = lasttime;
851 if (!db_update_media_record(bjcr, db, mr)) {
852 Pmsg1(0, _("Could not update media record. ERR=%s\n"), db_strerror(db));
856 Pmsg1(000, _("Updated Media record at end of Volume: %s\n"), mr->VolumeName);
863 static int create_pool_record(B_DB *db, POOL_DBR *pr)
867 pr->VolRetention = 355 * 3600 * 24; /* 1 year */
872 if (!db_create_pool_record(bjcr, db, pr)) {
873 Pmsg1(0, _("Could not create pool record. ERR=%s\n"), db_strerror(db));
877 Pmsg1(000, _("Created Pool record for Pool: %s\n"), pr->Name);
885 * Called from SOS to create a client for the current Job
887 static int create_client_record(B_DB *db, CLIENT_DBR *cr)
892 if (!db_create_client_record(bjcr, db, cr)) {
893 Pmsg1(0, _("Could not create Client record. ERR=%s\n"), db_strerror(db));
897 Pmsg1(000, _("Created Client record for Client: %s\n"), cr->Name);
902 static int create_fileset_record(B_DB *db, FILESET_DBR *fsr)
908 if (fsr->MD5[0] == 0) {
909 fsr->MD5[0] = ' '; /* Equivalent to nothing */
912 if (db_get_fileset_record(bjcr, db, fsr)) {
914 Pmsg1(000, _("Fileset \"%s\" already exists.\n"), fsr->FileSet);
917 if (!db_create_fileset_record(bjcr, db, fsr)) {
918 Pmsg2(0, _("Could not create FileSet record \"%s\". ERR=%s\n"),
919 fsr->FileSet, db_strerror(db));
923 Pmsg1(000, _("Created FileSet record \"%s\"\n"), fsr->FileSet);
930 * Simulate the two calls on the database to create
931 * the Job record and to update it when the Job actually
934 static JCR *create_job_record(B_DB *db, JOB_DBR *jr, SESSION_LABEL *label,
941 jr->JobId = label->JobId;
942 jr->JobType = label->JobType;
943 jr->JobLevel = label->JobLevel;
944 jr->JobStatus = JS_Created;
945 bstrncpy(jr->Name, label->JobName, sizeof(jr->Name));
946 bstrncpy(jr->Job, label->Job, sizeof(jr->Job));
947 if (label->VerNum >= 11) {
948 jr->SchedTime = btime_to_unix(label->write_btime);
950 dt.julian_day_number = label->write_date;
951 dt.julian_day_fraction = label->write_time;
953 jr->SchedTime = mktime(&tm);
956 jr->StartTime = jr->SchedTime;
957 jr->JobTDate = (utime_t)jr->SchedTime;
958 jr->VolSessionId = rec->VolSessionId;
959 jr->VolSessionTime = rec->VolSessionTime;
961 /* Now create a JCR as if starting the Job */
962 mjcr = create_jcr(jr, rec, label->JobId);
968 /* This creates the bare essentials */
969 if (!db_create_job_record(bjcr, db, jr)) {
970 Pmsg1(0, _("Could not create JobId record. ERR=%s\n"), db_strerror(db));
974 /* This adds the client, StartTime, JobTDate, ... */
975 if (!db_update_job_start_record(bjcr, db, jr)) {
976 Pmsg1(0, _("Could not update job start record. ERR=%s\n"), db_strerror(db));
979 Pmsg2(000, _("Created new JobId=%u record for original JobId=%u\n"), jr->JobId,
981 mjcr->JobId = jr->JobId; /* set new JobId */
986 * Simulate the database call that updates the Job
987 * at Job termination time.
989 static int update_job_record(B_DB *db, JOB_DBR *jr, SESSION_LABEL *elabel,
996 mjcr = get_jcr_by_session(rec->VolSessionId, rec->VolSessionTime);
998 Pmsg2(000, _("Could not find SessId=%d SessTime=%d for EOS record.\n"),
999 rec->VolSessionId, rec->VolSessionTime);
1002 if (elabel->VerNum >= 11) {
1003 jr->EndTime = btime_to_unix(elabel->write_btime);
1005 dt.julian_day_number = elabel->write_date;
1006 dt.julian_day_fraction = elabel->write_time;
1007 tm_decode(&dt, &tm);
1008 jr->EndTime = mktime(&tm);
1010 lasttime = jr->EndTime;
1011 mjcr->end_time = jr->EndTime;
1013 jr->JobId = mjcr->JobId;
1014 jr->JobStatus = elabel->JobStatus;
1015 mjcr->JobStatus = elabel->JobStatus;
1016 jr->JobFiles = elabel->JobFiles;
1017 jr->JobBytes = elabel->JobBytes;
1018 jr->VolSessionId = rec->VolSessionId;
1019 jr->VolSessionTime = rec->VolSessionTime;
1020 jr->JobTDate = (utime_t)mjcr->start_time;
1021 jr->ClientId = mjcr->ClientId;
1028 if (!db_update_job_end_record(bjcr, db, jr)) {
1029 Pmsg2(0, _("Could not update JobId=%u record. ERR=%s\n"), jr->JobId, db_strerror(db));
1034 Pmsg2(000, _("Updated Job termination record for JobId=%u TermStat=%c\n"), jr->JobId,
1038 const char *term_msg;
1039 static char term_code[70];
1040 char sdt[50], edt[50];
1041 char ec1[30], ec2[30], ec3[30];
1043 switch (mjcr->JobStatus) {
1045 term_msg = _("Backup OK");
1048 case JS_ErrorTerminated:
1049 term_msg = _("*** Backup Error ***");
1052 term_msg = _("Backup Canceled");
1055 term_msg = term_code;
1056 sprintf(term_code, _("Job Termination code: %d"), mjcr->JobStatus);
1059 bstrftime(sdt, sizeof(sdt), mjcr->start_time);
1060 bstrftime(edt, sizeof(edt), mjcr->end_time);
1061 Pmsg14(000, _("%s\n"
1065 "Backup Level: %s\n"
1069 "Files Written: %s\n"
1070 "Bytes Written: %s\n"
1071 "Volume Session Id: %d\n"
1072 "Volume Session Time: %d\n"
1073 "Last Volume Bytes: %s\n"
1074 "Termination: %s\n\n"),
1079 job_level_to_str(mjcr->JobLevel),
1083 edit_uint64_with_commas(mjcr->JobFiles, ec1),
1084 edit_uint64_with_commas(mjcr->JobBytes, ec2),
1086 mjcr->VolSessionTime,
1087 edit_uint64_with_commas(mr.VolBytes, ec3),
1094 static int create_jobmedia_record(B_DB *db, JCR *mjcr)
1097 DCR *dcr = mjcr->dcr;
1099 if (dev->is_tape()) {
1100 dcr->EndBlock = dev->EndBlock;
1101 dcr->EndFile = dev->EndFile;
1104 dcr->EndBlock = (uint32_t)dev->file_addr;
1105 dcr->EndFile = (uint32_t)(dev->file_addr >> 32);
1109 memset(&jmr, 0, sizeof(jmr));
1110 jmr.JobId = mjcr->JobId;
1111 jmr.MediaId = mr.MediaId;
1112 jmr.FirstIndex = dcr->VolFirstIndex;
1113 jmr.LastIndex = dcr->FileIndex;
1114 jmr.StartFile = dcr->StartFile;
1115 jmr.EndFile = dcr->EndFile;
1116 jmr.StartBlock = dcr->StartBlock;
1117 jmr.EndBlock = dcr->EndBlock;
1124 if (!db_create_jobmedia_record(bjcr, db, &jmr)) {
1125 Pmsg1(0, _("Could not create JobMedia record. ERR=%s\n"), db_strerror(db));
1129 Pmsg2(000, _("Created JobMedia record JobId %d, MediaId %d\n"),
1130 jmr.JobId, jmr.MediaId);
1136 * Simulate the database call that updates the MD5/SHA1 record
1138 static int update_SIG_record(B_DB *db, char *SIGbuf, DEV_RECORD *rec, int type)
1142 mjcr = get_jcr_by_session(rec->VolSessionId, rec->VolSessionTime);
1144 if (mr.VolJobs > 0) {
1145 Pmsg2(000, _("Could not find SessId=%d SessTime=%d for MD5/SHA1 record.\n"),
1146 rec->VolSessionId, rec->VolSessionTime);
1153 if (!update_db || mjcr->FileId == 0) {
1158 if (!db_add_SIG_to_file_record(bjcr, db, mjcr->FileId, SIGbuf, type)) {
1159 Pmsg1(0, _("Could not add MD5/SHA1 to File record. ERR=%s\n"), db_strerror(db));
1164 Pmsg0(000, _("Updated MD5/SHA1 record\n"));
1172 * Create a JCR as if we are really starting the job
1174 static JCR *create_jcr(JOB_DBR *jr, DEV_RECORD *rec, uint32_t JobId)
1178 * Transfer as much as possible to the Job JCR. Most important is
1179 * the JobId and the ClientId.
1181 jobjcr = new_jcr(sizeof(JCR), bscan_free_jcr);
1182 jobjcr->JobType = jr->JobType;
1183 jobjcr->JobLevel = jr->JobLevel;
1184 jobjcr->JobStatus = jr->JobStatus;
1185 bstrncpy(jobjcr->Job, jr->Job, sizeof(jobjcr->Job));
1186 jobjcr->JobId = JobId; /* this is JobId on tape */
1187 jobjcr->sched_time = jr->SchedTime;
1188 jobjcr->start_time = jr->StartTime;
1189 jobjcr->VolSessionId = rec->VolSessionId;
1190 jobjcr->VolSessionTime = rec->VolSessionTime;
1191 jobjcr->ClientId = jr->ClientId;
1192 new_dcr(jobjcr, dev);
1196 /* Dummies to replace askdir.c */
1197 bool dir_get_volume_info(DCR *dcr, enum get_vol_info_rw writing) { return 1;}
1198 bool dir_find_next_appendable_volume(DCR *dcr) { return 1;}
1199 bool dir_update_volume_info(DCR *dcr, bool relabel) { return 1; }
1200 bool dir_create_jobmedia_record(DCR *dcr) { return 1; }
1201 bool dir_ask_sysop_to_create_appendable_volume(DCR *dcr) { return 1; }
1202 bool dir_update_file_attributes(DCR *dcr, DEV_RECORD *rec) { return 1;}
1203 bool dir_send_job_status(JCR *jcr) {return 1;}
1204 int generate_job_event(JCR *jcr, const char *event) { return 1; }
1205 VOLRES *new_volume(DCR *dcr, const char *VolumeName) { return NULL; }
1206 bool free_volume(DEVICE *dev) { return true; }
1207 void free_unused_volume(DCR *dcr) { }
1209 bool dir_ask_sysop_to_mount_volume(DCR *dcr)
1211 DEVICE *dev = dcr->dev;
1212 Dmsg0(20, "Enter dir_ask_sysop_to_mount_volume\n");
1213 /* Close device so user can use autochanger if desired */
1214 if (dev_cap(dev, CAP_OFFLINEUNMOUNT)) {
1217 force_close_device(dev);
1218 fprintf(stderr, "Mount Volume \"%s\" on device %s and press return when ready: ",
1219 dcr->VolumeName, dev->print_name());