2 Bacula® - The Network Backup Solution
4 Copyright (C) 2001-2010 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 three of the GNU Affero 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 Affero 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 Kern Sibbald.
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
40 #include "findlib/find.h"
41 #include "cats/cats.h"
44 int generate_daemon_event(JCR *jcr, const char *event) { return 1; }
45 extern bool parse_sd_config(CONFIG *config, const char *configfile, int exit_code);
47 /* Forward referenced functions */
48 static void do_scan(void);
49 static bool record_cb(DCR *dcr, DEV_RECORD *rec);
50 static int create_file_attributes_record(B_DB *db, JCR *mjcr,
51 char *fname, char *lname, int type,
52 char *ap, DEV_RECORD *rec);
53 static int create_media_record(B_DB *db, MEDIA_DBR *mr, VOLUME_LABEL *vl);
54 static bool update_media_record(B_DB *db, MEDIA_DBR *mr);
55 static int create_pool_record(B_DB *db, POOL_DBR *pr);
56 static JCR *create_job_record(B_DB *db, JOB_DBR *mr, SESSION_LABEL *label, DEV_RECORD *rec);
57 static int update_job_record(B_DB *db, JOB_DBR *mr, SESSION_LABEL *elabel,
59 static int create_client_record(B_DB *db, CLIENT_DBR *cr);
60 static int create_fileset_record(B_DB *db, FILESET_DBR *fsr);
61 static int create_jobmedia_record(B_DB *db, JCR *jcr);
62 static JCR *create_jcr(JOB_DBR *jr, DEV_RECORD *rec, uint32_t JobId);
63 static int update_digest_record(B_DB *db, char *digest, DEV_RECORD *rec, int type);
67 static DEVICE *dev = NULL;
69 static JCR *bjcr; /* jcr for bscan */
70 static BSR *bsr = NULL;
75 static FILESET_DBR fsr;
78 static SESSION_LABEL label;
79 static SESSION_LABEL elabel;
82 static time_t lasttime = 0;
84 static const char *db_driver = "NULL";
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 int db_port = 0;
90 static const char *wd = NULL;
91 static bool update_db = false;
92 static bool update_vol_info = false;
93 static bool list_records = false;
94 static int ignored_msgs = 0;
96 static uint64_t currentVolumeSize;
97 static int last_pct = -1;
98 static bool showProgress = false;
99 static int num_jobs = 0;
100 static int num_pools = 0;
101 static int num_media = 0;
102 static int num_files = 0;
104 static CONFIG *config;
105 #define CONFIG_FILE "bacula-sd.conf"
106 char *configfile = NULL;
107 STORES *me = NULL; /* our Global resource */
108 bool forge_on = false; /* proceed inspite of I/O errors */
109 pthread_mutex_t device_release_mutex = PTHREAD_MUTEX_INITIALIZER;
110 pthread_cond_t wait_device_release = PTHREAD_COND_INITIALIZER;
117 "\nVersion: %s (%s)\n\n"
118 "Usage: bscan [ options ] <bacula-archive>\n"
119 " -b bootstrap specify a bootstrap file\n"
120 " -c <file> specify configuration file\n"
121 " -d <nn> set debug level to <nn>\n"
122 " -dt print timestamp in debug output\n"
123 " -m update media info in database\n"
124 " -D <driver name> specify the driver database name (default NULL)\n"
125 " -n <name> specify the database name (default bacula)\n"
126 " -u <user> specify database user name (default bacula)\n"
127 " -P <password> specify database password (default none)\n"
128 " -h <host> specify database host (default NULL)\n"
129 " -t <port> specify database port (default 0)\n"
130 " -p proceed inspite of I/O errors\n"
132 " -s synchronize or store in database\n"
133 " -S show scan progress periodically\n"
135 " -V <Volumes> specify Volume names (separated by |)\n"
136 " -w <dir> specify working directory (default from conf file)\n"
137 " -? print this message\n\n"), 2001, VERSION, BDATE);
141 int main (int argc, char *argv[])
144 struct stat stat_buf;
145 char *VolumeName = NULL;
147 setlocale(LC_ALL, "");
148 bindtextdomain("bacula", LOCALEDIR);
149 textdomain("bacula");
153 my_name_is(argc, argv, "bscan");
154 init_msg(NULL, NULL);
158 while ((ch = getopt(argc, argv, "b:c:d:D:h:p:mn:pP:rsSt:u:vV:w:?")) != -1) {
164 bsr = parse_bsr(NULL, optarg);
167 case 'c': /* specify config file */
168 if (configfile != NULL) {
171 configfile = bstrdup(optarg);
178 case 'd': /* debug level */
179 if (*optarg == 't') {
180 dbg_timestamp = true;
182 debug_level = atoi(optarg);
183 if (debug_level <= 0) {
194 db_port = atoi(optarg);
198 update_vol_info = true;
210 db_password = optarg;
229 case 'V': /* Volume name */
247 Pmsg0(0, _("Wrong number of arguments: \n"));
251 if (configfile == NULL) {
252 configfile = bstrdup(CONFIG_FILE);
255 config = new_config_parser();
256 parse_sd_config(config, configfile, M_ERROR_TERM);
258 me = (STORES *)GetNextRes(R_STORAGE, NULL);
261 Emsg1(M_ERROR_TERM, 0, _("No Storage resource defined in %s. Cannot continue.\n"),
265 /* Check if -w option given, otherwise use resource for working directory */
267 working_directory = wd;
268 } else if (!me->working_directory) {
269 Emsg1(M_ERROR_TERM, 0, _("No Working Directory defined in %s. Cannot continue.\n"),
272 working_directory = me->working_directory;
275 /* Check that working directory is good */
276 if (stat(working_directory, &stat_buf) != 0) {
277 Emsg1(M_ERROR_TERM, 0, _("Working Directory: %s not found. Cannot continue.\n"),
280 if (!S_ISDIR(stat_buf.st_mode)) {
281 Emsg1(M_ERROR_TERM, 0, _("Working Directory: %s is not a directory. Cannot continue.\n"),
285 bjcr = setup_jcr("bscan", argv[0], bsr, VolumeName, 1); /* read device */
289 dev = bjcr->read_dcr->dev;
293 fstat(dev->fd(), &sb);
294 currentVolumeSize = sb.st_size;
295 Pmsg1(000, _("First Volume Size = %s\n"),
296 edit_uint64(currentVolumeSize, ed1));
299 if ((db=db_init(NULL, db_driver, db_name, db_user, db_password,
300 db_host, db_port, NULL, 0)) == NULL) {
301 Emsg0(M_ERROR_TERM, 0, _("Could not init Bacula database\n"));
303 if (!db_open_database(NULL, db)) {
304 Emsg0(M_ERROR_TERM, 0, db_strerror(db));
306 Dmsg0(200, "Database opened\n");
308 Pmsg2(000, _("Using Database: %s, User: %s\n"), db_name, db_user);
313 printf("Records added or updated in the catalog:\n%7d Media\n%7d Pool\n%7d Job\n%7d File\n",
314 num_media, num_pools, num_jobs, num_files);
316 printf("Records would have been added or updated in the catalog:\n%7d Media\n%7d Pool\n%7d Job\n%7d File\n",
317 num_media, num_pools, num_jobs, num_files);
326 * We are at the end of reading a tape. Now, we simulate handling
327 * the end of writing a tape by wiffling through the attached
328 * jcrs creating jobmedia records.
330 static bool bscan_mount_next_read_volume(DCR *dcr)
332 DEVICE *dev = dcr->dev;
334 Dmsg1(100, "Walk attached jcrs. Volume=%s\n", dev->getVolCatName());
335 foreach_dlist(mdcr, dev->attached_dcrs) {
336 JCR *mjcr = mdcr->jcr;
337 Dmsg1(000, "========== JobId=%u ========\n", mjcr->JobId);
338 if (mjcr->JobId == 0) {
342 Pmsg1(000, _("Create JobMedia for Job %s\n"), mjcr->Job);
344 mdcr->StartBlock = dcr->StartBlock;
345 mdcr->StartFile = dcr->StartFile;
346 mdcr->EndBlock = dcr->EndBlock;
347 mdcr->EndFile = dcr->EndFile;
348 mdcr->VolMediaId = dcr->VolMediaId;
349 mjcr->read_dcr->VolLastIndex = dcr->VolLastIndex;
350 if (!create_jobmedia_record(db, mjcr)) {
351 Pmsg2(000, _("Could not create JobMedia record for Volume=%s Job=%s\n"),
352 dev->getVolCatName(), mjcr->Job);
356 update_media_record(db, &mr);
358 /* Now let common read routine get up next tape. Note,
359 * we call mount_next... with bscan's jcr because that is where we
360 * have the Volume list, but we get attached.
362 bool stat = mount_next_read_volume(dcr);
367 fstat(dev->fd(), &sb);
368 currentVolumeSize = sb.st_size;
369 Pmsg1(000, _("First Volume Size = %s\n"),
370 edit_uint64(currentVolumeSize, ed1));
375 static void do_scan()
377 attr = new_attr(bjcr);
379 memset(&ar, 0, sizeof(ar));
380 memset(&pr, 0, sizeof(pr));
381 memset(&jr, 0, sizeof(jr));
382 memset(&cr, 0, sizeof(cr));
383 memset(&fsr, 0, sizeof(fsr));
384 memset(&fr, 0, sizeof(fr));
386 /* Detach bscan's jcr as we are not a real Job on the tape */
388 read_records(bjcr->read_dcr, record_cb, bscan_mount_next_read_volume);
391 db_write_batch_file_records(bjcr); /* used by bulk batch file insert */
397 * Returns: true if OK
400 static bool record_cb(DCR *dcr, DEV_RECORD *rec)
404 DEVICE *dev = dcr->dev;
405 JCR *bjcr = dcr->jcr;
406 DEV_BLOCK *block = dcr->block;
407 char digest[BASE64_SIZE(CRYPTO_DIGEST_MAX_SIZE)];
409 if (rec->data_len > 0) {
410 mr.VolBytes += rec->data_len + WRITE_RECHDR_LENGTH; /* Accumulate Volume bytes */
411 if (showProgress && currentVolumeSize > 0) {
412 int pct = (mr.VolBytes * 100) / currentVolumeSize;
413 if (pct != last_pct) {
414 fprintf(stdout, _("done: %d%%\n"), pct);
422 Pmsg5(000, _("Record: SessId=%u SessTim=%u FileIndex=%d Stream=%d len=%u\n"),
423 rec->VolSessionId, rec->VolSessionTime, rec->FileIndex,
424 rec->Stream, rec->data_len);
427 * Check for Start or End of Session Record
430 if (rec->FileIndex < 0) {
431 bool save_update_db = update_db;
434 dump_label_record(dev, rec, 1);
436 switch (rec->FileIndex) {
438 Pmsg0(000, _("Volume is prelabeled. This tape cannot be scanned.\n"));
443 unser_volume_label(dev, rec);
444 /* Check Pool info */
445 bstrncpy(pr.Name, dev->VolHdr.PoolName, sizeof(pr.Name));
446 bstrncpy(pr.PoolType, dev->VolHdr.PoolType, sizeof(pr.PoolType));
448 if (db_get_pool_record(bjcr, db, &pr)) {
450 Pmsg1(000, _("Pool record for %s found in DB.\n"), pr.Name);
454 Pmsg1(000, _("VOL_LABEL: Pool record not found for Pool: %s\n"),
457 create_pool_record(db, &pr);
459 if (strcmp(pr.PoolType, dev->VolHdr.PoolType) != 0) {
460 Pmsg2(000, _("VOL_LABEL: PoolType mismatch. DB=%s Vol=%s\n"),
461 pr.PoolType, dev->VolHdr.PoolType);
463 } else if (verbose) {
464 Pmsg1(000, _("Pool type \"%s\" is OK.\n"), pr.PoolType);
467 /* Check Media Info */
468 memset(&mr, 0, sizeof(mr));
469 bstrncpy(mr.VolumeName, dev->VolHdr.VolumeName, sizeof(mr.VolumeName));
470 mr.PoolId = pr.PoolId;
472 if (db_get_media_record(bjcr, db, &mr)) {
474 Pmsg1(000, _("Media record for %s found in DB.\n"), mr.VolumeName);
476 /* Clear out some volume statistics that will be updated */
477 mr.VolJobs = mr.VolFiles = mr.VolBlocks = 0;
478 mr.VolBytes = rec->data_len + 20;
481 Pmsg1(000, _("VOL_LABEL: Media record not found for Volume: %s\n"),
484 bstrncpy(mr.MediaType, dev->VolHdr.MediaType, sizeof(mr.MediaType));
485 create_media_record(db, &mr, &dev->VolHdr);
487 if (strcmp(mr.MediaType, dev->VolHdr.MediaType) != 0) {
488 Pmsg2(000, _("VOL_LABEL: MediaType mismatch. DB=%s Vol=%s\n"),
489 mr.MediaType, dev->VolHdr.MediaType);
490 return true; /* ignore error */
491 } else if (verbose) {
492 Pmsg1(000, _("Media type \"%s\" is OK.\n"), mr.MediaType);
494 /* Reset some DCR variables */
495 foreach_dlist(dcr, dev->attached_dcrs) {
496 dcr->VolFirstIndex = dcr->FileIndex = 0;
497 dcr->StartBlock = dcr->EndBlock = 0;
498 dcr->StartFile = dcr->EndFile = 0;
502 Pmsg1(000, _("VOL_LABEL: OK for Volume: %s\n"), mr.VolumeName);
508 if (ignored_msgs > 0) {
509 Pmsg1(000, _("%d \"errors\" ignored before first Start of Session record.\n"),
513 unser_session_label(&label, rec);
514 memset(&jr, 0, sizeof(jr));
515 bstrncpy(jr.Job, label.Job, sizeof(jr.Job));
516 if (db_get_job_record(bjcr, db, &jr)) {
517 /* Job record already exists in DB */
518 update_db = false; /* don't change db in create_job_record */
520 Pmsg1(000, _("SOS_LABEL: Found Job record for JobId: %d\n"), jr.JobId);
523 /* Must create a Job record in DB */
525 Pmsg1(000, _("SOS_LABEL: Job record not found for JobId: %d\n"),
529 /* Create Client record if not already there */
530 bstrncpy(cr.Name, label.ClientName, sizeof(cr.Name));
531 create_client_record(db, &cr);
532 jr.ClientId = cr.ClientId;
534 /* process label, if Job record exists don't update db */
535 mjcr = create_job_record(db, &jr, &label, rec);
536 dcr = mjcr->read_dcr;
537 update_db = save_update_db;
539 jr.PoolId = pr.PoolId;
540 mjcr->start_time = jr.StartTime;
541 mjcr->set_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 free_dcr(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->maskedStream) {
662 case STREAM_UNIX_ATTRIBUTES:
663 case STREAM_UNIX_ATTRIBUTES_EX:
665 if (!unpack_attributes_record(bjcr, rec->Stream, rec->data, rec->data_len, attr)) {
666 Emsg0(M_ERROR_TERM, 0, _("Cannot continue.\n"));
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);
690 case STREAM_RESTORE_OBJECT:
692 /* Implement putting into catalog */
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->maskedStream == 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: /* Deprecated Standard ACL attributes on UNIX */
794 case STREAM_UNIX_DEFAULT_ACL: /* Deprecated Default ACL attributes on UNIX */
795 case STREAM_ACL_AIX_TEXT:
796 case STREAM_ACL_DARWIN_ACCESS_ACL:
797 case STREAM_ACL_FREEBSD_DEFAULT_ACL:
798 case STREAM_ACL_FREEBSD_ACCESS_ACL:
799 case STREAM_ACL_HPUX_ACL_ENTRY:
800 case STREAM_ACL_IRIX_DEFAULT_ACL:
801 case STREAM_ACL_IRIX_ACCESS_ACL:
802 case STREAM_ACL_LINUX_DEFAULT_ACL:
803 case STREAM_ACL_LINUX_ACCESS_ACL:
804 case STREAM_ACL_TRU64_DEFAULT_ACL:
805 case STREAM_ACL_TRU64_DEFAULT_DIR_ACL:
806 case STREAM_ACL_TRU64_ACCESS_ACL:
807 case STREAM_ACL_SOLARIS_ACLENT:
808 case STREAM_ACL_SOLARIS_ACE:
809 case STREAM_ACL_AFS_TEXT:
810 case STREAM_ACL_AIX_AIXC:
811 case STREAM_ACL_AIX_NFS4:
812 /* Ignore Unix ACL attributes */
815 case STREAM_XATTR_TRU64:
816 case STREAM_XATTR_AIX:
817 case STREAM_XATTR_OPENBSD:
818 case STREAM_XATTR_SOLARIS_SYS:
819 case STREAM_XATTR_SOLARIS:
820 case STREAM_XATTR_DARWIN:
821 case STREAM_XATTR_FREEBSD:
822 case STREAM_XATTR_LINUX:
823 case STREAM_XATTR_NETBSD:
824 /* Ignore Unix Extended attributes */
828 Pmsg2(0, _("Unknown stream type!!! stream=%d len=%i\n"), rec->Stream, rec->data_len);
835 * Free the Job Control Record if no one is still using it.
836 * Called from main free_jcr() routine in src/lib/jcr.c so
837 * that we can do our Director specific cleanup of the jcr.
839 static void bscan_free_jcr(JCR *jcr)
841 Dmsg0(200, "Start bscan free_jcr\n");
843 if (jcr->file_bsock) {
844 Dmsg0(200, "Close File bsock\n");
845 bnet_close(jcr->file_bsock);
847 if (jcr->store_bsock) {
848 Dmsg0(200, "Close Store bsock\n");
849 bnet_close(jcr->store_bsock);
851 if (jcr->RestoreBootstrap) {
852 free(jcr->RestoreBootstrap);
859 free_dcr(jcr->read_dcr);
860 jcr->read_dcr = NULL;
862 Dmsg0(200, "End bscan free_jcr\n");
866 * We got a File Attributes record on the tape. Now, lookup the Job
867 * record, and then create the attributes record.
869 static int create_file_attributes_record(B_DB *db, JCR *mjcr,
870 char *fname, char *lname, int type,
871 char *ap, DEV_RECORD *rec)
873 DCR *dcr = mjcr->read_dcr;
876 ar.ClientId = mjcr->ClientId;
877 ar.JobId = mjcr->JobId;
878 ar.Stream = rec->Stream;
879 if (type == FT_DELETED) {
882 ar.FileIndex = rec->FileIndex;
885 if (dcr->VolFirstIndex == 0) {
886 dcr->VolFirstIndex = rec->FileIndex;
888 dcr->FileIndex = rec->FileIndex;
895 if (!db_create_file_attributes_record(bjcr, db, &ar)) {
896 Pmsg1(0, _("Could not create File Attributes record. ERR=%s\n"), db_strerror(db));
899 mjcr->FileId = ar.FileId;
902 Pmsg1(000, _("Created File record: %s\n"), fname);
908 * For each Volume we see, we create a Medium record
910 static int create_media_record(B_DB *db, MEDIA_DBR *mr, VOLUME_LABEL *vl)
915 /* We mark Vols as Archive to keep them from being re-written */
916 bstrncpy(mr->VolStatus, "Archive", sizeof(mr->VolStatus));
917 mr->VolRetention = 365 * 3600 * 24; /* 1 year */
919 if (vl->VerNum >= 11) {
920 mr->set_first_written = true; /* Save FirstWritten during update_media */
921 mr->FirstWritten = btime_to_utime(vl->write_btime);
922 mr->LabelDate = btime_to_utime(vl->label_btime);
924 /* DEPRECATED DO NOT USE */
925 dt.julian_day_number = vl->write_date;
926 dt.julian_day_fraction = vl->write_time;
928 mr->FirstWritten = mktime(&tm);
929 dt.julian_day_number = vl->label_date;
930 dt.julian_day_fraction = vl->label_time;
932 mr->LabelDate = mktime(&tm);
934 lasttime = mr->LabelDate;
935 if (mr->VolJobs == 0) {
938 if (mr->VolMounts == 0) {
946 if (!db_create_media_record(bjcr, db, mr)) {
947 Pmsg1(0, _("Could not create media record. ERR=%s\n"), db_strerror(db));
950 if (!db_update_media_record(bjcr, db, mr)) {
951 Pmsg1(0, _("Could not update media record. ERR=%s\n"), db_strerror(db));
955 Pmsg1(000, _("Created Media record for Volume: %s\n"), mr->VolumeName);
962 * Called at end of media to update it
964 static bool update_media_record(B_DB *db, MEDIA_DBR *mr)
966 if (!update_db && !update_vol_info) {
970 mr->LastWritten = lasttime;
971 if (!db_update_media_record(bjcr, db, mr)) {
972 Pmsg1(0, _("Could not update media record. ERR=%s\n"), db_strerror(db));
976 Pmsg1(000, _("Updated Media record at end of Volume: %s\n"), mr->VolumeName);
983 static int create_pool_record(B_DB *db, POOL_DBR *pr)
987 pr->VolRetention = 355 * 3600 * 24; /* 1 year */
992 if (!db_create_pool_record(bjcr, db, pr)) {
993 Pmsg1(0, _("Could not create pool record. ERR=%s\n"), db_strerror(db));
997 Pmsg1(000, _("Created Pool record for Pool: %s\n"), pr->Name);
1005 * Called from SOS to create a client for the current Job
1007 static int create_client_record(B_DB *db, CLIENT_DBR *cr)
1010 * Note, update_db can temporarily be set false while
1011 * updating the database, so we must ensure that ClientId is non-zero.
1015 if (!db_get_client_record(bjcr, db, cr)) {
1016 Pmsg1(0, _("Could not get Client record. ERR=%s\n"), db_strerror(db));
1021 if (!db_create_client_record(bjcr, db, cr)) {
1022 Pmsg1(0, _("Could not create Client record. ERR=%s\n"), db_strerror(db));
1026 Pmsg1(000, _("Created Client record for Client: %s\n"), cr->Name);
1031 static int create_fileset_record(B_DB *db, FILESET_DBR *fsr)
1037 if (fsr->MD5[0] == 0) {
1038 fsr->MD5[0] = ' '; /* Equivalent to nothing */
1041 if (db_get_fileset_record(bjcr, db, fsr)) {
1043 Pmsg1(000, _("Fileset \"%s\" already exists.\n"), fsr->FileSet);
1046 if (!db_create_fileset_record(bjcr, db, fsr)) {
1047 Pmsg2(0, _("Could not create FileSet record \"%s\". ERR=%s\n"),
1048 fsr->FileSet, db_strerror(db));
1052 Pmsg1(000, _("Created FileSet record \"%s\"\n"), fsr->FileSet);
1059 * Simulate the two calls on the database to create
1060 * the Job record and to update it when the Job actually
1063 static JCR *create_job_record(B_DB *db, JOB_DBR *jr, SESSION_LABEL *label,
1067 struct date_time dt;
1070 jr->JobId = label->JobId;
1071 jr->JobType = label->JobType;
1072 jr->JobLevel = label->JobLevel;
1073 jr->JobStatus = JS_Created;
1074 bstrncpy(jr->Name, label->JobName, sizeof(jr->Name));
1075 bstrncpy(jr->Job, label->Job, sizeof(jr->Job));
1076 if (label->VerNum >= 11) {
1077 jr->SchedTime = btime_to_unix(label->write_btime);
1079 dt.julian_day_number = label->write_date;
1080 dt.julian_day_fraction = label->write_time;
1081 tm_decode(&dt, &tm);
1082 jr->SchedTime = mktime(&tm);
1085 jr->StartTime = jr->SchedTime;
1086 jr->JobTDate = (utime_t)jr->SchedTime;
1087 jr->VolSessionId = rec->VolSessionId;
1088 jr->VolSessionTime = rec->VolSessionTime;
1090 /* Now create a JCR as if starting the Job */
1091 mjcr = create_jcr(jr, rec, label->JobId);
1097 /* This creates the bare essentials */
1098 if (!db_create_job_record(bjcr, db, jr)) {
1099 Pmsg1(0, _("Could not create JobId record. ERR=%s\n"), db_strerror(db));
1103 /* This adds the client, StartTime, JobTDate, ... */
1104 if (!db_update_job_start_record(bjcr, db, jr)) {
1105 Pmsg1(0, _("Could not update job start record. ERR=%s\n"), db_strerror(db));
1108 Pmsg2(000, _("Created new JobId=%u record for original JobId=%u\n"), jr->JobId,
1110 mjcr->JobId = jr->JobId; /* set new JobId */
1115 * Simulate the database call that updates the Job
1116 * at Job termination time.
1118 static int update_job_record(B_DB *db, JOB_DBR *jr, SESSION_LABEL *elabel,
1121 struct date_time dt;
1125 mjcr = get_jcr_by_session(rec->VolSessionId, rec->VolSessionTime);
1127 Pmsg2(000, _("Could not find SessId=%d SessTime=%d for EOS record.\n"),
1128 rec->VolSessionId, rec->VolSessionTime);
1131 if (elabel->VerNum >= 11) {
1132 jr->EndTime = btime_to_unix(elabel->write_btime);
1134 dt.julian_day_number = elabel->write_date;
1135 dt.julian_day_fraction = elabel->write_time;
1136 tm_decode(&dt, &tm);
1137 jr->EndTime = mktime(&tm);
1139 lasttime = jr->EndTime;
1140 mjcr->end_time = jr->EndTime;
1142 jr->JobId = mjcr->JobId;
1143 jr->JobStatus = elabel->JobStatus;
1144 mjcr->JobStatus = elabel->JobStatus;
1145 jr->JobFiles = elabel->JobFiles;
1146 if (jr->JobFiles > 0) { /* If we found files, force PurgedFiles */
1147 jr->PurgedFiles = 0;
1149 jr->JobBytes = elabel->JobBytes;
1150 jr->VolSessionId = rec->VolSessionId;
1151 jr->VolSessionTime = rec->VolSessionTime;
1152 jr->JobTDate = (utime_t)mjcr->start_time;
1153 jr->ClientId = mjcr->ClientId;
1160 if (!db_update_job_end_record(bjcr, db, jr)) {
1161 Pmsg2(0, _("Could not update JobId=%u record. ERR=%s\n"), jr->JobId, db_strerror(db));
1166 Pmsg3(000, _("Updated Job termination record for JobId=%u Level=%s TermStat=%c\n"),
1167 jr->JobId, job_level_to_str(mjcr->getJobLevel()), jr->JobStatus);
1170 const char *term_msg;
1171 static char term_code[70];
1172 char sdt[50], edt[50];
1173 char ec1[30], ec2[30], ec3[30];
1175 switch (mjcr->JobStatus) {
1177 term_msg = _("Backup OK");
1180 term_msg = _("Backup OK -- with warnings");
1183 case JS_ErrorTerminated:
1184 term_msg = _("*** Backup Error ***");
1187 term_msg = _("Backup Canceled");
1190 term_msg = term_code;
1191 sprintf(term_code, _("Job Termination code: %d"), mjcr->JobStatus);
1194 bstrftime(sdt, sizeof(sdt), mjcr->start_time);
1195 bstrftime(edt, sizeof(edt), mjcr->end_time);
1196 Pmsg14(000, _("%s\n"
1200 "Backup Level: %s\n"
1204 "Files Written: %s\n"
1205 "Bytes Written: %s\n"
1206 "Volume Session Id: %d\n"
1207 "Volume Session Time: %d\n"
1208 "Last Volume Bytes: %s\n"
1209 "Termination: %s\n\n"),
1214 job_level_to_str(mjcr->getJobLevel()),
1218 edit_uint64_with_commas(mjcr->JobFiles, ec1),
1219 edit_uint64_with_commas(mjcr->JobBytes, ec2),
1221 mjcr->VolSessionTime,
1222 edit_uint64_with_commas(mr.VolBytes, ec3),
1229 static int create_jobmedia_record(B_DB *db, JCR *mjcr)
1232 DCR *dcr = mjcr->read_dcr;
1234 dcr->EndBlock = dev->EndBlock;
1235 dcr->EndFile = dev->EndFile;
1236 dcr->VolMediaId = dev->VolCatInfo.VolMediaId;
1238 memset(&jmr, 0, sizeof(jmr));
1239 jmr.JobId = mjcr->JobId;
1240 jmr.MediaId = mr.MediaId;
1241 jmr.FirstIndex = dcr->VolFirstIndex;
1242 jmr.LastIndex = dcr->VolLastIndex;
1243 jmr.StartFile = dcr->StartFile;
1244 jmr.EndFile = dcr->EndFile;
1245 jmr.StartBlock = dcr->StartBlock;
1246 jmr.EndBlock = dcr->EndBlock;
1253 if (!db_create_jobmedia_record(bjcr, db, &jmr)) {
1254 Pmsg1(0, _("Could not create JobMedia record. ERR=%s\n"), db_strerror(db));
1258 Pmsg2(000, _("Created JobMedia record JobId %d, MediaId %d\n"),
1259 jmr.JobId, jmr.MediaId);
1265 * Simulate the database call that updates the MD5/SHA1 record
1267 static int update_digest_record(B_DB *db, char *digest, DEV_RECORD *rec, int type)
1271 mjcr = get_jcr_by_session(rec->VolSessionId, rec->VolSessionTime);
1273 if (mr.VolJobs > 0) {
1274 Pmsg2(000, _("Could not find SessId=%d SessTime=%d for MD5/SHA1 record.\n"),
1275 rec->VolSessionId, rec->VolSessionTime);
1282 if (!update_db || mjcr->FileId == 0) {
1287 if (!db_add_digest_to_file_record(bjcr, db, mjcr->FileId, digest, type)) {
1288 Pmsg1(0, _("Could not add MD5/SHA1 to File record. ERR=%s\n"), db_strerror(db));
1293 Pmsg0(000, _("Updated MD5/SHA1 record\n"));
1301 * Create a JCR as if we are really starting the job
1303 static JCR *create_jcr(JOB_DBR *jr, DEV_RECORD *rec, uint32_t JobId)
1307 * Transfer as much as possible to the Job JCR. Most important is
1308 * the JobId and the ClientId.
1310 jobjcr = new_jcr(sizeof(JCR), bscan_free_jcr);
1311 jobjcr->set_JobType(jr->JobType);
1312 jobjcr->set_JobLevel(jr->JobLevel);
1313 jobjcr->JobStatus = jr->JobStatus;
1314 bstrncpy(jobjcr->Job, jr->Job, sizeof(jobjcr->Job));
1315 jobjcr->JobId = JobId; /* this is JobId on tape */
1316 jobjcr->sched_time = jr->SchedTime;
1317 jobjcr->start_time = jr->StartTime;
1318 jobjcr->VolSessionId = rec->VolSessionId;
1319 jobjcr->VolSessionTime = rec->VolSessionTime;
1320 jobjcr->ClientId = jr->ClientId;
1321 jobjcr->dcr = jobjcr->read_dcr = new_dcr(jobjcr, NULL, dev);
1326 /* Dummies to replace askdir.c */
1327 bool dir_find_next_appendable_volume(DCR *dcr) { return 1;}
1328 bool dir_update_volume_info(DCR *dcr, bool relabel, bool update_LastWritten) { return 1; }
1329 bool dir_create_jobmedia_record(DCR *dcr, bool zero) { return 1; }
1330 bool dir_ask_sysop_to_create_appendable_volume(DCR *dcr) { return 1; }
1331 bool dir_update_file_attributes(DCR *dcr, DEV_RECORD *rec) { return 1;}
1332 bool dir_send_job_status(JCR *jcr) {return 1;}
1333 int generate_job_event(JCR *jcr, const char *event) { return 1; }
1335 bool dir_ask_sysop_to_mount_volume(DCR *dcr, int /*mode*/)
1337 DEVICE *dev = dcr->dev;
1338 Dmsg0(20, "Enter dir_ask_sysop_to_mount_volume\n");
1339 /* Close device so user can use autochanger if desired */
1340 fprintf(stderr, _("Mount Volume \"%s\" on device %s and press return when ready: "),
1341 dcr->VolumeName, dev->print_name());
1347 bool dir_get_volume_info(DCR *dcr, enum get_vol_info_rw writing)
1349 Dmsg0(100, "Fake dir_get_volume_info\n");
1350 dcr->setVolCatName(dcr->VolumeName);
1351 dcr->VolCatInfo.VolCatParts = find_num_dvd_parts(dcr);
1352 Dmsg2(500, "Vol=%s num_parts=%d\n", dcr->getVolCatName(), dcr->VolCatInfo.VolCatParts);