/****     BTNtape Handler for SCSI tape drives        ****/
/**** Author: Bob Rethemeyer  (drBob@cup.portal.com)  ****/

#define VERSDATE "(03.14.94)"  /* (DD.MM.YY) */
#define VERSNAME "BTNtape-RAR 3.0 "
/* (If you recompile BTN, ^^^ put your initials here!) */

/*--------------------------------------------------------------------------
 *  (c) Copyright 1990,1994  Robert Rethemeyer.
 *  This software may be freely distributed and redistributed,
 *  for non-commercial purposes, provided this notice is included.
 *--------------------------------------------------------------------------
 *  BTNtape is an AmigaDOS device handler to make a simple DOS TAPE: device.
 *  It converts DOS packets for the device into I/O requests to a
 *  "SCSI-direct" compatible device driver.  It is based on "my.handler"
 *  by Phillip Lindsay and a SCSI-direct program by Robert Mitchell.
 *  Seek() handling derived from code by Dennis J. Brueni, 8-24-91.
 *  Source is ANSI C compliant.  Compile with SAS/C v6.
 *
 *  This handler works in conjunction with the accompanying TapeMon program.
 *--------------------------------------------------------------------------
 */
#define _STRICT_ANSI
#include <exec/types.h>
#include <exec/nodes.h>
#include <exec/lists.h>
#include <exec/ports.h>
#include <exec/tasks.h>
#include <exec/libraries.h>
#include <exec/io.h>
#include <exec/memory.h>
#include <devices/scsidisk.h>
#include <intuition/intuition.h>
#include <libraries/dos.h>
#include <libraries/dosextens.h>
#include <libraries/filehandler.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdarg.h>
#include "btn.h"
#include "tplink.h"

#include <proto/exec.h>
#include <proto/intuition.h>
#include <pragmas/exec_pragmas.h>
#include <pragmas/intuition_pragmas.h>

/* sense keys of interest */
#define NOS 0x00  /* no sense */
#define RCV 0x01  /* recovered error */
#define ILL 0x05  /* illegal request */
#define UAT 0x06  /* unit attention */
#define BLC 0x08  /* blank check */
#define VOF 0x0d  /* volume overflow */
/* pseudo sense keys returned by DoSense */
#define FMK 0x10  /* filemark */
#define EOM 0x11  /* end of tape */
#define ILI 0x12  /* incorr leng */
#define SERR 0x13 /* SCSI error  */

/*======== Global data */

struct ExecBase *SysBase;
struct IntuitionBase *IntuitionBase=NULL;
UBYTE           *cdb;          /* pointer to SCSI command buffer */
UBYTE           *sns;          /* pointer to sense data buffer   */
UBYTE           *inq;          /* pointer to inquiry data        */
struct SCSICmd  *cmd;          /* pointer to scsidirect command  */
struct IOStdReq *ior;          /* pointer to io request structure*/
UBYTE *TapeBuff[2]={NULL,NULL};/* pointers to 2 tape buffers     */
struct Process  *myproc;       /* ptr to handler's process struct*/
struct DosPacket *mypkt;       /* ptr to dos packet sent         */
struct tplink   *linktp;       /* pointer to link structure      */
struct MsgPort  *monport=NULL; /* ptr to mon communication msg port */
struct MsgPort  *btnport;      /* ptr to btn communication msg port */
struct BTNmsg    monmsg;       /* message we will send to tapemon*/
ULONG  blknum;                 /* block number for io operation  */
ULONG  blksize = 512;          /* bytes per tape block           */
ULONG  numblks = 1;            /* number of blocks per io oper   */
ULONG  TBSize;                 /* bytes in a tape buffer         */
ULONG  rwlen;                  /* bytes in a tape read/write     */
ULONG  rdlen;                  /* bytes/blks in a read           */
ULONG  expect;                 /* expect num of bytes transferred*/
ULONG  bugmask = 0;            /* 2090A bug circumvention        */
long   filenum = -1;           /* current file position on tape  */
long   lastwrtn= -1;           /* last filenum written           */
long   actual;                 /* actual num of bytes transferred*/
long   res1;                   /* #bytes xfered packet return    */
long   Boff;                   /* current offset in tape buffer  */
long   tpsize ;                /* tape size in blocks            */
long   reserved = 0;           /* number of reserved blks at BOT */
int    tapenum;                /* tape volume number             */
BOOL   inprog = FALSE;         /* io operation in progress flag  */
BOOL   reten = FALSE;          /* retension-on-UAT flag          */
BOOL   mbusy = FALSE;          /* waiting for reply from tapemon */
BOOL   bswap = FALSE;          /* swap-bytes flag */
BOOL   dbug = FALSE;           /* print debug info */
char   *z;                     /* scratch */
char   *devname, nbuf[32];     /* file name reference from Open()*/
char   dbb[80];                /* buffer for monitor messages    */
UBYTE  Lun = 0;                /* Logical Unit number << 5       */
UBYTE  varblk = 0;             /* variable-block flag for SEQ    */
UBYTE  fixedbit = 0;           /* fixed-block mode bit for SEQ   */
UBYTE versiontag[] = "$VER: " VERSNAME VERSDATE;
char  *cpywr = "(c) Copyright 1990-1994 R. Rethemeyer";

