/****************************************************************************
 *
 *                               CopiaDiski.c
 *
 *           Programmino didattico sull'uso del trackdisk device.
 *         Funzionalita' pressoche' identica al diskcopy di sistema.
 *        Realizzato per AMAZING.7 da Eugenio Ciceri il 9 Marzo 1989.
 * Questo programma si considera posto in PUBBLICO DOMINIO a condizione che
 *               il presente commento sia lasciato inalterato.
 *
 ****************************************************************************/

#include "exec/types.h"
#include "exec/exec.h"
#include "exec/execbase.h"
#include "exec/errors.h"
#include "devices/trackdisk.h"

#define ON  1
#define OFF 0
#ifndef NUMHEADS
    #define NUMHEADS    2
#endif

/** Dichiarazione delle funzioni esterne per evitare fastidiosi 'warnings' **/
extern void             *AllocMem();
extern struct MsgPort   *CreatePort();
extern struct IORequest *CreateExtIO();

/** Strutture per la comunicazione con il trackdisk device ******************/
struct MsgPort          *sourceport     =   NULL;
struct MsgPort          *destport       =   NULL;
struct IOExtTD          *sourcereq      =   NULL;
struct IOExtTD          *destreq        =   NULL;

/** Aree di memoria dove contenere i dati da e per il disco *****************/
UBYTE                   *sourcebuffer   =   NULL;
UBYTE                   *destbuffer     =   NULL;

/** Lunghezza di una traccia standard Amiga *********************************/
ULONG                   length          =   TD_SECTOR*22;
char                    source_error    =   1;
char                    dest_error      =   1;

/****************************************************************************
 *
 * ReadCylSecHd(diskreq,cyl,sec,hd,length,buffer)
 *
 * Routine di lettura dal dischetto.
 * Legge in buffer dal dischetto collegato al diskreq a partire da cilindro,
 * settore e testina indicati per una lunghezza length.
 *
 ****************************************************************************/

ReadCylSecHd(diskreq,cyl,sec,hd,length,buffer)
struct IOExtTD *diskreq;    /* Requester relativo al drive  */
UWORD cyl,sec,hd;           /* Cilindro, settore e testina  */
ULONG length;               /* Lunghezza dei dati           */
UBYTE *buffer;              /* Dove mettere i dati letti    */
{
/** Posizione dei dati sul disco relativi al cilindro, settore e testina ****/
    ULONG offset=TD_SECTOR*(sec+NUMSECS*hd+NUMSECS*NUMHEADS*cyl);
    char error;
    
    diskreq->iotd_Req.io_Command    =   ETD_READ;      /* Comando lettura   */
    diskreq->iotd_Req.io_Flags      =   0;
    diskreq->iotd_Req.io_Length     =   length;        /* Lunghezza lettura */
    diskreq->iotd_Req.io_Data       =   (APTR)buffer;  /* Destinazione dati */
    diskreq->iotd_Req.io_Offset     =   offset;        /* Posizione dati    */
    
/** Esegue la lettura *******************************************************/
    error=DoIO(diskreq);
    
/** Ritorna l'esito della lettura *******************************************/
    return(error);
}

/****************************************************************************
 *
 * FormatCylSecHd(diskreq,cyl,sec,hd,length,buffer)
 *
 * Routine di formattazione del dischetto.
 * Scrive sul dischetto collegato al diskreq a partire da cilindro, settore e
 * testina indicati per una lunghezza length i dati in buffer.
 * Viene utilizzato in questo programma per poter scrivere su dischetti anche
 * non formattati; il comando ETD_WRITE permette infatti di scrivere
 * unicamente gia' formattate.
 *
 ****************************************************************************/

FormatCylSecHd(diskreq,cyl,sec,hd,length,buffer)
struct IOExtTD *diskreq;    /* Requester relativo al drive          */
UWORD cyl,sec,hd;           /* Cilindro, settore e testina          */
ULONG length;               /* Lunghezza dei dati                   */
UBYTE *buffer;              /* Dove prelevare i dati da scrivere    */
{
/** Posizione dei dati sul disco relativi al cilindro, settore e testina ****/
    ULONG offset=TD_SECTOR*(sec+NUMSECS*hd+NUMSECS*NUMHEADS*cyl);
    char error;
    
    diskreq->iotd_Req.io_Command    =   TD_FORMAT;     /* Comando lettura */
    diskreq->iotd_Req.io_Flags      =   0;
    diskreq->iotd_Req.io_Length     =   length;        /* Lunghezza dati */
    diskreq->iotd_Req.io_Data       =   (APTR)buffer;  /* Sorgente dati */
    diskreq->iotd_Req.io_Offset     =   offset;        /* Posizione dati */
    
/** Esegue la formattazione *************************************************/
    error=DoIO(diskreq);
    
/** Ritorna l'esito della formattazione *************************************/
    return(error);
}

