[comp.sources.amiga] dfc

ain@j.cc.purdue.edu (Patrick White) (09/07/88)

Submitted by:	kddlab!ndsuvax!ncreed@uunet.uu.net (Walter Reed)
Summary:	An Intuitionized replacement for diskcopy and format
Poster Boy:	Patrick White	(ain@j.cc.purdue.edu)
Archive Name:	sources/amiga/volume5/dfc.s.sh.Z
Tested
 
NOTES:
    Re-share'ed.
    The "readme" file in the binaries shar is taken from the comments at
  the top of the source.
 
 
-- Pat White   (co-moderator comp.sources/binaries.amiga)
ARPA/UUCP: j.cc.purdue.edu!ain  BITNET: PATWHITE@PURCCVM  PHONE: (317) 743-8421
U.S.  Mail:  320 Brown St. apt. 406,    West Lafayette, IN 47906
[archives at: j.cc.purdue.edu.ARPA]
 
========================================
 
#	This is a shell archive.
#	Remove everything above and including the cut line.
#	Then run the rest of the file through sh.
#----cut here-----cut here-----cut here-----cut here----#
#!/bin/sh
# shar:	Shell Archiver
#	Run the following text with /bin/sh to create:
#	dfc.c
# This archive created: Tue Sep  6 13:04:48 1988
# By:	Patrick White (PUCC Land, USA)
cat << \SHAR_EOF > dfc.c
/*
 *   This is dfc, a disk copying and formatting utility written by Radical
 *   Eye Software.  This program is in the public domain; anyone can do
 *   anything they want with it.  This software is made available on an as-is
 *   basis; don't come to me if you destroy your entire fish disk library
 *   with it!  Of course, it was tested rather extensively before it was
 *   released . . .
 *
 *   This code was written with Manx 3.4b on February 8, 1988.
 *
 *   dfc is meant to replace both format and diskcopy, and be smaller than
 *   either.  It is meant to work exactly the same as either, depending on
 *   the command line arguments you give it.  In addition, it has several
 *   additional options, such as buffering of a disk for a quick additional
 *   copy, the ability to toggle verify mode on and off, and the ability to
 *   write to multiple disks at the same time.  It also has a nice Intuition
 *   interface.  It will not replace the `DiskCopy' or `Format' options from
 *   the workbench, however; these have to work with the Workbench startup
 *   message, which this program does not.
 *
 *   This program can be invoked with no command line options.  In this case,
 *   it defaults to a diskcopy from df0: to df1: with verify on and buffering
 *   on.  All keyword options can be preceded by a dash, for you Unix lovers
 *   out there, and can be abbreviated to one character.  The following
 *   parameters are accepted:
 *
 *   f[rom] disk       Use `disk' as the source drive.  The format for disk
 *                     is completely free; only the numerals in the argument
 *                     are looked at.
 *   t[o] disks        Use `disks' as the destination drives.  Again, the
 *                     format is unspecified.
 *   v[erify]          Verify all writes (default).
 *   nov[erify]        Turn off the verify mode.
 *   b[uffer]          Use extra buffer memory.  If your system has enough
 *                     memory, an entire disk will be cached in RAM.
 *                     Otherwise, almost all of the system memory will be
 *                     used.
 *   nob[uffer]        Do not buffer; use minimum memory.  (Default.)
 *   n[ame] diskname   Format the destination disk with name `diskname'.
 *                     Otherwise a diskcopy is assumed.
 *   d[...]            A drive argument.  Might not have a d; simple numbers
 *                     work as well.  The first occurance of such an argument
 *                     sets the source and destination fields; a subsequent
 *                     occurance only sets the destination.
 *
 *   Thus,
 *      dfc df0: name "Foo bar baz"
 *   formats drive df0: and names the resultant disk "Foo bar baz".
 *      dfc df0: df1:
 *   diskcopies from df0: to df1:.
 *      dfc -nov 0 123
 *   diskcopies from drive 0 to drives 1, 2, and 3, with verify turned off.
 *      dfc from df0: to df0:,df1:,df2:,df3:
 *   diskcopies from drive df0: to all four drives.
 *      dfc name Foo
 *   formats drive df1 with the name "Foo".
 *
 *   Once the program opens its window up, you will have a bunch of gadgets.
 *   The left-most column of gadgets is the source selection; here you can
 *   choose either format, or one of the four drives.  The next column is
 *   the destination column; choose any combination of the four drives by
 *   clicking on the gadgets.  Lines will be drawn between these two columns
 *   indicating the current selections.  If a drive is not available, no line
 *   will be drawn to it.
 *
 *   The topmost gadget is the name gadget.  This is used to set the name of
 *   your disk while formatting.
 *
 *   The `Go' gadget starts the program up; you had better have the disks in
 *   the drives when you hit this gadget!  The `Again' gadget makes another
 *   copy of the last disk, if buffering is on and enough buffers were
 *   allocated for an entire disk.  This gadget requires all destination
 *   disks to be in their drives already.
 *
 *   The `Retry' gadget tells the system to retry after a disk error.  The
 *   `Quit' gadget tells the system to abort an operation; you can hit this
 *   at an error, which aborts the current operation; you can hit this during
 *   a copy or format operation, which does the same; or you can hit this
 *   with the drives inactive, which exits the program.
 *
 *   The `Verify' gadget turns the verify mode on and off.  The `Buffer'
 *   gadget turns buffering on and off.
 *
 *   All of these gadgets have keyboard shortcuts which are simply the first
 *   letter of the gadget name.  The source gadgets can be selected with the
 *   digits `0', `1', `2', or `3'; the destination gadgets with the shifted
 *   versions of these keys (')', `!', `@', and `#'.)  The carriage return
 *   key is a synonym for `Go', for added diskcopy and format compatibility.
 *
 *   Enjoy this program!  Please send any bug reports to Tomas Rokicki,
 *   Box 2081, Stanford, CA  94309.
 */