/***********************  Main program  ********************************/
void _main(char *nu)
{
 struct SCSICmd scsicmd;  /* SCSIdirect command buffer */
 UBYTE  snsarea[64];      /* sense data buffer */
 UBYTE  cmdbuff[16];      /* SCSI command buffer */
 UBYTE  inqdata[40];      /* inquiry data buffer */
 struct tplink      tpl;       /* structure to link hndlr & mon     */
 struct DeviceNode  *mynode;   /* ptr to devnode passed in pkt Arg3 */
 struct MsgPort     *mp;       /* ptr to io message port            */
 struct MsgPort     *pktport;  /* ptr to process message port       */
 struct Message     *m;
 struct BTNmsg      *b;
 ULONG   dvnode;        /* ptr to devnode passed in pkt Arg3 */
 ULONG   unit=99999;    /* device SCSI unit address          */
 ULONG   bufmemtype=0;  /* type of mem for dynamic buffers   */
 ULONG   gotopos=0;     /* position to open at (blk/filemark)*/
 ULONG   fmarks=1;      /* # file marks for write/skip       */
 ULONG   pktsig;        /* signal mask for process msg port  */
 ULONG   btnsig=0;      /* signal mask for mon comm msg port */
 char    *driver;       /* name of SCSI device driver        */
 UBYTE   *dptr;         /* ptr to next byte in dos buffer    */
 long    dflags=0;      /* device flags                      */
 long    iostat;        /* status of previous read           */
 long    dcnt;          /* count of dos packet bytes to move */
 long    mcnt;          /* count of bytes to move            */
 long    rcnt;          /* count of bytes moved for read     */
 long    current;       /* current byte position in file     */
 long    remain;        /* bytes remaining in tape buffer    */
 long    pkcnt;         /* flag to indicate data written     */
 long    x;
 int     y=0;
 int     opmode=0;      /* how has the handler been opened   */
    #define CLOSED  0       /* not doing anything */
    #define READING 1       /* reading tape */
    #define WRITING 2       /* writing tape */
    #define CURRPOS 3       /* read current file position */
    #define RAWMODE 4       /* raw command mode */
    #define UMODSEL 5       /* user mode select */
 int     Bn;            /* current buffer number, 0 or 1 */
 int     position;      /* how to position tape at Open  */
    #define POSREW  0       /* rewind to beginning */
    #define POSNREW 1       /* don't move          */
    #define POSAPP  2       /* append to eod (seq) */
    #define POSEND  3       /* append to eod (seq) */
    #define POSSKIP 4       /* skip to file/block  */
 int     nrflag=POSREW; /* rewind behavior of TAPE: */
 BOOL    rewcl = FALSE; /* rewind-on-close flag */
 BOOL    eject = FALSE; /* eject-on-close flag  */
 BOOL    dirty = FALSE; /* buffer has unwritten data in it */
 BOOL    atend;         /* flags filemark encountered */
 BOOL    eot;           /* flags end-of-tape encountered */
 BOOL    rderr;         /* flags error during read */
 BOOL    errec = FALSE; /* attempt error recovery flag */
 BOOL    aonly = FALSE; /* append-only flag */
 BOOL    rdonly =FALSE; /* read-only flag */
 BOOL    starting=FALSE;/* flag detects first open message */
 BOOL    dos2=FALSE;    /* indicates if later OS version */
 int     Ctl=CTLIMM;    /* Controls overlapped dos/scsi I/O */
 char   *termn8 = "     TERMINATE BTN     ";
 char    portname[32] = "BTN_";
 char    monpname[32] = "TMON_";

/*======== Startup */

 SysBase = *((struct ExecBase **) (4));
 IntuitionBase = (struct IntuitionBase *)OpenLibrary("intuition.library",0);
 myproc = (struct Process *) FindTask(0L);          /* find this process */
 pktport= &myproc->pr_MsgPort;
 pktsig = 1L << pktport->mp_SigBit;
 WaitPort(pktport);                              /* wait for startup packet */
 m = GetMsg(pktport);
 mypkt = PKTADR(m);
 /* packet: Arg1=BSTR to name, Arg2=BSTR to startup string, Arg3=BPTR devnode*/
 mynode = (struct DeviceNode *) BADDR(mypkt->dp_Arg3);
 dvnode = (ULONG) mypkt->dp_Arg3;

/*======== Create linkage block for tapemon.
*/
 tpl.cpyw    = cpywr;
 tpl.version = "XXXXX " VERSNAME "(" __DATE__ ")";
 tpl.devnode = (void *)mynode;
 tpl.dbb     = dbb;
 tpl.unit    = &unit;
 tpl.Lun     = &Lun;
 tpl.sense   = NULL;
 tpl.badparm = FALSE;
 linktp = &tpl;

/*======== Extract info from mountlist Startup parameter. It may be
  ======== enclosed in quotes, and each item is separated by a single '/'.
  ======== First item is always the driver name, others may be in any order.
*/
 z = (char *)BADDR(mypkt->dp_Arg2)+1 ;  /* Arg2= BSTR to mountlist 'Startup'*/
 if(*z=='\"')  {                      /* remove quotes if any */
     z++;
     z[strlen(z)-1]= '\0' ;
 }
 driver = tpl.driver = z;
 for(; *z != '/'; z++);
 *(z++) = '\0';
 toUPPER(z);
 while (y!=100) {
   switch(y=getstart(&x)) {
     case 0:  /* UN */  unit = (ULONG) x;  break;
     case 1:  /* BS */  blksize = (ULONG) x;  break;
     case 2:  /* NB */  numblks = (ULONG) x;  break;
     case 3:  /* RB */  reserved = x;  break;
     case 4:  /* BT */  bufmemtype = (ULONG) x;  break;
     case 5:  /* FM */  fmarks = x & 0xff;  break;
     case 6:  /* ER */  errec = (x!=0);  break;
     case 7:  /* OV */  if(!x) Ctl = CTLWAIT; break;
     case 8:  /* DF */  dflags = x; break;
     case 9:  /* C9 */  if(x) bugmask = 0x01000000; break;
     case 10: /* NR */  nrflag = x ? POSNREW : POSREW; break;
     case 11: /* RT */  reten = (x!=0); break;
     case 12: /* RC */  rewcl = (x!=0); break;
     case 13: /* EC */  eject = (x!=0); break;
     case 14: /* SW */  bswap = (x!=0); break;
     case 15: /* RO */  rdonly= (x!=0); break;
     case 16: /* AO */  aonly = (x!=0); break;
     case 17: /* MR */  starting = (x!=0); break;
     case 18: /* DB */  dbug  = (x!=0); break;
 /***case -:   * VB *   if(x) varblk = 1;   break;  sorry, vb mode disabled */
     case 99: /* ?? */  tpl.badparm = TRUE; break;
     default: ;
   }
 }
 Lun = (UBYTE) unit;    Lun = ((Lun/10-(Lun/100)*10) & 7) << 5;
 rwlen = TBSize = numblks * blksize;   /* size of a tape buffer */

 cdb = cmdbuff;
 sns = snsarea;
 inq = tpl.inquiry = inqdata;
 cmd = &scsicmd;
 ior = (struct IOStdReq *) CreateExtIO( mp=CreatePort(NULL,0),
                                         sizeof(struct IOStdReq));

/*======== Open the SCSIdirect device */

 if ( OpenDevice(driver,unit,(struct IORequest *)ior,dflags)  )  {
    ask("Can't open SCSI-direct",termn8,NULL);
    LEAVE:
    returnpkt(DOSFALSE,ERROR_INVALID_COMPONENT_NAME);
    if(ior) DeleteExtIO((struct IORequest *)ior);
    if(mp) DeletePort(mp);
    CloseLibrary((struct Library *)IntuitionBase);
    return;
 }

/*======== Create the port that TapeMon will
  ======== use to acknowledge Mprintf() requests.
*/
 if( btnport = CreatePort(
               strcat(portname,myproc->pr_Task.tc_Node.ln_Name), 0) )
 {
    btnsig = 1L << btnport->mp_SigBit;
    monmsg.bmsg.mn_ReplyPort = btnport;

    /*======== If TapeMon is already running, send it a wakeup message. */
    Forbid();
    if(monport = FindPort(strcat(monpname,myproc->pr_Task.tc_Node.ln_Name)) )
       PutMsg(monport,&monmsg.bmsg);
    Permit();
    if(monport) { WaitPort(btnport);  GetMsg(btnport); }
    monport = NULL;
 }

/*======= Find the SCSI device type by INQUIRY.  Sequential or direct access?
*/
 if(x=TapeIO(INQUIRY,0,CTLWAIT)) {
    if(x <= HFERR_SelTimeout) {
       ask("Can't select tape drive",termn8,NULL);
       goto LEAVE;
    }
    TapeIO(TSENSE,0,CTLWAIT);
    inq[0] = 0x01; /* assume SEQ */
    inq[8] = 0;
 }
 inq[36] = 0;  /* null-terminate vendor info */
 if(SEQ) {
    reserved = 0;
    fixedbit = varblk ^ 0x01;  /* fixed or variable block mode */
    rdlen = (fixedbit) ? numblks : TBSize;
 }
 else if(DAC) rdlen = numblks;
 else { ask("UNit is not a tape drive",termn8,NULL); goto LEAVE; }

 mynode->dn_Task = &myproc->pr_MsgPort;    /* install handler taskid */
 returnpkt(DOSTRUE,0);                     /* reply to initial packet */

/* =========== The main packet processing loop =============== */

 for(;;)  {
   if( m = GetMsg(pktport) )             /* Check for packet from DOS */
      mypkt = PKTADR(m);
   else if( m = GetMsg(btnport) ) {      /* Check for message from TapeMon */
      if(m->mn_Node.ln_Type == NT_MESSAGE) {
         b = (struct BTNmsg *)m;
         /* Tapemon will send addr of port if it is starting up,
            or NULL if it is shutting down. */
         if(monport && b->mptr) b->mptr = NULL;  /* reject extra TapeMon */
         else { monport = b->mptr;  b->mptr = linktp; }
         ReplyMsg(m);
         if(!monport) mbusy=FALSE;
      }
      else /*NT_REPLYMSG*/  mbusy=FALSE;
      continue;
   }
   else {   /* Nothing to do but Wait() */
      Wait(pktsig | btnsig);
      continue;
   }

#ifdef DBUG
   if(dbug) Mprintf("PACKET=%d %08X %08X %08X\n", mypkt->dp_Type,
                    mypkt->dp_Arg1, mypkt->dp_Arg2, mypkt->dp_Arg3);
#endif

   switch(mypkt->dp_Type) {

     case ACTION_FINDINPUT:  /*----------- Open() ------------*/
     case ACTION_FINDOUTPUT:
     case ACTION_FINDUPDATE:
          if(opmode != CLOSED)  {
             returnpkt(DOSFALSE,ERROR_OBJECT_IN_USE);
             Mprintf("Already open\n");
             break;
          }

          /* Determine open mode */
          z = (char *)BADDR(mypkt->dp_Arg3);
          x = (UBYTE)(*z);
          devname = (char *)memcpy(nbuf, z+1, x);
          *(devname+x) = '\0';
          toUPPER(devname);
          z = devname;
          if(strchr(devname,':')) while(*(z++)!=':');  /*thx Tero Manninen*/
          if     (!strcmp(z,"RAWCMD"))  opmode=RAWMODE;
          else if(!strcmp(z,"MODESEL")) opmode=UMODSEL;
          else if(!strcmp(z,"POS"))     opmode=CURRPOS;
          else {    /* normal read/write */
             opmode = (mypkt->dp_Type==ACTION_FINDINPUT) ? READING : WRITING;
             if     (!strcmp(z,"*"))   position=POSNREW; /* current position */
             else if(!strcmp(z,"NR"))  position=POSNREW; /* current position */
             else if(!strcmp(z,"R"))   position=POSREW;  /* beginning of tape */
             else if(!strcmp(z,"APP")) position=POSAPP;  /* append at end */
             else if(!strcmp(z,"END")) position=POSEND;  /* append at end,slow*/
             else if(*z>='0' && *z<='9') {               /* specific position */
                   gotopos = strtoul(z,NULL,0);
                   position=POSSKIP;
             }
             else if(*z=='\0') position = nrflag;  /* TAPE: */
             else goto BADNAME;
          }

          if(starting) goto FASTOPEN; /* skip stuff on first open from mount */

          /* detect illegal combinations */
          if((opmode>=RAWMODE && mypkt->dp_Type==ACTION_FINDINPUT)
          || (opmode==CURRPOS && mypkt->dp_Type!=ACTION_FINDINPUT)
          || (opmode==READING && (position==POSAPP||position==POSEND))
          || (SEQ && opmode==WRITING && position==POSSKIP)
          || (DAC && (position==POSEND||position==POSAPP)) )
          { BADNAME:
             Mprintf("Invalid open name/combo %s\n",devname);
            NOOPEN:
             freestuff();
             opmode=CLOSED;
             returnpkt(DOSFALSE,ERROR_ACTION_NOT_KNOWN);
             break;
          }
          if(SEQ && aonly && opmode==WRITING) {
             if((position==POSNREW && lastwrtn>=0)
             ||  position==POSAPP
             ||  position==POSEND) ;
             else { Mprintf("%s is append-only\n",devname);
                    goto NOOPEN; }
          }
          if(opmode==WRITING && rdonly) {
             Mprintf("%s is read-only\n",devname);
             goto NOOPEN;
          }

          /* Allocate storage for buffers */
          TapeBuff[0] = (UBYTE *) AllocMem(TBSize, bufmemtype | MEMF_CLEAR);
          TapeBuff[1] = (UBYTE *) AllocMem(TBSize, bufmemtype | MEMF_CLEAR);
          if (!TapeBuff[0] || !TapeBuff[1]) {
               freestuff();
               opmode=CLOSED;
               returnpkt(DOSFALSE,ERROR_NO_FREE_STORE);
               Mprintf("Can't get %lu bytes for tape buffers\n",TBSize*2);
               break;
          }

          if(opmode<=WRITING) {

            /* Check device ready */
            if(x=TapeIO(TREADY,0,CTLWAIT))  {
               y=DoSense(x);
               if(y==UAT) {
                 blknum = filenum = reserved;  lastwrtn = -1;
                 if(reten) if(x=TapeIO(TRETEN,0,CTLWAIT)) DoSense(x);
               }
               else if(y==NOS) ;
               else if(y==ILL) ;
               else {
               GIVEUP:
                 freestuff();
                 opmode=CLOSED;
                 filenum = lastwrtn = -1;
                 returnpkt(DOSFALSE,ERROR_DEVICE_NOT_MOUNTED);
                 break;
               }
            }

            /* Check write-prot, block length */
            if(x=TapeIO(MDSNS,0,CTLWAIT)) DoSense(x);
            else {
               if(opmode==WRITING && (sns[2] & 0x80)) {
                 freestuff();
                 opmode=CLOSED;
                 returnpkt(DOSFALSE,ERROR_DISK_WRITE_PROTECTED);
                 break;
               }
               if(sns[3] >= 8) {
                 x = *((long *) &sns[8]) & 0x00ffffff;  /* get block len */
                 if(x != blksize) {  /* set block size with Mode Select */
                   Mprintf("Current block size = %lu\n",x);
                   if(x=TapeIO(MDSET,varblk,CTLWAIT)) DoSense(x);
                   else TapeIO(TSENSE,0,CTLWAIT);
                 }
               }
            }

            /* position the medium at starting spot */
            if(position==POSNREW) gotopos=blknum;
            if(postape(position,fmarks,gotopos)) goto GIVEUP;

            /* Get capacity for 3M tape */
            if(DAC) {
               tpsize = 0x7fffffff;
               if(TapeIO(RDCAP,0,CTLWAIT)) TapeIO(TSENSE,0,CTLWAIT);
               else {
                 tpsize = (((long)sns[2] << 8) | sns[3]) + 1;
                 Mprintf("Capacity: %ld blocks\n", tpsize);
               }
               Mprintf("%s Opened at block %lu\n",devname,blknum);
            }
            else Mprintf("%s Opened at file %ld\n",devname,filenum);
          }
          FASTOPEN:
          dirty=FALSE; inprog=FALSE; Boff=0; pkcnt=0; tapenum=1;
          iostat=0;  Bn=0;  remain=TBSize;  current=0;  atend=eot=rderr=FALSE;
          returnpkt(DOSTRUE,0);
          break;


     case ACTION_END:        /*----------- Close() -----------*/
          dcnt=DOSTRUE; mcnt=0; iostat=0;
          switch(opmode) {
             case WRITING:
                if(inprog) iostat = TapeIO(TFINISH,0,CTLWAIT);
                if(iostat) iostat = wrteot(Bn^1,1,iostat);
                if(iostat==0) {
                   if(dirty) {
                      memset(TapeBuff[Bn]+Boff,0,remain);
                      iostat = TapeIO(TWRITE,Bn,CTLWAIT);
                   }
                   if(iostat) iostat = wrteot(Bn,0,iostat);
                }
                if(iostat) { dcnt=DOSFALSE; Mprintf("Error during write\n"); }
                else if(pkcnt) {
                   blknum += actual;  x=0;
                   if(SEQ) {
                      x=TapeIO(WFMARK,fmarks,CTLWAIT);
                      lastwrtn = filenum++;
                   } else filenum = blknum;
                   if(x) DoSense(x);
                }
                break;
             case READING:
                if(inprog) iostat = TapeIO(TFINISH,0,CTLWAIT);
                if(iostat) { if(FMK==DoSense(iostat)) atend=TRUE; }
                if(DAC) filenum = blknum;
                else    filenum = atend ? filenum+1 : -1 ;
                lastwrtn = -1;
                break;
             case RAWMODE:
                if(x=TapeIO(RAWCMD,Bn,CTLWAIT)) DoSense(x); break;
             case UMODSEL:
                if(x=TapeIO(USRMODE,Bn,CTLWAIT)) DoSense(x); break;
             case CLOSED:
                dcnt=DOSFALSE;  mcnt=ERROR_ACTION_NOT_KNOWN; break;
             case CURRPOS:  break;
          }
          returnpkt(dcnt,mcnt);
          freestuff();
          if(starting) starting=FALSE;
          else if(opmode<=WRITING) {
                   Mprintf("Closed at block %lu, next file= %ld\n\n",
                      blknum,filenum);
                   if(rewcl) if(x=(TapeIO(TREWIND,0,CTLWAIT))) DoSense(x);
                   if(eject) if(x=(TapeIO(TEJECT,0,CTLWAIT))) DoSense(x);
               }
          opmode=CLOSED;
          break;

     case ACTION_READ:       /*------- Read(), Seek() --------*/
     case ACTION_SEEK:
          if(opmode!=READING && opmode!=CURRPOS) {
              Mprintf("Function/mode error\n");
              res1 = -1;
              goto RDEND;
          }
          if(opmode==CURRPOS && !atend) {  /* reading file position */
              remain = 1 + sprintf(TapeBuff[Bn],"%ld",filenum);
              dirty=atend=TRUE;
          }
          if(mypkt->dp_Type == ACTION_READ) {
              dptr = (UBYTE *) mypkt->dp_Arg2;
              dcnt = res1 =    mypkt->dp_Arg3;
              rcnt = 0;
          }
          else {  /* ACTION_SEEK */
              if(opmode==CURRPOS) goto BADSEEK;
              rcnt = -1;
              dcnt = mypkt->dp_Arg2;  /* offset */
              x    = mypkt->dp_Arg3;  /* type   */
              if(atend || eot || varblk) goto BADSEEK;
              if(x==OFFSET_END) goto BADSEEK;
              if(x==OFFSET_BEGINNING) dcnt -= current;
              /* x==OFFSET_CURRENT, dcnt is correct */
              res1 = current;
              if(dcnt<0) { /* backwards seek, must be to BOF */
                if((dcnt+current)==0) {
                   if(SEQ && position == POSNREW) goto BADSEEK;
                   if((dcnt+Boff)<0) {  /* still in buffer? */
                      if(inprog) TapeIO(TFINISH,0,CTLWAIT);
                      if(postape(position,fmarks,gotopos)) goto BADSEEK;
                      dirty=FALSE;  Bn=0;
                   }
                   current=0;  dcnt=0;  Boff=0;  remain=TBSize;
                }
                else {
                BADSEEK:
                   Mprintf("Disallowed seek type=%ld off=%ld curr=%ld\n",
                            x,dcnt,current);
                   res1 = -1;
                   goto RDEND;
                }
              }
          }
          while(dcnt) {
            if(!dirty) {  /* need to prefetch first buffer */
               iostat = TapeIO(TREAD,Bn,CTLWAIT);
               remain=0;  Bn=1;  dirty=TRUE;
            }
            if(remain==0)  {  /* current buffer exhausted */
               if(rderr) {
                  Mprintf("Error during read\n");
                  if(errec)
                     if(ask("  Ignore READ ERROR?", "IGNORE", "FAIL"))
                        rderr=FALSE;
                  if(rderr) { res1 = -1; goto RDEND; }
               }
               if(eot) {  /* end-of-tape */
                  TapeIO(TREWIND,0,CTLWAIT);
                  if(NewTape())  {
                     iostat=TapeIO(TREAD,(Bn^1),CTLWAIT);
                     eot=atend=FALSE;
                  }
                  else atend=TRUE;
               }
               if(atend)  { res1 = rcnt;  goto RDEND; }
               if(inprog)  iostat = TapeIO(TFINISH,0,CTLWAIT);
               if(iostat)    /* check status of previous read */
                  switch(DoSense(iostat)) {
                    case EOM: eot=TRUE;   /* & fall thru */
                    case FMK: atend=TRUE; /* & fall thru */
                    case RCV: break;
                    default:  rderr=TRUE;
                  }
               remain = actual;
               blknum += actual/blksize;  /* numblks; */
               if(!(atend || eot || rderr))  /* start refilling this buffer */
                   iostat = TapeIO(TREAD,Bn,Ctl);
               Bn ^= 1;      /* switch to other (full) buffer */
               Boff = 0;
               if(bswap) swapbytes(TapeBuff[Bn]);
            }
            mcnt = (dcnt>remain) ? remain : dcnt;
            if(mypkt->dp_Type == ACTION_READ)  {
               memcpy (dptr, TapeBuff[Bn]+Boff, mcnt);
               rcnt += mcnt ;
            }
            dcnt -= mcnt ;
            Boff += mcnt ;
            dptr += mcnt ;
            current += mcnt;
            remain -= mcnt ;
          }
          RDEND:
          returnpkt(res1,0);
          break;


     case ACTION_WRITE:      /*----------- Write() -----------*/
          if(opmode < WRITING) {
            Mprintf("Function/mode error\n");
            res1 = -1;
            goto WRTEND;
          }
          pkcnt++;
          dptr = (UBYTE *) mypkt->dp_Arg2;
          dcnt = res1 =    mypkt->dp_Arg3;
          while (dcnt)  {
            if(dcnt >= remain) {
               memcpy (TapeBuff[Bn]+Boff, dptr, remain);
               if(inprog) iostat = TapeIO(TFINISH,0,CTLWAIT);
               if(iostat)    /* check status of previous write */
                 if(iostat = wrteot(Bn^1,1,iostat)) {  /* possible EOT */
                    Mprintf("Error during write\n");
                    if(errec)
                       if(ask("  Ignore WRITE ERROR?", "IGNORE", "FAIL"))
                          iostat=0;
                    if(iostat) {
                       res1 = -1;
                       iostat=0;  dirty=FALSE;  pkcnt=0;
                       goto WRTEND;
                    }
                 }
               Boff += remain;
               iostat = TapeIO(TWRITE,Bn,Ctl); /* start writing full buffer */
               blknum += actual;
               dcnt -= remain;
               dptr += remain;
               Boff  = 0;
               remain= TBSize;
               Bn   ^= 1;      /* switch to other (empty) buffer */
               dirty = FALSE;
            }
            else {
               memcpy (TapeBuff[Bn]+Boff, dptr, dcnt);
               remain -= dcnt;
               Boff += dcnt;
               dcnt  = 0;
               dirty = TRUE;
            }
          }
          WRTEND:
          returnpkt(res1,0);
          break;


     case ACTION_IS_FILESYSTEM:  /* DOS 2+ only */
          dos2 = TRUE;
          returnpkt(DOSFALSE,0);
          break;

     case ACTION_CURRENT_VOLUME:
          returnpkt(dvnode,0);
          break;

     case ACTION_FLUSH:   /* flush buffers, NOOP */
          returnpkt(DOSTRUE,0);
          break;

     case ACTION_LOCATE_OBJECT:  /* lock */
          if(dos2) returnpkt(DOSFALSE,ERROR_ACTION_NOT_KNOWN);
          else     returnpkt(mypkt->dp_Arg1,0);
          break;

     case ACTION_FREE_LOCK:      /* unlock */
          if(dos2) returnpkt(DOSFALSE,ERROR_ACTION_NOT_KNOWN);
          else     returnpkt(DOSTRUE,0);
          break;

     case ACTION_SET_PROTECT:  /* SetProtection */
     case ACTION_DISK_INFO:    /* info */
          returnpkt(DOSFALSE,ERROR_ACTION_NOT_KNOWN);
          break;

     default:  /* others not supported */
          returnpkt(DOSFALSE,ERROR_ACTION_NOT_KNOWN);
          Mprintf("Unsupported_Pkt=%ld\n",mypkt->dp_Type);

     } /* end of switch */
 } /* end of loop */
} /* end of _main() */

