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

#define TVERSION "-BTNTAPE V2.0 RAR-" ## __DATE__

/*  (c) Copyright 1990, 1991  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.
 *  Source is ANSI C compliant.  Compile with SAS/C v5 or Manx v5.
 *
 *  This handler works in conjunction with the accompanying TapeMon program.
 *----------------------------------------------------------------------------
 */

#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>

#if defined AZTEC_C
  #include <functions.h>
/*#define strtoul strtol */
#elif defined LATTICE
  #include <proto/exec.h>
  #include <proto/intuition.h>
#endif

#include "tape.h"
#include "tplink.h"

/* sense keys */
#define NOS 0x00  /* no sense */
#define RCV 0x01  /* recovered error */
#define UAT 0x06  /* unit attention */
#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  */

struct things {  /* a collection of things we will have to alloc */
         UBYTE cmdbuff[16];         /* SCSI command buff */
         UBYTE snsarea[64];         /* sense data buffer */
         UBYTE inqdata[40];         /* inquiry data buff */
         struct SCSICmd scsicmd;    /* SCSIdirect cmd buff */
         UBYTE pad[16];  /* SCSICmd is smaller in old include files */
         } ;

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

struct IntuitionBase *IntuitionBase;
UBYTE           *cdb;          /* pointer to tape 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]   /* pointers to 2 tape buffers     */
                            ={NULL,NULL};
struct tplink   *linktp;       /* pointer to link structure      */
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  tranlen;                /* bytes/blks in a read/write     */
ULONG  bugmask = 0;            /* 2090A bug circumvention        */
ULONG  fmarks  = 1;            /* # file marks for write/skip    */
long   tpsize ;                /* tape size in blocks            */
long   reserved = 0;           /* number of reserved blks at BOT */
int    tapenum;                /* tape volume number             */
int    inprog = FALSE;         /* io operation in progress flag  */
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  fixedbit = 0;           /* fixed-block mode bit for SEQ   */

/***********************  Main program  ********************************/
#ifdef AZTEC_C
  #pragma intfunc(_main())
#endif
void _main(void)
{
 struct tplink      tpl;       /* structure to link hndlr & mon     */
 struct Process     *myproc;   /* ptr to handler's process struct   */
 struct DosPacket   *mypkt;    /* ptr to dos packet sent            */
 struct DeviceNode  *mynode;   /* ptr to devnode passed in pkt Arg3 */
 struct things      *xarea;    /* ptr to dynamic misc. areas        */
 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)*/
 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    Boff;          /* current offset in tape buffer     */
 long    remain;        /* bytes remaining in tape buffer    */
 long    Supra=FALSE;   /* flag for Supra circumvention      */
 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 RAWMODE 3       /* raw command mode */
    #define UMODSEL 4       /* user mode select */
 int     Bn;            /* current buffer number, 0 or 1     */
 int     dirty=FALSE;   /* buffer has unwritten data in it   */
 int     Ctl=CTLIMM;    /* Controls overlapped dos/scsi I/O  */
 BYTE    acksig;        /* monitor acknowledge signal number */
 UBYTE   varblk = 0;    /* variable-block flag for SEQ       */

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

 IntuitionBase = (struct IntuitionBase *)OpenLibrary("intuition.library",0);
 myproc  = (struct Process *) FindTask(0L);          /* find this process */
 mypkt = taskwait();                           /* wait for startup packet */
 /* 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 for the tape monitor:  install pointer to tplink
  ======== structure in the free pointer of the task block, so the tape
  ======== monitor can find it after FindTask().
*/
 tpl.keyword = "TapeHandler";
 tpl.version = TVERSION;
 tpl.devnode = (void *)mynode;
 tpl.dbb     = dbb;
 tpl.unit    = &unit;
 tpl.Lun     = &Lun;
 linktp = &tpl;
 ((struct Task *)myproc)->tc_UserData = (APTR) linktp;

/*======== 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:  /* LU */  Lun  = ((UBYTE)x & 7) << 5 ;  break;
     case 2:  /* BS */  blksize = (ULONG) x;  break;
     case 3:  /* NB */  numblks = (ULONG) x;  break;
     case 4:  /* RB */  reserved = x;  break;
     case 5:  /* BT */  bufmemtype = (ULONG) x;  break;
     case 6:  /* FM */  fmarks = x & 0xff;  break;
     case 7:  /* VB */  if(x) varblk = 1;   break;
     case 8:  /* OV */  if(!x) Ctl = CTLWAIT; break;
     case 9:  /* DF */  dflags = x; break;
     case 10: /* SU */  Supra = x;  break;
     case 11: /* C9 */  if(x) bugmask = 0x01000000; break;
     case 99: /* ?? */  tpl.badparm=TRUE; break;
     default: ;
   }
 }
 rwlen = TBSize = numblks * blksize;   /* size of a tape buffer */
 if(Supra)  rwlen = 0;

