Imported Upstream version 1.2.16rel
[debian/mtx] / scsitape.c
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 $
5  * $Revision: 1.1.1.1 $
6  */
7
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. 
14  */
15
16 /*#define DEBUG_PARTITION */
17 /*#define DEBUG 1 */
18
19 /* 
20    Commands:
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>.
30
31          write <blksize> <-- write blocks from stdin to the tape 
32          read  [<blksize>] [<#blocks/#bytes>] -- read blocks from tape, write to stdout. 
33
34    See the 'tapeinfo' program for status info about the tape drive.
35
36  */
37
38 #include <stdio.h>
39 #include <string.h>
40
41 #include "mtx.h"
42 #include "mtxl.h"
43
44 #include <unistd.h>
45 #include <sys/types.h>
46 #include <sys/ioctl.h>
47 #include <sys/mtio.h> /* will try issuing some ioctls for Solaris, sigh. */
48
49 void Usage(void) {
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");
51 }
52
53 #define arg1 (arg[0])  /* for backward compatibility, sigh */
54 static int arg[4];  /* the argument for the command, sigh. */
55
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;
59
60
61
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);
72
73 static int S_read(void);
74 static int S_write(void);
75
76
77 struct command_table_struct {
78   int num_args;
79   char *name;
80   int (*command)(void);
81 } command_table[] = {
82   { 1, "setblk", S_setblk },
83   { 1, "fsf", S_fsf },
84   { 1, "bsf", S_bsf },
85   { 0, "eod", S_eod },
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 }
94 };
95
96 char *argv0;
97
98 /* A table for printing out the peripheral device type as ASCII. */ 
99 static char *PeripheralDeviceType[32] = {
100   "Disk Drive",
101   "Tape Drive",
102   "Printer",
103   "Processor",
104   "Write-once",
105   "CD-ROM",
106   "Scanner",
107   "Optical",
108   "Medium Changer",
109   "Communications",
110   "ASC IT8",
111   "ASC IT8",
112   "RAID Array",
113   "Enclosure Services",
114   "OCR/W",
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 */
130   "Unknown"    /* 0x1f */
131 };
132
133
134
135 /* open_device() -- set the 'fh' variable.... */
136 void open_device(void) {
137
138   if (MediumChangerFD) {
139     SCSI_CloseDevice("Unknown",MediumChangerFD);  /* close it, sigh...  new device now! */
140   }
141
142   MediumChangerFD = SCSI_OpenDevice(device);
143
144 }
145
146 static int get_arg(char *arg) {
147   int retval=-1;
148
149   if (*arg < '0' || *arg > '9') {
150     return -1;  /* sorry! */
151   }
152
153   retval=atoi(arg);
154   return retval;
155 }
156
157
158 /* we see if we've got a file open. If not, we open one :-(. Then
159  * we execute the actual command. Or not :-(. 
160  */ 
161 int execute_command(struct command_table_struct *command) {
162
163   /* if the device is not already open, then open it from the 
164    * environment.
165    */
166   if (!MediumChangerFD) {
167     /* try to get it from STAPE or TAPE environment variable... */
168     device=getenv("STAPE");
169     if (device==NULL) {
170       device=getenv("TAPE");
171       if (device==NULL) {
172         Usage();
173       }
174     }
175     open_device();
176   }
177
178
179   /* okay, now to execute the command... */
180   return command->command();
181 }
182
183
184 /* parse_args():
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
190  * in the cabinet). 
191  */ 
192
193 int parse_args(int argc,char **argv) {
194   int i,cmd_tbl_idx,retval,arg_idx;
195   struct command_table_struct *command;
196
197   i=1;
198   arg_idx=0;
199   while (i<argc) {
200     if (strcmp(argv[i],"-f") == 0) {
201       i++;
202       if (i>=argc) {
203         Usage();
204       }
205       device=argv[i++];
206       open_device(); /* open the device and do a status scan on it... */
207     } else {
208       cmd_tbl_idx=0;
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... */
214           break;
215         }
216         /* otherwise we don't have a match... */
217         cmd_tbl_idx++;
218         command=&command_table[cmd_tbl_idx];
219       }
220       /* if it's not a command, exit.... */
221       if (!command->name) {
222         Usage();
223       }
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++) {
228         if (i < argc) {
229           arg[arg_idx]=get_arg(argv[i]);
230           if (arg[arg_idx] !=  -1) {
231             i++; /* increment i over the next cmd. */
232           }
233         } else {
234           arg[arg_idx]=0; /* default to 0 setmarks or whatever */
235         } 
236       }
237       retval=execute_command(command);  /* execute_command handles 'stuff' */
238       exit(retval);
239     }
240   }
241   return 0; /* should never get here */
242 }
243
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!
249
250  */
251 static int S_erase(void) {
252   int retval;
253   RequestSense_T *RequestSense;
254
255   retval=S_rewind();
256   if (retval) {
257     return retval;  /* we have an exit status :-(. */
258   } 
259
260   RequestSense=Erase(MediumChangerFD);
261   if (RequestSense) {
262     PrintRequestSense(RequestSense);
263     exit(1);  /* exit with an error status. */
264   }
265   return 0;
266 }
267
268 /* This should eject a tape or magazine, depending upon the device sent
269  * to.
270  */
271 static int S_eject(void)
272 {
273 int i;
274   i=Eject(MediumChangerFD);
275   if (i<0) {
276     fprintf(stderr,"scsitape:eject failed\n");
277     fflush(stderr);
278   }
279   return i;  /* if it failed, well, sigh.... */
280 }
281
282
283
284
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. 
287  */
288
289 static int S_mark(void) {
290   RequestSense_T RequestSense; /* for result of ReadElementStatus */
291   CDB_T CDB;
292   unsigned char buffer[6];
293   int count=arg1; /* voila! */
294
295   CDB[0]=0x10;  /* SET_MARK */
296   CDB[1]=0;
297   CDB[2]=(count >> 16) & 0xff;
298   CDB[3]=(count >>8) & 0xff;
299   CDB[4]=count & 0xff;
300   CDB[5]=0; 
301
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);
306     return 1;
307   }
308   return 0;
309 }
310 /* let's rewind to bod! 
311  */
312
313 static int S_rewind(void) {
314   RequestSense_T sense;
315   CDB_T CDB;
316   unsigned char buffer[6];
317
318   CDB[0]=0x01;  /* REWIND */
319   CDB[1]=0;
320   CDB[2]=0;
321   CDB[3]=0;
322   CDB[4]=0;
323   CDB[5]=0; 
324
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);
329     return 1;
330   }
331   return 0;
332 }
333
334
335
336
337 /* This is used for fsf and bsf. */
338 static int Space(int count,int code){
339   RequestSense_T sense;
340   CDB_T CDB;
341   unsigned char buffer[6];
342
343   CDB[0]=0x11;  /* SET_MARK */
344   CDB[1]=code;
345   CDB[2]=(count >> 16) & 0xff;
346   CDB[3]=(count >>8) & 0xff;
347   CDB[4]=count & 0xff;
348   CDB[5]=0; 
349
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);
354     return 1;
355   }
356   return 0;
357   
358 }
359
360
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. 
364  */
365
366 static int S_fsf(void) {
367   return Space(arg1,1); /* go forward! */
368 }
369
370 static int S_bsf(void) {
371   return Space(-arg1,1); /* go backward! */
372 }
373
374 static int S_eod(void) {
375   return Space(0,3); /* go to eod! */
376 }
377
378 /* sigh, abuse of the LOAD command...
379
380  */
381 static int S_reten(void) {
382   RequestSense_T sense;
383   CDB_T CDB;
384   unsigned char buffer[6];
385
386   CDB[0]=0x1b;  /* START_STOP */
387   CDB[1]=0; /* wait */
388   CDB[2]=0;
389   CDB[3]=0;
390   CDB[4]=3; /* reten. */ 
391   CDB[5]=0; 
392
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);
397     return 1;
398   }
399   return 0;
400
401 }
402
403 /* seek a position on the tape (sigh!) */
404 static int S_seek(void){
405   RequestSense_T sense;
406   CDB_T CDB;
407   unsigned char buffer[6];
408   int count =  arg1;
409
410   /* printf("count=%d\n",arg1); */
411
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;
418   CDB[6]=count & 0xff;
419   CDB[7]=0; 
420   CDB[8]=0; 
421   CDB[9]=0; 
422
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);
427     return 1;
428   }
429   return 0;
430 }
431
432 #ifdef MTSRSZ
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 */
436   int result;
437
438   /* okay, we have fh and count.... */
439   
440   /* Now to try the ioctl: */
441   mt_com.mt_op=MTSRSZ;
442   mt_com.mt_count=count;
443
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. 
446    */
447  
448   result=ioctl(fh, MTIOCTOP, (char *)&mt_com);
449
450   if (result < 0) {
451     return errno;
452   }
453
454   /* okay, we did okay. Return a value of None... */
455   return 0;
456 }
457 #endif  
458
459
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;
463   CDB_T CDB;
464   unsigned char buffer[12];
465   unsigned int count = (unsigned int) arg1;
466
467   
468   CDB[0]=0x15;  /* MODE SELECT */
469   CDB[1]=0x10;  /* scsi2 */
470   CDB[2]=0; 
471   CDB[3]=0;
472   CDB[4]=12; /* length of data */
473   CDB[5]=0;
474
475   slow_bzero((unsigned char *)&sense,sizeof(RequestSense_T));
476   slow_bzero(buffer,12);
477
478   /* Now to set the mode page header: */
479   buffer[0]=0;
480   buffer[1]=0;
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 */
484   buffer[5]=0; /* 1 */
485   buffer[6]=0; /* 2 */
486   buffer[7]=0; /* 3 */
487   buffer[8]=0; /* 4 */
488   buffer[9]=(count >> 16) & 0xff; /* 5 */
489   buffer[10]=(count >> 8) & 0xff; /* 6 */
490   buffer[11]= count & 0xff;  /* 7 */
491      
492   if (SCSI_ExecuteCommand(MediumChangerFD,Output,&CDB,6,buffer,12,&sense)!=0){
493     PrintRequestSense(&sense);
494     return 1;
495   }
496 #ifdef MTSRSZ
497   /*   Solaris_setblk(MediumChangerFD,count);   */
498 #endif
499
500   return 0;
501 }
502
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  *************************************************************************/ 
508
509 #define MAX_READ_SIZE 128*1024  /* max size of a variable-block read */
510
511 #define READ_OK 0
512 #define READ_FILEMARK 1
513 #define READ_EOD 2
514 #define READ_EOP 3
515 #define READ_SHORT 5
516 #define READ_ERROR 255
517
518 #define WRITE_OK 0
519 #define WRITE_ERROR 1
520 #define WRITE_EOM 2
521 #define WRITE_EOV 3
522
523
524 /* These are copied out of BRU 16.1, with all the boolean masks changed
525  * to our bitmasks.
526 */
527 #define S_NO_SENSE(s) ((s).SenseKey == 0x0)
528 #define S_RECOVERED_ERROR(s) ((s).SenseKey == 0x1)
529
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)
536
537 #define DEFAULT_TIMEOUT 3*60  /* 3 minutes here */
538
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.
542  */
543 #define SHORT_READ(s) (S_NO_SENSE((s)) && (s).ILI && (s).Valid &&  (s).AdditionalSenseCode==0  && (s).AdditionalSenseCodeQualifier==0)
544                        
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)
549
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. 
554  */
555 int SCSI_readt(DEVICE_TYPE fd, char * buf, unsigned int bufsize, unsigned int *len, unsigned int timeout) {
556   int rtnval;
557   CDB_T cmd;
558
559   int blockCount;
560   int info;
561   
562   RequestSense_T RequestSense;
563   
564   if (bufsize==0) { /* we are in variable block mode */
565     blockCount=MAX_READ_SIZE; /* variable block size. */
566   } else {
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. */
571     }
572   }
573   
574   if (timeout == 0) {
575     timeout = 1 * 60; /* 1 minutes */
576   }
577   
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 */
584   
585   /* okay, let's read, look @ the result code: */
586   rtnval=READ_OK;
587   if (SCSI_ExecuteCommand(fd,Input,&cmd,6,buf,(bufsize) ? *len : MAX_READ_SIZE,&RequestSense)) {
588     
589     rtnval=READ_ERROR;
590     if (HIT_EOP(RequestSense)) {
591       cmd[0]=0x08;
592       rtnval=READ_EOP;
593     }
594
595     if (HIT_FILEMARK(RequestSense)) {
596       rtnval=READ_FILEMARK;
597     }
598     if (HIT_EOD(RequestSense)) {
599       rtnval=READ_EOD;
600     }
601     if ( (bufsize==0) && SHORT_READ(RequestSense)) {
602       rtnval=READ_SHORT; /* we only do short reads for variable block mode */
603     }
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. 
609       */
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 ;
619     } else {
620       PrintRequestSense(&RequestSense);
621       exit(1);  /* foo. */
622     }
623   }
624   
625   return(rtnval);
626 }
627
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
630  * BRU types.
631  */ 
632 int SCSI_writet(DEVICE_TYPE fd, char * buf, unsigned int blocksize,
633                 unsigned int *len, 
634                 unsigned int timeout) {
635   CDB_T cmd;
636
637   int blockCount;
638   int rtnval=0;
639   RequestSense_T RequestSense;
640
641   if (blocksize==0) { /* we are in variable block mode */
642     blockCount=*len; /* variable block size. */
643   } else {
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. */
648     }
649   }
650
651   fprintf(stderr,"Writing %d blocks\n",blockCount);
652   
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 */
659   
660   
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)) {
665         exit(WRITE_EOV);
666       }
667       exit(WRITE_EOM); /* end of media! */
668     }
669     else { /* it was plain old write error: */
670       PrintRequestSense(&RequestSense);
671       exit(WRITE_ERROR);
672     }
673   } else {
674     rtnval = *len; /* worked! */
675   }
676   return(rtnval);
677 }
678
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. */
682   int buffersize;
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 */
687   int result;
688   int eof_input;
689   int infile=fileno(stdin); /* sigh */
690
691   if (blocksize==0) {
692     varsize=1;
693     buffersize=MAX_READ_SIZE;
694     len=MAX_READ_SIZE;
695   }  else {
696     varsize=0; /* fixed block mode */
697     buffersize=blocksize;
698     len=blocksize;
699   }
700   /* sigh, make it oversized just to have some */  
701   buffer=malloc(buffersize+8); 
702
703   eof_input=0;
704   while (!eof_input) {
705     /* size_t could be 64 bit on a 32 bit platform, so do casts. */
706     len=0;
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
709      * the buffer.
710      */
711     while (len < buffersize) {
712       result=(int)read(infile,buffer+len,(size_t)(buffersize-len));
713       if (!result) {
714         eof_input=1;
715         if (!len) { /* if we have no deata in our buffer, exit */
716           return 0; /* we're at end of file! */
717         }
718         break; /* otherwise, break and write the data */
719       }
720       len+=result;  /* add the result input to our length. */
721     }
722     
723
724     result=SCSI_writet(MediumChangerFD,buffer,blocksize,&len,DEFAULT_TIMEOUT);
725     if (!result) {
726       return 1; /* at end of tape! */
727     }
728     /* Now see if we have numbytes or numblocks. If so, we may wish to exit
729        this loop.
730     */
731     if (arg[1]) {
732       if (varsize) {
733         /***BUG***/
734         return 0; /* we will only write one block in variable size mode :-( */
735       } else {
736         if (numblocks) {
737           numblocks--;
738         } else {
739           return 0; /* we're done. */
740         }
741       }
742     }
743   }
744   /* and done! */
745   return 0;
746 }
747
748 /* Okay, the read thingy: */
749
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
752  * (respectively).
753  */ 
754
755
756 static int S_read(void) {
757   unsigned char *buffer; /* the buffer we're going to be reading out of */
758   int buffersize;
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. */
763
764   int result;
765
766   int outfile=fileno(stdout); /* sigh. */
767   
768
769   if (blocksize==0) {
770     varsize=1;
771     buffersize=MAX_READ_SIZE;
772     len=MAX_READ_SIZE;
773   }  else {
774     varsize=0; /* fixed block mode */
775     buffersize=blocksize;
776     len=blocksize;
777   }
778   /* sigh, make it oversized just to have some */  
779   buffer=malloc(buffersize+8); 
780   
781
782   while (1) {
783     if (varsize) {
784       /* it could have gotten reset by prior short read... */
785       len=MAX_READ_SIZE; 
786     }
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? */
790       if (len > 0) {
791         write(outfile,buffer,len);
792       }
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! */
798       }
799 #endif    
800       return 0; /* hit normal end of file. */
801     } else if (result==READ_SHORT) {
802       /* short reads are only valid in variable block mode. */
803       if (varsize) {
804         if (len > 0) {
805           write(outfile,buffer,len);
806         }
807       } else {
808         fprintf(stderr,"scsitape:Short Read encountered on input. Aborting.\n");
809         fflush(stderr);
810         exit(1); /* error exit! */
811       }
812     } else if (result==READ_OK) {
813       write(outfile,buffer,len);
814     } else {
815
816       fprintf(stderr,"scsitape:Read Error\n");
817       fflush(stderr);
818       exit(1);
819     }
820     /* Now see if we have numbytes or numblocks: if so, we may wish to
821      * exit this loop.
822      */
823     if (arg[1]) {
824       if (varsize) {
825         /****BUG****/ 
826         return 0; /* we're only reading one block in var size mode! */
827       } else {
828         if (numblocks) {
829           numblocks--;
830         } else {
831           return 0; /* we're done. */
832         }
833       }
834     }
835   }
836 } /* got the goddam cancer of the curly braces. You can tell I used to 
837    * do Lisp.
838    */
839
840
841 /* See parse_args for the scoop. parse_args does all. */
842 int main(int argc, char **argv) {
843   argv0=argv[0];
844   parse_args(argc,argv);
845
846   if (device) 
847     SCSI_CloseDevice(device,MediumChangerFD);
848
849   exit(0);
850 }
851