3 * Program to scan a Bacula Volume and compare it with
4 * the catalog and optionally synchronize the catalog
7 * Kern E. Sibbald, December 2001
13 Copyright (C) 2001, 2002 Kern Sibbald and John Walker
15 This program is free software; you can redistribute it and/or
16 modify it under the terms of the GNU General Public License as
17 published by the Free Software Foundation; either version 2 of
18 the License, or (at your option) any later version.
20 This program is distributed in the hope that it will be useful,
21 but WITHOUT ANY WARRANTY; without even the implied warranty of
22 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
23 General Public License for more details.
25 You should have received a copy of the GNU General Public
26 License along with this program; if not, write to the Free
27 Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
34 #include "findlib/find.h"
35 #include "cats/cats.h"
37 /* Forward referenced functions */
38 static void do_scan(char *fname);
39 static void record_cb(JCR *jcr, DEVICE *dev, DEV_BLOCK *block, DEV_RECORD *rec);
40 static int create_file_attributes_record(B_DB *db, char *fname, char *lname, int type,
41 char *ap, DEV_RECORD *rec);
42 static int create_media_record(B_DB *db, MEDIA_DBR *mr, VOLUME_LABEL *vl);
43 static int create_pool_record(B_DB *db, POOL_DBR *pr);
44 static int create_job_record(B_DB *db, JOB_DBR *mr, SESSION_LABEL *label, DEV_RECORD *rec);
45 static int update_job_record(B_DB *db, JOB_DBR *mr, SESSION_LABEL *elabel,
47 static int create_client_record(B_DB *db, CLIENT_DBR *cr);
48 static int create_fileset_record(B_DB *db, FILESET_DBR *fsr);
49 static int create_jobmedia_record(B_DB *db, JCR *jcr);
50 static void create_jcr(JOB_DBR *jr, DEV_RECORD *rec, uint32_t JobId);
53 /* Global variables */
54 static DEVICE *dev = NULL;
56 static JCR *jcr; /* jcr for bscan */
57 static JCR *jobjcr; /* jcr for simulating running job */
59 static struct stat statp;
61 static long record_file_index;
62 static POOLMEM *fname; /* original file name */
63 static POOLMEM *ofile; /* output name with prefix */
64 static POOLMEM *lname; /* link name */
69 static FILESET_DBR fsr;
71 static SESSION_LABEL label;
72 static SESSION_LABEL elabel;
74 static char *db_name = "bacula";
75 static char *db_user = "bacula";
76 static char *db_password = "";
77 static char *wd = "/tmp";
78 static int verbose = 0;
79 static int update_db = 0;
84 "\nVersion: " VERSION " (" DATE ")\n\n"
85 "Usage: bscan [-d debug_level] <bacula-archive>\n"
86 " -b bootstrap specify a bootstrap file\n"
87 " -dnn set debug level to nn\n"
88 " -n name specify the database name (default bacula)\n"
89 " -u user specify database user name (default bacula)\n"
90 " -p password specify database password (default none)\n"
91 " -s synchronize or store in database\n"
93 " -w dir specify working directory (default /tmp)\n"
94 " -? print this message\n\n"));
98 int main (int argc, char *argv[])
102 my_name_is(argc, argv, "bscan");
103 init_msg(NULL, NULL);
106 while ((ch = getopt(argc, argv, "b:d:n:p:su:vw:?")) != -1) {
109 bsr = parse_bsr(NULL, optarg);
111 case 'd': /* debug level */
112 debug_level = atoi(optarg);
113 if (debug_level <= 0)
126 db_password = optarg;
151 Pmsg0(0, _("Wrong number of arguments: \n"));
155 working_directory = wd;
157 jcr = setup_jcr("bscan", argv[0], bsr);
159 if ((db=db_init_database(NULL, db_name, db_user, db_password)) == NULL) {
160 Emsg0(M_ERROR_TERM, 0, _("Could not init Bacula database\n"));
162 if (!db_open_database(db)) {
163 Emsg0(M_ERROR_TERM, 0, db_strerror(db));
165 Dmsg0(200, "Database opened\n");
167 Pmsg2(000, _("Using Database: %s, User: %s\n"), db_name, db_user);
177 static void do_scan(char *devname)
180 dev = setup_to_read_device(jcr);
185 fname = get_pool_memory(PM_FNAME);
186 ofile = get_pool_memory(PM_FNAME);
187 lname = get_pool_memory(PM_FNAME);
189 memset(&ar, 0, sizeof(ar));
190 memset(&pr, 0, sizeof(pr));
191 memset(&jr, 0, sizeof(jr));
192 memset(&cr, 0, sizeof(cr));
193 memset(&fsr, 0, sizeof(fsr));
195 detach_jcr_from_device(dev, jcr);
197 read_records(jcr, dev, record_cb, mount_next_read_volume);
198 release_device(jcr, dev);
200 free_pool_memory(fname);
201 free_pool_memory(ofile);
202 free_pool_memory(lname);
206 static void record_cb(JCR *jcr, DEVICE *dev, DEV_BLOCK *block, DEV_RECORD *rec)
209 * Check for Start or End of Session Record
212 if (rec->FileIndex < 0) {
215 dump_label_record(dev, rec, 1);
217 switch (rec->FileIndex) {
219 Pmsg0(000, "Volume is prelabeled. This tape cannot be scanned.\n");
223 unser_volume_label(dev, rec);
224 /* Check Pool info */
225 strcpy(pr.Name, dev->VolHdr.PoolName);
226 strcpy(pr.PoolType, dev->VolHdr.PoolType);
227 if (db_get_pool_record(db, &pr)) {
229 Pmsg1(000, "Pool record for %s found in DB.\n", pr.Name);
232 Pmsg1(000, "VOL_LABEL: Pool record not found for Pool: %s\n",
234 create_pool_record(db, &pr);
236 if (strcmp(pr.PoolType, dev->VolHdr.PoolType) != 0) {
237 Pmsg2(000, "VOL_LABEL: PoolType mismatch. DB=%s Vol=%s\n",
238 pr.PoolType, dev->VolHdr.PoolType);
240 } else if (verbose) {
241 Pmsg1(000, "Pool type \"%s\" is OK.\n", pr.PoolType);
244 /* Check Media Info */
245 memset(&mr, 0, sizeof(mr));
246 strcpy(mr.VolumeName, dev->VolHdr.VolName);
247 mr.PoolId = pr.PoolId;
248 if (db_get_media_record(db, &mr)) {
250 Pmsg1(000, "Media record for %s found in DB.\n", mr.VolumeName);
253 Pmsg1(000, "VOL_LABEL: Media record not found for Volume: %s\n",
255 strcpy(mr.MediaType, dev->VolHdr.MediaType);
256 create_media_record(db, &mr, &dev->VolHdr);
258 if (strcmp(mr.MediaType, dev->VolHdr.MediaType) != 0) {
259 Pmsg2(000, "VOL_LABEL: MediaType mismatch. DB=%s Vol=%s\n",
260 mr.MediaType, dev->VolHdr.MediaType);
262 } else if (verbose) {
263 Pmsg1(000, "Media type \"%s\" is OK.\n", mr.MediaType);
265 /* Reset some JCR variables */
266 for (JCR *mjcr=NULL; (mjcr=next_attached_jcr(dev, mjcr)); ) {
267 mjcr->VolFirstFile = mjcr->FileIndex = 0;
268 mjcr->start_block = mjcr->end_block = 0;
269 mjcr->start_file = mjcr->end_file = 0;
272 Pmsg1(000, "VOL_LABEL: OK for Volume: %s\n", mr.VolumeName);
275 unser_session_label(&label, rec);
276 memset(&jr, 0, sizeof(jr));
277 jr.JobId = label.JobId;
278 if (db_get_job_record(db, &jr)) {
279 /* Job record already exists in DB */
280 create_jcr(&jr, rec, jr.JobId);
281 attach_jcr_to_device(dev, jobjcr);
283 Pmsg1(000, _("SOS_LABEL: Found Job record for JobId: %d\n"), jr.JobId);
287 /* Must create a Job record in DB */
288 Pmsg1(000, "SOS_LABEL: Job record not found for JobId: %d\n",
291 /* Create Client record */
292 strcpy(cr.Name, label.ClientName);
293 create_client_record(db, &cr);
294 jr.ClientId = cr.ClientId;
296 create_job_record(db, &jr, &label, rec);
297 jr.PoolId = pr.PoolId;
298 /* Set start positions into JCR */
299 jobjcr->start_block = dev->block_num;
300 jobjcr->start_file = dev->file;
302 if (rec->VolSessionId != jr.VolSessionId) {
303 Pmsg3(000, "SOS_LABEL: VolSessId mismatch for JobId=%u. DB=%d Vol=%d\n",
305 jr.VolSessionId, rec->VolSessionId);
308 if (rec->VolSessionTime != jr.VolSessionTime) {
309 Pmsg3(000, "SOS_LABEL: VolSessTime mismatch for JobId=%u. DB=%d Vol=%d\n",
311 jr.VolSessionTime, rec->VolSessionTime);
314 if (jr.PoolId != pr.PoolId) {
315 Pmsg3(000, "SOS_LABEL: PoolId mismatch for JobId=%u. DB=%d Vol=%d\n",
317 jr.PoolId, pr.PoolId);
322 unser_session_label(&elabel, rec);
324 /* Create FileSet record */
325 strcpy(fsr.FileSet, label.FileSetName);
326 create_fileset_record(db, &fsr);
327 jr.FileSetId = fsr.FileSetId;
329 /* Do the final update to the Job record */
330 update_job_record(db, &jr, &elabel, rec);
332 /* Create JobMedia record */
333 jobjcr = get_jcr_by_session(rec->VolSessionId, rec->VolSessionTime);
335 Pmsg2(000, _("Could not find SessId=%d SessTime=%d for EOS record.\n"),
336 rec->VolSessionId, rec->VolSessionTime);
340 jobjcr->end_block = dev->block_num;
341 jobjcr->end_file = dev->file;
342 create_jobmedia_record(db, jobjcr);
343 detach_jcr_from_device(dev, jobjcr);
346 Pmsg1(000, "EOS_LABEL: OK for JobId=%d\n", elabel.JobId);
357 /* File Attributes stream */
358 if (rec->Stream == STREAM_UNIX_ATTRIBUTES) {
361 if (sizeof_pool_memory(fname) < rec->data_len) {
362 fname = realloc_pool_memory(fname, rec->data_len + 1);
364 if (sizeof_pool_memory(lname) < rec->data_len) {
365 lname = realloc_pool_memory(lname, rec->data_len + 1);
371 * An Attributes record consists of:
376 * Link name (if file linked i.e. FT_LNK)
379 sscanf(rec->data, "%ld %d", &record_file_index, &type);
380 if (record_file_index != rec->FileIndex)
381 Emsg2(M_ERROR_TERM, 0, "Record header file index %ld not equal record index %ld\n",
382 rec->FileIndex, record_file_index);
384 while (*ap++ != ' ') /* skip record file index */
386 while (*ap++ != ' ') /* skip type */
388 /* Save filename and position to attributes */
393 *fp = *ap++; /* terminate filename & point to attribs */
395 /* Skip through attributes to link name */
400 strcat(lname, lp); /* "save" link name */
404 decode_stat(ap, &statp);
405 print_ls_output(fname, lname, type, &statp);
407 create_file_attributes_record(db, fname, lname, type, ap, rec);
409 /* Data stream and extracting */
410 } else if (rec->Stream == STREAM_FILE_DATA) {
412 } else if (rec->Stream == STREAM_GZIP_DATA) {
414 } else if (rec->Stream == STREAM_MD5_SIGNATURE) {
416 Pmsg0(000, _("Got MD5 record.\n"));
418 /* ****FIXME**** implement db_update_md5_record */
420 Pmsg2(0, _("Unknown stream type!!! stream=%d data=%s\n"), rec->Stream, rec->data);
426 * Free the Job Control Record if no one is still using it.
427 * Called from main free_jcr() routine in src/lib/jcr.c so
428 * that we can do our Director specific cleanup of the jcr.
430 static void dird_free_jcr(JCR *jcr)
432 Dmsg0(200, "Start dird free_jcr\n");
434 if (jcr->file_bsock) {
435 Dmsg0(200, "Close File bsock\n");
436 bnet_close(jcr->file_bsock);
438 if (jcr->store_bsock) {
439 Dmsg0(200, "Close Store bsock\n");
440 bnet_close(jcr->store_bsock);
442 if (jcr->RestoreBootstrap) {
443 free(jcr->RestoreBootstrap);
445 Dmsg0(200, "End dird free_jcr\n");
449 * We got a File Attributes record on the tape. Now, lookup the Job
450 * record, and then create the attributes record.
452 static int create_file_attributes_record(B_DB *db, char *fname, char *lname, int type,
453 char *ap, DEV_RECORD *rec)
461 mjcr = get_jcr_by_session(rec->VolSessionId, rec->VolSessionTime);
463 Pmsg2(000, _("Could not find Job SessId=%d SessTime=%d for Attributes record.\n"),
464 rec->VolSessionId, rec->VolSessionTime);
469 ar.ClientId = mjcr->ClientId;
470 ar.JobId = mjcr->JobId;
471 ar.Stream = rec->Stream;
472 ar.FileIndex = rec->FileIndex;
474 if (mjcr->VolFirstFile == 0) {
475 mjcr->VolFirstFile = rec->FileIndex;
477 mjcr->FileIndex = rec->FileIndex;
478 free_jcr(mjcr); /* done using JCR */
480 if (!db_create_file_attributes_record(db, &ar)) {
481 Pmsg1(0, _("Could not create File Attributes record. ERR=%s\n"), db_strerror(db));
485 Pmsg1(000, _("Created File record: %s\n"), fname);
491 * For each Volume we see, we create a Medium record
493 static int create_media_record(B_DB *db, MEDIA_DBR *mr, VOLUME_LABEL *vl)
500 strcpy(mr->VolStatus, "Full");
501 mr->VolRetention = 355 * 3600 * 24; /* 1 year */
502 dt.julian_day_number = vl->write_date;
503 dt.julian_day_fraction = vl->write_time;
505 mr->FirstWritten = mktime(&tm);
506 dt.julian_day_number = vl->label_date;
507 dt.julian_day_fraction = vl->label_time;
509 mr->LabelDate = mktime(&tm);
510 if (!db_create_media_record(db, mr)) {
511 Pmsg1(0, _("Could not create media record. ERR=%s\n"), db_strerror(db));
514 if (!db_update_media_record(db, mr)) {
515 Pmsg1(0, _("Could not update media record. ERR=%s\n"), db_strerror(db));
519 Pmsg2(000, _("Created Media record for Volume: %s, JobId: %d\n"),
520 mr->VolumeName, jr.JobId);
526 static int create_pool_record(B_DB *db, POOL_DBR *pr)
533 pr->VolRetention = 355 * 3600 * 24; /* 1 year */
534 if (!db_create_pool_record(db, pr)) {
535 Pmsg1(0, _("Could not create pool record. ERR=%s\n"), db_strerror(db));
539 Pmsg1(000, _("Created Pool record for Pool: %s\n"), pr->Name);
547 * Called from SOS to create a client for the current Job
549 static int create_client_record(B_DB *db, CLIENT_DBR *cr)
554 if (!db_create_client_record(db, cr)) {
555 Pmsg1(0, _("Could not create Client record. ERR=%s\n"), db_strerror(db));
559 Pmsg1(000, _("Created Client record for Client: %s\n"), cr->Name);
564 static int create_fileset_record(B_DB *db, FILESET_DBR *fsr)
569 if (!db_create_fileset_record(db, fsr)) {
570 Pmsg1(0, _("Could not create FileSet record. ERR=%s\n"), db_strerror(db));
574 Pmsg1(000, _("Created FileSet record %s\n"), fsr->FileSet);
580 * Simulate the two calls on the database to create
581 * the Job record and to update it when the Job actually
584 static int create_job_record(B_DB *db, JOB_DBR *jr, SESSION_LABEL *label,
593 Pmsg1(000, _("Creating Job record for JobId: %d\n"), jr->JobId);
595 jr->JobId = label->JobId;
596 jr->Type = label->JobType;
597 jr->Level = label->JobLevel;
598 jr->JobStatus = JS_Created;
599 strcpy(jr->Name, label->JobName);
600 strcpy(jr->Job, label->Job);
601 dt.julian_day_number = label->write_date;
602 dt.julian_day_fraction = label->write_time;
604 jr->SchedTime = mktime(&tm);
605 jr->StartTime = jr->SchedTime;
606 jr->JobTDate = (btime_t)jr->SchedTime;
607 jr->VolSessionId = rec->VolSessionId;
608 jr->VolSessionTime = rec->VolSessionTime;
610 /* This creates the bare essentials */
611 if (!db_create_job_record(db, jr)) {
612 Pmsg1(0, _("Could not create job record. ERR=%s\n"), db_strerror(db));
616 /* Now create a JCR as if starting the Job */
617 create_jcr(jr, rec, label->JobId);
619 /* This adds the client, StartTime, JobTDate, ... */
620 if (!db_update_job_start_record(db, jr)) {
621 Pmsg1(0, _("Could not update job start record. ERR=%s\n"), db_strerror(db));
625 Pmsg1(000, _("Created Job record for JobId: %d\n"), jr->JobId);
631 * Simulate the database call that updates the Job
632 * at Job termination time.
634 static int update_job_record(B_DB *db, JOB_DBR *jr, SESSION_LABEL *elabel,
644 mjcr = get_jcr_by_session(rec->VolSessionId, rec->VolSessionTime);
646 Pmsg2(000, _("Could not find SessId=%d SessTime=%d for EOS record.\n"),
647 rec->VolSessionId, rec->VolSessionTime);
650 dt.julian_day_number = elabel->write_date;
651 dt.julian_day_fraction = elabel->write_time;
653 jr->JobId = mjcr->JobId;
654 jr->JobStatus = JS_Terminated; /* ***FIXME*** need to add to EOS label */
655 jr->EndTime = mktime(&tm);
656 jr->JobFiles = elabel->JobFiles;
657 jr->JobBytes = elabel->JobBytes;
658 jr->VolSessionId = rec->VolSessionId;
659 jr->VolSessionTime = rec->VolSessionTime;
660 jr->JobTDate = (btime_t)mjcr->start_time;
661 jr->ClientId = mjcr->ClientId;
662 if (!db_update_job_end_record(db, jr)) {
663 Pmsg1(0, _("Could not update job record. ERR=%s\n"), db_strerror(db));
668 Pmsg1(000, _("Updated Job termination record for JobId: %d\n"), jr->JobId);
674 static int create_jobmedia_record(B_DB *db, JCR *mjcr)
681 memset(&jmr, 0, sizeof(jmr));
682 jmr.JobId = mjcr->JobId;
683 jmr.MediaId = mr.MediaId;
684 jmr.FirstIndex = mjcr->VolFirstFile;
685 jmr.LastIndex = mjcr->FileIndex;
686 jmr.StartFile = mjcr->start_file;
687 jmr.EndFile = mjcr->end_file;
688 jmr.StartBlock = mjcr->start_block;
689 jmr.EndBlock = mjcr->end_block;
691 if (!db_create_jobmedia_record(db, &jmr)) {
692 Pmsg1(0, _("Could not create JobMedia record. ERR=%s\n"), db_strerror(db));
696 Pmsg2(000, _("Created JobMedia record JobId %d, MediaId %d\n"),
697 jmr.JobId, jmr.MediaId);
703 * Create a JCR as if we are really starting the job
705 static void create_jcr(JOB_DBR *jr, DEV_RECORD *rec, uint32_t JobId)
708 * Transfer as much as possible to the Job JCR. Most important is
709 * the JobId and the ClientId.
711 jobjcr = new_jcr(sizeof(JCR), dird_free_jcr);
712 jobjcr->JobType = jr->Type;
713 jobjcr->JobLevel = jr->Level;
714 jobjcr->JobStatus = jr->JobStatus;
715 strcpy(jobjcr->Job, jr->Job);
716 jobjcr->JobId = JobId; /* this is JobId on tape */
717 jobjcr->sched_time = jr->SchedTime;
718 jobjcr->start_time = jr->StartTime;
719 jobjcr->VolSessionId = rec->VolSessionId;
720 jobjcr->VolSessionTime = rec->VolSessionTime;
721 jobjcr->ClientId = jr->ClientId;
722 attach_jcr_to_device(dev, jobjcr);
725 /* Dummies to replace askdir.c */
726 int dir_get_volume_info(JCR *jcr, int writing) { return 1;}
727 int dir_find_next_appendable_volume(JCR *jcr) { return 1;}
728 int dir_update_volume_info(JCR *jcr, VOLUME_CAT_INFO *vol, int relabel) { return 1; }
729 int dir_create_jobmedia_record(JCR *jcr) { return 1; }
730 int dir_ask_sysop_to_mount_next_volume(JCR *jcr, DEVICE *dev) { return 1; }
731 int dir_update_file_attributes(JCR *jcr, DEV_RECORD *rec) { return 1;}
732 int dir_send_job_status(JCR *jcr) {return 1;}
735 int dir_ask_sysop_to_mount_volume(JCR *jcr, DEVICE *dev)
738 * We are at the end of reading a tape. Now, we simulate handling
739 * the end of writing a tape by wiffling through the attached
740 * jcrs creating jobmedia records.
742 Dmsg1(100, "Walk attached jcrs. Volume=%s\n", dev->VolCatInfo.VolCatName);
743 for (JCR *mjcr=NULL; (mjcr=next_attached_jcr(dev, mjcr)); ) {
745 Pmsg1(000, "create JobMedia for Job %s\n", mjcr->Job);
747 mjcr->end_block = dev->block_num;
748 mjcr->end_file = dev->file;
749 if (!create_jobmedia_record(db, mjcr)) {
750 Pmsg2(000, _("Could not create JobMedia record for Volume=%s Job=%s\n"),
751 dev->VolCatInfo.VolCatName, mjcr->Job);
755 fprintf(stderr, "Mount Volume %s on device %s and press return when ready: ",
756 jcr->VolumeName, dev_name(dev));