/****************************************************************************
 *
 * Verify(diskreq,cyl,sec,hd,length,sourcebuffer,destbuffer)
 *
 * Routine di verifica dei dati letti dopo la formattazione.
 * Legge cio' che ha scritto sul dischetto destinazione e confronta i dati
 * con quelli nel buffer di scrittura.
 *
 ****************************************************************************/

Verify(diskreq,cyl,sec,hd,length,sourcebuffer,destbuffer)
struct IOExtTD *diskreq;    /* Requester relativo al drive  */
UWORD cyl,sec,hd;           /* Cilindro, settore e testina  */
ULONG length;               /* Lunghezza dei dati           */
/** I dati contenuti nei buffers vengono considerati ULONG anziche' UBYTE per
    poter errere letti a 4 per volta velocizzando la verifica. **************/
ULONG *sourcebuffer;        /* Dati di paragone             */
ULONG *destbuffer;          /* Dati da verificare           */
{
    ULONG i;
    char error;
    
/** Legge i dati per la verifica ********************************************/
    if (error=ReadCylSecHd(diskreq,cyl,sec,hd,length,destbuffer))
        return (error);
    
/** Confronta i dati e ritorna errore se non coincidono *********************/
    for (i=0; i<length/sizeof(ULONG); i++)
        if (*sourcebuffer++!=*destbuffer++)
            return (TDERR_NotSpecified);
    
/** Ritorna il valore di tutto OK *******************************************/
    return (0);
}

/****************************************************************************
 *
 * SetCount(diskreq)
 *
 * Legge e ricorda il numero di cambiamenti di disco effettuati sul drive
 * collegato al diskreq per poter segnalare l'errore DISKCHANGED nel caso in
 * cui venga cambiato il dischetto durante la copia.
 *
 ****************************************************************************/

SetCount(diskreq)
struct IOExtTD *diskreq;    /* Requester relativo al drive */
{
    char error;
    
/** Comando di lettura del numero di cambio di dischi. **********************/
    diskreq->iotd_Req.io_Command    =   TD_CHANGENUM;
    diskreq->iotd_Req.io_Flags      =   0;
    error=DoIO(diskreq);
    
/** Copia il valore ottenuto nel contatore **********************************/
    diskreq->iotd_Count             =   diskreq->iotd_Req.io_Actual;
    return (error);
}

/****************************************************************************
 *
 * MotorOff(diskreq)
 *
 * Spegne il motore del drive.
 *
 ****************************************************************************/

MotorOff(diskreq)
struct IOExtTD *diskreq;    /* Requester relativo al drive */
{
/** Comando di selezione delle operazioni relative al motore del drive ******/
    diskreq->iotd_Req.io_Command    =   TD_MOTOR;
    diskreq->iotd_Req.io_Flags      =   0;
    
/** Specifica di spegnimento del motore del drive ***************************/
    diskreq->iotd_Req.io_Length     =   OFF;
    return(DoIO(diskreq));
}

/****************************************************************************
 *
 * MotorOn(diskreq)
 *
 * Accende il motore del drive.
 * Quasi sempre inutile poiche' le operazioni di lettura e scrittura
 * accendono automaticamente il motore del drive interessato.
 *
 ****************************************************************************/

MotorOn(diskreq)
struct IOExtTD *diskreq;    /* Requester relativo al drive */
{
/** Comando di selezione delle operazioni relative al motore del drive ******/
    diskreq->iotd_Req.io_Command    =   TD_MOTOR;
    diskreq->iotd_Req.io_Flags      =   0;
    
/** Specifica di accensione del motore del drive ****************************/
    diskreq->iotd_Req.io_Length     =   ON;
    return(DoIO(diskreq));
}

/****************************************************************************
 *
 * WProtect(diskreq)
 *
 * Ritorna il valore 0 se il dischetto non e' protetto dalla scrittura,
 * ritorna il valore 1 in caso contrario.
 *
 ****************************************************************************/

WProtect(diskreq)
struct IOExtTD *diskreq;    /* Requester relativo al drive */
{
/** Comando di lettura dello stato di protezione del dischetto **************/
    diskreq->iotd_Req.io_Command    =   TD_PROTSTATUS;
    diskreq->iotd_Req.io_Flags      =   0;
    DoIO(diskreq);
    return (diskreq->iotd_Req.io_Actual);
}

/****************************************************************************
 *
 * EmptyDrive(diskreq)
 *
 * Ritorna il valore 0 se il dischetto non e' inserito nel drive,
 * ritorna il valore 1 in caso contrario.
 *
 ****************************************************************************/