#define TITLE "dfc 1 Radical Eye Software"
/*
 *   A handful of includes for good luck.
 */
#include "intuition/intuition.h"
#include "functions.h"
#include <exec/exec.h>
#include <exec/execbase.h>
#include <devices/trackdisk.h>
#include <libraries/dosextens.h>
/*
 *   These are the various globals this routine uses.
 */
struct StandardPacket *gpacket ;   /* packet to send various things */
struct IntuiMessage *message ;     /* the message we are working on */
struct Gadget *gadad ;             /* address of gadget from message */
struct Window *window ;            /* our window */
struct MsgPort *port ;             /* I/O port for dos communication */
struct IntuitionBase *IntuitionBase ;
                                   /* we need Intuition */
struct GfxBase *GfxBase ;          /* and graphics */
int havedisk ;                     /* do we have a full disk in RAM? */
int hibuf ;                        /* the highest buffer we have */
long output ;                      /* can we write output? */
char *buffers[81] ;                /* pointers to our buffers */
struct IOExtTD *diskreq[4] ;       /* our I/O request blocks */
long diskChangeCount[4] ;          /* last time disk was changed */
int source = 1 ;                   /* the source disk, not a mask */
int dest = 2 ;                     /* all destination drives */
int verify = 1 ;                   /* verify writes? */
int buffer = 0 ;                   /* are we using lots of memory? */
char namebuf[32] ;                 /* this buffer holds disk names */
char df0[] = "DF0:" ;              /* use only one copy of this name */
char df1[] = "DF1:" ;              /* ditto */
char df2[] = "DF2:" ;              /* ditto */
char df3[] = "DF3:" ;              /* ditto */
char *df[]={df0, df1, df2, df3};   /* for easy access to drive names */
char blank[] =   "                    " ;
                                   /* used to blank out the middle line */
char msg[] = "Disk df  is not complete" ;
                                   /* error message for a disk */
char reading[] = "Reading track xx dfx" ;
                                   /* message for reading disks */
char writing[] = "Writing track xx dfx" ;
                                   /* message for writing disks */
char veriing[] = "Ver'ing track xx dfx" ;
                                   /* message for ver'ing disks */
char nobuf[] = "! couldn't get buffer" ;
                                   /* if we can't get a buffer */
/*
 *   Always use topaz 80, and let's define an intuition buffer for our
 *   general string writing stuff.
 */
struct TextAttr myfont = {(STRPTR)"topaz.font", TOPAZ_EIGHTY, 0, 0 };
struct IntuiText intuitext = {1, 0, JAM2, 0, 0, &myfont} ;
/*
 *   Some defines setting the sizes of various things.  Play with these to
 *   resize things.  Too bad string gadgets still have problems with large
 *   fonts.
 */
#define WIDEGADG 83
#define NARROWGADG 51
#define STRGADG 256
#define GADGHEIGHT 13
#define LINE1 13
#define LINE2 LINE1+11
#define LINE3 LINE2+GADGHEIGHT
#define LINE4 LINE3+GADGHEIGHT
#define LINE5 LINE4+GADGHEIGHT
#define LINE6 LINE5+GADGHEIGHT
#define WINDOWHEIGHT LINE6+GADGHEIGHT+2
#define COL1 4
#define LINESTART COL1+NARROWGADG
#define STRSTART COL1+NARROWGADG+23
#define COL2 COL1+NARROWGADG+42
#define LINEEND COL2-1
#define COL3 COL2+NARROWGADG+10
#define COL4 COL3+WIDEGADG+10
#define WINDOWWIDTH COL4+WIDEGADG+4
#define TRACKSIZE (2*512*11)
/*
 *   Now, some defines to assist in declaring a few things, and
 *   centering strings, and the like.  These macros make the Intuition
 *   crap a lot easier to put together.
 */
#define makeintuitext(name,string,size) struct IntuiText name={1,0,JAM2,\
   (1+size-8*(sizeof(string)-1))/2,3,&myfont,(UBYTE*)string}
#define makebox(name,tname,width,height,off) short tname[]={0,0,width,0,\
   width,1,0,1,0,height-1,width,height-1,width,height,0,height,width,height,\
   width,0};\
   struct Border name={off,off,1,0,JAM2,10,tname}
#define makengadg(name,prev,xpos,ypos,text,ch) struct Gadget name={prev,\
   xpos,ypos,NARROWGADG,GADGHEIGHT,GADGHCOMP,RELVERIFY,BOOLGADGET,\
   (APTR)&narrowbox,NULL,&text,NULL,NULL,ch}
#define makewgadg(name,prev,xpos,ypos,text,ch) struct Gadget name={prev,\
   xpos,ypos,WIDEGADG,GADGHEIGHT,GADGHCOMP,RELVERIFY,BOOLGADGET,\
   (APTR)&widebox,NULL,&text,NULL,NULL,ch}
/*
 *   Now we declare all of our structures with the above macros.
 *   First, the text for our gadgets, and then the gadgets.  No sweat.
 */
