3 * Program to scan a Bacula Volume and compare it with
4 * the catalog and optionally synchronize the catalog
7 * Kern E. Sibbald, December 2001
13 Bacula® - The Network Backup Solution
15 Copyright (C) 2001-2006 Free Software Foundation Europe e.V.
17 The main author of Bacula is Kern Sibbald, with contributions from
18 many others, a complete list can be found in the file AUTHORS.
19 This program is Free Software; you can redistribute it and/or
20 modify it under the terms of version two of the GNU General Public
21 License as published by the Free Software Foundation and included
24 This program is distributed in the hope that it will be useful, but
25 WITHOUT ANY WARRANTY; without even the implied warranty of
26 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
27 General Public License for more details.
29 You should have received a copy of the GNU General Public License
30 along with this program; if not, write to the Free Software
31 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
34 Bacula® is a registered trademark of John Walker.
35 The licensor of Bacula is the Free Software Foundation Europe
36 (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zürich,
37 Switzerland, email:ftf@fsfeurope.org.
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_name = "bacula";
86 static const char *db_user = "bacula";
87 static const char *db_password = "";
88 static const char *db_host = NULL;
89 static const char *wd = NULL;
90 static bool update_db = false;
91 static bool update_vol_info = false;
92 static bool list_records = false;
93 static int ignored_msgs = 0;
95 static uint64_t currentVolumeSize;
96 static int last_pct = -1;
97 static bool showProgress = false;
98 static int num_jobs = 0;
99 static int num_pools = 0;
100 static int num_media = 0;
101 static int num_files = 0;
103 #define CONFIG_FILE "bacula-sd.conf"
104 char *configfile = NULL;
105 STORES *me = NULL; /* our Global resource */
106 bool forge_on = false; /* proceed inspite of I/O errors */
107 pthread_mutex_t device_release_mutex = PTHREAD_MUTEX_INITIALIZER;
108 pthread_cond_t wait_device_release = PTHREAD_COND_INITIALIZER;
115 "\nVersion: %s (%s)\n\n"
116 "Usage: bscan [ options ] <bacula-archive>\n"
117 " -b bootstrap specify a bootstrap file\n"
118 " -c <file> specify configuration file\n"
119 " -d <nn> set debug level to nn\n"
120 " -m update media info in database\n"
121 " -n <name> specify the database name (default bacula)\n"
122 " -u <user> specify database user name (default bacula)\n"
123 " -P <password specify database password (default none)\n"
124 " -h <host> specify database host (default NULL)\n"
125 " -p proceed inspite of I/O errors\n"
127 " -s synchronize or store in database\n"
128 " -S show scan progress periodically\n"
130 " -V <Volumes> specify Volume names (separated by |)\n"
131 " -w <dir> specify working directory (default from conf file)\n"
132 " -? print this message\n\n"), 2001, VERSION, BDATE);
136 int main (int argc, char *argv[])
139 struct stat stat_buf;
140 char *VolumeName = NULL;
142 setlocale(LC_ALL, "");
143 bindtextdomain("bacula", LOCALEDIR);
144 textdomain("bacula");
147 my_name_is(argc, argv, "bscan");
148 init_msg(NULL, NULL);
152 while ((ch = getopt(argc, argv, "b:c:d:h:mn:pP:rsSu:vV:w:?")) != -1) {
158 bsr = parse_bsr(NULL, optarg);
161 case 'c': /* specify config file */
162 if (configfile != NULL) {
165 configfile = bstrdup(optarg);
168 case 'd': /* debug level */
169 debug_level = atoi(optarg);
170 if (debug_level <= 0)
179 update_vol_info = true;
191 db_password = optarg;
210 case 'V': /* Volume name */
228 Pmsg0(0, _("Wrong number of arguments: \n"));
232 if (configfile == NULL) {
233 configfile = bstrdup(CONFIG_FILE);
236 parse_config(configfile);
238 me = (STORES *)GetNextRes(R_STORAGE, NULL);
241 Emsg1(M_ERROR_TERM, 0, _("No Storage resource defined in %s. Cannot continue.\n"),
245 /* Check if -w option given, otherwise use resource for working directory */
247 working_directory = wd;
248 } else if (!me->working_directory) {
249 Emsg1(M_ERROR_TERM, 0, _("No Working Directory defined in %s. Cannot continue.\n"),
252 working_directory = me->working_directory;
255 /* Check that working directory is good */
256 if (stat(working_directory, &stat_buf) != 0) {
257 Emsg1(M_ERROR_TERM, 0, _("Working Directory: %s not found. Cannot continue.\n"),
260 if (!S_ISDIR(stat_buf.st_mode)) {
261 Emsg1(M_ERROR_TERM, 0, _("Working Directory: %s is not a directory. Cannot continue.\n"),
265 bjcr = setup_jcr("bscan", argv[0], bsr, VolumeName, 1); /* read device */
269 dev = bjcr->read_dcr->dev;
273 fstat(dev->fd(), &sb);
274 currentVolumeSize = sb.st_size;
275 Pmsg1(000, _("First Volume Size = %sn"),
276 edit_uint64(currentVolumeSize, ed1));
279 if ((db=db_init_database(NULL, db_name, db_user, db_password,
280 db_host, 0, NULL, 0)) == NULL) {
281 Emsg0(M_ERROR_TERM, 0, _("Could not init Bacula database\n"));
283 if (!db_open_database(NULL, db)) {
284 Emsg0(M_ERROR_TERM, 0, db_strerror(db));
286 Dmsg0(200, "Database opened\n");
288 Pmsg2(000, _("Using Database: %s, User: %s\n"), db_name, db_user);
293 printf("Records added or updated in the catalog:\n%7d Media\n%7d Pool\n%7d Job\n%7d File\n",
294 num_media, num_pools, num_jobs, num_files);
297 printf("Records would have been added or updated in the catalog:\n%7d Media\n%7d Pool\n%7d Job\n%7d File\n",
298 num_media, num_pools, num_jobs, num_files);
307 * We are at the end of reading a tape. Now, we simulate handling
308 * the end of writing a tape by wiffling through the attached
309 * jcrs creating jobmedia records.
311 static bool bscan_mount_next_read_volume(DCR *dcr)
313 DEVICE *dev = dcr->dev;
315 Dmsg1(100, "Walk attached jcrs. Volume=%s\n", dev->VolCatInfo.VolCatName);
316 foreach_dlist(mdcr, dev->attached_dcrs) {
317 JCR *mjcr = mdcr->jcr;
318 if (mjcr->JobId == 0) {
322 Pmsg1(000, _("Create JobMedia for Job %s\n"), mjcr->Job);
324 if (dev->is_tape()) {
325 mdcr->EndBlock = dcr->EndBlock;
326 mdcr->EndFile = dcr->EndFile;
328 // mdcr->EndBlock = (uint32_t)dcr->file_addr;
329 // mdcr->EndFile = (uint32_t)(dcr->file_addr >> 32);
331 mjcr->read_dcr->VolLastIndex = dcr->VolLastIndex;
332 if (!create_jobmedia_record(db, mjcr)) {
333 Pmsg2(000, _("Could not create JobMedia record for Volume=%s Job=%s\n"),
334 dev->VolCatInfo.VolCatName, mjcr->Job);
337 /* Now let common read routine get up next tape. Note,
338 * we call mount_next... with bscan's jcr because that is where we
339 * have the Volume list, but we get attached.
341 bool stat = mount_next_read_volume(dcr);
346 fstat(dev->fd(), &sb);
347 currentVolumeSize = sb.st_size;
348 Pmsg1(000, _("First Volume Size = %sn"),
349 edit_uint64(currentVolumeSize, ed1));
354 static void do_scan()
358 memset(&ar, 0, sizeof(ar));
359 memset(&pr, 0, sizeof(pr));
360 memset(&jr, 0, sizeof(jr));
361 memset(&cr, 0, sizeof(cr));
362 memset(&fsr, 0, sizeof(fsr));
363 memset(&fr, 0, sizeof(fr));
365 /* Detach bscan's jcr as we are not a real Job on the tape */
367 read_records(bjcr->read_dcr, record_cb, bscan_mount_next_read_volume);
370 db_write_batch_file_records(bjcr); /* used by bulk batch file insert */
376 * Returns: true if OK
379 static bool record_cb(DCR *dcr, DEV_RECORD *rec)
383 DEVICE *dev = dcr->dev;
384 JCR *bjcr = dcr->jcr;
385 DEV_BLOCK *block = dcr->block;
386 char digest[BASE64_SIZE(CRYPTO_DIGEST_MAX_SIZE)];
388 if (rec->data_len > 0) {
389 mr.VolBytes += rec->data_len + WRITE_RECHDR_LENGTH; /* Accumulate Volume bytes */
390 if (showProgress && currentVolumeSize > 0) {
391 int pct = (mr.VolBytes * 100) / currentVolumeSize;
392 if (pct != last_pct) {
393 fprintf(stdout, _("done: %d%%\n"), pct);
401 Pmsg5(000, _("Record: SessId=%u SessTim=%u FileIndex=%d Stream=%d len=%u\n"),
402 rec->VolSessionId, rec->VolSessionTime, rec->FileIndex,
403 rec->Stream, rec->data_len);
406 * Check for Start or End of Session Record
409 if (rec->FileIndex < 0) {
410 bool save_update_db = update_db;
413 dump_label_record(dev, rec, 1);
415 switch (rec->FileIndex) {
417 Pmsg0(000, _("Volume is prelabeled. This tape cannot be scanned.\n"));
422 unser_volume_label(dev, rec);
423 /* Check Pool info */
424 bstrncpy(pr.Name, dev->VolHdr.PoolName, sizeof(pr.Name));
425 bstrncpy(pr.PoolType, dev->VolHdr.PoolType, sizeof(pr.PoolType));
427 if (db_get_pool_record(bjcr, db, &pr)) {
429 Pmsg1(000, _("Pool record for %s found in DB.\n"), pr.Name);
433 Pmsg1(000, _("VOL_LABEL: Pool record not found for Pool: %s\n"),
436 create_pool_record(db, &pr);
438 if (strcmp(pr.PoolType, dev->VolHdr.PoolType) != 0) {
439 Pmsg2(000, _("VOL_LABEL: PoolType mismatch. DB=%s Vol=%s\n"),
440 pr.PoolType, dev->VolHdr.PoolType);
442 } else if (verbose) {
443 Pmsg1(000, _("Pool type \"%s\" is OK.\n"), pr.PoolType);
446 /* Check Media Info */
447 memset(&mr, 0, sizeof(mr));
448 bstrncpy(mr.VolumeName, dev->VolHdr.VolumeName, sizeof(mr.VolumeName));
449 mr.PoolId = pr.PoolId;
451 if (db_get_media_record(bjcr, db, &mr)) {
453 Pmsg1(000, _("Media record for %s found in DB.\n"), mr.VolumeName);
455 /* Clear out some volume statistics that will be updated */
456 mr.VolJobs = mr.VolFiles = mr.VolBlocks = 0;
457 mr.VolBytes = rec->data_len + 20;
460 Pmsg1(000, _("VOL_LABEL: Media record not found for Volume: %s\n"),
463 bstrncpy(mr.MediaType, dev->VolHdr.MediaType, sizeof(mr.MediaType));
464 create_media_record(db, &mr, &dev->VolHdr);
466 if (strcmp(mr.MediaType, dev->VolHdr.MediaType) != 0) {
467 Pmsg2(000, _("VOL_LABEL: MediaType mismatch. DB=%s Vol=%s\n"),
468 mr.MediaType, dev->VolHdr.MediaType);
469 return true; /* ignore error */
470 } else if (verbose) {
471 Pmsg1(000, _("Media type \"%s\" is OK.\n"), mr.MediaType);
473 /* Reset some DCR variables */
474 foreach_dlist(dcr, dev->attached_dcrs) {
475 dcr->VolFirstIndex = dcr->FileIndex = 0;
476 dcr->StartBlock = dcr->EndBlock = 0;
477 dcr->StartFile = dcr->EndFile = 0;
480 Pmsg1(000, _("VOL_LABEL: OK for Volume: %s\n"), mr.VolumeName);
486 if (ignored_msgs > 0) {
487 Pmsg1(000, _("%d \"errors\" ignored before first Start of Session record.\n"),
491 unser_session_label(&label, rec);
492 memset(&jr, 0, sizeof(jr));
493 bstrncpy(jr.Job, label.Job, sizeof(jr.Job));
494 if (db_get_job_record(bjcr, db, &jr)) {
495 /* Job record already exists in DB */
496 update_db = false; /* don't change db in create_job_record */
498 Pmsg1(000, _("SOS_LABEL: Found Job record for JobId: %d\n"), jr.JobId);
501 /* Must create a Job record in DB */
503 Pmsg1(000, _("SOS_LABEL: Job record not found for JobId: %d\n"),
507 /* Create Client record if not already there */
508 bstrncpy(cr.Name, label.ClientName, sizeof(cr.Name));
509 create_client_record(db, &cr);
510 jr.ClientId = cr.ClientId;
512 /* process label, if Job record exists don't update db */
513 mjcr = create_job_record(db, &jr, &label, rec);
514 dcr = mjcr->read_dcr;
515 update_db = save_update_db;
517 jr.PoolId = pr.PoolId;
519 /* Set start positions into JCR */
520 if (dev->is_tape()) {
522 * Note, we have already advanced past current block,
523 * so the correct number is block_num - 1
525 dcr->StartBlock = dev->block_num - 1;
526 dcr->StartFile = dev->file;
528 dcr->StartBlock = (uint32_t)dev->file_addr;
529 dcr->StartFile = (uint32_t)(dev->file_addr >> 32);
532 mjcr->start_time = jr.StartTime;
533 mjcr->JobLevel = jr.JobLevel;
535 mjcr->client_name = get_pool_memory(PM_FNAME);
536 pm_strcpy(mjcr->client_name, label.ClientName);
537 mjcr->fileset_name = get_pool_memory(PM_FNAME);
538 pm_strcpy(mjcr->fileset_name, label.FileSetName);
539 bstrncpy(dcr->pool_type, label.PoolType, sizeof(dcr->pool_type));
540 bstrncpy(dcr->pool_name, label.PoolName, sizeof(dcr->pool_name));
542 if (rec->VolSessionId != jr.VolSessionId) {
543 Pmsg3(000, _("SOS_LABEL: VolSessId mismatch for JobId=%u. DB=%d Vol=%d\n"),
545 jr.VolSessionId, rec->VolSessionId);
546 return true; /* ignore error */
548 if (rec->VolSessionTime != jr.VolSessionTime) {
549 Pmsg3(000, _("SOS_LABEL: VolSessTime mismatch for JobId=%u. DB=%d Vol=%d\n"),
551 jr.VolSessionTime, rec->VolSessionTime);
552 return true; /* ignore error */
554 if (jr.PoolId != pr.PoolId) {
555 Pmsg3(000, _("SOS_LABEL: PoolId mismatch for JobId=%u. DB=%d Vol=%d\n"),
557 jr.PoolId, pr.PoolId);
558 return true; /* ignore error */
563 unser_session_label(&elabel, rec);
565 /* Create FileSet record */
566 bstrncpy(fsr.FileSet, label.FileSetName, sizeof(fsr.FileSet));
567 bstrncpy(fsr.MD5, label.FileSetMD5, sizeof(fsr.MD5));
568 create_fileset_record(db, &fsr);
569 jr.FileSetId = fsr.FileSetId;
571 mjcr = get_jcr_by_session(rec->VolSessionId, rec->VolSessionTime);
573 Pmsg2(000, _("Could not find SessId=%d SessTime=%d for EOS record.\n"),
574 rec->VolSessionId, rec->VolSessionTime);
578 /* Do the final update to the Job record */
579 update_job_record(db, &jr, &elabel, rec);
581 mjcr->end_time = jr.EndTime;
582 mjcr->JobStatus = JS_Terminated;
584 /* Create JobMedia record */
585 mjcr->read_dcr->VolLastIndex = dcr->VolLastIndex;
586 create_jobmedia_record(db, mjcr);
587 detach_dcr_from_dev(mjcr->read_dcr);
595 case EOT_LABEL: /* end of all tapes */
597 * Wiffle through all jobs still open and close
602 foreach_dlist(mdcr, dev->attached_dcrs) {
603 JCR *mjcr = mdcr->jcr;
604 if (!mjcr || mjcr->JobId == 0) {
607 jr.JobId = mjcr->JobId;
608 /* Mark Job as Error Terimined */
609 jr.JobStatus = JS_ErrorTerminated;
610 jr.JobFiles = mjcr->JobFiles;
611 jr.JobBytes = mjcr->JobBytes;
612 jr.VolSessionId = mjcr->VolSessionId;
613 jr.VolSessionTime = mjcr->VolSessionTime;
614 jr.JobTDate = (utime_t)mjcr->start_time;
615 jr.ClientId = mjcr->ClientId;
616 if (!db_update_job_end_record(bjcr, db, &jr)) {
617 Pmsg1(0, _("Could not update job record. ERR=%s\n"), db_strerror(db));
619 mjcr->read_dcr = NULL;
623 mr.VolFiles = rec->File;
624 mr.VolBlocks = rec->Block;
625 mr.VolBytes += mr.VolBlocks * WRITE_BLKHDR_LENGTH; /* approx. */
627 update_media_record(db, &mr);
628 Pmsg3(0, _("End of all Volumes. VolFiles=%u VolBlocks=%u VolBytes=%s\n"), mr.VolFiles,
629 mr.VolBlocks, edit_uint64_with_commas(mr.VolBytes, ec1));
637 mjcr = get_jcr_by_session(rec->VolSessionId, rec->VolSessionTime);
639 if (mr.VolJobs > 0) {
640 Pmsg2(000, _("Could not find Job for SessId=%d SessTime=%d record.\n"),
641 rec->VolSessionId, rec->VolSessionTime);
647 dcr = mjcr->read_dcr;
648 if (dcr->VolFirstIndex == 0) {
649 dcr->VolFirstIndex = block->FirstIndex;
652 /* File Attributes stream */
653 switch (rec->Stream) {
654 case STREAM_UNIX_ATTRIBUTES:
655 case STREAM_UNIX_ATTRIBUTES_EX:
657 if (!unpack_attributes_record(bjcr, rec->Stream, rec->data, attr)) {
658 Emsg0(M_ERROR_TERM, 0, _("Cannot continue.\n"));
661 if (attr->file_index != rec->FileIndex) {
662 Emsg2(M_ERROR_TERM, 0, _("Record header file index %ld not equal record index %ld\n"),
663 rec->FileIndex, attr->file_index);
667 decode_stat(attr->attr, &attr->statp, &attr->LinkFI);
668 build_attr_output_fnames(bjcr, attr);
669 print_ls_output(bjcr, attr);
671 fr.JobId = mjcr->JobId;
674 if (verbose && (num_files & 0x7FFF) == 0) {
675 char ed1[30], ed2[30], ed3[30], ed4[30];
676 Pmsg4(000, _("%s file records. At file:blk=%s:%s bytes=%s\n"),
677 edit_uint64_with_commas(num_files, ed1),
678 edit_uint64_with_commas(rec->File, ed2),
679 edit_uint64_with_commas(rec->Block, ed3),
680 edit_uint64_with_commas(mr.VolBytes, ed4));
682 create_file_attributes_record(db, mjcr, attr->fname, attr->lname,
683 attr->type, attr->attr, rec);
688 case STREAM_WIN32_DATA:
689 case STREAM_FILE_DATA:
690 case STREAM_SPARSE_DATA:
691 case STREAM_ENCRYPTED_FILE_DATA:
692 case STREAM_ENCRYPTED_WIN32_DATA:
693 case STREAM_ENCRYPTED_MACOS_FORK_DATA:
695 * For encrypted stream, this is an approximation.
696 * The data must be decrypted to know the correct length.
698 mjcr->JobBytes += rec->data_len;
699 if (rec->Stream == STREAM_SPARSE_DATA) {
700 mjcr->JobBytes -= sizeof(uint64_t);
703 free_jcr(mjcr); /* done using JCR */
706 case STREAM_GZIP_DATA:
707 case STREAM_ENCRYPTED_FILE_GZIP_DATA:
708 case STREAM_ENCRYPTED_WIN32_GZIP_DATA:
709 /* No correct, we should (decrypt and) expand it
712 mjcr->JobBytes += rec->data_len;
716 case STREAM_SPARSE_GZIP_DATA:
717 mjcr->JobBytes += rec->data_len - sizeof(uint64_t); /* No correct, we should expand it */
718 free_jcr(mjcr); /* done using JCR */
721 /* Win32 GZIP stream */
722 case STREAM_WIN32_GZIP_DATA:
723 mjcr->JobBytes += rec->data_len;
724 free_jcr(mjcr); /* done using JCR */
727 case STREAM_MD5_DIGEST:
728 bin_to_base64(digest, sizeof(digest), (char *)rec->data, CRYPTO_DIGEST_MD5_SIZE, true);
730 Pmsg1(000, _("Got MD5 record: %s\n"), digest);
732 update_digest_record(db, digest, rec, CRYPTO_DIGEST_MD5);
735 case STREAM_SHA1_DIGEST:
736 bin_to_base64(digest, sizeof(digest), (char *)rec->data, CRYPTO_DIGEST_SHA1_SIZE, true);
738 Pmsg1(000, _("Got SHA1 record: %s\n"), digest);
740 update_digest_record(db, digest, rec, CRYPTO_DIGEST_SHA1);
743 case STREAM_SHA256_DIGEST:
744 bin_to_base64(digest, sizeof(digest), (char *)rec->data, CRYPTO_DIGEST_SHA256_SIZE, true);
746 Pmsg1(000, _("Got SHA256 record: %s\n"), digest);
748 update_digest_record(db, digest, rec, CRYPTO_DIGEST_SHA256);
751 case STREAM_SHA512_DIGEST:
752 bin_to_base64(digest, sizeof(digest), (char *)rec->data, CRYPTO_DIGEST_SHA512_SIZE, true);
754 Pmsg1(000, _("Got SHA512 record: %s\n"), digest);
756 update_digest_record(db, digest, rec, CRYPTO_DIGEST_SHA512);
759 case STREAM_ENCRYPTED_SESSION_DATA:
760 // TODO landonf: Investigate crypto support in bscan
762 Pmsg0(000, _("Got signed digest record\n"));
766 case STREAM_SIGNED_DIGEST:
767 // TODO landonf: Investigate crypto support in bscan
769 Pmsg0(000, _("Got signed digest record\n"));
773 case STREAM_PROGRAM_NAMES:
775 Pmsg1(000, _("Got Prog Names Stream: %s\n"), rec->data);
779 case STREAM_PROGRAM_DATA:
781 Pmsg0(000, _("Got Prog Data Stream record.\n"));
785 case STREAM_UNIX_ATTRIBUTES_ACCESS_ACL: /* Standard ACL attributes on UNIX */
786 case STREAM_UNIX_ATTRIBUTES_DEFAULT_ACL: /* Default ACL attributes on UNIX */
787 /* Ignore Unix attributes */
791 Pmsg2(0, _("Unknown stream type!!! stream=%d len=%i\n"), rec->Stream, rec->data_len);
798 * Free the Job Control Record if no one is still using it.
799 * Called from main free_jcr() routine in src/lib/jcr.c so
800 * that we can do our Director specific cleanup of the jcr.
802 static void bscan_free_jcr(JCR *jcr)
804 Dmsg0(200, "Start bscan free_jcr\n");
806 if (jcr->file_bsock) {
807 Dmsg0(200, "Close File bsock\n");
808 bnet_close(jcr->file_bsock);
810 if (jcr->store_bsock) {
811 Dmsg0(200, "Close Store bsock\n");
812 bnet_close(jcr->store_bsock);
814 if (jcr->RestoreBootstrap) {
815 free(jcr->RestoreBootstrap);
822 free_dcr(jcr->read_dcr);
823 jcr->read_dcr = NULL;
825 Dmsg0(200, "End bscan free_jcr\n");
829 * We got a File Attributes record on the tape. Now, lookup the Job
830 * record, and then create the attributes record.
832 static int create_file_attributes_record(B_DB *db, JCR *mjcr,
833 char *fname, char *lname, int type,
834 char *ap, DEV_RECORD *rec)
836 DCR *dcr = mjcr->read_dcr;
839 ar.ClientId = mjcr->ClientId;
840 ar.JobId = mjcr->JobId;
841 ar.Stream = rec->Stream;
842 ar.FileIndex = rec->FileIndex;
844 if (dcr->VolFirstIndex == 0) {
845 dcr->VolFirstIndex = rec->FileIndex;
847 dcr->FileIndex = rec->FileIndex;
854 if (!db_create_file_attributes_record(bjcr, db, &ar)) {
855 Pmsg1(0, _("Could not create File Attributes record. ERR=%s\n"), db_strerror(db));
858 mjcr->FileId = ar.FileId;
861 Pmsg1(000, _("Created File record: %s\n"), fname);
867 * For each Volume we see, we create a Medium record
869 static int create_media_record(B_DB *db, MEDIA_DBR *mr, VOLUME_LABEL *vl)
874 /* We mark Vols as Archive to keep them from being re-written */
875 bstrncpy(mr->VolStatus, "Archive", sizeof(mr->VolStatus));
876 mr->VolRetention = 365 * 3600 * 24; /* 1 year */
878 if (vl->VerNum >= 11) {
879 mr->FirstWritten = btime_to_utime(vl->write_btime);
880 mr->LabelDate = btime_to_utime(vl->label_btime);
882 /* DEPRECATED DO NOT USE */
883 dt.julian_day_number = vl->write_date;
884 dt.julian_day_fraction = vl->write_time;
886 mr->FirstWritten = mktime(&tm);
887 dt.julian_day_number = vl->label_date;
888 dt.julian_day_fraction = vl->label_time;
890 mr->LabelDate = mktime(&tm);
892 lasttime = mr->LabelDate;
898 if (!db_create_media_record(bjcr, db, mr)) {
899 Pmsg1(0, _("Could not create media record. ERR=%s\n"), db_strerror(db));
902 if (!db_update_media_record(bjcr, db, mr)) {
903 Pmsg1(0, _("Could not update media record. ERR=%s\n"), db_strerror(db));
907 Pmsg1(000, _("Created Media record for Volume: %s\n"), mr->VolumeName);
914 * Called at end of media to update it
916 static bool update_media_record(B_DB *db, MEDIA_DBR *mr)
918 if (!update_db && !update_vol_info) {
922 mr->LastWritten = lasttime;
923 if (!db_update_media_record(bjcr, db, mr)) {
924 Pmsg1(0, _("Could not update media record. ERR=%s\n"), db_strerror(db));
928 Pmsg1(000, _("Updated Media record at end of Volume: %s\n"), mr->VolumeName);
935 static int create_pool_record(B_DB *db, POOL_DBR *pr)
939 pr->VolRetention = 355 * 3600 * 24; /* 1 year */
944 if (!db_create_pool_record(bjcr, db, pr)) {
945 Pmsg1(0, _("Could not create pool record. ERR=%s\n"), db_strerror(db));
949 Pmsg1(000, _("Created Pool record for Pool: %s\n"), pr->Name);
957 * Called from SOS to create a client for the current Job
959 static int create_client_record(B_DB *db, CLIENT_DBR *cr)
964 if (!db_create_client_record(bjcr, db, cr)) {
965 Pmsg1(0, _("Could not create Client record. ERR=%s\n"), db_strerror(db));
969 Pmsg1(000, _("Created Client record for Client: %s\n"), cr->Name);
974 static int create_fileset_record(B_DB *db, FILESET_DBR *fsr)
980 if (fsr->MD5[0] == 0) {
981 fsr->MD5[0] = ' '; /* Equivalent to nothing */
984 if (db_get_fileset_record(bjcr, db, fsr)) {
986 Pmsg1(000, _("Fileset \"%s\" already exists.\n"), fsr->FileSet);
989 if (!db_create_fileset_record(bjcr, db, fsr)) {
990 Pmsg2(0, _("Could not create FileSet record \"%s\". ERR=%s\n"),
991 fsr->FileSet, db_strerror(db));
995 Pmsg1(000, _("Created FileSet record \"%s\"\n"), fsr->FileSet);
1002 * Simulate the two calls on the database to create
1003 * the Job record and to update it when the Job actually
1006 static JCR *create_job_record(B_DB *db, JOB_DBR *jr, SESSION_LABEL *label,
1010 struct date_time dt;
1013 jr->JobId = label->JobId;
1014 jr->JobType = label->JobType;
1015 jr->JobLevel = label->JobLevel;
1016 jr->JobStatus = JS_Created;
1017 bstrncpy(jr->Name, label->JobName, sizeof(jr->Name));
1018 bstrncpy(jr->Job, label->Job, sizeof(jr->Job));
1019 if (label->VerNum >= 11) {
1020 jr->SchedTime = btime_to_unix(label->write_btime);
1022 dt.julian_day_number = label->write_date;
1023 dt.julian_day_fraction = label->write_time;
1024 tm_decode(&dt, &tm);
1025 jr->SchedTime = mktime(&tm);
1028 jr->StartTime = jr->SchedTime;
1029 jr->JobTDate = (utime_t)jr->SchedTime;
1030 jr->VolSessionId = rec->VolSessionId;
1031 jr->VolSessionTime = rec->VolSessionTime;
1033 /* Now create a JCR as if starting the Job */
1034 mjcr = create_jcr(jr, rec, label->JobId);
1040 /* This creates the bare essentials */
1041 if (!db_create_job_record(bjcr, db, jr)) {
1042 Pmsg1(0, _("Could not create JobId record. ERR=%s\n"), db_strerror(db));
1046 /* This adds the client, StartTime, JobTDate, ... */
1047 if (!db_update_job_start_record(bjcr, db, jr)) {
1048 Pmsg1(0, _("Could not update job start record. ERR=%s\n"), db_strerror(db));
1051 Pmsg2(000, _("Created new JobId=%u record for original JobId=%u\n"), jr->JobId,
1053 mjcr->JobId = jr->JobId; /* set new JobId */
1058 * Simulate the database call that updates the Job
1059 * at Job termination time.
1061 static int update_job_record(B_DB *db, JOB_DBR *jr, SESSION_LABEL *elabel,
1064 struct date_time dt;
1068 mjcr = get_jcr_by_session(rec->VolSessionId, rec->VolSessionTime);
1070 Pmsg2(000, _("Could not find SessId=%d SessTime=%d for EOS record.\n"),
1071 rec->VolSessionId, rec->VolSessionTime);
1074 if (elabel->VerNum >= 11) {
1075 jr->EndTime = btime_to_unix(elabel->write_btime);
1077 dt.julian_day_number = elabel->write_date;
1078 dt.julian_day_fraction = elabel->write_time;
1079 tm_decode(&dt, &tm);
1080 jr->EndTime = mktime(&tm);
1082 lasttime = jr->EndTime;
1083 mjcr->end_time = jr->EndTime;
1085 jr->JobId = mjcr->JobId;
1086 jr->JobStatus = elabel->JobStatus;
1087 mjcr->JobStatus = elabel->JobStatus;
1088 jr->JobFiles = elabel->JobFiles;
1089 jr->JobBytes = elabel->JobBytes;
1090 jr->VolSessionId = rec->VolSessionId;
1091 jr->VolSessionTime = rec->VolSessionTime;
1092 jr->JobTDate = (utime_t)mjcr->start_time;
1093 jr->ClientId = mjcr->ClientId;
1100 if (!db_update_job_end_record(bjcr, db, jr)) {
1101 Pmsg2(0, _("Could not update JobId=%u record. ERR=%s\n"), jr->JobId, db_strerror(db));
1106 Pmsg3(000, _("Updated Job termination record for JobId=%u Level=%s TermStat=%c\n"),
1107 jr->JobId, job_level_to_str(mjcr->JobLevel), jr->JobStatus);
1110 const char *term_msg;
1111 static char term_code[70];
1112 char sdt[50], edt[50];
1113 char ec1[30], ec2[30], ec3[30];
1115 switch (mjcr->JobStatus) {
1117 term_msg = _("Backup OK");
1120 case JS_ErrorTerminated:
1121 term_msg = _("*** Backup Error ***");
1124 term_msg = _("Backup Canceled");
1127 term_msg = term_code;
1128 sprintf(term_code, _("Job Termination code: %d"), mjcr->JobStatus);
1131 bstrftime(sdt, sizeof(sdt), mjcr->start_time);
1132 bstrftime(edt, sizeof(edt), mjcr->end_time);
1133 Pmsg14(000, _("%s\n"
1137 "Backup Level: %s\n"
1141 "Files Written: %s\n"
1142 "Bytes Written: %s\n"
1143 "Volume Session Id: %d\n"
1144 "Volume Session Time: %d\n"
1145 "Last Volume Bytes: %s\n"
1146 "Termination: %s\n\n"),
1151 job_level_to_str(mjcr->JobLevel),
1155 edit_uint64_with_commas(mjcr->JobFiles, ec1),
1156 edit_uint64_with_commas(mjcr->JobBytes, ec2),
1158 mjcr->VolSessionTime,
1159 edit_uint64_with_commas(mr.VolBytes, ec3),
1166 static int create_jobmedia_record(B_DB *db, JCR *mjcr)
1169 DCR *dcr = mjcr->read_dcr;
1171 if (dev->is_tape()) {
1172 dcr->EndBlock = dev->EndBlock;
1173 dcr->EndFile = dev->EndFile;
1176 dcr->EndBlock = (uint32_t)dev->file_addr;
1177 dcr->EndFile = (uint32_t)(dev->file_addr >> 32);
1181 memset(&jmr, 0, sizeof(jmr));
1182 jmr.JobId = mjcr->JobId;
1183 jmr.MediaId = mr.MediaId;
1184 jmr.FirstIndex = dcr->VolFirstIndex;
1185 jmr.LastIndex = dcr->VolLastIndex;
1186 jmr.StartFile = dcr->StartFile;
1187 jmr.EndFile = dcr->EndFile;
1188 jmr.StartBlock = dcr->StartBlock;
1189 jmr.EndBlock = dcr->EndBlock;
1196 if (!db_create_jobmedia_record(bjcr, db, &jmr)) {
1197 Pmsg1(0, _("Could not create JobMedia record. ERR=%s\n"), db_strerror(db));
1201 Pmsg2(000, _("Created JobMedia record JobId %d, MediaId %d\n"),
1202 jmr.JobId, jmr.MediaId);
1208 * Simulate the database call that updates the MD5/SHA1 record
1210 static int update_digest_record(B_DB *db, char *digest, DEV_RECORD *rec, int type)
1214 mjcr = get_jcr_by_session(rec->VolSessionId, rec->VolSessionTime);
1216 if (mr.VolJobs > 0) {
1217 Pmsg2(000, _("Could not find SessId=%d SessTime=%d for MD5/SHA1 record.\n"),
1218 rec->VolSessionId, rec->VolSessionTime);
1225 if (!update_db || mjcr->FileId == 0) {
1230 if (!db_add_digest_to_file_record(bjcr, db, mjcr->FileId, digest, type)) {
1231 Pmsg1(0, _("Could not add MD5/SHA1 to File record. ERR=%s\n"), db_strerror(db));
1236 Pmsg0(000, _("Updated MD5/SHA1 record\n"));
1244 * Create a JCR as if we are really starting the job
1246 static JCR *create_jcr(JOB_DBR *jr, DEV_RECORD *rec, uint32_t JobId)
1250 * Transfer as much as possible to the Job JCR. Most important is
1251 * the JobId and the ClientId.
1253 jobjcr = new_jcr(sizeof(JCR), bscan_free_jcr);
1254 jobjcr->JobType = jr->JobType;
1255 jobjcr->JobLevel = jr->JobLevel;
1256 jobjcr->JobStatus = jr->JobStatus;
1257 bstrncpy(jobjcr->Job, jr->Job, sizeof(jobjcr->Job));
1258 jobjcr->JobId = JobId; /* this is JobId on tape */
1259 jobjcr->sched_time = jr->SchedTime;
1260 jobjcr->start_time = jr->StartTime;
1261 jobjcr->VolSessionId = rec->VolSessionId;
1262 jobjcr->VolSessionTime = rec->VolSessionTime;
1263 jobjcr->ClientId = jr->ClientId;
1264 jobjcr->read_dcr = new_dcr(jobjcr, dev);
1269 /* Dummies to replace askdir.c */
1270 bool dir_find_next_appendable_volume(DCR *dcr) { return 1;}
1271 bool dir_update_volume_info(DCR *dcr, bool relabel) { return 1; }
1272 bool dir_create_jobmedia_record(DCR *dcr) { return 1; }
1273 bool dir_ask_sysop_to_create_appendable_volume(DCR *dcr) { return 1; }
1274 bool dir_update_file_attributes(DCR *dcr, DEV_RECORD *rec) { return 1;}
1275 bool dir_send_job_status(JCR *jcr) {return 1;}
1276 int generate_job_event(JCR *jcr, const char *event) { return 1; }
1278 bool dir_ask_sysop_to_mount_volume(DCR *dcr)
1280 DEVICE *dev = dcr->dev;
1281 Dmsg0(20, "Enter dir_ask_sysop_to_mount_volume\n");
1282 /* Close device so user can use autochanger if desired */
1283 fprintf(stderr, _("Mount Volume \"%s\" on device %s and press return when ready: "),
1284 dcr->VolumeName, dev->print_name());
1290 bool dir_get_volume_info(DCR *dcr, enum get_vol_info_rw writing)
1292 Dmsg0(100, "Fake dir_get_volume_info\n");
1293 bstrncpy(dcr->VolCatInfo.VolCatName, dcr->VolumeName, sizeof(dcr->VolCatInfo.VolCatName));
1294 dcr->VolCatInfo.VolCatParts = find_num_dvd_parts(dcr);
1295 Dmsg2(500, "Vol=%s num_parts=%d\n", dcr->VolCatInfo.VolCatName, dcr->VolCatInfo.VolCatParts);