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");
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(NULL, db_driver, db_name, db_user, db_password,
301 db_host, db_port, NULL, 0)) == 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->VolCatInfo.VolCatName);
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 (!create_jobmedia_record(db, mjcr)) {
352 Pmsg2(000, _("Could not create JobMedia record for Volume=%s Job=%s\n"),
353 dev->VolCatInfo.VolCatName, mjcr->Job);
357 update_media_record(db, &mr);
359 /* Now let common read routine get up next tape. Note,
360 * we call mount_next... with bscan's jcr because that is where we
361 * have the Volume list, but we get attached.
363 bool stat = mount_next_read_volume(dcr);
368 fstat(dev->fd(), &sb);
369 currentVolumeSize = sb.st_size;
370 Pmsg1(000, _("First Volume Size = %s\n"),
371 edit_uint64(currentVolumeSize, ed1));
376 static void do_scan()
378 attr = new_attr(bjcr);
380 memset(&ar, 0, sizeof(ar));
381 memset(&pr, 0, sizeof(pr));
382 memset(&jr, 0, sizeof(jr));
383 memset(&cr, 0, sizeof(cr));
384 memset(&fsr, 0, sizeof(fsr));
385 memset(&fr, 0, sizeof(fr));
387 /* Detach bscan's jcr as we are not a real Job on the tape */
389 read_records(bjcr->read_dcr, record_cb, bscan_mount_next_read_volume);
392 db_write_batch_file_records(bjcr); /* used by bulk batch file insert */
398 * Returns: true if OK
401 static bool record_cb(DCR *dcr, DEV_RECORD *rec)
405 DEVICE *dev = dcr->dev;
406 JCR *bjcr = dcr->jcr;
407 DEV_BLOCK *block = dcr->block;
408 char digest[BASE64_SIZE(CRYPTO_DIGEST_MAX_SIZE)];
410 if (rec->data_len > 0) {
411 mr.VolBytes += rec->data_len + WRITE_RECHDR_LENGTH; /* Accumulate Volume bytes */
412 if (showProgress && currentVolumeSize > 0) {
413 int pct = (mr.VolBytes * 100) / currentVolumeSize;
414 if (pct != last_pct) {
415 fprintf(stdout, _("done: %d%%\n"), pct);
423 Pmsg5(000, _("Record: SessId=%u SessTim=%u FileIndex=%d Stream=%d len=%u\n"),
424 rec->VolSessionId, rec->VolSessionTime, rec->FileIndex,
425 rec->Stream, rec->data_len);
428 * Check for Start or End of Session Record
431 if (rec->FileIndex < 0) {
432 bool save_update_db = update_db;
435 dump_label_record(dev, rec, 1);
437 switch (rec->FileIndex) {
439 Pmsg0(000, _("Volume is prelabeled. This tape cannot be scanned.\n"));
444 unser_volume_label(dev, rec);
445 /* Check Pool info */
446 bstrncpy(pr.Name, dev->VolHdr.PoolName, sizeof(pr.Name));
447 bstrncpy(pr.PoolType, dev->VolHdr.PoolType, sizeof(pr.PoolType));
449 if (db_get_pool_record(bjcr, db, &pr)) {
451 Pmsg1(000, _("Pool record for %s found in DB.\n"), pr.Name);
455 Pmsg1(000, _("VOL_LABEL: Pool record not found for Pool: %s\n"),
458 create_pool_record(db, &pr);
460 if (strcmp(pr.PoolType, dev->VolHdr.PoolType) != 0) {
461 Pmsg2(000, _("VOL_LABEL: PoolType mismatch. DB=%s Vol=%s\n"),
462 pr.PoolType, dev->VolHdr.PoolType);
464 } else if (verbose) {
465 Pmsg1(000, _("Pool type \"%s\" is OK.\n"), pr.PoolType);
468 /* Check Media Info */
469 memset(&mr, 0, sizeof(mr));
470 bstrncpy(mr.VolumeName, dev->VolHdr.VolumeName, sizeof(mr.VolumeName));
471 mr.PoolId = pr.PoolId;
473 if (db_get_media_record(bjcr, db, &mr)) {
475 Pmsg1(000, _("Media record for %s found in DB.\n"), mr.VolumeName);
477 /* Clear out some volume statistics that will be updated */
478 mr.VolJobs = mr.VolFiles = mr.VolBlocks = 0;
479 mr.VolBytes = rec->data_len + 20;
482 Pmsg1(000, _("VOL_LABEL: Media record not found for Volume: %s\n"),
485 bstrncpy(mr.MediaType, dev->VolHdr.MediaType, sizeof(mr.MediaType));
486 create_media_record(db, &mr, &dev->VolHdr);
488 if (strcmp(mr.MediaType, dev->VolHdr.MediaType) != 0) {
489 Pmsg2(000, _("VOL_LABEL: MediaType mismatch. DB=%s Vol=%s\n"),
490 mr.MediaType, dev->VolHdr.MediaType);
491 return true; /* ignore error */
492 } else if (verbose) {
493 Pmsg1(000, _("Media type \"%s\" is OK.\n"), mr.MediaType);
495 /* Reset some DCR variables */
496 foreach_dlist(dcr, dev->attached_dcrs) {
497 dcr->VolFirstIndex = dcr->FileIndex = 0;
498 dcr->StartBlock = dcr->EndBlock = 0;
499 dcr->StartFile = dcr->EndFile = 0;
503 Pmsg1(000, _("VOL_LABEL: OK for Volume: %s\n"), mr.VolumeName);
509 if (ignored_msgs > 0) {
510 Pmsg1(000, _("%d \"errors\" ignored before first Start of Session record.\n"),
514 unser_session_label(&label, rec);
515 memset(&jr, 0, sizeof(jr));
516 bstrncpy(jr.Job, label.Job, sizeof(jr.Job));
517 if (db_get_job_record(bjcr, db, &jr)) {
518 /* Job record already exists in DB */
519 update_db = false; /* don't change db in create_job_record */
521 Pmsg1(000, _("SOS_LABEL: Found Job record for JobId: %d\n"), jr.JobId);
524 /* Must create a Job record in DB */
526 Pmsg1(000, _("SOS_LABEL: Job record not found for JobId: %d\n"),
530 /* Create Client record if not already there */
531 bstrncpy(cr.Name, label.ClientName, sizeof(cr.Name));
532 create_client_record(db, &cr);
533 jr.ClientId = cr.ClientId;
535 /* process label, if Job record exists don't update db */
536 mjcr = create_job_record(db, &jr, &label, rec);
537 dcr = mjcr->read_dcr;
538 update_db = save_update_db;
540 jr.PoolId = pr.PoolId;
541 mjcr->start_time = jr.StartTime;
542 mjcr->set_JobLevel(jr.JobLevel);
544 mjcr->client_name = get_pool_memory(PM_FNAME);
545 pm_strcpy(mjcr->client_name, label.ClientName);
546 mjcr->fileset_name = get_pool_memory(PM_FNAME);
547 pm_strcpy(mjcr->fileset_name, label.FileSetName);
548 bstrncpy(dcr->pool_type, label.PoolType, sizeof(dcr->pool_type));
549 bstrncpy(dcr->pool_name, label.PoolName, sizeof(dcr->pool_name));
551 if (rec->VolSessionId != jr.VolSessionId) {
552 Pmsg3(000, _("SOS_LABEL: VolSessId mismatch for JobId=%u. DB=%d Vol=%d\n"),
554 jr.VolSessionId, rec->VolSessionId);
555 return true; /* ignore error */
557 if (rec->VolSessionTime != jr.VolSessionTime) {
558 Pmsg3(000, _("SOS_LABEL: VolSessTime mismatch for JobId=%u. DB=%d Vol=%d\n"),
560 jr.VolSessionTime, rec->VolSessionTime);
561 return true; /* ignore error */
563 if (jr.PoolId != pr.PoolId) {
564 Pmsg3(000, _("SOS_LABEL: PoolId mismatch for JobId=%u. DB=%d Vol=%d\n"),
566 jr.PoolId, pr.PoolId);
567 return true; /* ignore error */
572 unser_session_label(&elabel, rec);
574 /* Create FileSet record */
575 bstrncpy(fsr.FileSet, label.FileSetName, sizeof(fsr.FileSet));
576 bstrncpy(fsr.MD5, label.FileSetMD5, sizeof(fsr.MD5));
577 create_fileset_record(db, &fsr);
578 jr.FileSetId = fsr.FileSetId;
580 mjcr = get_jcr_by_session(rec->VolSessionId, rec->VolSessionTime);
582 Pmsg2(000, _("Could not find SessId=%d SessTime=%d for EOS record.\n"),
583 rec->VolSessionId, rec->VolSessionTime);
587 /* Do the final update to the Job record */
588 update_job_record(db, &jr, &elabel, rec);
590 mjcr->end_time = jr.EndTime;
591 mjcr->JobStatus = JS_Terminated;
593 /* Create JobMedia record */
594 mjcr->read_dcr->VolLastIndex = dcr->VolLastIndex;
595 create_jobmedia_record(db, mjcr);
596 detach_dcr_from_dev(mjcr->read_dcr);
604 case EOT_LABEL: /* end of all tapes */
606 * Wiffle through all jobs still open and close
611 foreach_dlist(mdcr, dev->attached_dcrs) {
612 JCR *mjcr = mdcr->jcr;
613 if (!mjcr || mjcr->JobId == 0) {
616 jr.JobId = mjcr->JobId;
617 /* Mark Job as Error Terimined */
618 jr.JobStatus = JS_ErrorTerminated;
619 jr.JobFiles = mjcr->JobFiles;
620 jr.JobBytes = mjcr->JobBytes;
621 jr.VolSessionId = mjcr->VolSessionId;
622 jr.VolSessionTime = mjcr->VolSessionTime;
623 jr.JobTDate = (utime_t)mjcr->start_time;
624 jr.ClientId = mjcr->ClientId;
625 if (!db_update_job_end_record(bjcr, db, &jr)) {
626 Pmsg1(0, _("Could not update job record. ERR=%s\n"), db_strerror(db));
628 mjcr->read_dcr = NULL;
632 mr.VolFiles = rec->File;
633 mr.VolBlocks = rec->Block;
634 mr.VolBytes += mr.VolBlocks * WRITE_BLKHDR_LENGTH; /* approx. */
636 update_media_record(db, &mr);
637 Pmsg3(0, _("End of all Volumes. VolFiles=%u VolBlocks=%u VolBytes=%s\n"), mr.VolFiles,
638 mr.VolBlocks, edit_uint64_with_commas(mr.VolBytes, ec1));
646 mjcr = get_jcr_by_session(rec->VolSessionId, rec->VolSessionTime);
648 if (mr.VolJobs > 0) {
649 Pmsg2(000, _("Could not find Job for SessId=%d SessTime=%d record.\n"),
650 rec->VolSessionId, rec->VolSessionTime);
656 dcr = mjcr->read_dcr;
657 if (dcr->VolFirstIndex == 0) {
658 dcr->VolFirstIndex = block->FirstIndex;
661 /* File Attributes stream */
662 switch (rec->Stream) {
663 case STREAM_UNIX_ATTRIBUTES:
664 case STREAM_UNIX_ATTRIBUTES_EX:
666 if (!unpack_attributes_record(bjcr, rec->Stream, rec->data, attr)) {
667 Emsg0(M_ERROR_TERM, 0, _("Cannot continue.\n"));
670 if (attr->file_index != rec->FileIndex) {
671 Emsg2(M_ERROR_TERM, 0, _("Record header file index %ld not equal record index %ld\n"),
672 rec->FileIndex, attr->file_index);
676 decode_stat(attr->attr, &attr->statp, &attr->LinkFI);
677 build_attr_output_fnames(bjcr, attr);
678 print_ls_output(bjcr, attr);
680 fr.JobId = mjcr->JobId;
683 if (verbose && (num_files & 0x7FFF) == 0) {
684 char ed1[30], ed2[30], ed3[30], ed4[30];
685 Pmsg4(000, _("%s file records. At file:blk=%s:%s bytes=%s\n"),
686 edit_uint64_with_commas(num_files, ed1),
687 edit_uint64_with_commas(rec->File, ed2),
688 edit_uint64_with_commas(rec->Block, ed3),
689 edit_uint64_with_commas(mr.VolBytes, ed4));
691 create_file_attributes_record(db, mjcr, attr->fname, attr->lname,
692 attr->type, attr->attr, rec);
697 case STREAM_WIN32_DATA:
698 case STREAM_FILE_DATA:
699 case STREAM_SPARSE_DATA:
700 case STREAM_ENCRYPTED_FILE_DATA:
701 case STREAM_ENCRYPTED_WIN32_DATA:
702 case STREAM_ENCRYPTED_MACOS_FORK_DATA:
704 * For encrypted stream, this is an approximation.
705 * The data must be decrypted to know the correct length.
707 mjcr->JobBytes += rec->data_len;
708 if (rec->Stream == STREAM_SPARSE_DATA) {
709 mjcr->JobBytes -= sizeof(uint64_t);
712 free_jcr(mjcr); /* done using JCR */
715 case STREAM_GZIP_DATA:
716 case STREAM_ENCRYPTED_FILE_GZIP_DATA:
717 case STREAM_ENCRYPTED_WIN32_GZIP_DATA:
718 /* No correct, we should (decrypt and) expand it
721 mjcr->JobBytes += rec->data_len;
725 case STREAM_SPARSE_GZIP_DATA:
726 mjcr->JobBytes += rec->data_len - sizeof(uint64_t); /* No correct, we should expand it */
727 free_jcr(mjcr); /* done using JCR */
730 /* Win32 GZIP stream */
731 case STREAM_WIN32_GZIP_DATA:
732 mjcr->JobBytes += rec->data_len;
733 free_jcr(mjcr); /* done using JCR */
736 case STREAM_MD5_DIGEST:
737 bin_to_base64(digest, sizeof(digest), (char *)rec->data, CRYPTO_DIGEST_MD5_SIZE, true);
739 Pmsg1(000, _("Got MD5 record: %s\n"), digest);
741 update_digest_record(db, digest, rec, CRYPTO_DIGEST_MD5);
744 case STREAM_SHA1_DIGEST:
745 bin_to_base64(digest, sizeof(digest), (char *)rec->data, CRYPTO_DIGEST_SHA1_SIZE, true);
747 Pmsg1(000, _("Got SHA1 record: %s\n"), digest);
749 update_digest_record(db, digest, rec, CRYPTO_DIGEST_SHA1);
752 case STREAM_SHA256_DIGEST:
753 bin_to_base64(digest, sizeof(digest), (char *)rec->data, CRYPTO_DIGEST_SHA256_SIZE, true);
755 Pmsg1(000, _("Got SHA256 record: %s\n"), digest);
757 update_digest_record(db, digest, rec, CRYPTO_DIGEST_SHA256);
760 case STREAM_SHA512_DIGEST:
761 bin_to_base64(digest, sizeof(digest), (char *)rec->data, CRYPTO_DIGEST_SHA512_SIZE, true);
763 Pmsg1(000, _("Got SHA512 record: %s\n"), digest);
765 update_digest_record(db, digest, rec, CRYPTO_DIGEST_SHA512);
768 case STREAM_ENCRYPTED_SESSION_DATA:
769 // TODO landonf: Investigate crypto support in bscan
771 Pmsg0(000, _("Got signed digest record\n"));
775 case STREAM_SIGNED_DIGEST:
776 // TODO landonf: Investigate crypto support in bscan
778 Pmsg0(000, _("Got signed digest record\n"));
782 case STREAM_PROGRAM_NAMES:
784 Pmsg1(000, _("Got Prog Names Stream: %s\n"), rec->data);
788 case STREAM_PROGRAM_DATA:
790 Pmsg0(000, _("Got Prog Data Stream record.\n"));
794 case STREAM_UNIX_ACCESS_ACL: /* Deprecated Standard ACL attributes on UNIX */
795 case STREAM_UNIX_DEFAULT_ACL: /* Deprecated Default ACL attributes on UNIX */
796 case STREAM_ACL_AIX_TEXT:
797 case STREAM_ACL_DARWIN_ACCESS_ACL:
798 case STREAM_ACL_FREEBSD_DEFAULT_ACL:
799 case STREAM_ACL_FREEBSD_ACCESS_ACL:
800 case STREAM_ACL_HPUX_ACL_ENTRY:
801 case STREAM_ACL_IRIX_DEFAULT_ACL:
802 case STREAM_ACL_IRIX_ACCESS_ACL:
803 case STREAM_ACL_LINUX_DEFAULT_ACL:
804 case STREAM_ACL_LINUX_ACCESS_ACL:
805 case STREAM_ACL_TRU64_DEFAULT_ACL:
806 case STREAM_ACL_TRU64_DEFAULT_DIR_ACL:
807 case STREAM_ACL_TRU64_ACCESS_ACL:
808 case STREAM_ACL_SOLARIS_ACLENT:
809 case STREAM_ACL_SOLARIS_ACE:
810 /* Ignore Unix ACL attributes */
813 case STREAM_XATTR_SOLARIS:
814 case STREAM_XATTR_DARWIN:
815 case STREAM_XATTR_FREEBSD:
816 case STREAM_XATTR_LINUX:
817 case STREAM_XATTR_NETBSD:
818 /* Ignore Unix Extended attributes */
822 Pmsg2(0, _("Unknown stream type!!! stream=%d len=%i\n"), rec->Stream, rec->data_len);
829 * Free the Job Control Record if no one is still using it.
830 * Called from main free_jcr() routine in src/lib/jcr.c so
831 * that we can do our Director specific cleanup of the jcr.
833 static void bscan_free_jcr(JCR *jcr)
835 Dmsg0(200, "Start bscan free_jcr\n");
837 if (jcr->file_bsock) {
838 Dmsg0(200, "Close File bsock\n");
839 bnet_close(jcr->file_bsock);
841 if (jcr->store_bsock) {
842 Dmsg0(200, "Close Store bsock\n");
843 bnet_close(jcr->store_bsock);
845 if (jcr->RestoreBootstrap) {
846 free(jcr->RestoreBootstrap);
853 free_dcr(jcr->read_dcr);
854 jcr->read_dcr = NULL;
856 Dmsg0(200, "End bscan free_jcr\n");
860 * We got a File Attributes record on the tape. Now, lookup the Job
861 * record, and then create the attributes record.
863 static int create_file_attributes_record(B_DB *db, JCR *mjcr,
864 char *fname, char *lname, int type,
865 char *ap, DEV_RECORD *rec)
867 DCR *dcr = mjcr->read_dcr;
870 ar.ClientId = mjcr->ClientId;
871 ar.JobId = mjcr->JobId;
872 ar.Stream = rec->Stream;
873 if (type == FT_DELETED) {
876 ar.FileIndex = rec->FileIndex;
879 if (dcr->VolFirstIndex == 0) {
880 dcr->VolFirstIndex = rec->FileIndex;
882 dcr->FileIndex = rec->FileIndex;
889 if (!db_create_file_attributes_record(bjcr, db, &ar)) {
890 Pmsg1(0, _("Could not create File Attributes record. ERR=%s\n"), db_strerror(db));
893 mjcr->FileId = ar.FileId;
896 Pmsg1(000, _("Created File record: %s\n"), fname);
902 * For each Volume we see, we create a Medium record
904 static int create_media_record(B_DB *db, MEDIA_DBR *mr, VOLUME_LABEL *vl)
909 /* We mark Vols as Archive to keep them from being re-written */
910 bstrncpy(mr->VolStatus, "Archive", sizeof(mr->VolStatus));
911 mr->VolRetention = 365 * 3600 * 24; /* 1 year */
913 if (vl->VerNum >= 11) {
914 mr->FirstWritten = btime_to_utime(vl->write_btime);
915 mr->LabelDate = btime_to_utime(vl->label_btime);
917 /* DEPRECATED DO NOT USE */
918 dt.julian_day_number = vl->write_date;
919 dt.julian_day_fraction = vl->write_time;
921 mr->FirstWritten = mktime(&tm);
922 dt.julian_day_number = vl->label_date;
923 dt.julian_day_fraction = vl->label_time;
925 mr->LabelDate = mktime(&tm);
927 lasttime = mr->LabelDate;
928 if (mr->VolJobs == 0) {
931 if (mr->VolMounts == 0) {
939 if (!db_create_media_record(bjcr, db, mr)) {
940 Pmsg1(0, _("Could not create media record. ERR=%s\n"), db_strerror(db));
943 if (!db_update_media_record(bjcr, db, mr)) {
944 Pmsg1(0, _("Could not update media record. ERR=%s\n"), db_strerror(db));
948 Pmsg1(000, _("Created Media record for Volume: %s\n"), mr->VolumeName);
955 * Called at end of media to update it
957 static bool update_media_record(B_DB *db, MEDIA_DBR *mr)
959 if (!update_db && !update_vol_info) {
963 mr->LastWritten = lasttime;
964 if (!db_update_media_record(bjcr, db, mr)) {
965 Pmsg1(0, _("Could not update media record. ERR=%s\n"), db_strerror(db));
969 Pmsg1(000, _("Updated Media record at end of Volume: %s\n"), mr->VolumeName);
976 static int create_pool_record(B_DB *db, POOL_DBR *pr)
980 pr->VolRetention = 355 * 3600 * 24; /* 1 year */
985 if (!db_create_pool_record(bjcr, db, pr)) {
986 Pmsg1(0, _("Could not create pool record. ERR=%s\n"), db_strerror(db));
990 Pmsg1(000, _("Created Pool record for Pool: %s\n"), pr->Name);
998 * Called from SOS to create a client for the current Job
1000 static int create_client_record(B_DB *db, CLIENT_DBR *cr)
1003 * Note, update_db can temporarily be set false while
1004 * updating the database, so we must ensure that ClientId is non-zero.
1008 if (!db_get_client_record(bjcr, db, cr)) {
1009 Pmsg1(0, _("Could not get Client record. ERR=%s\n"), db_strerror(db));
1014 if (!db_create_client_record(bjcr, db, cr)) {
1015 Pmsg1(0, _("Could not create Client record. ERR=%s\n"), db_strerror(db));
1019 Pmsg1(000, _("Created Client record for Client: %s\n"), cr->Name);
1024 static int create_fileset_record(B_DB *db, FILESET_DBR *fsr)
1030 if (fsr->MD5[0] == 0) {
1031 fsr->MD5[0] = ' '; /* Equivalent to nothing */
1034 if (db_get_fileset_record(bjcr, db, fsr)) {
1036 Pmsg1(000, _("Fileset \"%s\" already exists.\n"), fsr->FileSet);
1039 if (!db_create_fileset_record(bjcr, db, fsr)) {
1040 Pmsg2(0, _("Could not create FileSet record \"%s\". ERR=%s\n"),
1041 fsr->FileSet, db_strerror(db));
1045 Pmsg1(000, _("Created FileSet record \"%s\"\n"), fsr->FileSet);
1052 * Simulate the two calls on the database to create
1053 * the Job record and to update it when the Job actually
1056 static JCR *create_job_record(B_DB *db, JOB_DBR *jr, SESSION_LABEL *label,
1060 struct date_time dt;
1063 jr->JobId = label->JobId;
1064 jr->JobType = label->JobType;
1065 jr->JobLevel = label->JobLevel;
1066 jr->JobStatus = JS_Created;
1067 bstrncpy(jr->Name, label->JobName, sizeof(jr->Name));
1068 bstrncpy(jr->Job, label->Job, sizeof(jr->Job));
1069 if (label->VerNum >= 11) {
1070 jr->SchedTime = btime_to_unix(label->write_btime);
1072 dt.julian_day_number = label->write_date;
1073 dt.julian_day_fraction = label->write_time;
1074 tm_decode(&dt, &tm);
1075 jr->SchedTime = mktime(&tm);
1078 jr->StartTime = jr->SchedTime;
1079 jr->JobTDate = (utime_t)jr->SchedTime;
1080 jr->VolSessionId = rec->VolSessionId;
1081 jr->VolSessionTime = rec->VolSessionTime;
1083 /* Now create a JCR as if starting the Job */
1084 mjcr = create_jcr(jr, rec, label->JobId);
1090 /* This creates the bare essentials */
1091 if (!db_create_job_record(bjcr, db, jr)) {
1092 Pmsg1(0, _("Could not create JobId record. ERR=%s\n"), db_strerror(db));
1096 /* This adds the client, StartTime, JobTDate, ... */
1097 if (!db_update_job_start_record(bjcr, db, jr)) {
1098 Pmsg1(0, _("Could not update job start record. ERR=%s\n"), db_strerror(db));
1101 Pmsg2(000, _("Created new JobId=%u record for original JobId=%u\n"), jr->JobId,
1103 mjcr->JobId = jr->JobId; /* set new JobId */
1108 * Simulate the database call that updates the Job
1109 * at Job termination time.
1111 static int update_job_record(B_DB *db, JOB_DBR *jr, SESSION_LABEL *elabel,
1114 struct date_time dt;
1118 mjcr = get_jcr_by_session(rec->VolSessionId, rec->VolSessionTime);
1120 Pmsg2(000, _("Could not find SessId=%d SessTime=%d for EOS record.\n"),
1121 rec->VolSessionId, rec->VolSessionTime);
1124 if (elabel->VerNum >= 11) {
1125 jr->EndTime = btime_to_unix(elabel->write_btime);
1127 dt.julian_day_number = elabel->write_date;
1128 dt.julian_day_fraction = elabel->write_time;
1129 tm_decode(&dt, &tm);
1130 jr->EndTime = mktime(&tm);
1132 lasttime = jr->EndTime;
1133 mjcr->end_time = jr->EndTime;
1135 jr->JobId = mjcr->JobId;
1136 jr->JobStatus = elabel->JobStatus;
1137 mjcr->JobStatus = elabel->JobStatus;
1138 jr->JobFiles = elabel->JobFiles;
1139 jr->JobBytes = elabel->JobBytes;
1140 jr->VolSessionId = rec->VolSessionId;
1141 jr->VolSessionTime = rec->VolSessionTime;
1142 jr->JobTDate = (utime_t)mjcr->start_time;
1143 jr->ClientId = mjcr->ClientId;
1150 if (!db_update_job_end_record(bjcr, db, jr)) {
1151 Pmsg2(0, _("Could not update JobId=%u record. ERR=%s\n"), jr->JobId, db_strerror(db));
1156 Pmsg3(000, _("Updated Job termination record for JobId=%u Level=%s TermStat=%c\n"),
1157 jr->JobId, job_level_to_str(mjcr->get_JobLevel()), jr->JobStatus);
1160 const char *term_msg;
1161 static char term_code[70];
1162 char sdt[50], edt[50];
1163 char ec1[30], ec2[30], ec3[30];
1165 switch (mjcr->JobStatus) {
1167 term_msg = _("Backup OK");
1170 case JS_ErrorTerminated:
1171 term_msg = _("*** Backup Error ***");
1174 term_msg = _("Backup Canceled");
1177 term_msg = term_code;
1178 sprintf(term_code, _("Job Termination code: %d"), mjcr->JobStatus);
1181 bstrftime(sdt, sizeof(sdt), mjcr->start_time);
1182 bstrftime(edt, sizeof(edt), mjcr->end_time);
1183 Pmsg14(000, _("%s\n"
1187 "Backup Level: %s\n"
1191 "Files Written: %s\n"
1192 "Bytes Written: %s\n"
1193 "Volume Session Id: %d\n"
1194 "Volume Session Time: %d\n"
1195 "Last Volume Bytes: %s\n"
1196 "Termination: %s\n\n"),
1201 job_level_to_str(mjcr->get_JobLevel()),
1205 edit_uint64_with_commas(mjcr->JobFiles, ec1),
1206 edit_uint64_with_commas(mjcr->JobBytes, ec2),
1208 mjcr->VolSessionTime,
1209 edit_uint64_with_commas(mr.VolBytes, ec3),
1216 static int create_jobmedia_record(B_DB *db, JCR *mjcr)
1219 DCR *dcr = mjcr->read_dcr;
1221 dcr->EndBlock = dev->EndBlock;
1222 dcr->EndFile = dev->EndFile;
1223 dcr->VolMediaId = dev->VolCatInfo.VolMediaId;
1225 memset(&jmr, 0, sizeof(jmr));
1226 jmr.JobId = mjcr->JobId;
1227 jmr.MediaId = mr.MediaId;
1228 jmr.FirstIndex = dcr->VolFirstIndex;
1229 jmr.LastIndex = dcr->VolLastIndex;
1230 jmr.StartFile = dcr->StartFile;
1231 jmr.EndFile = dcr->EndFile;
1232 jmr.StartBlock = dcr->StartBlock;
1233 jmr.EndBlock = dcr->EndBlock;
1240 if (!db_create_jobmedia_record(bjcr, db, &jmr)) {
1241 Pmsg1(0, _("Could not create JobMedia record. ERR=%s\n"), db_strerror(db));
1245 Pmsg2(000, _("Created JobMedia record JobId %d, MediaId %d\n"),
1246 jmr.JobId, jmr.MediaId);
1252 * Simulate the database call that updates the MD5/SHA1 record
1254 static int update_digest_record(B_DB *db, char *digest, DEV_RECORD *rec, int type)
1258 mjcr = get_jcr_by_session(rec->VolSessionId, rec->VolSessionTime);
1260 if (mr.VolJobs > 0) {
1261 Pmsg2(000, _("Could not find SessId=%d SessTime=%d for MD5/SHA1 record.\n"),
1262 rec->VolSessionId, rec->VolSessionTime);
1269 if (!update_db || mjcr->FileId == 0) {
1274 if (!db_add_digest_to_file_record(bjcr, db, mjcr->FileId, digest, type)) {
1275 Pmsg1(0, _("Could not add MD5/SHA1 to File record. ERR=%s\n"), db_strerror(db));
1280 Pmsg0(000, _("Updated MD5/SHA1 record\n"));
1288 * Create a JCR as if we are really starting the job
1290 static JCR *create_jcr(JOB_DBR *jr, DEV_RECORD *rec, uint32_t JobId)
1294 * Transfer as much as possible to the Job JCR. Most important is
1295 * the JobId and the ClientId.
1297 jobjcr = new_jcr(sizeof(JCR), bscan_free_jcr);
1298 jobjcr->set_JobType(jr->JobType);
1299 jobjcr->set_JobLevel(jr->JobLevel);
1300 jobjcr->JobStatus = jr->JobStatus;
1301 bstrncpy(jobjcr->Job, jr->Job, sizeof(jobjcr->Job));
1302 jobjcr->JobId = JobId; /* this is JobId on tape */
1303 jobjcr->sched_time = jr->SchedTime;
1304 jobjcr->start_time = jr->StartTime;
1305 jobjcr->VolSessionId = rec->VolSessionId;
1306 jobjcr->VolSessionTime = rec->VolSessionTime;
1307 jobjcr->ClientId = jr->ClientId;
1308 jobjcr->dcr = jobjcr->read_dcr = new_dcr(jobjcr, NULL, dev);
1313 /* Dummies to replace askdir.c */
1314 bool dir_find_next_appendable_volume(DCR *dcr) { return 1;}
1315 bool dir_update_volume_info(DCR *dcr, bool relabel, bool update_LastWritten) { return 1; }
1316 bool dir_create_jobmedia_record(DCR *dcr, bool zero) { return 1; }
1317 bool dir_ask_sysop_to_create_appendable_volume(DCR *dcr) { return 1; }
1318 bool dir_update_file_attributes(DCR *dcr, DEV_RECORD *rec) { return 1;}
1319 bool dir_send_job_status(JCR *jcr) {return 1;}
1320 int generate_job_event(JCR *jcr, const char *event) { return 1; }
1322 bool dir_ask_sysop_to_mount_volume(DCR *dcr, int /*mode*/)
1324 DEVICE *dev = dcr->dev;
1325 Dmsg0(20, "Enter dir_ask_sysop_to_mount_volume\n");
1326 /* Close device so user can use autochanger if desired */
1327 fprintf(stderr, _("Mount Volume \"%s\" on device %s and press return when ready: "),
1328 dcr->VolumeName, dev->print_name());
1334 bool dir_get_volume_info(DCR *dcr, enum get_vol_info_rw writing)
1336 Dmsg0(100, "Fake dir_get_volume_info\n");
1337 bstrncpy(dcr->VolCatInfo.VolCatName, dcr->VolumeName, sizeof(dcr->VolCatInfo.VolCatName));
1338 dcr->VolCatInfo.VolCatParts = find_num_dvd_parts(dcr);
1339 Dmsg2(500, "Vol=%s num_parts=%d\n", dcr->VolCatInfo.VolCatName, dcr->VolCatInfo.VolCatParts);