makeintuitext(formattext,"Format",NARROWGADG);
makeintuitext(df0text,df0,NARROWGADG);
makeintuitext(df1text,df1,NARROWGADG);
makeintuitext(df2text,df2,NARROWGADG);
makeintuitext(df3text,df3,NARROWGADG);
makeintuitext(againtext,"Again",WIDEGADG);
makeintuitext(retrytext,"Retry",WIDEGADG);
makeintuitext(quittext,"Quit",WIDEGADG);
makeintuitext(verifyontext,"Verify On ",WIDEGADG);
makeintuitext(verifyofftext,"Verify Off",WIDEGADG);
makeintuitext(bufferontext,"Buffer On ",WIDEGADG);
makeintuitext(bufferofftext,"Buffer Off",WIDEGADG);
makeintuitext(gotext,"Go",WIDEGADG);
makebox(narrowbox,tn1,NARROWGADG-1,GADGHEIGHT-1,0);
makebox(widebox,tn2,WIDEGADG-1,GADGHEIGHT-1,0);
makebox(strbox,tn3,STRGADG-1,GADGHEIGHT-1,-2);
makengadg(formatgadg,NULL,COL1,LINE2,formattext,'f');
makengadg(sdf0gadg,&formatgadg,COL1,LINE3,df0text,'0');
makengadg(sdf1gadg,&sdf0gadg,COL1,LINE4,df1text,'1');
makengadg(sdf2gadg,&sdf1gadg,COL1,LINE5,df2text,'2');
makengadg(sdf3gadg,&sdf2gadg,COL1,LINE6,df3text,'3');
makengadg(ddf0gadg,&sdf3gadg,COL2,LINE3,df0text,')');
makengadg(ddf1gadg,&ddf0gadg,COL2,LINE4,df1text,'!');
makengadg(ddf2gadg,&ddf1gadg,COL2,LINE5,df2text,'@');
makengadg(ddf3gadg,&ddf2gadg,COL2,LINE6,df3text,'#');
makewgadg(gogadg,&ddf3gadg,COL3,LINE4,gotext,'g');
makewgadg(retrygadg,&gogadg,COL3,LINE5,retrytext,'r');
makewgadg(verifygadg,&retrygadg,COL3,LINE6,verifyontext,'v');
makewgadg(againgadg,&verifygadg,COL4,LINE4,againtext,'a');
makewgadg(quitgadg,&againgadg,COL4,LINE5,quittext,'q');
makewgadg(buffergadg,&quitgadg,COL4,LINE6,bufferontext,'b');
/*
 *   We need one last gadget, the string gadget, and its associated
 *   special info structure.
 */
struct StringInfo nameinfo={(UBYTE*)namebuf,NULL,0,31};
struct Gadget namegadg={&buffergadg,STRSTART+2,LINE2+2,STRGADG-4,
   GADGHEIGHT-4,GADGHCOMP,STRINGCENTER|RELVERIFY,STRGADGET,(APTR)&strbox,
   NULL,NULL,NULL,(APTR)&nameinfo,'n'};
/*
 *   Now we have our window structure.  Initially not resizeable.
 */
struct NewWindow newwindow = {200,20,WINDOWWIDTH,WINDOWHEIGHT,0,1,
   CLOSEWINDOW|VANILLAKEY|GADGETDOWN|GADGETUP,
   WINDOWDEPTH|WINDOWCLOSE|WINDOWDRAG|SMART_REFRESH|ACTIVATE,
   &namegadg,NULL,(UBYTE *)TITLE,NULL,NULL,-1,-1,-1,-1,WBENCHSCREEN};
/*
 *   Now we start coding.  This routine draws a string into the window at
 *   a specific X, Y location.
 */
draw(s, x, y)
char *s ;
int x, y ;
{
   intuitext.IText = (UBYTE *)s ;
   PrintIText(window->RPort, &intuitext, (long)x, (long)y) ;
}
/*
 *   This routine gives us those pretty little DF0:BUSY things, which keep
 *   AmigaDOS from futzing with the drives when we are playing with them.
 */
inhibit(d, t)
int d ;
long t ;
{
   struct MsgPort *handler ;
   struct StandardPacket *packet = gpacket ;

   handler = (struct MsgPort *)DeviceProc(df[d]) ;
   if (handler == NULL || port == NULL)
      return ;
   packet->sp_Msg.mn_Node.ln_Name = (char *)&(packet->sp_Pkt) ;
   packet->sp_Pkt.dp_Link = &(packet->sp_Msg) ;
   packet->sp_Pkt.dp_Port = port ;
   packet->sp_Pkt.dp_Type = ACTION_INHIBIT ;
   packet->sp_Pkt.dp_Arg1 = t ;
   PutMsg(handler, packet) ;
   WaitPort(port) ;
   GetMsg(port) ;
}
/*
 *   This routine looks at the global message and returns a character
 *   indicating the selected gadget.  This gives us easy equivalence
 *   of gadgets to vanillakeys, for instance.  This also replymsg()'s
 *   the message.
 */
int getop() {
   register long class ;
   register int op ;
   short code ;

   class = message->Class ;
   code = message->Code ;
   op = ' ' ;
   gadad = (struct Gadget *)(message->IAddress) ;
   ReplyMsg(message) ;
   message = NULL ;
   if (class == CLOSEWINDOW) {
      op = 'q' ;
   } else if (class == GADGETDOWN || class == GADGETUP) {
      op = gadad->GadgetID ;
   } else if (class == VANILLAKEY) {
      op = code ;
      gadad = NULL ;
   }
   return(upcase(op)) ;
}
/*
 *   We don't want to queue up messages, because the user might hit
 *   'g' 100 times accidentally.  We flush all pending messages, and
 *   return the last operation.  This way, this routine can be used
 *   to see if the user typed 'Q' to exit some operation.
 */
int disposemsgs() {
   register int op = 0 ;

   while (message = (struct IntuiMessage *)
                        GetMsg(window->UserPort))
      op = getop() ;
   return(op) ;
}
/*
 *   This is put into a function to help make the program smaller.
 *   It takes a character and makes sure it is uppercase.
 */
int upcase(c)
register int c ;
{
   if ('a' <= c && c <= 'z')
      return(c-32) ;
   else
      return(c) ;
}
/*
 *   Here we wait for a quit or retry key.  We also accept go, space,
 *   and carriage return.
 */
