1 /* Copyright 2001 Enhanced Software Technologies Inc.
2 * Copyright 2007-2008 by Robert Nelson <robertn@the-nelsons.org>
3 * Released under terms of the GNU General Public License as
4 * required by the license on 'mtxl.c'.
5 * $Date: 2008-08-19 03:03:38 -0700 (Tue, 19 Aug 2008) $
9 /* This is a generic SCSI tape control program. It operates by
10 * directly sending commands to the tape drive. If you are going
11 * through your operating system's SCSI tape driver, do *NOT* use
12 * this program! If, on the other hand, you are using raw READ and WRITE
13 * commands through your operating system's generic SCSI interface (or
14 * through our built-in 'read' and 'write'), this is the place for you.
17 /*#define DEBUG_PARTITION */
22 setblk <n> -- set the block size to <n>
23 fsf <n> -- go forward by <n> filemarks
24 bsf <n> -- go backward by <n> filemarks
25 eod -- go to end of data
26 rewind -- rewind back to start of data
27 eject -- rewind, then eject the tape.
28 erase -- (short) erase the tape (we have no long erase)
29 mark <n> -- write <n> filemarks.
30 seek <n> -- seek to position <n>.
32 write <blksize> <-- write blocks from stdin to the tape
33 read [<blksize>] [<#blocks/#bytes>] -- read blocks from tape, write to stdout.
35 See the 'tapeinfo' program for status info about the tape drive.
50 #include <sys/types.h>
54 #include <sys/ioctl.h>
58 #include <sys/mtio.h> /* will try issuing some ioctls for Solaris, sigh. */
66 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");
69 #define arg1 (arg[0]) /* for backward compatibility, sigh */
70 static int arg[4]; /* the argument for the command, sigh. */
72 /* the device handle we're operating upon, sigh. */
73 static char *device; /* the text of the device thingy. */
74 static DEVICE_TYPE MediumChangerFD = (DEVICE_TYPE) -1;
78 static int S_setblk(void);
79 static int S_fsf(void);
80 static int S_bsf(void);
81 static int S_eod(void);
82 static int S_rewind(void);
83 static int S_eject(void);
84 static int S_mark(void);
85 static int S_seek(void);
86 static int S_reten(void);
87 static int S_erase(void);
89 static int S_read(void);
90 static int S_write(void);
93 struct command_table_struct {
98 { 1, "setblk", S_setblk },
102 { 0, "rewind", S_rewind },
103 { 0, "eject", S_eject },
104 { 0, "reten", S_reten },
105 { 0, "erase", S_erase },
106 { 1, "mark", S_mark },
107 { 1, "seek", S_seek },
108 { 2, "read", S_read },
109 { 2, "write",S_write },
110 { 0, NULL, NULL } /* terminate list */
116 /* open_device() -- set the 'fh' variable.... */
117 void open_device(void)
119 if (MediumChangerFD != -1)
121 SCSI_CloseDevice("Unknown",MediumChangerFD); /* close it, sigh... new device now! */
124 MediumChangerFD = SCSI_OpenDevice(device);
127 static int get_arg(char *arg)
131 if (*arg < '0' || *arg > '9')
133 return -1; /* sorry! */
141 /* we see if we've got a file open. If not, we open one :-(. Then
142 * we execute the actual command. Or not :-(.
144 int execute_command(struct command_table_struct *command)
146 /* if the device is not already open, then open it from the
149 if (!MediumChangerFD == -1)
151 /* try to get it from STAPE or TAPE environment variable... */
152 device = getenv("STAPE");
155 device = getenv("TAPE");
164 /* okay, now to execute the command... */
165 return command->command();
170 * Basically, we are parsing argv/argc. We can have multiple commands
171 * on a line now, such as "unload 3 0 load 4 0" to unload one tape and
172 * load in another tape into drive 0, and we execute these commands one
173 * at a time as we come to them. If we don't have a -f at the start, we
174 * barf. If we leave out a drive #, we default to drive 0 (the first drive
178 int parse_args(int argc, char **argv)
180 int i, cmd_tbl_idx,retval,arg_idx;
181 struct command_table_struct *command;
187 if (strcmp(argv[i],"-f") == 0)
195 open_device(); /* open the device and do a status scan on it... */
200 command = &command_table[0]; /* default to the first command... */
201 command = &command_table[cmd_tbl_idx];
202 while (command->name)
204 if (strcmp(command->name,argv[i]) == 0)
206 /* we have a match... */
209 /* otherwise we don't have a match... */
211 command = &command_table[cmd_tbl_idx];
213 /* if it's not a command, exit.... */
214 if (command->name == NULL)
218 i++; /* go to the next argument, if possible... */
219 /* see if we need to gather arguments, though! */
220 arg1 = -1; /* default it to something */
221 for (arg_idx=0;arg_idx < command->num_args ; arg_idx++)
225 arg[arg_idx] = get_arg(argv[i]);
226 if (arg[arg_idx] != -1)
228 i++; /* increment i over the next cmd. */
233 arg[arg_idx] = 0; /* default to 0 setmarks or whatever */
236 retval=execute_command(command); /* execute_command handles 'stuff' */
240 return 0; /* should never get here */
243 /* For Linux, this allows us to do a short erase on a tape (sigh!).
244 * Note that you'll need to do a 'mt status' on the tape afterwards in
245 * order to get the tape driver in sync with the tape drive again. Also
246 * note that on other OS's, this might do other evil things to the tape
247 * driver. Note that to do an erase, you must first rewind!
250 static int S_erase(void)
253 RequestSense_T *RequestSense;
258 return retval; /* we have an exit status :-(. */
261 RequestSense=Erase(MediumChangerFD);
264 PrintRequestSense(RequestSense);
265 exit(1); /* exit with an error status. */
270 /* This should eject a tape or magazine, depending upon the device sent
273 static int S_eject(void)
276 i = LoadUnload(MediumChangerFD, 0);
279 fprintf(stderr,"scsitape:eject failed\n");
287 /* We write a filemarks of 0 before going to grab position, in order
288 * to insure that data in the buffer is not a problem.
291 static int S_mark(void)
293 RequestSense_T RequestSense; /* for result of ReadElementStatus */
295 unsigned char buffer[6];
296 int count = arg1; /* voila! */
298 CDB[0] = 0x10; /* SET_MARK */
300 CDB[2] = (unsigned char)(count >> 16);
301 CDB[3] = (unsigned char)(count >> 8);
302 CDB[4] = (unsigned char)count;
305 /* we really don't care if this command works or not, sigh. */
306 slow_bzero((char *)&RequestSense, sizeof(RequestSense_T));
308 if (SCSI_ExecuteCommand(MediumChangerFD, Input, &CDB, 6, buffer, 0, &RequestSense)!= 0)
310 PrintRequestSense(&RequestSense);
315 /* let's rewind to bod!
318 static int S_rewind(void)
320 RequestSense_T sense;
322 unsigned char buffer[6];
324 CDB[0] = 0x01; /* REWIND */
331 /* we really don't care if this command works or not, sigh. */
332 slow_bzero((char *)&sense,sizeof(RequestSense_T));
333 if (SCSI_ExecuteCommand(MediumChangerFD,Input,&CDB,6,buffer,0,&sense)!=0)
335 PrintRequestSense(&sense);
344 /* This is used for fsf and bsf. */
345 static int Space(int count, char code)
347 RequestSense_T sense;
349 unsigned char buffer[6];
351 CDB[0] = 0x11; /* SET_MARK */
353 CDB[2] = (unsigned char)(count >> 16);
354 CDB[3] = (unsigned char)(count >> 8);
355 CDB[4] = (unsigned char)count;
358 /* we really don't care if this command works or not, sigh. */
359 slow_bzero((char *)&sense,sizeof(RequestSense_T));
360 if (SCSI_ExecuteCommand(MediumChangerFD, Input, &CDB, 6, buffer, 0, &sense) != 0)
362 PrintRequestSense(&sense);
369 /* Let's try a fsf: */
370 /* We write a filemarks of 0 before going to grab position, in order
371 * to insure that data in the buffer is not a problem.
374 static int S_fsf(void)
376 return Space(arg1,1); /* go forward! */
379 static int S_bsf(void)
381 return Space(-arg1,1); /* go backward! */
384 static int S_eod(void)
386 return Space(0,3); /* go to eod! */
389 /* sigh, abuse of the LOAD command...
392 static int S_reten(void)
394 RequestSense_T sense;
396 unsigned char buffer[6];
398 CDB[0] = 0x1B; /* START_STOP */
399 CDB[1] = 0; /* wait */
402 CDB[4] = 3; /* reten. */
405 /* we really don't care if this command works or not, sigh. */
406 slow_bzero((char *)&sense, sizeof(RequestSense_T));
407 if (SCSI_ExecuteCommand(MediumChangerFD, Input, &CDB, 6, buffer, 0, &sense) != 0)
409 PrintRequestSense(&sense);
415 /* seek a position on the tape (sigh!) */
416 static int S_seek(void)
418 RequestSense_T sense;
420 unsigned char buffer[6];
423 /* printf("count=%d\n",arg1); */
425 CDB[0] = 0x2B; /* LOCATE */
426 CDB[1] = 0; /* Logical */
427 CDB[2] = 0; /* padding */
428 CDB[3] = (unsigned char)(count >> 24);
429 CDB[4] = (unsigned char)(count >> 16);
430 CDB[5] = (unsigned char)(count >> 8);
431 CDB[6] = (unsigned char)count;
436 /* we really don't care if this command works or not, sigh. */
437 slow_bzero((char *)&sense,sizeof(RequestSense_T));
438 if (SCSI_ExecuteCommand(MediumChangerFD, Input, &CDB, 10, buffer, 0, &sense) != 0)
440 PrintRequestSense(&sense);
447 static int Solaris_setblk(int fh,int count)
449 /* we get here only if we have a MTSRSZ, which means Solaris. */
450 struct mtop mt_com; /* the struct used for the MTIOCTOP ioctl */
453 /* okay, we have fh and count.... */
455 /* Now to try the ioctl: */
457 mt_com.mt_count=count;
459 /* surround the actual ioctl to enable threading, since fsf/etc. can be
460 * big time consumers and we want other threads to be able to run too.
463 result=ioctl(fh, MTIOCTOP, (char *)&mt_com);
470 /* okay, we did okay. Return a value of None... */
476 /* okay, this is a write: we need to set the block size to something: */
477 static int S_setblk(void)
479 RequestSense_T sense;
482 unsigned int count = (unsigned int) arg1;
484 CDB[0] = 0x15; /* MODE SELECT */
485 CDB[1] = 0x10; /* scsi2 */
488 CDB[4] = 12; /* length of data */
491 slow_bzero((char *)&sense, sizeof(RequestSense_T));
492 slow_bzero(buffer, 12);
494 /* Now to set the mode page header: */
497 buffer[2] = 0x10; /* we are in buffered mode now, people! */
498 buffer[3] = 8; /* block descriptor length. */
499 buffer[4] = 0; /* reset to default density, sigh. */ /* 0 */
500 buffer[5] = 0; /* 1 */
501 buffer[6] = 0; /* 2 */
502 buffer[7] = 0; /* 3 */
503 buffer[8] = 0; /* 4 */
504 buffer[9] = (unsigned char)(count >> 16); /* 5 */
505 buffer[10] = (unsigned char)(count >> 8); /* 6 */
506 buffer[11] = (unsigned char)count; /* 7 */
508 if (SCSI_ExecuteCommand(MediumChangerFD,Output,&CDB,6,buffer,12,&sense)!=0)
510 PrintRequestSense(&sense);
514 /* Solaris_setblk(MediumChangerFD,count); */
520 /*************************************************************************/
521 /* SCSI read/write calls. These are mostly pulled out of BRU 16.1,
522 * modified to work within the mtxl.h framework rather than the
523 * scsi_lowlevel.h framework.
524 *************************************************************************/
526 #define MAX_READ_SIZE 128*1024 /* max size of a variable-block read */
529 #define READ_FILEMARK 1
533 #define READ_ERROR 255
536 #define WRITE_ERROR 1
541 /* These are copied out of BRU 16.1, with all the boolean masks changed
544 #define S_NO_SENSE(s) ((s).SenseKey == 0x0)
545 #define S_RECOVERED_ERROR(s) ((s).SenseKey == 0x1)
547 #define S_NOT_READY(s) ((s).SenseKey == 0x2)
548 #define S_MEDIUM_ERROR(s) ((s).SenseKey == 0x3)
549 #define S_HARDWARE_ERROR(s) ((s).SenseKey == 0x4)
550 #define S_UNIT_ATTENTION(s) ((s).SenseKey == 0x6)
551 #define S_BLANK_CHECK(s) ((s).SenseKey == 0x8)
552 #define S_VOLUME_OVERFLOW(s) ((s).SenseKey == 0xd)
554 #define DEFAULT_TIMEOUT 3*60 /* 3 minutes here */
556 #define HIT_FILEMARK(s) (S_NO_SENSE((s)) && (s).Filemark && (s).Valid)
557 /* Sigh, the T-10 SSC spec says all of the following is needed to
558 * detect a short read while in variable block mode. We'll see.
560 #define SHORT_READ(s) (S_NO_SENSE((s)) && (s).ILI && (s).Valid && (s).AdditionalSenseCode==0 && (s).AdditionalSenseCodeQualifier==0)
562 #define HIT_EOD(s) (S_BLANK_CHECK((s)) && (s).Valid)
563 #define HIT_EOP(s) (S_MEDIUM_ERROR((s)) && (s).EOM && (s).Valid)
564 #define HIT_EOM(s) ((s).EOM && (s).Valid)
565 #define BECOMING_READY(s) (S_UNIT_ATTENTION((s)) && (s).AdditionalSenseCode == 0x28 && (s).AdditionalSenseCodeQualifier == 0)
567 /* Reading is a problem. We can hit a filemark, hit an EOD, or hit an
568 * EOP. Our caller may do something about that. Note that we assume that
569 * our caller has already put us into fixed block mode. If he has not, then
570 * we are in trouble anyhow.
572 int SCSI_readt(DEVICE_TYPE fd, char * buf, unsigned int bufsize, unsigned int *len, unsigned int timeout) {
579 RequestSense_T RequestSense;
583 /* we are in variable block mode */
584 blockCount=MAX_READ_SIZE; /* variable block size. */
588 blockCount= *len / bufsize;
589 if ((*len % bufsize) != 0)
591 fprintf(stderr,"Error: Data (%d bytes) not even multiple of block size (%d bytes).\n",*len,bufsize);
592 exit(1); /* we're finished, sigh. */
598 timeout = 1 * 60; /* 1 minutes */
601 memset(&cmd, 0, sizeof(CDB_T));
602 cmd[0] = 0x08; /* READ */
603 cmd[1] = (bufsize) ? 1 : 0; /* fixed length or var length blocks */
604 cmd[2] = (unsigned char)(blockCount >> 16); /* MSB */
605 cmd[3] = (unsigned char)(blockCount >> 8);
606 cmd[4] = (unsigned char)blockCount; /* LSB */
608 /* okay, let's read, look @ the result code: */
610 if (SCSI_ExecuteCommand(fd,Input,&cmd,6,buf,(bufsize) ? *len : MAX_READ_SIZE,&RequestSense))
613 if (HIT_EOP(RequestSense))
619 if (HIT_FILEMARK(RequestSense))
621 rtnval=READ_FILEMARK;
624 if (HIT_EOD(RequestSense))
629 if ( (bufsize==0) && SHORT_READ(RequestSense))
631 rtnval=READ_SHORT; /* we only do short reads for variable block mode */
634 if (rtnval != READ_ERROR)
636 /* info contains number of blocks or bytes *not* read. May be
637 negative if the block we were trying to read was too big. So
638 we will have to account for that and set it to zero if so, so that
639 we return the proper # of blocks read.
641 info = ((RequestSense.Information[0] << 24) +
642 (RequestSense.Information[1] << 16) +
643 (RequestSense.Information[2] << 8) +
644 RequestSense.Information[3]);
646 /* on 64-bit platforms, we may need to turn 'info' into a negative # */
647 if (info > 0x7fffffff)
651 info=0; /* make sure we don't return too big len read. */
653 /* Now set *len to # of bytes read. */
654 *len= bufsize ? (blockCount-info) * bufsize : MAX_READ_SIZE-info ;
658 PrintRequestSense(&RequestSense);
666 /* Low level SCSI write. Modified from BRU 16.1, with much BRU smarts
667 * taken out and with the various types changed to mtx types rather than
670 int SCSI_write(DEVICE_TYPE fd, char * buf, unsigned int blocksize,
677 RequestSense_T RequestSense;
681 /* we are in variable block mode */
682 blockCount = *len; /* variable block size. */
686 blockCount= *len / blocksize ;
687 if ((*len % blocksize) != 0)
689 fprintf(stderr,"Error: Data (%d bytes) not even multiple of block size (%d bytes).\n",*len,blocksize);
690 exit(1); /* we're finished, sigh. */
694 fprintf(stderr,"Writing %d blocks\n",blockCount);
696 memset(&cmd, 0, sizeof(CDB_T));
697 cmd[0] = 0x0a; /* WRITE */
698 cmd[1] = (blocksize) ? 1 : 0; /* fixed length or var length blocks */
699 cmd[2] = (unsigned char)(blockCount >> 16); /* MSB */
700 cmd[3] = (unsigned char)(blockCount >> 8);
701 cmd[4] = (unsigned char)blockCount; /* LSB */
704 if (SCSI_ExecuteCommand(fd,Output,&cmd,6,buf, *len, &RequestSense))
706 if (HIT_EOM(RequestSense))
708 /* we hit end of media. Return -1. */
709 if (S_VOLUME_OVERFLOW(RequestSense))
713 exit(WRITE_EOM); /* end of media! */
717 /* it was plain old write error: */
718 PrintRequestSense(&RequestSense);
724 rtnval = *len; /* worked! */
729 /* S_write is not implemented yet! */
730 static int S_write(void)
732 char *buffer; /* the buffer we're gonna read/write out of. */
734 int len; /* the length of the data in the buffer */
735 int blocksize = arg[0];
736 int numblocks = arg[1];
737 int varsize=0; /* variable size block flag */
740 int infile=fileno(stdin); /* sigh */
745 buffersize = MAX_READ_SIZE;
750 varsize = 0; /* fixed block mode */
751 buffersize = blocksize;
755 /* sigh, make it oversized just to have some */
756 buffer = malloc(buffersize+8);
761 /* size_t could be 64 bit on a 32 bit platform, so do casts. */
763 /* If it is a pipe, we could read 4096 bytes rather than the full
764 * 128K bytes or whatever, so we must gather multiple reads into
767 while (len < buffersize)
769 result=(int)read(infile, buffer + len, (size_t)(buffersize - len));
775 /* if we have no deata in our buffer, exit */
776 return 0; /* we're at end of file! */
778 break; /* otherwise, break and write the data */
780 len += result; /* add the result input to our length. */
783 result = SCSI_write(MediumChangerFD, buffer, blocksize, (unsigned int *)&len);
786 return 1; /* at end of tape! */
789 /* Now see if we have numbytes or numblocks. If so, we may wish to exit
797 return 0; /* we will only write one block in variable size mode :-( */
807 return 0; /* we're done. */
816 /* Okay, the read thingy: */
818 /* We have a device opened (we hope!) by the parser.
819 * we will have arg[0] and arg[1] being the blocksize and # of blocks
824 static int S_read(void)
826 char *buffer; /* the buffer we're going to be reading out of */
828 unsigned int len; /* the length of the data in the buffer */
829 int blocksize = arg[0];
830 int numblocks = arg[1];
831 int varsize = 0; /* variable size block flag. */
835 int outfile=fileno(stdout); /* sigh. */
840 buffersize=MAX_READ_SIZE;
845 varsize=0; /* fixed block mode */
846 buffersize=blocksize;
850 /* sigh, make it oversized just to have some */
851 buffer = malloc(buffersize + 8);
857 /* it could have gotten reset by prior short read... */
860 result=SCSI_readt(MediumChangerFD,buffer,blocksize, &len, DEFAULT_TIMEOUT);
862 if (result==READ_FILEMARK || result==READ_EOD || result==READ_EOP)
864 /* okay, normal end of file? */
867 write(outfile,buffer,len);
870 #ifdef NEED_TO_GO_PAST_FILEMARK
871 /* Now, let's try to go past the filemark if that's what we hit: */
872 if (result==READ_FILEMARK)
874 arg1 = 1; /* arg for S_fsf. */
875 S_fsf(); /* and go forward 1 filemark, we hope! */
878 return 0; /* hit normal end of file. */
880 else if (result == READ_SHORT)
882 /* short reads are only valid in variable block mode. */
887 write(outfile,buffer,len);
892 fprintf(stderr,"scsitape:Short Read encountered on input. Aborting.\n");
894 exit(1); /* error exit! */
897 else if (result == READ_OK)
899 write(outfile,buffer,len);
903 fprintf(stderr,"scsitape:Read Error\n");
908 /* Now see if we have numbytes or numblocks: if so, we may wish to
916 return 0; /* we're only reading one block in var size mode! */
924 return 0; /* we're done. */
931 /* See parse_args for the scoop. parse_args does all. */
932 int main(int argc, char **argv)
935 parse_args(argc, argv);
938 SCSI_CloseDevice(device,MediumChangerFD);