EmptyDrive(diskreq)
struct IOExtTD *diskreq;    /* Requester relativo al drive */
{
/** Comando di lettura dello stato di presenza del dischetto ****************/
    diskreq->iotd_Req.io_Command    =   TD_CHANGESTATE;
    diskreq->iotd_Req.io_Flags      =   0;
    DoIO(diskreq);
    return (diskreq->iotd_Req.io_Actual);
}

/****************************************************************************
 *
 * ExplainError(error)
 *
 * Routine di esplicazione degli errori.
 * Codifica l'errore e lo stampa.
 *
 ****************************************************************************/

ExplainError(error)
char error;
{
    if (!error)
        printf("OK");
    else
        switch (error)
        {
        case IOERR_OPENFAIL:
            printf("IOERR_OPENFAIL");
            break;
        case IOERR_ABORTED:
            printf("IOERR_ABORTED");
            break;
        case IOERR_NOCMD:
            printf("IOERR_NOCMD");
            break;
        case IOERR_BADLENGTH:
            printf("IOERR_BADLENGTH");
            break;
        case TDERR_NotSpecified:
            printf("TDERR_NotSpecified");
            break;
        case TDERR_NoSecHdr:
            printf("TDERR_NoSecHdr");
            break;
        case TDERR_BadSecPreamble:
            printf("TDERR_BadSecPreamble");
            break;
        case TDERR_BadSecID:
            printf("TDERR_BadSecID");
            break;
        case TDERR_BadHdrSum:
            printf("TDERR_BadHdrSum");
            break;
        case TDERR_BadSecSum:
            printf("TDERR_BadSecSum");
            break;
        case TDERR_TooFewSecs:
            printf("TDERR_TooFewSecs");
            break;
        case TDERR_BadSecHdr:
            printf("TDERR_BadSecHdr");
            break;
        case TDERR_WriteProt:
            printf("TDERR_WriteProt");
            break;
        case TDERR_DiskChanged:
            printf("TDERR_DiskChanged");
            break;
        case TDERR_SeekError:
            printf("TDERR_SeekError");
            break;
        case TDERR_NoMem:
            printf("TDERR_NoMem");
            break;
        case TDERR_BadUnitNum:
            printf("TDERR_BadUnitNum");
            break;
        case TDERR_BadDriveType:
            printf("TDERR_BadDriveType");
            break;
        case TDERR_DriveInUse:
            printf("TDERR_DriveInUse");
            break;
        case TDERR_PostReset:
            printf("TDERR_PostReset");
            break;
        default:
            printf("ERRORE NON CONOSCIUTO");
            break;
        }
    printf("\n");
}

/****************************************************************************
 *
 * Usage()
 *
 * Stampa la sintassi d'uso del programma.
 *
 ****************************************************************************/

Usage()
{
    printf("CopiaDiski by Eugenio Ciceri per AMAZING.7\n");
    printf("Sintassi: CopiaDiski <drive sorgente> <drive destinazione>\n");
    return (0);
}

/****************************************************************************
 *
 * MAIN()
 *
 * Copia un dischetto su un altro verificando l'integrita' della copia.
 *
 ****************************************************************************/