/**************************************************************************/

int DoSense(long iocode)
{
   UBYTE snskey;
   static char *snstext[] = {
       "NONE" , "RECOVERED ERROR", "NOT READY", "MEDIUM ERROR",
       "HARDWARE ERROR", "ILLEGAL REQUEST", "UNIT ATTENTION", "DATA PROTECT",
       "BLANK CHECK", "KEY=9", "COPY ABORTED", "ABORTED COMMAND", "KEY=C",
       "VOLUME OVERFLOW", "KEY=E", "KEY=F", "FILEMARK", "END-OF-MEDIUM",
       "INCORRECT BLOCK LENGTH"
   };
   static char *sderror[] = {
       "SELF-UNIT", "DMA", "PHASE", "PARITY", "SELECT-TIMEOUT", "BADSTATUS"
   };

   if(iocode==0 || iocode==HFERR_BadStatus)  {
       TapeIO(TSENSE,0,CTLWAIT);
       if((sns[0] & 0x70)==0x70) {  /* extended */
          snskey = sns[2] & 0x0f;  /* real sense key */
          if(snskey==0) {          /* pseudo sense keys */
              if(sns[2] & 0x20) snskey = ILI;
              if(sns[2] & 0x80) snskey = FMK;
              if(sns[2] & 0x40) snskey = EOM;
          }
       }
       else snskey = sns[0] & 0x0f; /* non-extended */
       linktp->sense = snstext[snskey];  /* keep last error info */
       linktp->xsns1 = sns[12];
       linktp->xsns2 = sns[13];
       linktp->sns = sns;  /* flag for tapemon to print all data */
       Mprintf("Sense: %s, other= %02X,%02X\n", snstext[snskey], sns[12],sns[13]);
       linktp->sns = NULL;
       return((int)snskey);
   }
   else if(iocode==FAKEOM) return(EOM);  /* DAC only */
   else {
       if(iocode>=40 && iocode<=45)
            Mprintf("SCSI %s ERROR\n",sderror[iocode-40]);
       else Mprintf("SCSI DRIVER ERROR CODE %lu\n",iocode);
       return(SERR);
   }
}

