2 Bacula® - The Network Backup Solution
4 Copyright (C) 2001-2008 Free Software Foundation Europe e.V.
6 The main author of Bacula is Kern Sibbald, with contributions from
7 many others, a complete list can be found in the file AUTHORS.
8 This program is Free Software; you can redistribute it and/or
9 modify it under the terms of version two of the GNU General Public
10 License as published by the Free Software Foundation and included
13 This program is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 General Public License for more details.
18 You should have received a copy of the GNU General Public License
19 along with this program; if not, write to the Free Software
20 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
23 Bacula® is a registered trademark of John Walker.
24 The licensor of Bacula is the Free Software Foundation Europe
25 (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zürich,
26 Switzerland, email:ftf@fsfeurope.org.
30 * Program to scan a Bacula Volume and compare it with
31 * the catalog and optionally synchronize the catalog
34 * Kern E. Sibbald, December 2001
42 #include "findlib/find.h"
43 #include "cats/cats.h"
46 int generate_daemon_event(JCR *jcr, const char *event) { return 1; }
48 /* Forward referenced functions */
49 static void do_scan(void);
50 static bool record_cb(DCR *dcr, DEV_RECORD *rec);
51 static int create_file_attributes_record(B_DB *db, JCR *mjcr,
52 char *fname, char *lname, int type,
53 char *ap, DEV_RECORD *rec);
54 static int create_media_record(B_DB *db, MEDIA_DBR *mr, VOLUME_LABEL *vl);
55 static bool update_media_record(B_DB *db, MEDIA_DBR *mr);
56 static int create_pool_record(B_DB *db, POOL_DBR *pr);
57 static JCR *create_job_record(B_DB *db, JOB_DBR *mr, SESSION_LABEL *label, DEV_RECORD *rec);
58 static int update_job_record(B_DB *db, JOB_DBR *mr, SESSION_LABEL *elabel,
60 static int create_client_record(B_DB *db, CLIENT_DBR *cr);
61 static int create_fileset_record(B_DB *db, FILESET_DBR *fsr);
62 static int create_jobmedia_record(B_DB *db, JCR *jcr);
63 static JCR *create_jcr(JOB_DBR *jr, DEV_RECORD *rec, uint32_t JobId);
64 static int update_digest_record(B_DB *db, char *digest, DEV_RECORD *rec, int type);
68 static DEVICE *dev = NULL;
70 static JCR *bjcr; /* jcr for bscan */
71 static BSR *bsr = NULL;
76 static FILESET_DBR fsr;
79 static SESSION_LABEL label;
80 static SESSION_LABEL elabel;
83 static time_t lasttime = 0;
85 static const char *db_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);
302 printf("Records would have been added or updated in the catalog:\n%7d Media\n%7d Pool\n%7d Job\n%7d File\n",
303 num_media, num_pools, num_jobs, num_files);
312 * We are at the end of reading a tape. Now, we simulate handling
313 * the end of writing a tape by wiffling through the attached
314 * jcrs creating jobmedia records.
316 static bool bscan_mount_next_read_volume(DCR *dcr)
318 DEVICE *dev = dcr->dev;
320 Dmsg1(100, "Walk attached jcrs. Volume=%s\n", dev->VolCatInfo.VolCatName);
321 foreach_dlist(mdcr, dev->attached_dcrs) {
322 JCR *mjcr = mdcr->jcr;
323 Dmsg1(000, "========== JobId=%u ========\n", mjcr->JobId);
324 if (mjcr->JobId == 0) {
328 Pmsg1(000, _("Create JobMedia for Job %s\n"), mjcr->Job);
330 mdcr->StartBlock = dcr->StartBlock;
331 mdcr->StartFile = dcr->StartFile;
332 mdcr->EndBlock = dcr->EndBlock;
333 mdcr->EndFile = dcr->EndFile;
334 mdcr->VolMediaId = dcr->VolMediaId;
335 mjcr->read_dcr->VolLastIndex = dcr->VolLastIndex;
336 if (!create_jobmedia_record(db, mjcr)) {
337 Pmsg2(000, _("Could not create JobMedia record for Volume=%s Job=%s\n"),
338 dev->VolCatInfo.VolCatName, mjcr->Job);
342 update_media_record(db, &mr);
344 /* Now let common read routine get up next tape. Note,
345 * we call mount_next... with bscan's jcr because that is where we
346 * have the Volume list, but we get attached.
348 bool stat = mount_next_read_volume(dcr);
353 fstat(dev->fd(), &sb);
354 currentVolumeSize = sb.st_size;
355 Pmsg1(000, _("First Volume Size = %sn"),
356 edit_uint64(currentVolumeSize, ed1));
361 static void do_scan()
363 attr = new_attr(bjcr);
365 memset(&ar, 0, sizeof(ar));
366 memset(&pr, 0, sizeof(pr));
367 memset(&jr, 0, sizeof(jr));
368 memset(&cr, 0, sizeof(cr));
369 memset(&fsr, 0, sizeof(fsr));
370 memset(&fr, 0, sizeof(fr));
372 /* Detach bscan's jcr as we are not a real Job on the tape */
374 read_records(bjcr->read_dcr, record_cb, bscan_mount_next_read_volume);
377 db_write_batch_file_records(bjcr); /* used by bulk batch file insert */
383 * Returns: true if OK
386 static bool record_cb(DCR *dcr, DEV_RECORD *rec)
390 DEVICE *dev = dcr->dev;
391 JCR *bjcr = dcr->jcr;
392 DEV_BLOCK *block = dcr->block;
393 char digest[BASE64_SIZE(CRYPTO_DIGEST_MAX_SIZE)];
395 if (rec->data_len > 0) {
396 mr.VolBytes += rec->data_len + WRITE_RECHDR_LENGTH; /* Accumulate Volume bytes */
397 if (showProgress && currentVolumeSize > 0) {
398 int pct = (mr.VolBytes * 100) / currentVolumeSize;
399 if (pct != last_pct) {
400 fprintf(stdout, _("done: %d%%\n"), pct);
408 Pmsg5(000, _("Record: SessId=%u SessTim=%u FileIndex=%d Stream=%d len=%u\n"),
409 rec->VolSessionId, rec->VolSessionTime, rec->FileIndex,
410 rec->Stream, rec->data_len);
413 * Check for Start or End of Session Record
416 if (rec->FileIndex < 0) {
417 bool save_update_db = update_db;
420 dump_label_record(dev, rec, 1);
422 switch (rec->FileIndex) {
424 Pmsg0(000, _("Volume is prelabeled. This tape cannot be scanned.\n"));
429 unser_volume_label(dev, rec);
430 /* Check Pool info */
431 bstrncpy(pr.Name, dev->VolHdr.PoolName, sizeof(pr.Name));
432 bstrncpy(pr.PoolType, dev->VolHdr.PoolType, sizeof(pr.PoolType));
434 if (db_get_pool_record(bjcr, db, &pr)) {
436 Pmsg1(000, _("Pool record for %s found in DB.\n"), pr.Name);
440 Pmsg1(000, _("VOL_LABEL: Pool record not found for Pool: %s\n"),
443 create_pool_record(db, &pr);
445 if (strcmp(pr.PoolType, dev->VolHdr.PoolType) != 0) {
446 Pmsg2(000, _("VOL_LABEL: PoolType mismatch. DB=%s Vol=%s\n"),
447 pr.PoolType, dev->VolHdr.PoolType);
449 } else if (verbose) {
450 Pmsg1(000, _("Pool type \"%s\" is OK.\n"), pr.PoolType);
453 /* Check Media Info */
454 memset(&mr, 0, sizeof(mr));
455 bstrncpy(mr.VolumeName, dev->VolHdr.VolumeName, sizeof(mr.VolumeName));
456 mr.PoolId = pr.PoolId;
458 if (db_get_media_record(bjcr, db, &mr)) {
460 Pmsg1(000, _("Media record for %s found in DB.\n"), mr.VolumeName);
462 /* Clear out some volume statistics that will be updated */
463 mr.VolJobs = mr.VolFiles = mr.VolBlocks = 0;
464 mr.VolBytes = rec->data_len + 20;
467 Pmsg1(000, _("VOL_LABEL: Media record not found for Volume: %s\n"),
470 bstrncpy(mr.MediaType, dev->VolHdr.MediaType, sizeof(mr.MediaType));
471 create_media_record(db, &mr, &dev->VolHdr);
473 if (strcmp(mr.MediaType, dev->VolHdr.MediaType) != 0) {
474 Pmsg2(000, _("VOL_LABEL: MediaType mismatch. DB=%s Vol=%s\n"),
475 mr.MediaType, dev->VolHdr.MediaType);
476 return true; /* ignore error */
477 } else if (verbose) {
478 Pmsg1(000, _("Media type \"%s\" is OK.\n"), mr.MediaType);
480 /* Reset some DCR variables */
481 foreach_dlist(dcr, dev->attached_dcrs) {
482 dcr->VolFirstIndex = dcr->FileIndex = 0;
483 dcr->StartBlock = dcr->EndBlock = 0;
484 dcr->StartFile = dcr->EndFile = 0;
488 Pmsg1(000, _("VOL_LABEL: OK for Volume: %s\n"), mr.VolumeName);
494 if (ignored_msgs > 0) {
495 Pmsg1(000, _("%d \"errors\" ignored before first Start of Session record.\n"),
499 unser_session_label(&label, rec);
500 memset(&jr, 0, sizeof(jr));
501 bstrncpy(jr.Job, label.Job, sizeof(jr.Job));
502 if (db_get_job_record(bjcr, db, &jr)) {
503 /* Job record already exists in DB */
504 update_db = false; /* don't change db in create_job_record */
506 Pmsg1(000, _("SOS_LABEL: Found Job record for JobId: %d\n"), jr.JobId);
509 /* Must create a Job record in DB */
511 Pmsg1(000, _("SOS_LABEL: Job record not found for JobId: %d\n"),
515 /* Create Client record if not already there */
516 bstrncpy(cr.Name, label.ClientName, sizeof(cr.Name));
517 create_client_record(db, &cr);
518 jr.ClientId = cr.ClientId;
520 /* process label, if Job record exists don't update db */
521 mjcr = create_job_record(db, &jr, &label, rec);
522 dcr = mjcr->read_dcr;
523 update_db = save_update_db;
525 jr.PoolId = pr.PoolId;
527 /* Set start positions into JCR */
528 if (dev->is_tape()) {
530 * Note, we have already advanced past current block,
531 * so the correct number is block_num - 1
533 dcr->StartBlock = dev->block_num - 1;
534 dcr->StartFile = dev->file;
536 dcr->StartBlock = (uint32_t)dev->file_addr;
537 dcr->StartFile = (uint32_t)(dev->file_addr >> 32);
540 mjcr->start_time = jr.StartTime;
541 mjcr->JobLevel = jr.JobLevel;
543 mjcr->client_name = get_pool_memory(PM_FNAME);
544 pm_strcpy(mjcr->client_name, label.ClientName);
545 mjcr->fileset_name = get_pool_memory(PM_FNAME);
546 pm_strcpy(mjcr->fileset_name, label.FileSetName);
547 bstrncpy(dcr->pool_type, label.PoolType, sizeof(dcr->pool_type));
548 bstrncpy(dcr->pool_name, label.PoolName, sizeof(dcr->pool_name));
550 if (rec->VolSessionId != jr.VolSessionId) {
551 Pmsg3(000, _("SOS_LABEL: VolSessId mismatch for JobId=%u. DB=%d Vol=%d\n"),
553 jr.VolSessionId, rec->VolSessionId);
554 return true; /* ignore error */
556 if (rec->VolSessionTime != jr.VolSessionTime) {
557 Pmsg3(000, _("SOS_LABEL: VolSessTime mismatch for JobId=%u. DB=%d Vol=%d\n"),
559 jr.VolSessionTime, rec->VolSessionTime);
560 return true; /* ignore error */
562 if (jr.PoolId != pr.PoolId) {
563 Pmsg3(000, _("SOS_LABEL: PoolId mismatch for JobId=%u. DB=%d Vol=%d\n"),
565 jr.PoolId, pr.PoolId);
566 return true; /* ignore error */
571 unser_session_label(&elabel, rec);
573 /* Create FileSet record */
574 bstrncpy(fsr.FileSet, label.FileSetName, sizeof(fsr.FileSet));
575 bstrncpy(fsr.MD5, label.FileSetMD5, sizeof(fsr.MD5));
576 create_fileset_record(db, &fsr);
577 jr.FileSetId = fsr.FileSetId;
579 mjcr = get_jcr_by_session(rec->VolSessionId, rec->VolSessionTime);
581 Pmsg2(000, _("Could not find SessId=%d SessTime=%d for EOS record.\n"),
582 rec->VolSessionId, rec->VolSessionTime);
586 /* Do the final update to the Job record */
587 update_job_record(db, &jr, &elabel, rec);
589 mjcr->end_time = jr.EndTime;
590 mjcr->JobStatus = JS_Terminated;
592 /* Create JobMedia record */
593 mjcr->read_dcr->VolLastIndex = dcr->VolLastIndex;
594 create_jobmedia_record(db, mjcr);
595 detach_dcr_from_dev(mjcr->read_dcr);
603 case EOT_LABEL: /* end of all tapes */
605 * Wiffle through all jobs still open and close
610 foreach_dlist(mdcr, dev->attached_dcrs) {
611 JCR *mjcr = mdcr->jcr;
612 if (!mjcr || mjcr->JobId == 0) {
615 jr.JobId = mjcr->JobId;
616 /* Mark Job as Error Terimined */
617 jr.JobStatus = JS_ErrorTerminated;
618 jr.JobFiles = mjcr->JobFiles;
619 jr.JobBytes = mjcr->JobBytes;
620 jr.VolSessionId = mjcr->VolSessionId;
621 jr.VolSessionTime = mjcr->VolSessionTime;
622 jr.JobTDate = (utime_t)mjcr->start_time;
623 jr.ClientId = mjcr->ClientId;
624 if (!db_update_job_end_record(bjcr, db, &jr)) {
625 Pmsg1(0, _("Could not update job record. ERR=%s\n"), db_strerror(db));
627 mjcr->read_dcr = NULL;
631 mr.VolFiles = rec->File;
632 mr.VolBlocks = rec->Block;
633 mr.VolBytes += mr.VolBlocks * WRITE_BLKHDR_LENGTH; /* approx. */
635 update_media_record(db, &mr);
636 Pmsg3(0, _("End of all Volumes. VolFiles=%u VolBlocks=%u VolBytes=%s\n"), mr.VolFiles,
637 mr.VolBlocks, edit_uint64_with_commas(mr.VolBytes, ec1));
645 mjcr = get_jcr_by_session(rec->VolSessionId, rec->VolSessionTime);
647 if (mr.VolJobs > 0) {
648 Pmsg2(000, _("Could not find Job for SessId=%d SessTime=%d record.\n"),
649 rec->VolSessionId, rec->VolSessionTime);
655 dcr = mjcr->read_dcr;
656 if (dcr->VolFirstIndex == 0) {
657 dcr->VolFirstIndex = block->FirstIndex;
660 /* File Attributes stream */
661 switch (rec->Stream) {
662 case STREAM_UNIX_ATTRIBUTES:
663 case STREAM_UNIX_ATTRIBUTES_EX:
665 if (!unpack_attributes_record(bjcr, rec->Stream, rec->data, attr)) {
666 Emsg0(M_ERROR_TERM, 0, _("Cannot continue.\n"));
669 if (attr->file_index != rec->FileIndex) {
670 Emsg2(M_ERROR_TERM, 0, _("Record header file index %ld not equal record index %ld\n"),
671 rec->FileIndex, attr->file_index);
675 decode_stat(attr->attr, &attr->statp, &attr->LinkFI);
676 build_attr_output_fnames(bjcr, attr);
677 print_ls_output(bjcr, attr);
679 fr.JobId = mjcr->JobId;
682 if (verbose && (num_files & 0x7FFF) == 0) {
683 char ed1[30], ed2[30], ed3[30], ed4[30];
684 Pmsg4(000, _("%s file records. At file:blk=%s:%s bytes=%s\n"),
685 edit_uint64_with_commas(num_files, ed1),
686 edit_uint64_with_commas(rec->File, ed2),
687 edit_uint64_with_commas(rec->Block, ed3),
688 edit_uint64_with_commas(mr.VolBytes, ed4));
690 create_file_attributes_record(db, mjcr, attr->fname, attr->lname,
691 attr->type, attr->attr, rec);
696 case STREAM_WIN32_DATA:
697 case STREAM_FILE_DATA:
698 case STREAM_SPARSE_DATA:
699 case STREAM_ENCRYPTED_FILE_DATA:
700 case STREAM_ENCRYPTED_WIN32_DATA:
701 case STREAM_ENCRYPTED_MACOS_FORK_DATA:
703 * For encrypted stream, this is an approximation.
704 * The data must be decrypted to know the correct length.
706 mjcr->JobBytes += rec->data_len;
707 if (rec->Stream == STREAM_SPARSE_DATA) {
708 mjcr->JobBytes -= sizeof(uint64_t);
711 free_jcr(mjcr); /* done using JCR */
714 case STREAM_GZIP_DATA:
715 case STREAM_ENCRYPTED_FILE_GZIP_DATA:
716 case STREAM_ENCRYPTED_WIN32_GZIP_DATA:
717 /* No correct, we should (decrypt and) expand it
720 mjcr->JobBytes += rec->data_len;
724 case STREAM_SPARSE_GZIP_DATA:
725 mjcr->JobBytes += rec->data_len - sizeof(uint64_t); /* No correct, we should expand it */
726 free_jcr(mjcr); /* done using JCR */
729 /* Win32 GZIP stream */
730 case STREAM_WIN32_GZIP_DATA:
731 mjcr->JobBytes += rec->data_len;
732 free_jcr(mjcr); /* done using JCR */
735 case STREAM_MD5_DIGEST:
736 bin_to_base64(digest, sizeof(digest), (char *)rec->data, CRYPTO_DIGEST_MD5_SIZE, true);
738 Pmsg1(000, _("Got MD5 record: %s\n"), digest);
740 update_digest_record(db, digest, rec, CRYPTO_DIGEST_MD5);
743 case STREAM_SHA1_DIGEST:
744 bin_to_base64(digest, sizeof(digest), (char *)rec->data, CRYPTO_DIGEST_SHA1_SIZE, true);
746 Pmsg1(000, _("Got SHA1 record: %s\n"), digest);
748 update_digest_record(db, digest, rec, CRYPTO_DIGEST_SHA1);
751 case STREAM_SHA256_DIGEST:
752 bin_to_base64(digest, sizeof(digest), (char *)rec->data, CRYPTO_DIGEST_SHA256_SIZE, true);
754 Pmsg1(000, _("Got SHA256 record: %s\n"), digest);
756 update_digest_record(db, digest, rec, CRYPTO_DIGEST_SHA256);
759 case STREAM_SHA512_DIGEST:
760 bin_to_base64(digest, sizeof(digest), (char *)rec->data, CRYPTO_DIGEST_SHA512_SIZE, true);
762 Pmsg1(000, _("Got SHA512 record: %s\n"), digest);
764 update_digest_record(db, digest, rec, CRYPTO_DIGEST_SHA512);
767 case STREAM_ENCRYPTED_SESSION_DATA:
768 // TODO landonf: Investigate crypto support in bscan
770 Pmsg0(000, _("Got signed digest record\n"));
774 case STREAM_SIGNED_DIGEST:
775 // TODO landonf: Investigate crypto support in bscan
777 Pmsg0(000, _("Got signed digest record\n"));
781 case STREAM_PROGRAM_NAMES:
783 Pmsg1(000, _("Got Prog Names Stream: %s\n"), rec->data);
787 case STREAM_PROGRAM_DATA:
789 Pmsg0(000, _("Got Prog Data Stream record.\n"));
793 case STREAM_UNIX_ACCESS_ACL: /* Standard ACL attributes on UNIX */
794 case STREAM_UNIX_DEFAULT_ACL: /* Default ACL attributes on UNIX */
795 /* Ignore Unix attributes */
799 Pmsg2(0, _("Unknown stream type!!! stream=%d len=%i\n"), rec->Stream, rec->data_len);
806 * Free the Job Control Record if no one is still using it.
807 * Called from main free_jcr() routine in src/lib/jcr.c so
808 * that we can do our Director specific cleanup of the jcr.
810 static void bscan_free_jcr(JCR *jcr)
812 Dmsg0(200, "Start bscan free_jcr\n");
814 if (jcr->file_bsock) {
815 Dmsg0(200, "Close File bsock\n");
816 bnet_close(jcr->file_bsock);
818 if (jcr->store_bsock) {
819 Dmsg0(200, "Close Store bsock\n");
820 bnet_close(jcr->store_bsock);
822 if (jcr->RestoreBootstrap) {
823 free(jcr->RestoreBootstrap);
830 free_dcr(jcr->read_dcr);
831 jcr->read_dcr = NULL;
833 Dmsg0(200, "End bscan free_jcr\n");
837 * We got a File Attributes record on the tape. Now, lookup the Job
838 * record, and then create the attributes record.
840 static int create_file_attributes_record(B_DB *db, JCR *mjcr,
841 char *fname, char *lname, int type,
842 char *ap, DEV_RECORD *rec)
844 DCR *dcr = mjcr->read_dcr;
847 ar.ClientId = mjcr->ClientId;
848 ar.JobId = mjcr->JobId;
849 ar.Stream = rec->Stream;
850 ar.FileIndex = rec->FileIndex;
852 if (dcr->VolFirstIndex == 0) {
853 dcr->VolFirstIndex = rec->FileIndex;
855 dcr->FileIndex = rec->FileIndex;
862 if (!db_create_file_attributes_record(bjcr, db, &ar)) {
863 Pmsg1(0, _("Could not create File Attributes record. ERR=%s\n"), db_strerror(db));
866 mjcr->FileId = ar.FileId;
869 Pmsg1(000, _("Created File record: %s\n"), fname);
875 * For each Volume we see, we create a Medium record
877 static int create_media_record(B_DB *db, MEDIA_DBR *mr, VOLUME_LABEL *vl)
882 /* We mark Vols as Archive to keep them from being re-written */
883 bstrncpy(mr->VolStatus, "Archive", sizeof(mr->VolStatus));
884 mr->VolRetention = 365 * 3600 * 24; /* 1 year */
886 if (vl->VerNum >= 11) {
887 mr->FirstWritten = btime_to_utime(vl->write_btime);
888 mr->LabelDate = btime_to_utime(vl->label_btime);
890 /* DEPRECATED DO NOT USE */
891 dt.julian_day_number = vl->write_date;
892 dt.julian_day_fraction = vl->write_time;
894 mr->FirstWritten = mktime(&tm);
895 dt.julian_day_number = vl->label_date;
896 dt.julian_day_fraction = vl->label_time;
898 mr->LabelDate = mktime(&tm);
900 lasttime = mr->LabelDate;
901 if (mr->VolJobs == 0) {
904 if (mr->VolMounts == 0) {
912 if (!db_create_media_record(bjcr, db, mr)) {
913 Pmsg1(0, _("Could not create media record. ERR=%s\n"), db_strerror(db));
916 if (!db_update_media_record(bjcr, db, mr)) {
917 Pmsg1(0, _("Could not update media record. ERR=%s\n"), db_strerror(db));
921 Pmsg1(000, _("Created Media record for Volume: %s\n"), mr->VolumeName);
928 * Called at end of media to update it
930 static bool update_media_record(B_DB *db, MEDIA_DBR *mr)
932 if (!update_db && !update_vol_info) {
936 mr->LastWritten = lasttime;
937 if (!db_update_media_record(bjcr, db, mr)) {
938 Pmsg1(0, _("Could not update media record. ERR=%s\n"), db_strerror(db));
942 Pmsg1(000, _("Updated Media record at end of Volume: %s\n"), mr->VolumeName);
949 static int create_pool_record(B_DB *db, POOL_DBR *pr)
953 pr->VolRetention = 355 * 3600 * 24; /* 1 year */
958 if (!db_create_pool_record(bjcr, db, pr)) {
959 Pmsg1(0, _("Could not create pool record. ERR=%s\n"), db_strerror(db));
963 Pmsg1(000, _("Created Pool record for Pool: %s\n"), pr->Name);
971 * Called from SOS to create a client for the current Job
973 static int create_client_record(B_DB *db, CLIENT_DBR *cr)
976 * Note, update_db can temporarily be set false while
977 * updating the database, so we must ensure that ClientId is non-zero.
981 if (!db_get_client_record(bjcr, db, cr)) {
982 Pmsg1(0, _("Could not get Client record. ERR=%s\n"), db_strerror(db));
987 if (!db_create_client_record(bjcr, db, cr)) {
988 Pmsg1(0, _("Could not create Client record. ERR=%s\n"), db_strerror(db));
992 Pmsg1(000, _("Created Client record for Client: %s\n"), cr->Name);
997 static int create_fileset_record(B_DB *db, FILESET_DBR *fsr)
1003 if (fsr->MD5[0] == 0) {
1004 fsr->MD5[0] = ' '; /* Equivalent to nothing */
1007 if (db_get_fileset_record(bjcr, db, fsr)) {
1009 Pmsg1(000, _("Fileset \"%s\" already exists.\n"), fsr->FileSet);
1012 if (!db_create_fileset_record(bjcr, db, fsr)) {
1013 Pmsg2(0, _("Could not create FileSet record \"%s\". ERR=%s\n"),
1014 fsr->FileSet, db_strerror(db));
1018 Pmsg1(000, _("Created FileSet record \"%s\"\n"), fsr->FileSet);
1025 * Simulate the two calls on the database to create
1026 * the Job record and to update it when the Job actually
1029 static JCR *create_job_record(B_DB *db, JOB_DBR *jr, SESSION_LABEL *label,
1033 struct date_time dt;
1036 jr->JobId = label->JobId;
1037 jr->JobType = label->JobType;
1038 jr->JobLevel = label->JobLevel;
1039 jr->JobStatus = JS_Created;
1040 bstrncpy(jr->Name, label->JobName, sizeof(jr->Name));
1041 bstrncpy(jr->Job, label->Job, sizeof(jr->Job));
1042 if (label->VerNum >= 11) {
1043 jr->SchedTime = btime_to_unix(label->write_btime);
1045 dt.julian_day_number = label->write_date;
1046 dt.julian_day_fraction = label->write_time;
1047 tm_decode(&dt, &tm);
1048 jr->SchedTime = mktime(&tm);
1051 jr->StartTime = jr->SchedTime;
1052 jr->JobTDate = (utime_t)jr->SchedTime;
1053 jr->VolSessionId = rec->VolSessionId;
1054 jr->VolSessionTime = rec->VolSessionTime;
1056 /* Now create a JCR as if starting the Job */
1057 mjcr = create_jcr(jr, rec, label->JobId);
1063 /* This creates the bare essentials */
1064 if (!db_create_job_record(bjcr, db, jr)) {
1065 Pmsg1(0, _("Could not create JobId record. ERR=%s\n"), db_strerror(db));
1069 /* This adds the client, StartTime, JobTDate, ... */
1070 if (!db_update_job_start_record(bjcr, db, jr)) {
1071 Pmsg1(0, _("Could not update job start record. ERR=%s\n"), db_strerror(db));
1074 Pmsg2(000, _("Created new JobId=%u record for original JobId=%u\n"), jr->JobId,
1076 mjcr->JobId = jr->JobId; /* set new JobId */
1081 * Simulate the database call that updates the Job
1082 * at Job termination time.
1084 static int update_job_record(B_DB *db, JOB_DBR *jr, SESSION_LABEL *elabel,
1087 struct date_time dt;
1091 mjcr = get_jcr_by_session(rec->VolSessionId, rec->VolSessionTime);
1093 Pmsg2(000, _("Could not find SessId=%d SessTime=%d for EOS record.\n"),
1094 rec->VolSessionId, rec->VolSessionTime);
1097 if (elabel->VerNum >= 11) {
1098 jr->EndTime = btime_to_unix(elabel->write_btime);
1100 dt.julian_day_number = elabel->write_date;
1101 dt.julian_day_fraction = elabel->write_time;
1102 tm_decode(&dt, &tm);
1103 jr->EndTime = mktime(&tm);
1105 lasttime = jr->EndTime;
1106 mjcr->end_time = jr->EndTime;
1108 jr->JobId = mjcr->JobId;
1109 jr->JobStatus = elabel->JobStatus;
1110 mjcr->JobStatus = elabel->JobStatus;
1111 jr->JobFiles = elabel->JobFiles;
1112 jr->JobBytes = elabel->JobBytes;
1113 jr->VolSessionId = rec->VolSessionId;
1114 jr->VolSessionTime = rec->VolSessionTime;
1115 jr->JobTDate = (utime_t)mjcr->start_time;
1116 jr->ClientId = mjcr->ClientId;
1123 if (!db_update_job_end_record(bjcr, db, jr)) {
1124 Pmsg2(0, _("Could not update JobId=%u record. ERR=%s\n"), jr->JobId, db_strerror(db));
1129 Pmsg3(000, _("Updated Job termination record for JobId=%u Level=%s TermStat=%c\n"),
1130 jr->JobId, job_level_to_str(mjcr->JobLevel), jr->JobStatus);
1133 const char *term_msg;
1134 static char term_code[70];
1135 char sdt[50], edt[50];
1136 char ec1[30], ec2[30], ec3[30];
1138 switch (mjcr->JobStatus) {
1140 term_msg = _("Backup OK");
1143 case JS_ErrorTerminated:
1144 term_msg = _("*** Backup Error ***");
1147 term_msg = _("Backup Canceled");
1150 term_msg = term_code;
1151 sprintf(term_code, _("Job Termination code: %d"), mjcr->JobStatus);
1154 bstrftime(sdt, sizeof(sdt), mjcr->start_time);
1155 bstrftime(edt, sizeof(edt), mjcr->end_time);
1156 Pmsg14(000, _("%s\n"
1160 "Backup Level: %s\n"
1164 "Files Written: %s\n"
1165 "Bytes Written: %s\n"
1166 "Volume Session Id: %d\n"
1167 "Volume Session Time: %d\n"
1168 "Last Volume Bytes: %s\n"
1169 "Termination: %s\n\n"),
1174 job_level_to_str(mjcr->JobLevel),
1178 edit_uint64_with_commas(mjcr->JobFiles, ec1),
1179 edit_uint64_with_commas(mjcr->JobBytes, ec2),
1181 mjcr->VolSessionTime,
1182 edit_uint64_with_commas(mr.VolBytes, ec3),
1189 static int create_jobmedia_record(B_DB *db, JCR *mjcr)
1192 DCR *dcr = mjcr->read_dcr;
1194 dcr->EndBlock = dev->EndBlock;
1195 dcr->EndFile = dev->EndFile;
1196 dcr->VolMediaId = dev->VolCatInfo.VolMediaId;
1198 memset(&jmr, 0, sizeof(jmr));
1199 jmr.JobId = mjcr->JobId;
1200 jmr.MediaId = mr.MediaId;
1201 jmr.FirstIndex = dcr->VolFirstIndex;
1202 jmr.LastIndex = dcr->VolLastIndex;
1203 jmr.StartFile = dcr->StartFile;
1204 jmr.EndFile = dcr->EndFile;
1205 jmr.StartBlock = dcr->StartBlock;
1206 jmr.EndBlock = dcr->EndBlock;
1213 if (!db_create_jobmedia_record(bjcr, db, &jmr)) {
1214 Pmsg1(0, _("Could not create JobMedia record. ERR=%s\n"), db_strerror(db));
1218 Pmsg2(000, _("Created JobMedia record JobId %d, MediaId %d\n"),
1219 jmr.JobId, jmr.MediaId);
1225 * Simulate the database call that updates the MD5/SHA1 record
1227 static int update_digest_record(B_DB *db, char *digest, DEV_RECORD *rec, int type)
1231 mjcr = get_jcr_by_session(rec->VolSessionId, rec->VolSessionTime);
1233 if (mr.VolJobs > 0) {
1234 Pmsg2(000, _("Could not find SessId=%d SessTime=%d for MD5/SHA1 record.\n"),
1235 rec->VolSessionId, rec->VolSessionTime);
1242 if (!update_db || mjcr->FileId == 0) {
1247 if (!db_add_digest_to_file_record(bjcr, db, mjcr->FileId, digest, type)) {
1248 Pmsg1(0, _("Could not add MD5/SHA1 to File record. ERR=%s\n"), db_strerror(db));
1253 Pmsg0(000, _("Updated MD5/SHA1 record\n"));
1261 * Create a JCR as if we are really starting the job
1263 static JCR *create_jcr(JOB_DBR *jr, DEV_RECORD *rec, uint32_t JobId)
1267 * Transfer as much as possible to the Job JCR. Most important is
1268 * the JobId and the ClientId.
1270 jobjcr = new_jcr(sizeof(JCR), bscan_free_jcr);
1271 jobjcr->JobType = jr->JobType;
1272 jobjcr->JobLevel = jr->JobLevel;
1273 jobjcr->JobStatus = jr->JobStatus;
1274 bstrncpy(jobjcr->Job, jr->Job, sizeof(jobjcr->Job));
1275 jobjcr->JobId = JobId; /* this is JobId on tape */
1276 jobjcr->sched_time = jr->SchedTime;
1277 jobjcr->start_time = jr->StartTime;
1278 jobjcr->VolSessionId = rec->VolSessionId;
1279 jobjcr->VolSessionTime = rec->VolSessionTime;
1280 jobjcr->ClientId = jr->ClientId;
1281 jobjcr->dcr = jobjcr->read_dcr = new_dcr(jobjcr, NULL, dev);
1286 /* Dummies to replace askdir.c */
1287 bool dir_find_next_appendable_volume(DCR *dcr) { return 1;}
1288 bool dir_update_volume_info(DCR *dcr, bool relabel, bool update_LastWritten) { return 1; }
1289 bool dir_create_jobmedia_record(DCR *dcr) { return 1; }
1290 bool dir_ask_sysop_to_create_appendable_volume(DCR *dcr) { return 1; }
1291 bool dir_update_file_attributes(DCR *dcr, DEV_RECORD *rec) { return 1;}
1292 bool dir_send_job_status(JCR *jcr) {return 1;}
1293 int generate_job_event(JCR *jcr, const char *event) { return 1; }
1295 bool dir_ask_sysop_to_mount_volume(DCR *dcr, int /*mode*/)
1297 DEVICE *dev = dcr->dev;
1298 Dmsg0(20, "Enter dir_ask_sysop_to_mount_volume\n");
1299 /* Close device so user can use autochanger if desired */
1300 fprintf(stderr, _("Mount Volume \"%s\" on device %s and press return when ready: "),
1301 dcr->VolumeName, dev->print_name());
1307 bool dir_get_volume_info(DCR *dcr, enum get_vol_info_rw writing)
1309 Dmsg0(100, "Fake dir_get_volume_info\n");
1310 bstrncpy(dcr->VolCatInfo.VolCatName, dcr->VolumeName, sizeof(dcr->VolCatInfo.VolCatName));
1311 dcr->VolCatInfo.VolCatParts = find_num_dvd_parts(dcr);
1312 Dmsg2(500, "Vol=%s num_parts=%d\n", dcr->VolCatInfo.VolCatName, dcr->VolCatInfo.VolCatParts);