int abortretry() {
   register int op ;

   disposemsgs() ;
   while (1) {
      while ((message = (struct IntuiMessage *)
                     GetMsg(window->UserPort))==NULL)
         WaitPort(window->UserPort) ;
      op = getop() ;
      if (op == 'Q' || op == 'R' || op == ' ' || op == 'G' || op == 10)
         return(op) ;
   }
}
/*
 *   This routine pads a string out to 40 characters.  Used to write to
 *   the top line of the window.
 */
static char ibuf[41] ;
char *to40(s)
register char *s ;
{
   register int i = 0 ;
   register char *p = ibuf ;

   while (*s != 0)
      p[i++] = *s++ ;
   while (i < 40)
      p[i++] = ' ' ;
   return(p) ;
}
/*
 *   This error routine draws a message up at the top of the screen, and
 *   waits for a response.  After it gets one, it returns the response.
 *   It clears the top line afterwards.
 */
int error(s)
register char *s ;
{
   int op ;

   DisplayBeep(NULL) ;
   if (*s == '!' || !window) {
      if (output) {
         Write(output, s, (long)strlen(s)) ;
         Write(output, "\n", 1L) ;
      }
      cleanup() ;
   } else {
      draw(to40(s), COL1, LINE1+2) ;
      op = abortretry() ;
   }
   draw(to40(""), COL1, LINE1+2) ;
   return(op) ;
}
/*
 *   If the user selects a set of drives, this routine first tries to
 *   allocate them.  Then, based on the success of the allocation, it
 *   draws lines in the window indicating the source and destination
 *   drives.
 */
redrawlines() {
   register int i, j ;

   allocdisks() ;
   SetAPen(window->RPort, 0L) ;
   RectFill(window->RPort, (long)LINESTART, (long)LINE2, (long)STRSTART-1,
                           (long) LINE6+GADGHEIGHT) ;
   RectFill(window->RPort, (long)STRSTART, (long)LINE3, (long)LINEEND,
                           (long)LINE6+GADGHEIGHT) ;
   SetAPen(window->RPort, 1L) ;
   i = LINE3 + (GADGHEIGHT+1)/2 + source * GADGHEIGHT ;
   for (j=0; j<4; j++)
      if (dest & (1 << j)) {
         Move(window->RPort, (long)LINESTART, (long)i) ;
         Draw(window->RPort, (long)LINEEND,
                         (long)(LINE3 + (GADGHEIGHT+1)/2 + j * GADGHEIGHT)) ;
      }
}
/*
 *   This routine parses a string, usually something like 'df0:' or
 *   'df0:,df1:,df2:', but can be even '012', into just a mask indicating
 *   which drives were selected.  It simply looks for the characters '0',
 *   '1', '2', and '3'.
 */
int getmask(s)
register char *s ;
{
   register int t = 0 ;

   while (*s != 0) {
      if ('0' <= *s && *s <= '3')
         t |= 1 << (*s - '0') ;
      s++ ;
   }
   return(t) ;
}
/*
 *   This is our exit routine.  It frees the drives, deletes the ports,
 *   buffers, closes the window, and libraries.  Then it exits.
 */
cleanup() {
   register int i ;

   for (i=0; i<4; i++)
      FreeDisk(i) ;
   if (gpacket)
      FreeMem(gpacket, (long)sizeof(struct StandardPacket)) ;
   if (port)
      DeletePort(port) ;
   freebuffers() ;
   if (window)
      CloseWindow(window) ;
   if (GfxBase)
      CloseLibrary(GfxBase) ;
   if (IntuitionBase)
      CloseLibrary(IntuitionBase) ;
   exit(0) ;
}
/*
 *   This routine attempts to allocate m buffers.  We try and leave at
 *   least 32K of memory, even with the allocations.  We set hibuf at the
 *   exit point.
 */
getbuffers(m)
int m ;
{
   register int i ;

   if (buffers[80]==NULL &&
      (buffers[80]=(char *)
              AllocMem((long)TRACKSIZE, MEMF_CHIP | MEMF_PUBLIC))==NULL)
      error(nobuf) ;
   hibuf = 0 ;
   for (i=0; i<m; i++)
      if (buffers[i]==NULL &&
         (AvailMem(MEMF_PUBLIC) < 32000 ||
         (buffers[i]=(char *)AllocMem((long)TRACKSIZE, MEMF_PUBLIC))==NULL))
            break ;
      else
         hibuf = i+1 ;
   if (hibuf < 1)
      error(nobuf) ;
   for (; i<80; i++)
      if (buffers[i] != NULL) {
         FreeMem(buffers[i], (long)TRACKSIZE) ;
         buffers[i] = NULL ;
      }
}
/*
 *   And this routine lets them all go.
 */
int freebuffers() {
   register int i ;

   for (i=0; i<81; i++)
      if (buffers[i] != NULL) {
         FreeMem(buffers[i], (long)TRACKSIZE) ;
         buffers[i] = NULL ;
      }
}
/*
 *   We attempt to create an I/O request.  We allocate memory for it, and
 *   then initialize some of the ports.
 */
struct IORequest *CreatExtIO() {
   register struct IORequest *ioReq ;

   ioReq = (struct IORequest *)AllocMem((long)sizeof(struct IOExtTD),
                                        MEMF_CLEAR | MEMF_PUBLIC) ;
   if (ioReq == NULL)
      return (NULL) ;
   ioReq->io_Message.mn_Node.ln_Type = NT_MESSAGE ;
   ioReq->io_Message.mn_Node.ln_Pri = 0 ;
   ioReq->io_Message.mn_ReplyPort = port ;
   return(ioReq) ;
}
/*
 *   This routine frees an I/O request.
 */
DeleteIO(ioExt)
struct IORequest *ioExt ;
{
   FreeMem(ioExt, (long)sizeof(struct IOExtTD)) ;
}
/*
 *   Now we try to allocate a disk.  First, we attempt to allocate an
 *   I/O request, and then we actually open the device.  If either of
 *   these fail, we return 0.  This can occur if a drive is opened that
 *   doesn't exist.  After we have the disk, we inhibit AmigaDOS from
 *   putzing with it.
 */