/**************************************************************************/
/* postape():  position the tape medium at the place we want to open it
*/

int postape(int how, int fm, long where)
{
 long x;  int y=0;  BOOL slow=TRUE;
   switch(how) {
    case POSNREW:            /* TAPE:*  */
       if(filenum>=0) {
          if(SEQ) blknum = 0;
          break;
       } /* else pos unknown, fall thru and rewind */
    case POSREW:             /* TAPE:   */
       if(x=TapeIO(TREWIND,0,CTLWAIT)) y=DoSense(x);
       blknum = reserved;
       break;
    case POSAPP:            /* TAPE:APP */
       if(x=TapeIO(TSKIPE,0,CTLWAIT)) y=DoSense(x);
       if(lastwrtn<0) filenum = -1;
       slow=FALSE; /* & fall thru */
    case POSEND:            /* TAPE:END */
       if((slow && lastwrtn<0) || y==ILL) {
          /* drive doesn't support skip-eod, or tape:end */
          y=0;
          if(!slow) Mprintf("(doing it the hard way)\n");
          if(x=TapeIO(TREWIND,0,CTLWAIT)) DoSense(x);
          while(y==0) {   /* skip files until we hit a blank spot */
             Mprintf("Skipping over file %ld\n",filenum);
             if(x=TapeIO(TSKIPF,fm,CTLWAIT)) y=DoSense(x);
             if(y==FMK) y=0;
             filenum++;
          }
          if(y==BLC) { y=0;  filenum--; }
       }
       blknum = 0;
       break;
    case POSSKIP:            /* TAPE:n */
       if(DAC) blknum = where;
       else { /* SEQ */
         if(x=TapeIO(TREWIND,0,CTLWAIT)) { y=DoSense(x); break; }
         if(fm==0) break;
         Mprintf("Skipping to file %ld\n",where);
         if(x=TapeIO(TSKIPF,where*fm,CTLWAIT)) y=DoSense(x);
         if(y==FMK) y=0;
         blknum = 0;
       }
       filenum = where;
       lastwrtn = -1;
       break;
   }
   if(y) Mprintf("Unable to position tape\n");
   return(y);
}

