2 Bacula® - The Network Backup Solution
4 Copyright (C) 2001-2014 Free Software Foundation Europe e.V.
6 The main author of Bacula is Kern Sibbald, with contributions from many
7 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 Bacula® is a registered trademark of Kern Sibbald.
18 * Program to scan a Bacula Volume and compare it with
19 * the catalog and optionally synchronize the catalog
22 * Kern E. Sibbald, December 2001
28 #include "findlib/find.h"
29 #include "cats/cats.h"
30 #include "cats/sql_glue.h"
33 int generate_daemon_event(JCR *jcr, const char *event) { return 1; }
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(B_DB *db, JCR *mjcr,
40 char *fname, char *lname, int type,
41 char *ap, DEV_RECORD *rec);
42 static int create_media_record(B_DB *db, MEDIA_DBR *mr, VOLUME_LABEL *vl);
43 static bool update_media_record(B_DB *db, MEDIA_DBR *mr);
44 static int create_pool_record(B_DB *db, POOL_DBR *pr);
45 static JCR *create_job_record(B_DB *db, JOB_DBR *mr, SESSION_LABEL *label, DEV_RECORD *rec);
46 static int update_job_record(B_DB *db, JOB_DBR *mr, SESSION_LABEL *elabel,
48 static int create_client_record(B_DB *db, CLIENT_DBR *cr);
49 static int create_fileset_record(B_DB *db, FILESET_DBR *fsr);
50 static int create_jobmedia_record(B_DB *db, JCR *jcr);
51 static JCR *create_jcr(JOB_DBR *jr, DEV_RECORD *rec, uint32_t JobId);
52 static int update_digest_record(B_DB *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 int db_port = 0;
79 static const char *wd = NULL;
80 static bool update_db = false;
81 static bool update_vol_info = false;
82 static bool list_records = false;
83 static int ignored_msgs = 0;
85 static uint64_t currentVolumeSize;
86 static int last_pct = -1;
87 static bool showProgress = false;
88 static int num_jobs = 0;
89 static int num_pools = 0;
90 static int num_media = 0;
91 static int num_files = 0;
93 static CONFIG *config;
94 #define CONFIG_FILE "bacula-sd.conf"
97 char *configfile = NULL;
98 STORES *me = NULL; /* our Global resource */
99 bool forge_on = false; /* proceed inspite of I/O errors */
100 pthread_mutex_t device_release_mutex = PTHREAD_MUTEX_INITIALIZER;
101 pthread_cond_t wait_device_release = PTHREAD_COND_INITIALIZER;
108 "\nVersion: %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 if ((db = db_init_database(NULL, db_driver, db_name, db_user, db_password,
287 db_host, db_port, NULL, false, false)) == NULL) {
288 Emsg0(M_ERROR_TERM, 0, _("Could not init Bacula database\n"));
290 if (!db_open_database(NULL, db)) {
291 Emsg0(M_ERROR_TERM, 0, db_strerror(db));
293 Dmsg0(200, "Database opened\n");
295 Pmsg2(000, _("Using Database: %s, User: %s\n"), db_name, db_user);
300 printf("Records added or updated in the catalog:\n%7d Media\n%7d Pool\n%7d Job\n%7d File\n",
301 num_media, num_pools, num_jobs, num_files);
303 printf("Records would have been added or updated in the catalog:\n%7d Media\n%7d Pool\n%7d Job\n%7d File\n",
304 num_media, num_pools, num_jobs, num_files);
313 * We are at the end of reading a tape. Now, we simulate handling
314 * the end of writing a tape by wiffling through the attached
315 * jcrs creating jobmedia records.
317 static bool bscan_mount_next_read_volume(DCR *dcr)
319 DEVICE *dev = dcr->dev;
321 Dmsg1(100, "Walk attached jcrs. Volume=%s\n", dev->getVolCatName());
322 foreach_dlist(mdcr, dev->attached_dcrs) {
323 JCR *mjcr = mdcr->jcr;
324 Dmsg1(100, "========== JobId=%u ========\n", mjcr->JobId);
325 if (mjcr->JobId == 0) {
329 Pmsg1(000, _("Create JobMedia for Job %s\n"), mjcr->Job);
331 mdcr->StartBlock = dcr->StartBlock;
332 mdcr->StartFile = dcr->StartFile;
333 mdcr->EndBlock = dcr->EndBlock;
334 mdcr->EndFile = dcr->EndFile;
335 mdcr->VolMediaId = dcr->VolMediaId;
336 mjcr->read_dcr->VolLastIndex = dcr->VolLastIndex;
337 if( mjcr->bscan_insert_jobmedia_records ) {
338 if (!create_jobmedia_record(db, mjcr)) {
339 Pmsg2(000, _("Could not create JobMedia record for Volume=%s Job=%s\n"),
340 dev->getVolCatName(), mjcr->Job);
345 update_media_record(db, &mr);
347 /* Now let common read routine get up next tape. Note,
348 * we call mount_next... with bscan's jcr because that is where we
349 * have the Volume list, but we get attached.
351 bool stat = mount_next_read_volume(dcr);
356 fstat(dev->fd(), &sb);
357 currentVolumeSize = sb.st_size;
358 Pmsg1(000, _("First Volume Size = %s\n"),
359 edit_uint64(currentVolumeSize, ed1));
364 static void do_scan()
366 attr = new_attr(bjcr);
368 memset(&ar, 0, sizeof(ar));
369 memset(&pr, 0, sizeof(pr));
370 memset(&jr, 0, sizeof(jr));
371 memset(&cr, 0, sizeof(cr));
372 memset(&fsr, 0, sizeof(fsr));
373 memset(&fr, 0, sizeof(fr));
375 /* Detach bscan's jcr as we are not a real Job on the tape */
377 read_records(bjcr->read_dcr, record_cb, bscan_mount_next_read_volume);
380 db_write_batch_file_records(bjcr); /* used by bulk batch file insert */
386 * Returns: true if OK
389 static bool record_cb(DCR *dcr, DEV_RECORD *rec)
393 DEVICE *dev = dcr->dev;
394 JCR *bjcr = dcr->jcr;
395 DEV_BLOCK *block = dcr->block;
397 db_int64_ctx jmr_count;
399 char digest[BASE64_SIZE(CRYPTO_DIGEST_MAX_SIZE)];
401 if (rec->data_len > 0) {
402 mr.VolBytes += rec->data_len + WRITE_RECHDR_LENGTH; /* Accumulate Volume bytes */
403 if (showProgress && currentVolumeSize > 0) {
404 int pct = (mr.VolBytes * 100) / currentVolumeSize;
405 if (pct != last_pct) {
406 fprintf(stdout, _("done: %d%%\n"), pct);
414 Pmsg5(000, _("Record: SessId=%u SessTim=%u FileIndex=%d Stream=%d len=%u\n"),
415 rec->VolSessionId, rec->VolSessionTime, rec->FileIndex,
416 rec->Stream, rec->data_len);
419 * Check for Start or End of Session Record
422 if (rec->FileIndex < 0) {
423 bool save_update_db = update_db;
426 dump_label_record(dev, rec, 1);
428 switch (rec->FileIndex) {
430 Pmsg0(000, _("Volume is prelabeled. This tape cannot be scanned.\n"));
435 unser_volume_label(dev, rec);
436 /* Check Pool info */
437 bstrncpy(pr.Name, dev->VolHdr.PoolName, sizeof(pr.Name));
438 bstrncpy(pr.PoolType, dev->VolHdr.PoolType, sizeof(pr.PoolType));
440 if (db_get_pool_numvols(bjcr, db, &pr)) {
442 Pmsg1(000, _("Pool record for %s found in DB.\n"), pr.Name);
446 Pmsg1(000, _("VOL_LABEL: Pool record not found for Pool: %s\n"),
449 create_pool_record(db, &pr);
451 if (strcmp(pr.PoolType, dev->VolHdr.PoolType) != 0) {
452 Pmsg2(000, _("VOL_LABEL: PoolType mismatch. DB=%s Vol=%s\n"),
453 pr.PoolType, dev->VolHdr.PoolType);
455 } else if (verbose) {
456 Pmsg1(000, _("Pool type \"%s\" is OK.\n"), pr.PoolType);
459 /* Check Media Info */
460 memset(&mr, 0, sizeof(mr));
461 bstrncpy(mr.VolumeName, dev->VolHdr.VolumeName, sizeof(mr.VolumeName));
462 mr.PoolId = pr.PoolId;
464 if (db_get_media_record(bjcr, db, &mr)) {
466 Pmsg1(000, _("Media record for %s found in DB.\n"), mr.VolumeName);
468 /* Clear out some volume statistics that will be updated */
469 mr.VolJobs = mr.VolFiles = mr.VolBlocks = 0;
470 mr.VolBytes = rec->data_len + 20;
473 Pmsg1(000, _("VOL_LABEL: Media record not found for Volume: %s\n"),
476 bstrncpy(mr.MediaType, dev->VolHdr.MediaType, sizeof(mr.MediaType));
477 create_media_record(db, &mr, &dev->VolHdr);
479 if (strcmp(mr.MediaType, dev->VolHdr.MediaType) != 0) {
480 Pmsg2(000, _("VOL_LABEL: MediaType mismatch. DB=%s Vol=%s\n"),
481 mr.MediaType, dev->VolHdr.MediaType);
482 return true; /* ignore error */
483 } else if (verbose) {
484 Pmsg1(000, _("Media type \"%s\" is OK.\n"), mr.MediaType);
486 /* Reset some DCR variables */
487 foreach_dlist(dcr, dev->attached_dcrs) {
488 dcr->VolFirstIndex = dcr->FileIndex = 0;
489 dcr->StartBlock = dcr->EndBlock = 0;
490 dcr->StartFile = dcr->EndFile = 0;
494 Pmsg1(000, _("VOL_LABEL: OK for Volume: %s\n"), mr.VolumeName);
500 if (ignored_msgs > 0) {
501 Pmsg1(000, _("%d \"errors\" ignored before first Start of Session record.\n"),
505 unser_session_label(&label, rec);
506 memset(&jr, 0, sizeof(jr));
507 bstrncpy(jr.Job, label.Job, sizeof(jr.Job));
508 if (db_get_job_record(bjcr, db, &jr)) {
509 /* Job record already exists in DB */
510 update_db = false; /* don't change db in create_job_record */
512 Pmsg1(000, _("SOS_LABEL: Found Job record for JobId: %d\n"), jr.JobId);
515 /* Must create a Job record in DB */
517 Pmsg1(000, _("SOS_LABEL: Job record not found for JobId: %d\n"),
522 /* Create Client record if not already there */
523 bstrncpy(cr.Name, label.ClientName, sizeof(cr.Name));
524 create_client_record(db, &cr);
525 jr.ClientId = cr.ClientId;
527 /* process label, if Job record exists don't update db */
528 mjcr = create_job_record(db, &jr, &label, rec);
529 dcr = mjcr->read_dcr;
530 update_db = save_update_db;
532 jr.PoolId = pr.PoolId;
533 mjcr->start_time = jr.StartTime;
534 mjcr->setJobLevel(jr.JobLevel);
536 mjcr->client_name = get_pool_memory(PM_FNAME);
537 pm_strcpy(mjcr->client_name, label.ClientName);
538 mjcr->fileset_name = get_pool_memory(PM_FNAME);
539 pm_strcpy(mjcr->fileset_name, label.FileSetName);
540 bstrncpy(dcr->pool_type, label.PoolType, sizeof(dcr->pool_type));
541 bstrncpy(dcr->pool_name, label.PoolName, sizeof(dcr->pool_name));
543 /* Look for existing Job Media records for this job. If there are
544 any, no new ones need be created. This may occur if File
545 Retention has expired before Job Retention, or if the volume
546 has already been bscan'd */
547 Mmsg(sql_buffer, "SELECT count(*) from JobMedia where JobId=%d", jr.JobId);
548 db_sql_query(db, sql_buffer.c_str(), db_int64_handler, &jmr_count);
549 if( jmr_count.value > 0 ) {
550 //FIELD NAME TO BE DEFINED/CONFIRMED (maybe a struct?)
551 mjcr->bscan_insert_jobmedia_records = false;
553 mjcr->bscan_insert_jobmedia_records = true;
556 if (rec->VolSessionId != jr.VolSessionId) {
557 Pmsg3(000, _("SOS_LABEL: VolSessId mismatch for JobId=%u. DB=%d Vol=%d\n"),
559 jr.VolSessionId, rec->VolSessionId);
560 return true; /* ignore error */
562 if (rec->VolSessionTime != jr.VolSessionTime) {
563 Pmsg3(000, _("SOS_LABEL: VolSessTime mismatch for JobId=%u. DB=%d Vol=%d\n"),
565 jr.VolSessionTime, rec->VolSessionTime);
566 return true; /* ignore error */
568 if (jr.PoolId != pr.PoolId) {
569 Pmsg3(000, _("SOS_LABEL: PoolId mismatch for JobId=%u. DB=%d Vol=%d\n"),
571 jr.PoolId, pr.PoolId);
572 return true; /* ignore error */
577 unser_session_label(&elabel, rec);
579 /* Create FileSet record */
580 bstrncpy(fsr.FileSet, label.FileSetName, sizeof(fsr.FileSet));
581 bstrncpy(fsr.MD5, label.FileSetMD5, sizeof(fsr.MD5));
582 create_fileset_record(db, &fsr);
583 jr.FileSetId = fsr.FileSetId;
585 mjcr = get_jcr_by_session(rec->VolSessionId, rec->VolSessionTime);
587 Pmsg2(000, _("Could not find SessId=%d SessTime=%d for EOS record.\n"),
588 rec->VolSessionId, rec->VolSessionTime);
592 /* Do the final update to the Job record */
593 update_job_record(db, &jr, &elabel, rec);
595 mjcr->end_time = jr.EndTime;
596 mjcr->JobStatus = JS_Terminated;
598 /* Create JobMedia record */
599 mjcr->read_dcr->VolLastIndex = dcr->VolLastIndex;
600 if( mjcr->bscan_insert_jobmedia_records ) {
601 create_jobmedia_record(db, mjcr);
603 free_dcr(mjcr->read_dcr);
611 case EOT_LABEL: /* end of all tapes */
613 * Wiffle through all jobs still open and close
618 foreach_dlist(mdcr, dev->attached_dcrs) {
619 JCR *mjcr = mdcr->jcr;
620 if (!mjcr || mjcr->JobId == 0) {
623 jr.JobId = mjcr->JobId;
624 /* Mark Job as Error Terimined */
625 jr.JobStatus = JS_ErrorTerminated;
626 jr.JobFiles = mjcr->JobFiles;
627 jr.JobBytes = mjcr->JobBytes;
628 jr.VolSessionId = mjcr->VolSessionId;
629 jr.VolSessionTime = mjcr->VolSessionTime;
630 jr.JobTDate = (utime_t)mjcr->start_time;
631 jr.ClientId = mjcr->ClientId;
632 if (!db_update_job_end_record(bjcr, db, &jr)) {
633 Pmsg1(0, _("Could not update job record. ERR=%s\n"), db_strerror(db));
635 mjcr->read_dcr = NULL;
639 mr.VolFiles = rec->File;
640 mr.VolBlocks = rec->Block;
641 mr.VolBytes += mr.VolBlocks * WRITE_BLKHDR_LENGTH; /* approx. */
643 update_media_record(db, &mr);
644 Pmsg3(0, _("End of all Volumes. VolFiles=%u VolBlocks=%u VolBytes=%s\n"), mr.VolFiles,
645 mr.VolBlocks, edit_uint64_with_commas(mr.VolBytes, ec1));
653 mjcr = get_jcr_by_session(rec->VolSessionId, rec->VolSessionTime);
655 if (mr.VolJobs > 0) {
656 Pmsg2(000, _("Could not find Job for SessId=%d SessTime=%d record.\n"),
657 rec->VolSessionId, rec->VolSessionTime);
663 dcr = mjcr->read_dcr;
664 if (dcr->VolFirstIndex == 0) {
665 dcr->VolFirstIndex = block->FirstIndex;
668 /* File Attributes stream */
669 switch (rec->maskedStream) {
670 case STREAM_UNIX_ATTRIBUTES:
671 case STREAM_UNIX_ATTRIBUTES_EX:
673 if (!unpack_attributes_record(bjcr, rec->Stream, rec->data, rec->data_len, attr)) {
674 Emsg0(M_ERROR_TERM, 0, _("Cannot continue.\n"));
678 decode_stat(attr->attr, &attr->statp, sizeof(attr->statp), &attr->LinkFI);
679 build_attr_output_fnames(bjcr, attr);
680 print_ls_output(bjcr, attr);
682 fr.JobId = mjcr->JobId;
685 if (verbose && (num_files & 0x7FFF) == 0) {
686 char ed1[30], ed2[30], ed3[30], ed4[30];
687 Pmsg4(000, _("%s file records. At file:blk=%s:%s bytes=%s\n"),
688 edit_uint64_with_commas(num_files, ed1),
689 edit_uint64_with_commas(rec->File, ed2),
690 edit_uint64_with_commas(rec->Block, ed3),
691 edit_uint64_with_commas(mr.VolBytes, ed4));
693 create_file_attributes_record(db, mjcr, attr->fname, attr->lname,
694 attr->type, attr->attr, rec);
698 case STREAM_RESTORE_OBJECT:
700 /* Implement putting into catalog */
704 case STREAM_WIN32_DATA:
705 case STREAM_FILE_DATA:
706 case STREAM_SPARSE_DATA:
707 case STREAM_MACOS_FORK_DATA:
708 case STREAM_ENCRYPTED_FILE_DATA:
709 case STREAM_ENCRYPTED_WIN32_DATA:
710 case STREAM_ENCRYPTED_MACOS_FORK_DATA:
712 * For encrypted stream, this is an approximation.
713 * The data must be decrypted to know the correct length.
715 mjcr->JobBytes += rec->data_len;
716 if (rec->maskedStream == STREAM_SPARSE_DATA) {
717 mjcr->JobBytes -= sizeof(uint64_t);
720 free_jcr(mjcr); /* done using JCR */
723 case STREAM_GZIP_DATA:
724 case STREAM_COMPRESSED_DATA:
725 case STREAM_ENCRYPTED_FILE_GZIP_DATA:
726 case STREAM_ENCRYPTED_FILE_COMPRESSED_DATA:
727 case STREAM_ENCRYPTED_WIN32_GZIP_DATA:
728 case STREAM_ENCRYPTED_WIN32_COMPRESSED_DATA:
729 /* No correct, we should (decrypt and) expand it
732 mjcr->JobBytes += rec->data_len;
736 case STREAM_SPARSE_GZIP_DATA:
737 case STREAM_SPARSE_COMPRESSED_DATA:
738 mjcr->JobBytes += rec->data_len - sizeof(uint64_t); /* No correct, we should expand it */
739 free_jcr(mjcr); /* done using JCR */
742 /* Win32 GZIP stream */
743 case STREAM_WIN32_GZIP_DATA:
744 case STREAM_WIN32_COMPRESSED_DATA:
745 mjcr->JobBytes += rec->data_len;
746 free_jcr(mjcr); /* done using JCR */
749 case STREAM_MD5_DIGEST:
750 bin_to_base64(digest, sizeof(digest), (char *)rec->data, CRYPTO_DIGEST_MD5_SIZE, true);
752 Pmsg1(000, _("Got MD5 record: %s\n"), digest);
754 update_digest_record(db, digest, rec, CRYPTO_DIGEST_MD5);
757 case STREAM_SHA1_DIGEST:
758 bin_to_base64(digest, sizeof(digest), (char *)rec->data, CRYPTO_DIGEST_SHA1_SIZE, true);
760 Pmsg1(000, _("Got SHA1 record: %s\n"), digest);
762 update_digest_record(db, digest, rec, CRYPTO_DIGEST_SHA1);
765 case STREAM_SHA256_DIGEST:
766 bin_to_base64(digest, sizeof(digest), (char *)rec->data, CRYPTO_DIGEST_SHA256_SIZE, true);
768 Pmsg1(000, _("Got SHA256 record: %s\n"), digest);
770 update_digest_record(db, digest, rec, CRYPTO_DIGEST_SHA256);
773 case STREAM_SHA512_DIGEST:
774 bin_to_base64(digest, sizeof(digest), (char *)rec->data, CRYPTO_DIGEST_SHA512_SIZE, true);
776 Pmsg1(000, _("Got SHA512 record: %s\n"), digest);
778 update_digest_record(db, digest, rec, CRYPTO_DIGEST_SHA512);
781 case STREAM_ENCRYPTED_SESSION_DATA:
782 // TODO landonf: Investigate crypto support in bscan
784 Pmsg0(000, _("Got signed digest record\n"));
788 case STREAM_SIGNED_DIGEST:
789 // TODO landonf: Investigate crypto support in bscan
791 Pmsg0(000, _("Got signed digest record\n"));
795 case STREAM_PROGRAM_NAMES:
797 Pmsg1(000, _("Got Prog Names Stream: %s\n"), rec->data);
801 case STREAM_PROGRAM_DATA:
803 Pmsg0(000, _("Got Prog Data Stream record.\n"));
807 case STREAM_UNIX_ACCESS_ACL: /* Deprecated Standard ACL attributes on UNIX */
808 case STREAM_UNIX_DEFAULT_ACL: /* Deprecated Default ACL attributes on UNIX */
809 case STREAM_HFSPLUS_ATTRIBUTES:
810 case STREAM_ACL_AIX_TEXT:
811 case STREAM_ACL_DARWIN_ACCESS_ACL:
812 case STREAM_ACL_FREEBSD_DEFAULT_ACL:
813 case STREAM_ACL_FREEBSD_ACCESS_ACL:
814 case STREAM_ACL_HPUX_ACL_ENTRY:
815 case STREAM_ACL_IRIX_DEFAULT_ACL:
816 case STREAM_ACL_IRIX_ACCESS_ACL:
817 case STREAM_ACL_LINUX_DEFAULT_ACL:
818 case STREAM_ACL_LINUX_ACCESS_ACL:
819 case STREAM_ACL_TRU64_DEFAULT_ACL:
820 case STREAM_ACL_TRU64_DEFAULT_DIR_ACL:
821 case STREAM_ACL_TRU64_ACCESS_ACL:
822 case STREAM_ACL_SOLARIS_ACLENT:
823 case STREAM_ACL_SOLARIS_ACE:
824 case STREAM_ACL_AFS_TEXT:
825 case STREAM_ACL_AIX_AIXC:
826 case STREAM_ACL_AIX_NFS4:
827 case STREAM_ACL_FREEBSD_NFS4_ACL:
828 case STREAM_ACL_HURD_DEFAULT_ACL:
829 case STREAM_ACL_HURD_ACCESS_ACL:
830 /* Ignore Unix ACL attributes */
833 case STREAM_XATTR_HURD:
834 case STREAM_XATTR_IRIX:
835 case STREAM_XATTR_TRU64:
836 case STREAM_XATTR_AIX:
837 case STREAM_XATTR_OPENBSD:
838 case STREAM_XATTR_SOLARIS_SYS:
839 case STREAM_XATTR_SOLARIS:
840 case STREAM_XATTR_DARWIN:
841 case STREAM_XATTR_FREEBSD:
842 case STREAM_XATTR_LINUX:
843 case STREAM_XATTR_NETBSD:
844 /* Ignore Unix Extended attributes */
848 Pmsg2(0, _("Unknown stream type!!! stream=%d len=%i\n"), rec->Stream, rec->data_len);
855 * Free the Job Control Record if no one is still using it.
856 * Called from main free_jcr() routine in src/lib/jcr.c so
857 * that we can do our Director specific cleanup of the jcr.
859 static void bscan_free_jcr(JCR *jcr)
861 Dmsg0(200, "Start bscan free_jcr\n");
863 free_bsock(jcr->file_bsock);
864 free_bsock(jcr->store_bsock);
865 if (jcr->RestoreBootstrap) {
866 free(jcr->RestoreBootstrap);
873 free_dcr(jcr->read_dcr);
874 jcr->read_dcr = NULL;
876 Dmsg0(200, "End bscan free_jcr\n");
880 * We got a File Attributes record on the tape. Now, lookup the Job
881 * record, and then create the attributes record.
883 static int create_file_attributes_record(B_DB *db, JCR *mjcr,
884 char *fname, char *lname, int type,
885 char *ap, DEV_RECORD *rec)
887 DCR *dcr = mjcr->read_dcr;
890 ar.ClientId = mjcr->ClientId;
891 ar.JobId = mjcr->JobId;
892 ar.Stream = rec->Stream;
893 if (type == FT_DELETED) {
896 ar.FileIndex = rec->FileIndex;
899 if (dcr->VolFirstIndex == 0) {
900 dcr->VolFirstIndex = rec->FileIndex;
902 dcr->FileIndex = rec->FileIndex;
909 if (!db_create_file_attributes_record(bjcr, db, &ar)) {
910 Pmsg1(0, _("Could not create File Attributes record. ERR=%s\n"), db_strerror(db));
913 mjcr->FileId = ar.FileId;
916 Pmsg1(000, _("Created File record: %s\n"), fname);
922 * For each Volume we see, we create a Medium record
924 static int create_media_record(B_DB *db, MEDIA_DBR *mr, VOLUME_LABEL *vl)
929 /* We mark Vols as Archive to keep them from being re-written */
930 bstrncpy(mr->VolStatus, "Archive", sizeof(mr->VolStatus));
931 mr->VolRetention = 365 * 3600 * 24; /* 1 year */
933 if (vl->VerNum >= 11) {
934 mr->set_first_written = true; /* Save FirstWritten during update_media */
935 mr->FirstWritten = btime_to_utime(vl->write_btime);
936 mr->LabelDate = btime_to_utime(vl->label_btime);
938 /* DEPRECATED DO NOT USE */
939 dt.julian_day_number = vl->write_date;
940 dt.julian_day_fraction = vl->write_time;
942 mr->FirstWritten = mktime(&tm);
943 dt.julian_day_number = vl->label_date;
944 dt.julian_day_fraction = vl->label_time;
946 mr->LabelDate = mktime(&tm);
948 lasttime = mr->LabelDate;
949 if (mr->VolJobs == 0) {
952 if (mr->VolMounts == 0) {
960 if (!db_create_media_record(bjcr, db, mr)) {
961 Pmsg1(0, _("Could not create media record. ERR=%s\n"), db_strerror(db));
964 if (!db_update_media_record(bjcr, db, mr)) {
965 Pmsg1(0, _("Could not update media record. ERR=%s\n"), db_strerror(db));
969 Pmsg1(000, _("Created Media record for Volume: %s\n"), mr->VolumeName);
976 * Called at end of media to update it
978 static bool update_media_record(B_DB *db, MEDIA_DBR *mr)
980 if (!update_db && !update_vol_info) {
984 mr->LastWritten = lasttime;
985 if (!db_update_media_record(bjcr, db, mr)) {
986 Pmsg1(0, _("Could not update media record. ERR=%s\n"), db_strerror(db));
990 Pmsg1(000, _("Updated Media record at end of Volume: %s\n"), mr->VolumeName);
997 static int create_pool_record(B_DB *db, POOL_DBR *pr)
1001 pr->VolRetention = 355 * 3600 * 24; /* 1 year */
1006 if (!db_create_pool_record(bjcr, db, pr)) {
1007 Pmsg1(0, _("Could not create pool record. ERR=%s\n"), db_strerror(db));
1011 Pmsg1(000, _("Created Pool record for Pool: %s\n"), pr->Name);
1019 * Called from SOS to create a client for the current Job
1021 static int create_client_record(B_DB *db, CLIENT_DBR *cr)
1024 * Note, update_db can temporarily be set false while
1025 * updating the database, so we must ensure that ClientId is non-zero.
1029 if (!db_get_client_record(bjcr, db, cr)) {
1030 Pmsg1(0, _("Could not get Client record. ERR=%s\n"), db_strerror(db));
1035 if (!db_create_client_record(bjcr, db, cr)) {
1036 Pmsg1(0, _("Could not create Client record. ERR=%s\n"), db_strerror(db));
1040 Pmsg1(000, _("Created Client record for Client: %s\n"), cr->Name);
1045 static int create_fileset_record(B_DB *db, FILESET_DBR *fsr)
1051 if (fsr->MD5[0] == 0) {
1052 fsr->MD5[0] = ' '; /* Equivalent to nothing */
1055 if (db_get_fileset_record(bjcr, db, fsr)) {
1057 Pmsg1(000, _("Fileset \"%s\" already exists.\n"), fsr->FileSet);
1060 if (!db_create_fileset_record(bjcr, db, fsr)) {
1061 Pmsg2(0, _("Could not create FileSet record \"%s\". ERR=%s\n"),
1062 fsr->FileSet, db_strerror(db));
1066 Pmsg1(000, _("Created FileSet record \"%s\"\n"), fsr->FileSet);
1073 * Simulate the two calls on the database to create
1074 * the Job record and to update it when the Job actually
1077 static JCR *create_job_record(B_DB *db, JOB_DBR *jr, SESSION_LABEL *label,
1081 struct date_time dt;
1084 jr->JobId = label->JobId;
1085 jr->JobType = label->JobType;
1086 jr->JobLevel = label->JobLevel;
1087 jr->JobStatus = JS_Created;
1088 bstrncpy(jr->Name, label->JobName, sizeof(jr->Name));
1089 bstrncpy(jr->Job, label->Job, sizeof(jr->Job));
1090 if (label->VerNum >= 11) {
1091 jr->SchedTime = btime_to_unix(label->write_btime);
1093 dt.julian_day_number = label->write_date;
1094 dt.julian_day_fraction = label->write_time;
1095 tm_decode(&dt, &tm);
1096 jr->SchedTime = mktime(&tm);
1099 jr->StartTime = jr->SchedTime;
1100 jr->JobTDate = (utime_t)jr->SchedTime;
1101 jr->VolSessionId = rec->VolSessionId;
1102 jr->VolSessionTime = rec->VolSessionTime;
1104 /* Now create a JCR as if starting the Job */
1105 mjcr = create_jcr(jr, rec, label->JobId);
1111 /* This creates the bare essentials */
1112 if (!db_create_job_record(bjcr, db, jr)) {
1113 Pmsg1(0, _("Could not create JobId record. ERR=%s\n"), db_strerror(db));
1117 /* This adds the client, StartTime, JobTDate, ... */
1118 if (!db_update_job_start_record(bjcr, db, jr)) {
1119 Pmsg1(0, _("Could not update job start record. ERR=%s\n"), db_strerror(db));
1122 Pmsg2(000, _("Created new JobId=%u record for original JobId=%u\n"), jr->JobId,
1124 mjcr->JobId = jr->JobId; /* set new JobId */
1129 * Simulate the database call that updates the Job
1130 * at Job termination time.
1132 static int update_job_record(B_DB *db, JOB_DBR *jr, SESSION_LABEL *elabel,
1135 struct date_time dt;
1139 mjcr = get_jcr_by_session(rec->VolSessionId, rec->VolSessionTime);
1141 Pmsg2(000, _("Could not find SessId=%d SessTime=%d for EOS record.\n"),
1142 rec->VolSessionId, rec->VolSessionTime);
1145 if (elabel->VerNum >= 11) {
1146 jr->EndTime = btime_to_unix(elabel->write_btime);
1148 dt.julian_day_number = elabel->write_date;
1149 dt.julian_day_fraction = elabel->write_time;
1150 tm_decode(&dt, &tm);
1151 jr->EndTime = mktime(&tm);
1153 lasttime = jr->EndTime;
1154 mjcr->end_time = jr->EndTime;
1156 jr->JobId = mjcr->JobId;
1157 jr->JobStatus = elabel->JobStatus;
1158 mjcr->JobStatus = elabel->JobStatus;
1159 jr->JobFiles = elabel->JobFiles;
1160 if (jr->JobFiles > 0) { /* If we found files, force PurgedFiles */
1161 jr->PurgedFiles = 0;
1163 jr->JobBytes = elabel->JobBytes;
1164 jr->VolSessionId = rec->VolSessionId;
1165 jr->VolSessionTime = rec->VolSessionTime;
1166 jr->JobTDate = (utime_t)mjcr->start_time;
1167 jr->ClientId = mjcr->ClientId;
1174 if (!db_update_job_end_record(bjcr, db, jr)) {
1175 Pmsg2(0, _("Could not update JobId=%u record. ERR=%s\n"), jr->JobId, db_strerror(db));
1180 Pmsg3(000, _("Updated Job termination record for JobId=%u Level=%s TermStat=%c\n"),
1181 jr->JobId, job_level_to_str(mjcr->getJobLevel()), jr->JobStatus);
1184 const char *term_msg;
1185 static char term_code[70];
1186 char sdt[50], edt[50];
1187 char ec1[30], ec2[30], ec3[30];
1189 switch (mjcr->JobStatus) {
1191 term_msg = _("Backup OK");
1194 term_msg = _("Backup OK -- with warnings");
1197 case JS_ErrorTerminated:
1198 term_msg = _("*** Backup Error ***");
1201 term_msg = _("Backup Canceled");
1204 term_msg = term_code;
1205 sprintf(term_code, _("Job Termination code: %d"), mjcr->JobStatus);
1208 bstrftime(sdt, sizeof(sdt), mjcr->start_time);
1209 bstrftime(edt, sizeof(edt), mjcr->end_time);
1210 Pmsg14(000, _("%s\n"
1214 "Backup Level: %s\n"
1218 "Files Written: %s\n"
1219 "Bytes Written: %s\n"
1220 "Volume Session Id: %d\n"
1221 "Volume Session Time: %d\n"
1222 "Last Volume Bytes: %s\n"
1223 "Termination: %s\n\n"),
1228 job_level_to_str(mjcr->getJobLevel()),
1232 edit_uint64_with_commas(mjcr->JobFiles, ec1),
1233 edit_uint64_with_commas(mjcr->JobBytes, ec2),
1235 mjcr->VolSessionTime,
1236 edit_uint64_with_commas(mr.VolBytes, ec3),
1243 static int create_jobmedia_record(B_DB *db, JCR *mjcr)
1246 DCR *dcr = mjcr->read_dcr;
1248 dcr->EndBlock = dev->EndBlock;
1249 dcr->EndFile = dev->EndFile;
1250 dcr->VolMediaId = dev->VolCatInfo.VolMediaId;
1252 memset(&jmr, 0, sizeof(jmr));
1253 jmr.JobId = mjcr->JobId;
1254 jmr.MediaId = mr.MediaId;
1255 jmr.FirstIndex = dcr->VolFirstIndex;
1256 jmr.LastIndex = dcr->VolLastIndex;
1257 jmr.StartFile = dcr->StartFile;
1258 jmr.EndFile = dcr->EndFile;
1259 jmr.StartBlock = dcr->StartBlock;
1260 jmr.EndBlock = dcr->EndBlock;
1267 if (!db_create_jobmedia_record(bjcr, db, &jmr)) {
1268 Pmsg1(0, _("Could not create JobMedia record. ERR=%s\n"), db_strerror(db));
1272 Pmsg2(000, _("Created JobMedia record JobId %d, MediaId %d\n"),
1273 jmr.JobId, jmr.MediaId);
1279 * Simulate the database call that updates the MD5/SHA1 record
1281 static int update_digest_record(B_DB *db, char *digest, DEV_RECORD *rec, int type)
1285 mjcr = get_jcr_by_session(rec->VolSessionId, rec->VolSessionTime);
1287 if (mr.VolJobs > 0) {
1288 Pmsg2(000, _("Could not find SessId=%d SessTime=%d for MD5/SHA1 record.\n"),
1289 rec->VolSessionId, rec->VolSessionTime);
1296 if (!update_db || mjcr->FileId == 0) {
1301 if (!db_add_digest_to_file_record(bjcr, db, mjcr->FileId, digest, type)) {
1302 Pmsg1(0, _("Could not add MD5/SHA1 to File record. ERR=%s\n"), db_strerror(db));
1307 Pmsg0(000, _("Updated MD5/SHA1 record\n"));
1315 * Create a JCR as if we are really starting the job
1317 static JCR *create_jcr(JOB_DBR *jr, DEV_RECORD *rec, uint32_t JobId)
1321 * Transfer as much as possible to the Job JCR. Most important is
1322 * the JobId and the ClientId.
1324 jobjcr = new_jcr(sizeof(JCR), bscan_free_jcr);
1325 jobjcr->setJobType(jr->JobType);
1326 jobjcr->setJobLevel(jr->JobLevel);
1327 jobjcr->JobStatus = jr->JobStatus;
1328 bstrncpy(jobjcr->Job, jr->Job, sizeof(jobjcr->Job));
1329 jobjcr->JobId = JobId; /* this is JobId on tape */
1330 jobjcr->sched_time = jr->SchedTime;
1331 jobjcr->start_time = jr->StartTime;
1332 jobjcr->VolSessionId = rec->VolSessionId;
1333 jobjcr->VolSessionTime = rec->VolSessionTime;
1334 jobjcr->ClientId = jr->ClientId;
1335 jobjcr->dcr = jobjcr->read_dcr = new_dcr(jobjcr, NULL, dev, SD_READ);
1340 /* Dummies to replace askdir.c */
1341 bool dir_find_next_appendable_volume(DCR *dcr) { return 1;}
1342 bool dir_update_volume_info(DCR *dcr, bool relabel, bool update_LastWritten) { return 1; }
1343 bool dir_create_jobmedia_record(DCR *dcr, bool zero) { return 1; }
1344 bool dir_ask_sysop_to_create_appendable_volume(DCR *dcr) { return 1; }
1345 bool dir_update_file_attributes(DCR *dcr, DEV_RECORD *rec) { return 1;}
1346 bool dir_send_job_status(JCR *jcr) {return 1;}
1347 int generate_job_event(JCR *jcr, const char *event) { return 1; }
1349 bool dir_ask_sysop_to_mount_volume(DCR *dcr, bool /*writing*/)
1351 DEVICE *dev = dcr->dev;
1352 Dmsg0(20, "Enter dir_ask_sysop_to_mount_volume\n");
1353 /* Close device so user can use autochanger if desired */
1354 fprintf(stderr, _("Mount Volume \"%s\" on device %s and press return when ready: "),
1355 dcr->VolumeName, dev->print_name());
1361 bool dir_get_volume_info(DCR *dcr, enum get_vol_info_rw writing)
1363 Dmsg0(100, "Fake dir_get_volume_info\n");
1364 dcr->setVolCatName(dcr->VolumeName);
1366 dcr->VolCatInfo.VolCatParts = find_num_dvd_parts(dcr);
1368 Dmsg2(500, "Vol=%s num_parts=%d\n", dcr->getVolCatName(), dcr->VolCatInfo.VolCatParts);