2 Bacula(R) - The Network Backup Solution
4 Copyright (C) 2000-2017 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
30 #include "findlib/find.h"
31 #include "cats/cats.h"
33 extern bool parse_sd_config(CONFIG *config, const char *configfile, int exit_code);
35 /* Forward referenced functions */
36 static void do_scan(void);
37 static bool record_cb(DCR *dcr, DEV_RECORD *rec);
38 static int create_file_attributes_record(BDB *db, JCR *mjcr,
39 char *fname, char *lname, int type,
40 char *ap, DEV_RECORD *rec);
41 static int create_media_record(BDB *db, MEDIA_DBR *mr, VOLUME_LABEL *vl);
42 static bool update_media_record(BDB *db, MEDIA_DBR *mr);
43 static int create_pool_record(BDB *db, POOL_DBR *pr);
44 static JCR *create_job_record(BDB *db, JOB_DBR *mr, SESSION_LABEL *label, DEV_RECORD *rec);
45 static int update_job_record(BDB *db, JOB_DBR *mr, SESSION_LABEL *elabel,
47 static int create_client_record(BDB *db, CLIENT_DBR *cr);
48 static int create_fileset_record(BDB *db, FILESET_DBR *fsr);
49 static int create_jobmedia_record(BDB *db, JCR *jcr);
50 static JCR *create_jcr(JOB_DBR *jr, DEV_RECORD *rec, uint32_t JobId);
51 static int update_digest_record(BDB *db, char *digest, DEV_RECORD *rec, int type);
55 static DEVICE *dev = NULL;
57 static JCR *bjcr; /* jcr for bscan */
58 static BSR *bsr = NULL;
63 static FILESET_DBR fsr;
66 static SESSION_LABEL label;
67 static SESSION_LABEL elabel;
70 static time_t lasttime = 0;
72 static const char *db_driver = "NULL";
73 static const char *db_name = "bacula";
74 static const char *db_user = "bacula";
75 static const char *db_password = "";
76 static const char *db_host = NULL;
77 static const char *db_ssl_key = NULL;
78 static const char *db_ssl_cert = NULL;
79 static const char *db_ssl_ca = NULL;
80 static const char *db_ssl_capath = NULL;
81 static const char *db_ssl_cipher = NULL;
82 static int db_port = 0;
83 static const char *wd = NULL;
84 static bool update_db = false;
85 static bool update_vol_info = false;
86 static bool list_records = false;
87 static int ignored_msgs = 0;
89 static uint64_t currentVolumeSize;
90 static int last_pct = -1;
91 static bool showProgress = false;
92 static int num_jobs = 0;
93 static int num_pools = 0;
94 static int num_media = 0;
95 static int num_files = 0;
97 static CONFIG *config;
98 #define CONFIG_FILE "bacula-sd.conf"
101 char *configfile = NULL;
107 "\n%sVersion: %s (%s)\n\n"
108 "Usage: bscan [ options ] <bacula-archive>\n"
109 " -b bootstrap specify a bootstrap file\n"
110 " -c <file> specify configuration file\n"
111 " -d <nn> set debug level to <nn>\n"
112 " -dt print timestamp in debug output\n"
113 " -m update media info in database\n"
114 " -D <driver name> specify the driver database name (default NULL)\n"
115 " -n <name> specify the database name (default bacula)\n"
116 " -u <user> specify database user name (default bacula)\n"
117 " -P <password> specify database password (default none)\n"
118 " -h <host> specify database host (default NULL)\n"
119 " -t <port> specify database port (default 0)\n"
120 " -p proceed inspite of I/O errors\n"
122 " -s synchronize or store in database\n"
123 " -S show scan progress periodically\n"
125 " -V <Volumes> specify Volume names (separated by |)\n"
126 " -w <dir> specify working directory (default from conf file)\n"
127 " -? print this message\n\n"),
128 2001, "", VERSION, BDATE);
132 int main (int argc, char *argv[])
135 struct stat stat_buf;
136 char *VolumeName = NULL;
137 BtoolsAskDirHandler askdir_handler;
139 init_askdir_handler(&askdir_handler);
140 setlocale(LC_ALL, "");
141 bindtextdomain("bacula", LOCALEDIR);
142 textdomain("bacula");
146 my_name_is(argc, argv, "bscan");
147 init_msg(NULL, NULL);
151 while ((ch = getopt(argc, argv, "b:c:d:D:h:p:mn:pP:rsSt:u:vV:w:?")) != -1) {
157 bsr = parse_bsr(NULL, optarg);
160 case 'c': /* specify config file */
161 if (configfile != NULL) {
164 configfile = bstrdup(optarg);
171 case 'd': /* debug level */
172 if (*optarg == 't') {
173 dbg_timestamp = true;
175 debug_level = atoi(optarg);
176 if (debug_level <= 0) {
187 db_port = atoi(optarg);
191 update_vol_info = true;
203 db_password = optarg;
222 case 'V': /* Volume name */
240 Pmsg0(0, _("Wrong number of arguments: \n"));
244 if (configfile == NULL) {
245 configfile = bstrdup(CONFIG_FILE);
248 config = New(CONFIG());
249 parse_sd_config(config, configfile, M_ERROR_TERM);
251 load_sd_plugins(me->plugin_directory);
253 /* Check if -w option given, otherwise use resource for working directory */
255 working_directory = wd;
256 } else if (!me->working_directory) {
257 Emsg1(M_ERROR_TERM, 0, _("No Working Directory defined in %s. Cannot continue.\n"),
260 working_directory = me->working_directory;
263 /* Check that working directory is good */
264 if (stat(working_directory, &stat_buf) != 0) {
265 Emsg1(M_ERROR_TERM, 0, _("Working Directory: %s not found. Cannot continue.\n"),
268 if (!S_ISDIR(stat_buf.st_mode)) {
269 Emsg1(M_ERROR_TERM, 0, _("Working Directory: %s is not a directory. Cannot continue.\n"),
273 bjcr = setup_jcr("bscan", argv[0], bsr, VolumeName, SD_READ);
277 dev = bjcr->read_dcr->dev;
281 fstat(dev->fd(), &sb);
282 currentVolumeSize = sb.st_size;
283 Pmsg1(000, _("First Volume Size = %s\n"),
284 edit_uint64(currentVolumeSize, ed1));
287 db = db_init_database(NULL, db_driver, db_name, db_user, db_password,
288 db_host, db_port, NULL,
289 db_ssl_key, db_ssl_cert, db_ssl_ca,
290 db_ssl_capath, db_ssl_cipher,
292 if (!db || !db_open_database(NULL, db)) {
293 Pmsg2(000, _("Could not open Catalog \"%s\", database \"%s\".\n"),
296 Jmsg(NULL, M_FATAL, 0, _("%s"), db_strerror(db));
297 Pmsg1(000, "%s", db_strerror(db));
298 db_close_database(NULL, db);
300 Jmsg(NULL, M_ERROR_TERM, 0, _("Could not open Catalog \"%s\", database \"%s\".\n"),
303 Dmsg0(200, "Database opened\n");
305 Pmsg2(000, _("Using Database: %s, User: %s\n"), db_name, db_user);
310 printf("Records added or updated in the catalog:\n%7d Media\n%7d Pool\n%7d Job\n%7d File\n",
311 num_media, num_pools, num_jobs, num_files);
313 printf("Records would have been added or updated in the catalog:\n%7d Media\n%7d Pool\n%7d Job\n%7d File\n",
314 num_media, num_pools, num_jobs, num_files);
323 * We are at the end of reading a Volume. Now, we simulate handling
324 * the end of writing a Volume by wiffling through the attached
325 * jcrs creating jobmedia records.
327 static bool bscan_mount_next_read_volume(DCR *dcr)
329 DEVICE *dev = dcr->dev;
331 Dmsg1(100, "Walk attached jcrs. Volume=%s\n", dev->getVolCatName());
332 foreach_dlist(mdcr, dev->attached_dcrs) {
333 JCR *mjcr = mdcr->jcr;
334 Dmsg1(100, "========== JobId=%u ========\n", mjcr->JobId);
335 if (mjcr->JobId == 0) {
339 Pmsg1(000, _("Create JobMedia for Job %s\n"), mjcr->Job);
341 mdcr->StartAddr = dcr->StartAddr;
342 mdcr->EndAddr = dcr->EndAddr;
343 mdcr->VolMediaId = dcr->VolMediaId;
344 mjcr->read_dcr->VolLastIndex = dcr->VolLastIndex;
345 if( mjcr->bscan_insert_jobmedia_records ) {
346 if (!create_jobmedia_record(db, mjcr)) {
347 Pmsg2(000, _("Could not create JobMedia record for Volume=%s Job=%s\n"),
348 dev->getVolCatName(), mjcr->Job);
353 update_media_record(db, &mr);
355 /* Now let common read routine get up next tape. Note,
356 * we call mount_next... with bscan's jcr because that is where we
357 * have the Volume list, but we get attached.
359 bool stat = mount_next_read_volume(dcr);
364 fstat(dev->fd(), &sb);
365 currentVolumeSize = sb.st_size;
366 Pmsg1(000, _("First Volume Size = %s\n"),
367 edit_uint64(currentVolumeSize, ed1));
372 static void do_scan()
374 attr = new_attr(bjcr);
376 memset(&ar, 0, sizeof(ar));
377 memset(&pr, 0, sizeof(pr));
378 memset(&jr, 0, sizeof(jr));
379 memset(&cr, 0, sizeof(cr));
380 memset(&fsr, 0, sizeof(fsr));
381 memset(&fr, 0, sizeof(fr));
383 /* Detach bscan's jcr as we are not a real Job on the tape */
385 read_records(bjcr->read_dcr, record_cb, bscan_mount_next_read_volume);
388 db_write_batch_file_records(bjcr); /* used by bulk batch file insert */
394 * Returns: true if OK
397 static bool record_cb(DCR *dcr, DEV_RECORD *rec)
401 DEVICE *dev = dcr->dev;
402 JCR *bjcr = dcr->jcr;
403 DEV_BLOCK *block = dcr->block;
405 db_int64_ctx jmr_count;
407 char digest[BASE64_SIZE(CRYPTO_DIGEST_MAX_SIZE)];
409 if (rec->data_len > 0) {
410 mr.VolBytes += rec->data_len + WRITE_RECHDR_LENGTH; /* Accumulate Volume bytes */
411 if (showProgress && currentVolumeSize > 0) {
412 int pct = (mr.VolBytes * 100) / currentVolumeSize;
413 if (pct != last_pct) {
414 fprintf(stdout, _("done: %d%%\n"), pct);
422 Pmsg5(000, _("Record: SessId=%u SessTim=%u FileIndex=%d Stream=%d len=%u\n"),
423 rec->VolSessionId, rec->VolSessionTime, rec->FileIndex,
424 rec->Stream, rec->data_len);
427 * Check for Start or End of Session Record
430 if (rec->FileIndex < 0) {
431 bool save_update_db = update_db;
434 dump_label_record(dev, rec, 1, false);
436 switch (rec->FileIndex) {
438 Pmsg0(000, _("Volume is prelabeled. This tape cannot be scanned.\n"));
443 unser_volume_label(dev, rec);
444 /* Check Pool info */
445 bstrncpy(pr.Name, dev->VolHdr.PoolName, sizeof(pr.Name));
446 bstrncpy(pr.PoolType, dev->VolHdr.PoolType, sizeof(pr.PoolType));
448 if (db_get_pool_numvols(bjcr, db, &pr)) {
450 Pmsg1(000, _("Pool record for %s found in DB.\n"), pr.Name);
454 Pmsg1(000, _("VOL_LABEL: Pool record not found for Pool: %s\n"),
457 create_pool_record(db, &pr);
459 if (strcmp(pr.PoolType, dev->VolHdr.PoolType) != 0) {
460 Pmsg2(000, _("VOL_LABEL: PoolType mismatch. DB=%s Vol=%s\n"),
461 pr.PoolType, dev->VolHdr.PoolType);
463 } else if (verbose) {
464 Pmsg1(000, _("Pool type \"%s\" is OK.\n"), pr.PoolType);
467 /* Check Media Info */
468 memset(&mr, 0, sizeof(mr));
469 bstrncpy(mr.VolumeName, dev->VolHdr.VolumeName, sizeof(mr.VolumeName));
470 mr.PoolId = pr.PoolId;
472 if (db_get_media_record(bjcr, db, &mr)) {
474 Pmsg1(000, _("Media record for %s found in DB.\n"), mr.VolumeName);
476 /* Clear out some volume statistics that will be updated */
477 mr.VolJobs = mr.VolFiles = mr.VolBlocks = 0;
478 mr.VolBytes = rec->data_len + 20;
481 Pmsg1(000, _("VOL_LABEL: Media record not found for Volume: %s\n"),
484 bstrncpy(mr.MediaType, dev->VolHdr.MediaType, sizeof(mr.MediaType));
485 create_media_record(db, &mr, &dev->VolHdr);
487 if (strcmp(mr.MediaType, dev->VolHdr.MediaType) != 0) {
488 Pmsg2(000, _("VOL_LABEL: MediaType mismatch. DB=%s Vol=%s\n"),
489 mr.MediaType, dev->VolHdr.MediaType);
490 return true; /* ignore error */
491 } else if (verbose) {
492 Pmsg1(000, _("Media type \"%s\" is OK.\n"), mr.MediaType);
494 /* Reset some DCR variables */
495 foreach_dlist(dcr, dev->attached_dcrs) {
496 dcr->VolFirstIndex = dcr->FileIndex = 0;
497 dcr->StartAddr = dcr->EndAddr = 0;
501 Pmsg1(000, _("VOL_LABEL: OK for Volume: %s\n"), mr.VolumeName);
507 if (ignored_msgs > 0) {
508 Pmsg1(000, _("%d \"errors\" ignored before first Start of Session record.\n"),
512 unser_session_label(&label, rec);
513 memset(&jr, 0, sizeof(jr));
514 bstrncpy(jr.Job, label.Job, sizeof(jr.Job));
515 if (db_get_job_record(bjcr, db, &jr)) {
516 /* Job record already exists in DB */
517 update_db = false; /* don't change db in create_job_record */
519 Pmsg1(000, _("SOS_LABEL: Found Job record for JobId: %d\n"), jr.JobId);
522 /* Must create a Job record in DB */
524 Pmsg1(000, _("SOS_LABEL: Job record not found for JobId: %d\n"),
529 /* Create Client record if not already there */
530 bstrncpy(cr.Name, label.ClientName, sizeof(cr.Name));
531 create_client_record(db, &cr);
532 jr.ClientId = cr.ClientId;
534 /* process label, if Job record exists don't update db */
535 mjcr = create_job_record(db, &jr, &label, rec);
536 dcr = mjcr->read_dcr;
537 update_db = save_update_db;
539 jr.PoolId = pr.PoolId;
540 mjcr->start_time = jr.StartTime;
541 mjcr->setJobLevel(jr.JobLevel);
543 mjcr->client_name = get_pool_memory(PM_FNAME);
544 pm_strcpy(mjcr->client_name, label.ClientName);
545 mjcr->fileset_name = get_pool_memory(PM_FNAME);
546 pm_strcpy(mjcr->fileset_name, label.FileSetName);
547 bstrncpy(dcr->pool_type, label.PoolType, sizeof(dcr->pool_type));
548 bstrncpy(dcr->pool_name, label.PoolName, sizeof(dcr->pool_name));
550 /* Look for existing Job Media records for this job. If there are
551 any, no new ones need be created. This may occur if File
552 Retention has expired before Job Retention, or if the volume
553 has already been bscan'd */
554 Mmsg(sql_buffer, "SELECT count(*) from JobMedia where JobId=%d", jr.JobId);
555 db_sql_query(db, sql_buffer.c_str(), db_int64_handler, &jmr_count);
556 if( jmr_count.value > 0 ) {
557 //FIELD NAME TO BE DEFINED/CONFIRMED (maybe a struct?)
558 mjcr->bscan_insert_jobmedia_records = false;
560 mjcr->bscan_insert_jobmedia_records = true;
563 if (rec->VolSessionId != jr.VolSessionId) {
564 Pmsg3(000, _("SOS_LABEL: VolSessId mismatch for JobId=%u. DB=%d Vol=%d\n"),
566 jr.VolSessionId, rec->VolSessionId);
567 return true; /* ignore error */
569 if (rec->VolSessionTime != jr.VolSessionTime) {
570 Pmsg3(000, _("SOS_LABEL: VolSessTime mismatch for JobId=%u. DB=%d Vol=%d\n"),
572 jr.VolSessionTime, rec->VolSessionTime);
573 return true; /* ignore error */
575 if (jr.PoolId != pr.PoolId) {
576 Pmsg3(000, _("SOS_LABEL: PoolId mismatch for JobId=%u. DB=%d Vol=%d\n"),
578 jr.PoolId, pr.PoolId);
579 return true; /* ignore error */
584 unser_session_label(&elabel, rec);
586 /* Create FileSet record */
587 bstrncpy(fsr.FileSet, label.FileSetName, sizeof(fsr.FileSet));
588 bstrncpy(fsr.MD5, label.FileSetMD5, sizeof(fsr.MD5));
589 create_fileset_record(db, &fsr);
590 jr.FileSetId = fsr.FileSetId;
592 mjcr = get_jcr_by_session(rec->VolSessionId, rec->VolSessionTime);
594 Pmsg2(000, _("Could not find SessId=%d SessTime=%d for EOS record.\n"),
595 rec->VolSessionId, rec->VolSessionTime);
599 /* Do the final update to the Job record */
600 update_job_record(db, &jr, &elabel, rec);
602 mjcr->end_time = jr.EndTime;
603 mjcr->JobStatus = JS_Terminated;
605 /* Create JobMedia record */
606 mjcr->read_dcr->VolLastIndex = dcr->VolLastIndex;
607 if( mjcr->bscan_insert_jobmedia_records ) {
608 create_jobmedia_record(db, mjcr);
610 free_dcr(mjcr->read_dcr);
618 case EOT_LABEL: /* end of all tapes */
620 * Wiffle through all jobs still open and close
625 foreach_dlist(mdcr, dev->attached_dcrs) {
626 JCR *mjcr = mdcr->jcr;
627 if (!mjcr || mjcr->JobId == 0) {
630 jr.JobId = mjcr->JobId;
631 /* Mark Job as Error Terimined */
632 jr.JobStatus = JS_ErrorTerminated;
633 jr.JobFiles = mjcr->JobFiles;
634 jr.JobBytes = mjcr->JobBytes;
635 jr.VolSessionId = mjcr->VolSessionId;
636 jr.VolSessionTime = mjcr->VolSessionTime;
637 jr.JobTDate = (utime_t)mjcr->start_time;
638 jr.ClientId = mjcr->ClientId;
639 if (!db_update_job_end_record(bjcr, db, &jr)) {
640 Pmsg1(0, _("Could not update job record. ERR=%s\n"), db_strerror(db));
642 mjcr->read_dcr = NULL;
646 mr.VolFiles = (uint32_t)(rec->Addr >> 32);
647 mr.VolBlocks = (uint32_t)rec->Addr;
648 mr.VolBytes += mr.VolBlocks * WRITE_BLKHDR_LENGTH; /* approx. */
650 update_media_record(db, &mr);
651 Pmsg3(0, _("End of all Volumes. VolFiles=%u VolBlocks=%u VolBytes=%s\n"), mr.VolFiles,
652 mr.VolBlocks, edit_uint64_with_commas(mr.VolBytes, ec1));
660 mjcr = get_jcr_by_session(rec->VolSessionId, rec->VolSessionTime);
662 if (mr.VolJobs > 0) {
663 Pmsg2(000, _("Could not find Job for SessId=%d SessTime=%d record.\n"),
664 rec->VolSessionId, rec->VolSessionTime);
670 dcr = mjcr->read_dcr;
671 if (dcr->VolFirstIndex == 0) {
672 dcr->VolFirstIndex = block->FirstIndex;
675 /* File Attributes stream */
676 switch (rec->maskedStream) {
677 case STREAM_UNIX_ATTRIBUTES:
678 case STREAM_UNIX_ATTRIBUTES_EX:
680 if (!unpack_attributes_record(bjcr, rec->Stream, rec->data, rec->data_len, attr)) {
681 Emsg0(M_ERROR_TERM, 0, _("Cannot continue.\n"));
685 decode_stat(attr->attr, &attr->statp, sizeof(attr->statp), &attr->LinkFI);
686 build_attr_output_fnames(bjcr, attr);
687 print_ls_output(bjcr, attr);
689 fr.JobId = mjcr->JobId;
692 if (verbose && (num_files & 0x7FFF) == 0) {
693 char ed1[30], ed2[30], ed3[30];
694 Pmsg3(000, _("%s file records. At addr=%s bytes=%s\n"),
695 edit_uint64_with_commas(num_files, ed1),
696 edit_uint64_with_commas(rec->Addr, ed2),
697 edit_uint64_with_commas(mr.VolBytes, ed3));
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_XACL_AIX_TEXT:
817 case STREAM_XACL_DARWIN_ACCESS:
818 case STREAM_XACL_FREEBSD_DEFAULT:
819 case STREAM_XACL_FREEBSD_ACCESS:
820 case STREAM_XACL_HPUX_ACL_ENTRY:
821 case STREAM_XACL_IRIX_DEFAULT:
822 case STREAM_XACL_IRIX_ACCESS:
823 case STREAM_XACL_LINUX_DEFAULT:
824 case STREAM_XACL_LINUX_ACCESS:
825 case STREAM_XACL_TRU64_DEFAULT:
826 case STREAM_XACL_TRU64_DEFAULT_DIR:
827 case STREAM_XACL_TRU64_ACCESS:
828 case STREAM_XACL_SOLARIS_POSIX:
829 case STREAM_XACL_SOLARIS_NFS4:
830 case STREAM_XACL_AFS_TEXT:
831 case STREAM_XACL_AIX_AIXC:
832 case STREAM_XACL_AIX_NFS4:
833 case STREAM_XACL_FREEBSD_NFS4:
834 case STREAM_XACL_HURD_DEFAULT:
835 case STREAM_XACL_HURD_ACCESS:
836 /* Ignore Unix ACL attributes */
839 case STREAM_XACL_HURD_XATTR:
840 case STREAM_XACL_IRIX_XATTR:
841 case STREAM_XACL_TRU64_XATTR:
842 case STREAM_XACL_AIX_XATTR:
843 case STREAM_XACL_OPENBSD_XATTR:
844 case STREAM_XACL_SOLARIS_SYS_XATTR:
845 case STREAM_XACL_SOLARIS_XATTR:
846 case STREAM_XACL_DARWIN_XATTR:
847 case STREAM_XACL_FREEBSD_XATTR:
848 case STREAM_XACL_LINUX_XATTR:
849 case STREAM_XACL_NETBSD_XATTR:
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;
1164 /* The JobStatus can't be 0 */
1165 if (elabel->JobStatus == 0) {
1166 Pmsg2(000, _("Could not find JobStatus for SessId=%d SessTime=%d in EOS record.\n"),
1167 rec->VolSessionId, rec->VolSessionTime);
1169 mjcr->JobStatus = jr->JobStatus =
1170 elabel->JobStatus ? elabel->JobStatus : JS_ErrorTerminated;
1172 jr->JobFiles = elabel->JobFiles;
1173 if (jr->JobFiles > 0) { /* If we found files, force PurgedFiles */
1174 jr->PurgedFiles = 0;
1176 jr->JobBytes = elabel->JobBytes;
1177 jr->VolSessionId = rec->VolSessionId;
1178 jr->VolSessionTime = rec->VolSessionTime;
1179 jr->JobTDate = (utime_t)mjcr->start_time;
1180 jr->ClientId = mjcr->ClientId;
1187 if (!db_update_job_end_record(bjcr, db, jr)) {
1188 Pmsg2(0, _("Could not update JobId=%u record. ERR=%s\n"), jr->JobId, db_strerror(db));
1193 Pmsg3(000, _("Updated Job termination record for JobId=%u Level=%s TermStat=%c\n"),
1194 jr->JobId, job_level_to_str(mjcr->getJobLevel()), jr->JobStatus);
1197 const char *term_msg;
1198 static char term_code[70];
1199 char sdt[50], edt[50];
1200 char ec1[30], ec2[30], ec3[30];
1202 switch (mjcr->JobStatus) {
1204 term_msg = _("Backup OK");
1207 term_msg = _("Backup OK -- with warnings");
1210 case JS_ErrorTerminated:
1211 term_msg = _("*** Backup Error ***");
1214 term_msg = _("Backup Canceled");
1217 term_msg = term_code;
1218 sprintf(term_code, _("Job Termination code: %d"), mjcr->JobStatus);
1221 bstrftime(sdt, sizeof(sdt), mjcr->start_time);
1222 bstrftime(edt, sizeof(edt), mjcr->end_time);
1223 Pmsg14(000, _("%s\n"
1227 "Backup Level: %s\n"
1231 "Files Written: %s\n"
1232 "Bytes Written: %s\n"
1233 "Volume Session Id: %d\n"
1234 "Volume Session Time: %d\n"
1235 "Last Volume Bytes: %s\n"
1236 "Termination: %s\n\n"),
1241 job_level_to_str(mjcr->getJobLevel()),
1245 edit_uint64_with_commas(mjcr->JobFiles, ec1),
1246 edit_uint64_with_commas(mjcr->JobBytes, ec2),
1248 mjcr->VolSessionTime,
1249 edit_uint64_with_commas(mr.VolBytes, ec3),
1256 static int create_jobmedia_record(BDB *db, JCR *mjcr)
1259 DCR *dcr = mjcr->read_dcr;
1261 dcr->EndAddr = dev->EndAddr;
1262 dcr->VolMediaId = dev->VolCatInfo.VolMediaId;
1264 memset(&jmr, 0, sizeof(jmr));
1265 jmr.JobId = mjcr->JobId;
1266 jmr.MediaId = mr.MediaId;
1267 jmr.FirstIndex = dcr->VolFirstIndex;
1268 jmr.LastIndex = dcr->VolLastIndex;
1269 jmr.StartBlock = (uint32_t)dcr->StartAddr;
1270 jmr.StartFile = (uint32_t)(dcr->StartAddr >> 32);
1271 jmr.EndBlock = (uint32_t)dcr->EndAddr;
1272 jmr.EndFile = (uint32_t)(dcr->EndAddr >> 32);
1277 if (!db_create_jobmedia_record(bjcr, db, &jmr)) {
1278 Pmsg1(0, _("Could not create JobMedia record. ERR=%s\n"), db_strerror(db));
1282 Pmsg2(000, _("Created JobMedia record JobId %d, MediaId %d\n"),
1283 jmr.JobId, jmr.MediaId);
1289 * Simulate the database call that updates the MD5/SHA1 record
1291 static int update_digest_record(BDB *db, char *digest, DEV_RECORD *rec, int type)
1295 mjcr = get_jcr_by_session(rec->VolSessionId, rec->VolSessionTime);
1297 if (mr.VolJobs > 0) {
1298 Pmsg2(000, _("Could not find SessId=%d SessTime=%d for MD5/SHA1 record.\n"),
1299 rec->VolSessionId, rec->VolSessionTime);
1306 if (!update_db || mjcr->FileId == 0) {
1311 if (!db_add_digest_to_file_record(bjcr, db, mjcr->FileId, digest, type)) {
1312 Pmsg1(0, _("Could not add MD5/SHA1 to File record. ERR=%s\n"), db_strerror(db));
1317 Pmsg0(000, _("Updated MD5/SHA1 record\n"));
1325 * Create a JCR as if we are really starting the job
1327 static JCR *create_jcr(JOB_DBR *jr, DEV_RECORD *rec, uint32_t JobId)
1331 * Transfer as much as possible to the Job JCR. Most important is
1332 * the JobId and the ClientId.
1334 jobjcr = new_jcr(sizeof(JCR), bscan_free_jcr);
1335 jobjcr->setJobType(jr->JobType);
1336 jobjcr->setJobLevel(jr->JobLevel);
1337 jobjcr->JobStatus = jr->JobStatus;
1338 bstrncpy(jobjcr->Job, jr->Job, sizeof(jobjcr->Job));
1339 jobjcr->JobId = JobId; /* this is JobId on tape */
1340 jobjcr->sched_time = jr->SchedTime;
1341 jobjcr->start_time = jr->StartTime;
1342 jobjcr->VolSessionId = rec->VolSessionId;
1343 jobjcr->VolSessionTime = rec->VolSessionTime;
1344 jobjcr->ClientId = jr->ClientId;
1345 jobjcr->dcr = jobjcr->read_dcr = new_dcr(jobjcr, NULL, dev, SD_READ);