/**************************************************************************/
/* wrteot:  handle messiness that happens when writing at end of tape.
      Returns non-zero status if not EOT or tape swap unsuccessful. */

long wrteot(int bfn, int dec, long stat)
{
   extern long actual;
   long saveboff, nn;
   long residue=0;
   long ios = stat;
   int  s = DoSense(ios);
   if(s==EOM || s==VOF) {   /* EOT? */
      if(s==VOF && (sns[0]&0xF0)==0xF0) residue = ((long)sns[5]<<8) + sns[6];
      if(residue) Mprintf("Holding %ld unwritten blocks\n",residue);
      if(residue>numblks) { Mprintf("OOPS, data lost!\n"); residue=0; }
      TapeIO(TREWIND,0,CTLWAIT);
      if(NewTape()) {       /* ask for new tape */
         /* shift unwritten bytes to start of buffer and rewrite on new tape */
         if(residue) {
            nn = (numblks-residue)*blksize;
            saveboff = Boff;
            Boff = residue*blksize;
            if(nn) memmove(TapeBuff[bfn],TapeBuff[bfn]+nn,Boff);
            blknum = reserved;
            ios=TapeIO(TWRITE,bfn,CTLWAIT);
            if(ios) s=DoSense(ios);
            blknum += actual;
            Boff = saveboff;
         }
         else ios=0;
      }
   }
   if(s==RCV) ios=0;  /* ignore recoveries */
   return(ios);
}