/*======== Allocate some memory for non-data buffers */

 if( !(xarea = (struct things *)
           AllocMem(sizeof(struct things), bufmemtype | MEMF_CLEAR) ))
           { returnpkt(mypkt,DOSFALSE,ERROR_NO_FREE_STORE);
             CloseLibrary((struct Library *)IntuitionBase);
             return;
           }
 cdb = &xarea->cmdbuff[0];
 sns = &xarea->snsarea[0];
 inq = tpl.inquiry = &xarea->inqdata[0];
 cmd = &xarea->scsicmd;
 ior = (struct IOStdReq *) CreateExtIO( CreatePort(0,0),
                                         sizeof(struct IOStdReq));

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

 if ( OpenDevice(driver,unit,(struct IORequest *)ior,dflags)  )  {
    returnpkt(mypkt,DOSFALSE,ERROR_INVALID_COMPONENT_NAME);
    CloseLibrary((struct Library *)IntuitionBase);
    FreeMem(xarea,sizeof(struct things));
    return;
 }
 mynode->dn_Task = &myproc->pr_MsgPort;    /* install handler taskid */
 returnpkt(mypkt,DOSTRUE,mypkt->dp_Res2);  /* reply to initial packet */

/*======== Allocate the signal that TapeMon will
  ======== use to acknowledge MPR requests.
*/
 acksig = AllocSignal(-1);
 if(acksig != -1) tpl.handsig = 1UL << acksig;
 /* else { monitor will not attempt to signal us } */

