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: %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;
376 if (rec->data_len > 0) {
377 mr.VolBytes += rec->data_len + WRITE_RECHDR_LENGTH; /* Accumulate Volume bytes */
379 int pct = (mr.VolBytes * 100) / currentVolumeSize;
380 if (pct != last_pct) {
381 fprintf(stdout, _("done: %d%%\n"), pct);
389 Pmsg5(000, _("Record: SessId=%u SessTim=%u FileIndex=%d Stream=%d len=%u\n"),
390 rec->VolSessionId, rec->VolSessionTime, rec->FileIndex,
391 rec->Stream, rec->data_len);
394 * Check for Start or End of Session Record
397 if (rec->FileIndex < 0) {
398 bool save_update_db = update_db;
401 dump_label_record(dev, rec, 1);
403 switch (rec->FileIndex) {
405 Pmsg0(000, _("Volume is prelabeled. This tape cannot be scanned.\n"));
410 unser_volume_label(dev, rec);
411 /* Check Pool info */
412 bstrncpy(pr.Name, dev->VolHdr.PoolName, sizeof(pr.Name));
413 bstrncpy(pr.PoolType, dev->VolHdr.PoolType, sizeof(pr.PoolType));
415 if (db_get_pool_record(bjcr, db, &pr)) {
417 Pmsg1(000, _("Pool record for %s found in DB.\n"), pr.Name);
421 Pmsg1(000, _("VOL_LABEL: Pool record not found for Pool: %s\n"),
424 create_pool_record(db, &pr);
426 if (strcmp(pr.PoolType, dev->VolHdr.PoolType) != 0) {
427 Pmsg2(000, _("VOL_LABEL: PoolType mismatch. DB=%s Vol=%s\n"),
428 pr.PoolType, dev->VolHdr.PoolType);
430 } else if (verbose) {
431 Pmsg1(000, _("Pool type \"%s\" is OK.\n"), pr.PoolType);
434 /* Check Media Info */
435 memset(&mr, 0, sizeof(mr));
436 bstrncpy(mr.VolumeName, dev->VolHdr.VolumeName, sizeof(mr.VolumeName));
437 mr.PoolId = pr.PoolId;
439 if (db_get_media_record(bjcr, db, &mr)) {
441 Pmsg1(000, _("Media record for %s found in DB.\n"), mr.VolumeName);
443 /* Clear out some volume statistics that will be updated */
444 mr.VolJobs = mr.VolFiles = mr.VolBlocks = 0;
445 mr.VolBytes = rec->data_len + 20;
448 Pmsg1(000, _("VOL_LABEL: Media record not found for Volume: %s\n"),
451 bstrncpy(mr.MediaType, dev->VolHdr.MediaType, sizeof(mr.MediaType));
452 create_media_record(db, &mr, &dev->VolHdr);
454 if (strcmp(mr.MediaType, dev->VolHdr.MediaType) != 0) {
455 Pmsg2(000, _("VOL_LABEL: MediaType mismatch. DB=%s Vol=%s\n"),
456 mr.MediaType, dev->VolHdr.MediaType);
457 return true; /* ignore error */
458 } else if (verbose) {
459 Pmsg1(000, _("Media type \"%s\" is OK.\n"), mr.MediaType);
461 /* Reset some DCR variables */
462 foreach_dlist(dcr, dev->attached_dcrs) {
463 dcr->VolFirstIndex = dcr->FileIndex = 0;
464 dcr->StartBlock = dcr->EndBlock = 0;
465 dcr->StartFile = dcr->EndFile = 0;
468 Pmsg1(000, _("VOL_LABEL: OK for Volume: %s\n"), mr.VolumeName);
474 if (ignored_msgs > 0) {
475 Pmsg1(000, _("%d \"errors\" ignored before first Start of Session record.\n"),
479 unser_session_label(&label, rec);
480 memset(&jr, 0, sizeof(jr));
481 bstrncpy(jr.Job, label.Job, sizeof(jr.Job));
482 if (db_get_job_record(bjcr, db, &jr)) {
483 /* Job record already exists in DB */
484 update_db = false; /* don't change db in create_job_record */
486 Pmsg1(000, _("SOS_LABEL: Found Job record for JobId: %d\n"), jr.JobId);
489 /* Must create a Job record in DB */
491 Pmsg1(000, _("SOS_LABEL: Job record not found for JobId: %d\n"),
495 /* Create Client record if not already there */
496 bstrncpy(cr.Name, label.ClientName, sizeof(cr.Name));
497 create_client_record(db, &cr);
498 jr.ClientId = cr.ClientId;
500 /* process label, if Job record exists don't update db */
501 mjcr = create_job_record(db, &jr, &label, rec);
502 dcr = mjcr->read_dcr;
503 update_db = save_update_db;
505 jr.PoolId = pr.PoolId;
507 /* Set start positions into JCR */
508 if (dev->is_tape()) {
510 * Note, we have already advanced past current block,
511 * so the correct number is block_num - 1
513 dcr->StartBlock = dev->block_num - 1;
514 dcr->StartFile = dev->file;
516 dcr->StartBlock = (uint32_t)dev->file_addr;
517 dcr->StartFile = (uint32_t)(dev->file_addr >> 32);
520 mjcr->start_time = jr.StartTime;
521 mjcr->JobLevel = jr.JobLevel;
523 mjcr->client_name = get_pool_memory(PM_FNAME);
524 pm_strcpy(mjcr->client_name, label.ClientName);
525 mjcr->fileset_name = get_pool_memory(PM_FNAME);
526 pm_strcpy(mjcr->fileset_name, label.FileSetName);
527 bstrncpy(dcr->pool_type, label.PoolType, sizeof(dcr->pool_type));
528 bstrncpy(dcr->pool_name, label.PoolName, sizeof(dcr->pool_name));
530 if (rec->VolSessionId != jr.VolSessionId) {
531 Pmsg3(000, _("SOS_LABEL: VolSessId mismatch for JobId=%u. DB=%d Vol=%d\n"),
533 jr.VolSessionId, rec->VolSessionId);
534 return true; /* ignore error */
536 if (rec->VolSessionTime != jr.VolSessionTime) {
537 Pmsg3(000, _("SOS_LABEL: VolSessTime mismatch for JobId=%u. DB=%d Vol=%d\n"),
539 jr.VolSessionTime, rec->VolSessionTime);
540 return true; /* ignore error */
542 if (jr.PoolId != pr.PoolId) {
543 Pmsg3(000, _("SOS_LABEL: PoolId mismatch for JobId=%u. DB=%d Vol=%d\n"),
545 jr.PoolId, pr.PoolId);
546 return true; /* ignore error */
551 unser_session_label(&elabel, rec);
553 /* Create FileSet record */
554 bstrncpy(fsr.FileSet, label.FileSetName, sizeof(fsr.FileSet));
555 bstrncpy(fsr.MD5, label.FileSetMD5, sizeof(fsr.MD5));
556 create_fileset_record(db, &fsr);
557 jr.FileSetId = fsr.FileSetId;
559 mjcr = get_jcr_by_session(rec->VolSessionId, rec->VolSessionTime);
561 Pmsg2(000, _("Could not find SessId=%d SessTime=%d for EOS record.\n"),
562 rec->VolSessionId, rec->VolSessionTime);
566 /* Do the final update to the Job record */
567 update_job_record(db, &jr, &elabel, rec);
569 mjcr->end_time = jr.EndTime;
570 mjcr->JobStatus = JS_Terminated;
572 /* Create JobMedia record */
573 create_jobmedia_record(db, mjcr);
574 dev->attached_dcrs->remove(mjcr->read_dcr);
582 case EOT_LABEL: /* end of all tapes */
584 * Wiffle through all jobs still open and close
589 foreach_dlist(mdcr, dev->attached_dcrs) {
590 JCR *mjcr = mdcr->jcr;
591 if (!mjcr || mjcr->JobId == 0) {
594 jr.JobId = mjcr->JobId;
595 /* Mark Job as Error Terimined */
596 jr.JobStatus = JS_ErrorTerminated;
597 jr.JobFiles = mjcr->JobFiles;
598 jr.JobBytes = mjcr->JobBytes;
599 jr.VolSessionId = mjcr->VolSessionId;
600 jr.VolSessionTime = mjcr->VolSessionTime;
601 jr.JobTDate = (utime_t)mjcr->start_time;
602 jr.ClientId = mjcr->ClientId;
603 if (!db_update_job_end_record(bjcr, db, &jr)) {
604 Pmsg1(0, _("Could not update job record. ERR=%s\n"), db_strerror(db));
606 mjcr->read_dcr = NULL;
610 mr.VolFiles = rec->File;
611 mr.VolBlocks = rec->Block;
612 mr.VolBytes += mr.VolBlocks * WRITE_BLKHDR_LENGTH; /* approx. */
614 update_media_record(db, &mr);
615 Pmsg3(0, _("End of all Volumes. VolFiles=%u VolBlocks=%u VolBytes=%s\n"), mr.VolFiles,
616 mr.VolBlocks, edit_uint64_with_commas(mr.VolBytes, ec1));
624 mjcr = get_jcr_by_session(rec->VolSessionId, rec->VolSessionTime);
626 if (mr.VolJobs > 0) {
627 Pmsg2(000, _("Could not find Job for SessId=%d SessTime=%d record.\n"),
628 rec->VolSessionId, rec->VolSessionTime);
634 dcr = mjcr->read_dcr;
635 if (dcr->VolFirstIndex == 0) {
636 dcr->VolFirstIndex = block->FirstIndex;
639 /* File Attributes stream */
640 switch (rec->Stream) {
641 case STREAM_UNIX_ATTRIBUTES:
642 case STREAM_UNIX_ATTRIBUTES_EX:
644 if (!unpack_attributes_record(bjcr, rec->Stream, rec->data, attr)) {
645 Emsg0(M_ERROR_TERM, 0, _("Cannot continue.\n"));
648 if (attr->file_index != rec->FileIndex) {
649 Emsg2(M_ERROR_TERM, 0, _("Record header file index %ld not equal record index %ld\n"),
650 rec->FileIndex, attr->file_index);
654 decode_stat(attr->attr, &attr->statp, &attr->LinkFI);
655 build_attr_output_fnames(bjcr, attr);
656 print_ls_output(bjcr, attr);
658 fr.JobId = mjcr->JobId;
661 if (verbose && (num_files & 0x7FFF) == 0) {
662 char ed1[30], ed2[30], ed3[30], ed4[30];
663 Pmsg4(000, _("%s file records. At file:blk=%s:%s bytes=%s\n"),
664 edit_uint64_with_commas(num_files, ed1),
665 edit_uint64_with_commas(rec->File, ed2),
666 edit_uint64_with_commas(rec->Block, ed3),
667 edit_uint64_with_commas(mr.VolBytes, ed4));
669 create_file_attributes_record(db, mjcr, attr->fname, attr->lname,
670 attr->type, attr->attr, rec);
675 case STREAM_WIN32_DATA:
676 case STREAM_FILE_DATA:
677 case STREAM_SPARSE_DATA:
678 mjcr->JobBytes += rec->data_len;
679 if (rec->Stream == STREAM_SPARSE_DATA) {
680 mjcr->JobBytes -= sizeof(uint64_t);
683 free_jcr(mjcr); /* done using JCR */
686 case STREAM_GZIP_DATA:
687 mjcr->JobBytes += rec->data_len; /* No correct, we should expand it */
688 free_jcr(mjcr); /* done using JCR */
691 case STREAM_SPARSE_GZIP_DATA:
692 mjcr->JobBytes += rec->data_len - sizeof(uint64_t); /* No correct, we should expand it */
693 free_jcr(mjcr); /* done using JCR */
696 /* Win32 GZIP stream */
697 case STREAM_WIN32_GZIP_DATA:
698 mjcr->JobBytes += rec->data_len;
699 free_jcr(mjcr); /* done using JCR */
702 case STREAM_MD5_SIGNATURE:
704 bin_to_base64(MD5buf, (char *)rec->data, 16); /* encode 16 bytes */
706 Pmsg1(000, _("Got MD5 record: %s\n"), MD5buf);
708 update_SIG_record(db, MD5buf, rec, MD5_SIG);
711 case STREAM_SHA1_SIGNATURE:
713 bin_to_base64(SIGbuf, (char *)rec->data, 20); /* encode 20 bytes */
715 Pmsg1(000, _("Got SHA1 record: %s\n"), SIGbuf);
717 update_SIG_record(db, SIGbuf, rec, SHA1_SIG);
721 case STREAM_PROGRAM_NAMES:
723 Pmsg1(000, _("Got Prog Names Stream: %s\n"), rec->data);
727 case STREAM_PROGRAM_DATA:
729 Pmsg0(000, _("Got Prog Data Stream record.\n"));
733 Pmsg2(0, _("Unknown stream type!!! stream=%d data=%s\n"), rec->Stream, rec->data);
740 * Free the Job Control Record if no one is still using it.
741 * Called from main free_jcr() routine in src/lib/jcr.c so
742 * that we can do our Director specific cleanup of the jcr.
744 static void bscan_free_jcr(JCR *jcr)
746 Dmsg0(200, "Start bscan free_jcr\n");
748 if (jcr->file_bsock) {
749 Dmsg0(200, "Close File bsock\n");
750 bnet_close(jcr->file_bsock);
752 if (jcr->store_bsock) {
753 Dmsg0(200, "Close Store bsock\n");
754 bnet_close(jcr->store_bsock);
756 if (jcr->RestoreBootstrap) {
757 free(jcr->RestoreBootstrap);
764 free_dcr(jcr->read_dcr);
765 jcr->read_dcr = NULL;
767 Dmsg0(200, "End bscan free_jcr\n");
771 * We got a File Attributes record on the tape. Now, lookup the Job
772 * record, and then create the attributes record.
774 static int create_file_attributes_record(B_DB *db, JCR *mjcr,
775 char *fname, char *lname, int type,
776 char *ap, DEV_RECORD *rec)
778 DCR *dcr = mjcr->read_dcr;
781 ar.ClientId = mjcr->ClientId;
782 ar.JobId = mjcr->JobId;
783 ar.Stream = rec->Stream;
784 ar.FileIndex = rec->FileIndex;
786 if (dcr->VolFirstIndex == 0) {
787 dcr->VolFirstIndex = rec->FileIndex;
789 dcr->FileIndex = rec->FileIndex;
796 if (!db_create_file_attributes_record(bjcr, db, &ar)) {
797 Pmsg1(0, _("Could not create File Attributes record. ERR=%s\n"), db_strerror(db));
800 mjcr->FileId = ar.FileId;
803 Pmsg1(000, _("Created File record: %s\n"), fname);
809 * For each Volume we see, we create a Medium record
811 static int create_media_record(B_DB *db, MEDIA_DBR *mr, VOLUME_LABEL *vl)
816 /* We mark Vols as Archive to keep them from being re-written */
817 bstrncpy(mr->VolStatus, "Archive", sizeof(mr->VolStatus));
818 mr->VolRetention = 365 * 3600 * 24; /* 1 year */
819 if (vl->VerNum >= 11) {
820 mr->FirstWritten = btime_to_utime(vl->write_btime);
821 mr->LabelDate = btime_to_utime(vl->label_btime);
823 /* DEPRECATED DO NOT USE */
824 dt.julian_day_number = vl->write_date;
825 dt.julian_day_fraction = vl->write_time;
827 mr->FirstWritten = mktime(&tm);
828 dt.julian_day_number = vl->label_date;
829 dt.julian_day_fraction = vl->label_time;
831 mr->LabelDate = mktime(&tm);
833 lasttime = mr->LabelDate;
839 if (!db_create_media_record(bjcr, db, mr)) {
840 Pmsg1(0, _("Could not create media record. ERR=%s\n"), db_strerror(db));
843 if (!db_update_media_record(bjcr, db, mr)) {
844 Pmsg1(0, _("Could not update media record. ERR=%s\n"), db_strerror(db));
848 Pmsg1(000, _("Created Media record for Volume: %s\n"), mr->VolumeName);
855 * Called at end of media to update it
857 static bool update_media_record(B_DB *db, MEDIA_DBR *mr)
859 if (!update_db && !update_vol_info) {
863 mr->LastWritten = lasttime;
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, _("Updated Media record at end of Volume: %s\n"), mr->VolumeName);
876 static int create_pool_record(B_DB *db, POOL_DBR *pr)
880 pr->VolRetention = 355 * 3600 * 24; /* 1 year */
885 if (!db_create_pool_record(bjcr, db, pr)) {
886 Pmsg1(0, _("Could not create pool record. ERR=%s\n"), db_strerror(db));
890 Pmsg1(000, _("Created Pool record for Pool: %s\n"), pr->Name);
898 * Called from SOS to create a client for the current Job
900 static int create_client_record(B_DB *db, CLIENT_DBR *cr)
905 if (!db_create_client_record(bjcr, db, cr)) {
906 Pmsg1(0, _("Could not create Client record. ERR=%s\n"), db_strerror(db));
910 Pmsg1(000, _("Created Client record for Client: %s\n"), cr->Name);
915 static int create_fileset_record(B_DB *db, FILESET_DBR *fsr)
921 if (fsr->MD5[0] == 0) {
922 fsr->MD5[0] = ' '; /* Equivalent to nothing */
925 if (db_get_fileset_record(bjcr, db, fsr)) {
927 Pmsg1(000, _("Fileset \"%s\" already exists.\n"), fsr->FileSet);
930 if (!db_create_fileset_record(bjcr, db, fsr)) {
931 Pmsg2(0, _("Could not create FileSet record \"%s\". ERR=%s\n"),
932 fsr->FileSet, db_strerror(db));
936 Pmsg1(000, _("Created FileSet record \"%s\"\n"), fsr->FileSet);
943 * Simulate the two calls on the database to create
944 * the Job record and to update it when the Job actually
947 static JCR *create_job_record(B_DB *db, JOB_DBR *jr, SESSION_LABEL *label,
954 jr->JobId = label->JobId;
955 jr->JobType = label->JobType;
956 jr->JobLevel = label->JobLevel;
957 jr->JobStatus = JS_Created;
958 bstrncpy(jr->Name, label->JobName, sizeof(jr->Name));
959 bstrncpy(jr->Job, label->Job, sizeof(jr->Job));
960 if (label->VerNum >= 11) {
961 jr->SchedTime = btime_to_unix(label->write_btime);
963 dt.julian_day_number = label->write_date;
964 dt.julian_day_fraction = label->write_time;
966 jr->SchedTime = mktime(&tm);
969 jr->StartTime = jr->SchedTime;
970 jr->JobTDate = (utime_t)jr->SchedTime;
971 jr->VolSessionId = rec->VolSessionId;
972 jr->VolSessionTime = rec->VolSessionTime;
974 /* Now create a JCR as if starting the Job */
975 mjcr = create_jcr(jr, rec, label->JobId);
981 /* This creates the bare essentials */
982 if (!db_create_job_record(bjcr, db, jr)) {
983 Pmsg1(0, _("Could not create JobId record. ERR=%s\n"), db_strerror(db));
987 /* This adds the client, StartTime, JobTDate, ... */
988 if (!db_update_job_start_record(bjcr, db, jr)) {
989 Pmsg1(0, _("Could not update job start record. ERR=%s\n"), db_strerror(db));
992 Pmsg2(000, _("Created new JobId=%u record for original JobId=%u\n"), jr->JobId,
994 mjcr->JobId = jr->JobId; /* set new JobId */
999 * Simulate the database call that updates the Job
1000 * at Job termination time.
1002 static int update_job_record(B_DB *db, JOB_DBR *jr, SESSION_LABEL *elabel,
1005 struct date_time dt;
1009 mjcr = get_jcr_by_session(rec->VolSessionId, rec->VolSessionTime);
1011 Pmsg2(000, _("Could not find SessId=%d SessTime=%d for EOS record.\n"),
1012 rec->VolSessionId, rec->VolSessionTime);
1015 if (elabel->VerNum >= 11) {
1016 jr->EndTime = btime_to_unix(elabel->write_btime);
1018 dt.julian_day_number = elabel->write_date;
1019 dt.julian_day_fraction = elabel->write_time;
1020 tm_decode(&dt, &tm);
1021 jr->EndTime = mktime(&tm);
1023 lasttime = jr->EndTime;
1024 mjcr->end_time = jr->EndTime;
1026 jr->JobId = mjcr->JobId;
1027 jr->JobStatus = elabel->JobStatus;
1028 mjcr->JobStatus = elabel->JobStatus;
1029 jr->JobFiles = elabel->JobFiles;
1030 jr->JobBytes = elabel->JobBytes;
1031 jr->VolSessionId = rec->VolSessionId;
1032 jr->VolSessionTime = rec->VolSessionTime;
1033 jr->JobTDate = (utime_t)mjcr->start_time;
1034 jr->ClientId = mjcr->ClientId;
1041 if (!db_update_job_end_record(bjcr, db, jr)) {
1042 Pmsg2(0, _("Could not update JobId=%u record. ERR=%s\n"), jr->JobId, db_strerror(db));
1047 Pmsg2(000, _("Updated Job termination record for JobId=%u TermStat=%c\n"), jr->JobId,
1051 const char *term_msg;
1052 static char term_code[70];
1053 char sdt[50], edt[50];
1054 char ec1[30], ec2[30], ec3[30];
1056 switch (mjcr->JobStatus) {
1058 term_msg = _("Backup OK");
1061 case JS_ErrorTerminated:
1062 term_msg = _("*** Backup Error ***");
1065 term_msg = _("Backup Canceled");
1068 term_msg = term_code;
1069 sprintf(term_code, _("Job Termination code: %d"), mjcr->JobStatus);
1072 bstrftime(sdt, sizeof(sdt), mjcr->start_time);
1073 bstrftime(edt, sizeof(edt), mjcr->end_time);
1074 Pmsg14(000, _("%s\n"
1078 "Backup Level: %s\n"
1082 "Files Written: %s\n"
1083 "Bytes Written: %s\n"
1084 "Volume Session Id: %d\n"
1085 "Volume Session Time: %d\n"
1086 "Last Volume Bytes: %s\n"
1087 "Termination: %s\n\n"),
1092 job_level_to_str(mjcr->JobLevel),
1096 edit_uint64_with_commas(mjcr->JobFiles, ec1),
1097 edit_uint64_with_commas(mjcr->JobBytes, ec2),
1099 mjcr->VolSessionTime,
1100 edit_uint64_with_commas(mr.VolBytes, ec3),
1107 static int create_jobmedia_record(B_DB *db, JCR *mjcr)
1110 DCR *dcr = mjcr->read_dcr;
1112 if (dev->is_tape()) {
1113 dcr->EndBlock = dev->EndBlock;
1114 dcr->EndFile = dev->EndFile;
1117 dcr->EndBlock = (uint32_t)dev->file_addr;
1118 dcr->EndFile = (uint32_t)(dev->file_addr >> 32);
1122 memset(&jmr, 0, sizeof(jmr));
1123 jmr.JobId = mjcr->JobId;
1124 jmr.MediaId = mr.MediaId;
1125 jmr.FirstIndex = dcr->VolFirstIndex;
1126 jmr.LastIndex = dcr->FileIndex;
1127 jmr.StartFile = dcr->StartFile;
1128 jmr.EndFile = dcr->EndFile;
1129 jmr.StartBlock = dcr->StartBlock;
1130 jmr.EndBlock = dcr->EndBlock;
1137 if (!db_create_jobmedia_record(bjcr, db, &jmr)) {
1138 Pmsg1(0, _("Could not create JobMedia record. ERR=%s\n"), db_strerror(db));
1142 Pmsg2(000, _("Created JobMedia record JobId %d, MediaId %d\n"),
1143 jmr.JobId, jmr.MediaId);
1149 * Simulate the database call that updates the MD5/SHA1 record
1151 static int update_SIG_record(B_DB *db, char *SIGbuf, DEV_RECORD *rec, int type)
1155 mjcr = get_jcr_by_session(rec->VolSessionId, rec->VolSessionTime);
1157 if (mr.VolJobs > 0) {
1158 Pmsg2(000, _("Could not find SessId=%d SessTime=%d for MD5/SHA1 record.\n"),
1159 rec->VolSessionId, rec->VolSessionTime);
1166 if (!update_db || mjcr->FileId == 0) {
1171 if (!db_add_SIG_to_file_record(bjcr, db, mjcr->FileId, SIGbuf, type)) {
1172 Pmsg1(0, _("Could not add MD5/SHA1 to File record. ERR=%s\n"), db_strerror(db));
1177 Pmsg0(000, _("Updated MD5/SHA1 record\n"));
1185 * Create a JCR as if we are really starting the job
1187 static JCR *create_jcr(JOB_DBR *jr, DEV_RECORD *rec, uint32_t JobId)
1191 * Transfer as much as possible to the Job JCR. Most important is
1192 * the JobId and the ClientId.
1194 jobjcr = new_jcr(sizeof(JCR), bscan_free_jcr);
1195 jobjcr->JobType = jr->JobType;
1196 jobjcr->JobLevel = jr->JobLevel;
1197 jobjcr->JobStatus = jr->JobStatus;
1198 bstrncpy(jobjcr->Job, jr->Job, sizeof(jobjcr->Job));
1199 jobjcr->JobId = JobId; /* this is JobId on tape */
1200 jobjcr->sched_time = jr->SchedTime;
1201 jobjcr->start_time = jr->StartTime;
1202 jobjcr->VolSessionId = rec->VolSessionId;
1203 jobjcr->VolSessionTime = rec->VolSessionTime;
1204 jobjcr->ClientId = jr->ClientId;
1205 jobjcr->read_dcr = new_dcr(jobjcr, dev);
1210 /* Dummies to replace askdir.c */
1211 bool dir_get_volume_info(DCR *dcr, enum get_vol_info_rw writing) { return 1;}
1212 bool dir_find_next_appendable_volume(DCR *dcr) { return 1;}
1213 bool dir_update_volume_info(DCR *dcr, bool relabel) { return 1; }
1214 bool dir_create_jobmedia_record(DCR *dcr) { return 1; }
1215 bool dir_ask_sysop_to_create_appendable_volume(DCR *dcr) { return 1; }
1216 bool dir_update_file_attributes(DCR *dcr, DEV_RECORD *rec) { return 1;}
1217 bool dir_send_job_status(JCR *jcr) {return 1;}
1218 int generate_job_event(JCR *jcr, const char *event) { return 1; }
1219 VOLRES *new_volume(DCR *dcr, const char *VolumeName) { return NULL; }
1220 bool free_volume(DEVICE *dev) { return true; }
1221 void free_unused_volume(DCR *dcr) { }
1223 bool dir_ask_sysop_to_mount_volume(DCR *dcr)
1225 DEVICE *dev = dcr->dev;
1226 Dmsg0(20, "Enter dir_ask_sysop_to_mount_volume\n");
1227 /* Close device so user can use autochanger if desired */
1228 if (dev_cap(dev, CAP_OFFLINEUNMOUNT)) {
1231 force_close_device(dev);
1232 fprintf(stderr, _("Mount Volume \"%s\" on device %s and press return when ready: "),
1233 dcr->VolumeName, dev->print_name());