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 mjcr->read_dcr->VolLastIndex = dcr->VolLastIndex;
325 if (!create_jobmedia_record(db, mjcr)) {
326 Pmsg2(000, _("Could not create JobMedia record for Volume=%s Job=%s\n"),
327 dev->VolCatInfo.VolCatName, mjcr->Job);
330 /* Now let common read routine get up next tape. Note,
331 * we call mount_next... with bscan's jcr because that is where we
332 * have the Volume list, but we get attached.
334 bool stat = mount_next_read_volume(dcr);
340 currentVolumeSize = sb.st_size;
341 Pmsg1(000, _("First Volume Size = %sn"),
342 edit_uint64(currentVolumeSize, ed1));
347 static void do_scan()
351 memset(&ar, 0, sizeof(ar));
352 memset(&pr, 0, sizeof(pr));
353 memset(&jr, 0, sizeof(jr));
354 memset(&cr, 0, sizeof(cr));
355 memset(&fsr, 0, sizeof(fsr));
356 memset(&fr, 0, sizeof(fr));
358 /* Detach bscan's jcr as we are not a real Job on the tape */
360 read_records(bjcr->read_dcr, record_cb, bscan_mount_next_read_volume);
366 * Returns: true if OK
369 static bool record_cb(DCR *dcr, DEV_RECORD *rec)
373 DEVICE *dev = dcr->dev;
374 JCR *bjcr = dcr->jcr;
375 DEV_BLOCK *block = dcr->block;
376 char digest[BASE64_SIZE(CRYPTO_DIGEST_MAX_SIZE)];
378 if (rec->data_len > 0) {
379 mr.VolBytes += rec->data_len + WRITE_RECHDR_LENGTH; /* Accumulate Volume bytes */
381 int pct = (mr.VolBytes * 100) / currentVolumeSize;
382 if (pct != last_pct) {
383 fprintf(stdout, _("done: %d%%\n"), pct);
391 Pmsg5(000, _("Record: SessId=%u SessTim=%u FileIndex=%d Stream=%d len=%u\n"),
392 rec->VolSessionId, rec->VolSessionTime, rec->FileIndex,
393 rec->Stream, rec->data_len);
396 * Check for Start or End of Session Record
399 if (rec->FileIndex < 0) {
400 bool save_update_db = update_db;
403 dump_label_record(dev, rec, 1);
405 switch (rec->FileIndex) {
407 Pmsg0(000, _("Volume is prelabeled. This tape cannot be scanned.\n"));
412 unser_volume_label(dev, rec);
413 /* Check Pool info */
414 bstrncpy(pr.Name, dev->VolHdr.PoolName, sizeof(pr.Name));
415 bstrncpy(pr.PoolType, dev->VolHdr.PoolType, sizeof(pr.PoolType));
417 if (db_get_pool_record(bjcr, db, &pr)) {
419 Pmsg1(000, _("Pool record for %s found in DB.\n"), pr.Name);
423 Pmsg1(000, _("VOL_LABEL: Pool record not found for Pool: %s\n"),
426 create_pool_record(db, &pr);
428 if (strcmp(pr.PoolType, dev->VolHdr.PoolType) != 0) {
429 Pmsg2(000, _("VOL_LABEL: PoolType mismatch. DB=%s Vol=%s\n"),
430 pr.PoolType, dev->VolHdr.PoolType);
432 } else if (verbose) {
433 Pmsg1(000, _("Pool type \"%s\" is OK.\n"), pr.PoolType);
436 /* Check Media Info */
437 memset(&mr, 0, sizeof(mr));
438 bstrncpy(mr.VolumeName, dev->VolHdr.VolumeName, sizeof(mr.VolumeName));
439 mr.PoolId = pr.PoolId;
441 if (db_get_media_record(bjcr, db, &mr)) {
443 Pmsg1(000, _("Media record for %s found in DB.\n"), mr.VolumeName);
445 /* Clear out some volume statistics that will be updated */
446 mr.VolJobs = mr.VolFiles = mr.VolBlocks = 0;
447 mr.VolBytes = rec->data_len + 20;
450 Pmsg1(000, _("VOL_LABEL: Media record not found for Volume: %s\n"),
453 bstrncpy(mr.MediaType, dev->VolHdr.MediaType, sizeof(mr.MediaType));
454 create_media_record(db, &mr, &dev->VolHdr);
456 if (strcmp(mr.MediaType, dev->VolHdr.MediaType) != 0) {
457 Pmsg2(000, _("VOL_LABEL: MediaType mismatch. DB=%s Vol=%s\n"),
458 mr.MediaType, dev->VolHdr.MediaType);
459 return true; /* ignore error */
460 } else if (verbose) {
461 Pmsg1(000, _("Media type \"%s\" is OK.\n"), mr.MediaType);
463 /* Reset some DCR variables */
464 foreach_dlist(dcr, dev->attached_dcrs) {
465 dcr->VolFirstIndex = dcr->FileIndex = 0;
466 dcr->StartBlock = dcr->EndBlock = 0;
467 dcr->StartFile = dcr->EndFile = 0;
470 Pmsg1(000, _("VOL_LABEL: OK for Volume: %s\n"), mr.VolumeName);
476 if (ignored_msgs > 0) {
477 Pmsg1(000, _("%d \"errors\" ignored before first Start of Session record.\n"),
481 unser_session_label(&label, rec);
482 memset(&jr, 0, sizeof(jr));
483 bstrncpy(jr.Job, label.Job, sizeof(jr.Job));
484 if (db_get_job_record(bjcr, db, &jr)) {
485 /* Job record already exists in DB */
486 update_db = false; /* don't change db in create_job_record */
488 Pmsg1(000, _("SOS_LABEL: Found Job record for JobId: %d\n"), jr.JobId);
491 /* Must create a Job record in DB */
493 Pmsg1(000, _("SOS_LABEL: Job record not found for JobId: %d\n"),
497 /* Create Client record if not already there */
498 bstrncpy(cr.Name, label.ClientName, sizeof(cr.Name));
499 create_client_record(db, &cr);
500 jr.ClientId = cr.ClientId;
502 /* process label, if Job record exists don't update db */
503 mjcr = create_job_record(db, &jr, &label, rec);
504 dcr = mjcr->read_dcr;
505 update_db = save_update_db;
507 jr.PoolId = pr.PoolId;
509 /* Set start positions into JCR */
510 if (dev->is_tape()) {
512 * Note, we have already advanced past current block,
513 * so the correct number is block_num - 1
515 dcr->StartBlock = dev->block_num - 1;
516 dcr->StartFile = dev->file;
518 dcr->StartBlock = (uint32_t)dev->file_addr;
519 dcr->StartFile = (uint32_t)(dev->file_addr >> 32);
522 mjcr->start_time = jr.StartTime;
523 mjcr->JobLevel = jr.JobLevel;
525 mjcr->client_name = get_pool_memory(PM_FNAME);
526 pm_strcpy(mjcr->client_name, label.ClientName);
527 mjcr->fileset_name = get_pool_memory(PM_FNAME);
528 pm_strcpy(mjcr->fileset_name, label.FileSetName);
529 bstrncpy(dcr->pool_type, label.PoolType, sizeof(dcr->pool_type));
530 bstrncpy(dcr->pool_name, label.PoolName, sizeof(dcr->pool_name));
532 if (rec->VolSessionId != jr.VolSessionId) {
533 Pmsg3(000, _("SOS_LABEL: VolSessId mismatch for JobId=%u. DB=%d Vol=%d\n"),
535 jr.VolSessionId, rec->VolSessionId);
536 return true; /* ignore error */
538 if (rec->VolSessionTime != jr.VolSessionTime) {
539 Pmsg3(000, _("SOS_LABEL: VolSessTime mismatch for JobId=%u. DB=%d Vol=%d\n"),
541 jr.VolSessionTime, rec->VolSessionTime);
542 return true; /* ignore error */
544 if (jr.PoolId != pr.PoolId) {
545 Pmsg3(000, _("SOS_LABEL: PoolId mismatch for JobId=%u. DB=%d Vol=%d\n"),
547 jr.PoolId, pr.PoolId);
548 return true; /* ignore error */
553 unser_session_label(&elabel, rec);
555 /* Create FileSet record */
556 bstrncpy(fsr.FileSet, label.FileSetName, sizeof(fsr.FileSet));
557 bstrncpy(fsr.MD5, label.FileSetMD5, sizeof(fsr.MD5));
558 create_fileset_record(db, &fsr);
559 jr.FileSetId = fsr.FileSetId;
561 mjcr = get_jcr_by_session(rec->VolSessionId, rec->VolSessionTime);
563 Pmsg2(000, _("Could not find SessId=%d SessTime=%d for EOS record.\n"),
564 rec->VolSessionId, rec->VolSessionTime);
568 /* Do the final update to the Job record */
569 update_job_record(db, &jr, &elabel, rec);
571 mjcr->end_time = jr.EndTime;
572 mjcr->JobStatus = JS_Terminated;
574 /* Create JobMedia record */
575 mjcr->read_dcr->VolLastIndex = dcr->VolLastIndex;
576 create_jobmedia_record(db, mjcr);
577 dev->attached_dcrs->remove(mjcr->read_dcr);
585 case EOT_LABEL: /* end of all tapes */
587 * Wiffle through all jobs still open and close
592 foreach_dlist(mdcr, dev->attached_dcrs) {
593 JCR *mjcr = mdcr->jcr;
594 if (!mjcr || mjcr->JobId == 0) {
597 jr.JobId = mjcr->JobId;
598 /* Mark Job as Error Terimined */
599 jr.JobStatus = JS_ErrorTerminated;
600 jr.JobFiles = mjcr->JobFiles;
601 jr.JobBytes = mjcr->JobBytes;
602 jr.VolSessionId = mjcr->VolSessionId;
603 jr.VolSessionTime = mjcr->VolSessionTime;
604 jr.JobTDate = (utime_t)mjcr->start_time;
605 jr.ClientId = mjcr->ClientId;
606 if (!db_update_job_end_record(bjcr, db, &jr)) {
607 Pmsg1(0, _("Could not update job record. ERR=%s\n"), db_strerror(db));
609 mjcr->read_dcr = NULL;
613 mr.VolFiles = rec->File;
614 mr.VolBlocks = rec->Block;
615 mr.VolBytes += mr.VolBlocks * WRITE_BLKHDR_LENGTH; /* approx. */
617 update_media_record(db, &mr);
618 Pmsg3(0, _("End of all Volumes. VolFiles=%u VolBlocks=%u VolBytes=%s\n"), mr.VolFiles,
619 mr.VolBlocks, edit_uint64_with_commas(mr.VolBytes, ec1));
627 mjcr = get_jcr_by_session(rec->VolSessionId, rec->VolSessionTime);
629 if (mr.VolJobs > 0) {
630 Pmsg2(000, _("Could not find Job for SessId=%d SessTime=%d record.\n"),
631 rec->VolSessionId, rec->VolSessionTime);
637 dcr = mjcr->read_dcr;
638 if (dcr->VolFirstIndex == 0) {
639 dcr->VolFirstIndex = block->FirstIndex;
642 /* File Attributes stream */
643 switch (rec->Stream) {
644 case STREAM_UNIX_ATTRIBUTES:
645 case STREAM_UNIX_ATTRIBUTES_EX:
647 if (!unpack_attributes_record(bjcr, rec->Stream, rec->data, attr)) {
648 Emsg0(M_ERROR_TERM, 0, _("Cannot continue.\n"));
651 if (attr->file_index != rec->FileIndex) {
652 Emsg2(M_ERROR_TERM, 0, _("Record header file index %ld not equal record index %ld\n"),
653 rec->FileIndex, attr->file_index);
657 decode_stat(attr->attr, &attr->statp, &attr->LinkFI);
658 build_attr_output_fnames(bjcr, attr);
659 print_ls_output(bjcr, attr);
661 fr.JobId = mjcr->JobId;
664 if (verbose && (num_files & 0x7FFF) == 0) {
665 char ed1[30], ed2[30], ed3[30], ed4[30];
666 Pmsg4(000, _("%s file records. At file:blk=%s:%s bytes=%s\n"),
667 edit_uint64_with_commas(num_files, ed1),
668 edit_uint64_with_commas(rec->File, ed2),
669 edit_uint64_with_commas(rec->Block, ed3),
670 edit_uint64_with_commas(mr.VolBytes, ed4));
672 create_file_attributes_record(db, mjcr, attr->fname, attr->lname,
673 attr->type, attr->attr, rec);
678 case STREAM_WIN32_DATA:
679 case STREAM_FILE_DATA:
680 case STREAM_SPARSE_DATA:
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 mjcr->JobBytes += rec->data_len; /* No correct, we should expand it */
691 free_jcr(mjcr); /* done using JCR */
694 case STREAM_SPARSE_GZIP_DATA:
695 mjcr->JobBytes += rec->data_len - sizeof(uint64_t); /* No correct, we should expand it */
696 free_jcr(mjcr); /* done using JCR */
699 /* Win32 GZIP stream */
700 case STREAM_WIN32_GZIP_DATA:
701 mjcr->JobBytes += rec->data_len;
702 free_jcr(mjcr); /* done using JCR */
705 case STREAM_MD5_DIGEST:
706 bin_to_base64(digest, (char *)rec->data, CRYPTO_DIGEST_MD5_SIZE);
708 Pmsg1(000, _("Got MD5 record: %s\n"), digest);
710 update_digest_record(db, digest, rec, CRYPTO_DIGEST_MD5);
713 case STREAM_SHA1_DIGEST:
714 bin_to_base64(digest, (char *)rec->data, CRYPTO_DIGEST_SHA1_SIZE);
716 Pmsg1(000, _("Got SHA1 record: %s\n"), digest);
718 update_digest_record(db, digest, rec, CRYPTO_DIGEST_SHA1);
721 case STREAM_SHA256_DIGEST:
722 bin_to_base64(digest, (char *)rec->data, CRYPTO_DIGEST_SHA256_SIZE);
724 Pmsg1(000, _("Got SHA256 record: %s\n"), digest);
726 update_digest_record(db, digest, rec, CRYPTO_DIGEST_SHA256);
729 case STREAM_SHA512_DIGEST:
730 bin_to_base64(digest, (char *)rec->data, CRYPTO_DIGEST_SHA512_SIZE);
732 Pmsg1(000, _("Got SHA512 record: %s\n"), digest);
734 update_digest_record(db, digest, rec, CRYPTO_DIGEST_SHA512);
737 case STREAM_SIGNED_DIGEST:
738 // TODO landonf: Investigate signed digest support in bscan
740 Pmsg0(000, _("Got signed digest record\n"));
744 case STREAM_PROGRAM_NAMES:
746 Pmsg1(000, _("Got Prog Names Stream: %s\n"), rec->data);
750 case STREAM_PROGRAM_DATA:
752 Pmsg0(000, _("Got Prog Data Stream record.\n"));
756 Pmsg2(0, _("Unknown stream type!!! stream=%d data=%s\n"), rec->Stream, rec->data);
763 * Free the Job Control Record if no one is still using it.
764 * Called from main free_jcr() routine in src/lib/jcr.c so
765 * that we can do our Director specific cleanup of the jcr.
767 static void bscan_free_jcr(JCR *jcr)
769 Dmsg0(200, "Start bscan free_jcr\n");
771 if (jcr->file_bsock) {
772 Dmsg0(200, "Close File bsock\n");
773 bnet_close(jcr->file_bsock);
775 if (jcr->store_bsock) {
776 Dmsg0(200, "Close Store bsock\n");
777 bnet_close(jcr->store_bsock);
779 if (jcr->RestoreBootstrap) {
780 free(jcr->RestoreBootstrap);
787 free_dcr(jcr->read_dcr);
788 jcr->read_dcr = NULL;
790 Dmsg0(200, "End bscan free_jcr\n");
794 * We got a File Attributes record on the tape. Now, lookup the Job
795 * record, and then create the attributes record.
797 static int create_file_attributes_record(B_DB *db, JCR *mjcr,
798 char *fname, char *lname, int type,
799 char *ap, DEV_RECORD *rec)
801 DCR *dcr = mjcr->read_dcr;
804 ar.ClientId = mjcr->ClientId;
805 ar.JobId = mjcr->JobId;
806 ar.Stream = rec->Stream;
807 ar.FileIndex = rec->FileIndex;
809 if (dcr->VolFirstIndex == 0) {
810 dcr->VolFirstIndex = rec->FileIndex;
812 dcr->FileIndex = rec->FileIndex;
819 if (!db_create_file_attributes_record(bjcr, db, &ar)) {
820 Pmsg1(0, _("Could not create File Attributes record. ERR=%s\n"), db_strerror(db));
823 mjcr->FileId = ar.FileId;
826 Pmsg1(000, _("Created File record: %s\n"), fname);
832 * For each Volume we see, we create a Medium record
834 static int create_media_record(B_DB *db, MEDIA_DBR *mr, VOLUME_LABEL *vl)
839 /* We mark Vols as Archive to keep them from being re-written */
840 bstrncpy(mr->VolStatus, "Archive", sizeof(mr->VolStatus));
841 mr->VolRetention = 365 * 3600 * 24; /* 1 year */
842 if (vl->VerNum >= 11) {
843 mr->FirstWritten = btime_to_utime(vl->write_btime);
844 mr->LabelDate = btime_to_utime(vl->label_btime);
846 /* DEPRECATED DO NOT USE */
847 dt.julian_day_number = vl->write_date;
848 dt.julian_day_fraction = vl->write_time;
850 mr->FirstWritten = mktime(&tm);
851 dt.julian_day_number = vl->label_date;
852 dt.julian_day_fraction = vl->label_time;
854 mr->LabelDate = mktime(&tm);
856 lasttime = mr->LabelDate;
862 if (!db_create_media_record(bjcr, db, mr)) {
863 Pmsg1(0, _("Could not create media record. ERR=%s\n"), db_strerror(db));
866 if (!db_update_media_record(bjcr, db, mr)) {
867 Pmsg1(0, _("Could not update media record. ERR=%s\n"), db_strerror(db));
871 Pmsg1(000, _("Created Media record for Volume: %s\n"), mr->VolumeName);
878 * Called at end of media to update it
880 static bool update_media_record(B_DB *db, MEDIA_DBR *mr)
882 if (!update_db && !update_vol_info) {
886 mr->LastWritten = lasttime;
887 if (!db_update_media_record(bjcr, db, mr)) {
888 Pmsg1(0, _("Could not update media record. ERR=%s\n"), db_strerror(db));
892 Pmsg1(000, _("Updated Media record at end of Volume: %s\n"), mr->VolumeName);
899 static int create_pool_record(B_DB *db, POOL_DBR *pr)
903 pr->VolRetention = 355 * 3600 * 24; /* 1 year */
908 if (!db_create_pool_record(bjcr, db, pr)) {
909 Pmsg1(0, _("Could not create pool record. ERR=%s\n"), db_strerror(db));
913 Pmsg1(000, _("Created Pool record for Pool: %s\n"), pr->Name);
921 * Called from SOS to create a client for the current Job
923 static int create_client_record(B_DB *db, CLIENT_DBR *cr)
928 if (!db_create_client_record(bjcr, db, cr)) {
929 Pmsg1(0, _("Could not create Client record. ERR=%s\n"), db_strerror(db));
933 Pmsg1(000, _("Created Client record for Client: %s\n"), cr->Name);
938 static int create_fileset_record(B_DB *db, FILESET_DBR *fsr)
944 if (fsr->MD5[0] == 0) {
945 fsr->MD5[0] = ' '; /* Equivalent to nothing */
948 if (db_get_fileset_record(bjcr, db, fsr)) {
950 Pmsg1(000, _("Fileset \"%s\" already exists.\n"), fsr->FileSet);
953 if (!db_create_fileset_record(bjcr, db, fsr)) {
954 Pmsg2(0, _("Could not create FileSet record \"%s\". ERR=%s\n"),
955 fsr->FileSet, db_strerror(db));
959 Pmsg1(000, _("Created FileSet record \"%s\"\n"), fsr->FileSet);
966 * Simulate the two calls on the database to create
967 * the Job record and to update it when the Job actually
970 static JCR *create_job_record(B_DB *db, JOB_DBR *jr, SESSION_LABEL *label,
977 jr->JobId = label->JobId;
978 jr->JobType = label->JobType;
979 jr->JobLevel = label->JobLevel;
980 jr->JobStatus = JS_Created;
981 bstrncpy(jr->Name, label->JobName, sizeof(jr->Name));
982 bstrncpy(jr->Job, label->Job, sizeof(jr->Job));
983 if (label->VerNum >= 11) {
984 jr->SchedTime = btime_to_unix(label->write_btime);
986 dt.julian_day_number = label->write_date;
987 dt.julian_day_fraction = label->write_time;
989 jr->SchedTime = mktime(&tm);
992 jr->StartTime = jr->SchedTime;
993 jr->JobTDate = (utime_t)jr->SchedTime;
994 jr->VolSessionId = rec->VolSessionId;
995 jr->VolSessionTime = rec->VolSessionTime;
997 /* Now create a JCR as if starting the Job */
998 mjcr = create_jcr(jr, rec, label->JobId);
1004 /* This creates the bare essentials */
1005 if (!db_create_job_record(bjcr, db, jr)) {
1006 Pmsg1(0, _("Could not create JobId record. ERR=%s\n"), db_strerror(db));
1010 /* This adds the client, StartTime, JobTDate, ... */
1011 if (!db_update_job_start_record(bjcr, db, jr)) {
1012 Pmsg1(0, _("Could not update job start record. ERR=%s\n"), db_strerror(db));
1015 Pmsg2(000, _("Created new JobId=%u record for original JobId=%u\n"), jr->JobId,
1017 mjcr->JobId = jr->JobId; /* set new JobId */
1022 * Simulate the database call that updates the Job
1023 * at Job termination time.
1025 static int update_job_record(B_DB *db, JOB_DBR *jr, SESSION_LABEL *elabel,
1028 struct date_time dt;
1032 mjcr = get_jcr_by_session(rec->VolSessionId, rec->VolSessionTime);
1034 Pmsg2(000, _("Could not find SessId=%d SessTime=%d for EOS record.\n"),
1035 rec->VolSessionId, rec->VolSessionTime);
1038 if (elabel->VerNum >= 11) {
1039 jr->EndTime = btime_to_unix(elabel->write_btime);
1041 dt.julian_day_number = elabel->write_date;
1042 dt.julian_day_fraction = elabel->write_time;
1043 tm_decode(&dt, &tm);
1044 jr->EndTime = mktime(&tm);
1046 lasttime = jr->EndTime;
1047 mjcr->end_time = jr->EndTime;
1049 jr->JobId = mjcr->JobId;
1050 jr->JobStatus = elabel->JobStatus;
1051 mjcr->JobStatus = elabel->JobStatus;
1052 jr->JobFiles = elabel->JobFiles;
1053 jr->JobBytes = elabel->JobBytes;
1054 jr->VolSessionId = rec->VolSessionId;
1055 jr->VolSessionTime = rec->VolSessionTime;
1056 jr->JobTDate = (utime_t)mjcr->start_time;
1057 jr->ClientId = mjcr->ClientId;
1064 if (!db_update_job_end_record(bjcr, db, jr)) {
1065 Pmsg2(0, _("Could not update JobId=%u record. ERR=%s\n"), jr->JobId, db_strerror(db));
1070 Pmsg2(000, _("Updated Job termination record for JobId=%u TermStat=%c\n"), jr->JobId,
1074 const char *term_msg;
1075 static char term_code[70];
1076 char sdt[50], edt[50];
1077 char ec1[30], ec2[30], ec3[30];
1079 switch (mjcr->JobStatus) {
1081 term_msg = _("Backup OK");
1084 case JS_ErrorTerminated:
1085 term_msg = _("*** Backup Error ***");
1088 term_msg = _("Backup Canceled");
1091 term_msg = term_code;
1092 sprintf(term_code, _("Job Termination code: %d"), mjcr->JobStatus);
1095 bstrftime(sdt, sizeof(sdt), mjcr->start_time);
1096 bstrftime(edt, sizeof(edt), mjcr->end_time);
1097 Pmsg14(000, _("%s\n"
1101 "Backup Level: %s\n"
1105 "Files Written: %s\n"
1106 "Bytes Written: %s\n"
1107 "Volume Session Id: %d\n"
1108 "Volume Session Time: %d\n"
1109 "Last Volume Bytes: %s\n"
1110 "Termination: %s\n\n"),
1115 job_level_to_str(mjcr->JobLevel),
1119 edit_uint64_with_commas(mjcr->JobFiles, ec1),
1120 edit_uint64_with_commas(mjcr->JobBytes, ec2),
1122 mjcr->VolSessionTime,
1123 edit_uint64_with_commas(mr.VolBytes, ec3),
1130 static int create_jobmedia_record(B_DB *db, JCR *mjcr)
1133 DCR *dcr = mjcr->read_dcr;
1135 if (dev->is_tape()) {
1136 dcr->EndBlock = dev->EndBlock;
1137 dcr->EndFile = dev->EndFile;
1140 dcr->EndBlock = (uint32_t)dev->file_addr;
1141 dcr->EndFile = (uint32_t)(dev->file_addr >> 32);
1145 memset(&jmr, 0, sizeof(jmr));
1146 jmr.JobId = mjcr->JobId;
1147 jmr.MediaId = mr.MediaId;
1148 jmr.FirstIndex = dcr->VolFirstIndex;
1149 jmr.LastIndex = dcr->VolLastIndex;
1150 jmr.StartFile = dcr->StartFile;
1151 jmr.EndFile = dcr->EndFile;
1152 jmr.StartBlock = dcr->StartBlock;
1153 jmr.EndBlock = dcr->EndBlock;
1160 if (!db_create_jobmedia_record(bjcr, db, &jmr)) {
1161 Pmsg1(0, _("Could not create JobMedia record. ERR=%s\n"), db_strerror(db));
1165 Pmsg2(000, _("Created JobMedia record JobId %d, MediaId %d\n"),
1166 jmr.JobId, jmr.MediaId);
1172 * Simulate the database call that updates the MD5/SHA1 record
1174 static int update_digest_record(B_DB *db, char *digest, DEV_RECORD *rec, int type)
1178 mjcr = get_jcr_by_session(rec->VolSessionId, rec->VolSessionTime);
1180 if (mr.VolJobs > 0) {
1181 Pmsg2(000, _("Could not find SessId=%d SessTime=%d for MD5/SHA1 record.\n"),
1182 rec->VolSessionId, rec->VolSessionTime);
1189 if (!update_db || mjcr->FileId == 0) {
1194 if (!db_add_digest_to_file_record(bjcr, db, mjcr->FileId, digest, type)) {
1195 Pmsg1(0, _("Could not add MD5/SHA1 to File record. ERR=%s\n"), db_strerror(db));
1200 Pmsg0(000, _("Updated MD5/SHA1 record\n"));
1208 * Create a JCR as if we are really starting the job
1210 static JCR *create_jcr(JOB_DBR *jr, DEV_RECORD *rec, uint32_t JobId)
1214 * Transfer as much as possible to the Job JCR. Most important is
1215 * the JobId and the ClientId.
1217 jobjcr = new_jcr(sizeof(JCR), bscan_free_jcr);
1218 jobjcr->JobType = jr->JobType;
1219 jobjcr->JobLevel = jr->JobLevel;
1220 jobjcr->JobStatus = jr->JobStatus;
1221 bstrncpy(jobjcr->Job, jr->Job, sizeof(jobjcr->Job));
1222 jobjcr->JobId = JobId; /* this is JobId on tape */
1223 jobjcr->sched_time = jr->SchedTime;
1224 jobjcr->start_time = jr->StartTime;
1225 jobjcr->VolSessionId = rec->VolSessionId;
1226 jobjcr->VolSessionTime = rec->VolSessionTime;
1227 jobjcr->ClientId = jr->ClientId;
1228 jobjcr->read_dcr = new_dcr(jobjcr, dev);
1233 /* Dummies to replace askdir.c */
1234 bool dir_get_volume_info(DCR *dcr, enum get_vol_info_rw writing) { return 1;}
1235 bool dir_find_next_appendable_volume(DCR *dcr) { return 1;}
1236 bool dir_update_volume_info(DCR *dcr, bool relabel) { return 1; }
1237 bool dir_create_jobmedia_record(DCR *dcr) { return 1; }
1238 bool dir_ask_sysop_to_create_appendable_volume(DCR *dcr) { return 1; }
1239 bool dir_update_file_attributes(DCR *dcr, DEV_RECORD *rec) { return 1;}
1240 bool dir_send_job_status(JCR *jcr) {return 1;}
1241 int generate_job_event(JCR *jcr, const char *event) { return 1; }
1242 VOLRES *new_volume(DCR *dcr, const char *VolumeName) { return NULL; }
1243 bool free_volume(DEVICE *dev) { return true; }
1244 void free_unused_volume(DCR *dcr) { }
1246 bool dir_ask_sysop_to_mount_volume(DCR *dcr)
1248 DEVICE *dev = dcr->dev;
1249 Dmsg0(20, "Enter dir_ask_sysop_to_mount_volume\n");
1250 /* Close device so user can use autochanger if desired */
1251 if (dev_cap(dev, CAP_OFFLINEUNMOUNT)) {
1254 force_close_device(dev);
1255 fprintf(stderr, _("Mount Volume \"%s\" on device %s and press return when ready: "),
1256 dcr->VolumeName, dev->print_name());