/****************************************************************/
/*                                                              */
/*                    COPYDISK V1.0                             */
/*                                                              */
/*                  (c) Copyright 1989                          */
/*                     Bob Rakosky                              */
/*                                                              */
/*                                                              */
/*  Usage:                                                      */
/*                                                              */
/*    Copydisk [FROM] drive [TO] drive [MULTI] [NOVERIFY]       */
/*                                                              */
/*    where the drives specified must both be 'trackdisk.device'*/
/*    controlled floppy drives (but may be the same device).    */
/*    The MULTI switch requests that multiple copies of the     */
/*    source disk be made.  The NOVERIFY switch will cause the  */
/*    copying to be performed faster, but will not check for    */
/*    errors on the destination disk.                           */
/*                                                              */
/*  NOTES:                                                      */
/*    This program requires ARP.library, which must be in your  */
/*    libs: directory.                                          */
/*                                                              */
/*    This program can be compiled with either the Lattice      */
/*    (version 5.02) or Manx (version 3.6a) compilers.          */
/*    To compile/link with Lattice, use the following:          */
/*           lc -O -cf copydisk                                 */
/*           blink from lib:arpc.o copydisk.o                   */
/*                 to   copydisk                                */
/*                 lib  lib:arp.lib lib:lc.lib lib:amiga.lib    */
/*                 SMALLCODE NODEBUG                            */
/*    To compile/link with Manx, use:                           */
/*           cc -o copydisk.o copydisk                          */
/*           ln -o copydisk -larp -lc                           */
/*                                                              */
/*                                                              */
/****************************************************************/
#include <exec/types.h>
#include <exec/memory.h>
#include <exec/ports.h>
#include <exec/io.h>
#include <exec/exec.h>
#include <devices/trackdisk.h>
#include <libraries/dos.h>
#include <libraries/dosextens.h>
#include <libraries/filehandler.h>
#include <libraries/arpbase.h>
#include <libraries/arpfunctions.h>
#include <stdio.h>

#ifdef LATTICE
#include <stdlib.h>
#include <string.h>
#include <dos.h>
#include <proto/exec.h>
#include <proto/intuition.h>
#include <proto/graphics.h>
#include <proto/dos.h>
#endif /* Lattice */

#ifdef AZTEC_C
#define __ARGS(a) ()
#include <functions.h>
struct IORequest *CreateExtIO();
void memcpy();
#define memcpy(to,from,lgth) movmem(from,to,lgth)
#endif /* Aztec */

/*
 *  The following variables support the 
 *  ARP startup code, including the needs of
 *  the builtin GADS() call contained in that 
 *  startup.
 */

#define ARG_FROM    1
#define ARG_TO      2
#define ARG_MULTI   3
#define ARG_NVFY    4

char *CLI_Template = "FROM/A,TO/A,MULTI/S,NOVERIFY/S";

char *CLI_Help = 

"\2331;32mFROM\2330;31m and \2331;32mTO\2330;31m must be \
floppy disk drives\n\
\2331;32mMULTI\2330;31m, if specified, requests multiple \
copies of the same disk\n\
\2331;32mNOVERIFY\2330;31m requests that the output disk \
is not validated after it is written\n";

/*
 *  Other global variables
 */

char c_rite[] =
    "\n\2332;33mCopydisk\2330;31m \251Copyright 1989 - Bob Rakosky\n\n";

char src_prompt[] = 
    "Place \2331;32mSource\2330;31m disk in drive %s:\n";

char dst_prompt[] =
    "Place \2331;32mDestination\2330;31m disk in drive %s:\n";

char ret_prompt[] =
    "   \2332;33mand press RETURN\2330;31m (ctrl-C/RETURN to abort)";

/* The following define is the length of a track, in bytes */
#define FULL_TRACK (TD_SECTOR * NUMSECS)

WORD    icur_trk = 0;
WORD    ocur_trk = 0;
WORD    max_trk = 0;

struct IOStdReq *td_ireq;
struct IOStdReq *td_oreq;
struct MsgPort *td_port;

/*
 *  The following structure is used to maintain a linked-list
 *  of tracks of data in memory
 */

struct cache {
    struct cache *next;
    UBYTE buf[FULL_TRACK];
}
     *trk_buf = NULL;

