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: 2007-03-24 18:14:01 -0700 (Sat, 24 Mar 2007) $
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.
49 #include <sys/types.h>
53 #include <sys/ioctl.h>
57 #include <sys/mtio.h> /* will try issuing some ioctls for Solaris, sigh. */
65 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");
68 #define arg1 (arg[0]) /* for backward compatibility, sigh */
69 static int arg[4]; /* the argument for the command, sigh. */
71 /* the device handle we're operating upon, sigh. */
72 static char *device; /* the text of the device thingy. */
73 static DEVICE_TYPE MediumChangerFD = (DEVICE_TYPE) -1;
77 static int S_setblk(void);
78 static int S_fsf(void);
79 static int S_bsf(void);
80 static int S_eod(void);
81 static int S_rewind(void);
82 static int S_eject(void);
83 static int S_mark(void);
84 static int S_seek(void);
85 static int S_reten(void);
86 static int S_erase(void);
88 static int S_read(void);
89 static int S_write(void);
92 struct command_table_struct {
97 { 1, "setblk", S_setblk },
101 { 0, "rewind", S_rewind },
102 { 0, "eject", S_eject },
103 { 0, "reten", S_reten },
104 { 0, "erase", S_erase },
105 { 1, "mark", S_mark },
106 { 1, "seek", S_seek },
107 { 2, "read", S_read },
108 { 2, "write",S_write },
109 { 0, NULL, NULL } /* terminate list */
115 /* open_device() -- set the 'fh' variable.... */
116 void open_device(void)
118 if (MediumChangerFD != -1)
120 SCSI_CloseDevice("Unknown",MediumChangerFD); /* close it, sigh... new device now! */
123 MediumChangerFD = SCSI_OpenDevice(device);
126 static int get_arg(char *arg)
130 if (*arg < '0' || *arg > '9')
132 return -1; /* sorry! */
140 /* we see if we've got a file open. If not, we open one :-(. Then
141 * we execute the actual command. Or not :-(.
143 int execute_command(struct command_table_struct *command)
145 /* if the device is not already open, then open it from the
148 if (!MediumChangerFD == -1)
150 /* try to get it from STAPE or TAPE environment variable... */
151 device = getenv("STAPE");
154 device = getenv("TAPE");
163 /* okay, now to execute the command... */
164 return command->command();
169 * Basically, we are parsing argv/argc. We can have multiple commands
170 * on a line now, such as "unload 3 0 load 4 0" to unload one tape and
171 * load in another tape into drive 0, and we execute these commands one
172 * at a time as we come to them. If we don't have a -f at the start, we
173 * barf. If we leave out a drive #, we default to drive 0 (the first drive
177 int parse_args(int argc, char **argv)
179 int i, cmd_tbl_idx,retval,arg_idx;
180 struct command_table_struct *command;
186 if (strcmp(argv[i],"-f") == 0)
194 open_device(); /* open the device and do a status scan on it... */
199 command = &command_table[0]; /* default to the first command... */
200 command = &command_table[cmd_tbl_idx];
201 while (command->name)
203 if (strcmp(command->name,argv[i]) == 0)
205 /* we have a match... */
208 /* otherwise we don't have a match... */
210 command = &command_table[cmd_tbl_idx];
212 /* if it's not a command, exit.... */
213 if (command->name == NULL)
217 i++; /* go to the next argument, if possible... */
218 /* see if we need to gather arguments, though! */
219 arg1 = -1; /* default it to something */
220 for (arg_idx=0;arg_idx < command->num_args ; arg_idx++)
224 arg[arg_idx] = get_arg(argv[i]);
225 if (arg[arg_idx] != -1)
227 i++; /* increment i over the next cmd. */
232 arg[arg_idx] = 0; /* default to 0 setmarks or whatever */
235 retval=execute_command(command); /* execute_command handles 'stuff' */
239 return 0; /* should never get here */
242 /* For Linux, this allows us to do a short erase on a tape (sigh!).
243 * Note that you'll need to do a 'mt status' on the tape afterwards in
244 * order to get the tape driver in sync with the tape drive again. Also
245 * note that on other OS's, this might do other evil things to the tape
246 * driver. Note that to do an erase, you must first rewind!
249 static int S_erase(void)
252 RequestSense_T *RequestSense;
257 return retval; /* we have an exit status :-(. */
260 RequestSense=Erase(MediumChangerFD);
263 PrintRequestSense(RequestSense);
264 exit(1); /* exit with an error status. */
269 /* This should eject a tape or magazine, depending upon the device sent
272 static int S_eject(void)
275 i = LoadUnload(MediumChangerFD, 0);
278 fprintf(stderr,"scsitape:eject failed\n");
286 /* We write a filemarks of 0 before going to grab position, in order
287 * to insure that data in the buffer is not a problem.
290 static int S_mark(void)
292 RequestSense_T RequestSense; /* for result of ReadElementStatus */
294 unsigned char buffer[6];
295 int count = arg1; /* voila! */
297 CDB[0] = 0x10; /* SET_MARK */
299 CDB[2] = (unsigned char)(count >> 16);
300 CDB[3] = (unsigned char)(count >> 8);
301 CDB[4] = (unsigned char)count;
304 /* we really don't care if this command works or not, sigh. */
305 slow_bzero((char *)&RequestSense, sizeof(RequestSense_T));
307 if (SCSI_ExecuteCommand(MediumChangerFD, Input, &CDB, 6, buffer, 0, &RequestSense)!= 0)
309 PrintRequestSense(&RequestSense);
314 /* let's rewind to bod!
317 static int S_rewind(void)
319 RequestSense_T sense;
321 unsigned char buffer[6];
323 CDB[0] = 0x01; /* REWIND */
330 /* we really don't care if this command works or not, sigh. */
331 slow_bzero((char *)&sense,sizeof(RequestSense_T));
332 if (SCSI_ExecuteCommand(MediumChangerFD,Input,&CDB,6,buffer,0,&sense)!=0)
334 PrintRequestSense(&sense);
343 /* This is used for fsf and bsf. */
344 static int Space(int count, char code)
346 RequestSense_T sense;
348 unsigned char buffer[6];
350 CDB[0] = 0x11; /* SET_MARK */
352 CDB[2] = (unsigned char)(count >> 16);
353 CDB[3] = (unsigned char)(count >> 8);
354 CDB[4] = (unsigned char)count;
357 /* we really don't care if this command works or not, sigh. */
358 slow_bzero((char *)&sense,sizeof(RequestSense_T));
359 if (SCSI_ExecuteCommand(MediumChangerFD, Input, &CDB, 6, buffer, 0, &sense) != 0)
361 PrintRequestSense(&sense);
368 /* Let's try a fsf: */
369 /* We write a filemarks of 0 before going to grab position, in order
370 * to insure that data in the buffer is not a problem.
373 static int S_fsf(void)
375 return Space(arg1,1); /* go forward! */
378 static int S_bsf(void)
380 return Space(-arg1,1); /* go backward! */
383 static int S_eod(void)
385 return Space(0,3); /* go to eod! */
388 /* sigh, abuse of the LOAD command...
391 static int S_reten(void)
393 RequestSense_T sense;
395 unsigned char buffer[6];
397 CDB[0] = 0x1B; /* START_STOP */
398 CDB[1] = 0; /* wait */
401 CDB[4] = 3; /* reten. */
404 /* we really don't care if this command works or not, sigh. */
405 slow_bzero((char *)&sense, sizeof(RequestSense_T));
406 if (SCSI_ExecuteCommand(MediumChangerFD, Input, &CDB, 6, buffer, 0, &sense) != 0)
408 PrintRequestSense(&sense);
414 /* seek a position on the tape (sigh!) */
415 static int S_seek(void)
417 RequestSense_T sense;
419 unsigned char buffer[6];
422 /* printf("count=%d\n",arg1); */
424 CDB[0] = 0x2B; /* LOCATE */
425 CDB[1] = 0; /* Logical */
426 CDB[2] = 0; /* padding */
427 CDB[3] = (unsigned char)(count >> 24);
428 CDB[4] = (unsigned char)(count >> 16);
429 CDB[5] = (unsigned char)(count >> 8);
430 CDB[6] = (unsigned char)count;
435 /* we really don't care if this command works or not, sigh. */
436 slow_bzero((char *)&sense,sizeof(RequestSense_T));
437 if (SCSI_ExecuteCommand(MediumChangerFD, Input, &CDB, 10, buffer, 0, &sense) != 0)
439 PrintRequestSense(&sense);
446 static int Solaris_setblk(int fh,int count)
448 /* we get here only if we have a MTSRSZ, which means Solaris. */
449 struct mtop mt_com; /* the struct used for the MTIOCTOP ioctl */
452 /* okay, we have fh and count.... */
454 /* Now to try the ioctl: */
456 mt_com.mt_count=count;
458 /* surround the actual ioctl to enable threading, since fsf/etc. can be
459 * big time consumers and we want other threads to be able to run too.
462 result=ioctl(fh, MTIOCTOP, (char *)&mt_com);
469 /* okay, we did okay. Return a value of None... */
475 /* okay, this is a write: we need to set the block size to something: */
476 static int S_setblk(void)
478 RequestSense_T sense;
481 unsigned int count = (unsigned int) arg1;
483 CDB[0] = 0x15; /* MODE SELECT */
484 CDB[1] = 0x10; /* scsi2 */
487 CDB[4] = 12; /* length of data */
490 slow_bzero((char *)&sense, sizeof(RequestSense_T));
491 slow_bzero(buffer, 12);
493 /* Now to set the mode page header: */
496 buffer[2] = 0x10; /* we are in buffered mode now, people! */
497 buffer[3] = 8; /* block descriptor length. */
498 buffer[4] = 0; /* reset to default density, sigh. */ /* 0 */
499 buffer[5] = 0; /* 1 */
500 buffer[6] = 0; /* 2 */
501 buffer[7] = 0; /* 3 */
502 buffer[8] = 0; /* 4 */
503 buffer[9] = (unsigned char)(count >> 16); /* 5 */
504 buffer[10] = (unsigned char)(count >> 8); /* 6 */
505 buffer[11] = (unsigned char)count; /* 7 */
507 if (SCSI_ExecuteCommand(MediumChangerFD,Output,&CDB,6,buffer,12,&sense)!=0)
509 PrintRequestSense(&sense);
513 /* Solaris_setblk(MediumChangerFD,count); */
519 /*************************************************************************/
520 /* SCSI read/write calls. These are mostly pulled out of BRU 16.1,
521 * modified to work within the mtxl.h framework rather than the
522 * scsi_lowlevel.h framework.
523 *************************************************************************/
525 #define MAX_READ_SIZE 128*1024 /* max size of a variable-block read */
528 #define READ_FILEMARK 1
532 #define READ_ERROR 255
535 #define WRITE_ERROR 1
540 /* These are copied out of BRU 16.1, with all the boolean masks changed
543 #define S_NO_SENSE(s) ((s).SenseKey == 0x0)
544 #define S_RECOVERED_ERROR(s) ((s).SenseKey == 0x1)
546 #define S_NOT_READY(s) ((s).SenseKey == 0x2)
547 #define S_MEDIUM_ERROR(s) ((s).SenseKey == 0x3)
548 #define S_HARDWARE_ERROR(s) ((s).SenseKey == 0x4)
549 #define S_UNIT_ATTENTION(s) ((s).SenseKey == 0x6)
550 #define S_BLANK_CHECK(s) ((s).SenseKey == 0x8)
551 #define S_VOLUME_OVERFLOW(s) ((s).SenseKey == 0xd)
553 #define DEFAULT_TIMEOUT 3*60 /* 3 minutes here */
555 #define HIT_FILEMARK(s) (S_NO_SENSE((s)) && (s).Filemark && (s).Valid)
556 /* Sigh, the T-10 SSC spec says all of the following is needed to
557 * detect a short read while in variable block mode. We'll see.
559 #define SHORT_READ(s) (S_NO_SENSE((s)) && (s).ILI && (s).Valid && (s).AdditionalSenseCode==0 && (s).AdditionalSenseCodeQualifier==0)
561 #define HIT_EOD(s) (S_BLANK_CHECK((s)) && (s).Valid)
562 #define HIT_EOP(s) (S_MEDIUM_ERROR((s)) && (s).EOM && (s).Valid)
563 #define HIT_EOM(s) ((s).EOM && (s).Valid)
564 #define BECOMING_READY(s) (S_UNIT_ATTENTION((s)) && (s).AdditionalSenseCode == 0x28 && (s).AdditionalSenseCodeQualifier == 0)
566 /* Reading is a problem. We can hit a filemark, hit an EOD, or hit an
567 * EOP. Our caller may do something about that. Note that we assume that
568 * our caller has already put us into fixed block mode. If he has not, then
569 * we are in trouble anyhow.
571 int SCSI_readt(DEVICE_TYPE fd, char * buf, unsigned int bufsize, unsigned int *len, unsigned int timeout) {
578 RequestSense_T RequestSense;
582 /* we are in variable block mode */
583 blockCount=MAX_READ_SIZE; /* variable block size. */
587 blockCount= *len / bufsize;
588 if ((*len % bufsize) != 0)
590 fprintf(stderr,"Error: Data (%d bytes) not even multiple of block size (%d bytes).\n",*len,bufsize);
591 exit(1); /* we're finished, sigh. */
597 timeout = 1 * 60; /* 1 minutes */
600 memset(&cmd, 0, sizeof(CDB_T));
601 cmd[0] = 0x08; /* READ */
602 cmd[1] = (bufsize) ? 1 : 0; /* fixed length or var length blocks */
603 cmd[2] = (unsigned char)(blockCount >> 16); /* MSB */
604 cmd[3] = (unsigned char)(blockCount >> 8);
605 cmd[4] = (unsigned char)blockCount; /* LSB */
607 /* okay, let's read, look @ the result code: */
609 if (SCSI_ExecuteCommand(fd,Input,&cmd,6,buf,(bufsize) ? *len : MAX_READ_SIZE,&RequestSense))
612 if (HIT_EOP(RequestSense))
618 if (HIT_FILEMARK(RequestSense))
620 rtnval=READ_FILEMARK;
623 if (HIT_EOD(RequestSense))
628 if ( (bufsize==0) && SHORT_READ(RequestSense))
630 rtnval=READ_SHORT; /* we only do short reads for variable block mode */
633 if (rtnval != READ_ERROR)
635 /* info contains number of blocks or bytes *not* read. May be
636 negative if the block we were trying to read was too big. So
637 we will have to account for that and set it to zero if so, so that
638 we return the proper # of blocks read.
640 info = ((RequestSense.Information[0] << 24) +
641 (RequestSense.Information[1] << 16) +
642 (RequestSense.Information[2] << 8) +
643 RequestSense.Information[3]);
645 /* on 64-bit platforms, we may need to turn 'info' into a negative # */
646 if (info > 0x7fffffff)
650 info=0; /* make sure we don't return too big len read. */
652 /* Now set *len to # of bytes read. */
653 *len= bufsize ? (blockCount-info) * bufsize : MAX_READ_SIZE-info ;
657 PrintRequestSense(&RequestSense);
665 /* Low level SCSI write. Modified from BRU 16.1, with much BRU smarts
666 * taken out and with the various types changed to mtx types rather than
669 int SCSI_write(DEVICE_TYPE fd, char * buf, unsigned int blocksize,
676 RequestSense_T RequestSense;
680 /* we are in variable block mode */
681 blockCount = *len; /* variable block size. */
685 blockCount= *len / blocksize ;
686 if ((*len % blocksize) != 0)
688 fprintf(stderr,"Error: Data (%d bytes) not even multiple of block size (%d bytes).\n",*len,blocksize);
689 exit(1); /* we're finished, sigh. */
693 fprintf(stderr,"Writing %d blocks\n",blockCount);
695 memset(&cmd, 0, sizeof(CDB_T));
696 cmd[0] = 0x0a; /* WRITE */
697 cmd[1] = (blocksize) ? 1 : 0; /* fixed length or var length blocks */
698 cmd[2] = (unsigned char)(blockCount >> 16); /* MSB */
699 cmd[3] = (unsigned char)(blockCount >> 8);
700 cmd[4] = (unsigned char)blockCount; /* LSB */
703 if (SCSI_ExecuteCommand(fd,Output,&cmd,6,buf, *len, &RequestSense))
705 if (HIT_EOM(RequestSense))
707 /* we hit end of media. Return -1. */
708 if (S_VOLUME_OVERFLOW(RequestSense))
712 exit(WRITE_EOM); /* end of media! */
716 /* it was plain old write error: */
717 PrintRequestSense(&RequestSense);
723 rtnval = *len; /* worked! */
728 /* S_write is not implemented yet! */
729 static int S_write(void)
731 char *buffer; /* the buffer we're gonna read/write out of. */
733 int len; /* the length of the data in the buffer */
734 int blocksize = arg[0];
735 int numblocks = arg[1];
736 int varsize=0; /* variable size block flag */
739 int infile=fileno(stdin); /* sigh */
744 buffersize = MAX_READ_SIZE;
749 varsize = 0; /* fixed block mode */
750 buffersize = blocksize;
754 /* sigh, make it oversized just to have some */
755 buffer = malloc(buffersize+8);
760 /* size_t could be 64 bit on a 32 bit platform, so do casts. */
762 /* If it is a pipe, we could read 4096 bytes rather than the full
763 * 128K bytes or whatever, so we must gather multiple reads into
766 while (len < buffersize)
768 result=(int)read(infile, buffer + len, (size_t)(buffersize - len));
774 /* if we have no deata in our buffer, exit */
775 return 0; /* we're at end of file! */
777 break; /* otherwise, break and write the data */
779 len += result; /* add the result input to our length. */
782 result = SCSI_write(MediumChangerFD, buffer, blocksize, (unsigned int *)&len);
785 return 1; /* at end of tape! */
788 /* Now see if we have numbytes or numblocks. If so, we may wish to exit
796 return 0; /* we will only write one block in variable size mode :-( */
806 return 0; /* we're done. */
815 /* Okay, the read thingy: */
817 /* We have a device opened (we hope!) by the parser.
818 * we will have arg[0] and arg[1] being the blocksize and # of blocks
823 static int S_read(void)
825 char *buffer; /* the buffer we're going to be reading out of */
827 unsigned int len; /* the length of the data in the buffer */
828 int blocksize = arg[0];
829 int numblocks = arg[1];
830 int varsize = 0; /* variable size block flag. */
834 int outfile=fileno(stdout); /* sigh. */
839 buffersize=MAX_READ_SIZE;
844 varsize=0; /* fixed block mode */
845 buffersize=blocksize;
849 /* sigh, make it oversized just to have some */
850 buffer = malloc(buffersize + 8);
856 /* it could have gotten reset by prior short read... */
859 result=SCSI_readt(MediumChangerFD,buffer,blocksize, &len, DEFAULT_TIMEOUT);
861 if (result==READ_FILEMARK || result==READ_EOD || result==READ_EOP)
863 /* okay, normal end of file? */
866 write(outfile,buffer,len);
869 #ifdef NEED_TO_GO_PAST_FILEMARK
870 /* Now, let's try to go past the filemark if that's what we hit: */
871 if (result==READ_FILEMARK)
873 arg1 = 1; /* arg for S_fsf. */
874 S_fsf(); /* and go forward 1 filemark, we hope! */
877 return 0; /* hit normal end of file. */
879 else if (result == READ_SHORT)
881 /* short reads are only valid in variable block mode. */
886 write(outfile,buffer,len);
891 fprintf(stderr,"scsitape:Short Read encountered on input. Aborting.\n");
893 exit(1); /* error exit! */
896 else if (result == READ_OK)
898 write(outfile,buffer,len);
902 fprintf(stderr,"scsitape:Read Error\n");
907 /* Now see if we have numbytes or numblocks: if so, we may wish to
915 return 0; /* we're only reading one block in var size mode! */
923 return 0; /* we're done. */
930 /* See parse_args for the scoop. parse_args does all. */
931 int main(int argc, char **argv)
934 parse_args(argc, argv);
937 SCSI_CloseDevice(device,MediumChangerFD);