/*
 * CrypDisk.c
 *
 * Created by Olaf 'Rhialto' Seibert, after an analogy with SmartDisk.
 *
 * Uses essential parts of the xpkIDEA.library.
 * Why not that library itself? Because it insists on adding junk to
 * the encrypted data. And we can't have that, or an encrypted sector
 * would not fit in the same place as its plaintext version.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <exec/memory.h>
#include <exec/io.h>
#include <exec/semaphores.h>
#include <devices/trackdisk.h>
#include <devices/timer.h>
#include <dos/dosextens.h>
#include <dos/filehandler.h>
#include <dos/dostags.h>

#include <clib/exec_protos.h>
#include <clib/alib_protos.h>
#include <clib/dos_protos.h>

/*#include <libraries/xpk.h>*/

#ifdef DEBUG
# include "User:msh/syslog/syslog.h"
#else
# define debug(x)       /* nothing */
#endif

#define DEFLO		(2 * SECTOR_SIZE)
#define DEFHI		0
#define DEFPSTART	0
#define DEFPEND 	(-SECTOR_SIZE)

#define SECTOR_SIZE	TD_SECTOR

struct IOStdReq *IO;		/* All IO goes through here now. */
struct timerequest *TimeIO;	/* For the timeout */
struct SignalSemaphore *ss;	/* To force single threading	 */
void	       *Unit;		/* We crypt this Unit of the device */
__chip char	globbuffer[SECTOR_SIZE];    /* V1.3 compat. */
char		password[128];
char		CrypDiskPortName[32];
int		Idle;
ULONG		LoBlock;
ULONG		HiBlock;
ULONG		PartitionStart;
ULONG		PartitionEnd = DEFPEND;
char	       *Device;
int		UnitNr;
char	       *DosDev;
extern struct DosLibrary *DOSBase;
char		Sys2;
/* char 	   Permute = 1;
#define PERMUTE (SECTOR_SIZE / 4 - 21)  /* Ticks field in many types */

#define DEV_BEGINIO (-30)

#define min(a,b)    ((a) < (b) ? (a) : (b))

void		(*oldbeginio)(__A1 struct IOStdReq *,
			      __A6 struct Device *dev);
void		mybeginio(__A1 struct IOStdReq *,
			  __A6 struct Device *dev);

struct CrypDiskPort {
    struct MsgPort	cdp_Port;
    short		cdp_Command;
};
struct CrypDiskPort *CrypDiskPort;

#define CMD_NONE	0
#define CMD_SUSPEND	1
#define CMD_RESUME	2
#define CMD_CLOSE	3
#define CMD_OPEN	4
#define CMD_QUIT	5

typedef int (*func)(BYTE *User);

/* defines for Flags: see xpk.h, XPKIF_xxxxx */

/**************************************************************************
 *
 *		       The XpkSubParams structure
 *
 */

typedef struct XpkSubParams {
	APTR	InBuf		; /* The input data		  */
	LONG	InLen		; /* The number of bytes to pack  */
	APTR	OutBuf		; /* The output buffer		  */
	LONG	OutBufLen	; /* The length of the output buf */
	LONG	OutLen		; /* Number of bytes written	  */
	LONG	Flags		; /* Flags for master/sub comm.   */
	LONG	Number		; /* The number of this chunk	  */
	LONG	Mode		; /* The packing mode to use	  */
	APTR	Password	; /* The password to use	  */
	LONG	Arg[4]		; /* Reserved; don't use          */
	LONG	Sub[4]		; /* Sublib private data	  */
} XPARAMS;

struct XpkSubParams CryptParams = {
	0, SECTOR_SIZE,
	0, SECTOR_SIZE, SECTOR_SIZE,
	0, 0, 76,		  /* CBC1 is default encryption type */
	&password[0]
};
struct XpkSubParams DecryptParams;

long	PackChunk  ( __A0 XPARAMS * );
void	PackFree   ( __A0 XPARAMS * );
long	PackReset  ( __A0 XPARAMS * );
long	UnpackChunk( __A0 XPARAMS * );
void	UnpackFree ( __A0 XPARAMS * );