int   num_bufs = 0; /* the number of track buffers that we've allocated */

char *src_name,
     *dst_name;

ULONG idsk_unit = -1,
      odsk_unit = -1;

UBYTE tdin_open  = FALSE,
      tdout_open = FALSE,
      idev_owned = FALSE,
      odev_owned = FALSE,
      dos_disk = FALSE,
      do_verify  = TRUE,
      do_multi = FALSE,
      disk_in_mem = FALSE,
      have_src_disk = FALSE;

char *td_buf = NULL;

char  inp_line[MaxInputBuf];

/*
 *  The following are the prototypes for all of the functions 
 *  local to this program.  Note that the __ARGS() macro will
 *  expand two different ways for Lattice and Manx.  For the
 *  Lattice compiler, the argument types for the functions are
 *  specified.  Since the Aztec compiler (v3.6a) doesn't support
 *  function prototypes, the __ARGS() macro expands to just the
 *  function-specification parentheses ().
 */

int  do_copy        __ARGS( (void) );
int  init_devs      __ARGS( (void) );
int  valid_devs     __ARGS( (void) );
int  open_td        __ARGS( (void) );
int  inhibit_drives __ARGS( (int) );
int  read_track     __ARGS( (struct cache *) );
int  write_track    __ARGS( (struct cache *) );
void touch_root_blk __ARGS( (char *) );
void stop_drives    __ARGS( (void) );
void cleanup        __ARGS( (void) );

/****************************************************************/
/*                                                              */
/* Function: main()                                             */
/*                                                              */
/* Purpose:  Main function of program                           */
/*                                                              */
/* Inputs:   Command line arguments                             */
/*                                                              */
/* Outputs:  Program exit code                                  */
/*                                                              */
/* Notes:                                                       */
/*   Since this program uses the ARP startup code, the          */
/*   Arp.library is opened and the command line arguments are   */
/*   processed according to our template by the startup code    */
/*   before control passes to the main() function.  Note that   */
/*   the builtin GADS() function to process the command line    */
/*   will cause the program to terminate before we get here,    */
/*   if an invalid command line is entered (except in the case  */
/*   that the program is invoked with NO arguments), in which   */
/*   case argc will contain a value of 1.                       */
/*                                                              */
/****************************************************************/

void main(argc,argv)
    int argc;
    char **argv;
{
    int ret;

    Printf(c_rite);

    if (argc == 1) {
        Printf("Usage: %s %s\n\n",argv[0],CLI_Template);
        Printf(CLI_Help);
        exit(10);
    }
        /* Save the pointers to the source/dest drive names */
    src_name = argv[ARG_FROM];
    dst_name = argv[ARG_TO];

    if (argv[ARG_MULTI])
    {
        do_multi = TRUE;
    }
    if (argv[ARG_NVFY]) 
    {
        do_verify = FALSE;
    }

    if (!init_devs())       /* if error during initialization */
    {
        cleanup();
        exit(20);
    }

    ret = do_copy();
    cleanup();
    exit(0);
}

/****************************************************************/
/*                                                              */
/* Function: do_copy()                                          */
/*                                                              */
/* Purpose:  controls the high-level reading and writing of     */
/*           disks                                              */
/*                                                              */
/* Inputs:   none                                               */
/*                                                              */
/* Outputs:  The (eventual) program return code                 */
/*                                                              */
/* Notes:                                                       */
/*   Only checks for cntl-c interrupts when the user is being   */
/*   prompted.                                                  */
/*                                                              */
/****************************************************************/

