2 Bacula® - The Network Backup Solution
4 Copyright (C) 2001-2011 Free Software Foundation Europe e.V.
6 The main author of Bacula is Kern Sibbald, with contributions from
7 many others, a complete list can be found in the file AUTHORS.
8 This program is Free Software; you can redistribute it and/or
9 modify it under the terms of version three of the GNU Affero General Public
10 License as published by the Free Software Foundation and included
13 This program is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 General Public License for more details.
18 You should have received a copy of the GNU Affero General Public License
19 along with this program; if not, write to the Free Software
20 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
23 Bacula® is a registered trademark of Kern Sibbald.
24 The licensor of Bacula is the Free Software Foundation Europe
25 (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zürich,
26 Switzerland, email:ftf@fsfeurope.org.
30 * Program to scan a Bacula Volume and compare it with
31 * the catalog and optionally synchronize the catalog
34 * Kern E. Sibbald, December 2001
40 #include "findlib/find.h"
41 #include "cats/cats.h"
42 #include "cats/sql_glue.h"
45 int generate_daemon_event(JCR *jcr, const char *event) { return 1; }
46 extern bool parse_sd_config(CONFIG *config, const char *configfile, int exit_code);
48 /* Forward referenced functions */
49 static void do_scan(void);
50 static bool record_cb(DCR *dcr, DEV_RECORD *rec);
51 static int create_file_attributes_record(B_DB *db, JCR *mjcr,
52 char *fname, char *lname, int type,
53 char *ap, DEV_RECORD *rec);
54 static int create_media_record(B_DB *db, MEDIA_DBR *mr, VOLUME_LABEL *vl);
55 static bool update_media_record(B_DB *db, MEDIA_DBR *mr);
56 static int create_pool_record(B_DB *db, POOL_DBR *pr);
57 static JCR *create_job_record(B_DB *db, JOB_DBR *mr, SESSION_LABEL *label, DEV_RECORD *rec);
58 static int update_job_record(B_DB *db, JOB_DBR *mr, SESSION_LABEL *elabel,
60 static int create_client_record(B_DB *db, CLIENT_DBR *cr);
61 static int create_fileset_record(B_DB *db, FILESET_DBR *fsr);
62 static int create_jobmedia_record(B_DB *db, JCR *jcr);
63 static JCR *create_jcr(JOB_DBR *jr, DEV_RECORD *rec, uint32_t JobId);
64 static int update_digest_record(B_DB *db, char *digest, DEV_RECORD *rec, int type);
68 static DEVICE *dev = NULL;
70 static JCR *bjcr; /* jcr for bscan */
71 static BSR *bsr = NULL;
76 static FILESET_DBR fsr;
79 static SESSION_LABEL label;
80 static SESSION_LABEL elabel;
83 static time_t lasttime = 0;
85 static const char *db_driver = "NULL";
86 static const char *db_name = "bacula";
87 static const char *db_user = "bacula";
88 static const char *db_password = "";
89 static const char *db_host = NULL;
90 static int db_port = 0;
91 static const char *wd = NULL;
92 static bool update_db = false;
93 static bool update_vol_info = false;
94 static bool list_records = false;
95 static int ignored_msgs = 0;
97 static uint64_t currentVolumeSize;
98 static int last_pct = -1;
99 static bool showProgress = false;
100 static int num_jobs = 0;
101 static int num_pools = 0;
102 static int num_media = 0;
103 static int num_files = 0;
105 static CONFIG *config;
106 #define CONFIG_FILE "bacula-sd.conf"
107 char *configfile = NULL;
108 STORES *me = NULL; /* our Global resource */
109 bool forge_on = false; /* proceed inspite of I/O errors */
110 pthread_mutex_t device_release_mutex = PTHREAD_MUTEX_INITIALIZER;
111 pthread_cond_t wait_device_release = PTHREAD_COND_INITIALIZER;
118 "\nVersion: %s (%s)\n\n"
119 "Usage: bscan [ options ] <bacula-archive>\n"
120 " -b bootstrap specify a bootstrap file\n"
121 " -c <file> specify configuration file\n"
122 " -d <nn> set debug level to <nn>\n"
123 " -dt print timestamp in debug output\n"
124 " -m update media info in database\n"
125 " -D <driver name> specify the driver database name (default NULL)\n"
126 " -n <name> specify the database name (default bacula)\n"
127 " -u <user> specify database user name (default bacula)\n"
128 " -P <password> specify database password (default none)\n"
129 " -h <host> specify database host (default NULL)\n"
130 " -t <port> specify database port (default 0)\n"
131 " -p proceed inspite of I/O errors\n"
133 " -s synchronize or store in database\n"
134 " -S show scan progress periodically\n"
136 " -V <Volumes> specify Volume names (separated by |)\n"
137 " -w <dir> specify working directory (default from conf file)\n"
138 " -? print this message\n\n"), 2001, VERSION, BDATE);
142 int main (int argc, char *argv[])
145 struct stat stat_buf;
146 char *VolumeName = NULL;
148 setlocale(LC_ALL, "");
149 bindtextdomain("bacula", LOCALEDIR);
150 textdomain("bacula");
154 my_name_is(argc, argv, "bscan");
155 init_msg(NULL, NULL);
159 while ((ch = getopt(argc, argv, "b:c:d:D:h:p:mn:pP:rsSt:u:vV:w:?")) != -1) {
165 bsr = parse_bsr(NULL, optarg);
168 case 'c': /* specify config file */
169 if (configfile != NULL) {
172 configfile = bstrdup(optarg);
179 case 'd': /* debug level */
180 if (*optarg == 't') {
181 dbg_timestamp = true;
183 debug_level = atoi(optarg);
184 if (debug_level <= 0) {
195 db_port = atoi(optarg);
199 update_vol_info = true;
211 db_password = optarg;
230 case 'V': /* Volume name */
248 Pmsg0(0, _("Wrong number of arguments: \n"));
252 if (configfile == NULL) {
253 configfile = bstrdup(CONFIG_FILE);
256 config = new_config_parser();
257 parse_sd_config(config, configfile, M_ERROR_TERM);
259 me = (STORES *)GetNextRes(R_STORAGE, NULL);
262 Emsg1(M_ERROR_TERM, 0, _("No Storage resource defined in %s. Cannot continue.\n"),
266 /* Check if -w option given, otherwise use resource for working directory */
268 working_directory = wd;
269 } else if (!me->working_directory) {
270 Emsg1(M_ERROR_TERM, 0, _("No Working Directory defined in %s. Cannot continue.\n"),
273 working_directory = me->working_directory;
276 /* Check that working directory is good */
277 if (stat(working_directory, &stat_buf) != 0) {
278 Emsg1(M_ERROR_TERM, 0, _("Working Directory: %s not found. Cannot continue.\n"),
281 if (!S_ISDIR(stat_buf.st_mode)) {
282 Emsg1(M_ERROR_TERM, 0, _("Working Directory: %s is not a directory. Cannot continue.\n"),
286 bjcr = setup_jcr("bscan", argv[0], bsr, VolumeName, 1); /* read device */
290 dev = bjcr->read_dcr->dev;
294 fstat(dev->fd(), &sb);
295 currentVolumeSize = sb.st_size;
296 Pmsg1(000, _("First Volume Size = %s\n"),
297 edit_uint64(currentVolumeSize, ed1));
300 if ((db = db_init_database(NULL, db_driver, db_name, db_user, db_password,
301 db_host, db_port, NULL, false, false)) == NULL) {
302 Emsg0(M_ERROR_TERM, 0, _("Could not init Bacula database\n"));
304 if (!db_open_database(NULL, db)) {
305 Emsg0(M_ERROR_TERM, 0, db_strerror(db));
307 Dmsg0(200, "Database opened\n");
309 Pmsg2(000, _("Using Database: %s, User: %s\n"), db_name, db_user);
314 printf("Records added or updated in the catalog:\n%7d Media\n%7d Pool\n%7d Job\n%7d File\n",
315 num_media, num_pools, num_jobs, num_files);
317 printf("Records would have been added or updated in the catalog:\n%7d Media\n%7d Pool\n%7d Job\n%7d File\n",
318 num_media, num_pools, num_jobs, num_files);
327 * We are at the end of reading a tape. Now, we simulate handling
328 * the end of writing a tape by wiffling through the attached
329 * jcrs creating jobmedia records.
331 static bool bscan_mount_next_read_volume(DCR *dcr)
333 DEVICE *dev = dcr->dev;
335 Dmsg1(100, "Walk attached jcrs. Volume=%s\n", dev->getVolCatName());
336 foreach_dlist(mdcr, dev->attached_dcrs) {
337 JCR *mjcr = mdcr->jcr;
338 Dmsg1(000, "========== JobId=%u ========\n", mjcr->JobId);
339 if (mjcr->JobId == 0) {
343 Pmsg1(000, _("Create JobMedia for Job %s\n"), mjcr->Job);
345 mdcr->StartBlock = dcr->StartBlock;
346 mdcr->StartFile = dcr->StartFile;
347 mdcr->EndBlock = dcr->EndBlock;
348 mdcr->EndFile = dcr->EndFile;
349 mdcr->VolMediaId = dcr->VolMediaId;
350 mjcr->read_dcr->VolLastIndex = dcr->VolLastIndex;
351 if( mjcr->bscan_insert_jobmedia_records ) {
352 if (!create_jobmedia_record(db, mjcr)) {
353 Pmsg2(000, _("Could not create JobMedia record for Volume=%s Job=%s\n"),
354 dev->getVolCatName(), mjcr->Job);
359 update_media_record(db, &mr);
361 /* Now let common read routine get up next tape. Note,
362 * we call mount_next... with bscan's jcr because that is where we
363 * have the Volume list, but we get attached.
365 bool stat = mount_next_read_volume(dcr);
370 fstat(dev->fd(), &sb);
371 currentVolumeSize = sb.st_size;
372 Pmsg1(000, _("First Volume Size = %s\n"),
373 edit_uint64(currentVolumeSize, ed1));
378 static void do_scan()
380 attr = new_attr(bjcr);
382 memset(&ar, 0, sizeof(ar));
383 memset(&pr, 0, sizeof(pr));
384 memset(&jr, 0, sizeof(jr));
385 memset(&cr, 0, sizeof(cr));
386 memset(&fsr, 0, sizeof(fsr));
387 memset(&fr, 0, sizeof(fr));
389 /* Detach bscan's jcr as we are not a real Job on the tape */
391 read_records(bjcr->read_dcr, record_cb, bscan_mount_next_read_volume);
394 db_write_batch_file_records(bjcr); /* used by bulk batch file insert */
400 * Returns: true if OK
403 static bool record_cb(DCR *dcr, DEV_RECORD *rec)
407 DEVICE *dev = dcr->dev;
408 JCR *bjcr = dcr->jcr;
409 DEV_BLOCK *block = dcr->block;
411 db_int64_ctx jmr_count;
413 char digest[BASE64_SIZE(CRYPTO_DIGEST_MAX_SIZE)];
415 if (rec->data_len > 0) {
416 mr.VolBytes += rec->data_len + WRITE_RECHDR_LENGTH; /* Accumulate Volume bytes */
417 if (showProgress && currentVolumeSize > 0) {
418 int pct = (mr.VolBytes * 100) / currentVolumeSize;
419 if (pct != last_pct) {
420 fprintf(stdout, _("done: %d%%\n"), pct);
428 Pmsg5(000, _("Record: SessId=%u SessTim=%u FileIndex=%d Stream=%d len=%u\n"),
429 rec->VolSessionId, rec->VolSessionTime, rec->FileIndex,
430 rec->Stream, rec->data_len);
433 * Check for Start or End of Session Record
436 if (rec->FileIndex < 0) {
437 bool save_update_db = update_db;
440 dump_label_record(dev, rec, 1);
442 switch (rec->FileIndex) {
444 Pmsg0(000, _("Volume is prelabeled. This tape cannot be scanned.\n"));
449 unser_volume_label(dev, rec);
450 /* Check Pool info */
451 bstrncpy(pr.Name, dev->VolHdr.PoolName, sizeof(pr.Name));
452 bstrncpy(pr.PoolType, dev->VolHdr.PoolType, sizeof(pr.PoolType));
454 if (db_get_pool_record(bjcr, db, &pr)) {
456 Pmsg1(000, _("Pool record for %s found in DB.\n"), pr.Name);
460 Pmsg1(000, _("VOL_LABEL: Pool record not found for Pool: %s\n"),
463 create_pool_record(db, &pr);
465 if (strcmp(pr.PoolType, dev->VolHdr.PoolType) != 0) {
466 Pmsg2(000, _("VOL_LABEL: PoolType mismatch. DB=%s Vol=%s\n"),
467 pr.PoolType, dev->VolHdr.PoolType);
469 } else if (verbose) {
470 Pmsg1(000, _("Pool type \"%s\" is OK.\n"), pr.PoolType);
473 /* Check Media Info */
474 memset(&mr, 0, sizeof(mr));
475 bstrncpy(mr.VolumeName, dev->VolHdr.VolumeName, sizeof(mr.VolumeName));
476 mr.PoolId = pr.PoolId;
478 if (db_get_media_record(bjcr, db, &mr)) {
480 Pmsg1(000, _("Media record for %s found in DB.\n"), mr.VolumeName);
482 /* Clear out some volume statistics that will be updated */
483 mr.VolJobs = mr.VolFiles = mr.VolBlocks = 0;
484 mr.VolBytes = rec->data_len + 20;
487 Pmsg1(000, _("VOL_LABEL: Media record not found for Volume: %s\n"),
490 bstrncpy(mr.MediaType, dev->VolHdr.MediaType, sizeof(mr.MediaType));
491 create_media_record(db, &mr, &dev->VolHdr);
493 if (strcmp(mr.MediaType, dev->VolHdr.MediaType) != 0) {
494 Pmsg2(000, _("VOL_LABEL: MediaType mismatch. DB=%s Vol=%s\n"),
495 mr.MediaType, dev->VolHdr.MediaType);
496 return true; /* ignore error */
497 } else if (verbose) {
498 Pmsg1(000, _("Media type \"%s\" is OK.\n"), mr.MediaType);
500 /* Reset some DCR variables */
501 foreach_dlist(dcr, dev->attached_dcrs) {
502 dcr->VolFirstIndex = dcr->FileIndex = 0;
503 dcr->StartBlock = dcr->EndBlock = 0;
504 dcr->StartFile = dcr->EndFile = 0;
508 Pmsg1(000, _("VOL_LABEL: OK for Volume: %s\n"), mr.VolumeName);
514 if (ignored_msgs > 0) {
515 Pmsg1(000, _("%d \"errors\" ignored before first Start of Session record.\n"),
519 unser_session_label(&label, rec);
520 memset(&jr, 0, sizeof(jr));
521 bstrncpy(jr.Job, label.Job, sizeof(jr.Job));
522 if (db_get_job_record(bjcr, db, &jr)) {
523 /* Job record already exists in DB */
524 update_db = false; /* don't change db in create_job_record */
526 Pmsg1(000, _("SOS_LABEL: Found Job record for JobId: %d\n"), jr.JobId);
529 /* Must create a Job record in DB */
531 Pmsg1(000, _("SOS_LABEL: Job record not found for JobId: %d\n"),
536 /* Create Client record if not already there */
537 bstrncpy(cr.Name, label.ClientName, sizeof(cr.Name));
538 create_client_record(db, &cr);
539 jr.ClientId = cr.ClientId;
541 /* process label, if Job record exists don't update db */
542 mjcr = create_job_record(db, &jr, &label, rec);
543 dcr = mjcr->read_dcr;
544 update_db = save_update_db;
546 jr.PoolId = pr.PoolId;
547 mjcr->start_time = jr.StartTime;
548 mjcr->setJobLevel(jr.JobLevel);
550 mjcr->client_name = get_pool_memory(PM_FNAME);
551 pm_strcpy(mjcr->client_name, label.ClientName);
552 mjcr->fileset_name = get_pool_memory(PM_FNAME);
553 pm_strcpy(mjcr->fileset_name, label.FileSetName);
554 bstrncpy(dcr->pool_type, label.PoolType, sizeof(dcr->pool_type));
555 bstrncpy(dcr->pool_name, label.PoolName, sizeof(dcr->pool_name));
557 /* Look for existing Job Media records for this job. If there are
558 any, no new ones need be created. This may occur if File
559 Retention has expired before Job Retention, or if the volume
560 has already been bscan'd */
561 Mmsg(sql_buffer, "SELECT count(*) from JobMedia where JobId=%d", jr.JobId);
562 db_sql_query(db, sql_buffer.c_str(), db_int64_handler, &jmr_count);
563 if( jmr_count.value > 0 ) {
564 //FIELD NAME TO BE DEFINED/CONFIRMED (maybe a struct?)
565 mjcr->bscan_insert_jobmedia_records = false;
567 mjcr->bscan_insert_jobmedia_records = true;
570 if (rec->VolSessionId != jr.VolSessionId) {
571 Pmsg3(000, _("SOS_LABEL: VolSessId mismatch for JobId=%u. DB=%d Vol=%d\n"),
573 jr.VolSessionId, rec->VolSessionId);
574 return true; /* ignore error */
576 if (rec->VolSessionTime != jr.VolSessionTime) {
577 Pmsg3(000, _("SOS_LABEL: VolSessTime mismatch for JobId=%u. DB=%d Vol=%d\n"),
579 jr.VolSessionTime, rec->VolSessionTime);
580 return true; /* ignore error */
582 if (jr.PoolId != pr.PoolId) {
583 Pmsg3(000, _("SOS_LABEL: PoolId mismatch for JobId=%u. DB=%d Vol=%d\n"),
585 jr.PoolId, pr.PoolId);
586 return true; /* ignore error */
591 unser_session_label(&elabel, rec);
593 /* Create FileSet record */
594 bstrncpy(fsr.FileSet, label.FileSetName, sizeof(fsr.FileSet));
595 bstrncpy(fsr.MD5, label.FileSetMD5, sizeof(fsr.MD5));
596 create_fileset_record(db, &fsr);
597 jr.FileSetId = fsr.FileSetId;
599 mjcr = get_jcr_by_session(rec->VolSessionId, rec->VolSessionTime);
601 Pmsg2(000, _("Could not find SessId=%d SessTime=%d for EOS record.\n"),
602 rec->VolSessionId, rec->VolSessionTime);
606 /* Do the final update to the Job record */
607 update_job_record(db, &jr, &elabel, rec);
609 mjcr->end_time = jr.EndTime;
610 mjcr->JobStatus = JS_Terminated;
612 /* Create JobMedia record */
613 mjcr->read_dcr->VolLastIndex = dcr->VolLastIndex;
614 if( mjcr->bscan_insert_jobmedia_records ) {
615 create_jobmedia_record(db, mjcr);
617 free_dcr(mjcr->read_dcr);
625 case EOT_LABEL: /* end of all tapes */
627 * Wiffle through all jobs still open and close
632 foreach_dlist(mdcr, dev->attached_dcrs) {
633 JCR *mjcr = mdcr->jcr;
634 if (!mjcr || mjcr->JobId == 0) {
637 jr.JobId = mjcr->JobId;
638 /* Mark Job as Error Terimined */
639 jr.JobStatus = JS_ErrorTerminated;
640 jr.JobFiles = mjcr->JobFiles;
641 jr.JobBytes = mjcr->JobBytes;
642 jr.VolSessionId = mjcr->VolSessionId;
643 jr.VolSessionTime = mjcr->VolSessionTime;
644 jr.JobTDate = (utime_t)mjcr->start_time;
645 jr.ClientId = mjcr->ClientId;
646 if (!db_update_job_end_record(bjcr, db, &jr)) {
647 Pmsg1(0, _("Could not update job record. ERR=%s\n"), db_strerror(db));
649 mjcr->read_dcr = NULL;
653 mr.VolFiles = rec->File;
654 mr.VolBlocks = rec->Block;
655 mr.VolBytes += mr.VolBlocks * WRITE_BLKHDR_LENGTH; /* approx. */
657 update_media_record(db, &mr);
658 Pmsg3(0, _("End of all Volumes. VolFiles=%u VolBlocks=%u VolBytes=%s\n"), mr.VolFiles,
659 mr.VolBlocks, edit_uint64_with_commas(mr.VolBytes, ec1));
667 mjcr = get_jcr_by_session(rec->VolSessionId, rec->VolSessionTime);
669 if (mr.VolJobs > 0) {
670 Pmsg2(000, _("Could not find Job for SessId=%d SessTime=%d record.\n"),
671 rec->VolSessionId, rec->VolSessionTime);
677 dcr = mjcr->read_dcr;
678 if (dcr->VolFirstIndex == 0) {
679 dcr->VolFirstIndex = block->FirstIndex;
682 /* File Attributes stream */
683 switch (rec->maskedStream) {
684 case STREAM_UNIX_ATTRIBUTES:
685 case STREAM_UNIX_ATTRIBUTES_EX:
687 if (!unpack_attributes_record(bjcr, rec->Stream, rec->data, rec->data_len, attr)) {
688 Emsg0(M_ERROR_TERM, 0, _("Cannot continue.\n"));
692 decode_stat(attr->attr, &attr->statp, sizeof(attr->statp), &attr->LinkFI);
693 build_attr_output_fnames(bjcr, attr);
694 print_ls_output(bjcr, attr);
696 fr.JobId = mjcr->JobId;
699 if (verbose && (num_files & 0x7FFF) == 0) {
700 char ed1[30], ed2[30], ed3[30], ed4[30];
701 Pmsg4(000, _("%s file records. At file:blk=%s:%s bytes=%s\n"),
702 edit_uint64_with_commas(num_files, ed1),
703 edit_uint64_with_commas(rec->File, ed2),
704 edit_uint64_with_commas(rec->Block, ed3),
705 edit_uint64_with_commas(mr.VolBytes, ed4));
707 create_file_attributes_record(db, mjcr, attr->fname, attr->lname,
708 attr->type, attr->attr, rec);
712 case STREAM_RESTORE_OBJECT:
714 /* Implement putting into catalog */
718 case STREAM_WIN32_DATA:
719 case STREAM_FILE_DATA:
720 case STREAM_SPARSE_DATA:
721 case STREAM_MACOS_FORK_DATA:
722 case STREAM_ENCRYPTED_FILE_DATA:
723 case STREAM_ENCRYPTED_WIN32_DATA:
724 case STREAM_ENCRYPTED_MACOS_FORK_DATA:
726 * For encrypted stream, this is an approximation.
727 * The data must be decrypted to know the correct length.
729 mjcr->JobBytes += rec->data_len;
730 if (rec->maskedStream == STREAM_SPARSE_DATA) {
731 mjcr->JobBytes -= sizeof(uint64_t);
734 free_jcr(mjcr); /* done using JCR */
737 case STREAM_GZIP_DATA:
738 case STREAM_COMPRESSED_DATA:
739 case STREAM_ENCRYPTED_FILE_GZIP_DATA:
740 case STREAM_ENCRYPTED_FILE_COMPRESSED_DATA:
741 case STREAM_ENCRYPTED_WIN32_GZIP_DATA:
742 case STREAM_ENCRYPTED_WIN32_COMPRESSED_DATA:
743 /* No correct, we should (decrypt and) expand it
746 mjcr->JobBytes += rec->data_len;
750 case STREAM_SPARSE_GZIP_DATA:
751 case STREAM_SPARSE_COMPRESSED_DATA:
752 mjcr->JobBytes += rec->data_len - sizeof(uint64_t); /* No correct, we should expand it */
753 free_jcr(mjcr); /* done using JCR */
756 /* Win32 GZIP stream */
757 case STREAM_WIN32_GZIP_DATA:
758 case STREAM_WIN32_COMPRESSED_DATA:
759 mjcr->JobBytes += rec->data_len;
760 free_jcr(mjcr); /* done using JCR */
763 case STREAM_MD5_DIGEST:
764 bin_to_base64(digest, sizeof(digest), (char *)rec->data, CRYPTO_DIGEST_MD5_SIZE, true);
766 Pmsg1(000, _("Got MD5 record: %s\n"), digest);
768 update_digest_record(db, digest, rec, CRYPTO_DIGEST_MD5);
771 case STREAM_SHA1_DIGEST:
772 bin_to_base64(digest, sizeof(digest), (char *)rec->data, CRYPTO_DIGEST_SHA1_SIZE, true);
774 Pmsg1(000, _("Got SHA1 record: %s\n"), digest);
776 update_digest_record(db, digest, rec, CRYPTO_DIGEST_SHA1);
779 case STREAM_SHA256_DIGEST:
780 bin_to_base64(digest, sizeof(digest), (char *)rec->data, CRYPTO_DIGEST_SHA256_SIZE, true);
782 Pmsg1(000, _("Got SHA256 record: %s\n"), digest);
784 update_digest_record(db, digest, rec, CRYPTO_DIGEST_SHA256);
787 case STREAM_SHA512_DIGEST:
788 bin_to_base64(digest, sizeof(digest), (char *)rec->data, CRYPTO_DIGEST_SHA512_SIZE, true);
790 Pmsg1(000, _("Got SHA512 record: %s\n"), digest);
792 update_digest_record(db, digest, rec, CRYPTO_DIGEST_SHA512);
795 case STREAM_ENCRYPTED_SESSION_DATA:
796 // TODO landonf: Investigate crypto support in bscan
798 Pmsg0(000, _("Got signed digest record\n"));
802 case STREAM_SIGNED_DIGEST:
803 // TODO landonf: Investigate crypto support in bscan
805 Pmsg0(000, _("Got signed digest record\n"));
809 case STREAM_PROGRAM_NAMES:
811 Pmsg1(000, _("Got Prog Names Stream: %s\n"), rec->data);
815 case STREAM_PROGRAM_DATA:
817 Pmsg0(000, _("Got Prog Data Stream record.\n"));
821 case STREAM_UNIX_ACCESS_ACL: /* Deprecated Standard ACL attributes on UNIX */
822 case STREAM_UNIX_DEFAULT_ACL: /* Deprecated Default ACL attributes on UNIX */
823 case STREAM_HFSPLUS_ATTRIBUTES:
824 case STREAM_ACL_AIX_TEXT:
825 case STREAM_ACL_DARWIN_ACCESS_ACL:
826 case STREAM_ACL_FREEBSD_DEFAULT_ACL:
827 case STREAM_ACL_FREEBSD_ACCESS_ACL:
828 case STREAM_ACL_HPUX_ACL_ENTRY:
829 case STREAM_ACL_IRIX_DEFAULT_ACL:
830 case STREAM_ACL_IRIX_ACCESS_ACL:
831 case STREAM_ACL_LINUX_DEFAULT_ACL:
832 case STREAM_ACL_LINUX_ACCESS_ACL:
833 case STREAM_ACL_TRU64_DEFAULT_ACL:
834 case STREAM_ACL_TRU64_DEFAULT_DIR_ACL:
835 case STREAM_ACL_TRU64_ACCESS_ACL:
836 case STREAM_ACL_SOLARIS_ACLENT:
837 case STREAM_ACL_SOLARIS_ACE:
838 case STREAM_ACL_AFS_TEXT:
839 case STREAM_ACL_AIX_AIXC:
840 case STREAM_ACL_AIX_NFS4:
841 case STREAM_ACL_FREEBSD_NFS4_ACL:
842 /* Ignore Unix ACL attributes */
845 case STREAM_XATTR_TRU64:
846 case STREAM_XATTR_AIX:
847 case STREAM_XATTR_OPENBSD:
848 case STREAM_XATTR_SOLARIS_SYS:
849 case STREAM_XATTR_SOLARIS:
850 case STREAM_XATTR_DARWIN:
851 case STREAM_XATTR_FREEBSD:
852 case STREAM_XATTR_LINUX:
853 case STREAM_XATTR_NETBSD:
854 /* Ignore Unix Extended attributes */
858 Pmsg2(0, _("Unknown stream type!!! stream=%d len=%i\n"), rec->Stream, rec->data_len);
865 * Free the Job Control Record if no one is still using it.
866 * Called from main free_jcr() routine in src/lib/jcr.c so
867 * that we can do our Director specific cleanup of the jcr.
869 static void bscan_free_jcr(JCR *jcr)
871 Dmsg0(200, "Start bscan free_jcr\n");
873 if (jcr->file_bsock) {
874 Dmsg0(200, "Close File bsock\n");
875 jcr->file_bsock->close();
877 if (jcr->store_bsock) {
878 Dmsg0(200, "Close Store bsock\n");
879 jcr->store_bsock->close();
881 if (jcr->RestoreBootstrap) {
882 free(jcr->RestoreBootstrap);
889 free_dcr(jcr->read_dcr);
890 jcr->read_dcr = NULL;
892 Dmsg0(200, "End bscan free_jcr\n");
896 * We got a File Attributes record on the tape. Now, lookup the Job
897 * record, and then create the attributes record.
899 static int create_file_attributes_record(B_DB *db, JCR *mjcr,
900 char *fname, char *lname, int type,
901 char *ap, DEV_RECORD *rec)
903 DCR *dcr = mjcr->read_dcr;
906 ar.ClientId = mjcr->ClientId;
907 ar.JobId = mjcr->JobId;
908 ar.Stream = rec->Stream;
909 if (type == FT_DELETED) {
912 ar.FileIndex = rec->FileIndex;
915 if (dcr->VolFirstIndex == 0) {
916 dcr->VolFirstIndex = rec->FileIndex;
918 dcr->FileIndex = rec->FileIndex;
925 if (!db_create_file_attributes_record(bjcr, db, &ar)) {
926 Pmsg1(0, _("Could not create File Attributes record. ERR=%s\n"), db_strerror(db));
929 mjcr->FileId = ar.FileId;
932 Pmsg1(000, _("Created File record: %s\n"), fname);
938 * For each Volume we see, we create a Medium record
940 static int create_media_record(B_DB *db, MEDIA_DBR *mr, VOLUME_LABEL *vl)
945 /* We mark Vols as Archive to keep them from being re-written */
946 bstrncpy(mr->VolStatus, "Archive", sizeof(mr->VolStatus));
947 mr->VolRetention = 365 * 3600 * 24; /* 1 year */
949 if (vl->VerNum >= 11) {
950 mr->set_first_written = true; /* Save FirstWritten during update_media */
951 mr->FirstWritten = btime_to_utime(vl->write_btime);
952 mr->LabelDate = btime_to_utime(vl->label_btime);
954 /* DEPRECATED DO NOT USE */
955 dt.julian_day_number = vl->write_date;
956 dt.julian_day_fraction = vl->write_time;
958 mr->FirstWritten = mktime(&tm);
959 dt.julian_day_number = vl->label_date;
960 dt.julian_day_fraction = vl->label_time;
962 mr->LabelDate = mktime(&tm);
964 lasttime = mr->LabelDate;
965 if (mr->VolJobs == 0) {
968 if (mr->VolMounts == 0) {
976 if (!db_create_media_record(bjcr, db, mr)) {
977 Pmsg1(0, _("Could not create media record. ERR=%s\n"), db_strerror(db));
980 if (!db_update_media_record(bjcr, db, mr)) {
981 Pmsg1(0, _("Could not update media record. ERR=%s\n"), db_strerror(db));
985 Pmsg1(000, _("Created Media record for Volume: %s\n"), mr->VolumeName);
992 * Called at end of media to update it
994 static bool update_media_record(B_DB *db, MEDIA_DBR *mr)
996 if (!update_db && !update_vol_info) {
1000 mr->LastWritten = lasttime;
1001 if (!db_update_media_record(bjcr, db, mr)) {
1002 Pmsg1(0, _("Could not update media record. ERR=%s\n"), db_strerror(db));
1006 Pmsg1(000, _("Updated Media record at end of Volume: %s\n"), mr->VolumeName);
1013 static int create_pool_record(B_DB *db, POOL_DBR *pr)
1017 pr->VolRetention = 355 * 3600 * 24; /* 1 year */
1022 if (!db_create_pool_record(bjcr, db, pr)) {
1023 Pmsg1(0, _("Could not create pool record. ERR=%s\n"), db_strerror(db));
1027 Pmsg1(000, _("Created Pool record for Pool: %s\n"), pr->Name);
1035 * Called from SOS to create a client for the current Job
1037 static int create_client_record(B_DB *db, CLIENT_DBR *cr)
1040 * Note, update_db can temporarily be set false while
1041 * updating the database, so we must ensure that ClientId is non-zero.
1045 if (!db_get_client_record(bjcr, db, cr)) {
1046 Pmsg1(0, _("Could not get Client record. ERR=%s\n"), db_strerror(db));
1051 if (!db_create_client_record(bjcr, db, cr)) {
1052 Pmsg1(0, _("Could not create Client record. ERR=%s\n"), db_strerror(db));
1056 Pmsg1(000, _("Created Client record for Client: %s\n"), cr->Name);
1061 static int create_fileset_record(B_DB *db, FILESET_DBR *fsr)
1067 if (fsr->MD5[0] == 0) {
1068 fsr->MD5[0] = ' '; /* Equivalent to nothing */
1071 if (db_get_fileset_record(bjcr, db, fsr)) {
1073 Pmsg1(000, _("Fileset \"%s\" already exists.\n"), fsr->FileSet);
1076 if (!db_create_fileset_record(bjcr, db, fsr)) {
1077 Pmsg2(0, _("Could not create FileSet record \"%s\". ERR=%s\n"),
1078 fsr->FileSet, db_strerror(db));
1082 Pmsg1(000, _("Created FileSet record \"%s\"\n"), fsr->FileSet);
1089 * Simulate the two calls on the database to create
1090 * the Job record and to update it when the Job actually
1093 static JCR *create_job_record(B_DB *db, JOB_DBR *jr, SESSION_LABEL *label,
1097 struct date_time dt;
1100 jr->JobId = label->JobId;
1101 jr->JobType = label->JobType;
1102 jr->JobLevel = label->JobLevel;
1103 jr->JobStatus = JS_Created;
1104 bstrncpy(jr->Name, label->JobName, sizeof(jr->Name));
1105 bstrncpy(jr->Job, label->Job, sizeof(jr->Job));
1106 if (label->VerNum >= 11) {
1107 jr->SchedTime = btime_to_unix(label->write_btime);
1109 dt.julian_day_number = label->write_date;
1110 dt.julian_day_fraction = label->write_time;
1111 tm_decode(&dt, &tm);
1112 jr->SchedTime = mktime(&tm);
1115 jr->StartTime = jr->SchedTime;
1116 jr->JobTDate = (utime_t)jr->SchedTime;
1117 jr->VolSessionId = rec->VolSessionId;
1118 jr->VolSessionTime = rec->VolSessionTime;
1120 /* Now create a JCR as if starting the Job */
1121 mjcr = create_jcr(jr, rec, label->JobId);
1127 /* This creates the bare essentials */
1128 if (!db_create_job_record(bjcr, db, jr)) {
1129 Pmsg1(0, _("Could not create JobId record. ERR=%s\n"), db_strerror(db));
1133 /* This adds the client, StartTime, JobTDate, ... */
1134 if (!db_update_job_start_record(bjcr, db, jr)) {
1135 Pmsg1(0, _("Could not update job start record. ERR=%s\n"), db_strerror(db));
1138 Pmsg2(000, _("Created new JobId=%u record for original JobId=%u\n"), jr->JobId,
1140 mjcr->JobId = jr->JobId; /* set new JobId */
1145 * Simulate the database call that updates the Job
1146 * at Job termination time.
1148 static int update_job_record(B_DB *db, JOB_DBR *jr, SESSION_LABEL *elabel,
1151 struct date_time dt;
1155 mjcr = get_jcr_by_session(rec->VolSessionId, rec->VolSessionTime);
1157 Pmsg2(000, _("Could not find SessId=%d SessTime=%d for EOS record.\n"),
1158 rec->VolSessionId, rec->VolSessionTime);
1161 if (elabel->VerNum >= 11) {
1162 jr->EndTime = btime_to_unix(elabel->write_btime);
1164 dt.julian_day_number = elabel->write_date;
1165 dt.julian_day_fraction = elabel->write_time;
1166 tm_decode(&dt, &tm);
1167 jr->EndTime = mktime(&tm);
1169 lasttime = jr->EndTime;
1170 mjcr->end_time = jr->EndTime;
1172 jr->JobId = mjcr->JobId;
1173 jr->JobStatus = elabel->JobStatus;
1174 mjcr->JobStatus = elabel->JobStatus;
1175 jr->JobFiles = elabel->JobFiles;
1176 if (jr->JobFiles > 0) { /* If we found files, force PurgedFiles */
1177 jr->PurgedFiles = 0;
1179 jr->JobBytes = elabel->JobBytes;
1180 jr->VolSessionId = rec->VolSessionId;
1181 jr->VolSessionTime = rec->VolSessionTime;
1182 jr->JobTDate = (utime_t)mjcr->start_time;
1183 jr->ClientId = mjcr->ClientId;
1190 if (!db_update_job_end_record(bjcr, db, jr)) {
1191 Pmsg2(0, _("Could not update JobId=%u record. ERR=%s\n"), jr->JobId, db_strerror(db));
1196 Pmsg3(000, _("Updated Job termination record for JobId=%u Level=%s TermStat=%c\n"),
1197 jr->JobId, job_level_to_str(mjcr->getJobLevel()), jr->JobStatus);
1200 const char *term_msg;
1201 static char term_code[70];
1202 char sdt[50], edt[50];
1203 char ec1[30], ec2[30], ec3[30];
1205 switch (mjcr->JobStatus) {
1207 term_msg = _("Backup OK");
1210 term_msg = _("Backup OK -- with warnings");
1213 case JS_ErrorTerminated:
1214 term_msg = _("*** Backup Error ***");
1217 term_msg = _("Backup Canceled");
1220 term_msg = term_code;
1221 sprintf(term_code, _("Job Termination code: %d"), mjcr->JobStatus);
1224 bstrftime(sdt, sizeof(sdt), mjcr->start_time);
1225 bstrftime(edt, sizeof(edt), mjcr->end_time);
1226 Pmsg14(000, _("%s\n"
1230 "Backup Level: %s\n"
1234 "Files Written: %s\n"
1235 "Bytes Written: %s\n"
1236 "Volume Session Id: %d\n"
1237 "Volume Session Time: %d\n"
1238 "Last Volume Bytes: %s\n"
1239 "Termination: %s\n\n"),
1244 job_level_to_str(mjcr->getJobLevel()),
1248 edit_uint64_with_commas(mjcr->JobFiles, ec1),
1249 edit_uint64_with_commas(mjcr->JobBytes, ec2),
1251 mjcr->VolSessionTime,
1252 edit_uint64_with_commas(mr.VolBytes, ec3),
1259 static int create_jobmedia_record(B_DB *db, JCR *mjcr)
1262 DCR *dcr = mjcr->read_dcr;
1264 dcr->EndBlock = dev->EndBlock;
1265 dcr->EndFile = dev->EndFile;
1266 dcr->VolMediaId = dev->VolCatInfo.VolMediaId;
1268 memset(&jmr, 0, sizeof(jmr));
1269 jmr.JobId = mjcr->JobId;
1270 jmr.MediaId = mr.MediaId;
1271 jmr.FirstIndex = dcr->VolFirstIndex;
1272 jmr.LastIndex = dcr->VolLastIndex;
1273 jmr.StartFile = dcr->StartFile;
1274 jmr.EndFile = dcr->EndFile;
1275 jmr.StartBlock = dcr->StartBlock;
1276 jmr.EndBlock = dcr->EndBlock;
1283 if (!db_create_jobmedia_record(bjcr, db, &jmr)) {
1284 Pmsg1(0, _("Could not create JobMedia record. ERR=%s\n"), db_strerror(db));
1288 Pmsg2(000, _("Created JobMedia record JobId %d, MediaId %d\n"),
1289 jmr.JobId, jmr.MediaId);
1295 * Simulate the database call that updates the MD5/SHA1 record
1297 static int update_digest_record(B_DB *db, char *digest, DEV_RECORD *rec, int type)
1301 mjcr = get_jcr_by_session(rec->VolSessionId, rec->VolSessionTime);
1303 if (mr.VolJobs > 0) {
1304 Pmsg2(000, _("Could not find SessId=%d SessTime=%d for MD5/SHA1 record.\n"),
1305 rec->VolSessionId, rec->VolSessionTime);
1312 if (!update_db || mjcr->FileId == 0) {
1317 if (!db_add_digest_to_file_record(bjcr, db, mjcr->FileId, digest, type)) {
1318 Pmsg1(0, _("Could not add MD5/SHA1 to File record. ERR=%s\n"), db_strerror(db));
1323 Pmsg0(000, _("Updated MD5/SHA1 record\n"));
1331 * Create a JCR as if we are really starting the job
1333 static JCR *create_jcr(JOB_DBR *jr, DEV_RECORD *rec, uint32_t JobId)
1337 * Transfer as much as possible to the Job JCR. Most important is
1338 * the JobId and the ClientId.
1340 jobjcr = new_jcr(sizeof(JCR), bscan_free_jcr);
1341 jobjcr->setJobType(jr->JobType);
1342 jobjcr->setJobLevel(jr->JobLevel);
1343 jobjcr->JobStatus = jr->JobStatus;
1344 bstrncpy(jobjcr->Job, jr->Job, sizeof(jobjcr->Job));
1345 jobjcr->JobId = JobId; /* this is JobId on tape */
1346 jobjcr->sched_time = jr->SchedTime;
1347 jobjcr->start_time = jr->StartTime;
1348 jobjcr->VolSessionId = rec->VolSessionId;
1349 jobjcr->VolSessionTime = rec->VolSessionTime;
1350 jobjcr->ClientId = jr->ClientId;
1351 jobjcr->dcr = jobjcr->read_dcr = new_dcr(jobjcr, NULL, dev);
1356 /* Dummies to replace askdir.c */
1357 bool dir_find_next_appendable_volume(DCR *dcr) { return 1;}
1358 bool dir_update_volume_info(DCR *dcr, bool relabel, bool update_LastWritten) { return 1; }
1359 bool dir_create_jobmedia_record(DCR *dcr, bool zero) { return 1; }
1360 bool dir_ask_sysop_to_create_appendable_volume(DCR *dcr) { return 1; }
1361 bool dir_update_file_attributes(DCR *dcr, DEV_RECORD *rec) { return 1;}
1362 bool dir_send_job_status(JCR *jcr) {return 1;}
1363 int generate_job_event(JCR *jcr, const char *event) { return 1; }
1365 bool dir_ask_sysop_to_mount_volume(DCR *dcr, int /*mode*/)
1367 DEVICE *dev = dcr->dev;
1368 Dmsg0(20, "Enter dir_ask_sysop_to_mount_volume\n");
1369 /* Close device so user can use autochanger if desired */
1370 fprintf(stderr, _("Mount Volume \"%s\" on device %s and press return when ready: "),
1371 dcr->VolumeName, dev->print_name());
1377 bool dir_get_volume_info(DCR *dcr, enum get_vol_info_rw writing)
1379 Dmsg0(100, "Fake dir_get_volume_info\n");
1380 dcr->setVolCatName(dcr->VolumeName);
1381 dcr->VolCatInfo.VolCatParts = find_num_dvd_parts(dcr);
1382 Dmsg2(500, "Vol=%s num_parts=%d\n", dcr->getVolCatName(), dcr->VolCatInfo.VolCatParts);