2 Bacula(R) - The Network Backup Solution
4 Copyright (C) 2000-2016 Kern Sibbald
6 The original author of Bacula is Kern Sibbald, with contributions
7 from many others, a complete list can be found in the file AUTHORS.
9 You may use this file and others of this release according to the
10 license defined in the LICENSE file, which includes the Affero General
11 Public License, v3.0 ("AGPLv3") and some additional permissions and
12 terms pursuant to its AGPLv3 Section 7.
14 This notice must be preserved when any source code is
15 conveyed and/or propagated.
17 Bacula(R) is a registered trademark of Kern Sibbald.
21 * Program to scan a Bacula Volume and compare it with
22 * the catalog and optionally synchronize the catalog
25 * Kern E. Sibbald, December 2001
31 #include "findlib/find.h"
32 #include "cats/cats.h"
34 extern bool parse_sd_config(CONFIG *config, const char *configfile, int exit_code);
36 /* Forward referenced functions */
37 static void do_scan(void);
38 static bool record_cb(DCR *dcr, DEV_RECORD *rec);
39 static int create_file_attributes_record(BDB *db, JCR *mjcr,
40 char *fname, char *lname, int type,
41 char *ap, DEV_RECORD *rec);
42 static int create_media_record(BDB *db, MEDIA_DBR *mr, VOLUME_LABEL *vl);
43 static bool update_media_record(BDB *db, MEDIA_DBR *mr);
44 static int create_pool_record(BDB *db, POOL_DBR *pr);
45 static JCR *create_job_record(BDB *db, JOB_DBR *mr, SESSION_LABEL *label, DEV_RECORD *rec);
46 static int update_job_record(BDB *db, JOB_DBR *mr, SESSION_LABEL *elabel,
48 static int create_client_record(BDB *db, CLIENT_DBR *cr);
49 static int create_fileset_record(BDB *db, FILESET_DBR *fsr);
50 static int create_jobmedia_record(BDB *db, JCR *jcr);
51 static JCR *create_jcr(JOB_DBR *jr, DEV_RECORD *rec, uint32_t JobId);
52 static int update_digest_record(BDB *db, char *digest, DEV_RECORD *rec, int type);
56 static DEVICE *dev = NULL;
58 static JCR *bjcr; /* jcr for bscan */
59 static BSR *bsr = NULL;
64 static FILESET_DBR fsr;
67 static SESSION_LABEL label;
68 static SESSION_LABEL elabel;
71 static time_t lasttime = 0;
73 static const char *db_driver = "NULL";
74 static const char *db_name = "bacula";
75 static const char *db_user = "bacula";
76 static const char *db_password = "";
77 static const char *db_host = NULL;
78 static const char *db_ssl_key = NULL;
79 static const char *db_ssl_cert = NULL;
80 static const char *db_ssl_ca = NULL;
81 static const char *db_ssl_capath = NULL;
82 static const char *db_ssl_cipher = NULL;
83 static int db_port = 0;
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 static CONFIG *config;
99 #define CONFIG_FILE "bacula-sd.conf"
102 char *configfile = NULL;
103 STORES *me = NULL; /* our Global resource */
104 bool forge_on = false; /* proceed inspite of I/O errors */
105 pthread_mutex_t device_release_mutex = PTHREAD_MUTEX_INITIALIZER;
106 pthread_cond_t wait_device_release = PTHREAD_COND_INITIALIZER;
112 "\n%sVersion: %s (%s)\n\n"
113 "Usage: bscan [ options ] <bacula-archive>\n"
114 " -b bootstrap specify a bootstrap file\n"
115 " -c <file> specify configuration file\n"
116 " -d <nn> set debug level to <nn>\n"
117 " -dt print timestamp in debug output\n"
118 " -m update media info in database\n"
119 " -D <driver name> specify the driver database name (default NULL)\n"
120 " -n <name> specify the database name (default bacula)\n"
121 " -u <user> specify database user name (default bacula)\n"
122 " -P <password> specify database password (default none)\n"
123 " -h <host> specify database host (default NULL)\n"
124 " -k <sslkey> path name to the key file (default NULL)\n"
125 " -e <sslcert> path name to the certificate file (default NULL)\n"
126 " -a <sslca> path name to the CA certificate file (default NULL)\n"
127 " -t <port> specify database port (default 0)\n"
128 " -p proceed inspite of I/O errors\n"
130 " -s synchronize or store in database\n"
131 " -S show scan progress periodically\n"
133 " -V <Volumes> specify Volume names (separated by |)\n"
134 " -w <dir> specify working directory (default from conf file)\n"
135 " -? print this message\n\n"),
136 2001, "", VERSION, BDATE);
140 int main (int argc, char *argv[])
143 struct stat stat_buf;
144 char *VolumeName = NULL;
146 setlocale(LC_ALL, "");
147 bindtextdomain("bacula", LOCALEDIR);
148 textdomain("bacula");
152 my_name_is(argc, argv, "bscan");
153 init_msg(NULL, NULL);
157 while ((ch = getopt(argc, argv, "b:c:d:D:h:k:e:a:p:mn:pP:rsSt:u:vV:w:?")) != -1) {
163 bsr = parse_bsr(NULL, optarg);
166 case 'c': /* specify config file */
167 if (configfile != NULL) {
170 configfile = bstrdup(optarg);
177 case 'd': /* debug level */
178 if (*optarg == 't') {
179 dbg_timestamp = true;
181 debug_level = atoi(optarg);
182 if (debug_level <= 0) {
197 db_ssl_cert = optarg;
205 db_port = atoi(optarg);
209 update_vol_info = true;
221 db_password = optarg;
240 case 'V': /* Volume name */
258 Pmsg0(0, _("Wrong number of arguments: \n"));
262 if (configfile == NULL) {
263 configfile = bstrdup(CONFIG_FILE);
266 config = new_config_parser();
267 parse_sd_config(config, configfile, M_ERROR_TERM);
269 load_sd_plugins(me->plugin_directory);
271 /* Check if -w option given, otherwise use resource for working directory */
273 working_directory = wd;
274 } else if (!me->working_directory) {
275 Emsg1(M_ERROR_TERM, 0, _("No Working Directory defined in %s. Cannot continue.\n"),
278 working_directory = me->working_directory;
281 /* Check that working directory is good */
282 if (stat(working_directory, &stat_buf) != 0) {
283 Emsg1(M_ERROR_TERM, 0, _("Working Directory: %s not found. Cannot continue.\n"),
286 if (!S_ISDIR(stat_buf.st_mode)) {
287 Emsg1(M_ERROR_TERM, 0, _("Working Directory: %s is not a directory. Cannot continue.\n"),
291 bjcr = setup_jcr("bscan", argv[0], bsr, VolumeName, SD_READ);
295 dev = bjcr->read_dcr->dev;
299 fstat(dev->fd(), &sb);
300 currentVolumeSize = sb.st_size;
301 Pmsg1(000, _("First Volume Size = %s\n"),
302 edit_uint64(currentVolumeSize, ed1));
305 db = db_init_database(NULL, db_driver, db_name, db_user, db_password,
306 db_host, db_port, NULL,
307 db_ssl_key, db_ssl_cert, db_ssl_ca,
308 db_ssl_capath, db_ssl_cipher,
310 if (!db || !db_open_database(NULL, db)) {
311 Pmsg2(000, _("Could not open Catalog \"%s\", database \"%s\".\n"),
314 Jmsg(NULL, M_FATAL, 0, _("%s"), db_strerror(db));
315 Pmsg1(000, "%s", db_strerror(db));
316 db_close_database(NULL, db);
318 Jmsg(NULL, M_ERROR_TERM, 0, _("Could not open Catalog \"%s\", database \"%s\".\n"),
321 Dmsg0(200, "Database opened\n");
323 Pmsg2(000, _("Using Database: %s, User: %s\n"), db_name, db_user);
328 printf("Records added or updated in the catalog:\n%7d Media\n%7d Pool\n%7d Job\n%7d File\n",
329 num_media, num_pools, num_jobs, num_files);
331 printf("Records would have been added or updated in the catalog:\n%7d Media\n%7d Pool\n%7d Job\n%7d File\n",
332 num_media, num_pools, num_jobs, num_files);
341 * We are at the end of reading a tape. Now, we simulate handling
342 * the end of writing a tape by wiffling through the attached
343 * jcrs creating jobmedia records.
345 static bool bscan_mount_next_read_volume(DCR *dcr)
347 DEVICE *dev = dcr->dev;
349 Dmsg1(100, "Walk attached jcrs. Volume=%s\n", dev->getVolCatName());
350 foreach_dlist(mdcr, dev->attached_dcrs) {
351 JCR *mjcr = mdcr->jcr;
352 Dmsg1(100, "========== JobId=%u ========\n", mjcr->JobId);
353 if (mjcr->JobId == 0) {
357 Pmsg1(000, _("Create JobMedia for Job %s\n"), mjcr->Job);
359 mdcr->StartBlock = dcr->StartBlock;
360 mdcr->StartFile = dcr->StartFile;
361 mdcr->EndBlock = dcr->EndBlock;
362 mdcr->EndFile = dcr->EndFile;
363 mdcr->VolMediaId = dcr->VolMediaId;
364 mjcr->read_dcr->VolLastIndex = dcr->VolLastIndex;
365 if( mjcr->bscan_insert_jobmedia_records ) {
366 if (!create_jobmedia_record(db, mjcr)) {
367 Pmsg2(000, _("Could not create JobMedia record for Volume=%s Job=%s\n"),
368 dev->getVolCatName(), mjcr->Job);
373 update_media_record(db, &mr);
375 /* Now let common read routine get up next tape. Note,
376 * we call mount_next... with bscan's jcr because that is where we
377 * have the Volume list, but we get attached.
379 bool stat = mount_next_read_volume(dcr);
384 fstat(dev->fd(), &sb);
385 currentVolumeSize = sb.st_size;
386 Pmsg1(000, _("First Volume Size = %s\n"),
387 edit_uint64(currentVolumeSize, ed1));
392 static void do_scan()
394 attr = new_attr(bjcr);
396 memset(&ar, 0, sizeof(ar));
397 memset(&pr, 0, sizeof(pr));
398 memset(&jr, 0, sizeof(jr));
399 memset(&cr, 0, sizeof(cr));
400 memset(&fsr, 0, sizeof(fsr));
401 memset(&fr, 0, sizeof(fr));
403 /* Detach bscan's jcr as we are not a real Job on the tape */
405 read_records(bjcr->read_dcr, record_cb, bscan_mount_next_read_volume);
408 db_write_batch_file_records(bjcr); /* used by bulk batch file insert */
414 * Returns: true if OK
417 static bool record_cb(DCR *dcr, DEV_RECORD *rec)
421 DEVICE *dev = dcr->dev;
422 JCR *bjcr = dcr->jcr;
423 DEV_BLOCK *block = dcr->block;
425 db_int64_ctx jmr_count;
427 char digest[BASE64_SIZE(CRYPTO_DIGEST_MAX_SIZE)];
429 if (rec->data_len > 0) {
430 mr.VolBytes += rec->data_len + WRITE_RECHDR_LENGTH; /* Accumulate Volume bytes */
431 if (showProgress && currentVolumeSize > 0) {
432 int pct = (mr.VolBytes * 100) / currentVolumeSize;
433 if (pct != last_pct) {
434 fprintf(stdout, _("done: %d%%\n"), pct);
442 Pmsg5(000, _("Record: SessId=%u SessTim=%u FileIndex=%d Stream=%d len=%u\n"),
443 rec->VolSessionId, rec->VolSessionTime, rec->FileIndex,
444 rec->Stream, rec->data_len);
447 * Check for Start or End of Session Record
450 if (rec->FileIndex < 0) {
451 bool save_update_db = update_db;
454 dump_label_record(dev, rec, 1, false);
456 switch (rec->FileIndex) {
458 Pmsg0(000, _("Volume is prelabeled. This tape cannot be scanned.\n"));
463 unser_volume_label(dev, rec);
464 /* Check Pool info */
465 bstrncpy(pr.Name, dev->VolHdr.PoolName, sizeof(pr.Name));
466 bstrncpy(pr.PoolType, dev->VolHdr.PoolType, sizeof(pr.PoolType));
468 if (db_get_pool_numvols(bjcr, db, &pr)) {
470 Pmsg1(000, _("Pool record for %s found in DB.\n"), pr.Name);
474 Pmsg1(000, _("VOL_LABEL: Pool record not found for Pool: %s\n"),
477 create_pool_record(db, &pr);
479 if (strcmp(pr.PoolType, dev->VolHdr.PoolType) != 0) {
480 Pmsg2(000, _("VOL_LABEL: PoolType mismatch. DB=%s Vol=%s\n"),
481 pr.PoolType, dev->VolHdr.PoolType);
483 } else if (verbose) {
484 Pmsg1(000, _("Pool type \"%s\" is OK.\n"), pr.PoolType);
487 /* Check Media Info */
488 memset(&mr, 0, sizeof(mr));
489 bstrncpy(mr.VolumeName, dev->VolHdr.VolumeName, sizeof(mr.VolumeName));
490 mr.PoolId = pr.PoolId;
492 if (db_get_media_record(bjcr, db, &mr)) {
494 Pmsg1(000, _("Media record for %s found in DB.\n"), mr.VolumeName);
496 /* Clear out some volume statistics that will be updated */
497 mr.VolJobs = mr.VolFiles = mr.VolBlocks = 0;
498 mr.VolBytes = rec->data_len + 20;
501 Pmsg1(000, _("VOL_LABEL: Media record not found for Volume: %s\n"),
504 bstrncpy(mr.MediaType, dev->VolHdr.MediaType, sizeof(mr.MediaType));
505 create_media_record(db, &mr, &dev->VolHdr);
507 if (strcmp(mr.MediaType, dev->VolHdr.MediaType) != 0) {
508 Pmsg2(000, _("VOL_LABEL: MediaType mismatch. DB=%s Vol=%s\n"),
509 mr.MediaType, dev->VolHdr.MediaType);
510 return true; /* ignore error */
511 } else if (verbose) {
512 Pmsg1(000, _("Media type \"%s\" is OK.\n"), mr.MediaType);
514 /* Reset some DCR variables */
515 foreach_dlist(dcr, dev->attached_dcrs) {
516 dcr->VolFirstIndex = dcr->FileIndex = 0;
517 dcr->StartBlock = dcr->EndBlock = 0;
518 dcr->StartFile = dcr->EndFile = 0;
522 Pmsg1(000, _("VOL_LABEL: OK for Volume: %s\n"), mr.VolumeName);
528 if (ignored_msgs > 0) {
529 Pmsg1(000, _("%d \"errors\" ignored before first Start of Session record.\n"),
533 unser_session_label(&label, rec);
534 memset(&jr, 0, sizeof(jr));
535 bstrncpy(jr.Job, label.Job, sizeof(jr.Job));
536 if (db_get_job_record(bjcr, db, &jr)) {
537 /* Job record already exists in DB */
538 update_db = false; /* don't change db in create_job_record */
540 Pmsg1(000, _("SOS_LABEL: Found Job record for JobId: %d\n"), jr.JobId);
543 /* Must create a Job record in DB */
545 Pmsg1(000, _("SOS_LABEL: Job record not found for JobId: %d\n"),
550 /* Create Client record if not already there */
551 bstrncpy(cr.Name, label.ClientName, sizeof(cr.Name));
552 create_client_record(db, &cr);
553 jr.ClientId = cr.ClientId;
555 /* process label, if Job record exists don't update db */
556 mjcr = create_job_record(db, &jr, &label, rec);
557 dcr = mjcr->read_dcr;
558 update_db = save_update_db;
560 jr.PoolId = pr.PoolId;
561 mjcr->start_time = jr.StartTime;
562 mjcr->setJobLevel(jr.JobLevel);
564 mjcr->client_name = get_pool_memory(PM_FNAME);
565 pm_strcpy(mjcr->client_name, label.ClientName);
566 mjcr->fileset_name = get_pool_memory(PM_FNAME);
567 pm_strcpy(mjcr->fileset_name, label.FileSetName);
568 bstrncpy(dcr->pool_type, label.PoolType, sizeof(dcr->pool_type));
569 bstrncpy(dcr->pool_name, label.PoolName, sizeof(dcr->pool_name));
571 /* Look for existing Job Media records for this job. If there are
572 any, no new ones need be created. This may occur if File
573 Retention has expired before Job Retention, or if the volume
574 has already been bscan'd */
575 Mmsg(sql_buffer, "SELECT count(*) from JobMedia where JobId=%d", jr.JobId);
576 db_sql_query(db, sql_buffer.c_str(), db_int64_handler, &jmr_count);
577 if( jmr_count.value > 0 ) {
578 //FIELD NAME TO BE DEFINED/CONFIRMED (maybe a struct?)
579 mjcr->bscan_insert_jobmedia_records = false;
581 mjcr->bscan_insert_jobmedia_records = true;
584 if (rec->VolSessionId != jr.VolSessionId) {
585 Pmsg3(000, _("SOS_LABEL: VolSessId mismatch for JobId=%u. DB=%d Vol=%d\n"),
587 jr.VolSessionId, rec->VolSessionId);
588 return true; /* ignore error */
590 if (rec->VolSessionTime != jr.VolSessionTime) {
591 Pmsg3(000, _("SOS_LABEL: VolSessTime mismatch for JobId=%u. DB=%d Vol=%d\n"),
593 jr.VolSessionTime, rec->VolSessionTime);
594 return true; /* ignore error */
596 if (jr.PoolId != pr.PoolId) {
597 Pmsg3(000, _("SOS_LABEL: PoolId mismatch for JobId=%u. DB=%d Vol=%d\n"),
599 jr.PoolId, pr.PoolId);
600 return true; /* ignore error */
605 unser_session_label(&elabel, rec);
607 /* Create FileSet record */
608 bstrncpy(fsr.FileSet, label.FileSetName, sizeof(fsr.FileSet));
609 bstrncpy(fsr.MD5, label.FileSetMD5, sizeof(fsr.MD5));
610 create_fileset_record(db, &fsr);
611 jr.FileSetId = fsr.FileSetId;
613 mjcr = get_jcr_by_session(rec->VolSessionId, rec->VolSessionTime);
615 Pmsg2(000, _("Could not find SessId=%d SessTime=%d for EOS record.\n"),
616 rec->VolSessionId, rec->VolSessionTime);
620 /* Do the final update to the Job record */
621 update_job_record(db, &jr, &elabel, rec);
623 mjcr->end_time = jr.EndTime;
624 mjcr->JobStatus = JS_Terminated;
626 /* Create JobMedia record */
627 mjcr->read_dcr->VolLastIndex = dcr->VolLastIndex;
628 if( mjcr->bscan_insert_jobmedia_records ) {
629 create_jobmedia_record(db, mjcr);
631 free_dcr(mjcr->read_dcr);
639 case EOT_LABEL: /* end of all tapes */
641 * Wiffle through all jobs still open and close
646 foreach_dlist(mdcr, dev->attached_dcrs) {
647 JCR *mjcr = mdcr->jcr;
648 if (!mjcr || mjcr->JobId == 0) {
651 jr.JobId = mjcr->JobId;
652 /* Mark Job as Error Terimined */
653 jr.JobStatus = JS_ErrorTerminated;
654 jr.JobFiles = mjcr->JobFiles;
655 jr.JobBytes = mjcr->JobBytes;
656 jr.VolSessionId = mjcr->VolSessionId;
657 jr.VolSessionTime = mjcr->VolSessionTime;
658 jr.JobTDate = (utime_t)mjcr->start_time;
659 jr.ClientId = mjcr->ClientId;
660 if (!db_update_job_end_record(bjcr, db, &jr)) {
661 Pmsg1(0, _("Could not update job record. ERR=%s\n"), db_strerror(db));
663 mjcr->read_dcr = NULL;
667 mr.VolFiles = rec->File;
668 mr.VolBlocks = rec->Block;
669 mr.VolBytes += mr.VolBlocks * WRITE_BLKHDR_LENGTH; /* approx. */
671 update_media_record(db, &mr);
672 Pmsg3(0, _("End of all Volumes. VolFiles=%u VolBlocks=%u VolBytes=%s\n"), mr.VolFiles,
673 mr.VolBlocks, edit_uint64_with_commas(mr.VolBytes, ec1));
681 mjcr = get_jcr_by_session(rec->VolSessionId, rec->VolSessionTime);
683 if (mr.VolJobs > 0) {
684 Pmsg2(000, _("Could not find Job for SessId=%d SessTime=%d record.\n"),
685 rec->VolSessionId, rec->VolSessionTime);
691 dcr = mjcr->read_dcr;
692 if (dcr->VolFirstIndex == 0) {
693 dcr->VolFirstIndex = block->FirstIndex;
696 /* File Attributes stream */
697 switch (rec->maskedStream) {
698 case STREAM_UNIX_ATTRIBUTES:
699 case STREAM_UNIX_ATTRIBUTES_EX:
701 if (!unpack_attributes_record(bjcr, rec->Stream, rec->data, rec->data_len, attr)) {
702 Emsg0(M_ERROR_TERM, 0, _("Cannot continue.\n"));
706 decode_stat(attr->attr, &attr->statp, sizeof(attr->statp), &attr->LinkFI);
707 build_attr_output_fnames(bjcr, attr);
708 print_ls_output(bjcr, attr);
710 fr.JobId = mjcr->JobId;
713 if (verbose && (num_files & 0x7FFF) == 0) {
714 char ed1[30], ed2[30], ed3[30], ed4[30];
715 Pmsg4(000, _("%s file records. At file:blk=%s:%s bytes=%s\n"),
716 edit_uint64_with_commas(num_files, ed1),
717 edit_uint64_with_commas(rec->File, ed2),
718 edit_uint64_with_commas(rec->Block, ed3),
719 edit_uint64_with_commas(mr.VolBytes, ed4));
721 create_file_attributes_record(db, mjcr, attr->fname, attr->lname,
722 attr->type, attr->attr, rec);
726 case STREAM_RESTORE_OBJECT:
728 /* Implement putting into catalog */
732 case STREAM_WIN32_DATA:
733 case STREAM_FILE_DATA:
734 case STREAM_SPARSE_DATA:
735 case STREAM_MACOS_FORK_DATA:
736 case STREAM_ENCRYPTED_FILE_DATA:
737 case STREAM_ENCRYPTED_WIN32_DATA:
738 case STREAM_ENCRYPTED_MACOS_FORK_DATA:
740 * For encrypted stream, this is an approximation.
741 * The data must be decrypted to know the correct length.
743 mjcr->JobBytes += rec->data_len;
744 if (rec->maskedStream == STREAM_SPARSE_DATA) {
745 mjcr->JobBytes -= sizeof(uint64_t);
748 free_jcr(mjcr); /* done using JCR */
751 case STREAM_GZIP_DATA:
752 case STREAM_COMPRESSED_DATA:
753 case STREAM_ENCRYPTED_FILE_GZIP_DATA:
754 case STREAM_ENCRYPTED_FILE_COMPRESSED_DATA:
755 case STREAM_ENCRYPTED_WIN32_GZIP_DATA:
756 case STREAM_ENCRYPTED_WIN32_COMPRESSED_DATA:
757 /* No correct, we should (decrypt and) expand it
760 mjcr->JobBytes += rec->data_len;
764 case STREAM_SPARSE_GZIP_DATA:
765 case STREAM_SPARSE_COMPRESSED_DATA:
766 mjcr->JobBytes += rec->data_len - sizeof(uint64_t); /* No correct, we should expand it */
767 free_jcr(mjcr); /* done using JCR */
770 /* Win32 GZIP stream */
771 case STREAM_WIN32_GZIP_DATA:
772 case STREAM_WIN32_COMPRESSED_DATA:
773 mjcr->JobBytes += rec->data_len;
774 free_jcr(mjcr); /* done using JCR */
777 case STREAM_MD5_DIGEST:
778 bin_to_base64(digest, sizeof(digest), (char *)rec->data, CRYPTO_DIGEST_MD5_SIZE, true);
780 Pmsg1(000, _("Got MD5 record: %s\n"), digest);
782 update_digest_record(db, digest, rec, CRYPTO_DIGEST_MD5);
785 case STREAM_SHA1_DIGEST:
786 bin_to_base64(digest, sizeof(digest), (char *)rec->data, CRYPTO_DIGEST_SHA1_SIZE, true);
788 Pmsg1(000, _("Got SHA1 record: %s\n"), digest);
790 update_digest_record(db, digest, rec, CRYPTO_DIGEST_SHA1);
793 case STREAM_SHA256_DIGEST:
794 bin_to_base64(digest, sizeof(digest), (char *)rec->data, CRYPTO_DIGEST_SHA256_SIZE, true);
796 Pmsg1(000, _("Got SHA256 record: %s\n"), digest);
798 update_digest_record(db, digest, rec, CRYPTO_DIGEST_SHA256);
801 case STREAM_SHA512_DIGEST:
802 bin_to_base64(digest, sizeof(digest), (char *)rec->data, CRYPTO_DIGEST_SHA512_SIZE, true);
804 Pmsg1(000, _("Got SHA512 record: %s\n"), digest);
806 update_digest_record(db, digest, rec, CRYPTO_DIGEST_SHA512);
809 case STREAM_ENCRYPTED_SESSION_DATA:
810 // TODO landonf: Investigate crypto support in bscan
812 Pmsg0(000, _("Got signed digest record\n"));
816 case STREAM_SIGNED_DIGEST:
817 // TODO landonf: Investigate crypto support in bscan
819 Pmsg0(000, _("Got signed digest record\n"));
823 case STREAM_PROGRAM_NAMES:
825 Pmsg1(000, _("Got Prog Names Stream: %s\n"), rec->data);
829 case STREAM_PROGRAM_DATA:
831 Pmsg0(000, _("Got Prog Data Stream record.\n"));
835 case STREAM_UNIX_ACCESS_ACL: /* Deprecated Standard ACL attributes on UNIX */
836 case STREAM_UNIX_DEFAULT_ACL: /* Deprecated Default ACL attributes on UNIX */
837 case STREAM_HFSPLUS_ATTRIBUTES:
838 case STREAM_XACL_AIX_TEXT:
839 case STREAM_XACL_DARWIN_ACCESS:
840 case STREAM_XACL_FREEBSD_DEFAULT:
841 case STREAM_XACL_FREEBSD_ACCESS:
842 case STREAM_XACL_HPUX_ACL_ENTRY:
843 case STREAM_XACL_IRIX_DEFAULT:
844 case STREAM_XACL_IRIX_ACCESS:
845 case STREAM_XACL_LINUX_DEFAULT:
846 case STREAM_XACL_LINUX_ACCESS:
847 case STREAM_XACL_TRU64_DEFAULT:
848 case STREAM_XACL_TRU64_DEFAULT_DIR:
849 case STREAM_XACL_TRU64_ACCESS:
850 case STREAM_XACL_SOLARIS_POSIX:
851 case STREAM_XACL_SOLARIS_NFS4:
852 case STREAM_XACL_AFS_TEXT:
853 case STREAM_XACL_AIX_AIXC:
854 case STREAM_XACL_AIX_NFS4:
855 case STREAM_XACL_FREEBSD_NFS4:
856 case STREAM_XACL_HURD_DEFAULT:
857 case STREAM_XACL_HURD_ACCESS:
858 /* Ignore Unix ACL attributes */
861 case STREAM_XACL_HURD_XATTR:
862 case STREAM_XACL_IRIX_XATTR:
863 case STREAM_XACL_TRU64_XATTR:
864 case STREAM_XACL_AIX_XATTR:
865 case STREAM_XACL_OPENBSD_XATTR:
866 case STREAM_XACL_SOLARIS_SYS_XATTR:
867 case STREAM_XACL_SOLARIS_XATTR:
868 case STREAM_XACL_DARWIN_XATTR:
869 case STREAM_XACL_FREEBSD_XATTR:
870 case STREAM_XACL_LINUX_XATTR:
871 case STREAM_XACL_NETBSD_XATTR:
872 /* Ignore Unix Extended attributes */
876 Pmsg2(0, _("Unknown stream type!!! stream=%d len=%i\n"), rec->Stream, rec->data_len);
883 * Free the Job Control Record if no one is still using it.
884 * Called from main free_jcr() routine in src/lib/jcr.c so
885 * that we can do our Director specific cleanup of the jcr.
887 static void bscan_free_jcr(JCR *jcr)
889 Dmsg0(200, "Start bscan free_jcr\n");
891 free_bsock(jcr->file_bsock);
892 free_bsock(jcr->store_bsock);
893 if (jcr->RestoreBootstrap) {
894 free(jcr->RestoreBootstrap);
901 free_dcr(jcr->read_dcr);
902 jcr->read_dcr = NULL;
904 Dmsg0(200, "End bscan free_jcr\n");
908 * We got a File Attributes record on the tape. Now, lookup the Job
909 * record, and then create the attributes record.
911 static int create_file_attributes_record(BDB *db, JCR *mjcr,
912 char *fname, char *lname, int type,
913 char *ap, DEV_RECORD *rec)
915 DCR *dcr = mjcr->read_dcr;
918 ar.ClientId = mjcr->ClientId;
919 ar.JobId = mjcr->JobId;
920 ar.Stream = rec->Stream;
921 if (type == FT_DELETED) {
924 ar.FileIndex = rec->FileIndex;
927 if (dcr->VolFirstIndex == 0) {
928 dcr->VolFirstIndex = rec->FileIndex;
930 dcr->FileIndex = rec->FileIndex;
937 if (!db_create_file_attributes_record(bjcr, db, &ar)) {
938 Pmsg1(0, _("Could not create File Attributes record. ERR=%s\n"), db_strerror(db));
941 mjcr->FileId = ar.FileId;
944 Pmsg1(000, _("Created File record: %s\n"), fname);
950 * For each Volume we see, we create a Medium record
952 static int create_media_record(BDB *db, MEDIA_DBR *mr, VOLUME_LABEL *vl)
957 /* We mark Vols as Archive to keep them from being re-written */
958 bstrncpy(mr->VolStatus, "Archive", sizeof(mr->VolStatus));
959 mr->VolRetention = 365 * 3600 * 24; /* 1 year */
961 if (vl->VerNum >= 11) {
962 mr->set_first_written = true; /* Save FirstWritten during update_media */
963 mr->FirstWritten = btime_to_utime(vl->write_btime);
964 mr->LabelDate = btime_to_utime(vl->label_btime);
966 /* DEPRECATED DO NOT USE */
967 dt.julian_day_number = vl->write_date;
968 dt.julian_day_fraction = vl->write_time;
970 mr->FirstWritten = mktime(&tm);
971 dt.julian_day_number = vl->label_date;
972 dt.julian_day_fraction = vl->label_time;
974 mr->LabelDate = mktime(&tm);
976 lasttime = mr->LabelDate;
977 if (mr->VolJobs == 0) {
980 if (mr->VolMounts == 0) {
988 if (!db_create_media_record(bjcr, db, mr)) {
989 Pmsg1(0, _("Could not create media record. ERR=%s\n"), db_strerror(db));
992 if (!db_update_media_record(bjcr, db, mr)) {
993 Pmsg1(0, _("Could not update media record. ERR=%s\n"), db_strerror(db));
997 Pmsg1(000, _("Created Media record for Volume: %s\n"), mr->VolumeName);
1004 * Called at end of media to update it
1006 static bool update_media_record(BDB *db, MEDIA_DBR *mr)
1008 if (!update_db && !update_vol_info) {
1012 mr->LastWritten = lasttime;
1013 if (!db_update_media_record(bjcr, db, mr)) {
1014 Pmsg1(0, _("Could not update media record. ERR=%s\n"), db_strerror(db));
1018 Pmsg1(000, _("Updated Media record at end of Volume: %s\n"), mr->VolumeName);
1025 static int create_pool_record(BDB *db, POOL_DBR *pr)
1029 pr->VolRetention = 355 * 3600 * 24; /* 1 year */
1034 if (!db_create_pool_record(bjcr, db, pr)) {
1035 Pmsg1(0, _("Could not create pool record. ERR=%s\n"), db_strerror(db));
1039 Pmsg1(000, _("Created Pool record for Pool: %s\n"), pr->Name);
1047 * Called from SOS to create a client for the current Job
1049 static int create_client_record(BDB *db, CLIENT_DBR *cr)
1052 * Note, update_db can temporarily be set false while
1053 * updating the database, so we must ensure that ClientId is non-zero.
1057 if (!db_get_client_record(bjcr, db, cr)) {
1058 Pmsg1(0, _("Could not get Client record. ERR=%s\n"), db_strerror(db));
1063 if (!db_create_client_record(bjcr, db, cr)) {
1064 Pmsg1(0, _("Could not create Client record. ERR=%s\n"), db_strerror(db));
1068 Pmsg1(000, _("Created Client record for Client: %s\n"), cr->Name);
1073 static int create_fileset_record(BDB *db, FILESET_DBR *fsr)
1079 if (fsr->MD5[0] == 0) {
1080 fsr->MD5[0] = ' '; /* Equivalent to nothing */
1083 if (db_get_fileset_record(bjcr, db, fsr)) {
1085 Pmsg1(000, _("Fileset \"%s\" already exists.\n"), fsr->FileSet);
1088 if (!db_create_fileset_record(bjcr, db, fsr)) {
1089 Pmsg2(0, _("Could not create FileSet record \"%s\". ERR=%s\n"),
1090 fsr->FileSet, db_strerror(db));
1094 Pmsg1(000, _("Created FileSet record \"%s\"\n"), fsr->FileSet);
1101 * Simulate the two calls on the database to create
1102 * the Job record and to update it when the Job actually
1105 static JCR *create_job_record(BDB *db, JOB_DBR *jr, SESSION_LABEL *label,
1109 struct date_time dt;
1112 jr->JobId = label->JobId;
1113 jr->JobType = label->JobType;
1114 jr->JobLevel = label->JobLevel;
1115 jr->JobStatus = JS_Created;
1116 bstrncpy(jr->Name, label->JobName, sizeof(jr->Name));
1117 bstrncpy(jr->Job, label->Job, sizeof(jr->Job));
1118 if (label->VerNum >= 11) {
1119 jr->SchedTime = btime_to_unix(label->write_btime);
1121 dt.julian_day_number = label->write_date;
1122 dt.julian_day_fraction = label->write_time;
1123 tm_decode(&dt, &tm);
1124 jr->SchedTime = mktime(&tm);
1127 jr->StartTime = jr->SchedTime;
1128 jr->JobTDate = (utime_t)jr->SchedTime;
1129 jr->VolSessionId = rec->VolSessionId;
1130 jr->VolSessionTime = rec->VolSessionTime;
1132 /* Now create a JCR as if starting the Job */
1133 mjcr = create_jcr(jr, rec, label->JobId);
1139 /* This creates the bare essentials */
1140 if (!db_create_job_record(bjcr, db, jr)) {
1141 Pmsg1(0, _("Could not create JobId record. ERR=%s\n"), db_strerror(db));
1145 /* This adds the client, StartTime, JobTDate, ... */
1146 if (!db_update_job_start_record(bjcr, db, jr)) {
1147 Pmsg1(0, _("Could not update job start record. ERR=%s\n"), db_strerror(db));
1150 Pmsg2(000, _("Created new JobId=%u record for original JobId=%u\n"), jr->JobId,
1152 mjcr->JobId = jr->JobId; /* set new JobId */
1157 * Simulate the database call that updates the Job
1158 * at Job termination time.
1160 static int update_job_record(BDB *db, JOB_DBR *jr, SESSION_LABEL *elabel,
1163 struct date_time dt;
1167 mjcr = get_jcr_by_session(rec->VolSessionId, rec->VolSessionTime);
1169 Pmsg2(000, _("Could not find SessId=%d SessTime=%d for EOS record.\n"),
1170 rec->VolSessionId, rec->VolSessionTime);
1173 if (elabel->VerNum >= 11) {
1174 jr->EndTime = btime_to_unix(elabel->write_btime);
1176 dt.julian_day_number = elabel->write_date;
1177 dt.julian_day_fraction = elabel->write_time;
1178 tm_decode(&dt, &tm);
1179 jr->EndTime = mktime(&tm);
1181 lasttime = jr->EndTime;
1182 mjcr->end_time = jr->EndTime;
1184 jr->JobId = mjcr->JobId;
1185 jr->JobStatus = elabel->JobStatus;
1186 mjcr->JobStatus = elabel->JobStatus;
1187 jr->JobFiles = elabel->JobFiles;
1188 if (jr->JobFiles > 0) { /* If we found files, force PurgedFiles */
1189 jr->PurgedFiles = 0;
1191 jr->JobBytes = elabel->JobBytes;
1192 jr->VolSessionId = rec->VolSessionId;
1193 jr->VolSessionTime = rec->VolSessionTime;
1194 jr->JobTDate = (utime_t)mjcr->start_time;
1195 jr->ClientId = mjcr->ClientId;
1202 if (!db_update_job_end_record(bjcr, db, jr)) {
1203 Pmsg2(0, _("Could not update JobId=%u record. ERR=%s\n"), jr->JobId, db_strerror(db));
1208 Pmsg3(000, _("Updated Job termination record for JobId=%u Level=%s TermStat=%c\n"),
1209 jr->JobId, job_level_to_str(mjcr->getJobLevel()), jr->JobStatus);
1212 const char *term_msg;
1213 static char term_code[70];
1214 char sdt[50], edt[50];
1215 char ec1[30], ec2[30], ec3[30];
1217 switch (mjcr->JobStatus) {
1219 term_msg = _("Backup OK");
1222 term_msg = _("Backup OK -- with warnings");
1225 case JS_ErrorTerminated:
1226 term_msg = _("*** Backup Error ***");
1229 term_msg = _("Backup Canceled");
1232 term_msg = term_code;
1233 sprintf(term_code, _("Job Termination code: %d"), mjcr->JobStatus);
1236 bstrftime(sdt, sizeof(sdt), mjcr->start_time);
1237 bstrftime(edt, sizeof(edt), mjcr->end_time);
1238 Pmsg14(000, _("%s\n"
1242 "Backup Level: %s\n"
1246 "Files Written: %s\n"
1247 "Bytes Written: %s\n"
1248 "Volume Session Id: %d\n"
1249 "Volume Session Time: %d\n"
1250 "Last Volume Bytes: %s\n"
1251 "Termination: %s\n\n"),
1256 job_level_to_str(mjcr->getJobLevel()),
1260 edit_uint64_with_commas(mjcr->JobFiles, ec1),
1261 edit_uint64_with_commas(mjcr->JobBytes, ec2),
1263 mjcr->VolSessionTime,
1264 edit_uint64_with_commas(mr.VolBytes, ec3),
1271 static int create_jobmedia_record(BDB *db, JCR *mjcr)
1274 DCR *dcr = mjcr->read_dcr;
1276 dcr->EndBlock = dev->EndBlock;
1277 dcr->EndFile = dev->EndFile;
1278 dcr->VolMediaId = dev->VolCatInfo.VolMediaId;
1280 memset(&jmr, 0, sizeof(jmr));
1281 jmr.JobId = mjcr->JobId;
1282 jmr.MediaId = mr.MediaId;
1283 jmr.FirstIndex = dcr->VolFirstIndex;
1284 jmr.LastIndex = dcr->VolLastIndex;
1285 jmr.StartFile = dcr->StartFile;
1286 jmr.EndFile = dcr->EndFile;
1287 jmr.StartBlock = dcr->StartBlock;
1288 jmr.EndBlock = dcr->EndBlock;
1295 if (!db_create_jobmedia_record(bjcr, db, &jmr)) {
1296 Pmsg1(0, _("Could not create JobMedia record. ERR=%s\n"), db_strerror(db));
1300 Pmsg2(000, _("Created JobMedia record JobId %d, MediaId %d\n"),
1301 jmr.JobId, jmr.MediaId);
1307 * Simulate the database call that updates the MD5/SHA1 record
1309 static int update_digest_record(BDB *db, char *digest, DEV_RECORD *rec, int type)
1313 mjcr = get_jcr_by_session(rec->VolSessionId, rec->VolSessionTime);
1315 if (mr.VolJobs > 0) {
1316 Pmsg2(000, _("Could not find SessId=%d SessTime=%d for MD5/SHA1 record.\n"),
1317 rec->VolSessionId, rec->VolSessionTime);
1324 if (!update_db || mjcr->FileId == 0) {
1329 if (!db_add_digest_to_file_record(bjcr, db, mjcr->FileId, digest, type)) {
1330 Pmsg1(0, _("Could not add MD5/SHA1 to File record. ERR=%s\n"), db_strerror(db));
1335 Pmsg0(000, _("Updated MD5/SHA1 record\n"));
1343 * Create a JCR as if we are really starting the job
1345 static JCR *create_jcr(JOB_DBR *jr, DEV_RECORD *rec, uint32_t JobId)
1349 * Transfer as much as possible to the Job JCR. Most important is
1350 * the JobId and the ClientId.
1352 jobjcr = new_jcr(sizeof(JCR), bscan_free_jcr);
1353 jobjcr->setJobType(jr->JobType);
1354 jobjcr->setJobLevel(jr->JobLevel);
1355 jobjcr->JobStatus = jr->JobStatus;
1356 bstrncpy(jobjcr->Job, jr->Job, sizeof(jobjcr->Job));
1357 jobjcr->JobId = JobId; /* this is JobId on tape */
1358 jobjcr->sched_time = jr->SchedTime;
1359 jobjcr->start_time = jr->StartTime;
1360 jobjcr->VolSessionId = rec->VolSessionId;
1361 jobjcr->VolSessionTime = rec->VolSessionTime;
1362 jobjcr->ClientId = jr->ClientId;
1363 jobjcr->dcr = jobjcr->read_dcr = new_dcr(jobjcr, NULL, dev, SD_READ);
1368 /* Dummies to replace askdir.c */
1369 bool dir_find_next_appendable_volume(DCR *dcr) { return 1;}
1370 bool dir_update_volume_info(DCR *dcr, bool relabel, bool update_LastWritten) { return 1; }
1371 bool dir_create_jobmedia_record(DCR *dcr, bool zero) { return 1; }
1372 bool flush_jobmedia_queue(JCR *jcr) { return true; }
1373 bool dir_ask_sysop_to_create_appendable_volume(DCR *dcr) { return 1; }
1374 bool dir_update_file_attributes(DCR *dcr, DEV_RECORD *rec) { return 1;}
1375 bool dir_send_job_status(JCR *jcr) {return 1;}
1376 int generate_job_event(JCR *jcr, const char *event) { return 1; }
1378 bool dir_ask_sysop_to_mount_volume(DCR *dcr, bool /*writing*/)
1380 DEVICE *dev = dcr->dev;
1381 Dmsg0(20, "Enter dir_ask_sysop_to_mount_volume\n");
1382 /* Close device so user can use autochanger if desired */
1383 fprintf(stderr, _("Mount Volume \"%s\" on device %s and press return when ready: "),
1384 dcr->VolumeName, dev->print_name());
1390 bool dir_get_volume_info(DCR *dcr, enum get_vol_info_rw writing)
1392 Dmsg0(100, "Fake dir_get_volume_info\n");
1393 dcr->setVolCatName(dcr->VolumeName);
1394 Dmsg2(500, "Vol=%s VolType=%d\n", dcr->getVolCatName(), dcr->VolCatInfo.VolCatType);