int do_copy()
{
    struct cache *buf_ptr;

    while (TRUE) 
    {
        if (!have_src_disk)
        {
          /* prompt for source disk to be inserted */
            Printf(src_prompt,src_name);
            if (odsk_unit != idsk_unit)     /* if not single-drive copy */
            {
                Printf(dst_prompt,dst_name);
            }
            Printf(ret_prompt);
            ReadLine(inp_line);
            if (CheckAbort(NULL))   /* check for cntl-c */
            {
                return(0);
            }
            have_src_disk = TRUE;
        }
        icur_trk = ocur_trk = 0;
        while (ocur_trk < max_trk)
        {
            if (!disk_in_mem)   /* if we haven't buffered the src disk yet */
            {
                buf_ptr = trk_buf;      /* point to first cache */
                while ((buf_ptr) && (icur_trk < max_trk))
                {
                    if (!read_track(buf_ptr)) 
                    {
                        Printf("Unable to read source disk\n");
                        return(10);
                    }
                    buf_ptr = buf_ptr->next;    /* next in linked-list */
                }
                if (num_bufs >= max_trk) 
                {
                    disk_in_mem = TRUE;     /* we've cached the entire disk */
                }
            }
            stop_drives();                  /* turn the motors off */
            if (idsk_unit == odsk_unit) 
            {
              /* single drive copy - can't have source 
               * and dest both present
               */
                Printf(dst_prompt,dst_name);
                Printf(ret_prompt);
                ReadLine(inp_line);
                if (CheckAbort(NULL))
                {
                    return(0);
                }
                have_src_disk = FALSE;
            }
            buf_ptr = trk_buf;      /* point to first cache */
            while ((buf_ptr) && (ocur_trk < max_trk))
            {
                if (!write_track(buf_ptr)) 
                {
                    Printf("Unable to write destination disk\n");
                    return(10);
                }
                buf_ptr = buf_ptr->next;    /* next in linked-list */
            }
            stop_drives();                  /* motor off */
            if (ocur_trk < max_trk)     /* if we haven't written the */
            {                           /* entire disk               */
                if (idsk_unit == odsk_unit) 
                {
                  /* single-drive copy */
                    Printf(src_prompt,src_name);
                    Printf(ret_prompt);
                    ReadLine(inp_line);
                    if (CheckAbort(NULL))
                    {
                        return(0);          /* cntl-c detected */
                    }
                }
                continue;
            }
        }
        Printf("\nDisk copied\n");
        if (!do_multi)
        {
            break;          /* exit the while(TRUE) if single copy */
        }
        if (disk_in_mem)
        {
            Printf(dst_prompt,dst_name);
            Printf(ret_prompt);
            ReadLine(inp_line);
            if (CheckAbort(NULL))
            {
                break;
            }
        }
    }
    return(0);
}


/****************************************************************/
/*                                                              */
/* Function: init_devs()                                        */
/*                                                              */
/* Purpose:  Validates and initializes disk device(s) specified */
/*           Allocates required buffers                         */
/*                                                              */
/* Inputs:   none                                               */
/*                                                              */
/* Outputs:  Boolean success indicator                          */
/*                                                              */
/* Globals:  Device unit numbers                                */
/*           IORequest structures                               */
/*           Cache buffers to hold as much of disk data as      */
/*              memory will allow                               */
/*           Trackdisk IO buffer (in CHIP memory)               */
/*                                                              */
/* Notes:                                                       */
/*    High level driver for initialization/validation           */
/*    Uses the ARP memory allocation routines to track our      */
/*      memory allocations so that they are automatically freed */
/*      upon program exit.                                      */
/*                                                              */
/*                                                              */
/*                                                              */
/****************************************************************/

int init_devs()
{
    long    totmem;
    int     i;

    struct cache *cache_ptr;

        /* Note that if the drives have been specified using
         * a trailing colon, as is the standard means of 
         * specifying a device name, this colon is stripped 
         * off.  This is because the device name will be 
         * compared with the device name as contained in the
         * AmigaDOS system list of devices, and the names in
         * that list do _NOT_ contain the colons.
         */
    i = strlen(src_name);
    if (src_name[i-1] == ':')
    {
        src_name[i-1] = '\0';
    }
    i = strlen(dst_name);
    if (dst_name[i-1] == ':')
    {
        dst_name[i-1] = '\0';
    }

    if (!valid_devs())      /* insure that the devices specified    */
    {                       /* are valid device names, and are      */
        return(FALSE);      /* actual 'trackdisk.device' devices    */
    }

    if (!open_td())         /* open the devices */
    {
        return(FALSE);
    }
        /* Keep the file system from accessing the drives */
    if (!inhibit_drives((int)TRUE))
    {
        return(FALSE);
    }

      /* NOTE that trackdisk requires it's buffer to be in 
       *     CHIP memory
       */
    td_buf = (char *)ArpAllocMem(FULL_TRACK,
                              MEMF_CHIP | MEMF_PUBLIC |MEMF_CLEAR);
    if (!td_buf) {
        Printf("Not Enough Chip MEMORY to Proceed\n");
        return(FALSE);
    }

      /*
       *  We want to allocate enough cache buffers to hold the entire
       *  source diskette in memory, if possible.  We will, however,
       *  insure that at least 64K of memory is left unallocated.
       *  This will insure that the system has enough memory to handle
       *  most transient requirements.
       */
    totmem = (long)AvailMem(0L);
    totmem -= 65536L;        /* leave at least 64K available to the system */

    if (totmem >= (sizeof(struct cache)<<1) ) /* enough for at least two */
    {
        totmem /= sizeof(struct cache);
        if (totmem > max_trk) totmem = max_trk;
        for (num_bufs = 0; num_bufs < totmem; num_bufs++)
        {
            cache_ptr = (struct cache *) 
                    ArpAllocMem((long)sizeof(struct cache),(long)MEMF_CLEAR);
            if (!cache_ptr) 
            {
                break;
            }
            cache_ptr->next = trk_buf;
            trk_buf = cache_ptr;
        }
    }
    return(TRUE);
}