long	PackInit   ( __A0 XPARAMS * );  /* optional */
long	UnpackInit ( __A0 XPARAMS * );  /* optional */

struct MsgPort *
CreateExtPort(UBYTE *name, long pri, long size)
{
    struct MsgPort *port;

    if (port = AllocMem(size, MEMF_PUBLIC | MEMF_CLEAR)) {
	if ((char)(port->mp_SigBit = AllocSignal(-1L)) >= 0) {
	    port->mp_Node.ln_Pri = pri;
	    port->mp_Node.ln_Type = NT_MSGPORT;
	    port->mp_Node.ln_Name = name;
	    port->mp_Flags   = PA_SIGNAL;
	    port->mp_SigTask = FindTask(NULL);
	    NewList(&port->mp_MsgList);
	    if (name)
		AddPort(port);
	} else {
	    FreeMem(port, size);
	    port = NULL;
	}
    }
    return port;
}

void
DeleteExtPort(struct MsgPort *port, long size)
{
    if (port) {
	if (port->mp_Node.ln_Name)
	    RemPort(port);
	if (port->mp_Flags == PA_SIGNAL) {
	    FreeSignal(port->mp_SigBit);
	    port->mp_Flags = PA_IGNORE;
	}
	FreeMem(port, size);
    }
}

char	       *
strsave(char *s)
{
    char	   *n;

    if (n = malloc(strlen(s) + 1)) {
	strcpy(n, s);
	return n;
    }
    exit(20);
}

int
Encrypt(BYTE *User)
{
#ifdef PERMUTE
    if (Permute) {
	long		tmp;

	/* Unsafe: what if we're encrypting ROM? */
	tmp = ((long *)User)[0];
	((long *)User)[0] = ((long *)User)[PERMUTE];
	((long *)User)[PERMUTE] = tmp;
	debug(("Permute %08lx - %08lx\n", tmp,  ((long *)User)[0]));
    }
#endif
    CryptParams.InBuf = User;
    CryptParams.OutBuf = globbuffer;
    PackChunk(&CryptParams);
    PackReset(&CryptParams);
#ifdef PERMUTE
    if (Permute) {
	long		tmp;

	tmp = ((long *)User)[0];
	((long *)User)[0] = ((long *)User)[PERMUTE];
	((long *)User)[PERMUTE] = tmp;
    }
#endif

    IO->io_Command = CMD_WRITE;

    oldbeginio(IO, IO->io_Device);
    return WaitIO((struct IORequest *)IO);
}

int
Decrypt(BYTE *User)
{
    int err;

    IO->io_Command = CMD_READ;

    oldbeginio(IO, IO->io_Device);
    if (err = WaitIO((struct IORequest *)IO))
	return err;

    DecryptParams.InBuf = globbuffer;
    DecryptParams.OutBuf = User;
    UnpackChunk(&DecryptParams);
#ifdef PERMUTE
    if (Permute) {
	long		tmp;

	tmp = ((long *)User)[0];
	((long *)User)[0] = ((long *)User)[PERMUTE];
	((long *)User)[PERMUTE] = tmp;
	debug(("Permute %08lx - %08lx\n", tmp,  ((long *)User)[0]));
    }
#endif
}

