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 John Walker.
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; }
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 #define CONFIG_FILE "bacula-sd.conf"
106 char *configfile = NULL;
107 STORES *me = NULL; /* our Global resource */
108 bool forge_on = false; /* proceed inspite of I/O errors */
109 pthread_mutex_t device_release_mutex = PTHREAD_MUTEX_INITIALIZER;
110 pthread_cond_t wait_device_release = PTHREAD_COND_INITIALIZER;
117 "\nVersion: %s (%s)\n\n"
118 "Usage: bscan [ options ] <bacula-archive>\n"
119 " -b bootstrap specify a bootstrap file\n"
120 " -c <file> specify configuration file\n"
121 " -d <nn> set debug level to <nn>\n"
122 " -dt print timestamp in debug output\n"
123 " -m update media info in database\n"
124 " -D <driver name> specify the driver database name (default NULL)\n"
125 " -n <name> specify the database name (default bacula)\n"
126 " -u <user> specify database user name (default bacula)\n"
127 " -P <password> specify database password (default none)\n"
128 " -h <host> specify database host (default NULL)\n"
129 " -t <port> specify database port (default 0)\n"
130 " -p proceed inspite of I/O errors\n"
132 " -s synchronize or store in database\n"
133 " -S show scan progress periodically\n"
135 " -V <Volumes> specify Volume names (separated by |)\n"
136 " -w <dir> specify working directory (default from conf file)\n"
137 " -? print this message\n\n"), 2001, VERSION, BDATE);
141 int main (int argc, char *argv[])
144 struct stat stat_buf;
145 char *VolumeName = NULL;
147 setlocale(LC_ALL, "");
148 bindtextdomain("bacula", LOCALEDIR);
149 textdomain("bacula");
152 my_name_is(argc, argv, "bscan");
153 init_msg(NULL, NULL);
157 while ((ch = getopt(argc, argv, "b:c:dD:h:p:mn:pP:rsStu:vV:w:?")) != -1) {
163 bsr = parse_bsr(NULL, optarg);
166 case 'c': /* specify config file */
167 if (configfile != NULL) {
170 configfile = bstrdup(optarg);
177 case 'd': /* debug level */
178 if (*optarg == 't') {
179 dbg_timestamp = true;
181 debug_level = atoi(optarg);
182 if (debug_level <= 0) {
193 db_port = atoi(optarg);
197 update_vol_info = true;
209 db_password = optarg;
228 case 'V': /* Volume name */
246 Pmsg0(0, _("Wrong number of arguments: \n"));
250 if (configfile == NULL) {
251 configfile = bstrdup(CONFIG_FILE);
254 parse_config(configfile);
256 me = (STORES *)GetNextRes(R_STORAGE, NULL);
259 Emsg1(M_ERROR_TERM, 0, _("No Storage resource defined in %s. Cannot continue.\n"),
263 /* Check if -w option given, otherwise use resource for working directory */
265 working_directory = wd;
266 } else if (!me->working_directory) {
267 Emsg1(M_ERROR_TERM, 0, _("No Working Directory defined in %s. Cannot continue.\n"),
270 working_directory = me->working_directory;
273 /* Check that working directory is good */
274 if (stat(working_directory, &stat_buf) != 0) {
275 Emsg1(M_ERROR_TERM, 0, _("Working Directory: %s not found. Cannot continue.\n"),
278 if (!S_ISDIR(stat_buf.st_mode)) {
279 Emsg1(M_ERROR_TERM, 0, _("Working Directory: %s is not a directory. Cannot continue.\n"),
283 bjcr = setup_jcr("bscan", argv[0], bsr, VolumeName, 1); /* read device */
287 dev = bjcr->read_dcr->dev;
291 fstat(dev->fd(), &sb);
292 currentVolumeSize = sb.st_size;
293 Pmsg1(000, _("First Volume Size = %sn"),
294 edit_uint64(currentVolumeSize, ed1));
297 if ((db=db_init(NULL, db_driver, db_name, db_user, db_password,
298 db_host, db_port, NULL, 0)) == NULL) {
299 Emsg0(M_ERROR_TERM, 0, _("Could not init Bacula database\n"));
301 if (!db_open_database(NULL, db)) {
302 Emsg0(M_ERROR_TERM, 0, db_strerror(db));
304 Dmsg0(200, "Database opened\n");
306 Pmsg2(000, _("Using Database: %s, User: %s\n"), db_name, db_user);
311 printf("Records added or updated in the catalog:\n%7d Media\n%7d Pool\n%7d Job\n%7d File\n",
312 num_media, num_pools, num_jobs, num_files);
314 printf("Records would have been 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);
324 * We are at the end of reading a tape. Now, we simulate handling
325 * the end of writing a tape by wiffling through the attached
326 * jcrs creating jobmedia records.
328 static bool bscan_mount_next_read_volume(DCR *dcr)
330 DEVICE *dev = dcr->dev;
332 Dmsg1(100, "Walk attached jcrs. Volume=%s\n", dev->VolCatInfo.VolCatName);
333 foreach_dlist(mdcr, dev->attached_dcrs) {
334 JCR *mjcr = mdcr->jcr;
335 Dmsg1(000, "========== JobId=%u ========\n", mjcr->JobId);
336 if (mjcr->JobId == 0) {
340 Pmsg1(000, _("Create JobMedia for Job %s\n"), mjcr->Job);
342 mdcr->StartBlock = dcr->StartBlock;
343 mdcr->StartFile = dcr->StartFile;
344 mdcr->EndBlock = dcr->EndBlock;
345 mdcr->EndFile = dcr->EndFile;
346 mdcr->VolMediaId = dcr->VolMediaId;
347 mjcr->read_dcr->VolLastIndex = dcr->VolLastIndex;
348 if (!create_jobmedia_record(db, mjcr)) {
349 Pmsg2(000, _("Could not create JobMedia record for Volume=%s Job=%s\n"),
350 dev->VolCatInfo.VolCatName, mjcr->Job);
354 update_media_record(db, &mr);
356 /* Now let common read routine get up next tape. Note,
357 * we call mount_next... with bscan's jcr because that is where we
358 * have the Volume list, but we get attached.
360 bool stat = mount_next_read_volume(dcr);
365 fstat(dev->fd(), &sb);
366 currentVolumeSize = sb.st_size;
367 Pmsg1(000, _("First Volume Size = %sn"),
368 edit_uint64(currentVolumeSize, ed1));
373 static void do_scan()
375 attr = new_attr(bjcr);
377 memset(&ar, 0, sizeof(ar));
378 memset(&pr, 0, sizeof(pr));
379 memset(&jr, 0, sizeof(jr));
380 memset(&cr, 0, sizeof(cr));
381 memset(&fsr, 0, sizeof(fsr));
382 memset(&fr, 0, sizeof(fr));
384 /* Detach bscan's jcr as we are not a real Job on the tape */
386 read_records(bjcr->read_dcr, record_cb, bscan_mount_next_read_volume);
389 db_write_batch_file_records(bjcr); /* used by bulk batch file insert */
395 * Returns: true if OK
398 static bool record_cb(DCR *dcr, DEV_RECORD *rec)
402 DEVICE *dev = dcr->dev;
403 JCR *bjcr = dcr->jcr;
404 DEV_BLOCK *block = dcr->block;
405 char digest[BASE64_SIZE(CRYPTO_DIGEST_MAX_SIZE)];
407 if (rec->data_len > 0) {
408 mr.VolBytes += rec->data_len + WRITE_RECHDR_LENGTH; /* Accumulate Volume bytes */
409 if (showProgress && currentVolumeSize > 0) {
410 int pct = (mr.VolBytes * 100) / currentVolumeSize;
411 if (pct != last_pct) {
412 fprintf(stdout, _("done: %d%%\n"), pct);
420 Pmsg5(000, _("Record: SessId=%u SessTim=%u FileIndex=%d Stream=%d len=%u\n"),
421 rec->VolSessionId, rec->VolSessionTime, rec->FileIndex,
422 rec->Stream, rec->data_len);
425 * Check for Start or End of Session Record
428 if (rec->FileIndex < 0) {
429 bool save_update_db = update_db;
432 dump_label_record(dev, rec, 1);
434 switch (rec->FileIndex) {
436 Pmsg0(000, _("Volume is prelabeled. This tape cannot be scanned.\n"));
441 unser_volume_label(dev, rec);
442 /* Check Pool info */
443 bstrncpy(pr.Name, dev->VolHdr.PoolName, sizeof(pr.Name));
444 bstrncpy(pr.PoolType, dev->VolHdr.PoolType, sizeof(pr.PoolType));
446 if (db_get_pool_record(bjcr, db, &pr)) {
448 Pmsg1(000, _("Pool record for %s found in DB.\n"), pr.Name);
452 Pmsg1(000, _("VOL_LABEL: Pool record not found for Pool: %s\n"),
455 create_pool_record(db, &pr);
457 if (strcmp(pr.PoolType, dev->VolHdr.PoolType) != 0) {
458 Pmsg2(000, _("VOL_LABEL: PoolType mismatch. DB=%s Vol=%s\n"),
459 pr.PoolType, dev->VolHdr.PoolType);
461 } else if (verbose) {
462 Pmsg1(000, _("Pool type \"%s\" is OK.\n"), pr.PoolType);
465 /* Check Media Info */
466 memset(&mr, 0, sizeof(mr));
467 bstrncpy(mr.VolumeName, dev->VolHdr.VolumeName, sizeof(mr.VolumeName));
468 mr.PoolId = pr.PoolId;
470 if (db_get_media_record(bjcr, db, &mr)) {
472 Pmsg1(000, _("Media record for %s found in DB.\n"), mr.VolumeName);
474 /* Clear out some volume statistics that will be updated */
475 mr.VolJobs = mr.VolFiles = mr.VolBlocks = 0;
476 mr.VolBytes = rec->data_len + 20;
479 Pmsg1(000, _("VOL_LABEL: Media record not found for Volume: %s\n"),
482 bstrncpy(mr.MediaType, dev->VolHdr.MediaType, sizeof(mr.MediaType));
483 create_media_record(db, &mr, &dev->VolHdr);
485 if (strcmp(mr.MediaType, dev->VolHdr.MediaType) != 0) {
486 Pmsg2(000, _("VOL_LABEL: MediaType mismatch. DB=%s Vol=%s\n"),
487 mr.MediaType, dev->VolHdr.MediaType);
488 return true; /* ignore error */
489 } else if (verbose) {
490 Pmsg1(000, _("Media type \"%s\" is OK.\n"), mr.MediaType);
492 /* Reset some DCR variables */
493 foreach_dlist(dcr, dev->attached_dcrs) {
494 dcr->VolFirstIndex = dcr->FileIndex = 0;
495 dcr->StartBlock = dcr->EndBlock = 0;
496 dcr->StartFile = dcr->EndFile = 0;
500 Pmsg1(000, _("VOL_LABEL: OK for Volume: %s\n"), mr.VolumeName);
506 if (ignored_msgs > 0) {
507 Pmsg1(000, _("%d \"errors\" ignored before first Start of Session record.\n"),
511 unser_session_label(&label, rec);
512 memset(&jr, 0, sizeof(jr));
513 bstrncpy(jr.Job, label.Job, sizeof(jr.Job));
514 if (db_get_job_record(bjcr, db, &jr)) {
515 /* Job record already exists in DB */
516 update_db = false; /* don't change db in create_job_record */
518 Pmsg1(000, _("SOS_LABEL: Found Job record for JobId: %d\n"), jr.JobId);
521 /* Must create a Job record in DB */
523 Pmsg1(000, _("SOS_LABEL: Job record not found for JobId: %d\n"),
527 /* Create Client record if not already there */
528 bstrncpy(cr.Name, label.ClientName, sizeof(cr.Name));
529 create_client_record(db, &cr);
530 jr.ClientId = cr.ClientId;
532 /* process label, if Job record exists don't update db */
533 mjcr = create_job_record(db, &jr, &label, rec);
534 dcr = mjcr->read_dcr;
535 update_db = save_update_db;
537 jr.PoolId = pr.PoolId;
538 mjcr->start_time = jr.StartTime;
539 mjcr->JobLevel = jr.JobLevel;
541 mjcr->client_name = get_pool_memory(PM_FNAME);
542 pm_strcpy(mjcr->client_name, label.ClientName);
543 mjcr->fileset_name = get_pool_memory(PM_FNAME);
544 pm_strcpy(mjcr->fileset_name, label.FileSetName);
545 bstrncpy(dcr->pool_type, label.PoolType, sizeof(dcr->pool_type));
546 bstrncpy(dcr->pool_name, label.PoolName, sizeof(dcr->pool_name));
548 if (rec->VolSessionId != jr.VolSessionId) {
549 Pmsg3(000, _("SOS_LABEL: VolSessId mismatch for JobId=%u. DB=%d Vol=%d\n"),
551 jr.VolSessionId, rec->VolSessionId);
552 return true; /* ignore error */
554 if (rec->VolSessionTime != jr.VolSessionTime) {
555 Pmsg3(000, _("SOS_LABEL: VolSessTime mismatch for JobId=%u. DB=%d Vol=%d\n"),
557 jr.VolSessionTime, rec->VolSessionTime);
558 return true; /* ignore error */
560 if (jr.PoolId != pr.PoolId) {
561 Pmsg3(000, _("SOS_LABEL: PoolId mismatch for JobId=%u. DB=%d Vol=%d\n"),
563 jr.PoolId, pr.PoolId);
564 return true; /* ignore error */
569 unser_session_label(&elabel, rec);
571 /* Create FileSet record */
572 bstrncpy(fsr.FileSet, label.FileSetName, sizeof(fsr.FileSet));
573 bstrncpy(fsr.MD5, label.FileSetMD5, sizeof(fsr.MD5));
574 create_fileset_record(db, &fsr);
575 jr.FileSetId = fsr.FileSetId;
577 mjcr = get_jcr_by_session(rec->VolSessionId, rec->VolSessionTime);
579 Pmsg2(000, _("Could not find SessId=%d SessTime=%d for EOS record.\n"),
580 rec->VolSessionId, rec->VolSessionTime);
584 /* Do the final update to the Job record */
585 update_job_record(db, &jr, &elabel, rec);
587 mjcr->end_time = jr.EndTime;
588 mjcr->JobStatus = JS_Terminated;
590 /* Create JobMedia record */
591 mjcr->read_dcr->VolLastIndex = dcr->VolLastIndex;
592 create_jobmedia_record(db, mjcr);
593 detach_dcr_from_dev(mjcr->read_dcr);
601 case EOT_LABEL: /* end of all tapes */
603 * Wiffle through all jobs still open and close
608 foreach_dlist(mdcr, dev->attached_dcrs) {
609 JCR *mjcr = mdcr->jcr;
610 if (!mjcr || mjcr->JobId == 0) {
613 jr.JobId = mjcr->JobId;
614 /* Mark Job as Error Terimined */
615 jr.JobStatus = JS_ErrorTerminated;
616 jr.JobFiles = mjcr->JobFiles;
617 jr.JobBytes = mjcr->JobBytes;
618 jr.VolSessionId = mjcr->VolSessionId;
619 jr.VolSessionTime = mjcr->VolSessionTime;
620 jr.JobTDate = (utime_t)mjcr->start_time;
621 jr.ClientId = mjcr->ClientId;
622 if (!db_update_job_end_record(bjcr, db, &jr, false)) {
623 Pmsg1(0, _("Could not update job record. ERR=%s\n"), db_strerror(db));
625 mjcr->read_dcr = NULL;
629 mr.VolFiles = rec->File;
630 mr.VolBlocks = rec->Block;
631 mr.VolBytes += mr.VolBlocks * WRITE_BLKHDR_LENGTH; /* approx. */
633 update_media_record(db, &mr);
634 Pmsg3(0, _("End of all Volumes. VolFiles=%u VolBlocks=%u VolBytes=%s\n"), mr.VolFiles,
635 mr.VolBlocks, edit_uint64_with_commas(mr.VolBytes, ec1));
643 mjcr = get_jcr_by_session(rec->VolSessionId, rec->VolSessionTime);
645 if (mr.VolJobs > 0) {
646 Pmsg2(000, _("Could not find Job for SessId=%d SessTime=%d record.\n"),
647 rec->VolSessionId, rec->VolSessionTime);
653 dcr = mjcr->read_dcr;
654 if (dcr->VolFirstIndex == 0) {
655 dcr->VolFirstIndex = block->FirstIndex;
658 /* File Attributes stream */
659 switch (rec->Stream) {
660 case STREAM_UNIX_ATTRIBUTES:
661 case STREAM_UNIX_ATTRIBUTES_EX:
663 if (!unpack_attributes_record(bjcr, rec->Stream, rec->data, attr)) {
664 Emsg0(M_ERROR_TERM, 0, _("Cannot continue.\n"));
667 if (attr->file_index != rec->FileIndex) {
668 Emsg2(M_ERROR_TERM, 0, _("Record header file index %ld not equal record index %ld\n"),
669 rec->FileIndex, attr->file_index);
673 decode_stat(attr->attr, &attr->statp, &attr->LinkFI);
674 build_attr_output_fnames(bjcr, attr);
675 print_ls_output(bjcr, attr);
677 fr.JobId = mjcr->JobId;
680 if (verbose && (num_files & 0x7FFF) == 0) {
681 char ed1[30], ed2[30], ed3[30], ed4[30];
682 Pmsg4(000, _("%s file records. At file:blk=%s:%s bytes=%s\n"),
683 edit_uint64_with_commas(num_files, ed1),
684 edit_uint64_with_commas(rec->File, ed2),
685 edit_uint64_with_commas(rec->Block, ed3),
686 edit_uint64_with_commas(mr.VolBytes, ed4));
688 create_file_attributes_record(db, mjcr, attr->fname, attr->lname,
689 attr->type, attr->attr, rec);
694 case STREAM_WIN32_DATA:
695 case STREAM_FILE_DATA:
696 case STREAM_SPARSE_DATA:
697 case STREAM_ENCRYPTED_FILE_DATA:
698 case STREAM_ENCRYPTED_WIN32_DATA:
699 case STREAM_ENCRYPTED_MACOS_FORK_DATA:
701 * For encrypted stream, this is an approximation.
702 * The data must be decrypted to know the correct length.
704 mjcr->JobBytes += rec->data_len;
705 if (rec->Stream == STREAM_SPARSE_DATA) {
706 mjcr->JobBytes -= sizeof(uint64_t);
709 free_jcr(mjcr); /* done using JCR */
712 case STREAM_GZIP_DATA:
713 case STREAM_ENCRYPTED_FILE_GZIP_DATA:
714 case STREAM_ENCRYPTED_WIN32_GZIP_DATA:
715 /* No correct, we should (decrypt and) expand it
718 mjcr->JobBytes += rec->data_len;
722 case STREAM_SPARSE_GZIP_DATA:
723 mjcr->JobBytes += rec->data_len - sizeof(uint64_t); /* No correct, we should expand it */
724 free_jcr(mjcr); /* done using JCR */
727 /* Win32 GZIP stream */
728 case STREAM_WIN32_GZIP_DATA:
729 mjcr->JobBytes += rec->data_len;
730 free_jcr(mjcr); /* done using JCR */
733 case STREAM_MD5_DIGEST:
734 bin_to_base64(digest, sizeof(digest), (char *)rec->data, CRYPTO_DIGEST_MD5_SIZE, true);
736 Pmsg1(000, _("Got MD5 record: %s\n"), digest);
738 update_digest_record(db, digest, rec, CRYPTO_DIGEST_MD5);
741 case STREAM_SHA1_DIGEST:
742 bin_to_base64(digest, sizeof(digest), (char *)rec->data, CRYPTO_DIGEST_SHA1_SIZE, true);
744 Pmsg1(000, _("Got SHA1 record: %s\n"), digest);
746 update_digest_record(db, digest, rec, CRYPTO_DIGEST_SHA1);
749 case STREAM_SHA256_DIGEST:
750 bin_to_base64(digest, sizeof(digest), (char *)rec->data, CRYPTO_DIGEST_SHA256_SIZE, true);
752 Pmsg1(000, _("Got SHA256 record: %s\n"), digest);
754 update_digest_record(db, digest, rec, CRYPTO_DIGEST_SHA256);
757 case STREAM_SHA512_DIGEST:
758 bin_to_base64(digest, sizeof(digest), (char *)rec->data, CRYPTO_DIGEST_SHA512_SIZE, true);
760 Pmsg1(000, _("Got SHA512 record: %s\n"), digest);
762 update_digest_record(db, digest, rec, CRYPTO_DIGEST_SHA512);
765 case STREAM_ENCRYPTED_SESSION_DATA:
766 // TODO landonf: Investigate crypto support in bscan
768 Pmsg0(000, _("Got signed digest record\n"));
772 case STREAM_SIGNED_DIGEST:
773 // TODO landonf: Investigate crypto support in bscan
775 Pmsg0(000, _("Got signed digest record\n"));
779 case STREAM_PROGRAM_NAMES:
781 Pmsg1(000, _("Got Prog Names Stream: %s\n"), rec->data);
785 case STREAM_PROGRAM_DATA:
787 Pmsg0(000, _("Got Prog Data Stream record.\n"));
791 case STREAM_UNIX_ACCESS_ACL: /* Standard ACL attributes on UNIX */
792 case STREAM_UNIX_DEFAULT_ACL: /* Default ACL attributes on UNIX */
793 /* Ignore Unix attributes */
797 Pmsg2(0, _("Unknown stream type!!! stream=%d len=%i\n"), rec->Stream, rec->data_len);
804 * Free the Job Control Record if no one is still using it.
805 * Called from main free_jcr() routine in src/lib/jcr.c so
806 * that we can do our Director specific cleanup of the jcr.
808 static void bscan_free_jcr(JCR *jcr)
810 Dmsg0(200, "Start bscan free_jcr\n");
812 if (jcr->file_bsock) {
813 Dmsg0(200, "Close File bsock\n");
814 bnet_close(jcr->file_bsock);
816 if (jcr->store_bsock) {
817 Dmsg0(200, "Close Store bsock\n");
818 bnet_close(jcr->store_bsock);
820 if (jcr->RestoreBootstrap) {
821 free(jcr->RestoreBootstrap);
828 free_dcr(jcr->read_dcr);
829 jcr->read_dcr = NULL;
831 Dmsg0(200, "End bscan free_jcr\n");
835 * We got a File Attributes record on the tape. Now, lookup the Job
836 * record, and then create the attributes record.
838 static int create_file_attributes_record(B_DB *db, JCR *mjcr,
839 char *fname, char *lname, int type,
840 char *ap, DEV_RECORD *rec)
842 DCR *dcr = mjcr->read_dcr;
845 ar.ClientId = mjcr->ClientId;
846 ar.JobId = mjcr->JobId;
847 ar.Stream = rec->Stream;
848 if (type == FT_DELETED) {
851 ar.FileIndex = rec->FileIndex;
854 if (dcr->VolFirstIndex == 0) {
855 dcr->VolFirstIndex = rec->FileIndex;
857 dcr->FileIndex = rec->FileIndex;
864 if (!db_create_file_attributes_record(bjcr, db, &ar)) {
865 Pmsg1(0, _("Could not create File Attributes record. ERR=%s\n"), db_strerror(db));
868 mjcr->FileId = ar.FileId;
871 Pmsg1(000, _("Created File record: %s\n"), fname);
877 * For each Volume we see, we create a Medium record
879 static int create_media_record(B_DB *db, MEDIA_DBR *mr, VOLUME_LABEL *vl)
884 /* We mark Vols as Archive to keep them from being re-written */
885 bstrncpy(mr->VolStatus, "Archive", sizeof(mr->VolStatus));
886 mr->VolRetention = 365 * 3600 * 24; /* 1 year */
888 if (vl->VerNum >= 11) {
889 mr->FirstWritten = btime_to_utime(vl->write_btime);
890 mr->LabelDate = btime_to_utime(vl->label_btime);
892 /* DEPRECATED DO NOT USE */
893 dt.julian_day_number = vl->write_date;
894 dt.julian_day_fraction = vl->write_time;
896 mr->FirstWritten = mktime(&tm);
897 dt.julian_day_number = vl->label_date;
898 dt.julian_day_fraction = vl->label_time;
900 mr->LabelDate = mktime(&tm);
902 lasttime = mr->LabelDate;
903 if (mr->VolJobs == 0) {
906 if (mr->VolMounts == 0) {
914 if (!db_create_media_record(bjcr, db, mr)) {
915 Pmsg1(0, _("Could not create media record. ERR=%s\n"), db_strerror(db));
918 if (!db_update_media_record(bjcr, db, mr)) {
919 Pmsg1(0, _("Could not update media record. ERR=%s\n"), db_strerror(db));
923 Pmsg1(000, _("Created Media record for Volume: %s\n"), mr->VolumeName);
930 * Called at end of media to update it
932 static bool update_media_record(B_DB *db, MEDIA_DBR *mr)
934 if (!update_db && !update_vol_info) {
938 mr->LastWritten = lasttime;
939 if (!db_update_media_record(bjcr, db, mr)) {
940 Pmsg1(0, _("Could not update media record. ERR=%s\n"), db_strerror(db));
944 Pmsg1(000, _("Updated Media record at end of Volume: %s\n"), mr->VolumeName);
951 static int create_pool_record(B_DB *db, POOL_DBR *pr)
955 pr->VolRetention = 355 * 3600 * 24; /* 1 year */
960 if (!db_create_pool_record(bjcr, db, pr)) {
961 Pmsg1(0, _("Could not create pool record. ERR=%s\n"), db_strerror(db));
965 Pmsg1(000, _("Created Pool record for Pool: %s\n"), pr->Name);
973 * Called from SOS to create a client for the current Job
975 static int create_client_record(B_DB *db, CLIENT_DBR *cr)
978 * Note, update_db can temporarily be set false while
979 * updating the database, so we must ensure that ClientId is non-zero.
983 if (!db_get_client_record(bjcr, db, cr)) {
984 Pmsg1(0, _("Could not get Client record. ERR=%s\n"), db_strerror(db));
989 if (!db_create_client_record(bjcr, db, cr)) {
990 Pmsg1(0, _("Could not create Client record. ERR=%s\n"), db_strerror(db));
994 Pmsg1(000, _("Created Client record for Client: %s\n"), cr->Name);
999 static int create_fileset_record(B_DB *db, FILESET_DBR *fsr)
1005 if (fsr->MD5[0] == 0) {
1006 fsr->MD5[0] = ' '; /* Equivalent to nothing */
1009 if (db_get_fileset_record(bjcr, db, fsr)) {
1011 Pmsg1(000, _("Fileset \"%s\" already exists.\n"), fsr->FileSet);
1014 if (!db_create_fileset_record(bjcr, db, fsr)) {
1015 Pmsg2(0, _("Could not create FileSet record \"%s\". ERR=%s\n"),
1016 fsr->FileSet, db_strerror(db));
1020 Pmsg1(000, _("Created FileSet record \"%s\"\n"), fsr->FileSet);
1027 * Simulate the two calls on the database to create
1028 * the Job record and to update it when the Job actually
1031 static JCR *create_job_record(B_DB *db, JOB_DBR *jr, SESSION_LABEL *label,
1035 struct date_time dt;
1038 jr->JobId = label->JobId;
1039 jr->JobType = label->JobType;
1040 jr->JobLevel = label->JobLevel;
1041 jr->JobStatus = JS_Created;
1042 bstrncpy(jr->Name, label->JobName, sizeof(jr->Name));
1043 bstrncpy(jr->Job, label->Job, sizeof(jr->Job));
1044 if (label->VerNum >= 11) {
1045 jr->SchedTime = btime_to_unix(label->write_btime);
1047 dt.julian_day_number = label->write_date;
1048 dt.julian_day_fraction = label->write_time;
1049 tm_decode(&dt, &tm);
1050 jr->SchedTime = mktime(&tm);
1053 jr->StartTime = jr->SchedTime;
1054 jr->JobTDate = (utime_t)jr->SchedTime;
1055 jr->VolSessionId = rec->VolSessionId;
1056 jr->VolSessionTime = rec->VolSessionTime;
1058 /* Now create a JCR as if starting the Job */
1059 mjcr = create_jcr(jr, rec, label->JobId);
1065 /* This creates the bare essentials */
1066 if (!db_create_job_record(bjcr, db, jr)) {
1067 Pmsg1(0, _("Could not create JobId record. ERR=%s\n"), db_strerror(db));
1071 /* This adds the client, StartTime, JobTDate, ... */
1072 if (!db_update_job_start_record(bjcr, db, jr)) {
1073 Pmsg1(0, _("Could not update job start record. ERR=%s\n"), db_strerror(db));
1076 Pmsg2(000, _("Created new JobId=%u record for original JobId=%u\n"), jr->JobId,
1078 mjcr->JobId = jr->JobId; /* set new JobId */
1083 * Simulate the database call that updates the Job
1084 * at Job termination time.
1086 static int update_job_record(B_DB *db, JOB_DBR *jr, SESSION_LABEL *elabel,
1089 struct date_time dt;
1093 mjcr = get_jcr_by_session(rec->VolSessionId, rec->VolSessionTime);
1095 Pmsg2(000, _("Could not find SessId=%d SessTime=%d for EOS record.\n"),
1096 rec->VolSessionId, rec->VolSessionTime);
1099 if (elabel->VerNum >= 11) {
1100 jr->EndTime = btime_to_unix(elabel->write_btime);
1102 dt.julian_day_number = elabel->write_date;
1103 dt.julian_day_fraction = elabel->write_time;
1104 tm_decode(&dt, &tm);
1105 jr->EndTime = mktime(&tm);
1107 lasttime = jr->EndTime;
1108 mjcr->end_time = jr->EndTime;
1110 jr->JobId = mjcr->JobId;
1111 jr->JobStatus = elabel->JobStatus;
1112 mjcr->JobStatus = elabel->JobStatus;
1113 jr->JobFiles = elabel->JobFiles;
1114 jr->JobBytes = elabel->JobBytes;
1115 jr->VolSessionId = rec->VolSessionId;
1116 jr->VolSessionTime = rec->VolSessionTime;
1117 jr->JobTDate = (utime_t)mjcr->start_time;
1118 jr->ClientId = mjcr->ClientId;
1125 if (!db_update_job_end_record(bjcr, db, jr, false)) {
1126 Pmsg2(0, _("Could not update JobId=%u record. ERR=%s\n"), jr->JobId, db_strerror(db));
1131 Pmsg3(000, _("Updated Job termination record for JobId=%u Level=%s TermStat=%c\n"),
1132 jr->JobId, job_level_to_str(mjcr->JobLevel), jr->JobStatus);
1135 const char *term_msg;
1136 static char term_code[70];
1137 char sdt[50], edt[50];
1138 char ec1[30], ec2[30], ec3[30];
1140 switch (mjcr->JobStatus) {
1142 term_msg = _("Backup OK");
1145 case JS_ErrorTerminated:
1146 term_msg = _("*** Backup Error ***");
1149 term_msg = _("Backup Canceled");
1152 term_msg = term_code;
1153 sprintf(term_code, _("Job Termination code: %d"), mjcr->JobStatus);
1156 bstrftime(sdt, sizeof(sdt), mjcr->start_time);
1157 bstrftime(edt, sizeof(edt), mjcr->end_time);
1158 Pmsg14(000, _("%s\n"
1162 "Backup Level: %s\n"
1166 "Files Written: %s\n"
1167 "Bytes Written: %s\n"
1168 "Volume Session Id: %d\n"
1169 "Volume Session Time: %d\n"
1170 "Last Volume Bytes: %s\n"
1171 "Termination: %s\n\n"),
1176 job_level_to_str(mjcr->JobLevel),
1180 edit_uint64_with_commas(mjcr->JobFiles, ec1),
1181 edit_uint64_with_commas(mjcr->JobBytes, ec2),
1183 mjcr->VolSessionTime,
1184 edit_uint64_with_commas(mr.VolBytes, ec3),
1191 static int create_jobmedia_record(B_DB *db, JCR *mjcr)
1194 DCR *dcr = mjcr->read_dcr;
1196 dcr->EndBlock = dev->EndBlock;
1197 dcr->EndFile = dev->EndFile;
1198 dcr->VolMediaId = dev->VolCatInfo.VolMediaId;
1200 memset(&jmr, 0, sizeof(jmr));
1201 jmr.JobId = mjcr->JobId;
1202 jmr.MediaId = mr.MediaId;
1203 jmr.FirstIndex = dcr->VolFirstIndex;
1204 jmr.LastIndex = dcr->VolLastIndex;
1205 jmr.StartFile = dcr->StartFile;
1206 jmr.EndFile = dcr->EndFile;
1207 jmr.StartBlock = dcr->StartBlock;
1208 jmr.EndBlock = dcr->EndBlock;
1215 if (!db_create_jobmedia_record(bjcr, db, &jmr)) {
1216 Pmsg1(0, _("Could not create JobMedia record. ERR=%s\n"), db_strerror(db));
1220 Pmsg2(000, _("Created JobMedia record JobId %d, MediaId %d\n"),
1221 jmr.JobId, jmr.MediaId);
1227 * Simulate the database call that updates the MD5/SHA1 record
1229 static int update_digest_record(B_DB *db, char *digest, DEV_RECORD *rec, int type)
1233 mjcr = get_jcr_by_session(rec->VolSessionId, rec->VolSessionTime);
1235 if (mr.VolJobs > 0) {
1236 Pmsg2(000, _("Could not find SessId=%d SessTime=%d for MD5/SHA1 record.\n"),
1237 rec->VolSessionId, rec->VolSessionTime);
1244 if (!update_db || mjcr->FileId == 0) {
1249 if (!db_add_digest_to_file_record(bjcr, db, mjcr->FileId, digest, type)) {
1250 Pmsg1(0, _("Could not add MD5/SHA1 to File record. ERR=%s\n"), db_strerror(db));
1255 Pmsg0(000, _("Updated MD5/SHA1 record\n"));
1263 * Create a JCR as if we are really starting the job
1265 static JCR *create_jcr(JOB_DBR *jr, DEV_RECORD *rec, uint32_t JobId)
1269 * Transfer as much as possible to the Job JCR. Most important is
1270 * the JobId and the ClientId.
1272 jobjcr = new_jcr(sizeof(JCR), bscan_free_jcr);
1273 jobjcr->JobType = jr->JobType;
1274 jobjcr->JobLevel = jr->JobLevel;
1275 jobjcr->JobStatus = jr->JobStatus;
1276 bstrncpy(jobjcr->Job, jr->Job, sizeof(jobjcr->Job));
1277 jobjcr->JobId = JobId; /* this is JobId on tape */
1278 jobjcr->sched_time = jr->SchedTime;
1279 jobjcr->start_time = jr->StartTime;
1280 jobjcr->VolSessionId = rec->VolSessionId;
1281 jobjcr->VolSessionTime = rec->VolSessionTime;
1282 jobjcr->ClientId = jr->ClientId;
1283 jobjcr->dcr = jobjcr->read_dcr = new_dcr(jobjcr, NULL, dev);
1288 /* Dummies to replace askdir.c */
1289 bool dir_find_next_appendable_volume(DCR *dcr) { return 1;}
1290 bool dir_update_volume_info(DCR *dcr, bool relabel, bool update_LastWritten) { return 1; }
1291 bool dir_create_jobmedia_record(DCR *dcr) { return 1; }
1292 bool dir_ask_sysop_to_create_appendable_volume(DCR *dcr) { return 1; }
1293 bool dir_update_file_attributes(DCR *dcr, DEV_RECORD *rec) { return 1;}
1294 bool dir_send_job_status(JCR *jcr) {return 1;}
1295 int generate_job_event(JCR *jcr, const char *event) { return 1; }
1297 bool dir_ask_sysop_to_mount_volume(DCR *dcr, int /*mode*/)
1299 DEVICE *dev = dcr->dev;
1300 Dmsg0(20, "Enter dir_ask_sysop_to_mount_volume\n");
1301 /* Close device so user can use autochanger if desired */
1302 fprintf(stderr, _("Mount Volume \"%s\" on device %s and press return when ready: "),
1303 dcr->VolumeName, dev->print_name());
1309 bool dir_get_volume_info(DCR *dcr, enum get_vol_info_rw writing)
1311 Dmsg0(100, "Fake dir_get_volume_info\n");
1312 bstrncpy(dcr->VolCatInfo.VolCatName, dcr->VolumeName, sizeof(dcr->VolCatInfo.VolCatName));
1313 dcr->VolCatInfo.VolCatParts = find_num_dvd_parts(dcr);
1314 Dmsg2(500, "Vol=%s num_parts=%d\n", dcr->VolCatInfo.VolCatName, dcr->VolCatInfo.VolCatParts);