/****************************************************************/
/*                                                              */
/* Function: valid_devs()                                       */
/*                                                              */
/* Purpose:  Verifies that the devices requested for source and */
/*           destination are valid devices in the system, and   */
/*           are, in fact, controlled by trackdisk.device       */
/*                                                              */
/* Inputs:   Global: source-name, destination-name              */
/*                                                              */
/* Outputs:  Boolean success indicator                          */
/*           Global: source/destination unit numbers            */
/*                                                              */
/* Notes:                                                       */
/*   This routine walks through the system-level linked list    */
/*   of device nodes.  It must do this with task-switching      */
/*   disabled, to insure that this chain doesn't change while   */
/*   we are inspecting it.                                      */
/*                                                              */
/*   NOTE that the DosLibrary structure, which is used to       */
/*   locate the device node list, is normally pointed to by the */
/*   standard DosBase pointer.  However, because we are using   */
/*   the ARP startup, DosBase really points to ArpBase, and not */
/*   all of the DosLibrary structure is valid.  We have to use  */
/*   the REAL DosBase pointer, which is conveniently saved for  */
/*   us in the Arp Library structure.                           */
/*                                                              */
/****************************************************************/

int valid_devs()
{
    char    nm_buf[23],
            dev_buf[64];
    int     err = FALSE;
    struct DosLibrary *real_dos;
    struct DeviceNode *dlist;
    struct RootNode *rn;
    struct DosInfo *di;
    struct FileSysStartupMsg *fs;

    Forbid();   /* insure that the linked-list won't change on us */

      /* NOTE: DosBase does not _really_ point to DosBase */
    real_dos = (struct DosLibrary *)ArpBase->DosBase;

    rn = (struct RootNode *)real_dos->dl_Root;
    di = (struct DosInfo *) BADDR(rn->rn_Info); /* DosInfo is a BCPL ptr */
    dlist = (struct DeviceNode *) BADDR(di->di_DevInfo);  /* first entry */

    while (dlist) {                     /* until we get to end-of-chain */
        if (dlist->dn_Type == DLT_DEVICE)
            /*
             *  Note:  the DeviceNode list contains entries for
             *    devices, directories (logical assigns), and
             *    volumes.  We are only interested in the device
             *    entries.
             */
        {
            BtoCStr(nm_buf,dlist->dn_Name,23L); /* convert BCPL string */

            if ((Strcmp(src_name,nm_buf)==0) || /* does it match? */
                (Strcmp(dst_name,nm_buf) == 0)) 
            {
                fs = (struct FileSysStartupMsg *) 
                            BADDR(dlist->dn_Startup);   /* it's a BPTR */

                BtoCStr(dev_buf,fs->fssm_Device,63L);   /* convert BCPL str */
                if (Strcmp(dev_buf,TD_NAME) == 0)   /* is it trackdisk? */
                {
                    if (Strcmp(src_name,nm_buf)==0)
                    {
                        idsk_unit = fs->fssm_Unit;  /* grab the real unit # */
                    }
                    if (Strcmp(dst_name,nm_buf)==0)
                    {
                        odsk_unit = fs->fssm_Unit;
                    }
                }
                else        /* the device is not trackdisk.device */
                {
                    Permit();       /* remember to enable multitasking */
                    Printf("Error: %s device not a floppy drive\n",
                        (Strcmp(src_name,nm_buf)==0) ? "Source" : "Destination");
                    return(FALSE);
                }
            }
        }
        dlist = (struct DeviceNode *) BADDR(dlist->dn_Next); /* next in chain */
    }
    Permit();               /* multitasking OK again */

      /*  
       *  NOTE:  the device unit numbers are initialized to -1
       *  if that value hasn't changed, then no matching entry 
       *  was found for that device name in the list.
       */
    if (idsk_unit == -1L)
    {
        Printf("Error: Source device not found\n");
        err = TRUE;
    }
    if (odsk_unit == -1L) 
    {
        Printf("Error: Destination device not found\n");
        err = TRUE;
    }
    if (err) 
    {
        return(FALSE);
    }
    return(TRUE);
}