/*======= Find the SCSI device type by INQUIRY.  Sequential or direct access?
*/
 if(TapeIO(INQUIRY,0,CTLWAIT)) TapeIO(TSENSE,0,CTLWAIT);
 inq[36] = 0;  /* null-terminate vendor info */
 if(SEQ) {
   reserved = 0;
   fixedbit = varblk ^ 0x01;  /* fixed or variable block mode */
   tranlen = (fixedbit) ? numblks : TBSize;
 }
 else /* DAC */ tranlen = numblks;

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

 for (;;)  {
   mypkt = taskwait();          /* wait for a packet */
   switch(mypkt->dp_Type) {

     case ACTION_FINDINPUT:  /*----------- Open() ------------*/
     case ACTION_FINDOUTPUT:
          if(opmode != CLOSED)  {
             returnpkt(mypkt,DOSFALSE,ERROR_OBJECT_IN_USE);
             break;
          }
          /* 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();
               returnpkt(mypkt,DOSFALSE,ERROR_NO_FREE_STORE);
               MPR0("Can't get memory for tape buffers\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);
          for(z=devname; *(z++)!=':' ;);
          if     (!strcmp(z,"RAWCMD"))  opmode=RAWMODE;
          else if(!strcmp(z,"MODESEL")) opmode=UMODSEL;
          else {    /* normal read/write */
             opmode = (mypkt->dp_Type==ACTION_FINDINPUT) ? READING : WRITING;
             if(!strcmp(z,"*")) gotopos=blknum;     /* current position */
             else if(*z>='0' && *z<='9')     /* specific position */
                   gotopos = (ULONG) strtoul (z,NULL,0);
             else if(*z=='\0') gotopos=0;    /* beginning of tape */
             else goto BADNAME;
          }
          if(opmode>=RAWMODE && mypkt->dp_Type==ACTION_FINDINPUT)  {
            BADNAME:
             freestuff();   /* can't read from raw/ms */
             opmode=CLOSED;
             returnpkt(mypkt,DOSFALSE,ERROR_ACTION_NOT_KNOWN);
             break;
          }

          /* Check device ready */
          if(x=TapeIO(TREADY,0,CTLWAIT))  {
             y=DoSense(x);
             if     (y==UAT) blknum = reserved;
             else if(y==NOS) ;
             else {
               freestuff();
               opmode=CLOSED;
               returnpkt(mypkt,DOSFALSE,ERROR_DEVICE_NOT_MOUNTED);
               break;
             }
          }

          if(opmode<=WRITING) {

            /* Check write-prot, block length */
            if(TapeIO(MDSNS,0,CTLWAIT))  TapeIO(TSENSE,0,CTLWAIT);
            else {
               if(opmode==WRITING && (sns[2] & 0x80)) {
                 freestuff();
                 opmode=CLOSED;
                 returnpkt(mypkt,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 */
                   TapeIO(MDSET,(int)varblk,CTLWAIT);
                   TapeIO(TSENSE,0,CTLWAIT);
                 }
               }
            }

            /* Position tape */
            if(gotopos==0) {    /* tape: */
               TapeIO(TREWIND,0,CTLWAIT);
               blknum = reserved;
            }
            else if(gotopos != blknum) {   /* tape:n */
               if(DAC) blknum = gotopos;
               else {
                 TapeIO(TREWIND,0,CTLWAIT);
                 if(x=TapeIO(TSKIP,(int)gotopos,CTLWAIT)) DoSense(x);
                 blknum = 0;
               }
            }
            else if(SEQ) blknum = 0;  /* tape:*  */

            /* Get capacity for 3M tape */
            if(DAC) {
               tpsize = 0x7fffffff;
               if(TapeIO(RDCAP,0,CTLWAIT)) TapeIO(TSENSE,0,CTLWAIT);
               else {
                 tpsize = ((sns[2] << 8) | sns[3]) + 1;
                 MPR1("Capacity: %u blocks\n", tpsize);
               }
            }
          }

          dirty=FALSE; inprog=FALSE; Boff=0; pkcnt=0; tapenum=1;
          MPR2("%s Opened at block %u\n",devname,blknum);

          if(opmode==READING) {  /* for reads, prefetch 1st buffer */
             iostat = TapeIO(TREAD,0,CTLWAIT);
             remain=0;  Bn=1;
          }
          else { remain=TBSize;  Bn=0;  iostat=0; }

          returnpkt(mypkt,DOSTRUE,mypkt->dp_Res2);
          break;


     case ACTION_END:        /*----------- Close() -----------*/
          switch(opmode) {
             case CLOSED:
                returnpkt(mypkt,DOSFALSE,ERROR_ACTION_NOT_KNOWN); break;
             case RAWMODE:
                if(x=TapeIO(RAWCMD,Bn,CTLWAIT)) DoSense(x); break;
             case UMODSEL:
                if(x=TapeIO(USRMODE,Bn,CTLWAIT)) DoSense(x); break;
             case READING:
                if(inprog) iostat = TapeIO(TFINISH,0,CTLWAIT);
                if(iostat) DoSense(iostat);
                break;
             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) { MPR0("Error writing final block\n"); }
                else if(pkcnt) {
                   blknum += numblks;  x=0;
                   if(SEQ) x=TapeIO(WFMARK,0,CTLWAIT);
                   if(x) DoSense(x);
                }
          }
          returnpkt(mypkt,DOSTRUE,mypkt->dp_Res2);
          opmode=CLOSED;
          freestuff();
          MPR1("Closed at block %u\n",blknum);
          break;

     case ACTION_READ:       /*----------- Read() ------------*/
          if(opmode != READING) {
            MPR0("Function/mode error\n");
            mypkt->dp_Arg3 = -1;
            goto RDEND;
          }
          dptr = (UBYTE *) mypkt->dp_Arg2;
          dcnt =           mypkt->dp_Arg3;
          while(dcnt)   {
            if(remain==0)   {
               if(inprog) iostat = TapeIO(TFINISH,0,CTLWAIT);
               if(iostat) {  /* check status of previous read */
                  y=DoSense(iostat);  iostat=0;
                  switch(y) {
                    case RCV:
                       y=0; break;
                    case FMK:
                       mypkt->dp_Arg3 = 0;  break;
                    case VOF:
                    case EOM:  /* End of Tape: (sequential) */
                       if(NewTape())  {   /* ask for new tape */
                          y=0;            /* and reread buffer */
                          iostat=TapeIO(TREAD,(Bn^1),CTLWAIT);
                          if(iostat) y=DoSense(iostat); /* and fall thru */
                          else break;
                       }
                    default:
                       MPR0("Error during read\n");
                       mypkt->dp_Arg3 = -1;
                  }
                  if(y) goto RDEND;
               }
               blknum += numblks;
               iostat = TapeIO(TREAD,Bn,Ctl); /* start refilling this buffer */
               Bn ^= 1;      /* switch to other (full) buffer */
               remain = TBSize;
               Boff = 0;
            }
            mcnt = (dcnt>remain) ? remain : dcnt;
            memcpy (dptr, &TapeBuff[Bn][Boff], mcnt);
            dcnt -= mcnt ;
            Boff += mcnt ;
            dptr += mcnt ;
            remain -= mcnt ;
          }
          RDEND:
          returnpkt(mypkt,mypkt->dp_Arg3,mypkt->dp_Arg2);
          break;


     case ACTION_WRITE:      /*----------- Write() -----------*/
          if(opmode < WRITING) {
            MPR0("Function/mode error\n");
            mypkt->dp_Arg3 = -1;
            goto WRTEND;
          }
          pkcnt++;
          dptr = (UBYTE *) mypkt->dp_Arg2;
          dcnt =           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 */
                    mypkt->dp_Arg3 = -1;
                    MPR0("Error during write\n");
                    iostat=0;  dirty=FALSE;  pkcnt=0;
                    goto WRTEND;
                 }
               }
               iostat = TapeIO(TWRITE,Bn,Ctl); /* start writing full buffer */
               blknum += numblks;
               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(mypkt,mypkt->dp_Arg3,mypkt->dp_Res2);
          break;


     case ACTION_CURRENT_VOLUME:  /* info not supported */
          returnpkt(mypkt,dvnode,DOSFALSE);
          break;

     case ACTION_LOCATE_OBJECT:  /* lock */
          returnpkt(mypkt,mypkt->dp_Arg1,mypkt->dp_Res2);
          break;

     case ACTION_FREE_LOCK:      /* unlock */
          returnpkt(mypkt,DOSTRUE,mypkt->dp_Res2);
          break;

     case ACTION_FLUSH:   /* flush buffers, NOOP */
          returnpkt(mypkt,DOSTRUE,mypkt->dp_Res2);
          break;

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

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

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

