/* Copyright © Feb.1997 by Michael Christoph */
/* CheckDisk.c - Start-Routine und Parameterauswertung */

/* Example: CheckDisk
**
**          Prüft Disketten auf Lesefehler/Prüfsummenfehler
**          durch einlesen aller Tracks
**
** Shell-Fail-Code nach dem Programmlauf:
**  DOSFAIL_OK    => Diskette ist ok
**  DOSFAIL_WARN  => übergebene Argumente waren falsch
**  DOSFAIL_ABORT => Überprüfung wurde vorzeitig abgebrochen
**  DOSFAIL_ERROR => Diskette hat Fehler
**  DOSFAIL_FAIL  => Fehler in der Programmumgebung (z.B. Speichermangel)
*/

// mcpp:mcppc3 -gs -o CheckDisk p:pLib/Startcode.o CheckDisk.c CheckDiskCom.c p:pLib/StdIO.o -l posstub -l pos

#define PROGNAME "CheckDisk"
#define VERSIONR "1.0"
#define PROGDATE "06.04.1997"
#define COPYRIGT "Copyright © Feb.1997 by Michael Christoph"

/************************** INCLUDES ********************************/

#include "CheckDisk.h"

#include <pExec/Library.h>
#include <pDos/DosDev.h>
#include <pDos/ArgTags.h>
#include <pDos/DosSig.h>
#include <pDos/Lock.h>
#include <pUtil/TagItem.h>

#include <proto/pLibExt.h>
#include <proto/pExec2.h>
#include <proto/pDos2.h>

extern "C" {
 #include <string.h>
}

/********************* EIGENE STRUKTUREN ****************************/

struct DriveDatas
{
  const pOS_DosDevice *Dev;

  /* Daten zum Öffnen des Devices */
  const CHAR   *DevName;
  UBYTE         DevUnit;
  ULONG         DevFlags;

  ULONG         StartCyl;
  ULONG         EndCyl;
  ULONG         BlockPerCyl;
  ULONG         BytesPerBlock;

  /* Infos über zu kopierende Diskette */
  CHAR          SourceDiskName[40];
  pOS_DateStamp SourceDate;
};

/************************** DEFINES *********************************/

#define RAWCODE_CtrlC   3
#define RAWCODE_Return 13

/********************* EXTERNE VARIABLEN ****************************/

extern struct pOS_DosBase *gb_DosBase;

/************************* PRTOTYPEN ********************************/

const pOS_DosDevice *GetDriveData(const CHAR *drive, struct DriveDatas &data);
BOOL GetDiskData(struct DriveDatas &data);         /* TRUE wenn Daten ermittelt */
BOOL DiskInDrive(const struct DriveDatas &data);   /* TRUE=Diskette im Laufwerk */
BOOL DiskProtected(const struct DriveDatas &data); /* TRUE=schreibgeschützt */

SLONG ReadSourceDisk(const struct DriveDatas &data);
SLONG WriteDestDisk(const struct DriveDatas &data, BOOL verify);

SLONG CheckDisk(const struct DriveDatas &data);

dossize_t pOS_Printf(const CHAR *format, ...);

/******************* VARIABLEN DEKLARATIONEN ************************/

const CHAR *gb_PrgHelpText="$H(DRIVE)Name des Gerätes mit abschließendem \":\" (z.B. DF0:)\n";
const CHAR *gb_PrgHeader  =PROGNAME" - Überprüft Disketten auf Lesefehler";
const CHAR *gb_PrgVersion ="$VER: "PROGNAME" "VERSIONR" ("PROGDATE") ("COPYRIGT")";
const CHAR *gb_PrgTemplate="DRIVE/A";

enum { ARG_drive, ARG_MAX };
ULONG args[ARG_MAX]={ 0 };

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