/****************************************************************/
/*                                                              */
/* Function: open_td()                                          */
/*                                                              */
/* Purpose:  Open the trackdisk.device units, allocate the IO   */
/*           request structures, get/validate disk information  */
/*           (total number of tracks).                          */
/*                                                              */
/* Inputs:   Global: unit numbers (src and dest)                */
/*                                                              */
/* Outputs:  Boolean success indicator                          */
/*           Global:  input and output IO request structures    */
/*                    max-track                                 */
/* Notes:                                                       */
/*    We only will perform the copy if the source and destina-  */
/*    tion devices have the same maximum number of tracks.  We  */
/*    won't, for example, copy from a 5-1/4" floppy to a 3-1/2" */
/*    drive.                                                    */
/*                                                              */
/****************************************************************/

int open_td()
{
    td_port = (struct MsgPort *) CreatePort(0,0);
    if (!td_port) 
    {
        Printf("Unable to allocate a Port/Signal Bit\n");
        return(FALSE);
    }
    td_ireq = CreateStdIO(td_port);
    if (!td_ireq) {
        Printf(" Not Enough Memory to Proceed\n");
        return(FALSE);
    }
    if (OpenDevice(TD_NAME,idsk_unit,(struct IORequest *)td_ireq,0L)) {
        Printf(" Can\'t allocate Source Device\n");
        return(FALSE);
    }
    tdin_open = TRUE;

    td_oreq = CreateStdIO(td_port);
    if (!td_oreq) {
        Printf(" Not Enough Memory to Proceed\n");
        return(FALSE);
    }
    if (OpenDevice(TD_NAME,odsk_unit,(struct IORequest *)td_oreq,0L)) {
        Printf(" Can\'t allocate Target Device\n");
        return(FALSE);
    }
    tdout_open = TRUE;

    td_ireq->io_Command = TD_GETNUMTRACKS;
    DoIO((struct IORequest *)td_ireq);
    max_trk = td_ireq->io_Actual;
    if (odsk_unit != idsk_unit) {
        td_oreq->io_Command = TD_GETNUMTRACKS;
        DoIO((struct IORequest *)td_oreq);
        if (max_trk != td_oreq->io_Actual) 
        {
            Printf("Incompatible devices - different track numbers\n");
            return(FALSE);
        }
    }
    return(TRUE);
}

/****************************************************************/
/*                                                              */
/* Function: inhibit_drives()                                   */
/*                                                              */
/* Purpose:  Either dis-allow the file-system from using the    */
/*           drives, or re-allow standard file-system access    */
/*                                                              */
/* Inputs:   Boolean - TRUE == inhibit file-system access       */
/*                    FALSE == allow file-system access         */
/*           Global: device names                               */
/*                                                              */
/* Outputs:  Boolean success indicator                          */
/*                                                              */
/* Notes:                                                       */
/*   Uses DOS packet-level IO.                                  */
/*   When the devices are 'inhibited', they are treated as      */
/*    "Not a DOS disk", and will show as DFn:BUSY on the WB     */
/*   screen.                                                    */
/*                                                              */
/****************************************************************/

