1 /* Copyright 2001 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/05 17:10:27 $
8 /* This is a generic SCSI tape control program. It operates by
9 * directly sending commands to the tape drive. If you are going
10 * through your operating system's SCSI tape driver, do *NOT* use
11 * this program! If, on the other hand, you are using raw READ and WRITE
12 * commands through your operating system's generic SCSI interface (or
13 * through our built-in 'read' and 'write'), this is the place for you.
16 /*#define DEBUG_PARTITION */
21 setblk <n> -- set the block size to <n>
22 fsf <n> -- go forward by <n> filemarks
23 bsf <n> -- go backward by <n> filemarks
24 eod -- go to end of data
25 rewind -- rewind back to start of data
26 eject -- rewind, then eject the tape.
27 erase -- (short) erase the tape (we have no long erase)
28 mark <n> -- write <n> filemarks.
29 seek <n> -- seek to position <n>.
31 write <blksize> <-- write blocks from stdin to the tape
32 read [<blksize>] [<#blocks/#bytes>] -- read blocks from tape, write to stdout.
34 See the 'tapeinfo' program for status info about the tape drive.
45 #include <sys/types.h>
46 #include <sys/ioctl.h>
47 #include <sys/mtio.h> /* will try issuing some ioctls for Solaris, sigh. */
50 FatalError("Usage: scsitape -f <generic-device> <command> where <command> is:\n setblk <n> | fsf <n> | bsf <n> | eod | rewind | eject | mark <n> |\n seek <n> | read [<blksize> [<numblocks]] | write [<blocksize>] \n");
53 #define arg1 (arg[0]) /* for backward compatibility, sigh */
54 static int arg[4]; /* the argument for the command, sigh. */
56 /* the device handle we're operating upon, sigh. */
57 static unsigned char *device; /* the text of the device thingy. */
58 static DEVICE_TYPE MediumChangerFD = (DEVICE_TYPE) 0;
62 static int S_setblk(void);
63 static int S_fsf(void);
64 static int S_bsf(void);
65 static int S_eod(void);
66 static int S_rewind(void);
67 static int S_eject(void);
68 static int S_mark(void);
69 static int S_seek(void);
70 static int S_reten(void);
71 static int S_erase(void);
73 static int S_read(void);
74 static int S_write(void);
77 struct command_table_struct {
82 { 1, "setblk", S_setblk },
86 { 0, "rewind", S_rewind },
87 { 0, "eject", S_eject },
88 { 0, "reten", S_reten },
89 { 0, "erase", S_erase },
90 { 1, "mark", S_mark },
91 { 1, "seek", S_seek },
92 { 2, "read", S_read },
93 { 2, "write",S_write }
98 /* A table for printing out the peripheral device type as ASCII. */
99 static char *PeripheralDeviceType[32] = {
113 "Enclosure Services",
115 "Bridging Expander", /* 0x10 */
116 "Reserved", /* 0x11 */
117 "Reserved", /* 0x12 */
118 "Reserved", /* 0x13 */
119 "Reserved", /* 0x14 */
120 "Reserved", /* 0x15 */
121 "Reserved", /* 0x16 */
122 "Reserved", /* 0x17 */
123 "Reserved", /* 0x18 */
124 "Reserved", /* 0x19 */
125 "Reserved", /* 0x1a */
126 "Reserved", /* 0x1b */
127 "Reserved", /* 0x1c */
128 "Reserved", /* 0x1d */
129 "Reserved", /* 0x1e */
135 /* open_device() -- set the 'fh' variable.... */
136 void open_device(void) {
138 if (MediumChangerFD) {
139 SCSI_CloseDevice("Unknown",MediumChangerFD); /* close it, sigh... new device now! */
142 MediumChangerFD = SCSI_OpenDevice(device);
146 static int get_arg(char *arg) {
149 if (*arg < '0' || *arg > '9') {
150 return -1; /* sorry! */
158 /* we see if we've got a file open. If not, we open one :-(. Then
159 * we execute the actual command. Or not :-(.
161 int execute_command(struct command_table_struct *command) {
163 /* if the device is not already open, then open it from the
166 if (!MediumChangerFD) {
167 /* try to get it from STAPE or TAPE environment variable... */
168 device=getenv("STAPE");
170 device=getenv("TAPE");
179 /* okay, now to execute the command... */
180 return command->command();
185 * Basically, we are parsing argv/argc. We can have multiple commands
186 * on a line now, such as "unload 3 0 load 4 0" to unload one tape and
187 * load in another tape into drive 0, and we execute these commands one
188 * at a time as we come to them. If we don't have a -f at the start, we
189 * barf. If we leave out a drive #, we default to drive 0 (the first drive
193 int parse_args(int argc,char **argv) {
194 int i,cmd_tbl_idx,retval,arg_idx;
195 struct command_table_struct *command;
200 if (strcmp(argv[i],"-f") == 0) {
206 open_device(); /* open the device and do a status scan on it... */
209 command=&command_table[0]; /* default to the first command... */
210 command=&command_table[cmd_tbl_idx];
211 while (command->name) {
212 if (!strcmp(command->name,argv[i])) {
213 /* we have a match... */
216 /* otherwise we don't have a match... */
218 command=&command_table[cmd_tbl_idx];
220 /* if it's not a command, exit.... */
221 if (!command->name) {
224 i++; /* go to the next argument, if possible... */
225 /* see if we need to gather arguments, though! */
226 arg1=-1; /* default it to something */
227 for (arg_idx=0;arg_idx < command->num_args ; arg_idx++) {
229 arg[arg_idx]=get_arg(argv[i]);
230 if (arg[arg_idx] != -1) {
231 i++; /* increment i over the next cmd. */
234 arg[arg_idx]=0; /* default to 0 setmarks or whatever */
237 retval=execute_command(command); /* execute_command handles 'stuff' */
241 return 0; /* should never get here */
244 /* For Linux, this allows us to do a short erase on a tape (sigh!).
245 * Note that you'll need to do a 'mt status' on the tape afterwards in
246 * order to get the tape driver in sync with the tape drive again. Also
247 * note that on other OS's, this might do other evil things to the tape
248 * driver. Note that to do an erase, you must first rewind!
251 static int S_erase(void) {
253 RequestSense_T *RequestSense;
257 return retval; /* we have an exit status :-(. */
260 RequestSense=Erase(MediumChangerFD);
262 PrintRequestSense(RequestSense);
263 exit(1); /* exit with an error status. */
268 /* This should eject a tape or magazine, depending upon the device sent
271 static int S_eject(void)
274 i=Eject(MediumChangerFD);
276 fprintf(stderr,"scsitape:eject failed\n");
279 return i; /* if it failed, well, sigh.... */
285 /* We write a filemarks of 0 before going to grab position, in order
286 * to insure that data in the buffer is not a problem.
289 static int S_mark(void) {
290 RequestSense_T RequestSense; /* for result of ReadElementStatus */
292 unsigned char buffer[6];
293 int count=arg1; /* voila! */
295 CDB[0]=0x10; /* SET_MARK */
297 CDB[2]=(count >> 16) & 0xff;
298 CDB[3]=(count >>8) & 0xff;
302 /* we really don't care if this command works or not, sigh. */
303 slow_bzero((unsigned char *)&RequestSense,sizeof(RequestSense_T));
304 if (SCSI_ExecuteCommand(MediumChangerFD,Input,&CDB,6,buffer,0,&RequestSense)!=0){
305 PrintRequestSense(&RequestSense);
310 /* let's rewind to bod!
313 static int S_rewind(void) {
314 RequestSense_T sense;
316 unsigned char buffer[6];
318 CDB[0]=0x01; /* REWIND */
325 /* we really don't care if this command works or not, sigh. */
326 slow_bzero((unsigned char *)&sense,sizeof(RequestSense_T));
327 if (SCSI_ExecuteCommand(MediumChangerFD,Input,&CDB,6,buffer,0,&sense)!=0){
328 PrintRequestSense(&sense);
337 /* This is used for fsf and bsf. */
338 static int Space(int count,int code){
339 RequestSense_T sense;
341 unsigned char buffer[6];
343 CDB[0]=0x11; /* SET_MARK */
345 CDB[2]=(count >> 16) & 0xff;
346 CDB[3]=(count >>8) & 0xff;
350 /* we really don't care if this command works or not, sigh. */
351 slow_bzero((unsigned char *)&sense,sizeof(RequestSense_T));
352 if (SCSI_ExecuteCommand(MediumChangerFD,Input,&CDB,6,buffer,0,&sense)!=0){
353 PrintRequestSense(&sense);
361 /* Let's try a fsf: */
362 /* We write a filemarks of 0 before going to grab position, in order
363 * to insure that data in the buffer is not a problem.
366 static int S_fsf(void) {
367 return Space(arg1,1); /* go forward! */
370 static int S_bsf(void) {
371 return Space(-arg1,1); /* go backward! */
374 static int S_eod(void) {
375 return Space(0,3); /* go to eod! */
378 /* sigh, abuse of the LOAD command...
381 static int S_reten(void) {
382 RequestSense_T sense;
384 unsigned char buffer[6];
386 CDB[0]=0x1b; /* START_STOP */
390 CDB[4]=3; /* reten. */
393 /* we really don't care if this command works or not, sigh. */
394 slow_bzero((unsigned char *)&sense,sizeof(RequestSense_T));
395 if (SCSI_ExecuteCommand(MediumChangerFD,Input,&CDB,6,buffer,0,&sense)!=0){
396 PrintRequestSense(&sense);
403 /* seek a position on the tape (sigh!) */
404 static int S_seek(void){
405 RequestSense_T sense;
407 unsigned char buffer[6];
410 /* printf("count=%d\n",arg1); */
412 CDB[0]=0x2b; /* LOCATE */
413 CDB[1]=0; /* Logical */
414 CDB[2]=0; /* padding */
415 CDB[3]=(count >> 24) & 0xff;
416 CDB[4]=(count >> 16) & 0xff;
417 CDB[5]=(count >>8) & 0xff;
423 /* we really don't care if this command works or not, sigh. */
424 slow_bzero((unsigned char *)&sense,sizeof(RequestSense_T));
425 if (SCSI_ExecuteCommand(MediumChangerFD,Input,&CDB,10,buffer,0,&sense)!=0){
426 PrintRequestSense(&sense);
433 static int Solaris_setblk(int fh,int count) {
434 /* we get here only if we have a MTSRSZ, which means Solaris. */
435 struct mtop mt_com; /* the struct used for the MTIOCTOP ioctl */
438 /* okay, we have fh and count.... */
440 /* Now to try the ioctl: */
442 mt_com.mt_count=count;
444 /* surround the actual ioctl to enable threading, since fsf/etc. can be
445 * big time consumers and we want other threads to be able to run too.
448 result=ioctl(fh, MTIOCTOP, (char *)&mt_com);
454 /* okay, we did okay. Return a value of None... */
460 /* okay, this is a write: we need to set the block size to something: */
461 static int S_setblk(void) {
462 RequestSense_T sense;
464 unsigned char buffer[12];
465 unsigned int count = (unsigned int) arg1;
468 CDB[0]=0x15; /* MODE SELECT */
469 CDB[1]=0x10; /* scsi2 */
472 CDB[4]=12; /* length of data */
475 slow_bzero((unsigned char *)&sense,sizeof(RequestSense_T));
476 slow_bzero(buffer,12);
478 /* Now to set the mode page header: */
481 buffer[2]=0x10; /* we are in buffered mode now, people! */
482 buffer[3]=8; /* block descriptor length. */
483 buffer[4]=0; /* reset to default density, sigh. */ /* 0 */
488 buffer[9]=(count >> 16) & 0xff; /* 5 */
489 buffer[10]=(count >> 8) & 0xff; /* 6 */
490 buffer[11]= count & 0xff; /* 7 */
492 if (SCSI_ExecuteCommand(MediumChangerFD,Output,&CDB,6,buffer,12,&sense)!=0){
493 PrintRequestSense(&sense);
497 /* Solaris_setblk(MediumChangerFD,count); */
503 /*************************************************************************/
504 /* SCSI read/write calls. These are mostly pulled out of BRU 16.1,
505 * modified to work within the mtxl.h framework rather than the
506 * scsi_lowlevel.h framework.
507 *************************************************************************/
509 #define MAX_READ_SIZE 128*1024 /* max size of a variable-block read */
512 #define READ_FILEMARK 1
516 #define READ_ERROR 255
519 #define WRITE_ERROR 1
524 /* These are copied out of BRU 16.1, with all the boolean masks changed
527 #define S_NO_SENSE(s) ((s).SenseKey == 0x0)
528 #define S_RECOVERED_ERROR(s) ((s).SenseKey == 0x1)
530 #define S_NOT_READY(s) ((s).SenseKey == 0x2)
531 #define S_MEDIUM_ERROR(s) ((s).SenseKey == 0x3)
532 #define S_HARDWARE_ERROR(s) ((s).SenseKey == 0x4)
533 #define S_UNIT_ATTENTION(s) ((s).SenseKey == 0x6)
534 #define S_BLANK_CHECK(s) ((s).SenseKey == 0x8)
535 #define S_VOLUME_OVERFLOW(s) ((s).SenseKey == 0xd)
537 #define DEFAULT_TIMEOUT 3*60 /* 3 minutes here */
539 #define HIT_FILEMARK(s) (S_NO_SENSE((s)) && (s).Filemark && (s).Valid)
540 /* Sigh, the T-10 SSC spec says all of the following is needed to
541 * detect a short read while in variable block mode. We'll see.
543 #define SHORT_READ(s) (S_NO_SENSE((s)) && (s).ILI && (s).Valid && (s).AdditionalSenseCode==0 && (s).AdditionalSenseCodeQualifier==0)
545 #define HIT_EOD(s) (S_BLANK_CHECK((s)) && (s).Valid)
546 #define HIT_EOP(s) (S_MEDIUM_ERROR((s)) && (s).EOM && (s).Valid)
547 #define HIT_EOM(s) ((s).EOM && (s).Valid)
548 #define BECOMING_READY(s) (S_UNIT_ATTENTION((s)) && (s).AdditionalSenseCode == 0x28 && (s).AdditionalSenseCodeQualifier == 0)
550 /* Reading is a problem. We can hit a filemark, hit an EOD, or hit an
551 * EOP. Our caller may do something about that. Note that we assume that
552 * our caller has already put us into fixed block mode. If he has not, then
553 * we are in trouble anyhow.
555 int SCSI_readt(DEVICE_TYPE fd, char * buf, unsigned int bufsize, unsigned int *len, unsigned int timeout) {
562 RequestSense_T RequestSense;
564 if (bufsize==0) { /* we are in variable block mode */
565 blockCount=MAX_READ_SIZE; /* variable block size. */
567 blockCount= *len / bufsize ;
568 if ((*len % bufsize) != 0) {
569 fprintf(stderr,"Error: Data (%d bytes) not even multiple of block size (%d bytes).\n",*len,bufsize);
570 exit(1); /* we're finished, sigh. */
575 timeout = 1 * 60; /* 1 minutes */
578 memset(&cmd, 0, sizeof(CDB_T));
579 cmd[0] = 0x08; /* READ */
580 cmd[1] = (bufsize) ? 1 : 0; /* fixed length or var length blocks */
581 cmd[2] = (blockCount >> 16) & 0xff; /* MSB */
582 cmd[3] = (blockCount >> 8) & 0xff;
583 cmd[4] = blockCount & 0xff; /* LSB */
585 /* okay, let's read, look @ the result code: */
587 if (SCSI_ExecuteCommand(fd,Input,&cmd,6,buf,(bufsize) ? *len : MAX_READ_SIZE,&RequestSense)) {
590 if (HIT_EOP(RequestSense)) {
595 if (HIT_FILEMARK(RequestSense)) {
596 rtnval=READ_FILEMARK;
598 if (HIT_EOD(RequestSense)) {
601 if ( (bufsize==0) && SHORT_READ(RequestSense)) {
602 rtnval=READ_SHORT; /* we only do short reads for variable block mode */
604 if (rtnval != READ_ERROR) {
605 /* info contains number of blocks or bytes *not* read. May be
606 negative if the block we were trying to read was too big. So
607 we will have to account for that and set it to zero if so, so that
608 we return the proper # of blocks read.
610 info=((RequestSense.Information[0]<<24) +
611 (RequestSense.Information[1]<<16) +
612 (RequestSense.Information[2]<<8) +
613 RequestSense.Information[3]);
614 /* on 64-bit platforms, we may need to turn 'info' into a negative # */
615 if (info > 0x7fffffff) info = 0;
616 if (info < 0) info=0; /* make sure we don't return too big len read. */
617 /* Now set *len to # of bytes read. */
618 *len= bufsize ? (blockCount-info) * bufsize : MAX_READ_SIZE-info ;
620 PrintRequestSense(&RequestSense);
628 /* Low level SCSI write. Modified from BRU 16.1, with much BRU smarts
629 * taken out and with the various types changed to mtx types rather than
632 int SCSI_writet(DEVICE_TYPE fd, char * buf, unsigned int blocksize,
634 unsigned int timeout) {
639 RequestSense_T RequestSense;
641 if (blocksize==0) { /* we are in variable block mode */
642 blockCount=*len; /* variable block size. */
644 blockCount= *len / blocksize ;
645 if ((*len % blocksize) != 0) {
646 fprintf(stderr,"Error: Data (%d bytes) not even multiple of block size (%d bytes).\n",*len,blocksize);
647 exit(1); /* we're finished, sigh. */
651 fprintf(stderr,"Writing %d blocks\n",blockCount);
653 memset(&cmd, 0, sizeof(CDB_T));
654 cmd[0] = 0x0a; /* WRITE */
655 cmd[1] = (blocksize) ? 1 : 0; /* fixed length or var length blocks */
656 cmd[2] = (blockCount >> 16) & 0xff; /* MSB */
657 cmd[3] = (blockCount >> 8) & 0xff;
658 cmd[4] = blockCount & 0xff; /* LSB */
661 if (SCSI_ExecuteCommand(fd,Output,&cmd,6,buf, *len, &RequestSense)) {
662 if (HIT_EOM(RequestSense)) {
663 /* we hit end of media. Return -1. */
664 if (S_VOLUME_OVERFLOW(RequestSense)) {
667 exit(WRITE_EOM); /* end of media! */
669 else { /* it was plain old write error: */
670 PrintRequestSense(&RequestSense);
674 rtnval = *len; /* worked! */
679 /* S_write is not implemented yet! */
680 static int S_write(void) {
681 unsigned char *buffer; /* the buffer we're gonna read/write out of. */
683 int len; /* the length of the data in the buffer */
684 int blocksize=arg[0];
685 int numblocks=arg[1];
686 int varsize=0; /* variable size block flag */
689 int infile=fileno(stdin); /* sigh */
693 buffersize=MAX_READ_SIZE;
696 varsize=0; /* fixed block mode */
697 buffersize=blocksize;
700 /* sigh, make it oversized just to have some */
701 buffer=malloc(buffersize+8);
705 /* size_t could be 64 bit on a 32 bit platform, so do casts. */
707 /* If it is a pipe, we could read 4096 bytes rather than the full
708 * 128K bytes or whatever, so we must gather multiple reads into
711 while (len < buffersize) {
712 result=(int)read(infile,buffer+len,(size_t)(buffersize-len));
715 if (!len) { /* if we have no deata in our buffer, exit */
716 return 0; /* we're at end of file! */
718 break; /* otherwise, break and write the data */
720 len+=result; /* add the result input to our length. */
724 result=SCSI_writet(MediumChangerFD,buffer,blocksize,&len,DEFAULT_TIMEOUT);
726 return 1; /* at end of tape! */
728 /* Now see if we have numbytes or numblocks. If so, we may wish to exit
734 return 0; /* we will only write one block in variable size mode :-( */
739 return 0; /* we're done. */
748 /* Okay, the read thingy: */
750 /* We have a device opened (we hope!) by the parser.
751 * we will have arg[0] and arg[1] being the blocksize and # of blocks
756 static int S_read(void) {
757 unsigned char *buffer; /* the buffer we're going to be reading out of */
759 int len; /* the length of the data in the buffer */
760 int blocksize=arg[0];
761 int numblocks=arg[1];
762 int varsize=0; /* variable size block flag. */
766 int outfile=fileno(stdout); /* sigh. */
771 buffersize=MAX_READ_SIZE;
774 varsize=0; /* fixed block mode */
775 buffersize=blocksize;
778 /* sigh, make it oversized just to have some */
779 buffer=malloc(buffersize+8);
784 /* it could have gotten reset by prior short read... */
787 result=SCSI_readt(MediumChangerFD,buffer,blocksize, &len, DEFAULT_TIMEOUT);
788 if (result==READ_FILEMARK || result==READ_EOD || result==READ_EOP) {
789 /* okay, normal end of file? */
791 write(outfile,buffer,len);
793 #ifdef NEED_TO_GO_PAST_FILEMARK
794 /* Now, let's try to go past the filemark if that's what we hit: */
795 if (result==READ_FILEMARK) {
796 arg1=1; /* arg for S_fsf. */
797 S_fsf(); /* and go forward 1 filemark, we hope! */
800 return 0; /* hit normal end of file. */
801 } else if (result==READ_SHORT) {
802 /* short reads are only valid in variable block mode. */
805 write(outfile,buffer,len);
808 fprintf(stderr,"scsitape:Short Read encountered on input. Aborting.\n");
810 exit(1); /* error exit! */
812 } else if (result==READ_OK) {
813 write(outfile,buffer,len);
816 fprintf(stderr,"scsitape:Read Error\n");
820 /* Now see if we have numbytes or numblocks: if so, we may wish to
826 return 0; /* we're only reading one block in var size mode! */
831 return 0; /* we're done. */
836 } /* got the goddam cancer of the curly braces. You can tell I used to
841 /* See parse_args for the scoop. parse_args does all. */
842 int main(int argc, char **argv) {
844 parse_args(argc,argv);
847 SCSI_CloseDevice(device,MediumChangerFD);