#ifdef __cplusplus
extern "C"
#endif
void main(void)
{
  SLONG res=DOSFAIL_FAIL;

  struct pOS_DosArgs *rda;
  if((rda=pOS_ReadDosArgs(gb_PrgTemplate,args,sizeof(args)/sizeof(ULONG),
    ARGTAG_PrgHeaderText, gb_PrgHeader,    /* kurze Programm-Beschreibung */
    ARGTAG_HelpText,      gb_PrgHelpText,  /* Help-Texte */
    ARGTAG_PrgVerText,    gb_PrgVersion,   /* VER-String */
    TAG_END)))
  {
    const CHAR *drive=(CHAR*)args[ARG_drive];

    struct DriveDatas data;
    if(GetDriveData(drive,data))
    {
      if(GetDiskData(data))  /* dann ermitteln der Geometry-Daten */
      {
        /* Laufwerk gegen Zugriffe sperren */
        if(pOS_InhibitDosDevice((pOS_DosDevice *)data.Dev,TRUE))
        {
          if(DiskInDrive(data))  /* Abfrage ob Diskette im Laufwerk ist */
          {

            pOS_Printf("Checking Volume \"%s\" in drive \"%s:\" with device \"%s\" unit %ld ...\n"
                       ,data.SourceDiskName
                       ,data.Dev->ddv_Dev.lib_Node.ln_Name
                       ,data.DevName
                       ,data.DevUnit
                      );

            res = CheckDisk(data);
          }
          else pOS_Printf("ERROR: no disk inserted in drive %s.\n",drive);

          /* Zugriffssperre vom Laufwerk aufheben */
          pOS_InhibitDosDevice((pOS_DosDevice *)data.Dev,FALSE);
        }
        else pOS_Printf("ERROR: Can't inhibit drive %s, Err %ld.\n",drive,pOS_GetIoErr());
      }
    }
    //else pOS_Printf("ERROR: Can't get DosDevice for drive %s, Err %ld.\n",drive,pOS_GetIoErr());

    pOS_DeleteDosArgs(rda);
  }
  else res=DOSFAIL_WARN;

  /* Fehlercode für Shell setzen */
  pOS_SetShellFail( res );
}

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

const pOS_DosDevice *GetDriveData(const CHAR *drive, struct DriveDatas &data)
{
  /* ermittelt welcher Handler und Unit für 'drive' zuständig ist */
  /* füllt handler mit dem Namen und unit mit der Nummer zum Verarbeiten mit pOS_OpenDevice() */

  data.Dev=NULL;
  data.SourceDiskName[0]='\0';

  /* kurz Locken, damit Handler und Volume korrekt gesetzt werden !! */
  pOS_FileLock *fl;
  if((fl=pOS_LockObject(NULL,drive,FILELKACC_Shared|FILELKACC_NoReq)))
  {
    pOS_UnlockObject(fl);
  }

  const pOS_DosDevice *const handlerdev=pOS_GetDosDeviceName(drive,DDTYP_Handler);
  if(handlerdev)
  {
    if(handlerdev->ddv_Type == DDTYP_Handler)
    {
      const struct pOS_DosMountDevice *const mount=handlerdev->ddv_U.ddv_Handler.ddvh_Mount;
      if(mount)
      {
        if(mount->dmd_Type == DMDTYP_BOD)
        {
          data.Dev=handlerdev;

          data.DevName=mount->dmd_U.dmd_BOD.dmbod_DevName;
          data.DevUnit=mount->dmd_U.dmd_BOD.dmbod_UnitNum;
          data.DevFlags=mount->dmd_U.dmd_BOD.dmbod_Flags;
        }
        else pOS_Printf("ERROR: drive not a block-oriented-device.\n");
      }
      else pOS_Printf("ERROR: can't get mountdevice.\n");
    }
    else pOS_Printf("ERROR: HandlerType not a handler.\n");
  }
  else pOS_Printf("ERROR: can't get doshandler for drive %s.\n",drive);

  return( data.Dev );
}

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

BOOL GetDiskData(struct DriveDatas &data)
{
  /* Daten über Source-Diskette ermitteln */
  /* z.B. wird erst nach dem Programmstart eine HD-Disk eingelegt, sind die Geometry-Daten falsch */
  /*      dehalb erst vor dem Kopierstart auslesen !! */
  /* liefert TRUE wenn Diskette eingelegt ist, sonst FALSE */

  /* nach Inhibit ist ActVolume immer NULL und von Zieldiskette auch uninterressant, deshalb kein Fehler */
  const struct pOS_DosDevice *const volume=data.Dev->ddv_U.ddv_Handler.ddvh_ActVolume;
  if(volume)
  {
    strcpy(data.SourceDiskName,volume->ddv_Dev.lib_Node.ln_Name);
    data.SourceDate=volume->ddv_U.ddv_Volume.ddvv_Date;
  }
  //else pOS_Printf("ERROR: no ActVolume in handler.\n");
  else { data.SourceDiskName[0]='\0'; }

  const struct pOS_DosMountDevice *const mount=data.Dev->ddv_U.ddv_Handler.ddvh_Mount;
  if(mount)
  {
    if(mount->dmd_Type == DMDTYP_BOD)
    {
      data.StartCyl=mount->dmd_U.dmd_BOD.dmbod_StartCyl;
      data.EndCyl=mount->dmd_U.dmd_BOD.dmbod_EndCyl;
      data.BlockPerCyl=mount->dmd_U.dmd_BOD.dmbod_BlockPerCyl;
      data.BytesPerBlock=mount->dmd_U.dmd_BOD.dmbod_BytesPerBlock;
    }
    else pOS_Printf("ERROR: drive not a block-oriented-device.\n");
  }
  else pOS_Printf("ERROR: can't get mountdevice.\n");

  return( mount ? TRUE : FALSE );
}

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

