/* SCSI tape utility. By John Wilson . Copyright (C) 1999-2000 by Digby's Bitpile, Inc. All rights reserved. This program may be freely copied as long as source code is available which includes this notice. 02/13/1999 JMBW Created. 06/20/1999 JMBW IGET, IPUT commands. V1.0. 07/14/1999 JMBW Optional fixed record sizes in IGET/IPUT. 11/15/2000 JMBW Fixed xrewind() to ignore EOM status on rewind. */ #include #include #include #include "st.h" #define RECMAX 16384 /* max record size */ scsireq r; /* request block */ static byte far buf[RECMAX]; /* data buffer */ signed long int reqcount; /* requested byte count for record I/O */ static byte filbuf[4+RECMAX+4]; /* NEAR data buffer for file I/O */ /* (includes leading/trailing length dwords) */ /* SCSI access externals: */ void scsiinit(); int xscsiw(scsireq *); /* print check condition from sense key */ static void check() { char *s; switch(r.srsns[2]&0x0F) { case 0x00: s="No sense"; break; case 0x01: s="Soft error"; break; case 0x02: /* (won't get this, remapped to SC$OFL) */ s="Not ready"; break; case 0x03: s="Medium error"; break; case 0x04: s="Hardware error"; break; case 0x05: s="Illegal request"; break; case 0x06: /* (won't get this, remapped to SC$ATN) */ s="Unit attention"; break; case 0x07: s="Write protect error"; break; case 0x08: s="Blank check"; break; case 0x09: s="Data underflow"; break; // case 0x0A: /* ??? */ case 0x0B: s="Aborted command"; break; // case 0x0C: /* ??? */ case 0x0D: s="Volume overflow"; break; case 0x0E: s="Miscompare"; break; // case 0x0F: /* ??? */ default: s="Unknown sense key"; } fprintf(stderr,"?%s\n",s); } /* sort out check information and get actual record length */ /* returns 1 if length was valid (or =0L for tape mark), 0 if other error */ static int actuallen(int n,long int *p) { if(n==SCCHK) { /* was check condition */ if((r.srsns[2]&0x0F)==0) { /* no sense, EOF or residual */ if(r.srsns[2]&0x80) { /* tape mark */ *p=0L; return(1); } if(r.srsns[0]&0x80) { /* residual count valid */ *p=reqcount- (((long int)r.srsns[3]<<24L)| ((long int)r.srsns[4]<<16L)| ((long int)r.srsns[5]<<8L)| (long int)r.srsns[6]); return(1); } } } return(0); /* some other problem, flag a real error */ } /* print err msg if appropriate */ static void err(int n) { char *s; long int actual; fflush(stdout); /* make sure they're synced up */ fflush(stderr); switch(n) { case SCIOE: s="I/O error"; break; case SCCHK: s="Check condition"; break; case SCATN: s="Unit attention"; break; case SCOFL: s="Device not ready"; break; default: return; } fprintf(stderr,"?%s\n",s); if(n==SCCHK) { /* was check condition */ check(); if(actuallen(n,&actual)) { if(actual) fprintf(stderr, "%%Actual count = %ld\n",actual); else fprintf(stderr,"%%EOF mark\n"); } } } /* execute current cmd with no data phase */ static int nodata() { r.srdir=0; /* no data phase */ r.sroob=0; /* so no out-of-band data */ r.srbuf=(void far *)0; /* no data buffer */ r.srsiz=0; /* get the hint yet? */ return(xscsiw(&r)); } /* read a record */ static int xread() { static byte rdcdb[6] = { 0x08, 0x00, 0x00, 0x00, 0x00, 0x00 }; r.srcdl=6; /* 6-byte command */ memcpy(r.srcdb,rdcdb,6); /* copy it in */ r.srcdb[2]=(RECMAX>>16)&0xFF; /* big-endian buf size */ r.srcdb[3]=(RECMAX>>8)&0xFF; r.srcdb[4]=RECMAX&0xFF; reqcount=RECMAX; r.srdir=1; /* read from dev to memory */ r.sroob=0; /* no out-of-band data */ r.srbuf=buf; /* pt at data buffer */ r.srsiz=RECMAX; /* requested count */ return(xscsiw(&r)); } /* rewind the tape */ static int xrewind() { static byte rewcdb[6] = { 0x01, 0x00, 0x00, 0x00, 0x00, 0x00 }; int rc; r.srcdl=6; /* 6-byte command */ memcpy(r.srcdb,rewcdb,6); /* copy it in */ rc=nodata(); /* end-of-media check condition is OK, change to "no error" */ if(rc==SCCHK&&(r.srsns[2]&0x4F)==0x40) rc=0; return(rc); } /* space by files/records (BYFILES decides which) */ static int space(int byfiles,long int count) { static byte spacdb[6] = { 0x11, 0x00, 0x00, 0x00, 0x00, 0x00 }; r.srcdl=6; /* 6-byte command */ memcpy(r.srcdb,spacdb,6); /* copy it in */ r.srcdb[1]=byfiles?1:0; /* LSB=1 to space by files, 0 => by recs */ r.srcdb[2]=(count>>16L)&0xFF; /* 24-bit signed big-endian count */ r.srcdb[3]=(count>>8L)&0xFF; r.srcdb[4]=count&0xFF; reqcount=count; return(nodata()); } /* unload the tape */ static int unload() { static byte ssucdb[6] = { 0x1B, 0x00, 0x00, 0x00, 0x02, 0x00 }; /* START/STOP UNIT, LoEj=1, Start=0 */ r.srcdl=6; /* 6-byte command */ memcpy(r.srcdb,ssucdb,6); /* copy it in */ return(nodata()); } /* turn write buffering on or off */ static int wbuf(long int flag) { static byte mslcdb[6] = { 0x15, 0x00, 0x00, 0x00, 0x00, 0x00 }; r.srcdl=6; /* 6-byte command */ memcpy(r.srcdb,mslcdb,6); /* copy it in */ r.srcdb[4]=4; /* allocation length */ /* use out-of-band data buf (for the heck of it) for parms */ r.srobb[0]=0x03; /* length of rest of buf */ r.srobb[1]=0x00; /* medium type */ r.srobb[2]=flag?0x10:0x00; /* WP=0, buffering=on/off, speed=default */ r.srobb[3]=0x00; /* no block descriptors */ r.srdir=-1; /* write from memory to dev */ r.sroob=1; /* out-of-band data */ r.srsiz=4; /* requested count */ return(xscsiw(&r)); } /* write EOF marks */ static int weof(long int count) { static byte wtmcdb[6] = { 0x10, 0x00, 0x00, 0x00, 0x00, 0x00 }; r.srcdl=6; /* 6-byte command */ memcpy(r.srcdb,wtmcdb,6); /* copy it in */ r.srcdb[2]=(count>>16)&0xFF; /* 24-bit big-endian count */ r.srcdb[3]=(count>>8)&0xFF; r.srcdb[4]=count&0xFF; reqcount=count; return(nodata()); } /* write a record */ static int xwrite(long int len) { static byte wrcdb[6] = { 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00 }; r.srcdl=6; /* 6-byte command */ memcpy(r.srcdb,wrcdb,6); /* copy it in */ r.srcdb[2]=(len>>16L)&0xFF; /* big-endian buf size */ r.srcdb[3]=(len>>8L)&0xFF; r.srcdb[4]=len&0xFF; reqcount=len; r.srdir=-1; /* write from memory to dev */ r.sroob=0; /* no out-of-band data */ r.srbuf=buf; /* pt at data buffer */ r.srsiz=len; /* requested count */ return(xscsiw(&r)); } /* parse device name (SCSIht_l: format just like PUTR and E11) */ void devname(char *s) { char c, *s0=s; int n; if(strnicmp(s,"SCSI",4)!=0) goto punt; /* should start with SCSI */ s+=4; /* skip it */ /* optional letter is HA number */ c=toupper(*s); if(c>='A'&&c<='Z') { r.srhst=c-'A'; s++; } else r.srhst=0; /* optional number is target ID */ n=0; while(isdigit(*s)) { n=n*10+(*s-'0'); s++; } r.srtrg=n; /* optional underscore and number gives LUN */ n=0; if(*s=='_') { s++; while(isdigit(*s)) { /* grab decimal LUN */ n=n*10+(*s-'0'); s++; } } r.srlun=n; /* optional colon ends dev name */ if(*s==':') s++; if(*s!='\0') goto punt; /* should have gotten everything */ return; punt: fprintf(stderr,"?Invalid device name: %s\n",s0); exit(1); } /* grab numeric argument (count argument) */ static long int count(int argc,char **argv) { if(argc>1) return(atol(argv[1])); /* 2nd word is count parameter */ else return(1L); /* default count is 1 */ } /* grab filename argument */ static char *filename(int argc,char **argv) { if(argc>1) return(argv[1]); fprintf(stderr,"?Missing filename\n"); exit(1); return(NULL); /* Watcom is picky! */ } /* grab a fixed recordlength argument (after the filename) */ static long int reclen(int argc,char **argv) { if(argc>2) return(atol(argv[2])); /* 3rd word is record length */ else return(0L); } /* high-level commands that do multiple operations */ /* get image of entire tape (BOT to LEOT) into a file */ /* if recl is given, read starts here and ends at first TM (no rew bef/aft) */ static void iget(int argc,char **argv) { FILE *out; /* output file pointer */ byte far *s; /* source, dest pointers */ byte *d; char *f; /* filename ptr */ int rc, prev; long int actual; /* actual length of each record read */ unsigned char len[4]; long int recl; /* fixed record length if NZ */ /* (no header/trailer counts in file) */ /* open output file */ out=fopen(f=filename(argc,argv),"wb"); if(out==NULL) { perror(f); exit(1); } /* get fixed record length, variable if 0 or missing */ recl=reclen(argc,argv); /* rewind -- do it twice to absorb unit attention status */ if(!recl) { (void)xrewind(); /* may get unit attn, ignore error */ if(rc=xrewind()) goto punt; /* we care if it fails this time */ } /* start reading */ for(prev=1;;) { /* prev=1 for starters so EOF @BOT = LEOT */ rc=xread(); /* try to read */ actual=reqcount; /* got it all as far as we know so far */ if(rc) { /* read error, might just be check */ if(!actuallen(rc,&actual)) goto punt; } if(recl) { /* fixed-size records */ /* stop at first tape mark */ if(actual==0) { /* tape mark, done */ if(rc=xrewind()) goto punt; /* rewind */ fclose(out); return; } /* length must be what we were told */ if(actual!=recl) { fprintf(stderr, "%%Bad record length, expected %ld, got %ld\n", recl,actual); fclose(out); return; } } else { /* encode record length for prefix/suffix in file */ len[0]=actual&0xFF; len[1]=(actual>>8)&0xFF; len[2]=(actual>>16)&0xFF; len[3]=(actual>>24)&0xFF; /* write prefix */ if(fwrite(len,sizeof(len),1,out)!=1) goto filerr; } /* write data and suffix, unless tape mark */ if(actual) { /* non-null data record */ for(s=buf,d=filbuf;s<(buf+reqcount);) *d++=*s++; if(fwrite(filbuf,(size_t)actual,1,out)!=1) goto filerr; if(!recl) { if(fwrite(len,sizeof(len),1,out)!=1) goto filerr; } prev=0; } else { /* tape mark */ if(prev) { /* two TMs in a row, LEOT */ if(!recl) { if(rc=xrewind()) goto punt; /* rew */ } fclose(out); return; } prev=1; } } punt: err(rc); /* error, give up */ fclose(out); return; filerr: perror(f); /* file write error */ fclose(out); } /* put image file onto tape */ /* if recl is given, write starts here and no TMs or rewind at end */ static void iput(int argc,char **argv) { FILE *in; /* input file pointer */ byte *s; /* source, dest pointers */ byte far *d; char *f; /* filename ptr */ int rc; long int actual; /* actual length of each record copied */ unsigned char len[4]; long int recl; /* fixed record length if NZ */ /* (no header/trailer counts in file) */ /* open input file */ in=fopen(f=filename(argc,argv),"rb"); if(in==NULL) { perror(f); exit(1); } /* get fixed record length, variable if 0 or missing */ recl=reclen(argc,argv); /* rewind -- do it twice to absorb unit attention status */ if(!recl) { (void)xrewind(); /* may get unit attn, ignore error */ if(rc=xrewind()) goto punt; /* we care if it fails this time */ } (void)wbuf(1L); /* required for TZ30/TK50Z-GA to work */ /* (ignore error, if any, on other drives) */ /* start writing */ for(;;) { if(recl) /* fixed record size */ actual=recl; else { /* variable record size, get it from file */ /* read prefix */ if(fread(len,sizeof(len),1,in)!=1) { if(feof(in)) { /* EOF, finish up */ if(rc=xrewind()) goto punt; /* rew */ fclose(in); return; } else goto filerr; /* other error */ } /* decode record length from prefix */ actual=(long int)len[0]| ((long int)len[1]<<8L)| ((long int)len[2]<<16L)| ((long int)len[3]<<24L); } /* write data and check suffix, unless tape mark */ if(actual) { /* non-null data record */ /* get data part of record */ if(fread(filbuf,(size_t)actual,1,in)!=1) { if(recl&&feof(in)) { /* end of fixed-recsize file */ fclose(in); return; } else goto filerr; } /* copy to buffer where ASPI can reach it */ for(s=filbuf,d=buf;s<(filbuf+actual);) *d++=*s++; /* write the buffer */ if(rc=xwrite(actual)) goto punt; if(!recl) { /* variable record size */ /* read trailing length dword */ if(fread(len,sizeof(len),1,in)!=1) goto filerr; /* must match leading length dword */ if(((long int)len[0]| ((long int)len[1]<<8L)| ((long int)len[2]<<16L)| ((long int)len[3]<<24L))!=actual) { fprintf(stderr,"?File format error\n"); fclose(in); exit(1); } } } else { /* tape mark */ if(rc=weof(1L)) goto punt; } } punt: err(rc); /* error, give up */ fclose(in); return; filerr: perror(f); /* file read error */ fclose(in); } main(int argc,char **argv) { char *s; long int n; char far *p=buf; /* set default values */ r.srhst=0; /* HA # */ r.srtrg=0; /* target */ r.srlun=0; /* LUN */ /* check for environment variable */ if((s=getenv("TAPE"))!=NULL) devname(s); if(argc--==0) goto usage; /* skip filename */ argv++; /* check for device name that may come first (with -f option) */ if(argc>=2&&strnicmp(*argv,"-f",2)==0) { devname(*++argv); argc-=2, ++argv; } scsiinit(); /* init connection */ /* fill buf with As for "write" command (trashed by other cmds) */ while(p www.dbit.com\n\ usage: st [-f device] operation\n\ \n\ bsf [n] backspace file(s)\n\ bsr [n] backspace record(s)\n\ fsf [n] forward space file(s)\n\ fsr [n] forward space record(s)\n\ iget file [n] get tape into image file (n=reclen, 0 or none if variable)\n\ iput file [n] put image file onto tape\n\ read read (and toss) a record\n\ rewind rewind tape\n\ unload unload tape\n\ wbuf [n] enable(1)/disable(0) write buffering (req'd for TZ30)\n\ weof [n] write EOF mark(s)\n\ write write 512-byte record of ASCII As\n\ \n\ Format of \"device\" string or TAPE environment variable: SCSIht_l:\n\ h optional host adapter letter (A=1st HA, B=2nd ...)\n\ t decimal SCSI target number (0-15)\n\ l decimal SCSI LUN number (0-7), default=0 (leave _ out too)\n\ In most cases h and l are defaulted, so \"SCSI2:\" refers to SCSI target 2.\n\ "); exit(1); }