int inhibit_drives(i_flag)
    int i_flag;
{
    char dev_nam[10];
    int     i;
    long    pkt_args[7];
    struct MsgPort *idev_proc,
                   *odev_proc;


    sprintf(dev_nam,"%s:",src_name);    /* insure the name ends with : */
        /*  
         *  Get the address of the process's MsgPort structure for 
         *  the device driver.
         */
    idev_proc = (struct MsgPort *)DeviceProc(dev_nam);
    if (!idev_proc) {
        Printf(" Can\'t locate Source Device's process\n");
        return(FALSE);
    }
      /*
       *  If the input and output devices are the same, the processes
       *  are the same.  Otherwise, repeat for the output device
       */
    if (odsk_unit == idsk_unit) {
        odev_proc = idev_proc;
    }
    else {
        sprintf(dev_nam,"%s:",dst_name);
        odev_proc = (struct MsgPort *)DeviceProc(dev_nam);
        if (!odev_proc) {
            Printf(" Can\'t locate Target Device's process\n");
            return(FALSE);
        }
    }
    for (i=0;i<7;i++) 
    {
        pkt_args[i] = 0;    /* initialize the packet argument array */
    }
    pkt_args[0] = i_flag;   /* First argument is a Boolean value */

    if (!SendPacket(ACTION_INHIBIT,pkt_args,idev_proc))
    {
        Printf("Unable to inhibit source drive\n");
        return(FALSE);
    }
    if (i_flag)
    {
        idev_owned = TRUE;
    }

    if (idsk_unit != odsk_unit)     /* only if two devices */
    {
        pkt_args[0] = i_flag;
        if (!SendPacket(ACTION_INHIBIT,pkt_args,odev_proc))
        {
            Printf("Unable to inhibit destination drive\n");
            return(FALSE);
        }
        if (i_flag)
        {
            odev_owned = TRUE;
        }
    }
    return(TRUE);
}

/****************************************************************/
/*                                                              */
/* Function: read_track()                                       */
/*                                                              */
/* Purpose:  reads one track from the input disk                */
/*                                                              */
/* Inputs:   cache buffer pointer into which to copy the data   */
/*                                                              */
/* Outputs:  Boolean success indicator                          */
/*           Global: Boolean DOS disk indicator (track 0 only)  */
/*                                                              */
/* Notes:                                                       */
/*   We will retry the read up to 3 times                       */
/*                                                              */
/*   For track 0 only, we will inspect the first 4 bytes of the */
/*   first sector to determine whether the disk we are reading  */
/*   is a DOS disk.  We will have to 'twiddle' with DOS disks   */
/*   when writing to insure we don't create an 'identical' disk.*/
/*                                                              */
/****************************************************************/

int read_track(cbuf)
    struct cache *cbuf;
{
    long i; 
    int retry;
    
    Printf(" Reading   Cylinder %ld Head %ld    \r",
            (long)(icur_trk >> 1),
            (long)(icur_trk & 1));
    for (retry=0;retry<3;retry++)       /* up to 3 retries */
    {
        td_ireq->io_Command = CMD_READ;
        td_ireq->io_Length = FULL_TRACK;
        td_ireq->io_Offset = (long)(icur_trk * FULL_TRACK);
            /* 
             * NOTE that trackdisk reads must be to a buffer
             * that is in CHIP memory.  Consequently, we can't 
             * read directly into our cache buffer.
             */
        td_ireq->io_Data = (APTR) td_buf;
        i = DoIO((struct IORequest *)td_ireq);
        if (!i)     /* success */
        {
            if (icur_trk == 0)
            {
                if ((Strncmp(td_buf,"DOS\000",4L) == 0) ||
                    (Strncmp(td_buf,"DOS\001",4L) == 0) )
                {
                    dos_disk = TRUE;
                }
                else
                {
                    dos_disk = FALSE;
                }
            }
            icur_trk++;
            memcpy(cbuf->buf,td_buf,(int)FULL_TRACK);   /* copy to cache */
            return(TRUE);
        }
    }
    return(FALSE);
}

/****************************************************************/
/*                                                              */
/* Function: write_track()                                      */
/*                                                              */
/* Purpose:  Writes one track of data to the destination device */
/*           and optionally reads it back in to verify the write*/
/*                                                              */
/* Inputs:   cache buffer                                       */
/*                                                              */
/* Outputs:  boolean success indicator                          */
/*                                                              */
/* Notes:                                                       */
/*   Uses the 'format' command, rather than the 'write' command,*/
/*   as this is faster, and works whether the disk is formatted */
/*   or not.  It is, however, a little more prone to errors,    */
/*   which is why the 'verify' option is recommended.           */
/*                                                              */
/****************************************************************/