main(argc,argv)
LONG argc;      /* Contatore dei parametri passati al programma           */
char *argv[];   /* Matrice di puntatori ai parametri passati al programma */
{
    UWORD cyl           =   0;  /* Cilindro */
    UWORD sec           =   0;  /* Settore */
    UWORD hd            =   0;  /* Testina */
    UWORD source        =   0;  /* Drive sorgente (originale) */
    UWORD destination   =   1;  /* Drive destinazione (copia) */
    
    char  error         =   0;
    
/** Se non sono stati passati argomenti spiega la sintassi ed esce **********/
    if (argc<=1)
    {
        Usage();
        return (0);
    }
    argv++;
    
/** Legge il numero di drive contenente il disco originale ******************/
    source=atoi(*argv++);
    
/** Se non sono stati passati altri argomenti spiega la sintassi ed esce ****/
    if (!--argc)
    {
        Usage();
        return (0);
    }
    
/** Legge il numero di drive contenente il disco copia **********************/
    destination=atoi(*argv++);
    
/** In questa versione non e' possibile effettuare copie con un solo drive...
    ma potete sempre divertirvi a modificare questo programma per le vostre
    esigenze ****************************************************************/
    if (source==destination)
    {
        printf("Spiacente: funziono solo con due drives...\n");
        return (0);
    }
    
/** Tenta di riservare spazio per i dati di lettura e scrittura *************/
    if (!(sourcebuffer=(UBYTE *)AllocMem(length,MEMF_CHIP|MEMF_CLEAR)))
    {
        printf("Errore: Non c'e' memoria per il sourcebuffer.\n");
        CleanUp();
        return (1);
    }
    if (!(destbuffer=(UBYTE *)AllocMem(length,MEMF_CHIP|MEMF_CLEAR)))
    {
        printf("Errore: Non c'e' memoria per il destbuffer.\n");
        CleanUp();
        return (1);
    }
    
/** Tenta di creare le strutture di comunicazione con il trackdisk device ***/
    if (!(sourceport=CreatePort(0,0)))
    {
        printf("Errore: Non riesco a creare la sourceport.\n");
        CleanUp();
        return (2);
    }
    if (!(destport=CreatePort(0,0)))
    {
        printf("Errore: Non riesco a creare la destport.\n");
        CleanUp();
        return (2);
    }
    if (!(sourcereq=
        (struct IOExtTD *)CreateExtIO(sourceport,sizeof(struct IOExtTD))))
    {
        printf("Errore in CreateExtIO().\n");
        CleanUp();
        return (3);
    }
    if (!(destreq=
        (struct IOExtTD *)CreateExtIO(destport,sizeof(struct IOExtTD))))
    {
        printf("Errore in CreateExtIO().\n");
        CleanUp();
        return (3);
    }
    
/** Tenta di aprire il trackdisk device per i drive 0 e 1 *******************/
    if (source_error=OpenDevice(TD_NAME,source,sourcereq,0))
    {
        printf("Errore in OpenDevice()=%lx\n",source_error);
        CleanUp();
        return (4);
    }
    if (dest_error=OpenDevice(TD_NAME,destination,destreq,0))
    {
        printf("Errore in OpenDevice()=%lx\n",dest_error);
        CleanUp();
        return (4);
    }
    
/** Ricorda il numero di cambi di disco effettuati sui drive per poter
    segnalare eventuali cambiamenti durante la copia ************************/
    SetCount(sourcereq);
    SetCount(destreq);
    
/** Controlla la presenza dei dischetti nei drives **************************/
    if (EmptyDrive(sourcereq))
    {
        printf("Manca il dischetto nel drive %d.\n",source);
        CleanUp();
        return (5);
    }
    if (EmptyDrive(destreq))
    {
        printf("Manca il dischetto nel drive %d.\n",destination);
        CleanUp();
        return (5);
    }
    
/** Controlla che il dischetto di copia non sia protetto dalla scrittura ****/
    if (WProtect(destreq))
    {
        printf("Il dischetto destinazione e' protetto dalla scrittura.\n");
        CleanUp();
        return (6);
    }
    
/** Per tutte e 80 le tracce (da 0 a 79)... *********************************/
    for (cyl=0; cyl<80; cyl++)
    {
/** ...tenta di leggere la traccia ******************************************/
        printf("Leggo la traccia    %d\n",cyl);
        if (error=ReadCylSecHd(sourcereq,cyl,sec,hd,length,sourcebuffer))
        {
            printf("\n");
/** Se non ci riesce spiega perche' ed esce con errore... *******************/
            ExplainError(error);
            CleanUp();
            return (10);
        }
/** ... altrimenti tenta di scrivere (formattandola) la traccia sulla copia */
        printf("Scrivo la traccia   %d\n",cyl);
        if (error=FormatCylSecHd(destreq,cyl,sec,hd,length,sourcebuffer))
        {
            printf("\n");
            ExplainError(error);
            CleanUp();
            return (10);
        }
/** Infine verifica che cio' che c'e' sulla copia sia uguale all'originale **/
        printf("Verifico la traccia %d\n",cyl);
        if (error=Verify(destreq,cyl,sec,hd,length,sourcebuffer,destbuffer))
        {
            printf("\n");
            ExplainError(error);
            CleanUp();
            return (10);
        }
    }
    
/** N.B.: Il segno "" che trovate nelle precedenti istruzioni si ottiene
    premendo i tasti 'CTRL' e 'K' e serve per spostare il cursore verso
    l'alto ******************************************************************/
    printf("\nCopia eseguita.\n");
    
/** Libera la memoria... ****************************************************/
    CleanUp();
    
/** ... e ritorna. **********************************************************/
    return (0);
}

/****************************************************************************
 *
 * CleanUp()
 *
 * Routine di uscita 'pulita'.
 *
 * Spegne i motori dei drive, chiude il trackdisk device e restituisce tutta
 * la memoria che aveva occupato.
 *
 ****************************************************************************/

CleanUp()
{
    if (sourcereq)
    {
        if (!source_error)
        {
            MotorOff(sourcereq);
            CloseDevice(sourcereq);
        }
        DeleteExtIO(sourcereq,sizeof(struct IOExtTD));
    }
    if (sourceport)
        DeletePort(sourceport);
    if (destreq)
    {
        if (!dest_error)
        {
            MotorOff(destreq);
            CloseDevice(destreq);
        }
        DeleteExtIO(destreq,sizeof(struct IOExtTD));
    }
    if (destport)
        DeletePort(destport);
    if (sourcebuffer)
        FreeMem(sourcebuffer,length);
    if (destbuffer)
        FreeMem(destbuffer,length);
    return (0);
}