LONG
DoSectors(ULONG Offset, BYTE *Data, LONG Length, func f)
{
    ULONG	    Actual = 0;
    struct MsgPort *port;

    port = CreatePort(NULL, 0);
    IO->io_Message.mn_ReplyPort = port;

    /*
     * See if we are below the encrypted region
     */
    while (Offset < LoBlock) {
	IO->io_Offset = Offset;
	IO->io_Data = Data;
	IO->io_Length = min(LoBlock - Offset, Length);

	debug(("Doing < LoBlock: %lx %lx\n", IO->io_Offset, IO->io_Length));
	oldbeginio(IO, IO->io_Device);
	WaitIO((struct IORequest *)IO);

	Data += IO->io_Actual;
	Offset += IO->io_Actual;
	Length -= IO->io_Actual;
	Actual += IO->io_Actual;
	if (Length == 0 || IO->io_Actual == 0)
	    goto finish;
    }

    /*
     * See if we will go above the encrypted region
     */
    if (Offset + Length >= HiBlock) {
	debug(("Doing >= HiBlock\n"));
	/*
	 * Do completely encrypted part first, recursively.
	 */
	while (Offset < HiBlock) {
	    ULONG done;

	    debug(("...Doing LoBlock <= ... < HiBlock\n"));
	    done = DoSectors(Offset, Data, min(HiBlock - Offset, Length), f);
	    debug(("...done LoBlock <= ... < HiBlock\n"));
	    Actual += done;
	    Offset += done;
	    Length -= done;
	    Actual += done;
	    if (Length == 0 || done == 0)
		goto finish;
	}
	IO->io_Message.mn_ReplyPort = port;
	/*
	 * Then do the rest.
	 */
	while (Length > 0) {
	    IO->io_Offset = Offset;
	    IO->io_Data = Data;
	    IO->io_Length = Length;

	    debug(("Doing >= HiBlock\n"));
	    oldbeginio(IO, IO->io_Device);
	    if (WaitIO((struct IORequest *)IO))
		goto finish;

	    Data += IO->io_Actual;
	    Offset += IO->io_Actual;
	    Length -= IO->io_Actual;
	    Actual += IO->io_Actual;
	}
	goto finish;
    }

    /*
     * If we don't know a password yet, suspend, and wait for
     * the main task to get one for us.
     */
    if (CryptParams.Sub[0] == NULL) {   /* No password yet */
	PutMsg(&CrypDiskPort->cdp_Port, &IO->io_Message);
	debug(("Waiting for AutoOpen\n"));
	ReleaseSemaphore(ss);
	WaitPort(port);
	ObtainSemaphore(ss);
    }

    while (Length > 0) {
	IO->io_Offset = Offset;
	IO->io_Data = globbuffer;
	IO->io_Length = SECTOR_SIZE;

	if (f(Data))
	    break;

	Data += SECTOR_SIZE;
	Offset += SECTOR_SIZE;
	Length -= SECTOR_SIZE;
	Actual += SECTOR_SIZE;
	Idle = 0;
    }

finish:
    DeletePort(port);
    return Actual;
}

__geta4 void
mybeginio(__A1 struct IOStdReq *req,
	  __A6 struct Device *dev)
{
    ULONG	    Offset;
    BYTE	   *Data;
    LONG	    Length;

    ObtainSemaphore(ss);

    /* if not our unit, don't do anything. */
    if (req->io_Unit != Unit) {
	oldbeginio(req, dev);
	ReleaseSemaphore(ss);
	return;
    }

    Offset = req->io_Offset;
    Data = req->io_Data;
    Length = req->io_Length;

    debug(("Command %d\n", req->io_Command));

    switch (req->io_Command & 0xFF) {
    case CMD_READ:
	debug(("READ sec %d: ", Offset / SECTOR_SIZE));
	IO->io_Command = CMD_READ;
	req->io_Actual = DoSectors(Offset, Data, Length, Decrypt);
	req->io_Error = IO->io_Error;
	debug(("%08lx %08lx %08lx %08lx\n", (*(struct { long l[4]; } *)Data)));
	debug(("\tLen: %x Act: %x Err: %d\n", Length, req->io_Actual, req->io_Error));
	goto TermIO;
    case CMD_WRITE:
    case TD_FORMAT:
	debug(("WRITE sec %d: ", Offset / SECTOR_SIZE));
	debug(("%08lx %08lx %08lx %08lx\n", (*(struct { long l[4]; } *)Data)));
	IO->io_Command = CMD_WRITE;
	req->io_Actual = DoSectors(Offset, Data, Length, Encrypt);
	req->io_Error = IO->io_Error;
	debug(("\tLen: %x Act: %x Err: %d\n", Length, req->io_Actual, req->io_Error));
    TermIO:
	if ((req->io_Flags & IOF_QUICK) == 0)
	    ReplyMsg(&req->io_Message);
	break;
    default:
	oldbeginio(req, dev);
    }
    ReleaseSemaphore(ss);
}

