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 plus additions
22 that are listed in the file LICENSE.
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);
151 while ((ch = getopt(argc, argv, "b:c:d:h:mn:pP:rsSu:vV:w:?")) != -1) {
157 bsr = parse_bsr(NULL, optarg);
160 case 'c': /* specify config file */
161 if (configfile != NULL) {
164 configfile = bstrdup(optarg);
167 case 'd': /* debug level */
168 debug_level = atoi(optarg);
169 if (debug_level <= 0)
178 update_vol_info = true;
190 db_password = optarg;
209 case 'V': /* Volume name */
227 Pmsg0(0, _("Wrong number of arguments: \n"));
231 if (configfile == NULL) {
232 configfile = bstrdup(CONFIG_FILE);
235 parse_config(configfile);
237 me = (STORES *)GetNextRes(R_STORAGE, NULL);
240 Emsg1(M_ERROR_TERM, 0, _("No Storage resource defined in %s. Cannot continue.\n"),
244 /* Check if -w option given, otherwise use resource for working directory */
246 working_directory = wd;
247 } else if (!me->working_directory) {
248 Emsg1(M_ERROR_TERM, 0, _("No Working Directory defined in %s. Cannot continue.\n"),
251 working_directory = me->working_directory;
254 /* Check that working directory is good */
255 if (stat(working_directory, &stat_buf) != 0) {
256 Emsg1(M_ERROR_TERM, 0, _("Working Directory: %s not found. Cannot continue.\n"),
259 if (!S_ISDIR(stat_buf.st_mode)) {
260 Emsg1(M_ERROR_TERM, 0, _("Working Directory: %s is not a directory. Cannot continue.\n"),
264 bjcr = setup_jcr("bscan", argv[0], bsr, VolumeName, 1); /* read device */
268 dev = bjcr->read_dcr->dev;
273 currentVolumeSize = sb.st_size;
274 Pmsg1(000, _("First Volume Size = %sn"),
275 edit_uint64(currentVolumeSize, ed1));
278 if ((db=db_init_database(NULL, db_name, db_user, db_password,
279 db_host, 0, NULL, 0)) == NULL) {
280 Emsg0(M_ERROR_TERM, 0, _("Could not init Bacula database\n"));
282 if (!db_open_database(NULL, db)) {
283 Emsg0(M_ERROR_TERM, 0, db_strerror(db));
285 Dmsg0(200, "Database opened\n");
287 Pmsg2(000, _("Using Database: %s, User: %s\n"), db_name, db_user);
292 printf("Records added or updated in the catalog:\n%7d Media\n%7d Pool\n%7d Job\n%7d File\n",
293 num_media, num_pools, num_jobs, num_files);
296 printf("Records would have been added or updated in the catalog:\n%7d Media\n%7d Pool\n%7d Job\n%7d File\n",
297 num_media, num_pools, num_jobs, num_files);
306 * We are at the end of reading a tape. Now, we simulate handling
307 * the end of writing a tape by wiffling through the attached
308 * jcrs creating jobmedia records.
310 static bool bscan_mount_next_read_volume(DCR *dcr)
312 DEVICE *dev = dcr->dev;
314 Dmsg1(100, "Walk attached jcrs. Volume=%s\n", dev->VolCatInfo.VolCatName);
315 foreach_dlist(mdcr, dev->attached_dcrs) {
316 JCR *mjcr = mdcr->jcr;
317 if (mjcr->JobId == 0) {
321 Pmsg1(000, _("Create JobMedia for Job %s\n"), mjcr->Job);
323 if (dev->is_tape()) {
324 mdcr->EndBlock = dcr->EndBlock;
325 mdcr->EndFile = dcr->EndFile;
327 // mdcr->EndBlock = (uint32_t)dcr->file_addr;
328 // mdcr->EndFile = (uint32_t)(dcr->file_addr >> 32);
330 mjcr->read_dcr->VolLastIndex = dcr->VolLastIndex;
331 if (!create_jobmedia_record(db, mjcr)) {
332 Pmsg2(000, _("Could not create JobMedia record for Volume=%s Job=%s\n"),
333 dev->VolCatInfo.VolCatName, mjcr->Job);
336 /* Now let common read routine get up next tape. Note,
337 * we call mount_next... with bscan's jcr because that is where we
338 * have the Volume list, but we get attached.
340 bool stat = mount_next_read_volume(dcr);
346 currentVolumeSize = sb.st_size;
347 Pmsg1(000, _("First Volume Size = %sn"),
348 edit_uint64(currentVolumeSize, ed1));
353 static void do_scan()
357 memset(&ar, 0, sizeof(ar));
358 memset(&pr, 0, sizeof(pr));
359 memset(&jr, 0, sizeof(jr));
360 memset(&cr, 0, sizeof(cr));
361 memset(&fsr, 0, sizeof(fsr));
362 memset(&fr, 0, sizeof(fr));
364 /* Detach bscan's jcr as we are not a real Job on the tape */
366 read_records(bjcr->read_dcr, record_cb, bscan_mount_next_read_volume);
372 * Returns: true if OK
375 static bool record_cb(DCR *dcr, DEV_RECORD *rec)
379 DEVICE *dev = dcr->dev;
380 JCR *bjcr = dcr->jcr;
381 DEV_BLOCK *block = dcr->block;
382 char digest[BASE64_SIZE(CRYPTO_DIGEST_MAX_SIZE)];
384 if (rec->data_len > 0) {
385 mr.VolBytes += rec->data_len + WRITE_RECHDR_LENGTH; /* Accumulate Volume bytes */
386 if (showProgress && currentVolumeSize > 0) {
387 int pct = (mr.VolBytes * 100) / currentVolumeSize;
388 if (pct != last_pct) {
389 fprintf(stdout, _("done: %d%%\n"), pct);
397 Pmsg5(000, _("Record: SessId=%u SessTim=%u FileIndex=%d Stream=%d len=%u\n"),
398 rec->VolSessionId, rec->VolSessionTime, rec->FileIndex,
399 rec->Stream, rec->data_len);
402 * Check for Start or End of Session Record
405 if (rec->FileIndex < 0) {
406 bool save_update_db = update_db;
409 dump_label_record(dev, rec, 1);
411 switch (rec->FileIndex) {
413 Pmsg0(000, _("Volume is prelabeled. This tape cannot be scanned.\n"));
418 unser_volume_label(dev, rec);
419 /* Check Pool info */
420 bstrncpy(pr.Name, dev->VolHdr.PoolName, sizeof(pr.Name));
421 bstrncpy(pr.PoolType, dev->VolHdr.PoolType, sizeof(pr.PoolType));
423 if (db_get_pool_record(bjcr, db, &pr)) {
425 Pmsg1(000, _("Pool record for %s found in DB.\n"), pr.Name);
429 Pmsg1(000, _("VOL_LABEL: Pool record not found for Pool: %s\n"),
432 create_pool_record(db, &pr);
434 if (strcmp(pr.PoolType, dev->VolHdr.PoolType) != 0) {
435 Pmsg2(000, _("VOL_LABEL: PoolType mismatch. DB=%s Vol=%s\n"),
436 pr.PoolType, dev->VolHdr.PoolType);
438 } else if (verbose) {
439 Pmsg1(000, _("Pool type \"%s\" is OK.\n"), pr.PoolType);
442 /* Check Media Info */
443 memset(&mr, 0, sizeof(mr));
444 bstrncpy(mr.VolumeName, dev->VolHdr.VolumeName, sizeof(mr.VolumeName));
445 mr.PoolId = pr.PoolId;
447 if (db_get_media_record(bjcr, db, &mr)) {
449 Pmsg1(000, _("Media record for %s found in DB.\n"), mr.VolumeName);
451 /* Clear out some volume statistics that will be updated */
452 mr.VolJobs = mr.VolFiles = mr.VolBlocks = 0;
453 mr.VolBytes = rec->data_len + 20;
456 Pmsg1(000, _("VOL_LABEL: Media record not found for Volume: %s\n"),
459 bstrncpy(mr.MediaType, dev->VolHdr.MediaType, sizeof(mr.MediaType));
460 create_media_record(db, &mr, &dev->VolHdr);
462 if (strcmp(mr.MediaType, dev->VolHdr.MediaType) != 0) {
463 Pmsg2(000, _("VOL_LABEL: MediaType mismatch. DB=%s Vol=%s\n"),
464 mr.MediaType, dev->VolHdr.MediaType);
465 return true; /* ignore error */
466 } else if (verbose) {
467 Pmsg1(000, _("Media type \"%s\" is OK.\n"), mr.MediaType);
469 /* Reset some DCR variables */
470 foreach_dlist(dcr, dev->attached_dcrs) {
471 dcr->VolFirstIndex = dcr->FileIndex = 0;
472 dcr->StartBlock = dcr->EndBlock = 0;
473 dcr->StartFile = dcr->EndFile = 0;
476 Pmsg1(000, _("VOL_LABEL: OK for Volume: %s\n"), mr.VolumeName);
482 if (ignored_msgs > 0) {
483 Pmsg1(000, _("%d \"errors\" ignored before first Start of Session record.\n"),
487 unser_session_label(&label, rec);
488 memset(&jr, 0, sizeof(jr));
489 bstrncpy(jr.Job, label.Job, sizeof(jr.Job));
490 if (db_get_job_record(bjcr, db, &jr)) {
491 /* Job record already exists in DB */
492 update_db = false; /* don't change db in create_job_record */
494 Pmsg1(000, _("SOS_LABEL: Found Job record for JobId: %d\n"), jr.JobId);
497 /* Must create a Job record in DB */
499 Pmsg1(000, _("SOS_LABEL: Job record not found for JobId: %d\n"),
503 /* Create Client record if not already there */
504 bstrncpy(cr.Name, label.ClientName, sizeof(cr.Name));
505 create_client_record(db, &cr);
506 jr.ClientId = cr.ClientId;
508 /* process label, if Job record exists don't update db */
509 mjcr = create_job_record(db, &jr, &label, rec);
510 dcr = mjcr->read_dcr;
511 update_db = save_update_db;
513 jr.PoolId = pr.PoolId;
515 /* Set start positions into JCR */
516 if (dev->is_tape()) {
518 * Note, we have already advanced past current block,
519 * so the correct number is block_num - 1
521 dcr->StartBlock = dev->block_num - 1;
522 dcr->StartFile = dev->file;
524 dcr->StartBlock = (uint32_t)dev->file_addr;
525 dcr->StartFile = (uint32_t)(dev->file_addr >> 32);
528 mjcr->start_time = jr.StartTime;
529 mjcr->JobLevel = jr.JobLevel;
531 mjcr->client_name = get_pool_memory(PM_FNAME);
532 pm_strcpy(mjcr->client_name, label.ClientName);
533 mjcr->fileset_name = get_pool_memory(PM_FNAME);
534 pm_strcpy(mjcr->fileset_name, label.FileSetName);
535 bstrncpy(dcr->pool_type, label.PoolType, sizeof(dcr->pool_type));
536 bstrncpy(dcr->pool_name, label.PoolName, sizeof(dcr->pool_name));
538 if (rec->VolSessionId != jr.VolSessionId) {
539 Pmsg3(000, _("SOS_LABEL: VolSessId mismatch for JobId=%u. DB=%d Vol=%d\n"),
541 jr.VolSessionId, rec->VolSessionId);
542 return true; /* ignore error */
544 if (rec->VolSessionTime != jr.VolSessionTime) {
545 Pmsg3(000, _("SOS_LABEL: VolSessTime mismatch for JobId=%u. DB=%d Vol=%d\n"),
547 jr.VolSessionTime, rec->VolSessionTime);
548 return true; /* ignore error */
550 if (jr.PoolId != pr.PoolId) {
551 Pmsg3(000, _("SOS_LABEL: PoolId mismatch for JobId=%u. DB=%d Vol=%d\n"),
553 jr.PoolId, pr.PoolId);
554 return true; /* ignore error */
559 unser_session_label(&elabel, rec);
561 /* Create FileSet record */
562 bstrncpy(fsr.FileSet, label.FileSetName, sizeof(fsr.FileSet));
563 bstrncpy(fsr.MD5, label.FileSetMD5, sizeof(fsr.MD5));
564 create_fileset_record(db, &fsr);
565 jr.FileSetId = fsr.FileSetId;
567 mjcr = get_jcr_by_session(rec->VolSessionId, rec->VolSessionTime);
569 Pmsg2(000, _("Could not find SessId=%d SessTime=%d for EOS record.\n"),
570 rec->VolSessionId, rec->VolSessionTime);
574 /* Do the final update to the Job record */
575 update_job_record(db, &jr, &elabel, rec);
577 mjcr->end_time = jr.EndTime;
578 mjcr->JobStatus = JS_Terminated;
580 /* Create JobMedia record */
581 mjcr->read_dcr->VolLastIndex = dcr->VolLastIndex;
582 create_jobmedia_record(db, mjcr);
583 detach_dcr_from_dev(mjcr->read_dcr);
591 case EOT_LABEL: /* end of all tapes */
593 * Wiffle through all jobs still open and close
598 foreach_dlist(mdcr, dev->attached_dcrs) {
599 JCR *mjcr = mdcr->jcr;
600 if (!mjcr || mjcr->JobId == 0) {
603 jr.JobId = mjcr->JobId;
604 /* Mark Job as Error Terimined */
605 jr.JobStatus = JS_ErrorTerminated;
606 jr.JobFiles = mjcr->JobFiles;
607 jr.JobBytes = mjcr->JobBytes;
608 jr.VolSessionId = mjcr->VolSessionId;
609 jr.VolSessionTime = mjcr->VolSessionTime;
610 jr.JobTDate = (utime_t)mjcr->start_time;
611 jr.ClientId = mjcr->ClientId;
612 if (!db_update_job_end_record(bjcr, db, &jr)) {
613 Pmsg1(0, _("Could not update job record. ERR=%s\n"), db_strerror(db));
615 mjcr->read_dcr = NULL;
619 mr.VolFiles = rec->File;
620 mr.VolBlocks = rec->Block;
621 mr.VolBytes += mr.VolBlocks * WRITE_BLKHDR_LENGTH; /* approx. */
623 update_media_record(db, &mr);
624 Pmsg3(0, _("End of all Volumes. VolFiles=%u VolBlocks=%u VolBytes=%s\n"), mr.VolFiles,
625 mr.VolBlocks, edit_uint64_with_commas(mr.VolBytes, ec1));
633 mjcr = get_jcr_by_session(rec->VolSessionId, rec->VolSessionTime);
635 if (mr.VolJobs > 0) {
636 Pmsg2(000, _("Could not find Job for SessId=%d SessTime=%d record.\n"),
637 rec->VolSessionId, rec->VolSessionTime);
643 dcr = mjcr->read_dcr;
644 if (dcr->VolFirstIndex == 0) {
645 dcr->VolFirstIndex = block->FirstIndex;
648 /* File Attributes stream */
649 switch (rec->Stream) {
650 case STREAM_UNIX_ATTRIBUTES:
651 case STREAM_UNIX_ATTRIBUTES_EX:
653 if (!unpack_attributes_record(bjcr, rec->Stream, rec->data, attr)) {
654 Emsg0(M_ERROR_TERM, 0, _("Cannot continue.\n"));
657 if (attr->file_index != rec->FileIndex) {
658 Emsg2(M_ERROR_TERM, 0, _("Record header file index %ld not equal record index %ld\n"),
659 rec->FileIndex, attr->file_index);
663 decode_stat(attr->attr, &attr->statp, &attr->LinkFI);
664 build_attr_output_fnames(bjcr, attr);
665 print_ls_output(bjcr, attr);
667 fr.JobId = mjcr->JobId;
670 if (verbose && (num_files & 0x7FFF) == 0) {
671 char ed1[30], ed2[30], ed3[30], ed4[30];
672 Pmsg4(000, _("%s file records. At file:blk=%s:%s bytes=%s\n"),
673 edit_uint64_with_commas(num_files, ed1),
674 edit_uint64_with_commas(rec->File, ed2),
675 edit_uint64_with_commas(rec->Block, ed3),
676 edit_uint64_with_commas(mr.VolBytes, ed4));
678 create_file_attributes_record(db, mjcr, attr->fname, attr->lname,
679 attr->type, attr->attr, rec);
684 case STREAM_WIN32_DATA:
685 case STREAM_FILE_DATA:
686 case STREAM_SPARSE_DATA:
687 case STREAM_ENCRYPTED_FILE_DATA:
688 case STREAM_ENCRYPTED_WIN32_DATA:
689 case STREAM_ENCRYPTED_MACOS_FORK_DATA:
691 * For encrypted stream, this is an approximation.
692 * The data must be decrypted to know the correct length.
694 mjcr->JobBytes += rec->data_len;
695 if (rec->Stream == STREAM_SPARSE_DATA) {
696 mjcr->JobBytes -= sizeof(uint64_t);
699 free_jcr(mjcr); /* done using JCR */
702 case STREAM_GZIP_DATA:
703 case STREAM_ENCRYPTED_FILE_GZIP_DATA:
704 case STREAM_ENCRYPTED_WIN32_GZIP_DATA:
705 /* No correct, we should (decrypt and) expand it
708 mjcr->JobBytes += rec->data_len;
712 case STREAM_SPARSE_GZIP_DATA:
713 mjcr->JobBytes += rec->data_len - sizeof(uint64_t); /* No correct, we should expand it */
714 free_jcr(mjcr); /* done using JCR */
717 /* Win32 GZIP stream */
718 case STREAM_WIN32_GZIP_DATA:
719 mjcr->JobBytes += rec->data_len;
720 free_jcr(mjcr); /* done using JCR */
723 case STREAM_MD5_DIGEST:
724 bin_to_base64(digest, sizeof(digest), (char *)rec->data, CRYPTO_DIGEST_MD5_SIZE, true);
726 Pmsg1(000, _("Got MD5 record: %s\n"), digest);
728 update_digest_record(db, digest, rec, CRYPTO_DIGEST_MD5);
731 case STREAM_SHA1_DIGEST:
732 bin_to_base64(digest, sizeof(digest), (char *)rec->data, CRYPTO_DIGEST_SHA1_SIZE, true);
734 Pmsg1(000, _("Got SHA1 record: %s\n"), digest);
736 update_digest_record(db, digest, rec, CRYPTO_DIGEST_SHA1);
739 case STREAM_SHA256_DIGEST:
740 bin_to_base64(digest, sizeof(digest), (char *)rec->data, CRYPTO_DIGEST_SHA256_SIZE, true);
742 Pmsg1(000, _("Got SHA256 record: %s\n"), digest);
744 update_digest_record(db, digest, rec, CRYPTO_DIGEST_SHA256);
747 case STREAM_SHA512_DIGEST:
748 bin_to_base64(digest, sizeof(digest), (char *)rec->data, CRYPTO_DIGEST_SHA512_SIZE, true);
750 Pmsg1(000, _("Got SHA512 record: %s\n"), digest);
752 update_digest_record(db, digest, rec, CRYPTO_DIGEST_SHA512);
755 case STREAM_ENCRYPTED_SESSION_DATA:
756 // TODO landonf: Investigate crypto support in bscan
758 Pmsg0(000, _("Got signed digest record\n"));
762 case STREAM_SIGNED_DIGEST:
763 // TODO landonf: Investigate crypto support in bscan
765 Pmsg0(000, _("Got signed digest record\n"));
769 case STREAM_PROGRAM_NAMES:
771 Pmsg1(000, _("Got Prog Names Stream: %s\n"), rec->data);
775 case STREAM_PROGRAM_DATA:
777 Pmsg0(000, _("Got Prog Data Stream record.\n"));
781 case STREAM_UNIX_ATTRIBUTES_ACCESS_ACL: /* Standard ACL attributes on UNIX */
782 case STREAM_UNIX_ATTRIBUTES_DEFAULT_ACL: /* Default ACL attributes on UNIX */
783 /* Ignore Unix attributes */
787 Pmsg2(0, _("Unknown stream type!!! stream=%d len=%i\n"), rec->Stream, rec->data_len);
794 * Free the Job Control Record if no one is still using it.
795 * Called from main free_jcr() routine in src/lib/jcr.c so
796 * that we can do our Director specific cleanup of the jcr.
798 static void bscan_free_jcr(JCR *jcr)
800 Dmsg0(200, "Start bscan free_jcr\n");
802 if (jcr->file_bsock) {
803 Dmsg0(200, "Close File bsock\n");
804 bnet_close(jcr->file_bsock);
806 if (jcr->store_bsock) {
807 Dmsg0(200, "Close Store bsock\n");
808 bnet_close(jcr->store_bsock);
810 if (jcr->RestoreBootstrap) {
811 free(jcr->RestoreBootstrap);
818 free_dcr(jcr->read_dcr);
819 jcr->read_dcr = NULL;
821 Dmsg0(200, "End bscan free_jcr\n");
825 * We got a File Attributes record on the tape. Now, lookup the Job
826 * record, and then create the attributes record.
828 static int create_file_attributes_record(B_DB *db, JCR *mjcr,
829 char *fname, char *lname, int type,
830 char *ap, DEV_RECORD *rec)
832 DCR *dcr = mjcr->read_dcr;
835 ar.ClientId = mjcr->ClientId;
836 ar.JobId = mjcr->JobId;
837 ar.Stream = rec->Stream;
838 ar.FileIndex = rec->FileIndex;
840 if (dcr->VolFirstIndex == 0) {
841 dcr->VolFirstIndex = rec->FileIndex;
843 dcr->FileIndex = rec->FileIndex;
850 if (!db_create_file_attributes_record(bjcr, db, &ar)) {
851 Pmsg1(0, _("Could not create File Attributes record. ERR=%s\n"), db_strerror(db));
854 mjcr->FileId = ar.FileId;
857 Pmsg1(000, _("Created File record: %s\n"), fname);
863 * For each Volume we see, we create a Medium record
865 static int create_media_record(B_DB *db, MEDIA_DBR *mr, VOLUME_LABEL *vl)
870 /* We mark Vols as Archive to keep them from being re-written */
871 bstrncpy(mr->VolStatus, "Archive", sizeof(mr->VolStatus));
872 mr->VolRetention = 365 * 3600 * 24; /* 1 year */
874 if (vl->VerNum >= 11) {
875 mr->FirstWritten = btime_to_utime(vl->write_btime);
876 mr->LabelDate = btime_to_utime(vl->label_btime);
878 /* DEPRECATED DO NOT USE */
879 dt.julian_day_number = vl->write_date;
880 dt.julian_day_fraction = vl->write_time;
882 mr->FirstWritten = mktime(&tm);
883 dt.julian_day_number = vl->label_date;
884 dt.julian_day_fraction = vl->label_time;
886 mr->LabelDate = mktime(&tm);
888 lasttime = mr->LabelDate;
894 if (!db_create_media_record(bjcr, db, mr)) {
895 Pmsg1(0, _("Could not create media record. ERR=%s\n"), db_strerror(db));
898 if (!db_update_media_record(bjcr, db, mr)) {
899 Pmsg1(0, _("Could not update media record. ERR=%s\n"), db_strerror(db));
903 Pmsg1(000, _("Created Media record for Volume: %s\n"), mr->VolumeName);
910 * Called at end of media to update it
912 static bool update_media_record(B_DB *db, MEDIA_DBR *mr)
914 if (!update_db && !update_vol_info) {
918 mr->LastWritten = lasttime;
919 if (!db_update_media_record(bjcr, db, mr)) {
920 Pmsg1(0, _("Could not update media record. ERR=%s\n"), db_strerror(db));
924 Pmsg1(000, _("Updated Media record at end of Volume: %s\n"), mr->VolumeName);
931 static int create_pool_record(B_DB *db, POOL_DBR *pr)
935 pr->VolRetention = 355 * 3600 * 24; /* 1 year */
940 if (!db_create_pool_record(bjcr, db, pr)) {
941 Pmsg1(0, _("Could not create pool record. ERR=%s\n"), db_strerror(db));
945 Pmsg1(000, _("Created Pool record for Pool: %s\n"), pr->Name);
953 * Called from SOS to create a client for the current Job
955 static int create_client_record(B_DB *db, CLIENT_DBR *cr)
960 if (!db_create_client_record(bjcr, db, cr)) {
961 Pmsg1(0, _("Could not create Client record. ERR=%s\n"), db_strerror(db));
965 Pmsg1(000, _("Created Client record for Client: %s\n"), cr->Name);
970 static int create_fileset_record(B_DB *db, FILESET_DBR *fsr)
976 if (fsr->MD5[0] == 0) {
977 fsr->MD5[0] = ' '; /* Equivalent to nothing */
980 if (db_get_fileset_record(bjcr, db, fsr)) {
982 Pmsg1(000, _("Fileset \"%s\" already exists.\n"), fsr->FileSet);
985 if (!db_create_fileset_record(bjcr, db, fsr)) {
986 Pmsg2(0, _("Could not create FileSet record \"%s\". ERR=%s\n"),
987 fsr->FileSet, db_strerror(db));
991 Pmsg1(000, _("Created FileSet record \"%s\"\n"), fsr->FileSet);
998 * Simulate the two calls on the database to create
999 * the Job record and to update it when the Job actually
1002 static JCR *create_job_record(B_DB *db, JOB_DBR *jr, SESSION_LABEL *label,
1006 struct date_time dt;
1009 jr->JobId = label->JobId;
1010 jr->JobType = label->JobType;
1011 jr->JobLevel = label->JobLevel;
1012 jr->JobStatus = JS_Created;
1013 bstrncpy(jr->Name, label->JobName, sizeof(jr->Name));
1014 bstrncpy(jr->Job, label->Job, sizeof(jr->Job));
1015 if (label->VerNum >= 11) {
1016 jr->SchedTime = btime_to_unix(label->write_btime);
1018 dt.julian_day_number = label->write_date;
1019 dt.julian_day_fraction = label->write_time;
1020 tm_decode(&dt, &tm);
1021 jr->SchedTime = mktime(&tm);
1024 jr->StartTime = jr->SchedTime;
1025 jr->JobTDate = (utime_t)jr->SchedTime;
1026 jr->VolSessionId = rec->VolSessionId;
1027 jr->VolSessionTime = rec->VolSessionTime;
1029 /* Now create a JCR as if starting the Job */
1030 mjcr = create_jcr(jr, rec, label->JobId);
1036 /* This creates the bare essentials */
1037 if (!db_create_job_record(bjcr, db, jr)) {
1038 Pmsg1(0, _("Could not create JobId record. ERR=%s\n"), db_strerror(db));
1042 /* This adds the client, StartTime, JobTDate, ... */
1043 if (!db_update_job_start_record(bjcr, db, jr)) {
1044 Pmsg1(0, _("Could not update job start record. ERR=%s\n"), db_strerror(db));
1047 Pmsg2(000, _("Created new JobId=%u record for original JobId=%u\n"), jr->JobId,
1049 mjcr->JobId = jr->JobId; /* set new JobId */
1054 * Simulate the database call that updates the Job
1055 * at Job termination time.
1057 static int update_job_record(B_DB *db, JOB_DBR *jr, SESSION_LABEL *elabel,
1060 struct date_time dt;
1064 mjcr = get_jcr_by_session(rec->VolSessionId, rec->VolSessionTime);
1066 Pmsg2(000, _("Could not find SessId=%d SessTime=%d for EOS record.\n"),
1067 rec->VolSessionId, rec->VolSessionTime);
1070 if (elabel->VerNum >= 11) {
1071 jr->EndTime = btime_to_unix(elabel->write_btime);
1073 dt.julian_day_number = elabel->write_date;
1074 dt.julian_day_fraction = elabel->write_time;
1075 tm_decode(&dt, &tm);
1076 jr->EndTime = mktime(&tm);
1078 lasttime = jr->EndTime;
1079 mjcr->end_time = jr->EndTime;
1081 jr->JobId = mjcr->JobId;
1082 jr->JobStatus = elabel->JobStatus;
1083 mjcr->JobStatus = elabel->JobStatus;
1084 jr->JobFiles = elabel->JobFiles;
1085 jr->JobBytes = elabel->JobBytes;
1086 jr->VolSessionId = rec->VolSessionId;
1087 jr->VolSessionTime = rec->VolSessionTime;
1088 jr->JobTDate = (utime_t)mjcr->start_time;
1089 jr->ClientId = mjcr->ClientId;
1096 if (!db_update_job_end_record(bjcr, db, jr)) {
1097 Pmsg2(0, _("Could not update JobId=%u record. ERR=%s\n"), jr->JobId, db_strerror(db));
1102 Pmsg3(000, _("Updated Job termination record for JobId=%u Level=%s TermStat=%c\n"),
1103 jr->JobId, job_level_to_str(mjcr->JobLevel), jr->JobStatus);
1106 const char *term_msg;
1107 static char term_code[70];
1108 char sdt[50], edt[50];
1109 char ec1[30], ec2[30], ec3[30];
1111 switch (mjcr->JobStatus) {
1113 term_msg = _("Backup OK");
1116 case JS_ErrorTerminated:
1117 term_msg = _("*** Backup Error ***");
1120 term_msg = _("Backup Canceled");
1123 term_msg = term_code;
1124 sprintf(term_code, _("Job Termination code: %d"), mjcr->JobStatus);
1127 bstrftime(sdt, sizeof(sdt), mjcr->start_time);
1128 bstrftime(edt, sizeof(edt), mjcr->end_time);
1129 Pmsg14(000, _("%s\n"
1133 "Backup Level: %s\n"
1137 "Files Written: %s\n"
1138 "Bytes Written: %s\n"
1139 "Volume Session Id: %d\n"
1140 "Volume Session Time: %d\n"
1141 "Last Volume Bytes: %s\n"
1142 "Termination: %s\n\n"),
1147 job_level_to_str(mjcr->JobLevel),
1151 edit_uint64_with_commas(mjcr->JobFiles, ec1),
1152 edit_uint64_with_commas(mjcr->JobBytes, ec2),
1154 mjcr->VolSessionTime,
1155 edit_uint64_with_commas(mr.VolBytes, ec3),
1162 static int create_jobmedia_record(B_DB *db, JCR *mjcr)
1165 DCR *dcr = mjcr->read_dcr;
1167 if (dev->is_tape()) {
1168 dcr->EndBlock = dev->EndBlock;
1169 dcr->EndFile = dev->EndFile;
1172 dcr->EndBlock = (uint32_t)dev->file_addr;
1173 dcr->EndFile = (uint32_t)(dev->file_addr >> 32);
1177 memset(&jmr, 0, sizeof(jmr));
1178 jmr.JobId = mjcr->JobId;
1179 jmr.MediaId = mr.MediaId;
1180 jmr.FirstIndex = dcr->VolFirstIndex;
1181 jmr.LastIndex = dcr->VolLastIndex;
1182 jmr.StartFile = dcr->StartFile;
1183 jmr.EndFile = dcr->EndFile;
1184 jmr.StartBlock = dcr->StartBlock;
1185 jmr.EndBlock = dcr->EndBlock;
1192 if (!db_create_jobmedia_record(bjcr, db, &jmr)) {
1193 Pmsg1(0, _("Could not create JobMedia record. ERR=%s\n"), db_strerror(db));
1197 Pmsg2(000, _("Created JobMedia record JobId %d, MediaId %d\n"),
1198 jmr.JobId, jmr.MediaId);
1204 * Simulate the database call that updates the MD5/SHA1 record
1206 static int update_digest_record(B_DB *db, char *digest, DEV_RECORD *rec, int type)
1210 mjcr = get_jcr_by_session(rec->VolSessionId, rec->VolSessionTime);
1212 if (mr.VolJobs > 0) {
1213 Pmsg2(000, _("Could not find SessId=%d SessTime=%d for MD5/SHA1 record.\n"),
1214 rec->VolSessionId, rec->VolSessionTime);
1221 if (!update_db || mjcr->FileId == 0) {
1226 if (!db_add_digest_to_file_record(bjcr, db, mjcr->FileId, digest, type)) {
1227 Pmsg1(0, _("Could not add MD5/SHA1 to File record. ERR=%s\n"), db_strerror(db));
1232 Pmsg0(000, _("Updated MD5/SHA1 record\n"));
1240 * Create a JCR as if we are really starting the job
1242 static JCR *create_jcr(JOB_DBR *jr, DEV_RECORD *rec, uint32_t JobId)
1246 * Transfer as much as possible to the Job JCR. Most important is
1247 * the JobId and the ClientId.
1249 jobjcr = new_jcr(sizeof(JCR), bscan_free_jcr);
1250 jobjcr->JobType = jr->JobType;
1251 jobjcr->JobLevel = jr->JobLevel;
1252 jobjcr->JobStatus = jr->JobStatus;
1253 bstrncpy(jobjcr->Job, jr->Job, sizeof(jobjcr->Job));
1254 jobjcr->JobId = JobId; /* this is JobId on tape */
1255 jobjcr->sched_time = jr->SchedTime;
1256 jobjcr->start_time = jr->StartTime;
1257 jobjcr->VolSessionId = rec->VolSessionId;
1258 jobjcr->VolSessionTime = rec->VolSessionTime;
1259 jobjcr->ClientId = jr->ClientId;
1260 jobjcr->read_dcr = new_dcr(jobjcr, dev);
1265 /* Dummies to replace askdir.c */
1266 bool dir_find_next_appendable_volume(DCR *dcr) { return 1;}
1267 bool dir_update_volume_info(DCR *dcr, bool relabel) { return 1; }
1268 bool dir_create_jobmedia_record(DCR *dcr) { return 1; }
1269 bool dir_ask_sysop_to_create_appendable_volume(DCR *dcr) { return 1; }
1270 bool dir_update_file_attributes(DCR *dcr, DEV_RECORD *rec) { return 1;}
1271 bool dir_send_job_status(JCR *jcr) {return 1;}
1272 int generate_job_event(JCR *jcr, const char *event) { return 1; }
1274 bool dir_ask_sysop_to_mount_volume(DCR *dcr)
1276 DEVICE *dev = dcr->dev;
1277 Dmsg0(20, "Enter dir_ask_sysop_to_mount_volume\n");
1278 /* Close device so user can use autochanger if desired */
1279 fprintf(stderr, _("Mount Volume \"%s\" on device %s and press return when ready: "),
1280 dcr->VolumeName, dev->print_name());
1286 bool dir_get_volume_info(DCR *dcr, enum get_vol_info_rw writing)
1288 Dmsg0(100, "Fake dir_get_volume_info\n");
1289 bstrncpy(dcr->VolCatInfo.VolCatName, dcr->VolumeName, sizeof(dcr->VolCatInfo.VolCatName));
1290 dcr->VolCatInfo.VolCatParts = find_num_dvd_parts(dcr);
1291 Dmsg2(500, "Vol=%s num_parts=%d\n", dcr->VolCatInfo.VolCatName, dcr->VolCatInfo.VolCatParts);