int OpenDisk(i)
register int i ;
{
   register struct IOExtTD **p = diskreq+i ;

   if (*p)
      return(1) ;
   if ((*p = (struct IOExtTD *)CreatExtIO()) &&
       OpenDevice(TD_NAME, (long)i, *p, 0L) == 0) {
      inhibit(i, TRUE) ;
      return(1) ;
   } else {
      if (*p) {
         DeleteIO(*p) ;
         *p = NULL ;
      }
      return(0) ;
   }
}
/*
 *   Here we release a disk.  We check that we have it first!  Then, we
 *   close the device, uninhibit the drive, kill the I/O request, and
 *   exit.
 */
FreeDisk(i)
register int i ;
{
   register struct IOExtTD **p = diskreq+i ;

   if (*p) {
      CloseDevice(*p) ;
      inhibit(i, FALSE) ;
      DeleteIO(*p) ;
      *p = NULL ;
   }
}
/*
 *   This routine attempts to allocate all of the disks we need, based on the
 *   current settings of source and dest.  If a disk cannot be allocated, it
 *   is removed from both source or dest.  Source might be set to point to df0,
 *   if this occurs.  Then, if we can't allocate df0, we exit fatally.  This
 *   occurance can actually happen, if some other program has df0:.  (I think.)
 */
allocdisks() {
   register int i, need ;

top:
   need = dest ;
   if (source != -1)
      need |= 1 << source ;
   for (i=0; i<4; i++, need >>= 1) {
      if (need & 1) {
         if (! OpenDisk(i)) {
            dest &= ~(1 << i) ;
            if (source == i) {
               if (i == 0)
                  error("! I couldn't allocate the internal drive") ;
               source = 0 ;
               goto top ;
            }
        }
      } else
        FreeDisk(i) ;
   }
}
/*
 *   We call this routine if we are going to be accessing this
 *   disk.  It gets the changecount, so our read/write routines work.
 */
int InitDisk(d)
register int d ;
{
   register struct IOExtTD *p = diskreq[d] ;
   int result ;

   p->iotd_Req.io_Command = TD_CHANGENUM ;
   DoIO(p) ;
   result = (diskChangeCount[d] != p->iotd_Req.io_Actual) ;
   diskChangeCount[d] = p->iotd_Req.io_Actual ;
   return(result) ;
}
/*
 *   The plural of the above routine takes a mask and initializes all of
 *   the drives.  It adds the source drive to the list automatically.
 */
initdisks(mask)
int mask ;
{
   int d ;

   if (source != -1)
      mask |= (1 << source) ;
   for (d=0; d<4; d++)
      if (mask & (1 << d))
         InitDisk(d) ;
}
/*
 *   Kill the motor of a drive.  So someone can stick disks in and out.
 */
motoroff(d)
register int d ;
{
   register struct IOExtTD *p = diskreq[d] ;

   p->iotd_Req.io_Length = 0 ;
   p->iotd_Req.io_Command = TD_MOTOR ;
   DoIO(p) ;
}
/*
 *   This routine is a fast machine-language block move, that moves
 *   exactly one block of data.  Do not change TRACKSIZE and expect
 *   this still to work!
 */
fcpy(dest, src)
long *dest, *src ;
{
#asm
	movem.l	a0-a6/d0-d7,-(a7)
	move.l	12(a5),a0
	move.l	8(a5),a1
	move.l	#43,d0
lsdf:
	movem.l	(a0)+,a2-a6/d1-d7
	movem.l	a2-a6/d1-d7,(a1)
	add.w	#48,a1
	movem.l	(a0)+,a2-a6/d1-d7
	movem.l	a2-a6/d1-d7,(a1)
	add.w	#48,a1
	movem.l	(a0)+,a2-a6/d1-d7
	movem.l	a2-a6/d1-d7,(a1)
	add.w	#48,a1
	movem.l	(a0)+,a2-a6/d1-d7
	movem.l	a2-a6/d1-d7,(a1)
	add.w	#48,a1
	movem.l	(a0)+,a2-a6/d1-d7
	movem.l	a2-a6/d1-d7,(a1)
	add.w	#48,a1
	movem.l	(a0)+,a2-a5
	movem.l	a2-a5,(a1)
	add.w	#16,a1
	dbra	d0,lsdf
	movem.l	(a7)+,a0-a6/d0-d7
#endasm
}
/*
 *   Another fast assembly language routine for verifying a buffer.  This
 *   routine returns 0 if the two buffers are the same, and something else
 *   otherwise.
 */
int fcmp(dest, src)
long *dest, *src ;
{
	register int foo = 0 ;
#asm
	movem.l	d0/a0/a1,-(a7)
	move.l	12(a5),a0
	move.l	8(a5),a1
	move.l	#2815,d0
alsdf:
	cmp.l	(a0)+,(a1)+
	dbne	d0,alsdf
	move.w	d0,d4
	addq.w	#1,d4
	movem.l	(a7)+,a0/a1/d0
#endasm
   return(foo) ;
}
/*
 *   This routine turns on a particular gadget.
 */
turnon(g)
register struct Gadget *g ;
{
   if (g->Flags & GADGDISABLED) {
      SetAPen(window->RPort, 0L) ;
      RectFill(window->RPort, (long)g->LeftEdge, (long)g->TopEdge,
                              (long)g->LeftEdge+g->Width-1,
                              (long)g->TopEdge+g->Height-1) ;
      SetAPen(window->RPort, 1L) ;
      OnGadget(g, window, NULL) ;
   }
}
/*
 *   This routine turns off a particular gadget.
 */