void
FreeKey(void)
{
    ObtainSemaphore(ss);
    if (CryptParams.Sub[0]) {
	PackFree(&CryptParams);
	CryptParams.Sub[0] = NULL;
    }
    if (DecryptParams.Sub[0]) {
	UnpackFree(&DecryptParams);
	DecryptParams.Sub[0] = NULL;
    }
    ReleaseSemaphore(ss);
}

int
ReadPassword(char *name)
{
    ULONG	    fh;
    int 	    len;

top:
    fh = Open(name, MODE_OLDFILE);
    if (fh) {
	int		interactive;
	int		i;
	char		c;

	FreeKey();
	interactive = IsInteractive(fh);
	if (interactive) {
	    Write(fh, "Password: ", 10L);
	    for (i = 0; i < sizeof(password); i++) {
		Read(fh, &c, 1);
		password[i] = c;
		if (c == '\r' || c == '\n')
		    break;
	    }
	    len = i;
	} else
	    len = Read(fh, password, sizeof(password)) - 1;
	Close(fh);
	if (interactive && (fh = Open(name, MODE_OLDFILE))) {
	    Write(fh, "Verify:   ", 10L);
	    for (i = 0; i <= len; i++) {
		Read(fh, &c, 1);
		if (c != password[i])
		    break;
	    }
	    Close(fh);
	    if (i <= len)
		goto top;
	}
	if (len > 0) {  /* kill newline */
	    password[len] = '\0';
	}
	if (strncmp(password, "##f", 3) == 0) {
	    len = ReadPassword(&password[3]);
	} else {
	    debug(("Password: `%s'\n", password));
	    ObtainSemaphore(ss);
	    len = PackInit(&CryptParams) || UnpackInit(&DecryptParams);
	    ReleaseSemaphore(ss);
	}
	setmem(password, sizeof(password), 0);  /* clear password */
    }

    return len;
}

void
GetPassword(void)
{
    while (ReadPassword("CON:020/170/600/030/CrypDisk Password..."))
	;
}

int
Suspend(void)
{
    if (Unit != NULL) {
	Unit = NULL;		/* We are inactive */
	return 1;
    }
    return 0;
}
int
Resume(void)
{
    if (Unit == NULL) {
	Idle = 0;
	Unit = IO->io_Unit;	/* We are active */
	return 1;
    }
    return 0;
}
int
XClose(void)
{
    int 	    changed;

    changed = Suspend();
    FreeKey();                  /* Ask for password when needed */
    return changed;
}
int
XOpen(void)
{
    GetPassword();              /* Explicitly ask for password  */
    Resume();
    return 1;
}

/*
 * 2.04+ ONLY!
 */

void
SetDosDev(char *name)
{
    struct DosList *dl = NULL;
    char	   *colon;

    if (name == NULL) {
	DosDev = NULL;
	PartitionStart = DEFPSTART;
	PartitionEnd = DEFPEND;
	LoBlock = DEFLO;
	HiBlock = DEFHI;
	return;
    }

    DosDev = strsave(name);
    colon = strrchr(DosDev, ':');

    dl = LockDosList(LDF_DEVICES | LDF_READ);
    if (dl) {
	struct FileSysStartupMsg *fssm;

	*colon = '\0';
	dl = FindDosEntry(dl, DosDev, LDF_DEVICES);
	if (dl) {
	    fssm = BADDR(dl->dol_misc.dol_handler.dol_Startup);
	    if (fssm) {
		struct DosEnvec *env;

		env = BADDR(fssm->fssm_Environ);
		if (env) {
		    long	    spc;    /* sectors per cylinder */
		    long	    bps;    /* bytes per sector */

		    spc = env->de_Surfaces * env->de_BlocksPerTrack;
		    bps = env->de_SizeBlock * 4;
		    PartitionStart = env->de_LowCyl * spc * bps;
		    PartitionEnd =  (env->de_HighCyl+1) * spc * bps;
		    LoBlock =  env->de_Reserved * bps;
		    HiBlock = -env->de_PreAlloc * bps;

		    Device = strsave((char *)BADDR(fssm->fssm_Device) + 1); /* BSTR! */
		    UnitNr = fssm->fssm_Unit;
		    debug(("%s -> %s %d\n", DosDev, Device, UnitNr));
		}
	    }
	}

	UnLockDosList(LDF_DEVICES | LDF_READ);
	*colon = ':';
    }
}