int DoSense(long iocode)
{
   /* iocode is (ioerr<<8 + cmdstatus) */
   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"
   };
   iocode=iocode>>8;

   if(iocode==0 || iocode==HFERR_BadStatus)  {
       TapeIO(TSENSE,0,CTLWAIT);
       if((sns[0] & 0x70)==0x70) {  /* extended */
          if     (sns[2] & 0x80) snskey = FMK;  /* pseudo sense keys */
          else if(sns[2] & 0x40) snskey = EOM;
          else if(sns[2] & 0x20) snskey = ILI;
          else                   snskey = sns[2] & 0x0f;  /* real sense key */
       }
       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 */
       MPR3("Sense: %s, other= %02X,%02X\n", snstext[snskey], sns[12],sns[13]);
       linktp->sns = NULL;
       return((int)snskey);
   }
   else { MPR1("SCSI %s ERROR\n",sderror[iocode-40]);
       return(SERR);
   }
}

/**************************************************************************/
/* 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)
{
   long ios = stat;
   int  s = DoSense(ios);
   if(s==EOM || s==VOF) {   /* EOT? */
      if(NewTape()) {       /* ask for new tape */
         if(dec) blknum -= numblks;  /* and rewrite old buffer */
         ios=TapeIO(TWRITE,bfn,CTLWAIT);
         if(ios) s=DoSense(ios);
         if(dec) blknum += numblks;
      }
   }
   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  12  /* number of keywords */
  static char *keys[NKEYS] =
      { "UN","LU","BS","NB","RB","BT","FM","VB","OV","DF","SU","C9" };
     /*  0    1    2    3    4    5    6    7    8    9    10   11 */
  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 */
}

/**************************************************************************
   MonPrint 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 installed a pointer to its task in
    the link structure, and we can Signal() it.
*/

void MonPrint(void)
{
   if(linktp->montask)  {
      Signal(linktp->montask, linktp->monsig);
      Wait  (linktp->handsig);
      Signal(linktp->montask, linktp->monsig);
      Wait  (linktp->handsig);
   }
   return;
}

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

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

/**************************************************************************
*  NewTape()   Displays a requester asking user to insert a new tape.
*/

long NewTape(void)
{
 long choice;
 static char rqm[40];
 static struct IntuiText rtxt[] = {
    { AUTOFRONTPEN,AUTOBACKPEN,AUTODRAWMODE,AUTOLEFTEDGE,AUTOTOPEDGE,
       AUTOITEXTFONT, (UBYTE *) &rqm[0], AUTONEXTTEXT },
    { AUTOFRONTPEN,AUTOBACKPEN,AUTODRAWMODE,AUTOLEFTEDGE,AUTOTOPEDGE,
       AUTOITEXTFONT, (UBYTE *) "Continue", AUTONEXTTEXT },
    { AUTOFRONTPEN,AUTOBACKPEN,AUTODRAWMODE,AUTOLEFTEDGE,AUTOTOPEDGE,
       AUTOITEXTFONT, (UBYTE *) "Abort", AUTONEXTTEXT }
 };

 MPR1("Time to insert tape# %d\n",++tapenum);
 sprintf(rqm," Insert tape %d for %s",tapenum,devname);
 choice=AutoRequest(NULL,&rtxt[0],&rtxt[1],&rtxt[2],NULL,NULL,250,50);
 if(choice) {
     DoSense(TapeIO(TREADY,0,CTLWAIT));  /* eat unit-atten status */
     if(TapeIO(TREWIND,0,CTLWAIT)) TapeIO(TSENSE,0,CTLWAIT);
     if(DAC) blknum=reserved;  /* reset block number */
 }
 return(choice);
}