int write_track(cbuf)
    struct cache *cbuf;
{
    long i;
    int  retry;
    static char *wr_prot_pr1 =
        "\nDisk in drive %s: is \23332;32mWRITE PROTECTED\23330;31m\n";
    static char *wr_prot_pr2 =
    "   \2332;33mPress RETURN to continue\2330;31m (ctrl-C/RETURN to abort)";



    Printf(" Writing   Cylinder %ld Head %ld     \r",
            (long)(ocur_trk>>1),
            (long)(ocur_trk & 1));

      /*
       *  NOTE:  AmigaDOS really doesn't like two DOS disks in the
       *  system that are absolutely identical.  This will cause the
       *  system to lock up or GURU if both disks are present at the
       *  same time, if the file-system has access to the disks.
       *  We will 'tweak' the root block of the output disk, to avoid
       *  the 'identical disk' syndrome.
       */
    if (dos_disk) 
    {
        if (ocur_trk == (max_trk >> 1)) /* root block is the middle */
        {                               /* sector of the device     */
            touch_root_blk(cbuf->buf);
        }
    }
      /*
       * We have to copy the data to our buffer in CHIP memory
       * for the trackdisk.device to be able to access it.
       */
    memcpy(td_buf,cbuf->buf,(int)FULL_TRACK);

    for (retry=0;retry<3;retry++)   /* retry up to 3 times */
    {
        td_oreq->io_Command = TD_FORMAT;
        td_oreq->io_Length = FULL_TRACK;
        td_oreq->io_Offset = (long)(ocur_trk * FULL_TRACK);
        td_oreq->io_Data = (APTR) td_buf;

        i=DoIO((struct IORequest *)td_oreq);
        if (i)      /* error occurred - retry */
        {
            if (td_oreq->io_Error == TDERR_WriteProt)
            {
                stop_drives();
                Printf(wr_prot_pr1,dst_name);
                Printf(wr_prot_pr2);
                ReadLine(inp_line);
                if (CheckAbort(NULL))   /* check for cntl-c */
                {
                    break;
                }
            }
            continue;
        }
        if (do_verify)
        {
            Printf(" Verifying Cylinder %ld Head %ld     \r",
                    (long)(ocur_trk>>1),
                    (long)(ocur_trk & 1));
            td_oreq->io_Command = CMD_READ;
            td_oreq->io_Length = FULL_TRACK;
            td_oreq->io_Offset = (long)(ocur_trk * FULL_TRACK);
            td_oreq->io_Data = (APTR) td_buf;

            i=DoIO((struct IORequest *)td_oreq);
            if (i)      /* can't read track - retry the write */
            {
                continue;
            }
            i = memcmp(td_buf,cbuf->buf,(int)FULL_TRACK);
            if (i)      /* compare failed */
            {
                continue;
            }
        }
        ocur_trk++;
        return(TRUE);
    }
    return(FALSE);
}

/****************************************************************/
/*                                                              */
/* Function: touch_root_blk()                                   */
/*                                                              */
/* Purpose:  'Tweaks' the root block, adjusting the 'date       */
/*           created' field, so that this disk won't be         */
/*           identical to the disk we read.                     */
/*                                                              */
/* Inputs:   Pointer to the root block image                    */
/*                                                              */
/* Outputs:  none (Root block image is updated)                 */
/*                                                              */
/* Notes:                                                       */
/*   We verify that the block passed is indeed the root block.  */
/*                                                              */
/*   The AmigaDOS Tech. Ref. Manual documents the format of the */
/*   root block in terms of long-words.  The values used in this*/
/*   routine match that documentation.                          */
/*                                                              */
/*   The checksum for the block has to be re-calculated.  The   */
/*   checksum is the value that causes the sum of all the long- */
/*   words in the sector (including the checksum) to total a    */
/*   value of 0.                                                */
/*                                                              */
/****************************************************************/