/*
 * Why do I create a new process just to execute this simple function?
 * Well, because uninhibiting causes a packet to be sent to the
 * filesystem, which tries to identify the disk before it returns
 * the packet. So, this task can block on the operation that causes
 * a password request... a deadlock situation. We could also have
 * used a task for the password, but using 2.04+ functions is so
 * much easier, and here we are already restricted to 2.04.
 */

__geta4 void
AsyncDiskChange(__A0 char *name, __D0 long len)
{
    debug(("Inhibit TRUE "));
    Inhibit(name, TRUE);
    debug(("Inhibit FALSE"));
    Inhibit(name, FALSE);       /* This causes disk I/O before completion */
    debug(("\n"));
}

void
DiskChange(char *name)
{
    if (Sys2 && name) {
	static struct TagItem tags[] = {
	    NP_Arguments, 0,
	    NP_Entry, (long) AsyncDiskChange,
	    NP_Name, (long)"Async Disk Changer",
	    /* rest is just attempted optimisation... */
	    NP_Input, 0,
	    NP_CloseInput, FALSE,
	    NP_Output, 0,
	    NP_CloseOutput, FALSE,
	    NP_Error, 0,
	    NP_CloseError, FALSE,
	    NP_CopyVars, FALSE,
	};
	tags[0].ti_Data = (long) name;
	debug(("CreateNewProc for %s\n", name));
	CreateNewProc(&tags[0]);
    }
}

