2 Bacula® - The Network Backup Solution
4 Copyright (C) 2001-2007 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_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 Dmsg1(000, "========== JobId=%u ========\n", mjcr->JobId);
319 if (mjcr->JobId == 0) {
323 Pmsg1(000, _("Create JobMedia for Job %s\n"), mjcr->Job);
325 mdcr->StartBlock = dcr->StartBlock;
326 mdcr->StartFile = dcr->StartFile;
327 mdcr->EndBlock = dcr->EndBlock;
328 mdcr->EndFile = dcr->EndFile;
329 mdcr->VolMediaId = dcr->VolMediaId;
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);
337 update_media_record(db, &mr);
339 /* Now let common read routine get up next tape. Note,
340 * we call mount_next... with bscan's jcr because that is where we
341 * have the Volume list, but we get attached.
343 bool stat = mount_next_read_volume(dcr);
348 fstat(dev->fd(), &sb);
349 currentVolumeSize = sb.st_size;
350 Pmsg1(000, _("First Volume Size = %sn"),
351 edit_uint64(currentVolumeSize, ed1));
356 static void do_scan()
358 attr = new_attr(bjcr);
360 memset(&ar, 0, sizeof(ar));
361 memset(&pr, 0, sizeof(pr));
362 memset(&jr, 0, sizeof(jr));
363 memset(&cr, 0, sizeof(cr));
364 memset(&fsr, 0, sizeof(fsr));
365 memset(&fr, 0, sizeof(fr));
367 /* Detach bscan's jcr as we are not a real Job on the tape */
369 read_records(bjcr->read_dcr, record_cb, bscan_mount_next_read_volume);
372 db_write_batch_file_records(bjcr); /* used by bulk batch file insert */
378 * Returns: true if OK
381 static bool record_cb(DCR *dcr, DEV_RECORD *rec)
385 DEVICE *dev = dcr->dev;
386 JCR *bjcr = dcr->jcr;
387 DEV_BLOCK *block = dcr->block;
388 char digest[BASE64_SIZE(CRYPTO_DIGEST_MAX_SIZE)];
390 if (rec->data_len > 0) {
391 mr.VolBytes += rec->data_len + WRITE_RECHDR_LENGTH; /* Accumulate Volume bytes */
392 if (showProgress && currentVolumeSize > 0) {
393 int pct = (mr.VolBytes * 100) / currentVolumeSize;
394 if (pct != last_pct) {
395 fprintf(stdout, _("done: %d%%\n"), pct);
403 Pmsg5(000, _("Record: SessId=%u SessTim=%u FileIndex=%d Stream=%d len=%u\n"),
404 rec->VolSessionId, rec->VolSessionTime, rec->FileIndex,
405 rec->Stream, rec->data_len);
408 * Check for Start or End of Session Record
411 if (rec->FileIndex < 0) {
412 bool save_update_db = update_db;
415 dump_label_record(dev, rec, 1);
417 switch (rec->FileIndex) {
419 Pmsg0(000, _("Volume is prelabeled. This tape cannot be scanned.\n"));
424 unser_volume_label(dev, rec);
425 /* Check Pool info */
426 bstrncpy(pr.Name, dev->VolHdr.PoolName, sizeof(pr.Name));
427 bstrncpy(pr.PoolType, dev->VolHdr.PoolType, sizeof(pr.PoolType));
429 if (db_get_pool_record(bjcr, db, &pr)) {
431 Pmsg1(000, _("Pool record for %s found in DB.\n"), pr.Name);
435 Pmsg1(000, _("VOL_LABEL: Pool record not found for Pool: %s\n"),
438 create_pool_record(db, &pr);
440 if (strcmp(pr.PoolType, dev->VolHdr.PoolType) != 0) {
441 Pmsg2(000, _("VOL_LABEL: PoolType mismatch. DB=%s Vol=%s\n"),
442 pr.PoolType, dev->VolHdr.PoolType);
444 } else if (verbose) {
445 Pmsg1(000, _("Pool type \"%s\" is OK.\n"), pr.PoolType);
448 /* Check Media Info */
449 memset(&mr, 0, sizeof(mr));
450 bstrncpy(mr.VolumeName, dev->VolHdr.VolumeName, sizeof(mr.VolumeName));
451 mr.PoolId = pr.PoolId;
453 if (db_get_media_record(bjcr, db, &mr)) {
455 Pmsg1(000, _("Media record for %s found in DB.\n"), mr.VolumeName);
457 /* Clear out some volume statistics that will be updated */
458 mr.VolJobs = mr.VolFiles = mr.VolBlocks = 0;
459 mr.VolBytes = rec->data_len + 20;
462 Pmsg1(000, _("VOL_LABEL: Media record not found for Volume: %s\n"),
465 bstrncpy(mr.MediaType, dev->VolHdr.MediaType, sizeof(mr.MediaType));
466 create_media_record(db, &mr, &dev->VolHdr);
468 if (strcmp(mr.MediaType, dev->VolHdr.MediaType) != 0) {
469 Pmsg2(000, _("VOL_LABEL: MediaType mismatch. DB=%s Vol=%s\n"),
470 mr.MediaType, dev->VolHdr.MediaType);
471 return true; /* ignore error */
472 } else if (verbose) {
473 Pmsg1(000, _("Media type \"%s\" is OK.\n"), mr.MediaType);
475 /* Reset some DCR variables */
476 foreach_dlist(dcr, dev->attached_dcrs) {
477 dcr->VolFirstIndex = dcr->FileIndex = 0;
478 dcr->StartBlock = dcr->EndBlock = 0;
479 dcr->StartFile = dcr->EndFile = 0;
483 Pmsg1(000, _("VOL_LABEL: OK for Volume: %s\n"), mr.VolumeName);
489 if (ignored_msgs > 0) {
490 Pmsg1(000, _("%d \"errors\" ignored before first Start of Session record.\n"),
494 unser_session_label(&label, rec);
495 memset(&jr, 0, sizeof(jr));
496 bstrncpy(jr.Job, label.Job, sizeof(jr.Job));
497 if (db_get_job_record(bjcr, db, &jr)) {
498 /* Job record already exists in DB */
499 update_db = false; /* don't change db in create_job_record */
501 Pmsg1(000, _("SOS_LABEL: Found Job record for JobId: %d\n"), jr.JobId);
504 /* Must create a Job record in DB */
506 Pmsg1(000, _("SOS_LABEL: Job record not found for JobId: %d\n"),
510 /* Create Client record if not already there */
511 bstrncpy(cr.Name, label.ClientName, sizeof(cr.Name));
512 create_client_record(db, &cr);
513 jr.ClientId = cr.ClientId;
515 /* process label, if Job record exists don't update db */
516 mjcr = create_job_record(db, &jr, &label, rec);
517 dcr = mjcr->read_dcr;
518 update_db = save_update_db;
520 jr.PoolId = pr.PoolId;
522 /* Set start positions into JCR */
523 if (dev->is_tape()) {
525 * Note, we have already advanced past current block,
526 * so the correct number is block_num - 1
528 dcr->StartBlock = dev->block_num - 1;
529 dcr->StartFile = dev->file;
531 dcr->StartBlock = (uint32_t)dev->file_addr;
532 dcr->StartFile = (uint32_t)(dev->file_addr >> 32);
535 mjcr->start_time = jr.StartTime;
536 mjcr->JobLevel = jr.JobLevel;
538 mjcr->client_name = get_pool_memory(PM_FNAME);
539 pm_strcpy(mjcr->client_name, label.ClientName);
540 mjcr->fileset_name = get_pool_memory(PM_FNAME);
541 pm_strcpy(mjcr->fileset_name, label.FileSetName);
542 bstrncpy(dcr->pool_type, label.PoolType, sizeof(dcr->pool_type));
543 bstrncpy(dcr->pool_name, label.PoolName, sizeof(dcr->pool_name));
545 if (rec->VolSessionId != jr.VolSessionId) {
546 Pmsg3(000, _("SOS_LABEL: VolSessId mismatch for JobId=%u. DB=%d Vol=%d\n"),
548 jr.VolSessionId, rec->VolSessionId);
549 return true; /* ignore error */
551 if (rec->VolSessionTime != jr.VolSessionTime) {
552 Pmsg3(000, _("SOS_LABEL: VolSessTime mismatch for JobId=%u. DB=%d Vol=%d\n"),
554 jr.VolSessionTime, rec->VolSessionTime);
555 return true; /* ignore error */
557 if (jr.PoolId != pr.PoolId) {
558 Pmsg3(000, _("SOS_LABEL: PoolId mismatch for JobId=%u. DB=%d Vol=%d\n"),
560 jr.PoolId, pr.PoolId);
561 return true; /* ignore error */
566 unser_session_label(&elabel, rec);
568 /* Create FileSet record */
569 bstrncpy(fsr.FileSet, label.FileSetName, sizeof(fsr.FileSet));
570 bstrncpy(fsr.MD5, label.FileSetMD5, sizeof(fsr.MD5));
571 create_fileset_record(db, &fsr);
572 jr.FileSetId = fsr.FileSetId;
574 mjcr = get_jcr_by_session(rec->VolSessionId, rec->VolSessionTime);
576 Pmsg2(000, _("Could not find SessId=%d SessTime=%d for EOS record.\n"),
577 rec->VolSessionId, rec->VolSessionTime);
581 /* Do the final update to the Job record */
582 update_job_record(db, &jr, &elabel, rec);
584 mjcr->end_time = jr.EndTime;
585 mjcr->JobStatus = JS_Terminated;
587 /* Create JobMedia record */
588 mjcr->read_dcr->VolLastIndex = dcr->VolLastIndex;
589 create_jobmedia_record(db, mjcr);
590 detach_dcr_from_dev(mjcr->read_dcr);
598 case EOT_LABEL: /* end of all tapes */
600 * Wiffle through all jobs still open and close
605 foreach_dlist(mdcr, dev->attached_dcrs) {
606 JCR *mjcr = mdcr->jcr;
607 if (!mjcr || mjcr->JobId == 0) {
610 jr.JobId = mjcr->JobId;
611 /* Mark Job as Error Terimined */
612 jr.JobStatus = JS_ErrorTerminated;
613 jr.JobFiles = mjcr->JobFiles;
614 jr.JobBytes = mjcr->JobBytes;
615 jr.VolSessionId = mjcr->VolSessionId;
616 jr.VolSessionTime = mjcr->VolSessionTime;
617 jr.JobTDate = (utime_t)mjcr->start_time;
618 jr.ClientId = mjcr->ClientId;
619 if (!db_update_job_end_record(bjcr, db, &jr)) {
620 Pmsg1(0, _("Could not update job record. ERR=%s\n"), db_strerror(db));
622 mjcr->read_dcr = NULL;
626 mr.VolFiles = rec->File;
627 mr.VolBlocks = rec->Block;
628 mr.VolBytes += mr.VolBlocks * WRITE_BLKHDR_LENGTH; /* approx. */
630 update_media_record(db, &mr);
631 Pmsg3(0, _("End of all Volumes. VolFiles=%u VolBlocks=%u VolBytes=%s\n"), mr.VolFiles,
632 mr.VolBlocks, edit_uint64_with_commas(mr.VolBytes, ec1));
640 mjcr = get_jcr_by_session(rec->VolSessionId, rec->VolSessionTime);
642 if (mr.VolJobs > 0) {
643 Pmsg2(000, _("Could not find Job for SessId=%d SessTime=%d record.\n"),
644 rec->VolSessionId, rec->VolSessionTime);
650 dcr = mjcr->read_dcr;
651 if (dcr->VolFirstIndex == 0) {
652 dcr->VolFirstIndex = block->FirstIndex;
655 /* File Attributes stream */
656 switch (rec->Stream) {
657 case STREAM_UNIX_ATTRIBUTES:
658 case STREAM_UNIX_ATTRIBUTES_EX:
660 if (!unpack_attributes_record(bjcr, rec->Stream, rec->data, attr)) {
661 Emsg0(M_ERROR_TERM, 0, _("Cannot continue.\n"));
664 if (attr->file_index != rec->FileIndex) {
665 Emsg2(M_ERROR_TERM, 0, _("Record header file index %ld not equal record index %ld\n"),
666 rec->FileIndex, attr->file_index);
670 decode_stat(attr->attr, &attr->statp, &attr->LinkFI);
671 build_attr_output_fnames(bjcr, attr);
672 print_ls_output(bjcr, attr);
674 fr.JobId = mjcr->JobId;
677 if (verbose && (num_files & 0x7FFF) == 0) {
678 char ed1[30], ed2[30], ed3[30], ed4[30];
679 Pmsg4(000, _("%s file records. At file:blk=%s:%s bytes=%s\n"),
680 edit_uint64_with_commas(num_files, ed1),
681 edit_uint64_with_commas(rec->File, ed2),
682 edit_uint64_with_commas(rec->Block, ed3),
683 edit_uint64_with_commas(mr.VolBytes, ed4));
685 create_file_attributes_record(db, mjcr, attr->fname, attr->lname,
686 attr->type, attr->attr, rec);
691 case STREAM_WIN32_DATA:
692 case STREAM_FILE_DATA:
693 case STREAM_SPARSE_DATA:
694 case STREAM_ENCRYPTED_FILE_DATA:
695 case STREAM_ENCRYPTED_WIN32_DATA:
696 case STREAM_ENCRYPTED_MACOS_FORK_DATA:
698 * For encrypted stream, this is an approximation.
699 * The data must be decrypted to know the correct length.
701 mjcr->JobBytes += rec->data_len;
702 if (rec->Stream == STREAM_SPARSE_DATA) {
703 mjcr->JobBytes -= sizeof(uint64_t);
706 free_jcr(mjcr); /* done using JCR */
709 case STREAM_GZIP_DATA:
710 case STREAM_ENCRYPTED_FILE_GZIP_DATA:
711 case STREAM_ENCRYPTED_WIN32_GZIP_DATA:
712 /* No correct, we should (decrypt and) expand it
715 mjcr->JobBytes += rec->data_len;
719 case STREAM_SPARSE_GZIP_DATA:
720 mjcr->JobBytes += rec->data_len - sizeof(uint64_t); /* No correct, we should expand it */
721 free_jcr(mjcr); /* done using JCR */
724 /* Win32 GZIP stream */
725 case STREAM_WIN32_GZIP_DATA:
726 mjcr->JobBytes += rec->data_len;
727 free_jcr(mjcr); /* done using JCR */
730 case STREAM_MD5_DIGEST:
731 bin_to_base64(digest, sizeof(digest), (char *)rec->data, CRYPTO_DIGEST_MD5_SIZE, true);
733 Pmsg1(000, _("Got MD5 record: %s\n"), digest);
735 update_digest_record(db, digest, rec, CRYPTO_DIGEST_MD5);
738 case STREAM_SHA1_DIGEST:
739 bin_to_base64(digest, sizeof(digest), (char *)rec->data, CRYPTO_DIGEST_SHA1_SIZE, true);
741 Pmsg1(000, _("Got SHA1 record: %s\n"), digest);
743 update_digest_record(db, digest, rec, CRYPTO_DIGEST_SHA1);
746 case STREAM_SHA256_DIGEST:
747 bin_to_base64(digest, sizeof(digest), (char *)rec->data, CRYPTO_DIGEST_SHA256_SIZE, true);
749 Pmsg1(000, _("Got SHA256 record: %s\n"), digest);
751 update_digest_record(db, digest, rec, CRYPTO_DIGEST_SHA256);
754 case STREAM_SHA512_DIGEST:
755 bin_to_base64(digest, sizeof(digest), (char *)rec->data, CRYPTO_DIGEST_SHA512_SIZE, true);
757 Pmsg1(000, _("Got SHA512 record: %s\n"), digest);
759 update_digest_record(db, digest, rec, CRYPTO_DIGEST_SHA512);
762 case STREAM_ENCRYPTED_SESSION_DATA:
763 // TODO landonf: Investigate crypto support in bscan
765 Pmsg0(000, _("Got signed digest record\n"));
769 case STREAM_SIGNED_DIGEST:
770 // TODO landonf: Investigate crypto support in bscan
772 Pmsg0(000, _("Got signed digest record\n"));
776 case STREAM_PROGRAM_NAMES:
778 Pmsg1(000, _("Got Prog Names Stream: %s\n"), rec->data);
782 case STREAM_PROGRAM_DATA:
784 Pmsg0(000, _("Got Prog Data Stream record.\n"));
788 case STREAM_UNIX_ATTRIBUTES_ACCESS_ACL: /* Standard ACL attributes on UNIX */
789 case STREAM_UNIX_ATTRIBUTES_DEFAULT_ACL: /* Default ACL attributes on UNIX */
790 /* Ignore Unix attributes */
794 Pmsg2(0, _("Unknown stream type!!! stream=%d len=%i\n"), rec->Stream, rec->data_len);
801 * Free the Job Control Record if no one is still using it.
802 * Called from main free_jcr() routine in src/lib/jcr.c so
803 * that we can do our Director specific cleanup of the jcr.
805 static void bscan_free_jcr(JCR *jcr)
807 Dmsg0(200, "Start bscan free_jcr\n");
809 if (jcr->file_bsock) {
810 Dmsg0(200, "Close File bsock\n");
811 bnet_close(jcr->file_bsock);
813 if (jcr->store_bsock) {
814 Dmsg0(200, "Close Store bsock\n");
815 bnet_close(jcr->store_bsock);
817 if (jcr->RestoreBootstrap) {
818 free(jcr->RestoreBootstrap);
825 free_dcr(jcr->read_dcr);
826 jcr->read_dcr = NULL;
828 Dmsg0(200, "End bscan free_jcr\n");
832 * We got a File Attributes record on the tape. Now, lookup the Job
833 * record, and then create the attributes record.
835 static int create_file_attributes_record(B_DB *db, JCR *mjcr,
836 char *fname, char *lname, int type,
837 char *ap, DEV_RECORD *rec)
839 DCR *dcr = mjcr->read_dcr;
842 ar.ClientId = mjcr->ClientId;
843 ar.JobId = mjcr->JobId;
844 ar.Stream = rec->Stream;
845 ar.FileIndex = rec->FileIndex;
847 if (dcr->VolFirstIndex == 0) {
848 dcr->VolFirstIndex = rec->FileIndex;
850 dcr->FileIndex = rec->FileIndex;
857 if (!db_create_file_attributes_record(bjcr, db, &ar)) {
858 Pmsg1(0, _("Could not create File Attributes record. ERR=%s\n"), db_strerror(db));
861 mjcr->FileId = ar.FileId;
864 Pmsg1(000, _("Created File record: %s\n"), fname);
870 * For each Volume we see, we create a Medium record
872 static int create_media_record(B_DB *db, MEDIA_DBR *mr, VOLUME_LABEL *vl)
877 /* We mark Vols as Archive to keep them from being re-written */
878 bstrncpy(mr->VolStatus, "Archive", sizeof(mr->VolStatus));
879 mr->VolRetention = 365 * 3600 * 24; /* 1 year */
881 if (vl->VerNum >= 11) {
882 mr->FirstWritten = btime_to_utime(vl->write_btime);
883 mr->LabelDate = btime_to_utime(vl->label_btime);
885 /* DEPRECATED DO NOT USE */
886 dt.julian_day_number = vl->write_date;
887 dt.julian_day_fraction = vl->write_time;
889 mr->FirstWritten = mktime(&tm);
890 dt.julian_day_number = vl->label_date;
891 dt.julian_day_fraction = vl->label_time;
893 mr->LabelDate = mktime(&tm);
895 lasttime = mr->LabelDate;
896 if (mr->VolJobs == 0) {
899 if (mr->VolMounts == 0) {
907 if (!db_create_media_record(bjcr, db, mr)) {
908 Pmsg1(0, _("Could not create media record. ERR=%s\n"), db_strerror(db));
911 if (!db_update_media_record(bjcr, db, mr)) {
912 Pmsg1(0, _("Could not update media record. ERR=%s\n"), db_strerror(db));
916 Pmsg1(000, _("Created Media record for Volume: %s\n"), mr->VolumeName);
923 * Called at end of media to update it
925 static bool update_media_record(B_DB *db, MEDIA_DBR *mr)
927 if (!update_db && !update_vol_info) {
931 mr->LastWritten = lasttime;
932 if (!db_update_media_record(bjcr, db, mr)) {
933 Pmsg1(0, _("Could not update media record. ERR=%s\n"), db_strerror(db));
937 Pmsg1(000, _("Updated Media record at end of Volume: %s\n"), mr->VolumeName);
944 static int create_pool_record(B_DB *db, POOL_DBR *pr)
948 pr->VolRetention = 355 * 3600 * 24; /* 1 year */
953 if (!db_create_pool_record(bjcr, db, pr)) {
954 Pmsg1(0, _("Could not create pool record. ERR=%s\n"), db_strerror(db));
958 Pmsg1(000, _("Created Pool record for Pool: %s\n"), pr->Name);
966 * Called from SOS to create a client for the current Job
968 static int create_client_record(B_DB *db, CLIENT_DBR *cr)
973 if (!db_create_client_record(bjcr, db, cr)) {
974 Pmsg1(0, _("Could not create Client record. ERR=%s\n"), db_strerror(db));
978 Pmsg1(000, _("Created Client record for Client: %s\n"), cr->Name);
983 static int create_fileset_record(B_DB *db, FILESET_DBR *fsr)
989 if (fsr->MD5[0] == 0) {
990 fsr->MD5[0] = ' '; /* Equivalent to nothing */
993 if (db_get_fileset_record(bjcr, db, fsr)) {
995 Pmsg1(000, _("Fileset \"%s\" already exists.\n"), fsr->FileSet);
998 if (!db_create_fileset_record(bjcr, db, fsr)) {
999 Pmsg2(0, _("Could not create FileSet record \"%s\". ERR=%s\n"),
1000 fsr->FileSet, db_strerror(db));
1004 Pmsg1(000, _("Created FileSet record \"%s\"\n"), fsr->FileSet);
1011 * Simulate the two calls on the database to create
1012 * the Job record and to update it when the Job actually
1015 static JCR *create_job_record(B_DB *db, JOB_DBR *jr, SESSION_LABEL *label,
1019 struct date_time dt;
1022 jr->JobId = label->JobId;
1023 jr->JobType = label->JobType;
1024 jr->JobLevel = label->JobLevel;
1025 jr->JobStatus = JS_Created;
1026 bstrncpy(jr->Name, label->JobName, sizeof(jr->Name));
1027 bstrncpy(jr->Job, label->Job, sizeof(jr->Job));
1028 if (label->VerNum >= 11) {
1029 jr->SchedTime = btime_to_unix(label->write_btime);
1031 dt.julian_day_number = label->write_date;
1032 dt.julian_day_fraction = label->write_time;
1033 tm_decode(&dt, &tm);
1034 jr->SchedTime = mktime(&tm);
1037 jr->StartTime = jr->SchedTime;
1038 jr->JobTDate = (utime_t)jr->SchedTime;
1039 jr->VolSessionId = rec->VolSessionId;
1040 jr->VolSessionTime = rec->VolSessionTime;
1042 /* Now create a JCR as if starting the Job */
1043 mjcr = create_jcr(jr, rec, label->JobId);
1049 /* This creates the bare essentials */
1050 if (!db_create_job_record(bjcr, db, jr)) {
1051 Pmsg1(0, _("Could not create JobId record. ERR=%s\n"), db_strerror(db));
1055 /* This adds the client, StartTime, JobTDate, ... */
1056 if (!db_update_job_start_record(bjcr, db, jr)) {
1057 Pmsg1(0, _("Could not update job start record. ERR=%s\n"), db_strerror(db));
1060 Pmsg2(000, _("Created new JobId=%u record for original JobId=%u\n"), jr->JobId,
1062 mjcr->JobId = jr->JobId; /* set new JobId */
1067 * Simulate the database call that updates the Job
1068 * at Job termination time.
1070 static int update_job_record(B_DB *db, JOB_DBR *jr, SESSION_LABEL *elabel,
1073 struct date_time dt;
1077 mjcr = get_jcr_by_session(rec->VolSessionId, rec->VolSessionTime);
1079 Pmsg2(000, _("Could not find SessId=%d SessTime=%d for EOS record.\n"),
1080 rec->VolSessionId, rec->VolSessionTime);
1083 if (elabel->VerNum >= 11) {
1084 jr->EndTime = btime_to_unix(elabel->write_btime);
1086 dt.julian_day_number = elabel->write_date;
1087 dt.julian_day_fraction = elabel->write_time;
1088 tm_decode(&dt, &tm);
1089 jr->EndTime = mktime(&tm);
1091 lasttime = jr->EndTime;
1092 mjcr->end_time = jr->EndTime;
1094 jr->JobId = mjcr->JobId;
1095 jr->JobStatus = elabel->JobStatus;
1096 mjcr->JobStatus = elabel->JobStatus;
1097 jr->JobFiles = elabel->JobFiles;
1098 jr->JobBytes = elabel->JobBytes;
1099 jr->VolSessionId = rec->VolSessionId;
1100 jr->VolSessionTime = rec->VolSessionTime;
1101 jr->JobTDate = (utime_t)mjcr->start_time;
1102 jr->ClientId = mjcr->ClientId;
1109 if (!db_update_job_end_record(bjcr, db, jr)) {
1110 Pmsg2(0, _("Could not update JobId=%u record. ERR=%s\n"), jr->JobId, db_strerror(db));
1115 Pmsg3(000, _("Updated Job termination record for JobId=%u Level=%s TermStat=%c\n"),
1116 jr->JobId, job_level_to_str(mjcr->JobLevel), jr->JobStatus);
1119 const char *term_msg;
1120 static char term_code[70];
1121 char sdt[50], edt[50];
1122 char ec1[30], ec2[30], ec3[30];
1124 switch (mjcr->JobStatus) {
1126 term_msg = _("Backup OK");
1129 case JS_ErrorTerminated:
1130 term_msg = _("*** Backup Error ***");
1133 term_msg = _("Backup Canceled");
1136 term_msg = term_code;
1137 sprintf(term_code, _("Job Termination code: %d"), mjcr->JobStatus);
1140 bstrftime(sdt, sizeof(sdt), mjcr->start_time);
1141 bstrftime(edt, sizeof(edt), mjcr->end_time);
1142 Pmsg14(000, _("%s\n"
1146 "Backup Level: %s\n"
1150 "Files Written: %s\n"
1151 "Bytes Written: %s\n"
1152 "Volume Session Id: %d\n"
1153 "Volume Session Time: %d\n"
1154 "Last Volume Bytes: %s\n"
1155 "Termination: %s\n\n"),
1160 job_level_to_str(mjcr->JobLevel),
1164 edit_uint64_with_commas(mjcr->JobFiles, ec1),
1165 edit_uint64_with_commas(mjcr->JobBytes, ec2),
1167 mjcr->VolSessionTime,
1168 edit_uint64_with_commas(mr.VolBytes, ec3),
1175 static int create_jobmedia_record(B_DB *db, JCR *mjcr)
1178 DCR *dcr = mjcr->read_dcr;
1180 dcr->EndBlock = dev->EndBlock;
1181 dcr->EndFile = dev->EndFile;
1182 dcr->VolMediaId = dev->VolCatInfo.VolMediaId;
1184 memset(&jmr, 0, sizeof(jmr));
1185 jmr.JobId = mjcr->JobId;
1186 jmr.MediaId = mr.MediaId;
1187 jmr.FirstIndex = dcr->VolFirstIndex;
1188 jmr.LastIndex = dcr->VolLastIndex;
1189 jmr.StartFile = dcr->StartFile;
1190 jmr.EndFile = dcr->EndFile;
1191 jmr.StartBlock = dcr->StartBlock;
1192 jmr.EndBlock = dcr->EndBlock;
1199 if (!db_create_jobmedia_record(bjcr, db, &jmr)) {
1200 Pmsg1(0, _("Could not create JobMedia record. ERR=%s\n"), db_strerror(db));
1204 Pmsg2(000, _("Created JobMedia record JobId %d, MediaId %d\n"),
1205 jmr.JobId, jmr.MediaId);
1211 * Simulate the database call that updates the MD5/SHA1 record
1213 static int update_digest_record(B_DB *db, char *digest, DEV_RECORD *rec, int type)
1217 mjcr = get_jcr_by_session(rec->VolSessionId, rec->VolSessionTime);
1219 if (mr.VolJobs > 0) {
1220 Pmsg2(000, _("Could not find SessId=%d SessTime=%d for MD5/SHA1 record.\n"),
1221 rec->VolSessionId, rec->VolSessionTime);
1228 if (!update_db || mjcr->FileId == 0) {
1233 if (!db_add_digest_to_file_record(bjcr, db, mjcr->FileId, digest, type)) {
1234 Pmsg1(0, _("Could not add MD5/SHA1 to File record. ERR=%s\n"), db_strerror(db));
1239 Pmsg0(000, _("Updated MD5/SHA1 record\n"));
1247 * Create a JCR as if we are really starting the job
1249 static JCR *create_jcr(JOB_DBR *jr, DEV_RECORD *rec, uint32_t JobId)
1253 * Transfer as much as possible to the Job JCR. Most important is
1254 * the JobId and the ClientId.
1256 jobjcr = new_jcr(sizeof(JCR), bscan_free_jcr);
1257 jobjcr->JobType = jr->JobType;
1258 jobjcr->JobLevel = jr->JobLevel;
1259 jobjcr->JobStatus = jr->JobStatus;
1260 bstrncpy(jobjcr->Job, jr->Job, sizeof(jobjcr->Job));
1261 jobjcr->JobId = JobId; /* this is JobId on tape */
1262 jobjcr->sched_time = jr->SchedTime;
1263 jobjcr->start_time = jr->StartTime;
1264 jobjcr->VolSessionId = rec->VolSessionId;
1265 jobjcr->VolSessionTime = rec->VolSessionTime;
1266 jobjcr->ClientId = jr->ClientId;
1267 jobjcr->dcr = jobjcr->read_dcr = new_dcr(jobjcr, NULL, dev);
1272 /* Dummies to replace askdir.c */
1273 bool dir_find_next_appendable_volume(DCR *dcr) { return 1;}
1274 bool dir_update_volume_info(DCR *dcr, bool relabel) { return 1; }
1275 bool dir_create_jobmedia_record(DCR *dcr) { return 1; }
1276 bool dir_ask_sysop_to_create_appendable_volume(DCR *dcr) { return 1; }
1277 bool dir_update_file_attributes(DCR *dcr, DEV_RECORD *rec) { return 1;}
1278 bool dir_send_job_status(JCR *jcr) {return 1;}
1279 int generate_job_event(JCR *jcr, const char *event) { return 1; }
1281 bool dir_ask_sysop_to_mount_volume(DCR *dcr)
1283 DEVICE *dev = dcr->dev;
1284 Dmsg0(20, "Enter dir_ask_sysop_to_mount_volume\n");
1285 /* Close device so user can use autochanger if desired */
1286 fprintf(stderr, _("Mount Volume \"%s\" on device %s and press return when ready: "),
1287 dcr->VolumeName, dev->print_name());
1293 bool dir_get_volume_info(DCR *dcr, enum get_vol_info_rw writing)
1295 Dmsg0(100, "Fake dir_get_volume_info\n");
1296 bstrncpy(dcr->VolCatInfo.VolCatName, dcr->VolumeName, sizeof(dcr->VolCatInfo.VolCatName));
1297 dcr->VolCatInfo.VolCatParts = find_num_dvd_parts(dcr);
1298 Dmsg2(500, "Vol=%s num_parts=%d\n", dcr->VolCatInfo.VolCatName, dcr->VolCatInfo.VolCatParts);