BOOL DiskInDrive(const struct DriveDatas &data)
{
  BOOL res=FALSE;

  struct pOS_TrackdiskIO *ioetd=NULL;
  if((ioetd=TDOpen(data.DevName,data.DevUnit,data.DevFlags)))
  {
    ULONG changestate=TDSimpleIO(ioetd,TDCMD_ChangeState);
    if(ioetd->tdio_Error==IOERR_None)
    {
      if(changestate == 0) res=TRUE;  /* Diskette im Laufwerk */
    }
    else pOS_Printf("ERROR: can't get ChangeState from drive %s.\n",data.Dev->ddv_Dev.lib_Node.ln_Name);
    TDClose(ioetd);
  }
  else pOS_Printf("ERROR: opening the %s unit %ld !\n",data.DevName,data.DevUnit);

  return( res );
}

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

SLONG CheckDisk(const struct DriveDatas &data)
{
  SLONG res=DOSFAIL_FAIL;

  UBYTE *buffer=NULL;
  struct pOS_TrackdiskIO *ioetd=NULL;
  ULONG numcyl, cyl, restcyl;
  SBYTE error;
  BOOL noerror;

  /* IO installieren und Device öffnen */
  if((ioetd=TDOpen(data.DevName,data.DevUnit,data.DevFlags)))
  {
    /* benötigte Daten über Diskette zusammenstellen */
    /* Anzahl der Cylinder ermitteln */
    numcyl=data.EndCyl-data.StartCyl+1;
    restcyl=numcyl-1;
    const ULONG TD_CYLSIZE=data.BlockPerCyl * data.BytesPerBlock;

    /* Speicher für einen kompletten Cylinder reservieren */
    if((buffer=ALLOCCyl()))
    {
      noerror=TRUE;
      pOS_Printf("\033[0 p");  /* Cursor ausschalten, sieht besser aus und steigert Textausgabegeschwindigkeit */

      res=DOSFAIL_ERROR;
      /* Schleife für alle Cylinder */
      for(cyl=data.StartCyl; cyl<=data.EndCyl; cyl++,restcyl--)
      {
        pOS_Printf("\rReading cylinder %ld, %ld to go ",cyl,restcyl);

        /* Track einlesen, 0 ist ok, sonst liegt ein Fehler vor */
        if((error=TDReadCyl(ioetd,buffer,cyl)) != 0)
        {
          pOS_Printf("\rError %ld in cylinder %ld: %s\033[K\n",error,cyl,TDError(error));
          noerror=FALSE;

          /* wurde Diskette gewechselt, dann sofort abbrechen */
          if(error==TDIOERR_DiskChanged)
            break;
        }

        /* mit CTRL+C vorzeitiger Abbruch möglich */
        if(pOS_SetSignal(0,0) & DOSSIGF_CTRL_C)
        {
          pOS_SetSignal(0,DOSSIGF_CTRL_C);  /* gesetztes Signal entfernen (optional) */
          noerror=FALSE;
          res=DOSFAIL_ABORT;
          pOS_Printf("\n***Break\n");
          break;
        }
      }

      if(noerror) { res=DOSFAIL_OK; pOS_Printf("\rNo errors detected.\033[K\n"); }
      else { pOS_Printf("\r\033[K"); }
      pOS_Printf("\033[ p");  /* Cursor wieder ein */

      /* Speicher freigeben */
      FREECyl(buffer);
    }
    else pOS_Printf("ERROR: unable to allocate memory for cylinder buffer !\n");

    /* Device wieder schließen */
    TDClose(ioetd);
  }
  else pOS_Printf("ERROR: opening the %s unit %ld !\n",data.DevName,data.DevUnit);

  return( res );
}

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

dossize_t pOS_Printf(const CHAR *format, ...)
{
  return( pOS_VFWritef(pOS_GetStdOutput(),format,(ULONG*)(&format)+1) );
}

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