turnoff(g)
register struct Gadget *g ;
{
   if (!(g->Flags & GADGDISABLED))
      OffGadget(g, window, NULL) ;
}
/*
 *   Our main copy routine.  The variable j holds the current destinations
 *   that are being written into; as disks drop like flies, j will drop
 *   them; at the end, we print a message about all the disks that dropped
 *   out.  We start by initializing the disks.
 */
goforit(again)
int again ;
{
   register int i, j, k, t ;
   register int ohbuf ;

   turnoff(&gogadg) ;
   turnoff(&verifygadg) ;
   turnoff(&buffergadg) ;
   turnoff(&againgadg) ;
   turnon(&retrygadg) ;
   turnoff(&namegadg) ;
   RefreshGadgets(window->FirstGadget, window, NULL) ;
   j = dest ;
   initdisks(j) ;
   ohbuf = hibuf ;
/*
 *   If we are formatting, we only use one buffer.  This avoids the
 *   unsightly delay which happens if we build up the formatted disk in
 *   memory first; the user wonders what the hell is going on.  We first
 *   check that the user isn't trying to read and write from the same disk;
 *   if he is, he will have to swap about 160 times, so we tell him no go.
 */
   if (source == -1)
      ohbuf = 1 ;
   if (ohbuf == 1 && source >= 0 && (dest & (1 << source))) {
      while (error("No buffering?") == 'R') ;
      goto finishup ;
   }
   for (t=0; t<80; t+=ohbuf) {
      if (! again) {
         for (k=0; k<ohbuf && t+k<80; k++) {
            if (disposemsgs()=='Q')
               goto aborted ;
            if (getdata(source, t+k, k)==0)
               goto finishup ;
         }
         if (source != -1) {
            if (dest & (1 << source)) {
               for (i=0; i<4; i++)
                  if (j & (1 << i))
                     motoroff(i) ;
               do {
                  if (error("Enter destination disks")=='Q')
                     goto finishup ;
               } while (! InitDisk(source)) ;
               initdisks(j) ;
            }
         }
         if (ohbuf == 80)
            havedisk = 1 ;
      }
      for (k=0; k<ohbuf && t+k<80; k++) {
         if (disposemsgs()=='Q')
            goto aborted ;
         for (i=0; i<4; i++) {
            if (j & (1 << i)) {
               if (writedata(i, t+k, k)==0) {
                  j &= ~(1 << i) ;
                  motoroff(i) ;
               }
            }
         }
      }
      if (! again) {
         if (source != -1 && (dest & (1 << source)) && t+k < 80) {
            motoroff(source) ;
            do {
               if (error("Enter source disk")=='Q')
                  goto finishup ;
            } while (! InitDisk(source)) ;
         }
      }
   }
   goto finishup ;
/*
 *   On exit, we turn off all the motors, clear out the middle line, and
 *   return.
 */
aborted:
   j = 0 ;
finishup:
   for (i=0; i<4; i++)
      if (diskreq[i])
         motoroff(i) ;
   for (i=0; i<4; i++)
      if ((dest-j) & (1<<i)) {
         msg[7] = i + '0' ;
         error(msg) ;
      }
   draw(blank, COL3, LINE3+3) ;
   disposemsgs() ;
}
/*
 *   This routine writes a given message to the screen; either reading,
 *   writing, or ver'ing.  It fills in the track and disk number.  It
 *   checks first that the source isn't `format', which is created behind
 *   the scenes instead of being read from an actual disk.  If we are
 *   reading from track 40, we get the name of the disk and put it up on
 *   the screen.
 */
writeop(s, t, d)
register char *s ;
register int t, d ;
{
   if (d != -1) {
      s[14] = '0' + t / 10 ;
      s[15] = '0' + t % 10 ;
      s[19] = '0' + d ;
      draw(s, COL3, LINE3+3) ;
   }
}
/*
 *   This routine gets data from disk d, track t, into buffer b.
 *   If the disk is -1, it gets it from the format routine (later.)
 *   We return 1 if success; 0 if failure.  If there is an error, we allow
 *   the user to retry as many times as he likes.
 */
int getdata(d,t,b)
register int d, t, b ;
{
   register struct IOExtTD *p = diskreq[d] ;
   register int i = 0 ;

   if (d==-1) {
      makeformatdata(t, b) ;
      return(1) ;
   }
   do {
      writeop(reading, t, d) ;
      p->iotd_Req.io_Length = TRACKSIZE ;
      p->iotd_Req.io_Data = (APTR)buffers[80] ;
      p->iotd_Req.io_Command = ETD_READ ;
      p->iotd_Count = diskChangeCount[d] ;
      p->iotd_Req.io_Offset = t * (long)TRACKSIZE ;
      DoIO(p) ;
      fcpy(buffers[b], buffers[80]) ;
   } while (p->iotd_Req.io_Error != 0 &&
            ((i=error("Read Error; Quit/Retry?"))!='Q')) ;
   if (t==40 && i != 'Q')
      writename(b) ;
   return (i != 'Q') ;
}
/*
 *   writedata is analagous to the above routine.  However, if verify is
 *   turned on, then we read the data back in from the disk and make sure
 *   that it is correct.  Note that whenever we write track 40, we first
 *   update the root block creation date and last modified date.
 */
