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 " -dt print timestamp in debug output\n"
121 " -m update media info in database\n"
122 " -n <name> specify the database name (default bacula)\n"
123 " -u <user> specify database user name (default bacula)\n"
124 " -P <password> specify database password (default none)\n"
125 " -h <host> specify database host (default NULL)\n"
126 " -p proceed inspite of I/O errors\n"
128 " -s synchronize or store in database\n"
129 " -S show scan progress periodically\n"
131 " -V <Volumes> specify Volume names (separated by |)\n"
132 " -w <dir> specify working directory (default from conf file)\n"
133 " -? print this message\n\n"), 2001, VERSION, BDATE);
137 int main (int argc, char *argv[])
140 struct stat stat_buf;
141 char *VolumeName = NULL;
143 setlocale(LC_ALL, "");
144 bindtextdomain("bacula", LOCALEDIR);
145 textdomain("bacula");
148 my_name_is(argc, argv, "bscan");
149 init_msg(NULL, NULL);
153 while ((ch = getopt(argc, argv, "b:c:d:h:mn:pP:rsSu:vV:w:?")) != -1) {
159 bsr = parse_bsr(NULL, optarg);
162 case 'c': /* specify config file */
163 if (configfile != NULL) {
166 configfile = bstrdup(optarg);
169 case 'd': /* debug level */
170 if (*optarg == 't') {
171 dbg_timestamp = true;
173 debug_level = atoi(optarg);
174 if (debug_level <= 0) {
185 update_vol_info = true;
197 db_password = optarg;
216 case 'V': /* Volume name */
234 Pmsg0(0, _("Wrong number of arguments: \n"));
238 if (configfile == NULL) {
239 configfile = bstrdup(CONFIG_FILE);
242 parse_config(configfile);
244 me = (STORES *)GetNextRes(R_STORAGE, NULL);
247 Emsg1(M_ERROR_TERM, 0, _("No Storage resource defined in %s. Cannot continue.\n"),
251 /* Check if -w option given, otherwise use resource for working directory */
253 working_directory = wd;
254 } else if (!me->working_directory) {
255 Emsg1(M_ERROR_TERM, 0, _("No Working Directory defined in %s. Cannot continue.\n"),
258 working_directory = me->working_directory;
261 /* Check that working directory is good */
262 if (stat(working_directory, &stat_buf) != 0) {
263 Emsg1(M_ERROR_TERM, 0, _("Working Directory: %s not found. Cannot continue.\n"),
266 if (!S_ISDIR(stat_buf.st_mode)) {
267 Emsg1(M_ERROR_TERM, 0, _("Working Directory: %s is not a directory. Cannot continue.\n"),
271 bjcr = setup_jcr("bscan", argv[0], bsr, VolumeName, 1); /* read device */
275 dev = bjcr->read_dcr->dev;
279 fstat(dev->fd(), &sb);
280 currentVolumeSize = sb.st_size;
281 Pmsg1(000, _("First Volume Size = %sn"),
282 edit_uint64(currentVolumeSize, ed1));
285 if ((db=db_init_database(NULL, db_name, db_user, db_password,
286 db_host, 0, NULL, 0)) == NULL) {
287 Emsg0(M_ERROR_TERM, 0, _("Could not init Bacula database\n"));
289 if (!db_open_database(NULL, db)) {
290 Emsg0(M_ERROR_TERM, 0, db_strerror(db));
292 Dmsg0(200, "Database opened\n");
294 Pmsg2(000, _("Using Database: %s, User: %s\n"), db_name, db_user);
299 printf("Records added or updated in the catalog:\n%7d Media\n%7d Pool\n%7d Job\n%7d File\n",
300 num_media, num_pools, num_jobs, num_files);
303 printf("Records would have been added or updated in the catalog:\n%7d Media\n%7d Pool\n%7d Job\n%7d File\n",
304 num_media, num_pools, num_jobs, num_files);
313 * We are at the end of reading a tape. Now, we simulate handling
314 * the end of writing a tape by wiffling through the attached
315 * jcrs creating jobmedia records.
317 static bool bscan_mount_next_read_volume(DCR *dcr)
319 DEVICE *dev = dcr->dev;
321 Dmsg1(100, "Walk attached jcrs. Volume=%s\n", dev->VolCatInfo.VolCatName);
322 foreach_dlist(mdcr, dev->attached_dcrs) {
323 JCR *mjcr = mdcr->jcr;
324 Dmsg1(000, "========== JobId=%u ========\n", mjcr->JobId);
325 if (mjcr->JobId == 0) {
329 Pmsg1(000, _("Create JobMedia for Job %s\n"), mjcr->Job);
331 mdcr->StartBlock = dcr->StartBlock;
332 mdcr->StartFile = dcr->StartFile;
333 mdcr->EndBlock = dcr->EndBlock;
334 mdcr->EndFile = dcr->EndFile;
335 mdcr->VolMediaId = dcr->VolMediaId;
336 mjcr->read_dcr->VolLastIndex = dcr->VolLastIndex;
337 if (!create_jobmedia_record(db, mjcr)) {
338 Pmsg2(000, _("Could not create JobMedia record for Volume=%s Job=%s\n"),
339 dev->VolCatInfo.VolCatName, mjcr->Job);
343 update_media_record(db, &mr);
345 /* Now let common read routine get up next tape. Note,
346 * we call mount_next... with bscan's jcr because that is where we
347 * have the Volume list, but we get attached.
349 bool stat = mount_next_read_volume(dcr);
354 fstat(dev->fd(), &sb);
355 currentVolumeSize = sb.st_size;
356 Pmsg1(000, _("First Volume Size = %sn"),
357 edit_uint64(currentVolumeSize, ed1));
362 static void do_scan()
364 attr = new_attr(bjcr);
366 memset(&ar, 0, sizeof(ar));
367 memset(&pr, 0, sizeof(pr));
368 memset(&jr, 0, sizeof(jr));
369 memset(&cr, 0, sizeof(cr));
370 memset(&fsr, 0, sizeof(fsr));
371 memset(&fr, 0, sizeof(fr));
373 /* Detach bscan's jcr as we are not a real Job on the tape */
375 read_records(bjcr->read_dcr, record_cb, bscan_mount_next_read_volume);
378 db_write_batch_file_records(bjcr); /* used by bulk batch file insert */
384 * Returns: true if OK
387 static bool record_cb(DCR *dcr, DEV_RECORD *rec)
391 DEVICE *dev = dcr->dev;
392 JCR *bjcr = dcr->jcr;
393 DEV_BLOCK *block = dcr->block;
394 char digest[BASE64_SIZE(CRYPTO_DIGEST_MAX_SIZE)];
396 if (rec->data_len > 0) {
397 mr.VolBytes += rec->data_len + WRITE_RECHDR_LENGTH; /* Accumulate Volume bytes */
398 if (showProgress && currentVolumeSize > 0) {
399 int pct = (mr.VolBytes * 100) / currentVolumeSize;
400 if (pct != last_pct) {
401 fprintf(stdout, _("done: %d%%\n"), pct);
409 Pmsg5(000, _("Record: SessId=%u SessTim=%u FileIndex=%d Stream=%d len=%u\n"),
410 rec->VolSessionId, rec->VolSessionTime, rec->FileIndex,
411 rec->Stream, rec->data_len);
414 * Check for Start or End of Session Record
417 if (rec->FileIndex < 0) {
418 bool save_update_db = update_db;
421 dump_label_record(dev, rec, 1);
423 switch (rec->FileIndex) {
425 Pmsg0(000, _("Volume is prelabeled. This tape cannot be scanned.\n"));
430 unser_volume_label(dev, rec);
431 /* Check Pool info */
432 bstrncpy(pr.Name, dev->VolHdr.PoolName, sizeof(pr.Name));
433 bstrncpy(pr.PoolType, dev->VolHdr.PoolType, sizeof(pr.PoolType));
435 if (db_get_pool_record(bjcr, db, &pr)) {
437 Pmsg1(000, _("Pool record for %s found in DB.\n"), pr.Name);
441 Pmsg1(000, _("VOL_LABEL: Pool record not found for Pool: %s\n"),
444 create_pool_record(db, &pr);
446 if (strcmp(pr.PoolType, dev->VolHdr.PoolType) != 0) {
447 Pmsg2(000, _("VOL_LABEL: PoolType mismatch. DB=%s Vol=%s\n"),
448 pr.PoolType, dev->VolHdr.PoolType);
450 } else if (verbose) {
451 Pmsg1(000, _("Pool type \"%s\" is OK.\n"), pr.PoolType);
454 /* Check Media Info */
455 memset(&mr, 0, sizeof(mr));
456 bstrncpy(mr.VolumeName, dev->VolHdr.VolumeName, sizeof(mr.VolumeName));
457 mr.PoolId = pr.PoolId;
459 if (db_get_media_record(bjcr, db, &mr)) {
461 Pmsg1(000, _("Media record for %s found in DB.\n"), mr.VolumeName);
463 /* Clear out some volume statistics that will be updated */
464 mr.VolJobs = mr.VolFiles = mr.VolBlocks = 0;
465 mr.VolBytes = rec->data_len + 20;
468 Pmsg1(000, _("VOL_LABEL: Media record not found for Volume: %s\n"),
471 bstrncpy(mr.MediaType, dev->VolHdr.MediaType, sizeof(mr.MediaType));
472 create_media_record(db, &mr, &dev->VolHdr);
474 if (strcmp(mr.MediaType, dev->VolHdr.MediaType) != 0) {
475 Pmsg2(000, _("VOL_LABEL: MediaType mismatch. DB=%s Vol=%s\n"),
476 mr.MediaType, dev->VolHdr.MediaType);
477 return true; /* ignore error */
478 } else if (verbose) {
479 Pmsg1(000, _("Media type \"%s\" is OK.\n"), mr.MediaType);
481 /* Reset some DCR variables */
482 foreach_dlist(dcr, dev->attached_dcrs) {
483 dcr->VolFirstIndex = dcr->FileIndex = 0;
484 dcr->StartBlock = dcr->EndBlock = 0;
485 dcr->StartFile = dcr->EndFile = 0;
489 Pmsg1(000, _("VOL_LABEL: OK for Volume: %s\n"), mr.VolumeName);
495 if (ignored_msgs > 0) {
496 Pmsg1(000, _("%d \"errors\" ignored before first Start of Session record.\n"),
500 unser_session_label(&label, rec);
501 memset(&jr, 0, sizeof(jr));
502 bstrncpy(jr.Job, label.Job, sizeof(jr.Job));
503 if (db_get_job_record(bjcr, db, &jr)) {
504 /* Job record already exists in DB */
505 update_db = false; /* don't change db in create_job_record */
507 Pmsg1(000, _("SOS_LABEL: Found Job record for JobId: %d\n"), jr.JobId);
510 /* Must create a Job record in DB */
512 Pmsg1(000, _("SOS_LABEL: Job record not found for JobId: %d\n"),
516 /* Create Client record if not already there */
517 bstrncpy(cr.Name, label.ClientName, sizeof(cr.Name));
518 create_client_record(db, &cr);
519 jr.ClientId = cr.ClientId;
521 /* process label, if Job record exists don't update db */
522 mjcr = create_job_record(db, &jr, &label, rec);
523 dcr = mjcr->read_dcr;
524 update_db = save_update_db;
526 jr.PoolId = pr.PoolId;
528 /* Set start positions into JCR */
529 if (dev->is_tape()) {
531 * Note, we have already advanced past current block,
532 * so the correct number is block_num - 1
534 dcr->StartBlock = dev->block_num - 1;
535 dcr->StartFile = dev->file;
537 dcr->StartBlock = (uint32_t)dev->file_addr;
538 dcr->StartFile = (uint32_t)(dev->file_addr >> 32);
541 mjcr->start_time = jr.StartTime;
542 mjcr->JobLevel = jr.JobLevel;
544 mjcr->client_name = get_pool_memory(PM_FNAME);
545 pm_strcpy(mjcr->client_name, label.ClientName);
546 mjcr->fileset_name = get_pool_memory(PM_FNAME);
547 pm_strcpy(mjcr->fileset_name, label.FileSetName);
548 bstrncpy(dcr->pool_type, label.PoolType, sizeof(dcr->pool_type));
549 bstrncpy(dcr->pool_name, label.PoolName, sizeof(dcr->pool_name));
551 if (rec->VolSessionId != jr.VolSessionId) {
552 Pmsg3(000, _("SOS_LABEL: VolSessId mismatch for JobId=%u. DB=%d Vol=%d\n"),
554 jr.VolSessionId, rec->VolSessionId);
555 return true; /* ignore error */
557 if (rec->VolSessionTime != jr.VolSessionTime) {
558 Pmsg3(000, _("SOS_LABEL: VolSessTime mismatch for JobId=%u. DB=%d Vol=%d\n"),
560 jr.VolSessionTime, rec->VolSessionTime);
561 return true; /* ignore error */
563 if (jr.PoolId != pr.PoolId) {
564 Pmsg3(000, _("SOS_LABEL: PoolId mismatch for JobId=%u. DB=%d Vol=%d\n"),
566 jr.PoolId, pr.PoolId);
567 return true; /* ignore error */
572 unser_session_label(&elabel, rec);
574 /* Create FileSet record */
575 bstrncpy(fsr.FileSet, label.FileSetName, sizeof(fsr.FileSet));
576 bstrncpy(fsr.MD5, label.FileSetMD5, sizeof(fsr.MD5));
577 create_fileset_record(db, &fsr);
578 jr.FileSetId = fsr.FileSetId;
580 mjcr = get_jcr_by_session(rec->VolSessionId, rec->VolSessionTime);
582 Pmsg2(000, _("Could not find SessId=%d SessTime=%d for EOS record.\n"),
583 rec->VolSessionId, rec->VolSessionTime);
587 /* Do the final update to the Job record */
588 update_job_record(db, &jr, &elabel, rec);
590 mjcr->end_time = jr.EndTime;
591 mjcr->JobStatus = JS_Terminated;
593 /* Create JobMedia record */
594 mjcr->read_dcr->VolLastIndex = dcr->VolLastIndex;
595 create_jobmedia_record(db, mjcr);
596 detach_dcr_from_dev(mjcr->read_dcr);
604 case EOT_LABEL: /* end of all tapes */
606 * Wiffle through all jobs still open and close
611 foreach_dlist(mdcr, dev->attached_dcrs) {
612 JCR *mjcr = mdcr->jcr;
613 if (!mjcr || mjcr->JobId == 0) {
616 jr.JobId = mjcr->JobId;
617 /* Mark Job as Error Terimined */
618 jr.JobStatus = JS_ErrorTerminated;
619 jr.JobFiles = mjcr->JobFiles;
620 jr.JobBytes = mjcr->JobBytes;
621 jr.VolSessionId = mjcr->VolSessionId;
622 jr.VolSessionTime = mjcr->VolSessionTime;
623 jr.JobTDate = (utime_t)mjcr->start_time;
624 jr.ClientId = mjcr->ClientId;
625 if (!db_update_job_end_record(bjcr, db, &jr)) {
626 Pmsg1(0, _("Could not update job record. ERR=%s\n"), db_strerror(db));
628 mjcr->read_dcr = NULL;
632 mr.VolFiles = rec->File;
633 mr.VolBlocks = rec->Block;
634 mr.VolBytes += mr.VolBlocks * WRITE_BLKHDR_LENGTH; /* approx. */
636 update_media_record(db, &mr);
637 Pmsg3(0, _("End of all Volumes. VolFiles=%u VolBlocks=%u VolBytes=%s\n"), mr.VolFiles,
638 mr.VolBlocks, edit_uint64_with_commas(mr.VolBytes, ec1));
646 mjcr = get_jcr_by_session(rec->VolSessionId, rec->VolSessionTime);
648 if (mr.VolJobs > 0) {
649 Pmsg2(000, _("Could not find Job for SessId=%d SessTime=%d record.\n"),
650 rec->VolSessionId, rec->VolSessionTime);
656 dcr = mjcr->read_dcr;
657 if (dcr->VolFirstIndex == 0) {
658 dcr->VolFirstIndex = block->FirstIndex;
661 /* File Attributes stream */
662 switch (rec->Stream) {
663 case STREAM_UNIX_ATTRIBUTES:
664 case STREAM_UNIX_ATTRIBUTES_EX:
666 if (!unpack_attributes_record(bjcr, rec->Stream, rec->data, attr)) {
667 Emsg0(M_ERROR_TERM, 0, _("Cannot continue.\n"));
670 if (attr->file_index != rec->FileIndex) {
671 Emsg2(M_ERROR_TERM, 0, _("Record header file index %ld not equal record index %ld\n"),
672 rec->FileIndex, attr->file_index);
676 decode_stat(attr->attr, &attr->statp, &attr->LinkFI);
677 build_attr_output_fnames(bjcr, attr);
678 print_ls_output(bjcr, attr);
680 fr.JobId = mjcr->JobId;
683 if (verbose && (num_files & 0x7FFF) == 0) {
684 char ed1[30], ed2[30], ed3[30], ed4[30];
685 Pmsg4(000, _("%s file records. At file:blk=%s:%s bytes=%s\n"),
686 edit_uint64_with_commas(num_files, ed1),
687 edit_uint64_with_commas(rec->File, ed2),
688 edit_uint64_with_commas(rec->Block, ed3),
689 edit_uint64_with_commas(mr.VolBytes, ed4));
691 create_file_attributes_record(db, mjcr, attr->fname, attr->lname,
692 attr->type, attr->attr, rec);
697 case STREAM_WIN32_DATA:
698 case STREAM_FILE_DATA:
699 case STREAM_SPARSE_DATA:
700 case STREAM_ENCRYPTED_FILE_DATA:
701 case STREAM_ENCRYPTED_WIN32_DATA:
702 case STREAM_ENCRYPTED_MACOS_FORK_DATA:
704 * For encrypted stream, this is an approximation.
705 * The data must be decrypted to know the correct length.
707 mjcr->JobBytes += rec->data_len;
708 if (rec->Stream == STREAM_SPARSE_DATA) {
709 mjcr->JobBytes -= sizeof(uint64_t);
712 free_jcr(mjcr); /* done using JCR */
715 case STREAM_GZIP_DATA:
716 case STREAM_ENCRYPTED_FILE_GZIP_DATA:
717 case STREAM_ENCRYPTED_WIN32_GZIP_DATA:
718 /* No correct, we should (decrypt and) expand it
721 mjcr->JobBytes += rec->data_len;
725 case STREAM_SPARSE_GZIP_DATA:
726 mjcr->JobBytes += rec->data_len - sizeof(uint64_t); /* No correct, we should expand it */
727 free_jcr(mjcr); /* done using JCR */
730 /* Win32 GZIP stream */
731 case STREAM_WIN32_GZIP_DATA:
732 mjcr->JobBytes += rec->data_len;
733 free_jcr(mjcr); /* done using JCR */
736 case STREAM_MD5_DIGEST:
737 bin_to_base64(digest, sizeof(digest), (char *)rec->data, CRYPTO_DIGEST_MD5_SIZE, true);
739 Pmsg1(000, _("Got MD5 record: %s\n"), digest);
741 update_digest_record(db, digest, rec, CRYPTO_DIGEST_MD5);
744 case STREAM_SHA1_DIGEST:
745 bin_to_base64(digest, sizeof(digest), (char *)rec->data, CRYPTO_DIGEST_SHA1_SIZE, true);
747 Pmsg1(000, _("Got SHA1 record: %s\n"), digest);
749 update_digest_record(db, digest, rec, CRYPTO_DIGEST_SHA1);
752 case STREAM_SHA256_DIGEST:
753 bin_to_base64(digest, sizeof(digest), (char *)rec->data, CRYPTO_DIGEST_SHA256_SIZE, true);
755 Pmsg1(000, _("Got SHA256 record: %s\n"), digest);
757 update_digest_record(db, digest, rec, CRYPTO_DIGEST_SHA256);
760 case STREAM_SHA512_DIGEST:
761 bin_to_base64(digest, sizeof(digest), (char *)rec->data, CRYPTO_DIGEST_SHA512_SIZE, true);
763 Pmsg1(000, _("Got SHA512 record: %s\n"), digest);
765 update_digest_record(db, digest, rec, CRYPTO_DIGEST_SHA512);
768 case STREAM_ENCRYPTED_SESSION_DATA:
769 // TODO landonf: Investigate crypto support in bscan
771 Pmsg0(000, _("Got signed digest record\n"));
775 case STREAM_SIGNED_DIGEST:
776 // TODO landonf: Investigate crypto support in bscan
778 Pmsg0(000, _("Got signed digest record\n"));
782 case STREAM_PROGRAM_NAMES:
784 Pmsg1(000, _("Got Prog Names Stream: %s\n"), rec->data);
788 case STREAM_PROGRAM_DATA:
790 Pmsg0(000, _("Got Prog Data Stream record.\n"));
794 case STREAM_UNIX_ATTRIBUTES_ACCESS_ACL: /* Standard ACL attributes on UNIX */
795 case STREAM_UNIX_ATTRIBUTES_DEFAULT_ACL: /* Default ACL attributes on UNIX */
796 /* Ignore Unix attributes */
800 Pmsg2(0, _("Unknown stream type!!! stream=%d len=%i\n"), rec->Stream, rec->data_len);
807 * Free the Job Control Record if no one is still using it.
808 * Called from main free_jcr() routine in src/lib/jcr.c so
809 * that we can do our Director specific cleanup of the jcr.
811 static void bscan_free_jcr(JCR *jcr)
813 Dmsg0(200, "Start bscan free_jcr\n");
815 if (jcr->file_bsock) {
816 Dmsg0(200, "Close File bsock\n");
817 bnet_close(jcr->file_bsock);
819 if (jcr->store_bsock) {
820 Dmsg0(200, "Close Store bsock\n");
821 bnet_close(jcr->store_bsock);
823 if (jcr->RestoreBootstrap) {
824 free(jcr->RestoreBootstrap);
831 free_dcr(jcr->read_dcr);
832 jcr->read_dcr = NULL;
834 Dmsg0(200, "End bscan free_jcr\n");
838 * We got a File Attributes record on the tape. Now, lookup the Job
839 * record, and then create the attributes record.
841 static int create_file_attributes_record(B_DB *db, JCR *mjcr,
842 char *fname, char *lname, int type,
843 char *ap, DEV_RECORD *rec)
845 DCR *dcr = mjcr->read_dcr;
848 ar.ClientId = mjcr->ClientId;
849 ar.JobId = mjcr->JobId;
850 ar.Stream = rec->Stream;
851 ar.FileIndex = rec->FileIndex;
853 if (dcr->VolFirstIndex == 0) {
854 dcr->VolFirstIndex = rec->FileIndex;
856 dcr->FileIndex = rec->FileIndex;
863 if (!db_create_file_attributes_record(bjcr, db, &ar)) {
864 Pmsg1(0, _("Could not create File Attributes record. ERR=%s\n"), db_strerror(db));
867 mjcr->FileId = ar.FileId;
870 Pmsg1(000, _("Created File record: %s\n"), fname);
876 * For each Volume we see, we create a Medium record
878 static int create_media_record(B_DB *db, MEDIA_DBR *mr, VOLUME_LABEL *vl)
883 /* We mark Vols as Archive to keep them from being re-written */
884 bstrncpy(mr->VolStatus, "Archive", sizeof(mr->VolStatus));
885 mr->VolRetention = 365 * 3600 * 24; /* 1 year */
887 if (vl->VerNum >= 11) {
888 mr->FirstWritten = btime_to_utime(vl->write_btime);
889 mr->LabelDate = btime_to_utime(vl->label_btime);
891 /* DEPRECATED DO NOT USE */
892 dt.julian_day_number = vl->write_date;
893 dt.julian_day_fraction = vl->write_time;
895 mr->FirstWritten = mktime(&tm);
896 dt.julian_day_number = vl->label_date;
897 dt.julian_day_fraction = vl->label_time;
899 mr->LabelDate = mktime(&tm);
901 lasttime = mr->LabelDate;
902 if (mr->VolJobs == 0) {
905 if (mr->VolMounts == 0) {
913 if (!db_create_media_record(bjcr, db, mr)) {
914 Pmsg1(0, _("Could not create media record. ERR=%s\n"), db_strerror(db));
917 if (!db_update_media_record(bjcr, db, mr)) {
918 Pmsg1(0, _("Could not update media record. ERR=%s\n"), db_strerror(db));
922 Pmsg1(000, _("Created Media record for Volume: %s\n"), mr->VolumeName);
929 * Called at end of media to update it
931 static bool update_media_record(B_DB *db, MEDIA_DBR *mr)
933 if (!update_db && !update_vol_info) {
937 mr->LastWritten = lasttime;
938 if (!db_update_media_record(bjcr, db, mr)) {
939 Pmsg1(0, _("Could not update media record. ERR=%s\n"), db_strerror(db));
943 Pmsg1(000, _("Updated Media record at end of Volume: %s\n"), mr->VolumeName);
950 static int create_pool_record(B_DB *db, POOL_DBR *pr)
954 pr->VolRetention = 355 * 3600 * 24; /* 1 year */
959 if (!db_create_pool_record(bjcr, db, pr)) {
960 Pmsg1(0, _("Could not create pool record. ERR=%s\n"), db_strerror(db));
964 Pmsg1(000, _("Created Pool record for Pool: %s\n"), pr->Name);
972 * Called from SOS to create a client for the current Job
974 static int create_client_record(B_DB *db, CLIENT_DBR *cr)
979 if (!db_create_client_record(bjcr, db, cr)) {
980 Pmsg1(0, _("Could not create Client record. ERR=%s\n"), db_strerror(db));
984 Pmsg1(000, _("Created Client record for Client: %s\n"), cr->Name);
989 static int create_fileset_record(B_DB *db, FILESET_DBR *fsr)
995 if (fsr->MD5[0] == 0) {
996 fsr->MD5[0] = ' '; /* Equivalent to nothing */
999 if (db_get_fileset_record(bjcr, db, fsr)) {
1001 Pmsg1(000, _("Fileset \"%s\" already exists.\n"), fsr->FileSet);
1004 if (!db_create_fileset_record(bjcr, db, fsr)) {
1005 Pmsg2(0, _("Could not create FileSet record \"%s\". ERR=%s\n"),
1006 fsr->FileSet, db_strerror(db));
1010 Pmsg1(000, _("Created FileSet record \"%s\"\n"), fsr->FileSet);
1017 * Simulate the two calls on the database to create
1018 * the Job record and to update it when the Job actually
1021 static JCR *create_job_record(B_DB *db, JOB_DBR *jr, SESSION_LABEL *label,
1025 struct date_time dt;
1028 jr->JobId = label->JobId;
1029 jr->JobType = label->JobType;
1030 jr->JobLevel = label->JobLevel;
1031 jr->JobStatus = JS_Created;
1032 bstrncpy(jr->Name, label->JobName, sizeof(jr->Name));
1033 bstrncpy(jr->Job, label->Job, sizeof(jr->Job));
1034 if (label->VerNum >= 11) {
1035 jr->SchedTime = btime_to_unix(label->write_btime);
1037 dt.julian_day_number = label->write_date;
1038 dt.julian_day_fraction = label->write_time;
1039 tm_decode(&dt, &tm);
1040 jr->SchedTime = mktime(&tm);
1043 jr->StartTime = jr->SchedTime;
1044 jr->JobTDate = (utime_t)jr->SchedTime;
1045 jr->VolSessionId = rec->VolSessionId;
1046 jr->VolSessionTime = rec->VolSessionTime;
1048 /* Now create a JCR as if starting the Job */
1049 mjcr = create_jcr(jr, rec, label->JobId);
1055 /* This creates the bare essentials */
1056 if (!db_create_job_record(bjcr, db, jr)) {
1057 Pmsg1(0, _("Could not create JobId record. ERR=%s\n"), db_strerror(db));
1061 /* This adds the client, StartTime, JobTDate, ... */
1062 if (!db_update_job_start_record(bjcr, db, jr)) {
1063 Pmsg1(0, _("Could not update job start record. ERR=%s\n"), db_strerror(db));
1066 Pmsg2(000, _("Created new JobId=%u record for original JobId=%u\n"), jr->JobId,
1068 mjcr->JobId = jr->JobId; /* set new JobId */
1073 * Simulate the database call that updates the Job
1074 * at Job termination time.
1076 static int update_job_record(B_DB *db, JOB_DBR *jr, SESSION_LABEL *elabel,
1079 struct date_time dt;
1083 mjcr = get_jcr_by_session(rec->VolSessionId, rec->VolSessionTime);
1085 Pmsg2(000, _("Could not find SessId=%d SessTime=%d for EOS record.\n"),
1086 rec->VolSessionId, rec->VolSessionTime);
1089 if (elabel->VerNum >= 11) {
1090 jr->EndTime = btime_to_unix(elabel->write_btime);
1092 dt.julian_day_number = elabel->write_date;
1093 dt.julian_day_fraction = elabel->write_time;
1094 tm_decode(&dt, &tm);
1095 jr->EndTime = mktime(&tm);
1097 lasttime = jr->EndTime;
1098 mjcr->end_time = jr->EndTime;
1100 jr->JobId = mjcr->JobId;
1101 jr->JobStatus = elabel->JobStatus;
1102 mjcr->JobStatus = elabel->JobStatus;
1103 jr->JobFiles = elabel->JobFiles;
1104 jr->JobBytes = elabel->JobBytes;
1105 jr->VolSessionId = rec->VolSessionId;
1106 jr->VolSessionTime = rec->VolSessionTime;
1107 jr->JobTDate = (utime_t)mjcr->start_time;
1108 jr->ClientId = mjcr->ClientId;
1115 if (!db_update_job_end_record(bjcr, db, jr)) {
1116 Pmsg2(0, _("Could not update JobId=%u record. ERR=%s\n"), jr->JobId, db_strerror(db));
1121 Pmsg3(000, _("Updated Job termination record for JobId=%u Level=%s TermStat=%c\n"),
1122 jr->JobId, job_level_to_str(mjcr->JobLevel), jr->JobStatus);
1125 const char *term_msg;
1126 static char term_code[70];
1127 char sdt[50], edt[50];
1128 char ec1[30], ec2[30], ec3[30];
1130 switch (mjcr->JobStatus) {
1132 term_msg = _("Backup OK");
1135 case JS_ErrorTerminated:
1136 term_msg = _("*** Backup Error ***");
1139 term_msg = _("Backup Canceled");
1142 term_msg = term_code;
1143 sprintf(term_code, _("Job Termination code: %d"), mjcr->JobStatus);
1146 bstrftime(sdt, sizeof(sdt), mjcr->start_time);
1147 bstrftime(edt, sizeof(edt), mjcr->end_time);
1148 Pmsg14(000, _("%s\n"
1152 "Backup Level: %s\n"
1156 "Files Written: %s\n"
1157 "Bytes Written: %s\n"
1158 "Volume Session Id: %d\n"
1159 "Volume Session Time: %d\n"
1160 "Last Volume Bytes: %s\n"
1161 "Termination: %s\n\n"),
1166 job_level_to_str(mjcr->JobLevel),
1170 edit_uint64_with_commas(mjcr->JobFiles, ec1),
1171 edit_uint64_with_commas(mjcr->JobBytes, ec2),
1173 mjcr->VolSessionTime,
1174 edit_uint64_with_commas(mr.VolBytes, ec3),
1181 static int create_jobmedia_record(B_DB *db, JCR *mjcr)
1184 DCR *dcr = mjcr->read_dcr;
1186 dcr->EndBlock = dev->EndBlock;
1187 dcr->EndFile = dev->EndFile;
1188 dcr->VolMediaId = dev->VolCatInfo.VolMediaId;
1190 memset(&jmr, 0, sizeof(jmr));
1191 jmr.JobId = mjcr->JobId;
1192 jmr.MediaId = mr.MediaId;
1193 jmr.FirstIndex = dcr->VolFirstIndex;
1194 jmr.LastIndex = dcr->VolLastIndex;
1195 jmr.StartFile = dcr->StartFile;
1196 jmr.EndFile = dcr->EndFile;
1197 jmr.StartBlock = dcr->StartBlock;
1198 jmr.EndBlock = dcr->EndBlock;
1205 if (!db_create_jobmedia_record(bjcr, db, &jmr)) {
1206 Pmsg1(0, _("Could not create JobMedia record. ERR=%s\n"), db_strerror(db));
1210 Pmsg2(000, _("Created JobMedia record JobId %d, MediaId %d\n"),
1211 jmr.JobId, jmr.MediaId);
1217 * Simulate the database call that updates the MD5/SHA1 record
1219 static int update_digest_record(B_DB *db, char *digest, DEV_RECORD *rec, int type)
1223 mjcr = get_jcr_by_session(rec->VolSessionId, rec->VolSessionTime);
1225 if (mr.VolJobs > 0) {
1226 Pmsg2(000, _("Could not find SessId=%d SessTime=%d for MD5/SHA1 record.\n"),
1227 rec->VolSessionId, rec->VolSessionTime);
1234 if (!update_db || mjcr->FileId == 0) {
1239 if (!db_add_digest_to_file_record(bjcr, db, mjcr->FileId, digest, type)) {
1240 Pmsg1(0, _("Could not add MD5/SHA1 to File record. ERR=%s\n"), db_strerror(db));
1245 Pmsg0(000, _("Updated MD5/SHA1 record\n"));
1253 * Create a JCR as if we are really starting the job
1255 static JCR *create_jcr(JOB_DBR *jr, DEV_RECORD *rec, uint32_t JobId)
1259 * Transfer as much as possible to the Job JCR. Most important is
1260 * the JobId and the ClientId.
1262 jobjcr = new_jcr(sizeof(JCR), bscan_free_jcr);
1263 jobjcr->JobType = jr->JobType;
1264 jobjcr->JobLevel = jr->JobLevel;
1265 jobjcr->JobStatus = jr->JobStatus;
1266 bstrncpy(jobjcr->Job, jr->Job, sizeof(jobjcr->Job));
1267 jobjcr->JobId = JobId; /* this is JobId on tape */
1268 jobjcr->sched_time = jr->SchedTime;
1269 jobjcr->start_time = jr->StartTime;
1270 jobjcr->VolSessionId = rec->VolSessionId;
1271 jobjcr->VolSessionTime = rec->VolSessionTime;
1272 jobjcr->ClientId = jr->ClientId;
1273 jobjcr->dcr = jobjcr->read_dcr = new_dcr(jobjcr, NULL, dev);
1278 /* Dummies to replace askdir.c */
1279 bool dir_find_next_appendable_volume(DCR *dcr) { return 1;}
1280 bool dir_update_volume_info(DCR *dcr, bool relabel, bool update_LastWritten) { return 1; }
1281 bool dir_create_jobmedia_record(DCR *dcr) { return 1; }
1282 bool dir_ask_sysop_to_create_appendable_volume(DCR *dcr) { return 1; }
1283 bool dir_update_file_attributes(DCR *dcr, DEV_RECORD *rec) { return 1;}
1284 bool dir_send_job_status(JCR *jcr) {return 1;}
1285 int generate_job_event(JCR *jcr, const char *event) { return 1; }
1287 bool dir_ask_sysop_to_mount_volume(DCR *dcr)
1289 DEVICE *dev = dcr->dev;
1290 Dmsg0(20, "Enter dir_ask_sysop_to_mount_volume\n");
1291 /* Close device so user can use autochanger if desired */
1292 fprintf(stderr, _("Mount Volume \"%s\" on device %s and press return when ready: "),
1293 dcr->VolumeName, dev->print_name());
1299 bool dir_get_volume_info(DCR *dcr, enum get_vol_info_rw writing)
1301 Dmsg0(100, "Fake dir_get_volume_info\n");
1302 bstrncpy(dcr->VolCatInfo.VolCatName, dcr->VolumeName, sizeof(dcr->VolCatInfo.VolCatName));
1303 dcr->VolCatInfo.VolCatParts = find_num_dvd_parts(dcr);
1304 Dmsg2(500, "Vol=%s num_parts=%d\n", dcr->VolCatInfo.VolCatName, dcr->VolCatInfo.VolCatParts);