2 Bacula(R) - The Network Backup Solution
4 Copyright (C) 2000-2015 Kern Sibbald
5 Copyright (C) 2001-2014 Free Software Foundation Europe e.V.
7 The original author of Bacula is Kern Sibbald, with contributions
8 from many others, a complete list can be found in the file AUTHORS.
10 You may use this file and others of this release according to the
11 license defined in the LICENSE file, which includes the Affero General
12 Public License, v3.0 ("AGPLv3") and some additional permissions and
13 terms pursuant to its AGPLv3 Section 7.
15 This notice must be preserved when any source code is
16 conveyed and/or propagated.
18 Bacula(R) is a registered trademark of Kern Sibbald.
22 * Program to scan a Bacula Volume and compare it with
23 * the catalog and optionally synchronize the catalog
26 * Kern E. Sibbald, December 2001
32 #include "findlib/find.h"
33 #include "cats/cats.h"
35 extern bool parse_sd_config(CONFIG *config, const char *configfile, int exit_code);
37 /* Forward referenced functions */
38 static void do_scan(void);
39 static bool record_cb(DCR *dcr, DEV_RECORD *rec);
40 static int create_file_attributes_record(BDB *db, JCR *mjcr,
41 char *fname, char *lname, int type,
42 char *ap, DEV_RECORD *rec);
43 static int create_media_record(BDB *db, MEDIA_DBR *mr, VOLUME_LABEL *vl);
44 static bool update_media_record(BDB *db, MEDIA_DBR *mr);
45 static int create_pool_record(BDB *db, POOL_DBR *pr);
46 static JCR *create_job_record(BDB *db, JOB_DBR *mr, SESSION_LABEL *label, DEV_RECORD *rec);
47 static int update_job_record(BDB *db, JOB_DBR *mr, SESSION_LABEL *elabel,
49 static int create_client_record(BDB *db, CLIENT_DBR *cr);
50 static int create_fileset_record(BDB *db, FILESET_DBR *fsr);
51 static int create_jobmedia_record(BDB *db, JCR *jcr);
52 static JCR *create_jcr(JOB_DBR *jr, DEV_RECORD *rec, uint32_t JobId);
53 static int update_digest_record(BDB *db, char *digest, DEV_RECORD *rec, int type);
57 static DEVICE *dev = NULL;
59 static JCR *bjcr; /* jcr for bscan */
60 static BSR *bsr = NULL;
65 static FILESET_DBR fsr;
68 static SESSION_LABEL label;
69 static SESSION_LABEL elabel;
72 static time_t lasttime = 0;
74 static const char *db_driver = "NULL";
75 static const char *db_name = "bacula";
76 static const char *db_user = "bacula";
77 static const char *db_password = "";
78 static const char *db_host = NULL;
79 static int db_port = 0;
80 static const char *wd = NULL;
81 static bool update_db = false;
82 static bool update_vol_info = false;
83 static bool list_records = false;
84 static int ignored_msgs = 0;
86 static uint64_t currentVolumeSize;
87 static int last_pct = -1;
88 static bool showProgress = false;
89 static int num_jobs = 0;
90 static int num_pools = 0;
91 static int num_media = 0;
92 static int num_files = 0;
94 static CONFIG *config;
95 #define CONFIG_FILE "bacula-sd.conf"
98 char *configfile = NULL;
99 STORES *me = NULL; /* our Global resource */
100 bool forge_on = false; /* proceed inspite of I/O errors */
101 pthread_mutex_t device_release_mutex = PTHREAD_MUTEX_INITIALIZER;
102 pthread_cond_t wait_device_release = PTHREAD_COND_INITIALIZER;
108 "\n%sVersion: %s (%s)\n\n"
109 "Usage: bscan [ options ] <bacula-archive>\n"
110 " -b bootstrap specify a bootstrap file\n"
111 " -c <file> specify configuration file\n"
112 " -d <nn> set debug level to <nn>\n"
113 " -dt print timestamp in debug output\n"
114 " -m update media info in database\n"
115 " -D <driver name> specify the driver database name (default NULL)\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 " -t <port> specify database port (default 0)\n"
121 " -p proceed inspite of I/O errors\n"
123 " -s synchronize or store in database\n"
124 " -S show scan progress periodically\n"
126 " -V <Volumes> specify Volume names (separated by |)\n"
127 " -w <dir> specify working directory (default from conf file)\n"
128 " -? print this message\n\n"),
129 2001, "", VERSION, BDATE);
133 int main (int argc, char *argv[])
136 struct stat stat_buf;
137 char *VolumeName = NULL;
139 setlocale(LC_ALL, "");
140 bindtextdomain("bacula", LOCALEDIR);
141 textdomain("bacula");
145 my_name_is(argc, argv, "bscan");
146 init_msg(NULL, NULL);
150 while ((ch = getopt(argc, argv, "b:c:d:D:h:p:mn:pP:rsSt:u:vV:w:?")) != -1) {
156 bsr = parse_bsr(NULL, optarg);
159 case 'c': /* specify config file */
160 if (configfile != NULL) {
163 configfile = bstrdup(optarg);
170 case 'd': /* debug level */
171 if (*optarg == 't') {
172 dbg_timestamp = true;
174 debug_level = atoi(optarg);
175 if (debug_level <= 0) {
186 db_port = atoi(optarg);
190 update_vol_info = true;
202 db_password = optarg;
221 case 'V': /* Volume name */
239 Pmsg0(0, _("Wrong number of arguments: \n"));
243 if (configfile == NULL) {
244 configfile = bstrdup(CONFIG_FILE);
247 config = new_config_parser();
248 parse_sd_config(config, configfile, M_ERROR_TERM);
250 load_sd_plugins(me->plugin_directory);
252 /* Check if -w option given, otherwise use resource for working directory */
254 working_directory = wd;
255 } else if (!me->working_directory) {
256 Emsg1(M_ERROR_TERM, 0, _("No Working Directory defined in %s. Cannot continue.\n"),
259 working_directory = me->working_directory;
262 /* Check that working directory is good */
263 if (stat(working_directory, &stat_buf) != 0) {
264 Emsg1(M_ERROR_TERM, 0, _("Working Directory: %s not found. Cannot continue.\n"),
267 if (!S_ISDIR(stat_buf.st_mode)) {
268 Emsg1(M_ERROR_TERM, 0, _("Working Directory: %s is not a directory. Cannot continue.\n"),
272 bjcr = setup_jcr("bscan", argv[0], bsr, VolumeName, SD_READ);
276 dev = bjcr->read_dcr->dev;
280 fstat(dev->fd(), &sb);
281 currentVolumeSize = sb.st_size;
282 Pmsg1(000, _("First Volume Size = %s\n"),
283 edit_uint64(currentVolumeSize, ed1));
286 db = db_init_database(NULL, db_driver, db_name, db_user, db_password,
287 db_host, db_port, NULL, false, false);
288 if (!db || !db_open_database(NULL, db)) {
289 Pmsg2(000, _("Could not open Catalog \"%s\", database \"%s\".\n"),
292 Jmsg(NULL, M_FATAL, 0, _("%s"), db_strerror(db));
293 Pmsg1(000, "%s", db_strerror(db));
294 db_close_database(NULL, db);
296 Jmsg(NULL, M_ERROR_TERM, 0, _("Could not open Catalog \"%s\", database \"%s\".\n"),
299 Dmsg0(200, "Database opened\n");
301 Pmsg2(000, _("Using Database: %s, User: %s\n"), db_name, db_user);
306 printf("Records added or updated in the catalog:\n%7d Media\n%7d Pool\n%7d Job\n%7d File\n",
307 num_media, num_pools, num_jobs, num_files);
309 printf("Records would have been added or updated in the catalog:\n%7d Media\n%7d Pool\n%7d Job\n%7d File\n",
310 num_media, num_pools, num_jobs, num_files);
319 * We are at the end of reading a tape. Now, we simulate handling
320 * the end of writing a tape by wiffling through the attached
321 * jcrs creating jobmedia records.
323 static bool bscan_mount_next_read_volume(DCR *dcr)
325 DEVICE *dev = dcr->dev;
327 Dmsg1(100, "Walk attached jcrs. Volume=%s\n", dev->getVolCatName());
328 foreach_dlist(mdcr, dev->attached_dcrs) {
329 JCR *mjcr = mdcr->jcr;
330 Dmsg1(100, "========== JobId=%u ========\n", mjcr->JobId);
331 if (mjcr->JobId == 0) {
335 Pmsg1(000, _("Create JobMedia for Job %s\n"), mjcr->Job);
337 mdcr->StartBlock = dcr->StartBlock;
338 mdcr->StartFile = dcr->StartFile;
339 mdcr->EndBlock = dcr->EndBlock;
340 mdcr->EndFile = dcr->EndFile;
341 mdcr->VolMediaId = dcr->VolMediaId;
342 mjcr->read_dcr->VolLastIndex = dcr->VolLastIndex;
343 if( mjcr->bscan_insert_jobmedia_records ) {
344 if (!create_jobmedia_record(db, mjcr)) {
345 Pmsg2(000, _("Could not create JobMedia record for Volume=%s Job=%s\n"),
346 dev->getVolCatName(), mjcr->Job);
351 update_media_record(db, &mr);
353 /* Now let common read routine get up next tape. Note,
354 * we call mount_next... with bscan's jcr because that is where we
355 * have the Volume list, but we get attached.
357 bool stat = mount_next_read_volume(dcr);
362 fstat(dev->fd(), &sb);
363 currentVolumeSize = sb.st_size;
364 Pmsg1(000, _("First Volume Size = %s\n"),
365 edit_uint64(currentVolumeSize, ed1));
370 static void do_scan()
372 attr = new_attr(bjcr);
374 memset(&ar, 0, sizeof(ar));
375 memset(&pr, 0, sizeof(pr));
376 memset(&jr, 0, sizeof(jr));
377 memset(&cr, 0, sizeof(cr));
378 memset(&fsr, 0, sizeof(fsr));
379 memset(&fr, 0, sizeof(fr));
381 /* Detach bscan's jcr as we are not a real Job on the tape */
383 read_records(bjcr->read_dcr, record_cb, bscan_mount_next_read_volume);
386 db_write_batch_file_records(bjcr); /* used by bulk batch file insert */
392 * Returns: true if OK
395 static bool record_cb(DCR *dcr, DEV_RECORD *rec)
399 DEVICE *dev = dcr->dev;
400 JCR *bjcr = dcr->jcr;
401 DEV_BLOCK *block = dcr->block;
403 db_int64_ctx jmr_count;
405 char digest[BASE64_SIZE(CRYPTO_DIGEST_MAX_SIZE)];
407 if (rec->data_len > 0) {
408 mr.VolBytes += rec->data_len + WRITE_RECHDR_LENGTH; /* Accumulate Volume bytes */
409 if (showProgress && currentVolumeSize > 0) {
410 int pct = (mr.VolBytes * 100) / currentVolumeSize;
411 if (pct != last_pct) {
412 fprintf(stdout, _("done: %d%%\n"), pct);
420 Pmsg5(000, _("Record: SessId=%u SessTim=%u FileIndex=%d Stream=%d len=%u\n"),
421 rec->VolSessionId, rec->VolSessionTime, rec->FileIndex,
422 rec->Stream, rec->data_len);
425 * Check for Start or End of Session Record
428 if (rec->FileIndex < 0) {
429 bool save_update_db = update_db;
432 dump_label_record(dev, rec, 1, false);
434 switch (rec->FileIndex) {
436 Pmsg0(000, _("Volume is prelabeled. This tape cannot be scanned.\n"));
441 unser_volume_label(dev, rec);
442 /* Check Pool info */
443 bstrncpy(pr.Name, dev->VolHdr.PoolName, sizeof(pr.Name));
444 bstrncpy(pr.PoolType, dev->VolHdr.PoolType, sizeof(pr.PoolType));
446 if (db_get_pool_numvols(bjcr, db, &pr)) {
448 Pmsg1(000, _("Pool record for %s found in DB.\n"), pr.Name);
452 Pmsg1(000, _("VOL_LABEL: Pool record not found for Pool: %s\n"),
455 create_pool_record(db, &pr);
457 if (strcmp(pr.PoolType, dev->VolHdr.PoolType) != 0) {
458 Pmsg2(000, _("VOL_LABEL: PoolType mismatch. DB=%s Vol=%s\n"),
459 pr.PoolType, dev->VolHdr.PoolType);
461 } else if (verbose) {
462 Pmsg1(000, _("Pool type \"%s\" is OK.\n"), pr.PoolType);
465 /* Check Media Info */
466 memset(&mr, 0, sizeof(mr));
467 bstrncpy(mr.VolumeName, dev->VolHdr.VolumeName, sizeof(mr.VolumeName));
468 mr.PoolId = pr.PoolId;
470 if (db_get_media_record(bjcr, db, &mr)) {
472 Pmsg1(000, _("Media record for %s found in DB.\n"), mr.VolumeName);
474 /* Clear out some volume statistics that will be updated */
475 mr.VolJobs = mr.VolFiles = mr.VolBlocks = 0;
476 mr.VolBytes = rec->data_len + 20;
479 Pmsg1(000, _("VOL_LABEL: Media record not found for Volume: %s\n"),
482 bstrncpy(mr.MediaType, dev->VolHdr.MediaType, sizeof(mr.MediaType));
483 create_media_record(db, &mr, &dev->VolHdr);
485 if (strcmp(mr.MediaType, dev->VolHdr.MediaType) != 0) {
486 Pmsg2(000, _("VOL_LABEL: MediaType mismatch. DB=%s Vol=%s\n"),
487 mr.MediaType, dev->VolHdr.MediaType);
488 return true; /* ignore error */
489 } else if (verbose) {
490 Pmsg1(000, _("Media type \"%s\" is OK.\n"), mr.MediaType);
492 /* Reset some DCR variables */
493 foreach_dlist(dcr, dev->attached_dcrs) {
494 dcr->VolFirstIndex = dcr->FileIndex = 0;
495 dcr->StartBlock = dcr->EndBlock = 0;
496 dcr->StartFile = dcr->EndFile = 0;
500 Pmsg1(000, _("VOL_LABEL: OK for Volume: %s\n"), mr.VolumeName);
506 if (ignored_msgs > 0) {
507 Pmsg1(000, _("%d \"errors\" ignored before first Start of Session record.\n"),
511 unser_session_label(&label, rec);
512 memset(&jr, 0, sizeof(jr));
513 bstrncpy(jr.Job, label.Job, sizeof(jr.Job));
514 if (db_get_job_record(bjcr, db, &jr)) {
515 /* Job record already exists in DB */
516 update_db = false; /* don't change db in create_job_record */
518 Pmsg1(000, _("SOS_LABEL: Found Job record for JobId: %d\n"), jr.JobId);
521 /* Must create a Job record in DB */
523 Pmsg1(000, _("SOS_LABEL: Job record not found for JobId: %d\n"),
528 /* Create Client record if not already there */
529 bstrncpy(cr.Name, label.ClientName, sizeof(cr.Name));
530 create_client_record(db, &cr);
531 jr.ClientId = cr.ClientId;
533 /* process label, if Job record exists don't update db */
534 mjcr = create_job_record(db, &jr, &label, rec);
535 dcr = mjcr->read_dcr;
536 update_db = save_update_db;
538 jr.PoolId = pr.PoolId;
539 mjcr->start_time = jr.StartTime;
540 mjcr->setJobLevel(jr.JobLevel);
542 mjcr->client_name = get_pool_memory(PM_FNAME);
543 pm_strcpy(mjcr->client_name, label.ClientName);
544 mjcr->fileset_name = get_pool_memory(PM_FNAME);
545 pm_strcpy(mjcr->fileset_name, label.FileSetName);
546 bstrncpy(dcr->pool_type, label.PoolType, sizeof(dcr->pool_type));
547 bstrncpy(dcr->pool_name, label.PoolName, sizeof(dcr->pool_name));
549 /* Look for existing Job Media records for this job. If there are
550 any, no new ones need be created. This may occur if File
551 Retention has expired before Job Retention, or if the volume
552 has already been bscan'd */
553 Mmsg(sql_buffer, "SELECT count(*) from JobMedia where JobId=%d", jr.JobId);
554 db_sql_query(db, sql_buffer.c_str(), db_int64_handler, &jmr_count);
555 if( jmr_count.value > 0 ) {
556 //FIELD NAME TO BE DEFINED/CONFIRMED (maybe a struct?)
557 mjcr->bscan_insert_jobmedia_records = false;
559 mjcr->bscan_insert_jobmedia_records = true;
562 if (rec->VolSessionId != jr.VolSessionId) {
563 Pmsg3(000, _("SOS_LABEL: VolSessId mismatch for JobId=%u. DB=%d Vol=%d\n"),
565 jr.VolSessionId, rec->VolSessionId);
566 return true; /* ignore error */
568 if (rec->VolSessionTime != jr.VolSessionTime) {
569 Pmsg3(000, _("SOS_LABEL: VolSessTime mismatch for JobId=%u. DB=%d Vol=%d\n"),
571 jr.VolSessionTime, rec->VolSessionTime);
572 return true; /* ignore error */
574 if (jr.PoolId != pr.PoolId) {
575 Pmsg3(000, _("SOS_LABEL: PoolId mismatch for JobId=%u. DB=%d Vol=%d\n"),
577 jr.PoolId, pr.PoolId);
578 return true; /* ignore error */
583 unser_session_label(&elabel, rec);
585 /* Create FileSet record */
586 bstrncpy(fsr.FileSet, label.FileSetName, sizeof(fsr.FileSet));
587 bstrncpy(fsr.MD5, label.FileSetMD5, sizeof(fsr.MD5));
588 create_fileset_record(db, &fsr);
589 jr.FileSetId = fsr.FileSetId;
591 mjcr = get_jcr_by_session(rec->VolSessionId, rec->VolSessionTime);
593 Pmsg2(000, _("Could not find SessId=%d SessTime=%d for EOS record.\n"),
594 rec->VolSessionId, rec->VolSessionTime);
598 /* Do the final update to the Job record */
599 update_job_record(db, &jr, &elabel, rec);
601 mjcr->end_time = jr.EndTime;
602 mjcr->JobStatus = JS_Terminated;
604 /* Create JobMedia record */
605 mjcr->read_dcr->VolLastIndex = dcr->VolLastIndex;
606 if( mjcr->bscan_insert_jobmedia_records ) {
607 create_jobmedia_record(db, mjcr);
609 free_dcr(mjcr->read_dcr);
617 case EOT_LABEL: /* end of all tapes */
619 * Wiffle through all jobs still open and close
624 foreach_dlist(mdcr, dev->attached_dcrs) {
625 JCR *mjcr = mdcr->jcr;
626 if (!mjcr || mjcr->JobId == 0) {
629 jr.JobId = mjcr->JobId;
630 /* Mark Job as Error Terimined */
631 jr.JobStatus = JS_ErrorTerminated;
632 jr.JobFiles = mjcr->JobFiles;
633 jr.JobBytes = mjcr->JobBytes;
634 jr.VolSessionId = mjcr->VolSessionId;
635 jr.VolSessionTime = mjcr->VolSessionTime;
636 jr.JobTDate = (utime_t)mjcr->start_time;
637 jr.ClientId = mjcr->ClientId;
638 if (!db_update_job_end_record(bjcr, db, &jr)) {
639 Pmsg1(0, _("Could not update job record. ERR=%s\n"), db_strerror(db));
641 mjcr->read_dcr = NULL;
645 mr.VolFiles = rec->File;
646 mr.VolBlocks = rec->Block;
647 mr.VolBytes += mr.VolBlocks * WRITE_BLKHDR_LENGTH; /* approx. */
649 update_media_record(db, &mr);
650 Pmsg3(0, _("End of all Volumes. VolFiles=%u VolBlocks=%u VolBytes=%s\n"), mr.VolFiles,
651 mr.VolBlocks, edit_uint64_with_commas(mr.VolBytes, ec1));
659 mjcr = get_jcr_by_session(rec->VolSessionId, rec->VolSessionTime);
661 if (mr.VolJobs > 0) {
662 Pmsg2(000, _("Could not find Job for SessId=%d SessTime=%d record.\n"),
663 rec->VolSessionId, rec->VolSessionTime);
669 dcr = mjcr->read_dcr;
670 if (dcr->VolFirstIndex == 0) {
671 dcr->VolFirstIndex = block->FirstIndex;
674 /* File Attributes stream */
675 switch (rec->maskedStream) {
676 case STREAM_UNIX_ATTRIBUTES:
677 case STREAM_UNIX_ATTRIBUTES_EX:
679 if (!unpack_attributes_record(bjcr, rec->Stream, rec->data, rec->data_len, attr)) {
680 Emsg0(M_ERROR_TERM, 0, _("Cannot continue.\n"));
684 decode_stat(attr->attr, &attr->statp, sizeof(attr->statp), &attr->LinkFI);
685 build_attr_output_fnames(bjcr, attr);
686 print_ls_output(bjcr, attr);
688 fr.JobId = mjcr->JobId;
691 if (verbose && (num_files & 0x7FFF) == 0) {
692 char ed1[30], ed2[30], ed3[30], ed4[30];
693 Pmsg4(000, _("%s file records. At file:blk=%s:%s bytes=%s\n"),
694 edit_uint64_with_commas(num_files, ed1),
695 edit_uint64_with_commas(rec->File, ed2),
696 edit_uint64_with_commas(rec->Block, ed3),
697 edit_uint64_with_commas(mr.VolBytes, ed4));
699 create_file_attributes_record(db, mjcr, attr->fname, attr->lname,
700 attr->type, attr->attr, rec);
704 case STREAM_RESTORE_OBJECT:
706 /* Implement putting into catalog */
710 case STREAM_WIN32_DATA:
711 case STREAM_FILE_DATA:
712 case STREAM_SPARSE_DATA:
713 case STREAM_MACOS_FORK_DATA:
714 case STREAM_ENCRYPTED_FILE_DATA:
715 case STREAM_ENCRYPTED_WIN32_DATA:
716 case STREAM_ENCRYPTED_MACOS_FORK_DATA:
718 * For encrypted stream, this is an approximation.
719 * The data must be decrypted to know the correct length.
721 mjcr->JobBytes += rec->data_len;
722 if (rec->maskedStream == STREAM_SPARSE_DATA) {
723 mjcr->JobBytes -= sizeof(uint64_t);
726 free_jcr(mjcr); /* done using JCR */
729 case STREAM_GZIP_DATA:
730 case STREAM_COMPRESSED_DATA:
731 case STREAM_ENCRYPTED_FILE_GZIP_DATA:
732 case STREAM_ENCRYPTED_FILE_COMPRESSED_DATA:
733 case STREAM_ENCRYPTED_WIN32_GZIP_DATA:
734 case STREAM_ENCRYPTED_WIN32_COMPRESSED_DATA:
735 /* No correct, we should (decrypt and) expand it
738 mjcr->JobBytes += rec->data_len;
742 case STREAM_SPARSE_GZIP_DATA:
743 case STREAM_SPARSE_COMPRESSED_DATA:
744 mjcr->JobBytes += rec->data_len - sizeof(uint64_t); /* No correct, we should expand it */
745 free_jcr(mjcr); /* done using JCR */
748 /* Win32 GZIP stream */
749 case STREAM_WIN32_GZIP_DATA:
750 case STREAM_WIN32_COMPRESSED_DATA:
751 mjcr->JobBytes += rec->data_len;
752 free_jcr(mjcr); /* done using JCR */
755 case STREAM_MD5_DIGEST:
756 bin_to_base64(digest, sizeof(digest), (char *)rec->data, CRYPTO_DIGEST_MD5_SIZE, true);
758 Pmsg1(000, _("Got MD5 record: %s\n"), digest);
760 update_digest_record(db, digest, rec, CRYPTO_DIGEST_MD5);
763 case STREAM_SHA1_DIGEST:
764 bin_to_base64(digest, sizeof(digest), (char *)rec->data, CRYPTO_DIGEST_SHA1_SIZE, true);
766 Pmsg1(000, _("Got SHA1 record: %s\n"), digest);
768 update_digest_record(db, digest, rec, CRYPTO_DIGEST_SHA1);
771 case STREAM_SHA256_DIGEST:
772 bin_to_base64(digest, sizeof(digest), (char *)rec->data, CRYPTO_DIGEST_SHA256_SIZE, true);
774 Pmsg1(000, _("Got SHA256 record: %s\n"), digest);
776 update_digest_record(db, digest, rec, CRYPTO_DIGEST_SHA256);
779 case STREAM_SHA512_DIGEST:
780 bin_to_base64(digest, sizeof(digest), (char *)rec->data, CRYPTO_DIGEST_SHA512_SIZE, true);
782 Pmsg1(000, _("Got SHA512 record: %s\n"), digest);
784 update_digest_record(db, digest, rec, CRYPTO_DIGEST_SHA512);
787 case STREAM_ENCRYPTED_SESSION_DATA:
788 // TODO landonf: Investigate crypto support in bscan
790 Pmsg0(000, _("Got signed digest record\n"));
794 case STREAM_SIGNED_DIGEST:
795 // TODO landonf: Investigate crypto support in bscan
797 Pmsg0(000, _("Got signed digest record\n"));
801 case STREAM_PROGRAM_NAMES:
803 Pmsg1(000, _("Got Prog Names Stream: %s\n"), rec->data);
807 case STREAM_PROGRAM_DATA:
809 Pmsg0(000, _("Got Prog Data Stream record.\n"));
813 case STREAM_UNIX_ACCESS_ACL: /* Deprecated Standard ACL attributes on UNIX */
814 case STREAM_UNIX_DEFAULT_ACL: /* Deprecated Default ACL attributes on UNIX */
815 case STREAM_HFSPLUS_ATTRIBUTES:
816 case STREAM_ACL_AIX_TEXT:
817 case STREAM_ACL_DARWIN_ACCESS:
818 case STREAM_ACL_FREEBSD_DEFAULT:
819 case STREAM_ACL_FREEBSD_ACCESS:
820 case STREAM_ACL_HPUX_ACL_ENTRY:
821 case STREAM_ACL_IRIX_DEFAULT:
822 case STREAM_ACL_IRIX_ACCESS:
823 case STREAM_ACL_LINUX_DEFAULT:
824 case STREAM_ACL_LINUX_ACCESS:
825 case STREAM_ACL_TRU64_DEFAULT:
826 case STREAM_ACL_TRU64_DEFAULT_DIR:
827 case STREAM_ACL_TRU64_ACCESS:
828 case STREAM_ACL_SOLARIS_POSIX:
829 case STREAM_ACL_SOLARIS_NFS4:
830 case STREAM_ACL_AFS_TEXT:
831 case STREAM_ACL_AIX_AIXC:
832 case STREAM_ACL_AIX_NFS4:
833 case STREAM_ACL_FREEBSD_NFS4:
834 case STREAM_ACL_HURD_DEFAULT:
835 case STREAM_ACL_HURD_ACCESS:
836 /* Ignore Unix ACL attributes */
839 case STREAM_XATTR_HURD:
840 case STREAM_XATTR_IRIX:
841 case STREAM_XATTR_TRU64:
842 case STREAM_XATTR_AIX:
843 case STREAM_XATTR_OPENBSD:
844 case STREAM_XATTR_SOLARIS_SYS:
845 case STREAM_XATTR_SOLARIS:
846 case STREAM_XATTR_DARWIN:
847 case STREAM_XATTR_FREEBSD:
848 case STREAM_XATTR_LINUX:
849 case STREAM_XATTR_NETBSD:
850 /* Ignore Unix Extended attributes */
854 Pmsg2(0, _("Unknown stream type!!! stream=%d len=%i\n"), rec->Stream, rec->data_len);
861 * Free the Job Control Record if no one is still using it.
862 * Called from main free_jcr() routine in src/lib/jcr.c so
863 * that we can do our Director specific cleanup of the jcr.
865 static void bscan_free_jcr(JCR *jcr)
867 Dmsg0(200, "Start bscan free_jcr\n");
869 free_bsock(jcr->file_bsock);
870 free_bsock(jcr->store_bsock);
871 if (jcr->RestoreBootstrap) {
872 free(jcr->RestoreBootstrap);
879 free_dcr(jcr->read_dcr);
880 jcr->read_dcr = NULL;
882 Dmsg0(200, "End bscan free_jcr\n");
886 * We got a File Attributes record on the tape. Now, lookup the Job
887 * record, and then create the attributes record.
889 static int create_file_attributes_record(BDB *db, JCR *mjcr,
890 char *fname, char *lname, int type,
891 char *ap, DEV_RECORD *rec)
893 DCR *dcr = mjcr->read_dcr;
896 ar.ClientId = mjcr->ClientId;
897 ar.JobId = mjcr->JobId;
898 ar.Stream = rec->Stream;
899 if (type == FT_DELETED) {
902 ar.FileIndex = rec->FileIndex;
905 if (dcr->VolFirstIndex == 0) {
906 dcr->VolFirstIndex = rec->FileIndex;
908 dcr->FileIndex = rec->FileIndex;
915 if (!db_create_file_attributes_record(bjcr, db, &ar)) {
916 Pmsg1(0, _("Could not create File Attributes record. ERR=%s\n"), db_strerror(db));
919 mjcr->FileId = ar.FileId;
922 Pmsg1(000, _("Created File record: %s\n"), fname);
928 * For each Volume we see, we create a Medium record
930 static int create_media_record(BDB *db, MEDIA_DBR *mr, VOLUME_LABEL *vl)
935 /* We mark Vols as Archive to keep them from being re-written */
936 bstrncpy(mr->VolStatus, "Archive", sizeof(mr->VolStatus));
937 mr->VolRetention = 365 * 3600 * 24; /* 1 year */
939 if (vl->VerNum >= 11) {
940 mr->set_first_written = true; /* Save FirstWritten during update_media */
941 mr->FirstWritten = btime_to_utime(vl->write_btime);
942 mr->LabelDate = btime_to_utime(vl->label_btime);
944 /* DEPRECATED DO NOT USE */
945 dt.julian_day_number = vl->write_date;
946 dt.julian_day_fraction = vl->write_time;
948 mr->FirstWritten = mktime(&tm);
949 dt.julian_day_number = vl->label_date;
950 dt.julian_day_fraction = vl->label_time;
952 mr->LabelDate = mktime(&tm);
954 lasttime = mr->LabelDate;
955 if (mr->VolJobs == 0) {
958 if (mr->VolMounts == 0) {
966 if (!db_create_media_record(bjcr, db, mr)) {
967 Pmsg1(0, _("Could not create media record. ERR=%s\n"), db_strerror(db));
970 if (!db_update_media_record(bjcr, db, mr)) {
971 Pmsg1(0, _("Could not update media record. ERR=%s\n"), db_strerror(db));
975 Pmsg1(000, _("Created Media record for Volume: %s\n"), mr->VolumeName);
982 * Called at end of media to update it
984 static bool update_media_record(BDB *db, MEDIA_DBR *mr)
986 if (!update_db && !update_vol_info) {
990 mr->LastWritten = lasttime;
991 if (!db_update_media_record(bjcr, db, mr)) {
992 Pmsg1(0, _("Could not update media record. ERR=%s\n"), db_strerror(db));
996 Pmsg1(000, _("Updated Media record at end of Volume: %s\n"), mr->VolumeName);
1003 static int create_pool_record(BDB *db, POOL_DBR *pr)
1007 pr->VolRetention = 355 * 3600 * 24; /* 1 year */
1012 if (!db_create_pool_record(bjcr, db, pr)) {
1013 Pmsg1(0, _("Could not create pool record. ERR=%s\n"), db_strerror(db));
1017 Pmsg1(000, _("Created Pool record for Pool: %s\n"), pr->Name);
1025 * Called from SOS to create a client for the current Job
1027 static int create_client_record(BDB *db, CLIENT_DBR *cr)
1030 * Note, update_db can temporarily be set false while
1031 * updating the database, so we must ensure that ClientId is non-zero.
1035 if (!db_get_client_record(bjcr, db, cr)) {
1036 Pmsg1(0, _("Could not get Client record. ERR=%s\n"), db_strerror(db));
1041 if (!db_create_client_record(bjcr, db, cr)) {
1042 Pmsg1(0, _("Could not create Client record. ERR=%s\n"), db_strerror(db));
1046 Pmsg1(000, _("Created Client record for Client: %s\n"), cr->Name);
1051 static int create_fileset_record(BDB *db, FILESET_DBR *fsr)
1057 if (fsr->MD5[0] == 0) {
1058 fsr->MD5[0] = ' '; /* Equivalent to nothing */
1061 if (db_get_fileset_record(bjcr, db, fsr)) {
1063 Pmsg1(000, _("Fileset \"%s\" already exists.\n"), fsr->FileSet);
1066 if (!db_create_fileset_record(bjcr, db, fsr)) {
1067 Pmsg2(0, _("Could not create FileSet record \"%s\". ERR=%s\n"),
1068 fsr->FileSet, db_strerror(db));
1072 Pmsg1(000, _("Created FileSet record \"%s\"\n"), fsr->FileSet);
1079 * Simulate the two calls on the database to create
1080 * the Job record and to update it when the Job actually
1083 static JCR *create_job_record(BDB *db, JOB_DBR *jr, SESSION_LABEL *label,
1087 struct date_time dt;
1090 jr->JobId = label->JobId;
1091 jr->JobType = label->JobType;
1092 jr->JobLevel = label->JobLevel;
1093 jr->JobStatus = JS_Created;
1094 bstrncpy(jr->Name, label->JobName, sizeof(jr->Name));
1095 bstrncpy(jr->Job, label->Job, sizeof(jr->Job));
1096 if (label->VerNum >= 11) {
1097 jr->SchedTime = btime_to_unix(label->write_btime);
1099 dt.julian_day_number = label->write_date;
1100 dt.julian_day_fraction = label->write_time;
1101 tm_decode(&dt, &tm);
1102 jr->SchedTime = mktime(&tm);
1105 jr->StartTime = jr->SchedTime;
1106 jr->JobTDate = (utime_t)jr->SchedTime;
1107 jr->VolSessionId = rec->VolSessionId;
1108 jr->VolSessionTime = rec->VolSessionTime;
1110 /* Now create a JCR as if starting the Job */
1111 mjcr = create_jcr(jr, rec, label->JobId);
1117 /* This creates the bare essentials */
1118 if (!db_create_job_record(bjcr, db, jr)) {
1119 Pmsg1(0, _("Could not create JobId record. ERR=%s\n"), db_strerror(db));
1123 /* This adds the client, StartTime, JobTDate, ... */
1124 if (!db_update_job_start_record(bjcr, db, jr)) {
1125 Pmsg1(0, _("Could not update job start record. ERR=%s\n"), db_strerror(db));
1128 Pmsg2(000, _("Created new JobId=%u record for original JobId=%u\n"), jr->JobId,
1130 mjcr->JobId = jr->JobId; /* set new JobId */
1135 * Simulate the database call that updates the Job
1136 * at Job termination time.
1138 static int update_job_record(BDB *db, JOB_DBR *jr, SESSION_LABEL *elabel,
1141 struct date_time dt;
1145 mjcr = get_jcr_by_session(rec->VolSessionId, rec->VolSessionTime);
1147 Pmsg2(000, _("Could not find SessId=%d SessTime=%d for EOS record.\n"),
1148 rec->VolSessionId, rec->VolSessionTime);
1151 if (elabel->VerNum >= 11) {
1152 jr->EndTime = btime_to_unix(elabel->write_btime);
1154 dt.julian_day_number = elabel->write_date;
1155 dt.julian_day_fraction = elabel->write_time;
1156 tm_decode(&dt, &tm);
1157 jr->EndTime = mktime(&tm);
1159 lasttime = jr->EndTime;
1160 mjcr->end_time = jr->EndTime;
1162 jr->JobId = mjcr->JobId;
1163 jr->JobStatus = elabel->JobStatus;
1164 mjcr->JobStatus = elabel->JobStatus;
1165 jr->JobFiles = elabel->JobFiles;
1166 if (jr->JobFiles > 0) { /* If we found files, force PurgedFiles */
1167 jr->PurgedFiles = 0;
1169 jr->JobBytes = elabel->JobBytes;
1170 jr->VolSessionId = rec->VolSessionId;
1171 jr->VolSessionTime = rec->VolSessionTime;
1172 jr->JobTDate = (utime_t)mjcr->start_time;
1173 jr->ClientId = mjcr->ClientId;
1180 if (!db_update_job_end_record(bjcr, db, jr)) {
1181 Pmsg2(0, _("Could not update JobId=%u record. ERR=%s\n"), jr->JobId, db_strerror(db));
1186 Pmsg3(000, _("Updated Job termination record for JobId=%u Level=%s TermStat=%c\n"),
1187 jr->JobId, job_level_to_str(mjcr->getJobLevel()), jr->JobStatus);
1190 const char *term_msg;
1191 static char term_code[70];
1192 char sdt[50], edt[50];
1193 char ec1[30], ec2[30], ec3[30];
1195 switch (mjcr->JobStatus) {
1197 term_msg = _("Backup OK");
1200 term_msg = _("Backup OK -- with warnings");
1203 case JS_ErrorTerminated:
1204 term_msg = _("*** Backup Error ***");
1207 term_msg = _("Backup Canceled");
1210 term_msg = term_code;
1211 sprintf(term_code, _("Job Termination code: %d"), mjcr->JobStatus);
1214 bstrftime(sdt, sizeof(sdt), mjcr->start_time);
1215 bstrftime(edt, sizeof(edt), mjcr->end_time);
1216 Pmsg14(000, _("%s\n"
1220 "Backup Level: %s\n"
1224 "Files Written: %s\n"
1225 "Bytes Written: %s\n"
1226 "Volume Session Id: %d\n"
1227 "Volume Session Time: %d\n"
1228 "Last Volume Bytes: %s\n"
1229 "Termination: %s\n\n"),
1234 job_level_to_str(mjcr->getJobLevel()),
1238 edit_uint64_with_commas(mjcr->JobFiles, ec1),
1239 edit_uint64_with_commas(mjcr->JobBytes, ec2),
1241 mjcr->VolSessionTime,
1242 edit_uint64_with_commas(mr.VolBytes, ec3),
1249 static int create_jobmedia_record(BDB *db, JCR *mjcr)
1252 DCR *dcr = mjcr->read_dcr;
1254 dcr->EndBlock = dev->EndBlock;
1255 dcr->EndFile = dev->EndFile;
1256 dcr->VolMediaId = dev->VolCatInfo.VolMediaId;
1258 memset(&jmr, 0, sizeof(jmr));
1259 jmr.JobId = mjcr->JobId;
1260 jmr.MediaId = mr.MediaId;
1261 jmr.FirstIndex = dcr->VolFirstIndex;
1262 jmr.LastIndex = dcr->VolLastIndex;
1263 jmr.StartFile = dcr->StartFile;
1264 jmr.EndFile = dcr->EndFile;
1265 jmr.StartBlock = dcr->StartBlock;
1266 jmr.EndBlock = dcr->EndBlock;
1273 if (!db_create_jobmedia_record(bjcr, db, &jmr)) {
1274 Pmsg1(0, _("Could not create JobMedia record. ERR=%s\n"), db_strerror(db));
1278 Pmsg2(000, _("Created JobMedia record JobId %d, MediaId %d\n"),
1279 jmr.JobId, jmr.MediaId);
1285 * Simulate the database call that updates the MD5/SHA1 record
1287 static int update_digest_record(BDB *db, char *digest, DEV_RECORD *rec, int type)
1291 mjcr = get_jcr_by_session(rec->VolSessionId, rec->VolSessionTime);
1293 if (mr.VolJobs > 0) {
1294 Pmsg2(000, _("Could not find SessId=%d SessTime=%d for MD5/SHA1 record.\n"),
1295 rec->VolSessionId, rec->VolSessionTime);
1302 if (!update_db || mjcr->FileId == 0) {
1307 if (!db_add_digest_to_file_record(bjcr, db, mjcr->FileId, digest, type)) {
1308 Pmsg1(0, _("Could not add MD5/SHA1 to File record. ERR=%s\n"), db_strerror(db));
1313 Pmsg0(000, _("Updated MD5/SHA1 record\n"));
1321 * Create a JCR as if we are really starting the job
1323 static JCR *create_jcr(JOB_DBR *jr, DEV_RECORD *rec, uint32_t JobId)
1327 * Transfer as much as possible to the Job JCR. Most important is
1328 * the JobId and the ClientId.
1330 jobjcr = new_jcr(sizeof(JCR), bscan_free_jcr);
1331 jobjcr->setJobType(jr->JobType);
1332 jobjcr->setJobLevel(jr->JobLevel);
1333 jobjcr->JobStatus = jr->JobStatus;
1334 bstrncpy(jobjcr->Job, jr->Job, sizeof(jobjcr->Job));
1335 jobjcr->JobId = JobId; /* this is JobId on tape */
1336 jobjcr->sched_time = jr->SchedTime;
1337 jobjcr->start_time = jr->StartTime;
1338 jobjcr->VolSessionId = rec->VolSessionId;
1339 jobjcr->VolSessionTime = rec->VolSessionTime;
1340 jobjcr->ClientId = jr->ClientId;
1341 jobjcr->dcr = jobjcr->read_dcr = new_dcr(jobjcr, NULL, dev, SD_READ);
1346 /* Dummies to replace askdir.c */
1347 bool dir_find_next_appendable_volume(DCR *dcr) { return 1;}
1348 bool dir_update_volume_info(DCR *dcr, bool relabel, bool update_LastWritten) { return 1; }
1349 bool dir_create_jobmedia_record(DCR *dcr, bool zero) { return 1; }
1350 bool flush_jobmedia_queue(JCR *jcr) { return true; }
1351 bool dir_ask_sysop_to_create_appendable_volume(DCR *dcr) { return 1; }
1352 bool dir_update_file_attributes(DCR *dcr, DEV_RECORD *rec) { return 1;}
1353 bool dir_send_job_status(JCR *jcr) {return 1;}
1354 int generate_job_event(JCR *jcr, const char *event) { return 1; }
1356 bool dir_ask_sysop_to_mount_volume(DCR *dcr, bool /*writing*/)
1358 DEVICE *dev = dcr->dev;
1359 Dmsg0(20, "Enter dir_ask_sysop_to_mount_volume\n");
1360 /* Close device so user can use autochanger if desired */
1361 fprintf(stderr, _("Mount Volume \"%s\" on device %s and press return when ready: "),
1362 dcr->VolumeName, dev->print_name());
1368 bool dir_get_volume_info(DCR *dcr, enum get_vol_info_rw writing)
1370 Dmsg0(100, "Fake dir_get_volume_info\n");
1371 dcr->setVolCatName(dcr->VolumeName);
1372 Dmsg2(500, "Vol=%s VolType=%d\n", dcr->getVolCatName(), dcr->VolCatInfo.VolCatType);