int writedata(d,t,b)
register int d, t, b ;
{
   register struct IOExtTD *p = diskreq[d] ;
   register int i = 0 ;

top:
   if (t==40)
      updaterootblock(b) ;
   do {
      writeop(writing, t, d) ;
      fcpy(buffers[80], buffers[b]) ;
      p->iotd_Req.io_Length = TRACKSIZE ;
      p->iotd_Req.io_Data = (APTR)buffers[80] ;
      p->iotd_Req.io_Command = TD_FORMAT ;
      p->iotd_Count = diskChangeCount[d] ;
      p->iotd_Req.io_Offset = t * (long)TRACKSIZE ;
      DoIO(p) ;
   } while (p->iotd_Req.io_Error != 0 &&
            ((i=error("Write Error; Quit/Retry?"))!='Q')) ;
   if (i=='Q')
      return(0) ;
   if (verify) {
      writeop(veriing, t, d) ;
      p->iotd_Req.io_Length = TRACKSIZE ;
      p->iotd_Req.io_Data = (APTR)buffers[80] ;
      p->iotd_Req.io_Command = ETD_READ ;
      p->iotd_Count = diskChangeCount[d] ;
      p->iotd_Req.io_Offset = t * (long)TRACKSIZE ;
      DoIO(p) ;
      if ((p->iotd_Req.io_Error != 0 ||
          fcmp(buffers[80], buffers[b])) &&
          ((i=error("Verify Error; Quit/Retry?"))!='Q')) {
         if (t==0)
            InitDisk(d) ;
         goto top ;
      }
   }
   return (i != 'Q') ;
}
/*
 *   This routine creates data for the format option.  Note the clever
 *   way the data is built up; this routine should build a disk exactly
 *   the same way the standard AmigaDOS format does.  If we are on track
 *   40, some additional work must be done to create a root block and
 *   bitmap, but it's not too bad.
 */
makeformatdata(t, b)
int t, b ;
{
   register long *p ;
   register long cs ;
   register long i ;
   unsigned char *q ;

   p = (long *)buffers[b] ;
   cs = 'DOS\0' + (((long)t & 48) << 16) ;
   for (i=0; i<TRACKSIZE/4; i++)
      *p++ = cs + (i & 3327) ;
   if (t != 40)
      return ;
   p = (long *)buffers[b] ;
   for (i=0; i<256; i++)
      *p++ = 0 ;
   p = (long *)buffers[b] ;
   p[0] = 2 ;
   p[3] = 0x48 ;
   p[78] = 1 ;
   p[79] = 0x371 ;
   q = (unsigned char *)(p + 108) ;
   *q++ = strlen(namebuf) ;
   strcpy(q, namebuf) ;
   p[127] = 1 ;
   p += 128 ;
   for (i=1; i<55; i++)
      p[i] = 0xffffffff ;
   p[0] = 0xc000c037 ;
   p[28] = 0xffff3fff ;
   p[55] = 0x3fffffff ;
}
/*
 *   This routine recalculates the checksum for a block, and updates it in
 *   word 5.  The sum of all the words in a block must be 0.
 */
recheck(w)
register long *w ;
{
   register int i ;
   register long cs ;

   cs = 0 ;
   for (i=0; i<128; i++)
      cs += w[i] ;
   w[5] -= cs ;
}
/*
 *   We simply DateStamp the creation date, modification date, and
 *   rechecksum the block.
 */
updaterootblock(b)
register int b ;
{
   DateStamp(buffers[b] + 420) ;
   DateStamp(buffers[b] + 484) ;
   recheck(buffers[b]) ;
}
/*
 *   If we read from track 40, we write the name on the screen in
 *   the string gadget supplied for that purpose.
 */
writename(b)
int b ;
{
   int i = buffers[b][432] ;
   int j ;

   RemoveGadget(window, &namegadg) ;
   if (i > 31)
      i = 31 ;
   for (j=0; j<i; j++)
      namebuf[j] = buffers[b][j + 433] ;
   namebuf[j] = 0 ;
   AddGadget(window, &namegadg, -1L) ;
   RefreshGadgets(&namegadg, window, NULL) ;
}
/*
 *   And finally, our main routine!  This thing is awfully long; they do
 *   get that way sometimes, don't they?
 */
