2 Bacula® - The Network Backup Solution
4 Copyright (C) 2001-2008 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 two of the GNU 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 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
42 #include "findlib/find.h"
43 #include "cats/cats.h"
46 int generate_daemon_event(JCR *jcr, const char *event) { return 1; }
47 extern bool parse_sd_config(CONFIG *config, const char *configfile, int exit_code);
49 /* Forward referenced functions */
50 static void do_scan(void);
51 static bool record_cb(DCR *dcr, DEV_RECORD *rec);
52 static int create_file_attributes_record(B_DB *db, JCR *mjcr,
53 char *fname, char *lname, int type,
54 char *ap, DEV_RECORD *rec);
55 static int create_media_record(B_DB *db, MEDIA_DBR *mr, VOLUME_LABEL *vl);
56 static bool update_media_record(B_DB *db, MEDIA_DBR *mr);
57 static int create_pool_record(B_DB *db, POOL_DBR *pr);
58 static JCR *create_job_record(B_DB *db, JOB_DBR *mr, SESSION_LABEL *label, DEV_RECORD *rec);
59 static int update_job_record(B_DB *db, JOB_DBR *mr, SESSION_LABEL *elabel,
61 static int create_client_record(B_DB *db, CLIENT_DBR *cr);
62 static int create_fileset_record(B_DB *db, FILESET_DBR *fsr);
63 static int create_jobmedia_record(B_DB *db, JCR *jcr);
64 static JCR *create_jcr(JOB_DBR *jr, DEV_RECORD *rec, uint32_t JobId);
65 static int update_digest_record(B_DB *db, char *digest, DEV_RECORD *rec, int type);
69 static DEVICE *dev = NULL;
71 static JCR *bjcr; /* jcr for bscan */
72 static BSR *bsr = NULL;
77 static FILESET_DBR fsr;
80 static SESSION_LABEL label;
81 static SESSION_LABEL elabel;
84 static time_t lasttime = 0;
86 static const char *db_driver = "NULL";
87 static const char *db_name = "bacula";
88 static const char *db_user = "bacula";
89 static const char *db_password = "";
90 static const char *db_host = NULL;
91 static int db_port = 0;
92 static const char *wd = NULL;
93 static bool update_db = false;
94 static bool update_vol_info = false;
95 static bool list_records = false;
96 static int ignored_msgs = 0;
98 static uint64_t currentVolumeSize;
99 static int last_pct = -1;
100 static bool showProgress = false;
101 static int num_jobs = 0;
102 static int num_pools = 0;
103 static int num_media = 0;
104 static int num_files = 0;
106 static CONFIG *config;
107 #define CONFIG_FILE "bacula-sd.conf"
108 char *configfile = NULL;
109 STORES *me = NULL; /* our Global resource */
110 bool forge_on = false; /* proceed inspite of I/O errors */
111 pthread_mutex_t device_release_mutex = PTHREAD_MUTEX_INITIALIZER;
112 pthread_cond_t wait_device_release = PTHREAD_COND_INITIALIZER;
119 "\nVersion: %s (%s)\n\n"
120 "Usage: bscan [ options ] <bacula-archive>\n"
121 " -b bootstrap specify a bootstrap file\n"
122 " -c <file> specify configuration file\n"
123 " -d <nn> set debug level to <nn>\n"
124 " -dt print timestamp in debug output\n"
125 " -m update media info in database\n"
126 " -D <driver name> specify the driver database name (default NULL)\n"
127 " -n <name> specify the database name (default bacula)\n"
128 " -u <user> specify database user name (default bacula)\n"
129 " -P <password> specify database password (default none)\n"
130 " -h <host> specify database host (default NULL)\n"
131 " -t <port> specify database port (default 0)\n"
132 " -p proceed inspite of I/O errors\n"
134 " -s synchronize or store in database\n"
135 " -S show scan progress periodically\n"
137 " -V <Volumes> specify Volume names (separated by |)\n"
138 " -w <dir> specify working directory (default from conf file)\n"
139 " -? print this message\n\n"), 2001, VERSION, BDATE);
143 int main (int argc, char *argv[])
146 struct stat stat_buf;
147 char *VolumeName = NULL;
149 setlocale(LC_ALL, "");
150 bindtextdomain("bacula", LOCALEDIR);
151 textdomain("bacula");
155 my_name_is(argc, argv, "bscan");
156 init_msg(NULL, NULL);
160 while ((ch = getopt(argc, argv, "b:c:d:D:h:p:mn:pP:rsSt:u:vV:w:?")) != -1) {
166 bsr = parse_bsr(NULL, optarg);
169 case 'c': /* specify config file */
170 if (configfile != NULL) {
173 configfile = bstrdup(optarg);
180 case 'd': /* debug level */
181 if (*optarg == 't') {
182 dbg_timestamp = true;
184 debug_level = atoi(optarg);
185 if (debug_level <= 0) {
196 db_port = atoi(optarg);
200 update_vol_info = true;
212 db_password = optarg;
231 case 'V': /* Volume name */
249 Pmsg0(0, _("Wrong number of arguments: \n"));
253 if (configfile == NULL) {
254 configfile = bstrdup(CONFIG_FILE);
257 config = new_config_parser();
258 parse_sd_config(config, configfile, M_ERROR_TERM);
260 me = (STORES *)GetNextRes(R_STORAGE, NULL);
263 Emsg1(M_ERROR_TERM, 0, _("No Storage resource defined in %s. Cannot continue.\n"),
267 /* Check if -w option given, otherwise use resource for working directory */
269 working_directory = wd;
270 } else if (!me->working_directory) {
271 Emsg1(M_ERROR_TERM, 0, _("No Working Directory defined in %s. Cannot continue.\n"),
274 working_directory = me->working_directory;
277 /* Check that working directory is good */
278 if (stat(working_directory, &stat_buf) != 0) {
279 Emsg1(M_ERROR_TERM, 0, _("Working Directory: %s not found. Cannot continue.\n"),
282 if (!S_ISDIR(stat_buf.st_mode)) {
283 Emsg1(M_ERROR_TERM, 0, _("Working Directory: %s is not a directory. Cannot continue.\n"),
287 bjcr = setup_jcr("bscan", argv[0], bsr, VolumeName, 1); /* read device */
291 dev = bjcr->read_dcr->dev;
295 fstat(dev->fd(), &sb);
296 currentVolumeSize = sb.st_size;
297 Pmsg1(000, _("First Volume Size = %s\n"),
298 edit_uint64(currentVolumeSize, ed1));
301 if ((db=db_init(NULL, db_driver, db_name, db_user, db_password,
302 db_host, db_port, NULL, 0)) == NULL) {
303 Emsg0(M_ERROR_TERM, 0, _("Could not init Bacula database\n"));
305 if (!db_open_database(NULL, db)) {
306 Emsg0(M_ERROR_TERM, 0, db_strerror(db));
308 Dmsg0(200, "Database opened\n");
310 Pmsg2(000, _("Using Database: %s, User: %s\n"), db_name, db_user);
315 printf("Records added or updated in the catalog:\n%7d Media\n%7d Pool\n%7d Job\n%7d File\n",
316 num_media, num_pools, num_jobs, num_files);
318 printf("Records would have been added or updated in the catalog:\n%7d Media\n%7d Pool\n%7d Job\n%7d File\n",
319 num_media, num_pools, num_jobs, num_files);
328 * We are at the end of reading a tape. Now, we simulate handling
329 * the end of writing a tape by wiffling through the attached
330 * jcrs creating jobmedia records.
332 static bool bscan_mount_next_read_volume(DCR *dcr)
334 DEVICE *dev = dcr->dev;
336 Dmsg1(100, "Walk attached jcrs. Volume=%s\n", dev->VolCatInfo.VolCatName);
337 foreach_dlist(mdcr, dev->attached_dcrs) {
338 JCR *mjcr = mdcr->jcr;
339 Dmsg1(000, "========== JobId=%u ========\n", mjcr->JobId);
340 if (mjcr->JobId == 0) {
344 Pmsg1(000, _("Create JobMedia for Job %s\n"), mjcr->Job);
346 mdcr->StartBlock = dcr->StartBlock;
347 mdcr->StartFile = dcr->StartFile;
348 mdcr->EndBlock = dcr->EndBlock;
349 mdcr->EndFile = dcr->EndFile;
350 mdcr->VolMediaId = dcr->VolMediaId;
351 mjcr->read_dcr->VolLastIndex = dcr->VolLastIndex;
352 if (!create_jobmedia_record(db, mjcr)) {
353 Pmsg2(000, _("Could not create JobMedia record for Volume=%s Job=%s\n"),
354 dev->VolCatInfo.VolCatName, mjcr->Job);
358 update_media_record(db, &mr);
360 /* Now let common read routine get up next tape. Note,
361 * we call mount_next... with bscan's jcr because that is where we
362 * have the Volume list, but we get attached.
364 bool stat = mount_next_read_volume(dcr);
369 fstat(dev->fd(), &sb);
370 currentVolumeSize = sb.st_size;
371 Pmsg1(000, _("First Volume Size = %s\n"),
372 edit_uint64(currentVolumeSize, ed1));
377 static void do_scan()
379 attr = new_attr(bjcr);
381 memset(&ar, 0, sizeof(ar));
382 memset(&pr, 0, sizeof(pr));
383 memset(&jr, 0, sizeof(jr));
384 memset(&cr, 0, sizeof(cr));
385 memset(&fsr, 0, sizeof(fsr));
386 memset(&fr, 0, sizeof(fr));
388 /* Detach bscan's jcr as we are not a real Job on the tape */
390 read_records(bjcr->read_dcr, record_cb, bscan_mount_next_read_volume);
393 db_write_batch_file_records(bjcr); /* used by bulk batch file insert */
399 * Returns: true if OK
402 static bool record_cb(DCR *dcr, DEV_RECORD *rec)
406 DEVICE *dev = dcr->dev;
407 JCR *bjcr = dcr->jcr;
408 DEV_BLOCK *block = dcr->block;
409 char digest[BASE64_SIZE(CRYPTO_DIGEST_MAX_SIZE)];
411 if (rec->data_len > 0) {
412 mr.VolBytes += rec->data_len + WRITE_RECHDR_LENGTH; /* Accumulate Volume bytes */
413 if (showProgress && currentVolumeSize > 0) {
414 int pct = (mr.VolBytes * 100) / currentVolumeSize;
415 if (pct != last_pct) {
416 fprintf(stdout, _("done: %d%%\n"), pct);
424 Pmsg5(000, _("Record: SessId=%u SessTim=%u FileIndex=%d Stream=%d len=%u\n"),
425 rec->VolSessionId, rec->VolSessionTime, rec->FileIndex,
426 rec->Stream, rec->data_len);
429 * Check for Start or End of Session Record
432 if (rec->FileIndex < 0) {
433 bool save_update_db = update_db;
436 dump_label_record(dev, rec, 1);
438 switch (rec->FileIndex) {
440 Pmsg0(000, _("Volume is prelabeled. This tape cannot be scanned.\n"));
445 unser_volume_label(dev, rec);
446 /* Check Pool info */
447 bstrncpy(pr.Name, dev->VolHdr.PoolName, sizeof(pr.Name));
448 bstrncpy(pr.PoolType, dev->VolHdr.PoolType, sizeof(pr.PoolType));
450 if (db_get_pool_record(bjcr, db, &pr)) {
452 Pmsg1(000, _("Pool record for %s found in DB.\n"), pr.Name);
456 Pmsg1(000, _("VOL_LABEL: Pool record not found for Pool: %s\n"),
459 create_pool_record(db, &pr);
461 if (strcmp(pr.PoolType, dev->VolHdr.PoolType) != 0) {
462 Pmsg2(000, _("VOL_LABEL: PoolType mismatch. DB=%s Vol=%s\n"),
463 pr.PoolType, dev->VolHdr.PoolType);
465 } else if (verbose) {
466 Pmsg1(000, _("Pool type \"%s\" is OK.\n"), pr.PoolType);
469 /* Check Media Info */
470 memset(&mr, 0, sizeof(mr));
471 bstrncpy(mr.VolumeName, dev->VolHdr.VolumeName, sizeof(mr.VolumeName));
472 mr.PoolId = pr.PoolId;
474 if (db_get_media_record(bjcr, db, &mr)) {
476 Pmsg1(000, _("Media record for %s found in DB.\n"), mr.VolumeName);
478 /* Clear out some volume statistics that will be updated */
479 mr.VolJobs = mr.VolFiles = mr.VolBlocks = 0;
480 mr.VolBytes = rec->data_len + 20;
483 Pmsg1(000, _("VOL_LABEL: Media record not found for Volume: %s\n"),
486 bstrncpy(mr.MediaType, dev->VolHdr.MediaType, sizeof(mr.MediaType));
487 create_media_record(db, &mr, &dev->VolHdr);
489 if (strcmp(mr.MediaType, dev->VolHdr.MediaType) != 0) {
490 Pmsg2(000, _("VOL_LABEL: MediaType mismatch. DB=%s Vol=%s\n"),
491 mr.MediaType, dev->VolHdr.MediaType);
492 return true; /* ignore error */
493 } else if (verbose) {
494 Pmsg1(000, _("Media type \"%s\" is OK.\n"), mr.MediaType);
496 /* Reset some DCR variables */
497 foreach_dlist(dcr, dev->attached_dcrs) {
498 dcr->VolFirstIndex = dcr->FileIndex = 0;
499 dcr->StartBlock = dcr->EndBlock = 0;
500 dcr->StartFile = dcr->EndFile = 0;
504 Pmsg1(000, _("VOL_LABEL: OK for Volume: %s\n"), mr.VolumeName);
510 if (ignored_msgs > 0) {
511 Pmsg1(000, _("%d \"errors\" ignored before first Start of Session record.\n"),
515 unser_session_label(&label, rec);
516 memset(&jr, 0, sizeof(jr));
517 bstrncpy(jr.Job, label.Job, sizeof(jr.Job));
518 if (db_get_job_record(bjcr, db, &jr)) {
519 /* Job record already exists in DB */
520 update_db = false; /* don't change db in create_job_record */
522 Pmsg1(000, _("SOS_LABEL: Found Job record for JobId: %d\n"), jr.JobId);
525 /* Must create a Job record in DB */
527 Pmsg1(000, _("SOS_LABEL: Job record not found for JobId: %d\n"),
531 /* Create Client record if not already there */
532 bstrncpy(cr.Name, label.ClientName, sizeof(cr.Name));
533 create_client_record(db, &cr);
534 jr.ClientId = cr.ClientId;
536 /* process label, if Job record exists don't update db */
537 mjcr = create_job_record(db, &jr, &label, rec);
538 dcr = mjcr->read_dcr;
539 update_db = save_update_db;
541 jr.PoolId = pr.PoolId;
542 mjcr->start_time = jr.StartTime;
543 mjcr->set_JobLevel(jr.JobLevel);
545 mjcr->client_name = get_pool_memory(PM_FNAME);
546 pm_strcpy(mjcr->client_name, label.ClientName);
547 mjcr->fileset_name = get_pool_memory(PM_FNAME);
548 pm_strcpy(mjcr->fileset_name, label.FileSetName);
549 bstrncpy(dcr->pool_type, label.PoolType, sizeof(dcr->pool_type));
550 bstrncpy(dcr->pool_name, label.PoolName, sizeof(dcr->pool_name));
552 if (rec->VolSessionId != jr.VolSessionId) {
553 Pmsg3(000, _("SOS_LABEL: VolSessId mismatch for JobId=%u. DB=%d Vol=%d\n"),
555 jr.VolSessionId, rec->VolSessionId);
556 return true; /* ignore error */
558 if (rec->VolSessionTime != jr.VolSessionTime) {
559 Pmsg3(000, _("SOS_LABEL: VolSessTime mismatch for JobId=%u. DB=%d Vol=%d\n"),
561 jr.VolSessionTime, rec->VolSessionTime);
562 return true; /* ignore error */
564 if (jr.PoolId != pr.PoolId) {
565 Pmsg3(000, _("SOS_LABEL: PoolId mismatch for JobId=%u. DB=%d Vol=%d\n"),
567 jr.PoolId, pr.PoolId);
568 return true; /* ignore error */
573 unser_session_label(&elabel, rec);
575 /* Create FileSet record */
576 bstrncpy(fsr.FileSet, label.FileSetName, sizeof(fsr.FileSet));
577 bstrncpy(fsr.MD5, label.FileSetMD5, sizeof(fsr.MD5));
578 create_fileset_record(db, &fsr);
579 jr.FileSetId = fsr.FileSetId;
581 mjcr = get_jcr_by_session(rec->VolSessionId, rec->VolSessionTime);
583 Pmsg2(000, _("Could not find SessId=%d SessTime=%d for EOS record.\n"),
584 rec->VolSessionId, rec->VolSessionTime);
588 /* Do the final update to the Job record */
589 update_job_record(db, &jr, &elabel, rec);
591 mjcr->end_time = jr.EndTime;
592 mjcr->JobStatus = JS_Terminated;
594 /* Create JobMedia record */
595 mjcr->read_dcr->VolLastIndex = dcr->VolLastIndex;
596 create_jobmedia_record(db, mjcr);
597 free_dcr(mjcr->read_dcr);
605 case EOT_LABEL: /* end of all tapes */
607 * Wiffle through all jobs still open and close
612 foreach_dlist(mdcr, dev->attached_dcrs) {
613 JCR *mjcr = mdcr->jcr;
614 if (!mjcr || mjcr->JobId == 0) {
617 jr.JobId = mjcr->JobId;
618 /* Mark Job as Error Terimined */
619 jr.JobStatus = JS_ErrorTerminated;
620 jr.JobFiles = mjcr->JobFiles;
621 jr.JobBytes = mjcr->JobBytes;
622 jr.VolSessionId = mjcr->VolSessionId;
623 jr.VolSessionTime = mjcr->VolSessionTime;
624 jr.JobTDate = (utime_t)mjcr->start_time;
625 jr.ClientId = mjcr->ClientId;
626 if (!db_update_job_end_record(bjcr, db, &jr)) {
627 Pmsg1(0, _("Could not update job record. ERR=%s\n"), db_strerror(db));
629 mjcr->read_dcr = NULL;
633 mr.VolFiles = rec->File;
634 mr.VolBlocks = rec->Block;
635 mr.VolBytes += mr.VolBlocks * WRITE_BLKHDR_LENGTH; /* approx. */
637 update_media_record(db, &mr);
638 Pmsg3(0, _("End of all Volumes. VolFiles=%u VolBlocks=%u VolBytes=%s\n"), mr.VolFiles,
639 mr.VolBlocks, edit_uint64_with_commas(mr.VolBytes, ec1));
647 mjcr = get_jcr_by_session(rec->VolSessionId, rec->VolSessionTime);
649 if (mr.VolJobs > 0) {
650 Pmsg2(000, _("Could not find Job for SessId=%d SessTime=%d record.\n"),
651 rec->VolSessionId, rec->VolSessionTime);
657 dcr = mjcr->read_dcr;
658 if (dcr->VolFirstIndex == 0) {
659 dcr->VolFirstIndex = block->FirstIndex;
662 /* File Attributes stream */
663 switch (rec->Stream) {
664 case STREAM_UNIX_ATTRIBUTES:
665 case STREAM_UNIX_ATTRIBUTES_EX:
667 if (!unpack_attributes_record(bjcr, rec->Stream, rec->data, attr)) {
668 Emsg0(M_ERROR_TERM, 0, _("Cannot continue.\n"));
671 if (attr->file_index != rec->FileIndex) {
672 Emsg2(M_ERROR_TERM, 0, _("Record header file index %ld not equal record index %ld\n"),
673 rec->FileIndex, attr->file_index);
677 decode_stat(attr->attr, &attr->statp, &attr->LinkFI);
678 build_attr_output_fnames(bjcr, attr);
679 print_ls_output(bjcr, attr);
681 fr.JobId = mjcr->JobId;
684 if (verbose && (num_files & 0x7FFF) == 0) {
685 char ed1[30], ed2[30], ed3[30], ed4[30];
686 Pmsg4(000, _("%s file records. At file:blk=%s:%s bytes=%s\n"),
687 edit_uint64_with_commas(num_files, ed1),
688 edit_uint64_with_commas(rec->File, ed2),
689 edit_uint64_with_commas(rec->Block, ed3),
690 edit_uint64_with_commas(mr.VolBytes, ed4));
692 create_file_attributes_record(db, mjcr, attr->fname, attr->lname,
693 attr->type, attr->attr, rec);
698 case STREAM_WIN32_DATA:
699 case STREAM_FILE_DATA:
700 case STREAM_SPARSE_DATA:
701 case STREAM_ENCRYPTED_FILE_DATA:
702 case STREAM_ENCRYPTED_WIN32_DATA:
703 case STREAM_ENCRYPTED_MACOS_FORK_DATA:
705 * For encrypted stream, this is an approximation.
706 * The data must be decrypted to know the correct length.
708 mjcr->JobBytes += rec->data_len;
709 if (rec->Stream == STREAM_SPARSE_DATA) {
710 mjcr->JobBytes -= sizeof(uint64_t);
713 free_jcr(mjcr); /* done using JCR */
716 case STREAM_GZIP_DATA:
717 case STREAM_ENCRYPTED_FILE_GZIP_DATA:
718 case STREAM_ENCRYPTED_WIN32_GZIP_DATA:
719 /* No correct, we should (decrypt and) expand it
722 mjcr->JobBytes += rec->data_len;
726 case STREAM_SPARSE_GZIP_DATA:
727 mjcr->JobBytes += rec->data_len - sizeof(uint64_t); /* No correct, we should expand it */
728 free_jcr(mjcr); /* done using JCR */
731 /* Win32 GZIP stream */
732 case STREAM_WIN32_GZIP_DATA:
733 mjcr->JobBytes += rec->data_len;
734 free_jcr(mjcr); /* done using JCR */
737 case STREAM_MD5_DIGEST:
738 bin_to_base64(digest, sizeof(digest), (char *)rec->data, CRYPTO_DIGEST_MD5_SIZE, true);
740 Pmsg1(000, _("Got MD5 record: %s\n"), digest);
742 update_digest_record(db, digest, rec, CRYPTO_DIGEST_MD5);
745 case STREAM_SHA1_DIGEST:
746 bin_to_base64(digest, sizeof(digest), (char *)rec->data, CRYPTO_DIGEST_SHA1_SIZE, true);
748 Pmsg1(000, _("Got SHA1 record: %s\n"), digest);
750 update_digest_record(db, digest, rec, CRYPTO_DIGEST_SHA1);
753 case STREAM_SHA256_DIGEST:
754 bin_to_base64(digest, sizeof(digest), (char *)rec->data, CRYPTO_DIGEST_SHA256_SIZE, true);
756 Pmsg1(000, _("Got SHA256 record: %s\n"), digest);
758 update_digest_record(db, digest, rec, CRYPTO_DIGEST_SHA256);
761 case STREAM_SHA512_DIGEST:
762 bin_to_base64(digest, sizeof(digest), (char *)rec->data, CRYPTO_DIGEST_SHA512_SIZE, true);
764 Pmsg1(000, _("Got SHA512 record: %s\n"), digest);
766 update_digest_record(db, digest, rec, CRYPTO_DIGEST_SHA512);
769 case STREAM_ENCRYPTED_SESSION_DATA:
770 // TODO landonf: Investigate crypto support in bscan
772 Pmsg0(000, _("Got signed digest record\n"));
776 case STREAM_SIGNED_DIGEST:
777 // TODO landonf: Investigate crypto support in bscan
779 Pmsg0(000, _("Got signed digest record\n"));
783 case STREAM_PROGRAM_NAMES:
785 Pmsg1(000, _("Got Prog Names Stream: %s\n"), rec->data);
789 case STREAM_PROGRAM_DATA:
791 Pmsg0(000, _("Got Prog Data Stream record.\n"));
795 case STREAM_UNIX_ACCESS_ACL: /* Deprecated Standard ACL attributes on UNIX */
796 case STREAM_UNIX_DEFAULT_ACL: /* Deprecated Default ACL attributes on UNIX */
797 case STREAM_ACL_AIX_TEXT:
798 case STREAM_ACL_DARWIN_ACCESS_ACL:
799 case STREAM_ACL_FREEBSD_DEFAULT_ACL:
800 case STREAM_ACL_FREEBSD_ACCESS_ACL:
801 case STREAM_ACL_HPUX_ACL_ENTRY:
802 case STREAM_ACL_IRIX_DEFAULT_ACL:
803 case STREAM_ACL_IRIX_ACCESS_ACL:
804 case STREAM_ACL_LINUX_DEFAULT_ACL:
805 case STREAM_ACL_LINUX_ACCESS_ACL:
806 case STREAM_ACL_TRU64_DEFAULT_ACL:
807 case STREAM_ACL_TRU64_DEFAULT_DIR_ACL:
808 case STREAM_ACL_TRU64_ACCESS_ACL:
809 case STREAM_ACL_SOLARIS_ACLENT:
810 case STREAM_ACL_SOLARIS_ACE:
811 /* Ignore Unix ACL attributes */
814 case STREAM_XATTR_OPENBSD:
815 case STREAM_XATTR_SOLARIS_SYS:
816 case STREAM_XATTR_SOLARIS:
817 case STREAM_XATTR_DARWIN:
818 case STREAM_XATTR_FREEBSD:
819 case STREAM_XATTR_LINUX:
820 case STREAM_XATTR_NETBSD:
821 /* Ignore Unix Extended attributes */
825 Pmsg2(0, _("Unknown stream type!!! stream=%d len=%i\n"), rec->Stream, rec->data_len);
832 * Free the Job Control Record if no one is still using it.
833 * Called from main free_jcr() routine in src/lib/jcr.c so
834 * that we can do our Director specific cleanup of the jcr.
836 static void bscan_free_jcr(JCR *jcr)
838 Dmsg0(200, "Start bscan free_jcr\n");
840 if (jcr->file_bsock) {
841 Dmsg0(200, "Close File bsock\n");
842 bnet_close(jcr->file_bsock);
844 if (jcr->store_bsock) {
845 Dmsg0(200, "Close Store bsock\n");
846 bnet_close(jcr->store_bsock);
848 if (jcr->RestoreBootstrap) {
849 free(jcr->RestoreBootstrap);
856 free_dcr(jcr->read_dcr);
857 jcr->read_dcr = NULL;
859 Dmsg0(200, "End bscan free_jcr\n");
863 * We got a File Attributes record on the tape. Now, lookup the Job
864 * record, and then create the attributes record.
866 static int create_file_attributes_record(B_DB *db, JCR *mjcr,
867 char *fname, char *lname, int type,
868 char *ap, DEV_RECORD *rec)
870 DCR *dcr = mjcr->read_dcr;
873 ar.ClientId = mjcr->ClientId;
874 ar.JobId = mjcr->JobId;
875 ar.Stream = rec->Stream;
876 if (type == FT_DELETED) {
879 ar.FileIndex = rec->FileIndex;
882 if (dcr->VolFirstIndex == 0) {
883 dcr->VolFirstIndex = rec->FileIndex;
885 dcr->FileIndex = rec->FileIndex;
892 if (!db_create_file_attributes_record(bjcr, db, &ar)) {
893 Pmsg1(0, _("Could not create File Attributes record. ERR=%s\n"), db_strerror(db));
896 mjcr->FileId = ar.FileId;
899 Pmsg1(000, _("Created File record: %s\n"), fname);
905 * For each Volume we see, we create a Medium record
907 static int create_media_record(B_DB *db, MEDIA_DBR *mr, VOLUME_LABEL *vl)
912 /* We mark Vols as Archive to keep them from being re-written */
913 bstrncpy(mr->VolStatus, "Archive", sizeof(mr->VolStatus));
914 mr->VolRetention = 365 * 3600 * 24; /* 1 year */
916 if (vl->VerNum >= 11) {
917 mr->FirstWritten = btime_to_utime(vl->write_btime);
918 mr->LabelDate = btime_to_utime(vl->label_btime);
920 /* DEPRECATED DO NOT USE */
921 dt.julian_day_number = vl->write_date;
922 dt.julian_day_fraction = vl->write_time;
924 mr->FirstWritten = mktime(&tm);
925 dt.julian_day_number = vl->label_date;
926 dt.julian_day_fraction = vl->label_time;
928 mr->LabelDate = mktime(&tm);
930 lasttime = mr->LabelDate;
931 if (mr->VolJobs == 0) {
934 if (mr->VolMounts == 0) {
942 if (!db_create_media_record(bjcr, db, mr)) {
943 Pmsg1(0, _("Could not create media record. ERR=%s\n"), db_strerror(db));
946 if (!db_update_media_record(bjcr, db, mr)) {
947 Pmsg1(0, _("Could not update media record. ERR=%s\n"), db_strerror(db));
951 Pmsg1(000, _("Created Media record for Volume: %s\n"), mr->VolumeName);
958 * Called at end of media to update it
960 static bool update_media_record(B_DB *db, MEDIA_DBR *mr)
962 if (!update_db && !update_vol_info) {
966 mr->LastWritten = lasttime;
967 if (!db_update_media_record(bjcr, db, mr)) {
968 Pmsg1(0, _("Could not update media record. ERR=%s\n"), db_strerror(db));
972 Pmsg1(000, _("Updated Media record at end of Volume: %s\n"), mr->VolumeName);
979 static int create_pool_record(B_DB *db, POOL_DBR *pr)
983 pr->VolRetention = 355 * 3600 * 24; /* 1 year */
988 if (!db_create_pool_record(bjcr, db, pr)) {
989 Pmsg1(0, _("Could not create pool record. ERR=%s\n"), db_strerror(db));
993 Pmsg1(000, _("Created Pool record for Pool: %s\n"), pr->Name);
1001 * Called from SOS to create a client for the current Job
1003 static int create_client_record(B_DB *db, CLIENT_DBR *cr)
1006 * Note, update_db can temporarily be set false while
1007 * updating the database, so we must ensure that ClientId is non-zero.
1011 if (!db_get_client_record(bjcr, db, cr)) {
1012 Pmsg1(0, _("Could not get Client record. ERR=%s\n"), db_strerror(db));
1017 if (!db_create_client_record(bjcr, db, cr)) {
1018 Pmsg1(0, _("Could not create Client record. ERR=%s\n"), db_strerror(db));
1022 Pmsg1(000, _("Created Client record for Client: %s\n"), cr->Name);
1027 static int create_fileset_record(B_DB *db, FILESET_DBR *fsr)
1033 if (fsr->MD5[0] == 0) {
1034 fsr->MD5[0] = ' '; /* Equivalent to nothing */
1037 if (db_get_fileset_record(bjcr, db, fsr)) {
1039 Pmsg1(000, _("Fileset \"%s\" already exists.\n"), fsr->FileSet);
1042 if (!db_create_fileset_record(bjcr, db, fsr)) {
1043 Pmsg2(0, _("Could not create FileSet record \"%s\". ERR=%s\n"),
1044 fsr->FileSet, db_strerror(db));
1048 Pmsg1(000, _("Created FileSet record \"%s\"\n"), fsr->FileSet);
1055 * Simulate the two calls on the database to create
1056 * the Job record and to update it when the Job actually
1059 static JCR *create_job_record(B_DB *db, JOB_DBR *jr, SESSION_LABEL *label,
1063 struct date_time dt;
1066 jr->JobId = label->JobId;
1067 jr->JobType = label->JobType;
1068 jr->JobLevel = label->JobLevel;
1069 jr->JobStatus = JS_Created;
1070 bstrncpy(jr->Name, label->JobName, sizeof(jr->Name));
1071 bstrncpy(jr->Job, label->Job, sizeof(jr->Job));
1072 if (label->VerNum >= 11) {
1073 jr->SchedTime = btime_to_unix(label->write_btime);
1075 dt.julian_day_number = label->write_date;
1076 dt.julian_day_fraction = label->write_time;
1077 tm_decode(&dt, &tm);
1078 jr->SchedTime = mktime(&tm);
1081 jr->StartTime = jr->SchedTime;
1082 jr->JobTDate = (utime_t)jr->SchedTime;
1083 jr->VolSessionId = rec->VolSessionId;
1084 jr->VolSessionTime = rec->VolSessionTime;
1086 /* Now create a JCR as if starting the Job */
1087 mjcr = create_jcr(jr, rec, label->JobId);
1093 /* This creates the bare essentials */
1094 if (!db_create_job_record(bjcr, db, jr)) {
1095 Pmsg1(0, _("Could not create JobId record. ERR=%s\n"), db_strerror(db));
1099 /* This adds the client, StartTime, JobTDate, ... */
1100 if (!db_update_job_start_record(bjcr, db, jr)) {
1101 Pmsg1(0, _("Could not update job start record. ERR=%s\n"), db_strerror(db));
1104 Pmsg2(000, _("Created new JobId=%u record for original JobId=%u\n"), jr->JobId,
1106 mjcr->JobId = jr->JobId; /* set new JobId */
1111 * Simulate the database call that updates the Job
1112 * at Job termination time.
1114 static int update_job_record(B_DB *db, JOB_DBR *jr, SESSION_LABEL *elabel,
1117 struct date_time dt;
1121 mjcr = get_jcr_by_session(rec->VolSessionId, rec->VolSessionTime);
1123 Pmsg2(000, _("Could not find SessId=%d SessTime=%d for EOS record.\n"),
1124 rec->VolSessionId, rec->VolSessionTime);
1127 if (elabel->VerNum >= 11) {
1128 jr->EndTime = btime_to_unix(elabel->write_btime);
1130 dt.julian_day_number = elabel->write_date;
1131 dt.julian_day_fraction = elabel->write_time;
1132 tm_decode(&dt, &tm);
1133 jr->EndTime = mktime(&tm);
1135 lasttime = jr->EndTime;
1136 mjcr->end_time = jr->EndTime;
1138 jr->JobId = mjcr->JobId;
1139 jr->JobStatus = elabel->JobStatus;
1140 mjcr->JobStatus = elabel->JobStatus;
1141 jr->JobFiles = elabel->JobFiles;
1142 jr->JobBytes = elabel->JobBytes;
1143 jr->VolSessionId = rec->VolSessionId;
1144 jr->VolSessionTime = rec->VolSessionTime;
1145 jr->JobTDate = (utime_t)mjcr->start_time;
1146 jr->ClientId = mjcr->ClientId;
1153 if (!db_update_job_end_record(bjcr, db, jr)) {
1154 Pmsg2(0, _("Could not update JobId=%u record. ERR=%s\n"), jr->JobId, db_strerror(db));
1159 Pmsg3(000, _("Updated Job termination record for JobId=%u Level=%s TermStat=%c\n"),
1160 jr->JobId, job_level_to_str(mjcr->get_JobLevel()), jr->JobStatus);
1163 const char *term_msg;
1164 static char term_code[70];
1165 char sdt[50], edt[50];
1166 char ec1[30], ec2[30], ec3[30];
1168 switch (mjcr->JobStatus) {
1170 term_msg = _("Backup OK");
1173 term_msg = _("Backup OK -- with warnings");
1176 case JS_ErrorTerminated:
1177 term_msg = _("*** Backup Error ***");
1180 term_msg = _("Backup Canceled");
1183 term_msg = term_code;
1184 sprintf(term_code, _("Job Termination code: %d"), mjcr->JobStatus);
1187 bstrftime(sdt, sizeof(sdt), mjcr->start_time);
1188 bstrftime(edt, sizeof(edt), mjcr->end_time);
1189 Pmsg14(000, _("%s\n"
1193 "Backup Level: %s\n"
1197 "Files Written: %s\n"
1198 "Bytes Written: %s\n"
1199 "Volume Session Id: %d\n"
1200 "Volume Session Time: %d\n"
1201 "Last Volume Bytes: %s\n"
1202 "Termination: %s\n\n"),
1207 job_level_to_str(mjcr->get_JobLevel()),
1211 edit_uint64_with_commas(mjcr->JobFiles, ec1),
1212 edit_uint64_with_commas(mjcr->JobBytes, ec2),
1214 mjcr->VolSessionTime,
1215 edit_uint64_with_commas(mr.VolBytes, ec3),
1222 static int create_jobmedia_record(B_DB *db, JCR *mjcr)
1225 DCR *dcr = mjcr->read_dcr;
1227 dcr->EndBlock = dev->EndBlock;
1228 dcr->EndFile = dev->EndFile;
1229 dcr->VolMediaId = dev->VolCatInfo.VolMediaId;
1231 memset(&jmr, 0, sizeof(jmr));
1232 jmr.JobId = mjcr->JobId;
1233 jmr.MediaId = mr.MediaId;
1234 jmr.FirstIndex = dcr->VolFirstIndex;
1235 jmr.LastIndex = dcr->VolLastIndex;
1236 jmr.StartFile = dcr->StartFile;
1237 jmr.EndFile = dcr->EndFile;
1238 jmr.StartBlock = dcr->StartBlock;
1239 jmr.EndBlock = dcr->EndBlock;
1246 if (!db_create_jobmedia_record(bjcr, db, &jmr)) {
1247 Pmsg1(0, _("Could not create JobMedia record. ERR=%s\n"), db_strerror(db));
1251 Pmsg2(000, _("Created JobMedia record JobId %d, MediaId %d\n"),
1252 jmr.JobId, jmr.MediaId);
1258 * Simulate the database call that updates the MD5/SHA1 record
1260 static int update_digest_record(B_DB *db, char *digest, DEV_RECORD *rec, int type)
1264 mjcr = get_jcr_by_session(rec->VolSessionId, rec->VolSessionTime);
1266 if (mr.VolJobs > 0) {
1267 Pmsg2(000, _("Could not find SessId=%d SessTime=%d for MD5/SHA1 record.\n"),
1268 rec->VolSessionId, rec->VolSessionTime);
1275 if (!update_db || mjcr->FileId == 0) {
1280 if (!db_add_digest_to_file_record(bjcr, db, mjcr->FileId, digest, type)) {
1281 Pmsg1(0, _("Could not add MD5/SHA1 to File record. ERR=%s\n"), db_strerror(db));
1286 Pmsg0(000, _("Updated MD5/SHA1 record\n"));
1294 * Create a JCR as if we are really starting the job
1296 static JCR *create_jcr(JOB_DBR *jr, DEV_RECORD *rec, uint32_t JobId)
1300 * Transfer as much as possible to the Job JCR. Most important is
1301 * the JobId and the ClientId.
1303 jobjcr = new_jcr(sizeof(JCR), bscan_free_jcr);
1304 jobjcr->set_JobType(jr->JobType);
1305 jobjcr->set_JobLevel(jr->JobLevel);
1306 jobjcr->JobStatus = jr->JobStatus;
1307 bstrncpy(jobjcr->Job, jr->Job, sizeof(jobjcr->Job));
1308 jobjcr->JobId = JobId; /* this is JobId on tape */
1309 jobjcr->sched_time = jr->SchedTime;
1310 jobjcr->start_time = jr->StartTime;
1311 jobjcr->VolSessionId = rec->VolSessionId;
1312 jobjcr->VolSessionTime = rec->VolSessionTime;
1313 jobjcr->ClientId = jr->ClientId;
1314 jobjcr->dcr = jobjcr->read_dcr = new_dcr(jobjcr, NULL, dev);
1319 /* Dummies to replace askdir.c */
1320 bool dir_find_next_appendable_volume(DCR *dcr) { return 1;}
1321 bool dir_update_volume_info(DCR *dcr, bool relabel, bool update_LastWritten) { return 1; }
1322 bool dir_create_jobmedia_record(DCR *dcr, bool zero) { return 1; }
1323 bool dir_ask_sysop_to_create_appendable_volume(DCR *dcr) { return 1; }
1324 bool dir_update_file_attributes(DCR *dcr, DEV_RECORD *rec) { return 1;}
1325 bool dir_send_job_status(JCR *jcr) {return 1;}
1326 int generate_job_event(JCR *jcr, const char *event) { return 1; }
1328 bool dir_ask_sysop_to_mount_volume(DCR *dcr, int /*mode*/)
1330 DEVICE *dev = dcr->dev;
1331 Dmsg0(20, "Enter dir_ask_sysop_to_mount_volume\n");
1332 /* Close device so user can use autochanger if desired */
1333 fprintf(stderr, _("Mount Volume \"%s\" on device %s and press return when ready: "),
1334 dcr->VolumeName, dev->print_name());
1340 bool dir_get_volume_info(DCR *dcr, enum get_vol_info_rw writing)
1342 Dmsg0(100, "Fake dir_get_volume_info\n");
1343 bstrncpy(dcr->VolCatInfo.VolCatName, dcr->VolumeName, sizeof(dcr->VolCatInfo.VolCatName));
1344 dcr->VolCatInfo.VolCatParts = find_num_dvd_parts(dcr);
1345 Dmsg2(500, "Vol=%s num_parts=%d\n", dcr->VolCatInfo.VolCatName, dcr->VolCatInfo.VolCatParts);