/**************************************************************************/

void freestuff(void)
{
  if(TapeBuff[0]) FreeMem(TapeBuff[0],TBSize);
  if(TapeBuff[1]) FreeMem(TapeBuff[1],TBSize);
  TapeBuff[0] = TapeBuff[1] = NULL;
  return;
}

/**************************************************************************/
/* getstart:
    Given pointer (z) to a string of the form "SS-nn/..."
    returns a number corresponding to SS, the binary value of nn,
    and the pointer is updated to point after the slash.
*/
int getstart(long *num)
{
  extern char *z;
  #define NOTFND 99
  #define ENDLST 100
  #define NKEYS  19  /* number of keywords */
  static char *keys[NKEYS] =
  { "UN","BS","NB","RB","BT","FM","ER","OV","DF","C9",
  /* 0    1    2    3    4    5    6    7    8    9 */

    "NR","RT","RC","EC","SW","RO","AO","MR","DB"};
  /* 10   11   12   13   14   15   16   17   18 */
  char *ii, *jj;  int kk;

  if (*z == '\0') return(ENDLST);      /* return if end of string */
  for (ii=z;  *ii != '-'; ii++);       /* find the dash */
  for (jj=z;  *jj != '/' && *jj !='\0'; jj++); /* find the slash */
  *ii = '\0'; *jj = '\0';              /* null-terminate name & number */
  *num = (long) strtoul (++ii,NULL,0); /* return converted number */
  ii = z;     z = ++jj;                /* update ptr to next item */
  for (kk=0; kk <= NKEYS; kk++)  {     /* search for keyword */
     if (*ii == *keys[kk] && *(ii+1) == *(keys[kk]+1) )
        return(kk);                    /* return index of keyword if found */
  }
  return(NOTFND);                      /* didn't find keyword */
}