int
main(int argc, char *argv[])
{
    long	    tmp;
    int 	    devopen;
    struct MsgPort *iosink;
    long	    iomask;
    long	    portmask;
    long	    waitmask;
    int 	    timeout;
    int 	    interval;

    tmp = CMD_NONE;
    IO = NULL;
    iosink = NULL;
    TimeIO = NULL;
    devopen = 0;
    Device = "trackdisk.device";
    UnitNr = 0;
    CryptParams.Mode = 76;	/* CBC-1 */
    timeout = 60 * 60;		/* one hour timeout */
    LoBlock = DEFLO;
    HiBlock = DEFHI;
    Sys2 = (DOSBase->dl_lib.lib_Version >= 37);

#ifdef DEBUG
    initsyslog();
#endif
    if (Sys2)
	SetDosDev("DF0:");

    {	/* Parse arguments */
	char	      **av;

	av = &argv[1];
	while (av[0]) {
	    if (av[0][0] == '-') {
		switch (av[0][1]) {
		case 'd':   /* Device */
		    av++;
		    if (Sys2 && av[0][strlen(av[0]) - 1] == ':')
			SetDosDev(av[0]);
		    else {
			Device = av[0];
			SetDosDev(NULL);
		    }
		    break;
		case 'u':   /* Unit */
		    av++;
		    UnitNr = atoi(av[0]);
		    SetDosDev(NULL);
		    break;
		case 'm':   /* Submethod */
		    av++;
		    CryptParams.Mode = atoi(av[0]);
		    break;
		case 't':   /* Timeout */
		    av++;
		    timeout = atoi(av[0]) * 60;
		    break;
		case 'l':   /* Low block */
		    av++;
		    LoBlock = atoi(av[0]) * SECTOR_SIZE;
		    break;
		case 'h':   /* High block */
		    av++;
		    HiBlock = atoi(av[0]) * SECTOR_SIZE;
		    break;
		case 's':   /* Suspend */
		    tmp = CMD_SUSPEND;
		    break;
		case 'r':   /* Resume */
		    tmp = CMD_RESUME;
		    break;
		case 'c':   /* Close */
		    tmp = CMD_CLOSE;
		    break;
		case 'o':   /* Open */
		    tmp = CMD_OPEN;
		    break;
		case 'q':   /* Quit */
		    tmp = CMD_QUIT;
		    break;
		default:
		    goto usage;
		}
	    } else {
	    usage:
		printf(
"CrypDisk V1.0, by Olaf 'Rhialto' Seibert\n"
"Usage: [run] CrypDisk [-d <disk.device>] [-u <unitnum>]\n"
"                      [-l <low block>] [-h <high block>]\n"
"                      [-m <submethod(0-100)>] [-t <timeout in minutes>]\n"
"Remote control: -s(uspend/^E) -r(esume/^F) -c(lose/^D) -o(pen) -q(uit/^C)\n");
		if (Sys2) {
		    printf(
"2.04+: [-d <DOS:>] -l,-h: relative to partition\n");
		}
		goto fail;
	    }
	    if (av[0])
		av++;
	}
    }
    DecryptParams = CryptParams;
    LoBlock += PartitionStart;
    if ((long)HiBlock <= 0)
	HiBlock += PartitionEnd;
    else
	HiBlock += PartitionStart;
    debug(("LoBlock %lx HiBlock %lx\n", LoBlock, HiBlock));

			     /*v    12	    v*/
    sprintf(CrypDiskPortName, "CrypDisk:%02d:%.18s", UnitNr, Device);
    Forbid();
    if (CrypDiskPort = FindPort(CrypDiskPortName)) {
	if (tmp) {
	    CrypDiskPort->cdp_Command = tmp;
	    Signal(CrypDiskPort->cdp_Port.mp_SigTask, 1L << CrypDiskPort->cdp_Port.mp_SigBit);
	} else {
	    printf("CrypDisk already active on %s %d.\n", Device, UnitNr);
	}
	Permit();
	goto fail;
    } else {
	if (tmp) {
	    printf("CrypDisk not active on %s %d.\n", Device, UnitNr);
	    Permit();
	    goto fail;
	}
	CrypDiskPort = CreateExtPort(CrypDiskPortName, -10, sizeof(*CrypDiskPort));
	Permit();
    }

    iosink = CreatePort(NULL, 0);
    IO = (struct IOStdReq *) CreateExtIO(iosink, sizeof (*IO));
    tmp = OpenDevice(Device, UnitNr, (struct IORequest *) IO, 0);

    if (tmp) {
	printf("Unable to open %s unit %d\n", Device, UnitNr);
	goto fail;
    } else
	devopen = 1;

    TimeIO = (struct timereq *) CreateExtIO(iosink, sizeof (*TimeIO));
    tmp = OpenDevice(TIMERNAME, UNIT_VBLANK, (struct IORequest *) TimeIO, 0);
    if (tmp) {
	printf("Unable to open timer\n");
	goto fail;
    }

    ss = AllocMem(sizeof (struct SignalSemaphore), MEMF_PUBLIC);
    if (!ss)
	goto fail;
    InitSemaphore(ss);
    SumLibrary((struct Library *) IO->io_Device);

    interval = min(60, timeout >> 1);
    TimeIO->tr_node.io_Command = TR_ADDREQUEST;
    TimeIO->tr_time.tv_secs = interval;
    TimeIO->tr_time.tv_micro = 0;
    SendIO((struct IORequest *)TimeIO);
    oldbeginio = SetFunction((struct Library *) IO->io_Device, DEV_BEGINIO, &mybeginio);

    portmask = 1L << CrypDiskPort->cdp_Port.mp_SigBit;
    iomask = 1L << iosink->mp_SigBit;
    waitmask = portmask | iomask |
	       SIGBREAKF_CTRL_C | SIGBREAKF_CTRL_D |
	       SIGBREAKF_CTRL_E | SIGBREAKF_CTRL_F;

    Resume();
    DiskChange(DosDev);

    for (;;) {
	char	       *msg = NULL;
	ULONG		eventmask;

	eventmask = Wait(waitmask);
	if (CheckIO((struct IORequest *)TimeIO)) {
	    WaitIO((struct IORequest *)TimeIO);

	    TimeIO->tr_time.tv_secs = interval;
	    SendIO((struct IORequest *)TimeIO);
	    Idle += interval;
	    debug(("time (%d)\n", Idle));
	    if (timeout > 0 && Idle >= timeout) {
		Idle = 0;
		if (Unit) {     /* Are we actually active? */
		    printf("(Timeout - Closed)\n");
		    XClose();
		    Resume();
		    /*
		     * Eventually, a message will arrive at the
		     * CrypDisk port to ask for a re-opening.
		     */
		}
	    }
	}
	if (msg = GetMsg(&CrypDiskPort->cdp_Port)) {
	    debug(("AutoOpen.. get passwd\n"));
	    XOpen();
	    debug(("AutoOpen.. got passwd\n"));
	    do {
		debug(("AutoOpen.. reply %lx\n", msg));
		ReplyMsg((struct Message *)msg);
	    } while (msg = GetMsg(&CrypDiskPort->cdp_Port));
	    printf("(Auto-opened)\n");
	}
	if (eventmask & SIGBREAKF_CTRL_C)
	    break;
	if (eventmask & SIGBREAKF_CTRL_D)
	    goto close;
	if (eventmask & SIGBREAKF_CTRL_E)
	    goto suspend;
	if (eventmask & SIGBREAKF_CTRL_F)
	    goto resume;
	if (eventmask & portmask) {
	    switch (CrypDiskPort->cdp_Command) {
	    case CMD_NONE:
		break;
	    case CMD_SUSPEND:
	    suspend:
		if (Suspend()) {
		    DiskChange(DosDev);
		    msg = "(Suspended)";
		}
		break;
	    case CMD_RESUME:
	    resume:
		if (Resume()) {
		    DiskChange(DosDev);
		    msg = "(Resumed)";
		}
		break;
	    case CMD_CLOSE:
	    close:
		if (XClose()) {
		    DiskChange(DosDev);
		    msg = "(Closed)";
		}
		break;
	    case CMD_OPEN:
		if (XOpen()) {
		    DiskChange(DosDev);
		    msg = "(Opened)";
		}
		break;
	    case CMD_QUIT:
		goto quit;
	    }
	    CrypDiskPort->cdp_Command = CMD_NONE;
	    if (msg)
		printf("%s\n", msg);
	}
    }
quit:

    Unit = NULL;	/* Keeps visitors out */
    ObtainSemaphore(ss);
    SetFunction((struct Library *) IO->io_Device, DEV_BEGINIO, (APTR) oldbeginio);
    FreeKey();
    ReleaseSemaphore(ss);

fail:
    if (ss)
	FreeMem(ss, sizeof (struct SignalSemaphore));
    if (devopen) {
	IO->io_Message.mn_ReplyPort = CrypDiskPort;
	CloseDevice((struct IORequest *) IO);
    }
    if (TimeIO) {
	AbortIO((struct IORequest *) TimeIO);
	WaitIO((struct IORequest *) TimeIO);
	CloseDevice((struct IORequest *) TimeIO);
	DeleteExtIO((struct IORequest *) TimeIO);
    }
    if (IO)
	DeleteExtIO((struct IORequest *) IO);
    if (iosink)
	DeletePort(iosink);
    if (CrypDiskPort)
	DeleteExtPort(&CrypDiskPort->cdp_Port, sizeof (*CrypDiskPort));
#ifdef DEBUG
    uninitsyslog();
#endif

    return 0;
}

void chkabort(void){}    /* DICE specific */