main(argc, argv)
int argc ;
char *argv[] ;
{
   int op ;
   register char *p, *q ;
   int seen = 0 ;

   output = (long)Output() ;
/*
 *   First, we parse the arguments.  Arguments allowed are documented at
 *   the top of this file.  If there is a dash as the first character of
 *   an argument, we ignore it, thus allowing Unix-style options.
 */
   while (argc > 1) {
      argc-- ;
      argv++ ;
      p = *argv ;
      if (argc > 1)
         q = argv[1] ;
      else
         q = "" ;
      if (*p == '-')
         p++ ;
      switch (upcase(*p)) {
/*
 *   If the argument starts with a D, or a number, it specifies a drive.
 *   Actually, it might be a `DRIVE' keyword, which would return a mask of
 *   0, so we check the return value for 0.
 */
case '0' : case '1' : case '2' : case '3' : case 'D' :
         dest = getmask(p) ;
         if (dest != 0) {
            if (! seen)
               source = dest ;
            seen = 1 ;
         }
         break ;
/*
 *   The from keyword sets the source . . .
 */
case 'F' :
         argc-- ;
         argv++ ;
         source = getmask(q) ;
         seen = 1 ;
         break ;
/*
 *   The to keyword sets the destination . . .
 */
case 'T' :
         argc-- ;
         argv++ ;
         dest = getmask(q) ;
         break ;
/*
 *   This could either be a `nobuffer', `noverify', or `name' keyword.
 *   We check for any of these.
 */
case 'N' :
         if (p[1]=='o' || p[1]=='O') {
            if (p[2]=='v' || p[2]=='V')
               verify = 0 ;
            else if (p[2]=='b' || p[2]=='B')
               buffer = 0 ;
            else goto errorarg ;
         } else {
            if (strlen(q) > 30)
               q[30] = 0 ;
            strcpy(namebuf, q) ;
            source = 0 ;
            argc-- ;
            argv++ ;
         }
         break ;
/*
 *   Verify keyword is easy
 */
case 'V' :
         verify = 1 ;
         break ;
/*
 *   As is the buffer keyword.
 */
case 'B' :
         buffer = 1 ;
         break ;
/*
 *   We go ahead and print an error message if we didn't understand an
 *   option.
 */
default:
errorarg:
         if (output) {
            Write(output, "Unknown option ", 15L) ;
            Write(output, p, (long)strlen(p)) ;
            Write(output, "\n", 1L) ;
         }
      }
   }
/*
 *   Up to this point, the source has been a mask.  Now we turn it into an
 *   integer.
 */
   if (source & 8)
      source = 3 ;
   else if (source & 4)
      source = 2 ;
   else if (source & 2)
      source = 1 ;
   else if (source & 1)
      source = 0 ;
   else
      source = -1 ;
/*
 *   We initialize a few gadgets based on the parameters the user selected.
 */
   if (verify) {
      verifygadg.GadgetText = &verifyontext ;
   } else {
      verifygadg.GadgetText = &verifyofftext ;
   }
   if (buffer) {
      buffergadg.GadgetText = &bufferontext ;
      hibuf = 80 ;
   } else {
      buffergadg.GadgetText = &bufferofftext ;
      hibuf = 1 ;
   }
/*
 *   And now we try and open things up!  First intuition, then graphics,
 *   then our window and an I/O port.  If any of these fail, we simply
 *   exit.
 */
   if ((IntuitionBase = (struct IntuitionBase *)OpenLibrary(
         "intuition.library",33L))!=NULL &&
       (GfxBase = (struct GfxBase *)OpenLibrary(
         "graphics.library",0L))!=NULL &&
         (window=OpenWindow(&newwindow))!=NULL &&
       (port=CreatePort(0L, 0L)) &&
       (gpacket=(struct StandardPacket *)AllocMem(
               (long)sizeof(struct StandardPacket),MEMF_PUBLIC|MEMF_CLEAR))) {
/*
 *   We draw the lines between what the user has selected, and enter into
 *   our main command loop.  Then, we wait for a message.  After getting one,
 *   we drop into a case statement keying off what message it was.
 */
      redrawlines() ;
      getbuffers(hibuf) ;
      while (1) {
         if (havedisk)
            turnon(&againgadg) ;
         else
            turnoff(&againgadg) ;
         turnoff(&retrygadg) ;
         turnon(&gogadg) ;
         turnon(&verifygadg) ;
         turnon(&buffergadg) ;
         if (source == -1)
            turnon(&namegadg) ;
         else
            turnoff(&namegadg) ;
         RefreshGadgets(window->FirstGadget, window, NULL) ;
         while ((message = (struct IntuiMessage *)
                        GetMsg(window->UserPort))==NULL)
            WaitPort(window->UserPort) ;
         op = getop() ;
         switch(op) {
/*
 *   A shift-0, shift-1, shift-2, or shift-3 toggles the appropriate bit
 *   in the destination, and redraws the lines.  A 0, 1, 2, or 3 sets the
 *   source to that drive, and continues.
 */
case ')' :
            dest ^= 1 ;
            goto redrawem ;
case '!' :
            dest ^= 2 ;
            goto redrawem ;
case '@' :
            dest ^= 4 ;
            goto redrawem ;
case '#' :
            dest ^= 8 ;
            goto redrawem ;
case '0' : case '1' : case '2' : case '3' :
            source = op - '0' ;
redrawem :
            redrawlines() ;
            break ;
/*
 *   If verify is selected, we toggle the state of the verify flag, and
 *   update the gadget to reflect this state.
 */
case 'V' :
            verify = ! verify ;
            RemoveGadget(window, &verifygadg) ;
            if (verify) {
               verifygadg.GadgetText = &verifyontext ;
            } else {
               verifygadg.GadgetText = &verifyofftext ;
            }
            AddGadget(window, &verifygadg, -1L) ;
            RefreshGadgets(&verifygadg, window, NULL) ;
            break ;
/*
 *   The buffer option does essentially the same thing, only for buffering.
 */
case 'B' :
            buffer = ! buffer ;
            RemoveGadget(window, &buffergadg) ;
            if (buffer) {
               buffergadg.GadgetText = &bufferontext ;
               getbuffers(80) ;
            } else {
               buffergadg.GadgetText = &bufferofftext ;
               getbuffers(1) ;
            }
            havedisk = 0 ;
            AddGadget(window, &buffergadg, -1L) ;
            RefreshGadgets(&buffergadg, window, NULL) ;
            break ;
/*
 *   If the user selects `format', then we set the source appropriately and
 *   redraw the lines.  Of course, we no longer have a disk, as when the
 *   format is executed, it will destroy buffer 0.
 */
case 'F' :
            source = -1 ;
            havedisk = 0 ;
            goto redrawem ;
/*
 *   The name gadget, if invoked from the keyboard with the `n' key,
 *   Activates the string gadget.
 */
case 'N' :
            if (gadad == NULL && source == -1) {
               ActivateGadget(&namegadg, window, NULL) ;
            }
            break ;
/*
 *   The `G' gadget, or carriage return, starts us on our merry way.
 */
case 'G' : case 10 : case 32 : case 13 :
            goforit(0) ;
            break ;
/*
 *   The `A' gadget insures that we have a disk first, otherwise it
 *   complains.
 */
case 'A' :
            if (! havedisk) {
               while (error("No disk in memory")=='R') ;
            } else {
               goforit(1) ;
            }
            break ;
/*
 *   Retry is ignored in the main command loop, as there is nothing to
 *   retry!
 */
case 'R' :
/*
 *   Quit is handled at the bottom of the loop.
 */
case 'Q' :
/*
 *   As a default, we do nothing; not even an error message.
 */
default :
            break ;
         }
/*
 *   If the last gadget selected was 'Q', we exit.
 */
         if (op == 'Q')
            break ;
      }
   } else {
      error("! couldn't open window") ;
   }
/*
 *   Release memory and exit.
 */
   cleanup() ;
}
SHAR_EOF
#	End of shell archive
exit 0