3 * Dumb program to do an "ls" of a Bacula 1.0 mortal file.
8 Copyright (C) 2000, 2001, 2002 Kern Sibbald and John Walker
10 This program is free software; you can redistribute it and/or
11 modify it under the terms of the GNU General Public License as
12 published by the Free Software Foundation; either version 2 of
13 the License, or (at your option) any later version.
15 This program is distributed in the hope that it will be useful,
16 but WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 General Public License for more details.
20 You should have received a copy of the GNU General Public
21 License along with this program; if not, write to the Free
22 Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
29 #include "findlib/find.h"
31 static void do_blocks(char *infname);
32 static void do_jobs(char *infname);
33 static void do_ls(char *fname);
34 static void print_ls_output(char *fname, char *link, int type, struct stat *statp);
35 static void do_setup(char *infname);
36 static void do_close();
37 static void get_session_record(DEVICE *dev, DEV_RECORD *rec, SESSION_LABEL *sessrec);
40 static int default_tape = FALSE;
41 static int dump_label = FALSE;
42 static int list_blocks = FALSE;
43 static int list_jobs = FALSE;
44 static int verbose = 0;
45 static char Vol[2000];
48 static DEV_RECORD *rec;
49 static DEV_BLOCK *block;
50 static int NumVolumes, CurVolume;
54 extern char BaculaId[];
58 static BSR *bsr = NULL;
63 "\nVersion: " VERSION " (" DATE ")\n\n"
64 "Usage: bls [-d debug_level] <physical-device-name>\n"
65 " -b <file> specify a bootstrap file\n"
66 " -e <file> exclude list\n"
67 " -i <file> include list\n"
70 " -L list tape label\n"
71 " (none of above) list saved files\n"
72 " -t use default tape device\n"
74 " -? print this message\n\n");
78 static char *rec_state_to_str(DEV_RECORD *rec)
82 if (rec->state & REC_NO_HEADER) {
83 strcat(buf, "Nohdr,");
85 if (is_partial_record(rec)) {
86 strcat(buf, "partial,");
88 if (rec->state & REC_BLOCK_EMPTY) {
89 strcat(buf, "empty,");
91 if (rec->state & REC_NO_MATCH) {
92 strcat(buf, "Nomatch,");
94 if (rec->state & REC_CONTINUATION) {
98 buf[strlen(buf)-1] = 0;
105 int main (int argc, char *argv[])
111 my_name_is(argc, argv, "bls");
112 init_msg(NULL, NULL); /* initialize message handler */
114 memset(&ff, 0, sizeof(ff));
115 init_include_exclude_files(&ff);
117 while ((ch = getopt(argc, argv, "b:d:e:i:jkLtv?")) != -1) {
120 bsr = parse_bsr(NULL, optarg);
123 case 'd': /* debug level */
124 debug_level = atoi(optarg);
125 if (debug_level <= 0)
129 case 'e': /* exclude list */
130 if ((fd = fopen(optarg, "r")) == NULL) {
131 Pmsg2(0, "Could not open exclude file: %s, ERR=%s\n",
132 optarg, strerror(errno));
135 while (fgets(line, sizeof(line), fd) != NULL) {
136 strip_trailing_junk(line);
137 Dmsg1(100, "add_exclude %s\n", line);
138 add_fname_to_exclude_list(&ff, line);
143 case 'i': /* include list */
144 if ((fd = fopen(optarg, "r")) == NULL) {
145 Pmsg2(0, "Could not open include file: %s, ERR=%s\n",
146 optarg, strerror(errno));
149 while (fgets(line, sizeof(line), fd) != NULL) {
150 strip_trailing_junk(line);
151 Dmsg1(100, "add_include %s\n", line);
152 add_fname_to_include_list(&ff, 0, line);
186 if (!argc && !default_tape) {
187 Pmsg0(0, "No archive name specified\n");
191 if (ff.included_files_list == NULL) {
192 add_fname_to_include_list(&ff, 0, "/");
195 /* Try default device */
197 do_ls(DEFAULT_TAPE_DRIVE);
201 for (i=0; i < argc; i++) {
205 } else if (list_jobs) {
218 static void my_free_jcr(JCR *jcr)
224 * Setup device, jcr, and prepare to read
226 static void do_setup(char *infname)
228 jcr = new_jcr(sizeof(JCR), my_free_jcr);
229 jcr->VolSessionId = 1;
230 jcr->VolSessionTime = (uint32_t)time(NULL);
232 strcpy(jcr->Job, "bls");
233 jcr->dev_name = get_pool_memory(PM_FNAME);
234 strcpy(jcr->dev_name, infname);
238 if (strncmp(infname, "/dev/", 5) != 0) {
239 /* Try stripping file part */
240 p = infname + strlen(infname);
241 while (p >= infname && *p != '/')
244 strcpy(VolName, p+1);
248 Dmsg2(100, "Device=%s, Vol=%s.\n", infname, VolName);
249 dev = init_dev(NULL, infname);
251 Emsg1(M_FATAL, 0, "Cannot open %s\n", infname);
254 /* ***FIXME**** init capabilities */
255 if (!open_device(dev)) {
256 Emsg1(M_FATAL, 0, "Cannot open %s\n", infname);
259 Dmsg0(90, "Device opened for read.\n");
262 block = new_block(dev);
266 for (p = VolName; p && *p; ) {
274 pm_strcpy(&jcr->VolumeName, VolName);
275 Dmsg1(100, "Volume=%s\n", jcr->VolumeName);
276 if (!acquire_device_for_read(jcr, dev, block)) {
277 Emsg0(M_ERROR, 0, dev->errmsg);
282 static void do_close()
290 static int mount_next_volume(char *infname)
292 if (rec->remainder) {
293 Dmsg0(20, "Not end of record. Next volume has more data for current record.\n");
295 Dmsg2(20, "NumVolumes=%d CurVolume=%d\n", NumVolumes, CurVolume);
296 if (NumVolumes > 1 && CurVolume < NumVolumes) {
301 Dmsg1(20, "There is another volume %s.\n", p);
303 pm_strcpy(&jcr->VolumeName, VolName);
306 dev->state &= ~ST_READ;
307 if (!acquire_device_for_read(jcr, dev, block)) {
308 Emsg2(M_FATAL, 0, "Cannot open Dev=%s, Vol=%s\n", infname, VolName);
311 return 1; /* Next volume mounted */
313 printf("End of Device reached.\n");
318 * Device got an error, attempt to analyse it
320 static void display_error_status()
324 Emsg0(M_ERROR, 0, dev->errmsg);
325 status_dev(dev, &status);
326 Dmsg1(20, "Device status: %x\n", status);
328 Emsg0(M_ERROR_TERM, 0, "Unexpected End of Data\n");
329 else if (status & MT_EOT)
330 Emsg0(M_ERROR_TERM, 0, "Unexpected End of Tape\n");
331 else if (status & MT_EOF)
332 Emsg0(M_ERROR_TERM, 0, "Unexpected End of File\n");
333 else if (status & MT_DR_OPEN)
334 Emsg0(M_ERROR_TERM, 0, "Tape Door is Open\n");
335 else if (!(status & MT_ONLINE))
336 Emsg0(M_ERROR_TERM, 0, "Unexpected Tape is Off-line\n");
338 Emsg2(M_ERROR_TERM, 0, "Read error on Record Header %s: %s\n", dev_name(dev), strerror(errno));
342 /* List just block information */
343 static void do_blocks(char *infname)
346 dump_volume_label(dev);
348 /* Assume that we have already read the volume label.
349 * If on second or subsequent volume, adjust buffer pointer
351 if (dev->VolHdr.PrevVolName[0] != 0) { /* second volume */
353 Warning, this Volume is a continuation of Volume %s\n",
354 dev->VolHdr.PrevVolName);
361 if (!read_block_from_device(dev, block)) {
362 Dmsg0(20, "!read_block()\n");
363 if (dev->state & ST_EOT) {
364 if (!mount_next_volume(infname)) {
369 if (dev->state & ST_EOF) {
370 Emsg1(M_INFO, 0, "Got EOF on device %s\n", dev_name(dev));
371 Dmsg0(20, "read_record got eof. try again\n");
374 if (dev->state & ST_SHORT) {
375 Emsg0(M_INFO, 0, dev->errmsg);
378 display_error_status();
383 read_record_from_block(block, rec);
384 Pmsg6(-1, "Block: %d blen=%d First rec FI=%s SessId=%d Strm=%s rlen=%d\n",
385 block->BlockNumber, block->block_len,
386 FI_to_ascii(rec->FileIndex), rec->VolSessionId,
387 stream_to_ascii(rec->Stream), rec->data_len);
390 printf("Block: %d size=%d\n", block->BlockNumber, block->block_len);
397 /* Do list job records */
398 static void do_jobs(char *infname)
401 /* Assume that we have already read the volume label.
402 * If on second or subsequent volume, adjust buffer pointer
404 if (dev->VolHdr.PrevVolName[0] != 0) { /* second volume */
406 Warning, this Volume is a continuation of Volume %s\n",
407 dev->VolHdr.PrevVolName);
411 if (!read_block_from_device(dev, block)) {
412 Dmsg0(20, "!read_block()\n");
413 if (dev->state & ST_EOT) {
414 if (!mount_next_volume(infname)) {
419 if (dev->state & ST_EOF) {
420 Emsg1(M_INFO, 0, "Got EOF on device %s\n", dev_name(dev));
421 Dmsg0(20, "read_record got eof. try again\n");
424 if (dev->state & ST_SHORT) {
425 Pmsg0(000, "Got short block.\n");
426 Emsg0(M_INFO, 0, dev->errmsg);
429 display_error_status();
432 while (read_record_from_block(block, rec)) {
433 if (debug_level >= 30) {
434 Dmsg4(30, "VolSId=%ld FI=%s Strm=%s Size=%ld\n", rec->VolSessionId,
435 FI_to_ascii(rec->FileIndex), stream_to_ascii(rec->Stream),
441 * Check for End of File record (all zeros)
442 * NOTE: this no longer exists
444 if (rec->VolSessionId == 0 && rec->VolSessionTime == 0) {
445 Emsg0(M_ERROR_TERM, 0, "Zero VolSessionId and VolSessionTime. This shouldn't happen\n");
449 * Check for Start or End of Session Record
452 if (rec->FileIndex < 0) {
453 dump_label_record(dev, rec, verbose);
462 /* Do an ls type listing of an archive */
463 static void do_ls(char *infname)
468 long record_file_index;
469 uint32_t num_files = 0;
473 dump_volume_label(dev);
477 /* Assume that we have already read the volume label.
478 * If on second or subsequent volume, adjust buffer pointer
480 if (dev->VolHdr.PrevVolName[0] != 0) { /* second volume */
482 Warning, this Volume is a continuation of Volume %s\n",
483 dev->VolHdr.PrevVolName);
487 SESSION_LABEL sessrec;
489 if (!read_block_from_device(dev, block)) {
490 Dmsg0(20, "!read_record()\n");
491 if (dev->state & ST_EOT) {
493 Dmsg3(100, "EOT. stat=%s blk=%d rem=%d\n", rec_state_to_str(rec),
494 block->BlockNumber, rec->remainder);
495 if (!mount_next_volume(infname)) {
496 Dmsg3(100, "After mount next vol. stat=%s blk=%d rem=%d\n", rec_state_to_str(rec),
497 block->BlockNumber, rec->remainder);
500 Dmsg3(100, "After mount next vol. stat=%s blk=%d rem=%d\n", rec_state_to_str(rec),
501 block->BlockNumber, rec->remainder);
502 record = new_record();
503 read_block_from_device(dev, block);
504 read_record_from_block(block, record);
505 get_session_record(dev, record, &sessrec);
509 if (dev->state & ST_EOF) {
510 Emsg1(M_INFO, 0, "Got EOF on device %s\n", dev_name(dev));
511 Dmsg0(20, "read_record got eof. try again\n");
514 if (dev->state & ST_SHORT) {
515 Emsg0(M_INFO, 0, dev->errmsg);
518 display_error_status();
522 Dmsg2(10, "Block: %d blen=%d\n", block->BlockNumber, block->block_len);
527 for (rec->state=0; !is_block_empty(rec); ) {
528 if (!read_record_from_block(block, rec)) {
529 Dmsg3(10, "!read-break. stat=%s blk=%d rem=%d\n", rec_state_to_str(rec),
530 block->BlockNumber, rec->remainder);
533 Dmsg3(10, "read-OK. stat=%s blk=%d rem=%d\n", rec_state_to_str(rec),
534 block->BlockNumber, rec->remainder);
536 * At this point, we have at least a record header.
537 * Now decide if we want this record or not, but remember
538 * before accessing the record, we may need to read again to
543 Dmsg6(30, "recno=%d state=%s blk=%d SI=%d ST=%d FI=%d\n", record,
544 rec_state_to_str(rec), block->BlockNumber,
545 rec->VolSessionId, rec->VolSessionTime, rec->FileIndex);
547 if (debug_level >= 30) {
548 Dmsg4(30, "VolSId=%ld FI=%s Strm=%s Size=%ld\n", rec->VolSessionId,
549 FI_to_ascii(rec->FileIndex), stream_to_ascii(rec->Stream),
553 if (rec->FileIndex == EOM_LABEL) { /* end of tape? */
554 Dmsg0(40, "Get EOM LABEL\n");
556 break; /* yes, get out */
559 /* Some sort of label? */
560 if (rec->FileIndex < 0) {
561 get_session_record(dev, rec, &sessrec);
563 } /* end if label record */
568 if (bsr && !match_bsr(bsr, rec, &dev->VolHdr, &sessrec)) {
570 Dmsg5(10, "BSR no match rec=%d block=%d SessId=%d SessTime=%d FI=%d\n",
571 record, block->BlockNumber, rec->VolSessionId, rec->VolSessionTime,
575 continue; /* we don't want record, read next one */
577 if (is_partial_record(rec)) {
578 Dmsg6(10, "Partial, break. recno=%d state=%s blk=%d SI=%d ST=%d FI=%d\n", record,
579 rec_state_to_str(rec), block->BlockNumber,
580 rec->VolSessionId, rec->VolSessionTime, rec->FileIndex);
581 break; /* read second part of record */
584 /* File Attributes stream */
585 if (rec->Stream == STREAM_UNIX_ATTRIBUTES) {
587 sscanf(rec->data, "%ld %d", &record_file_index, &type);
588 if (record_file_index != rec->FileIndex) {
589 Emsg2(M_ERROR_TERM, 0, "Record header file index %ld not equal record index %ld\n",
590 rec->FileIndex, record_file_index);
594 while (*ap++ != ' ') /* skip record file index */
596 while (*ap++ != ' ') /* skip type */
598 /* Save filename and position to attributes */
603 *fp = *ap++; /* terminate filename & point to attribs */
605 decode_stat(ap, &statp);
606 /* Skip to link name */
609 print_ls_output(fname, ap, type, &statp);
615 printf("%u files found.\n", num_files);
620 extern char *getuser(uid_t uid);
621 extern char *getgroup(gid_t gid);
623 static void print_ls_output(char *fname, char *link, int type, struct stat *statp)
630 if (!file_is_included(&ff, fname) || file_is_excluded(&ff, fname)) {
633 p = encode_mode(statp->st_mode, buf);
634 n = sprintf(p, " %2d ", (uint32_t)statp->st_nlink);
636 n = sprintf(p, "%-8.8s %-8.8s", getuser(statp->st_uid), getgroup(statp->st_gid));
638 n = sprintf(p, "%8.8s ", edit_uint64(statp->st_size, ec1));
640 p = encode_time(statp->st_ctime, p);
644 for (f=fname; *f && (p-buf) < (int)sizeof(buf); )
646 if (type == FT_LNK) {
652 for (f=link; *f && (p-buf) < (int)sizeof(buf); )
660 static void get_session_record(DEVICE *dev, DEV_RECORD *rec, SESSION_LABEL *sessrec)
663 memset(sessrec, 0, sizeof(sessrec));
664 switch (rec->FileIndex) {
666 rtype = "Fresh Volume Label";
669 rtype = "Volume Label";
670 unser_volume_label(dev, rec);
673 rtype = "Begin Session";
674 unser_session_label(sessrec, rec);
677 rtype = "End Session";
680 rtype = "End of Media";
686 Dmsg5(10, "%s Record: VolSessionId=%d VolSessionTime=%d JobId=%d DataLen=%d\n",
687 rtype, rec->VolSessionId, rec->VolSessionTime, rec->Stream, rec->data_len);
691 /* Dummies to replace askdir.c */
692 int dir_get_volume_info(JCR *jcr) { return 1;}
693 int dir_find_next_appendable_volume(JCR *jcr) { return 1;}
694 int dir_update_volume_info(JCR *jcr, VOLUME_CAT_INFO *vol, int relabel) { return 1; }
695 int dir_create_jobmedia_record(JCR *jcr) { return 1; }
696 int dir_ask_sysop_to_mount_next_volume(JCR *jcr, DEVICE *dev) { return 1; }
697 int dir_update_file_attributes(JCR *jcr, DEV_RECORD *rec) { return 1;}
698 int dir_send_job_status(JCR *jcr) {return 1;}
701 int dir_ask_sysop_to_mount_volume(JCR *jcr, DEVICE *dev)
703 fprintf(stderr, "Mount Volume %s on device %s and press return when ready: ",
704 jcr->VolumeName, dev_name(dev));