1 /* Copyright 2000 Enhanced Software Technologies Inc.
2 * Released under terms of the GNU General Public License as
3 * required by the license on 'mtxl.c'.
4 * $Date: 2001/06/19 21:51:32 $
8 /*#define DEBUG_PARTITION */
11 /* What this does: This basically dumps out the contents of the following
14 * Inquiry -- prints full inquiry info. If it's not a tape drive, this is
21 * Log Sense: TapeAlert Page (if supported):
22 * TapeAlert:[message#]<Message> e.g. "TapeAlert:[22]Cleaning Cartridge Worn Out"
25 * Data Compression Page:
26 * DataCompEnabled:<yes|no>
27 * DataCompCapable:<yes|no>
28 * DataDeCompEnabled:<yes|no>
32 * Device Configuration Page:
34 * DevConfigComp:<#> -- the compression byte in device config page.
35 * EarlyWarningSize:<#> -- size of early warning buffer?
37 * Medium Partition Page:
41 * Partition[1]:<size>...
43 * Read Block Limits command:
44 * MinBlock:<#> -- Minimum block size.
45 * MaxBlock:<#> -- Maximum block size.
56 FatalError("Usage: tapeinfo -f <generic-device>\n");
59 /* A table for printing out the peripheral device type as ASCII. */
60 static char *PeripheralDeviceType[32] = {
76 "Bridging Expander", /* 0x10 */
77 "Reserved", /* 0x11 */
78 "Reserved", /* 0x12 */
79 "Reserved", /* 0x13 */
80 "Reserved", /* 0x14 */
81 "Reserved", /* 0x15 */
82 "Reserved", /* 0x16 */
83 "Reserved", /* 0x17 */
84 "Reserved", /* 0x18 */
85 "Reserved", /* 0x19 */
86 "Reserved", /* 0x1a */
87 "Reserved", /* 0x1b */
88 "Reserved", /* 0x1c */
89 "Reserved", /* 0x1d */
90 "Reserved", /* 0x1e */
96 /* we call it MediumChangerFD for history reasons, sigh. */
98 /* now to print inquiry information: Copied from other one.... */
100 static void ReportInquiry(DEVICE_TYPE MediumChangerFD)
102 RequestSense_T RequestSense;
106 Inquiry = RequestInquiry(MediumChangerFD,&RequestSense);
109 PrintRequestSense(&RequestSense);
110 FatalError("INQUIRY Command Failed\n");
113 printf("Product Type: %s\n",PeripheralDeviceType[Inquiry->PeripheralDeviceType]);
114 printf("Vendor ID: '");
115 for (i = 0; i < sizeof(Inquiry->VendorIdentification); i++)
116 printf("%c", Inquiry->VendorIdentification[i]);
117 printf("'\nProduct ID: '");
118 for (i = 0; i < sizeof(Inquiry->ProductIdentification); i++)
119 printf("%c", Inquiry->ProductIdentification[i]);
120 printf("'\nRevision: '");
121 for (i = 0; i < sizeof(Inquiry->ProductRevisionLevel); i++)
122 printf("%c", Inquiry->ProductRevisionLevel[i]);
124 if (Inquiry->MChngr) { /* check the attached-media-changer bit... */
125 printf("Attached Changer: Yes\n");
127 printf("Attached Changer: No\n");
129 free(Inquiry); /* well, we're about to exit, but ... */
135 /* Okay, now for the Log Sense Tape Alert Page (if supported): */
136 #define TAPEALERT_SIZE 2048 /* max size of tapealert buffer. */
137 #define MAX_TAPE_ALERT 0x41
139 static char *tapealert_messages[]= {
141 " Read: Having problems reading (slowing down)", /* 1 */
142 " Write: Having problems writing (losing capacity)", /* 2 */
143 " Hard Error: Uncorrectable read/write error", /* 3 */
144 " Media: Media Performance Degraded, Data Is At Risk", /* 4 */
145 " Read Failure: Tape faulty or tape drive broken", /* 5 */
146 "Write Failure: Tape faulty or tape drive broken", /* 6 */
147 " Media Life: The tape has reached the end of its useful life", /* 7 */
148 "Not Data Grade:Replace cartridge with one containing data grade tape",/*8*/
149 "Write Protect: Attempted to write to a write-protected cartridge",/*9 */
150 " No Removal: Cannot unload, initiator is preventing media removal", /*a*/
151 "Cleaning Media:Cannot back up or restore to a cleaning cartridge", /* b */
152 " Bad Format: The loaded tape contains data in an unsupported format", /*c */
153 " Snapped Tape: The data cartridge contains a broken tape", /* d */
156 "Undefined", /* 10 */
157 "Undefined", /* 11 */
158 "Undefined", /* 12 */
159 "Undefined", /* 13 */
160 " Clean Now: The tape drive neads cleaning NOW", /* 0x14 */
161 "Clean Periodic:The tape drive needs to be cleaned at next opportunity", /* 0x15 */
162 "Cleaning Media:Cannot clean because cleaning cartridge used up, insert new cleaning cartridge to clean the drive", /* 0x16 */
163 "Undefined", /* 0x17 */
164 "Undefined", /* 0x18 */
165 "Undefined", /* 0x19 */
166 "Undefined", /* 0x1a */
167 "Undefined", /* 0x1b */
168 "Undefined", /* 0x1c */
169 "Undefined", /* 0x1d */
170 " Hardware A: Tape drive has a problem not read/write related", /* 0x1e */
171 " Hardware B: Tape drive has a problem not read/write related", /* 0x1f */
172 " Interface: Problem with SCSI interface between tape drive and initiator", /* 0x20 */
173 " Eject Media: The current operation has failed. Eject and reload media", /* 0x21 */
174 "Download Fail: Attempt to download new firmware failed", /* 0x22 */
175 "Undefined", /* 0x23 */
176 "Undefined", /* 0x24 */
177 "Undefined", /* 0x25 */
178 "Undefined", /* 0x26 */
179 "Undefined", /* 0x27 */
180 "Loader Hardware A: Changer having problems communicating with tape drive", /* 0x28 40 */
181 "Loader Stray Tape: Stray tape left in drive from prior error", /* 0x29 41 */
182 "Loader Hardware B: Autoloader mechanism has a fault", /* 0x2a 42 */
183 " Loader Door: Loader door is open, please close it", /* 0x2b 43 */
184 "Undefined", /* 0x2c */
185 "Undefined", /* 0x2d */
186 "Undefined", /* 0x2e */
187 "Undefined", /* 0x2f */
188 "Undefined", /* 0x30 */
189 "Undefined", /* 0x31 */
190 "Undefined", /* 0x32 */
191 "Undefined", /* 0x33 */
192 "Undefined", /* 0x34 */
193 "Undefined", /* 0x35 */
194 "Undefined", /* 0x36 */
195 "Undefined", /* 0x37 */
196 "Undefined", /* 0x38 */
197 "Undefined", /* 0x39 */
198 "Undefined", /* 0x3a */
199 "Undefined", /* 0x3b */
200 "Undefined", /* 0x3c */
201 "Undefined", /* 0x3d */
202 "Undefined", /* 0x3e */
203 "Undefined", /* 0x3f */
204 "Undefined" /* 0x40 */
208 struct tapealert_struct {
214 static struct tapealert_struct *RequestTapeAlert(DEVICE_TYPE fd, RequestSense_T *sense) {
217 struct tapealert_struct *result;
218 int i,tapealert_len,result_idx;
220 unsigned char buffer[TAPEALERT_SIZE];
221 unsigned char *walkptr;
223 slow_bzero(buffer,TAPEALERT_SIZE); /*zero it... */
225 /* now to create the CDB block: */
226 CDB[0]=0x4d; /* Log Sense */
228 CDB[2]=0x2e; /* Tape Alert Page. */
233 CDB[7]=TAPEALERT_SIZE>>8 & 0xff; /* hi byte, allocation size */
234 CDB[8]=TAPEALERT_SIZE & 0xff; /* lo byte, allocation size */
235 CDB[9]=0; /* reserved */
237 if (SCSI_ExecuteCommand(fd,Input,&CDB,10,buffer,TAPEALERT_SIZE,sense)!=0){
241 result=xmalloc(sizeof(struct tapealert_struct));
243 /* okay, we have stuff in the result buffer: the first 4 bytes are a header:
244 * byte 0 should be 0x2e, byte 1 == 0, bytes 2,3 tell how long the
247 if ((buffer[0]&0x3f) != 0x2e) {
253 tapealert_len=(((int)buffer[2])<<8) + buffer[3];
256 if (!tapealert_len) {
262 /* okay, now allocate data and move the buffer over there: */
263 result->length=MAX_TAPE_ALERT;
264 result->data=xzmalloc(MAX_TAPE_ALERT); /* alloc & zero. */
268 while (i<tapealert_len) {
271 result_idx=(((int)walkptr[0])<<8) + walkptr[1]; /* the parameter #. */
272 if (result_idx > 0 && result_idx < MAX_TAPE_ALERT) {
274 result->data[result_idx]=1;
276 result->data[result_idx]=0;
279 fprintf(stderr,"Alert[0x%x]=%d\n",result_idx,result->data[result_idx]);
283 FatalError("Invalid tapealert page: %d\n",result_idx);
285 i=i+4+walkptr[3]; /* length byte! */
286 walkptr=walkptr+4+walkptr[3]; /* next! */
291 static void ReportTapeAlert(DEVICE_TYPE fd) {
292 /* we actually ignore a bad sense reading, like might happen if the
293 * tape drive does not support the tapealert page.
296 RequestSense_T RequestSense;
298 struct tapealert_struct *result;
301 result=RequestTapeAlert(fd,&RequestSense);
302 if (!result) return; /* sorry. Don't print sense here. */
303 if (!result->length) return; /* sorry, no alerts valid. */
305 for (i=0;i<result->length;i++) {
306 if (result->data[i]) {
307 printf("TapeAlert[%d]:%s.\n",i,tapealert_messages[i]);
316 *mode_sense(DEVICE_TYPE fd, int pagenum, int alloc_len, RequestSense_T *RequestSense) {
319 unsigned char *input_buffer; /*the input buffer -- has junk prepended to
323 unsigned char *retval; /* the return value. */
326 if (alloc_len > 255) {
327 FatalError("mode_sense(6) can only read up to 255 characters!\n");
330 input_buffer=(unsigned char *)xzmalloc(256); /* overdo it, eh? */
332 /* clear the sense buffer: */
333 slow_bzero((char *)RequestSense,sizeof(RequestSense_T));
336 /* returns an array of bytes in the page, or, if not possible, NULL. */
337 CDB[0]=0x1a; /* Mode Sense(6) */
339 CDB[2]=pagenum; /* the page to read. */
341 CDB[4]=255; /* allocation length. This does max of 256 bytes! */
344 if (SCSI_ExecuteCommand(fd,Input,&CDB,6,
345 input_buffer,255,RequestSense) != 0) {
346 #ifdef DEBUG_MODE_SENSE
347 fprintf(stderr,"Could not execute mode sense...\n");
350 return NULL; /* sorry, couldn't do it. */
353 /* Oh hell, write protect is the only thing I have: always print
354 * it if our mode page was 0x0fh, before skipping past buffer:
355 * if the media is *NOT* write protected, just skip, sigh.
357 * Oh poops, the blocksize is reported in the block descriptor header
358 < * too. Again, just print if our mode page was 0x0f...
360 if (pagenum == 0x0f) {
362 if (input_buffer[2] & 0x80) {
363 printf("WriteProtect: yes\n");
365 if (input_buffer[2] & 0x70) {
366 printf("BufferedMode: yes\n");
368 if (input_buffer[1] ) {
369 printf("Medium Type: 0x%x\n", input_buffer[1]);
371 printf("Medium Type: Not Loaded\n");
373 printf("Density Code: 0x%x\n", input_buffer[4]);
374 /* Put out the block size: */
375 blocklen=((int)input_buffer[9]<<16)+
376 ((int)input_buffer[10]<<8)+
378 printf("BlockSize: %d\n", blocklen);
379 /* This doesn't seem to do anything useful on my Tandberg so I
380 * am taking it out -- elg
382 /* Put out the # Blocks */
383 /* blocklen=((int)input_buffer[5]<<16)+ */
384 /* ((int)input_buffer[6]<<8)+ */
385 /* input_buffer[7]; */
386 /* printf("Num Blocks: %d\n", blocklen); */
388 /* First skip past any header.... */
389 tmp=input_buffer+4+input_buffer[3];
390 /* now find out real length of page... */
392 retval=xmalloc(pagelen);
393 /* and copy our data to the new page. */
394 for (i=0;i<pagelen;i++) {
397 /* okay, free our input buffer: */
406 #define DCE_MASK 0x80
407 #define DCC_MASK 0x40
408 #define DDE_MASK 0x80
410 static void ReportCompressionPage(DEVICE_TYPE fd) {
411 /* actually ignore a bad sense reading, like might happen if the tape
412 * drive does not support the mode sense compression page.
415 RequestSense_T RequestSense;
417 unsigned char *compression_page;
420 compression_page=mode_sense(fd,0x0f,16,&RequestSense);
422 if (!compression_page) {
426 /* Okay, we now have the compression page. Now print stuff from it: */
427 printf("DataCompEnabled: %s\n", (compression_page[2]&DCE_MASK)? "yes" : "no");
428 printf("DataCompCapable: %s\n", (compression_page[2]&DCC_MASK)? "yes" : "no");
429 printf("DataDeCompEnabled: %s\n", (compression_page[3]&DDE_MASK)? "yes" : "no");
430 printf("CompType: 0x%x\n",
431 (compression_page[4]<<24)+(compression_page[5]<<16)+(compression_page[6]<<8)+compression_page[7]);
432 printf("DeCompType: 0x%x\n",
433 (compression_page[8]<<24)+ (compression_page[9]<<16)+(compression_page[10]<<8)+compression_page[11]);
434 free(compression_page);
438 /* Now for the device configuration mode page: */
440 static void ReportConfigPage(DEVICE_TYPE fd) {
441 RequestSense_T RequestSense;
442 unsigned char *config_page;
444 config_page=mode_sense(fd,0x10,16,&RequestSense);
445 if (!config_page) return;
447 /* Now to print the stuff: */
448 printf("ActivePartition: %d\n",config_page[3]);
449 /* The following does NOT work accurately on any tape drive I know of... */
450 /* printf("DevConfigComp: %s\n", config_page[14] ? "yes" : "no"); */
451 printf("EarlyWarningSize: %d\n",
452 (config_page[11]<<16)+(config_page[12]<<8)+config_page[13]);
456 /* ***************************************
457 * Medium Partition Page:
459 * The problem here, as we oh so graphically demonstrated during debugging
460 * of the Linux 'st' driver :-), is that there are actually *TWO* formats for
461 * the Medium Partition Page: There is the "long" format, where there is a
462 * partition size word for each partition on the page, and there is a "short"
463 * format, beloved of DAT drives, which only has a partition size word for
464 * partition #1 (and no partition size word for partition #0, and no
465 * provisions for any more partitions). So we must look at the size and
466 * # of partitions defined to know what to report as what.
468 ********************************************/
470 static void ReportPartitionPage(DEVICE_TYPE fd) {
471 RequestSense_T RequestSense;
472 unsigned char *partition_page;
474 int num_parts,max_parts;
477 partition_page=mode_sense(fd,0x11,255,&RequestSense);
478 if (!partition_page) return;
480 /* Okay, now we have either old format or new format: */
481 num_parts=partition_page[3];
482 max_parts=partition_page[2];
484 printf("NumPartitions:%d\n",num_parts);
485 printf("MaxPartitions:%d\n",max_parts);
486 if (!num_parts) { /* if no additional partitions, then ... */
487 free(partition_page);
491 /* we know we have at least one partition if we got here. Check the
492 * page size field. If it is 8 or below, then we are the old format....
495 #ifdef DEBUG_PARTITION
496 fprintf(stderr,"partition_page[1]=%d\n",partition_page[1]);
499 if (partition_page[1]==8) {
501 printf("Partition1:%d\n",(partition_page[8]<<8)+partition_page[9]);
504 for (i=0;i<=max_parts;i++) {
505 #ifdef DEBUG_PARTITION
506 fprintf(stderr,"partition%d:[%d]%d [%d]%d\n",i,8+i*2,
507 partition_page[8+i*2]<<8, 9+i*2,partition_page[9+i*2]);
510 printf("Partition%d:%d\n",i,
511 (partition_page[8+i*2]<<8)+partition_page[9+i*2]);
514 free(partition_page);
518 static void ReportSerialNumber(DEVICE_TYPE fd) {
519 /* actually ignore a bad sense reading, like might happen if the
520 tape drive does not support the inquiry page 0x80.
523 RequestSense_T sense;
525 #define WILD_SER_SIZE 30
526 unsigned char buffer[WILD_SER_SIZE]; /* just wildly overestimate serial# length! */
531 CDB[0]=0x12; /* INQUIRY */
532 CDB[1]= 1; /* EVPD = 1 */
533 CDB[2]=0x80; /* The serial # page, hopefully. */
534 CDB[3]=0 ; /* reserved */
535 CDB[4]=WILD_SER_SIZE;
538 if (SCSI_ExecuteCommand(fd, Input, &CDB, 6,
539 &buffer, sizeof(buffer), &sense) != 0) {
540 /* PrintRequestSense(&sense); */ /* zap debugging output :-) */
541 /* printf("No Serial Number: None\n"); */
545 /* okay, we have something in our buffer. Byte 3 should be the length of
546 the sernum field, and bytes 4 onward are the serial #. */
549 bufptr= &(buffer[4]);
551 printf("SerialNumber: '");
552 for (i=0;i<lim;i++) {
560 /* Read Block Limits! */
562 void ReportBlockLimits(DEVICE_TYPE fd) {
563 RequestSense_T sense;
565 unsigned char buffer[6];
568 CDB[0]=0x05; /* READ_BLOCK_LIMITS */
571 CDB[3]=0; /* 1-5 all unused. */
575 slow_bzero((unsigned char *)&sense,sizeof(RequestSense_T));
576 if (SCSI_ExecuteCommand(fd,Input,&CDB,6,buffer,6,&sense)!=0){
580 /* okay, but if we did get a result, print it: */
581 printf("MinBlock:%d\n",
582 (buffer[4]<<8)+buffer[5]);
583 printf("MaxBlock:%d\n",
584 (buffer[1]<<16)+(buffer[2]<<8)+buffer[3]);
589 /* Do a READ_POSITION. This may not be always valid, but (shrug). */
590 void ReadPosition(DEVICE_TYPE fd) {
591 RequestSense_T sense;
593 unsigned char buffer[20];
594 unsigned int position;
597 CDB[0]=0x34; /* READ_POSITION */
600 CDB[3]=0; /* 1-9 all unused. */
608 slow_bzero((unsigned char *)&sense,sizeof(RequestSense_T));
610 /* set the timeout: */
611 SCSI_Set_Timeout(2); /* set timeout to 2 seconds! */
613 /* if we don't get a result (e.g. we issue this to a disk drive), punt. */
614 if (SCSI_ExecuteCommand(fd,Input,&CDB,10,buffer,20,&sense)!=0){
618 SCSI_Default_Timeout(); /* reset it to 5 minutes, sigh! */
619 /* okay, but if we did get a result, print it: */
623 #define RBL_BYCU 0x10
626 #define RBL_PERR 0x02
628 /* If we have BOP, go ahead and print that. */
629 if (buffer[0]&RBL_BOP) {
630 printf("BOP: yes\n");
632 /* if we have valid data, print it: */
633 if (buffer[0]&RBL_BPU) {
634 printf("Block Position: -1");
637 position= (unsigned int) (((unsigned int)buffer[4]<<24) +
638 ((unsigned int)buffer[5]<<16) +
639 ((unsigned int)buffer[6]<<8) + buffer[7]);
640 printf("Block Position: %d\n",position);
645 /* Test unit ready: This will tell us whether the tape drive
646 * is currently ready to read or write.
649 int TestUnitReady(DEVICE_TYPE fd) {
650 RequestSense_T sense;
652 unsigned char buffer[6];
655 CDB[0]=0x00; /* TEST_UNIT_READY */
658 CDB[3]=0; /* 1-5 all unused. */
662 slow_bzero((unsigned char *)&sense,sizeof(RequestSense_T));
663 if (SCSI_ExecuteCommand(fd,Input,&CDB,6,buffer,0,&sense)!=0){
664 printf("Ready: no\n");
668 printf("Ready: yes\n");
672 /* We write a filemarks of 0 before going to grab position, in order
673 * to insure that data in the buffer is not a problem.
676 int WriteFileMarks(DEVICE_TYPE fd,int count) {
677 RequestSense_T sense;
679 unsigned char buffer[6];
682 CDB[0]=0x10; /* WRITE_FILE_MARKS */
684 CDB[2]=(count >> 16) & 0xff;
685 CDB[3]=(count >>8) & 0xff;
689 /* we really don't care if this command works or not, sigh. */
690 slow_bzero((unsigned char *)&sense,sizeof(RequestSense_T));
691 if (SCSI_ExecuteCommand(fd,Input,&CDB,6,buffer,0,&sense)!=0){
698 /* This will get the SCSI ID and LUN of the target device, if such
699 * is available from the OS. Currently only Linux supports this,
700 * but other drivers could, if someone wants to write a
701 * SCSI_GetIDLun function for them.
703 #ifdef HAVE_GET_ID_LUN
705 static void ReportIDLun(DEVICE_TYPE fd) {
708 scsi_id=SCSI_GetIDLun(fd);
709 printf("SCSI ID: %d\nSCSI LUN: %d\n",scsi_id->id,scsi_id->lun);
714 /* we only have one argument: "-f <device>". */
715 int main(int argc, char **argv) {
722 fprintf(stderr,"argc=%d",argc);
726 if (strcmp(argv[1],"-f")!=0) {
731 fd=SCSI_OpenDevice(filename);
733 /* Now to call the various routines: */
735 ReportSerialNumber(fd);
737 /* ReportConfigPage(fd); */
738 /* ReportPartitionPage(fd); */
739 ReportBlockLimits(fd);
740 #ifdef HAVE_GET_ID_LUN
744 /* okay, we should only report position if the unit is ready :-(. */
745 if (TestUnitReady(fd)) {
746 ReportCompressionPage(fd);