void touch_root_blk(blk)
    char *blk;
{
    long *lptr;
    long max_lwords;
    long chksum;
    int i;

    lptr = (long *)blk;     /* treat block as array of longs */
    max_lwords = (TD_SECTOR >> 2);

    if ((lptr[0] != 2) ||
        (lptr[1] != 0) )        /* it's not the root block ??? */
    {
        return;
    }
    DateStamp(&lptr[max_lwords-7]);
    chksum = lptr[5] = 0;       /* initialize checksum to 0 */
    for (i=0;i<max_lwords;i++)
    {
        chksum += lptr[i];
    }
    lptr[5] = (long) (0 - chksum);
}

/****************************************************************/
/*                                                              */
/* Function: stop_drives()                                      */
/*                                                              */
/* Purpose:  Insures that the drive(s) motors are turned off    */
/*                                                              */
/* Inputs:   none                                               */
/*                                                              */
/* Outputs:  none                                               */
/*                                                              */
/* Notes:                                                       */
/*   The trackdisk.device will always turn a drive's motor on   */
/*   when it is needed (for a read or write), but does NOT turn */
/*   the motor off.  There is no harm done in attempting to     */
/*   turn off the motor if it is already off, so we don't really*/
/*   need to keep track of the motor's state, just turn it off  */
/*   when we want to insure it's off.                           */
/*                                                              */
/****************************************************************/

void stop_drives()
{
    td_ireq->io_Command = TD_MOTOR;     /* insure motor off */
    td_ireq->io_Length = 0;
    DoIO((struct IORequest *)td_ireq);
    if (odsk_unit != idsk_unit)
    {
        td_oreq->io_Command = TD_MOTOR;     /* insure motor off */
        td_oreq->io_Length = 0;
        DoIO((struct IORequest *)td_oreq);
    }
}

/****************************************************************/
/*                                                              */
/* Function: cleanup()                                          */
/*                                                              */
/* Purpose:  Undoes what we did                                 */
/*                                                              */
/* Inputs:   none                                               */
/*                                                              */
/* Outputs:  none                                               */
/*                                                              */
/* Notes:                                                       */
/*   returns resources to the system.  NOTE that since our      */
/*   memory allocations have all been used with the Arp         */
/*   allocation routines, they will automatically be freed      */
/*   during normal exit() processing (when the Arp.library is   */
/*   closed).                                                   */
/*                                                              */
/****************************************************************/

void cleanup()
{
    if (tdin_open && tdout_open)
    {
        stop_drives();
    }
    if (tdin_open) {
        CloseDevice((struct IORequest *)td_ireq);
        tdin_open = FALSE;
    }
    if (tdout_open) {
        CloseDevice((struct IORequest *)td_oreq);
        tdout_open = FALSE;
    }
    if (idev_owned || odev_owned)
    {
        inhibit_drives((int)FALSE);
        idev_owned = odev_owned = FALSE;
    }
    if (td_ireq) {
        DeleteStdIO(td_ireq);
        td_ireq = NULL;
    }
    if (td_oreq) {
        DeleteStdIO(td_oreq);
        td_oreq = NULL;
    }
    if (td_port) {
        DeletePort(td_port);
        td_port = NULL;
    }
    /*  Normally we would free up our buffer allocations here
     *  but since we allocated them using the ArpAllocMem function
     *  they will auto-magically be freed when we exit()
     */
}

#ifdef AZTEC_C

/****************************************************************/
/*                                                              */
/* Function:  memcmp()                                          */
/*                                                              */
/* Purpose:   compares two blocks of memory                     */
/*                                                              */
/* Inputs:    pointers to the memory, length of the block to    */
/*            compare                                           */
/*                                                              */
/* Outputs:   negative if first block is less than second       */
/*            0 if the two blocks are identical                 */
/*            positive if first block is greater than second    */
/*                                                              */
/* Notes:                                                       */
/*   This is a standard library function for Lattice, but the   */
/*   Manx compiler library didn't seem to have an equivalent    */
/*   function.                                                  */
/*                                                              */
/****************************************************************/

int memcmp(s1,s2,lgth)
    register char *s1, *s2;
    register int lgth;
{
    register int res;

    while (--lgth)
    {
        if (res = (*(s2++) - *(s1++)))
        {
            return(res);
        }
    }
    return(0);
}

#endif  /* if AZTEC */