/**************************************************************************
   Mprintf requests that the TapeMon program print the message in 'dbb'.
    Since this handler cannot do DOS I/O, it must beg the TapeMon program,
    possibly running in a CLI somewhere, to do the printf for it.  If the
    TapeMon is running, it will have sent us a message containing the port
    pointer for TapeMon, so we can send messages to it.
*/

void  Mprintf(char *format, ...)
{
  va_list ap;

   va_start(ap,format);
   if(monport) {
      if(mbusy) {   /* still waiting for reply from tapemon? Wait.*/
         WaitPort(btnport);
         GetMsg(btnport);
      }
      vsprintf(linktp->dbb,format,ap);
      monmsg.mptr = linktp;
      PutMsg(monport,&monmsg.bmsg);
      mbusy=TRUE;
   }
   va_end(ap);
   return;
}

/**************************************************************************/
/* toUPPER: convert string to upper case */

void toUPPER(char *zz)
{
  for(; *zz != '\0'; zz++)  if(*zz >= 'a') *zz -= 0x20;
  return;
}

/**************************************************************************
/* swapbytes: swap byte pairs */

void swapbytes(char *zz)
{
   char x;
   int n;
   long len = TBSize/2;
   for(n=0; n<len; n++) {
     x = *zz;
     *zz = *(zz+1);
     *(zz+1) = x;
     zz += 2;
   }
   return;
}

/**************************************************************************
*  NewTape()   Handles transition to a new tape.
*/

long NewTape(void)
{
 long choice,s,t,v;
 static char rqm[40];
 Mprintf("Time to insert tape# %d\n",++tapenum);
 sprintf(rqm," Insert tape %d for %s",tapenum,devname);
 do {
    s=NOS;
    choice= ask(rqm," Continue ", " Quit ");
    if(choice)
      do {
        if(t=TapeIO(TREADY,0,CTLWAIT)) s=DoSense(t);  /* eat unit-atten */
        else s=NOS;
        if(s==UAT && reten) if(v=TapeIO(TRETEN,0,CTLWAIT)) DoSense(v);
      } while(s==UAT);
 } while(s!=NOS);
 if(choice) {
    if(s=TapeIO(TREWIND,0,CTLWAIT)) DoSense(s);
    if(DAC) blknum=reserved;  /* reset block number */
 }
 return(choice);
}

/**************************************************************************
*  ask()   Displays a yes/no requester.
*/

long ask(char *txt, char *yes, char *no)
{
#define ITXT { AUTOFRONTPEN,AUTOBACKPEN,AUTODRAWMODE,AUTOLEFTEDGE, \
                  AUTOTOPEDGE, AUTOITEXTFONT, NULL, NULL }
 static struct IntuiText rtxt[3] = { ITXT, ITXT, ITXT };
 if(!IntuitionBase) return(0);
 rtxt[0].IText = (UBYTE *) txt;
 rtxt[1].IText = (UBYTE *) yes;
 rtxt[2].IText = (UBYTE *) no;
 return(AutoRequest(NULL,&rtxt[0],&rtxt[1],&rtxt[2],NULL,NULL,300,50));
}


/**************************************************************************
 * returnpkt() - reply DOS packet.
 *   Adapted from "misc.c" code by Phillip Lindsay (C) Commodore 1986
*/

void returnpkt(ULONG res1, ULONG res2)
{
 struct Message *mess;
 struct MsgPort *replyport;

#ifdef DBUG
  if(dbug) Mprintf("REPLY  %08X %08X\n", res1, res2);
#endif

 mypkt->dp_Res1          = res1;
 mypkt->dp_Res2          = res2;
 replyport               = mypkt->dp_Port;
 mess                    = mypkt->dp_Link;
 mypkt->dp_Port          = &myproc->pr_MsgPort;
 mess->mn_Node.ln_Name   = (char *) mypkt;
 mess->mn_Node.ln_Succ   = NULL;
 mess->mn_Node.ln_Pred   = NULL;

 PutMsg(replyport,mess);
}

