/*
    (C) 1995-96 AROS - The Amiga Replacement OS
    $Id: ram_handler.c,v 1.11 1997/01/27 00:22:40 ldp Exp $

    Desc: RAM: handler
    Lang: english
*/
#include <exec/errors.h>
#include <exec/types.h>
#include <exec/resident.h>
#include <exec/memory.h>
#include <exec/semaphores.h>
#include <proto/exec.h>
#include <utility/tagitem.h>
#include <proto/utility.h>
#include <dos/dosextens.h>
#include <dos/dosasl.h>
#include <dos/exall.h>
#include <dos/filesystem.h>
#include <proto/dos.h>
#include <aros/libcall.h>
#ifdef __GNUC__
#include "ram_handler_gcc.h"
#endif
#include <aros/machine.h>
#include <stddef.h>

#define NEWLIST(l)                          \
((l)->lh_Head=(struct Node *)&(l)->lh_Tail, \
 (l)->lh_Tail=NULL,                         \
 (l)->lh_TailPred=(struct Node *)(l))

extern const char name[];
extern const char version[];
extern const APTR inittabl[4];
extern void *const functable[];
extern const UBYTE datatable;
extern struct rambase *AROS_SLIB_ENTRY(init,ramdev)();
extern void AROS_SLIB_ENTRY(open,ramdev)();
extern BPTR AROS_SLIB_ENTRY(close,ramdev)();
extern BPTR AROS_SLIB_ENTRY(expunge,ramdev)();
extern int AROS_SLIB_ENTRY(null,ramdev)();
extern void AROS_SLIB_ENTRY(beginio,ramdev)();
extern LONG AROS_SLIB_ENTRY(abortio,ramdev)();
extern void deventry();
extern const char end;

/* Device node */
struct cnode
{
    struct MinNode node;
    LONG type;			/* ST_LINKDIR */
    char *name; 		/* Link's name */
    struct cnode *self; 	/* Pointer to top of structure */
    struct hnode *link; 	/* NULL */
    LONG usecount;		/* >0 usecount locked:+(~0ul/2+1) */
    ULONG protect;		/* 0 */
    char *comment;		/* NULL */
    struct vnode *volume;	/* Pointer to volume */
    struct DosList *doslist;	/* Pointer to doslist entry */
};

/* Volume node */
struct vnode
{
    struct MinNode node;
    LONG type;			/* ST_USERDIR */
    char *name; 		/* Directory name */
    struct vnode *self; 	/* Points to top of structure */
    struct hnode *link; 	/* This one is linked to me */
    LONG usecount;		/* >0 usecount locked:+(~0ul/2+1) */
    ULONG protect;		/* 0 */
    char *comment;		/* NULL */
    struct MinList list;	/* Contents of directory */
    ULONG volcount;		/* number of handles on this volume */
    struct DosList *doslist;	/* Pointer to doslist entry */
};

/* Directory node */
struct dnode
{
    struct MinNode node;
    LONG type;			/* ST_USERDIR */
    char *name; 		/* Directory name */
    struct vnode *volume;	/* Volume's root directory */
    struct hnode *link; 	/* This one is linked to me */
    LONG usecount;		/* >0 usecount locked:+(~0ul/2+1) */
    ULONG protect;		/* protection bits */
    char *comment;		/* Some comment */
    struct MinList list;	/* Contents of directory */
};

/* File node */
struct fnode
{
    struct MinNode node;
    LONG type;			/* ST_FILE */
    char *name; 		/* Filename */
    struct vnode *volume;	/* Volume's root directory */
    struct hnode *link; 	/* This one is linked to me */
    LONG usecount;		/* >0 usecount locked:+(~0ul/2+1) */
    ULONG protect;		/* protection bits */
    char *comment;		/* Some file comment */
    LONG size;			/* Filesize */
    UBYTE *blocks[16];		/* Upto 0x1000 bytes */
    UBYTE **iblocks[4]; 	/* Upto 0x41000 bytes */
    UBYTE ***i2block;		/* Upto 0x1041000 bytes */
    UBYTE ****i3block;		/* Upto 0x101041000 bytes */
};

/* Softlink node */
struct snode
{
    struct MinNode node;
    LONG type;			/* ST_SOFTLINK */
    char *name; 		/* Link's name */
    struct vnode *volume;	/* Volume's root directory */
    struct hnode *link; 	/* This one is hardlinked to me */
    LONG usecount;		/* >0 usecount locked:+(~0ul/2+1) */
    ULONG protect;		/* protection bits */
    char *comment;		/* Some file comment */
    char *contents;		/* Contents of soft link */
};

/* Hardlink node */
struct hnode
{
    struct MinNode node;
    LONG type;			/* ST_LINKDIR */
    char *name; 		/* Link's name */
    struct vnode *volume;	/* Volume's root directory */
    struct hnode *link; 	/* This one is hardlinked to me */
    LONG usecount;		/* >0 usecount locked:+(~0ul/2+1) */
    ULONG protect;		/* protection bits */
    char *comment;		/* Some file comment */
    struct hnode *orig; 	/* original object */
};

#define BLOCKSIZE	256
#define PBLOCKSIZE	(256*sizeof(UBYTE *))

struct filehandle
{
    struct dnode *node;
    IPTR position;
};

int entry(void)
{
    /* If the device was executed by accident return error code. */
    return -1;
}

const struct Resident resident=
{
    RTC_MATCHWORD,
    (struct Resident *)&resident,
    (APTR)&end,
    RTF_AUTOINIT,
    1,
    NT_LIBRARY,
    0,
    (char *)name,
    (char *)&version[6],
    (ULONG *)inittabl
};

const char name[]="ram.handler";

const char version[]="$VER: ram handler 1.0 (28.3.96)\n\015";

const APTR inittabl[4]=
{
    (APTR)sizeof(struct rambase),
    (APTR)functable,
    (APTR)&datatable,
    &AROS_SLIB_ENTRY(init,ramdev)
};

void *const functable[]=
{
    &AROS_SLIB_ENTRY(open,ramdev),
    &AROS_SLIB_ENTRY(close,ramdev),
    &AROS_SLIB_ENTRY(expunge,ramdev),
    &AROS_SLIB_ENTRY(null,ramdev),
    &AROS_SLIB_ENTRY(beginio,ramdev),
    &AROS_SLIB_ENTRY(abortio,ramdev),
    (void *)-1
};

const UBYTE datatable=0;

AROS_LH2(struct rambase *, init,
 AROS_LHA(struct rambase *, rambase, D0),
 AROS_LHA(BPTR,             segList,   A0),
	   struct ExecBase *, SysBase, 0, ramdev)
{
    AROS_LIBFUNC_INIT

    /* This function is single-threaded by exec by calling Forbid. */

    struct MsgPort *port;
    struct Task *task;
    struct SignalSemaphore *semaphore;
    APTR stack;

    /* Store arguments */
    rambase->sysbase=SysBase;
    rambase->seglist=segList;
    rambase->dosbase=(struct DosLibrary *)OpenLibrary("dos.library",39);
    if(rambase->dosbase!=NULL)
    {
	rambase->utilitybase=(struct UtilityBase *)OpenLibrary("utility.library",39);
	if(rambase->utilitybase!=NULL)
	{
	    port=(struct MsgPort *)AllocMem(sizeof(struct MsgPort),MEMF_PUBLIC|MEMF_CLEAR);
	    if(port!=NULL)
	    {
		rambase->port=port;
		NEWLIST(&port->mp_MsgList);
		port->mp_Node.ln_Type=NT_MSGPORT;
		port->mp_SigBit=SIGB_SINGLE;

		task=(struct Task *)AllocMem(sizeof(struct Task),MEMF_PUBLIC|MEMF_CLEAR);
		if(task!=NULL)
		{
		    port->mp_SigTask=task;
		    port->mp_Flags=PA_IGNORE;
		    NEWLIST(&task->tc_MemEntry);
		    task->tc_Node.ln_Type=NT_TASK;
		    task->tc_Node.ln_Name="ram.handler task";

		    stack=AllocMem(2048,MEMF_PUBLIC);
		    if(stack!=NULL)
		    {
			task->tc_SPLower=stack;
			task->tc_SPUpper=(BYTE *)stack+2048;
#if AROS_STACK_GROWS_DOWNWARDS
			task->tc_SPReg=(BYTE *)task->tc_SPUpper-SP_OFFSET-sizeof(APTR);
			((APTR *)task->tc_SPUpper)[-1]=rambase;
#else
			task->tc_SPReg=(BYTE *)task->tc_SPLower-SP_OFFSET+sizeof(APTR);
			*(APTR *)task->tc_SPLower=rambase;
#endif

			semaphore=(struct SignalSemaphore *)AllocMem(sizeof(struct SignalSemaphore),MEMF_PUBLIC|MEMF_CLEAR);
			if(semaphore!=NULL)
			{
			    rambase->sigsem=semaphore;
			    InitSemaphore(semaphore);

			    if(AddTask(task,deventry,NULL)!=NULL)
				return rambase;

			    FreeMem(semaphore,sizeof(struct SignalSemaphore));
			}
			FreeMem(stack,2048);
		    }
		    FreeMem(task,sizeof(struct Task));
		}
		FreeMem(port,sizeof(struct MsgPort));
	    }
	    CloseLibrary((struct Library *)rambase->utilitybase);
	}
	CloseLibrary((struct Library *)rambase->dosbase);
    }

    return NULL;
    AROS_LIBFUNC_EXIT
}

/* Use This from now on */
#ifdef SysBase
    #undef SysBase
#endif
#ifdef DOSBase
    #undef DOSBase
#endif
#ifdef UtilityBase
    #undef UtilityBase
#endif
#define SysBase rambase->sysbase
#define DOSBase rambase->dosbase
#define UtilityBase rambase->utilitybase

static STRPTR Strdup(struct rambase *rambase, STRPTR string)
{
    STRPTR s2=string,s3;
    while(*s2++)
	;
    s3=(STRPTR)AllocMem(s2-string,MEMF_ANY);
    if(s3!=NULL)
	CopyMem(string,s3,s2-string);
    return s3;
}

static void Strfree(struct rambase *rambase, STRPTR string)
{
    STRPTR s2=string;
    if(string==NULL)
	return;
    while(*s2++)
	;
    FreeMem(string,s2-string);
}

AROS_LH3(void, open,
 AROS_LHA(struct IOFileSys *, iofs, A1),
 AROS_LHA(ULONG,              unitnum, D0),
 AROS_LHA(ULONG,              flags, D1),
	   struct rambase *, rambase, 1, ramdev)
{
    AROS_LIBFUNC_INIT
    struct filehandle *fhv, *fhc;
    struct vnode *vol;
    struct cnode *dev;
    struct DosList *dlv;

    /* Keep compiler happy */
    unitnum=flags=0;

    /* I have one more opener. */
    rambase->device.dd_Library.lib_OpenCnt++;

    /* Mark Message as recently used. */
    iofs->IOFS.io_Message.mn_Node.ln_Type=NT_REPLYMSG;

    iofs->IOFS.io_Error=ERROR_NO_FREE_STORE;
    fhv=(struct filehandle*)AllocMem(sizeof(struct filehandle),MEMF_CLEAR);
    if(fhv!=NULL)
{
	fhc=(struct filehandle*)AllocMem(sizeof(struct filehandle),MEMF_CLEAR);
	if(fhc!=NULL)
    {
	    vol=(struct vnode *)AllocMem(sizeof(struct vnode),MEMF_CLEAR);
	    if(vol!=NULL)
	    {
		dev=(struct cnode *)AllocMem(sizeof(struct cnode),MEMF_CLEAR);
		if(dev!=NULL)
		{
		    vol->name=Strdup(rambase,"Ram Disk");
		    if(vol->name!=NULL)
		    {
			dlv=MakeDosEntry("Ram Disk",DLT_VOLUME);
			if(dlv!=NULL)
			{
			    vol->type=ST_USERDIR;
			    vol->self=vol;
			    vol->doslist=dlv;
			    vol->protect=FIBF_READ|FIBF_WRITE|FIBF_EXECUTE|FIBF_DELETE;
			    NEWLIST((struct List *)&vol->list);
			    fhv->node=(struct dnode *)vol;
			    dlv->dol_Unit  =(struct Unit *)fhv;
			    dlv->dol_Device=&rambase->device;
			    dev->type=ST_LINKDIR;
			    dev->self=dev;
			    dev->volume=vol;
			    fhc->node=(struct dnode *)dev;
			    iofs->IOFS.io_Unit=(struct Unit *)fhc;
			    iofs->IOFS.io_Device=&rambase->device;
			    AddDosEntry(dlv);
			    rambase->device.dd_Library.lib_Flags&=~LIBF_DELEXP;
			    iofs->IOFS.io_Error=0;
			    return;
    }
			Strfree(rambase,vol->name);
		    }
		    FreeMem(dev,sizeof(struct cnode));
		}
		FreeMem(vol,sizeof(struct vnode));
	    }
	    FreeMem(fhc,sizeof(struct filehandle));
	}
	FreeMem(fhv,sizeof(struct filehandle));
    }

    /* Set returncode */
    iofs->IOFS.io_Error=IOERR_OPENFAIL;

    rambase->device.dd_Library.lib_OpenCnt--;
    AROS_LIBFUNC_EXIT
}

AROS_LH0(BPTR, expunge, struct rambase *, rambase, 3, ramdev)
{
    AROS_LIBFUNC_INIT

    BPTR ret;
    /*
	This function is single-threaded by exec by calling Forbid.
	Never break the Forbid() or strange things might happen.
    */

    /* Test for openers. */
    if(rambase->device.dd_Library.lib_OpenCnt)
    {
	/* Set the delayed expunge flag and return. */
	rambase->device.dd_Library.lib_Flags|=LIBF_DELEXP;
	return 0;
    }

    /* Kill device task and free all resources */
    RemTask(rambase->port->mp_SigTask);
    FreeMem(rambase->sigsem,sizeof(struct SignalSemaphore));
    FreeMem(((struct Task *)rambase->port->mp_SigTask)->tc_SPLower,2048);
    FreeMem(rambase->port->mp_SigTask,sizeof(struct Task));
    FreeMem(rambase->port,sizeof(struct MsgPort));
    CloseLibrary((struct Library *)rambase->utilitybase);
    CloseLibrary((struct Library *)rambase->dosbase);

    /* Get rid of the device. Remove it from the list. */
    Remove(&rambase->device.dd_Library.lib_Node);

    /* Get returncode here - FreeMem() will destroy the field. */
    ret=rambase->seglist;

    /* Free the memory. */
    FreeMem((char *)rambase-rambase->device.dd_Library.lib_NegSize,
	    rambase->device.dd_Library.lib_NegSize+rambase->device.dd_Library.lib_PosSize);

    return ret;
    AROS_LIBFUNC_EXIT
}

AROS_LH0I(int, null, struct rambase *, rambase, 4, ramdev)
{
    AROS_LIBFUNC_INIT
    return 0;
    AROS_LIBFUNC_EXIT
}

AROS_LH1(void, beginio,
 AROS_LHA(struct IOFileSys *, iofs, A1),
	   struct rambase *, rambase, 5, ramdev)
{
    AROS_LIBFUNC_INIT

    /* WaitIO will look into this */
    iofs->IOFS.io_Message.mn_Node.ln_Type=NT_MESSAGE;

    /* Nothing is done quick */
    iofs->IOFS.io_Flags&=~IOF_QUICK;

    /* So let the device task do it */
    PutMsg(rambase->port,&iofs->IOFS.io_Message);

    AROS_LIBFUNC_EXIT
}

AROS_LH1(LONG, abortio,
 AROS_LHA(struct IOFileSys *, iofs, A1),
	   struct rambase *, rambase, 6, ramdev)
{
    AROS_LIBFUNC_INIT
    return 0;
    AROS_LIBFUNC_EXIT
}

static LONG getblock(struct rambase *rambase, struct fnode *file, LONG block, int mode, UBYTE **result)
{
    ULONG a, i;
    UBYTE **p, **p2;

    if(block<0x10)
    {
	p=&file->blocks[block];
	block=0;
	i=0;
    }else if(block<0x410)
    {
	block-=0x10;
	p=(UBYTE **)&file->iblocks[block/0x100];
	block&=0xff;
	i=1;
    }else if(block<0x10410)
    {
	block-=0x410;
	p=(UBYTE **)&file->i2block;
	i=2;
    }else
    {
	block-=0x10410;
	p=(UBYTE **)&file->i3block;
	i=3;
    }
    switch(mode)
    {
	case -1:
	    p2=(UBYTE **)*p;
	    if(!block)
		*p=NULL;
	    p=p2;
	    while(i--&&p!=NULL)
	    {
		a=(block>>i*8)&0xff;
		p2=(UBYTE **)p[a];
		if(!(block&((1<<i*8)-1)))
		{
		    p[a]=NULL;
		    if(!a)
			FreeMem(p,PBLOCKSIZE);
		}
		p=p2;
	    }
	    if(p!=NULL)
		FreeMem(p,BLOCKSIZE);
	    break;
	case 0:
	    p=(UBYTE **)*p;
	    while(i--&&p!=NULL)
		p=((UBYTE ***)p)[(block>>i*8)&0xff];
	    *result=(UBYTE *)p;
	    break;
	case 1:
	    while(i--)
	    {
		if(*p==NULL)
		{
		    *p=AllocMem(PBLOCKSIZE,MEMF_CLEAR);
		    if(*p==NULL)
			return ERROR_NO_FREE_STORE;
		}
		p=(UBYTE **)*p+((block>>i*8)&0xff);
	    }
	    if(*p==NULL)
	    {
		*p=AllocMem(BLOCKSIZE,MEMF_CLEAR);
		if(*p==NULL)
		    return ERROR_NO_FREE_STORE;
	    }
	    *result=*p;
	    break;
    }
    return 0;
}

static void zerofill(UBYTE *address, ULONG size)
{
    while(size--)
	*address++=0;
}

static void shrinkfile(struct rambase *rambase, struct fnode *file, LONG size)
{
    ULONG blocks, block;
    UBYTE *p;

    blocks=(size+BLOCKSIZE-1)/BLOCKSIZE;
    block =(file->size+BLOCKSIZE-1)/BLOCKSIZE;
    for(;block-->blocks;)
	(void)getblock(rambase,file,block,-1,&p);
    if(size&0xff)
    {
	(void)getblock(rambase,file,size,0,&p);
	if(p!=NULL)
	    zerofill(p+(size&0xff),-size&0xff);
    }
    file->size=size;
}

static void delete(struct rambase *rambase, struct fnode *file)
{
    struct hnode *link, *new, *more;
    struct Node *node;

    Strfree(rambase,file->name);
    Strfree(rambase,file->comment);
    Remove((struct Node *)file);

    if(file->type==ST_LINKDIR)
    {
	/* It is a link. Remove it from the chain. */
	link=((struct hnode *)file)->orig;
	((struct hnode *)file)->orig=NULL;
	file->type=link->type;
	while((struct fnode *)link->link!=file)
	    link=link->link;
	link->link=file->link;
    }else if(file->link!=NULL)
    {
	/* If there is a hard link to the object make the link the original */
	link=file->link;
	link->type=file->type;
	more=new->link;
	while(more!=NULL)
	{
	    more->orig=new;
	    more=more->link;
	}
	switch(file->type)
	{
	    case ST_USERDIR:
		while((node=RemHead((struct List *)&((struct dnode *)file)->list))!=NULL)
		    AddTail((struct List *)&((struct dnode *)file)->list,node);
		break;
	    case ST_FILE:
		CopyMemQuick(&file->size,&((struct fnode *)new)->size,sizeof(struct fnode)-offsetof(struct fnode,size));
		zerofill((UBYTE *)&file->size,sizeof(struct fnode)-offsetof(struct fnode,size));
		break;
	    case ST_SOFTLINK:
		((struct snode *)new)->contents=((struct snode *)file)->contents;
		((struct snode *)file)->contents=NULL;
		break;
	}
    }
    switch(file->type)
    {
	case ST_USERDIR:
	    FreeMem(file,sizeof(struct dnode));
	    return;
	case ST_FILE:
	    shrinkfile(rambase,file,0);
	    FreeMem(file,sizeof(struct fnode));
	    return;
	case ST_SOFTLINK:
	    Strfree(rambase,((struct snode *)file)->contents);
	    FreeMem(file,sizeof(struct snode));
	    return;
    }
}

static int fstrcmp(struct rambase *rambase, char *s1, char *s2)
{
    for(;;)
    {
	if(ToLower(*s1)!=ToLower(*s2))
	    return *s1||*s2!='/';
	if(!*s1)
	    return 0;
	s1++; s2++;
    }
}

static LONG findname(struct rambase *rambase, STRPTR *name, struct dnode **dnode)
{
    struct dnode *cur=*dnode;
    char *rest=*name;

    for(;;)
    {
	if(cur->type==ST_LINKDIR)
	    cur=(struct dnode *)((struct hnode *)cur)->orig;
	if(!*rest)
	    break;
	if(*rest=='/')
	{
	    if((struct dnode *)cur->volume==cur)
		return ERROR_OBJECT_NOT_FOUND;
	    while(cur->node.mln_Pred!=NULL)
		cur=(struct dnode *)cur->node.mln_Pred;
	    cur=(struct dnode *)((BYTE *)cur-offsetof(struct dnode,list));
	}else
	{
	    if(cur->type==ST_SOFTLINK)
	    {
		*dnode=cur;
		*name=rest;
		return ERROR_IS_SOFT_LINK;
	    }
	    if(cur->type!=ST_USERDIR)
		return ERROR_DIR_NOT_FOUND;
	    *dnode=cur;
	    cur=(struct dnode *)cur->list.mlh_Head;
	    for(;;)
	    {
		if(cur->node.mln_Succ==NULL)
		{
		    *name=rest;
		    return ERROR_OBJECT_NOT_FOUND;
		}
		if(!fstrcmp(rambase,cur->name,rest))
		    break;
		cur=(struct dnode *)cur->node.mln_Succ;
	    }
	}
	while(*rest)
	    if(*rest++=='/')
		    break;
    }
    *dnode=cur;
    return 0;
}

static LONG set_file_size(struct rambase *rambase, struct filehandle *handle, LONG *offl, LONG *offh, LONG mode)
{
    struct fnode *file=(struct fnode *)handle->node;
    LONG size=*offl;

    if((size<0?-1:0)!=*offh)
	return ERROR_SEEK_ERROR;

    if((size<0?-1:0)!=*offh)
	return ERROR_SEEK_ERROR;

    if(file->type!=ST_FILE)
	return ERROR_OBJECT_WRONG_TYPE;
    switch(mode)
    {
	case OFFSET_BEGINNING:	break;
	case OFFSET_CURRENT:	size+=handle->position; break;
	case OFFSET_END:	size+=file->size; break;
	default:		return ERROR_NOT_IMPLEMENTED;
    }
    if(size<0)
	return ERROR_SEEK_ERROR;
    if(size<file->size)
	shrinkfile(rambase,file,size);
    file->size=*offl=size;
    *offh=0;
    return 0;
}

static LONG read(struct rambase *rambase, struct filehandle *handle, APTR buffer, LONG *numbytes)
{
    struct fnode *file=(struct fnode *)handle->node;
    ULONG num =*numbytes;
    ULONG size=file->size;
    ULONG block, offset;
    UBYTE *buf=buffer, *p;

    if(handle->position>=size)
	num=0;
    else if(handle->position+num>size)
	num=size-handle->position;
    block =handle->position/BLOCKSIZE;
    offset=handle->position&(BLOCKSIZE-1);
    size  =BLOCKSIZE-offset;
    while(num)
    {
	if(size>num)
	    size=num;
	(void)getblock(rambase,file,block,0,&p);
	if(p!=NULL)
	    CopyMem(p+offset,buffer,size);
	else
	    zerofill(buffer,size);
	buffer+=size;
	num   -=size;
	block++;
	offset=0;
	size=BLOCKSIZE;
    }
    *numbytes=(UBYTE *)buffer-buf;
    handle->position+=*numbytes;
    return 0;
}

static LONG write(struct rambase *rambase, struct filehandle *handle, UBYTE *buffer, LONG *numbytes)
{
    struct fnode *file=(struct fnode *)handle->node;
    ULONG num =*numbytes;
    ULONG size=file->size;
    ULONG block, offset;
    UBYTE *buf=buffer, *p;
    LONG error=0;

    if((LONG)(handle->position+num)<0)
	return ERROR_OBJECT_TOO_LARGE;
    block =handle->position/BLOCKSIZE;
    offset=handle->position&(BLOCKSIZE-1);
    size  =BLOCKSIZE-offset;
    while(num)
    {
	if(size>num)
	    size=num;
	error=getblock(rambase,file,block,1,&p);
	if(error)
	    break;
	CopyMem(buffer,p+offset,size);
	buffer+=size;
	num   -=size;
	block++;
	offset=0;
	size=BLOCKSIZE;
    }
    *numbytes=(UBYTE *)buffer-buf;
    handle->position+=*numbytes;
    if(handle->position>file->size)
	file->size=handle->position;
    return error;
}

static LONG lock(struct dnode *dir, ULONG mode)
{
    if((mode&FMF_EXECUTE)&&!(dir->protect&FMF_EXECUTE))
	return ERROR_NOT_EXECUTABLE;
    if((mode&FMF_WRITE)&&!(dir->protect&FMF_WRITE))
	return ERROR_WRITE_PROTECTED;
    if((mode&FMF_READ)&&!(dir->protect&FMF_READ))
	return ERROR_READ_PROTECTED;
    if(mode&FMF_LOCK)
    {
	if(dir->usecount)
	    return ERROR_OBJECT_IN_USE;
	dir->usecount=~0ul/2+1;
    }else
	if(dir->usecount<0)
	    return ERROR_OBJECT_IN_USE;
    dir->usecount++;
    dir->volume->volcount++;
    return 0;
}

static LONG open_(struct rambase *rambase, struct filehandle **handle, STRPTR name, ULONG mode)
{
    struct dnode *dir=(*handle)->node;
    struct filehandle *fh;
    LONG error;

    fh=(struct filehandle *)AllocMem(sizeof(struct filehandle),MEMF_CLEAR);
    if(fh!=NULL)
    {
	error=findname(rambase,&name,&dir);
	if(!error)
	{
	    error=lock(dir,mode);
	    if(!error)
	    {
		fh->node=dir;
		*handle=fh;
		return 0;
	    }
	}
	FreeMem(fh,sizeof(struct filehandle));
    }else
	error=ERROR_NO_FREE_STORE;
    return error;
}

static LONG open_file(struct rambase *rambase, struct filehandle **handle, STRPTR name, ULONG mode, ULONG protect)
{
    struct dnode *dir=(*handle)->node;
    struct filehandle *fh;
    LONG error;

    fh=AllocMem(sizeof(struct filehandle),MEMF_CLEAR);
    if(fh!=NULL)
    {
	error=findname(rambase,&name,&dir);
	if((mode&FMF_CREATE)&&error==ERROR_OBJECT_NOT_FOUND)
	{
	    char *s=name;
	    struct fnode *file;
	    while(*s)
		if(*s++=='/')
		    return error;
	    file=(struct fnode *)AllocMem(sizeof(struct fnode),MEMF_CLEAR);
	    if(file!=NULL)
	    {
		file->name=Strdup(rambase,name);
		if(file->name!=NULL)
		{
		    file->type=ST_FILE;
		    file->protect=protect;
		    file->volume=dir->volume;
		    AddTail((struct List *)&dir->list,(struct Node *)file);
		    error=lock((struct dnode *)file,mode);
		    if(!error)
		    {
			fh->node=(struct dnode *)file;
			*handle=fh;
			return 0;
		    }
		    Strfree(rambase,file->name);
		}
		FreeMem(file,sizeof(struct fnode));
	    }
	    error=ERROR_NO_FREE_STORE;
	}else if(!error)
	{
	    if(dir->type!=ST_FILE)
		error=ERROR_OBJECT_WRONG_TYPE;
	    else
	    {
		error=lock(dir,mode);
		if(!error)
		{
		    fh->node=dir;
		    *handle=fh;
		    return 0;
		}
	    }
	}
	FreeMem(fh,sizeof(struct filehandle));
    }
    return error;
}

static LONG create_dir(struct rambase *rambase, struct filehandle **handle, STRPTR name, ULONG protect)
{
    struct dnode *dir=(*handle)->node, *new;
    struct filehandle *fh;
    STRPTR s;
    LONG error;

    error=findname(rambase,&name,&dir);
    if(!error)
	return ERROR_OBJECT_EXISTS;
    if(error!=ERROR_OBJECT_NOT_FOUND)
	return error;
    s=name;
    while(*s)
	if(*s++=='/')
	    return error;
    fh=AllocMem(sizeof(struct filehandle),MEMF_CLEAR);
    if(fh!=NULL)
    {
	new=(struct dnode *)AllocMem(sizeof(struct dnode),MEMF_CLEAR);
	if(new!=NULL)
	{
	    new->name=Strdup(rambase,name);
	    if(new->name!=NULL)
	    {
		new->type=ST_USERDIR;
		new->protect=protect;
		new->volume=dir->volume;
		new->volume->volcount++;
		new->usecount=~0ul/2+2;
		NEWLIST((struct List *)&new->list);
		AddTail((struct List *)&dir->list,(struct Node *)new);
		fh->node=new;
		*handle=fh;
		return 0;
	    }
	    FreeMem(new,sizeof(struct dnode));
	}
	FreeMem(fh,sizeof(struct filehandle));
    }
    return ERROR_NO_FREE_STORE;
}

static LONG free_lock(struct rambase *rambase, struct filehandle *filehandle)
{
    struct dnode *dnode=filehandle->node;
    dnode->usecount=(dnode->usecount-1)&~0ul/2;
    FreeMem(filehandle,sizeof(struct filehandle));
    dnode->volume->volcount--;
    return 0;
}

static LONG seek(struct rambase *rambase, struct filehandle *filehandle, LONG *posh, LONG *posl, LONG mode)
{
    struct fnode *file=(struct fnode *)filehandle->node;
    LONG pos=*posl;

    if((pos<0?-1:0)!=*posh)
	return ERROR_SEEK_ERROR;
    if(file->type!=ST_FILE)
	return ERROR_OBJECT_WRONG_TYPE;
    switch(mode)
    {
	case OFFSET_BEGINNING:	break;
	case OFFSET_CURRENT:	pos+=filehandle->position; break;
	case OFFSET_END:	pos+=file->size; break;
	default:		return ERROR_NOT_IMPLEMENTED;
    }
    if(pos<0)
	return ERROR_SEEK_ERROR;
    *posh=0;
    *posl=filehandle->position;
    filehandle->position=pos;
    return 0;
}

static const ULONG sizes[]=
{ 0, offsetof(struct ExAllData,ed_Type), offsetof(struct ExAllData,ed_Size),
  offsetof(struct ExAllData,ed_Prot), offsetof(struct ExAllData,ed_Days),
  offsetof(struct ExAllData,ed_Comment), offsetof(struct ExAllData,ed_OwnerUID),
  sizeof(struct ExAllData)
};

static LONG examine(struct fnode *file, struct ExAllData *ead, ULONG size, ULONG type)
{
    STRPTR next, end, name;
    if(type>ED_OWNER)
	return ERROR_BAD_NUMBER;
    next=(STRPTR)ead+sizes[type];
    end=(STRPTR)ead+size;
    switch(type)
    {
	case ED_OWNER:
	    ead->ed_OwnerUID=0;
	    ead->ed_OwnerGID=0;
	case ED_COMMENT:
	    if(file->comment!=NULL)
	    {
		ead->ed_Comment=next;
		name=file->comment;
		for(;;)
		{
		    if(next>=end)
			return ERROR_BUFFER_OVERFLOW;
		    if(!(*next++=*name++))
			break;
		}
	    }else
		ead->ed_Comment=NULL;
	case ED_DATE:
	    ead->ed_Days=0;
	    ead->ed_Mins=0;
	    ead->ed_Ticks=0;
	case ED_PROTECTION:
	    ead->ed_Prot=file->protect;
	case ED_SIZE:
	    ead->ed_Size=file->size;
	case ED_TYPE:
	    ead->ed_Type=file->type;
	    if(((struct vnode *)file)->self==(struct vnode *)file)
		ead->ed_Type=ST_ROOT;
	case ED_NAME:
	    ead->ed_Name=next;
	    name=file->name;
	    for(;;)
	    {
		if(next>=end)
		    return ERROR_BUFFER_OVERFLOW;
		if(!(*next++=*name++))
		    break;
	    }
	case 0:
	    ead->ed_Next=(struct ExAllData *)(((IPTR)next+AROS_PTRALIGN-1)&~(AROS_PTRALIGN-1));
    }
    return 0;
}

static LONG examine_all(struct filehandle *dir, struct ExAllData *ead, ULONG size, ULONG type)
{
    STRPTR end;
    struct ExAllData *last=NULL;
    struct fnode *ent;
    LONG error;
    end=(STRPTR)ead+size;
    if(dir->node->type!=ST_USERDIR)
	return ERROR_OBJECT_WRONG_TYPE;
    ent=(struct fnode *)dir->position;
    if(ent==NULL)
    {
	ent=(struct fnode *)dir->node->list.mlh_Head;
	ent->usecount++;
    }
    if(ent->node.mln_Succ==NULL)
	return ERROR_NO_MORE_ENTRIES;
    ent->usecount--;
    do
    {
	error=examine(ent,ead,end-(STRPTR)ead,type);
	if(error==ERROR_BUFFER_OVERFLOW)
	{
	    if(last==NULL)
		return error;
	    ent->usecount++;
	    last->ed_Next=NULL;
	    dir->position=(IPTR)ent;
	    return 0;
	}
	last=ead;
	ead=ead->ed_Next;
	ent=(struct fnode *)ent->node.mln_Succ;
    }while(ent->node.mln_Succ!=NULL);
    last->ed_Next=NULL;
    dir->position=(IPTR)ent;
    return 0;
}

static LONG delete_object(struct rambase *rambase, struct filehandle *filehandle, STRPTR name)
{
    struct dnode *file=filehandle->node;
    LONG error;
    error=findname(rambase,&name,&file);
    if(error)
	return error;
    if((struct dnode *)file->volume==file)
	return ERROR_OBJECT_WRONG_TYPE;
    if(file->usecount)
	return ERROR_OBJECT_IN_USE;
    if(!(file->protect&FIBF_DELETE))
	return ERROR_DELETE_PROTECTED;
    if(file->type==ST_USERDIR&&file->list.mlh_Head->mln_Succ!=NULL)
	return ERROR_DIRECTORY_NOT_EMPTY;
    delete(rambase,(struct fnode *)file);
    return 0;
}

AROS_LH1(BPTR, close,
 AROS_LHA(struct IOFileSys *, iofs, A1),
	   struct rambase *, rambase, 2, ramdev)
{
    AROS_LIBFUNC_INIT
    struct cnode *dev;
    struct vnode *vol;
    struct dnode *dir;
    struct fnode *file;
    struct filehandle *handle;

    handle=(struct filehandle *)iofs->IOFS.io_Unit;
    dev=(struct cnode *)handle->node;
    vol=dev->volume;
    if(dev->type!=ST_LINKDIR||dev->self!=dev)
    {
	iofs->io_DosError=ERROR_OBJECT_WRONG_TYPE;
	return 0;
    }
    if(vol->volcount)
    {
	iofs->io_DosError=ERROR_OBJECT_IN_USE;
	return 0;
    }

    /* Let any following attemps to use the device crash hard. */
    iofs->IOFS.io_Device=(struct Device *)-1;

    free_lock(rambase,handle);
    RemDosEntry(vol->doslist);
    FreeDosEntry(vol->doslist);

    while(vol->list.mlh_Head->mln_Succ!=NULL)
    {
	dir=(struct dnode *)vol->list.mlh_Head;
	if(dir->type==ST_USERDIR)
	    while((file=(struct fnode *)RemHead((struct List *)&dir->list))!=NULL)
		AddTail((struct List *)&vol->list,(struct Node *)dir);
	delete(rambase,(struct fnode *)dir);
    }
    Strfree(rambase,vol->name);
    FreeMem(vol,sizeof(struct vnode));

    Strfree(rambase,dev->name);
    FreeMem(dev,sizeof(struct cnode));

    iofs->io_DosError=0;

    /* I have one fewer opener. */
    if(!--rambase->device.dd_Library.lib_OpenCnt)
    {
	/* Delayed expunge pending? */
	if(rambase->device.dd_Library.lib_Flags&LIBF_DELEXP)
	    /* Then expunge the device */
	    return expunge();
    }
    return 0;
    AROS_LIBFUNC_EXIT
}

void deventry(struct rambase *rambase)
{
    struct IOFileSys *iofs;
    struct dnode *dir;
    LONG error=0;
    /*
	Init device port. AllocSignal() cannot fail because this is a
	freshly created task with all signal bits still free.
    */
    rambase->port->mp_SigBit=AllocSignal(-1);
    rambase->port->mp_Flags=PA_SIGNAL;

    /* Get and process the messages. */
    for(;;)
    {
	while((iofs=(struct IOFileSys *)GetMsg(rambase->port))!=NULL)
	{
	    switch(iofs->IOFS.io_Command)
	    {
		case FSA_OPEN:
		    /*
			get handle on a file or directory
			Unit *current; current directory / new handle on return
			STRPTR name;   file- or directoryname
			LONG mode;     open mode
		    */
		    error=open_(rambase,(struct filehandle **)&iofs->IOFS.io_Unit,
				      (STRPTR)iofs->io_Args[0], iofs->io_Args[1]);
		    break;

		case FSA_OPEN_FILE:
		    /*
			open a file or create a new one
			Unit *current; current directory / new handle on return
			STRPTR name;   file- or directoryname
			LONG mode;     open mode
			LONG protect;  protection flags if a new file is created
		    */
		    error=open_file(rambase,(struct filehandle **)&iofs->IOFS.io_Unit,
				      (STRPTR)iofs->io_Args[0], iofs->io_Args[1],
				      iofs->io_Args[2]);
		    break;

		case FSA_READ:
		    /*
			read a number of bytes from a file
			Unit *current; filehandle
			APTR buffer;   data
			LONG numbytes; number of bytes to read /
				       number of bytes read on return,
				       0 if there are no more bytes in the file
		    */
		    error=read(rambase,(struct filehandle *)iofs->IOFS.io_Unit,
			     (APTR)iofs->io_Args[0], &iofs->io_Args[1]);
		    break;

		case FSA_WRITE:
		    /*
			write a number of bytes to a file
			Unit *current; filehandle
			APTR buffer;   data
			LONG numbytes; number of bytes to write /
				       number of bytes written on return
		    */
		    error=write(rambase,(struct filehandle *)iofs->IOFS.io_Unit,
			      (APTR)iofs->io_Args[0], &iofs->io_Args[1]);
		    break;

		case FSA_SEEK:
		    /*
			set / read position in file
			Unit *current; filehandle
			LONG posh;
			LONG posl;     relative position /
				       old position on return
			LONG mode;     one of OFFSET_BEGINNING, OFFSET_CURRENT,
				       OFFSET_END
		    */
		    error=seek(rambase,(struct filehandle *)iofs->IOFS.io_Unit,
			     &iofs->io_Args[0], &iofs->io_Args[1], iofs->io_Args[2]);
		    break;

		case FSA_CLOSE:
		    /*
			get rid of a handle
			Unit *current; filehandle
		    */
		    error=free_lock(rambase,(struct filehandle *)iofs->IOFS.io_Unit);
		    break;

		case FSA_EXAMINE:
		    /*
			Get information about the current object
			Unit *current; current object
			struct ExAllData *ead; buffer to be filled
			ULONG size;    size of the buffer
			ULONG type;    type of information to get
		    */
		    error=examine((struct fnode *)((struct filehandle *)iofs->IOFS.io_Unit)->node,
				  (struct ExAllData*)iofs->io_Args[0],
				  iofs->io_Args[1], iofs->io_Args[2]);
		    break;

		case FSA_EXAMINE_ALL:
		    /*
			Read the current directory
			Unit *current; current directory
			struct ExAllData *ead; buffer to be filled
			ULONG size;    size of the buffer
			ULONG type;    type of information to get
		    */
		    error=examine_all((struct filehandle *)iofs->IOFS.io_Unit,
				      (struct ExAllData*)iofs->io_Args[0],
				      iofs->io_Args[1], iofs->io_Args[2]);
		    break;

		case FSA_CREATE_DIR:
		    /*
			Build lock and open a new directory
			Unit *current; current directory
			STRPTR name;   name of the dir to create
			LONG protect;  Protection flags for the new dir
		    */
		    error=create_dir(rambase,(struct filehandle **)&iofs->IOFS.io_Unit,
				     (STRPTR)iofs->io_Args[0], iofs->io_Args[1]);
		    break;

		case FSA_DELETE_OBJECT:
		    /*
			Delete file or directory
			Unit *current; current directory
			STRPTR name;   filename
		    */
		    error=delete_object(rambase,(struct filehandle *)iofs->IOFS.io_Unit,
					(STRPTR)iofs->io_Args[0]);
		    break;

		case FSA_SET_PROTECT:
		    /*
			Set protection bits for a certain file or directory.
			Unit *current; current directory
			STRPTR name;   filename
			ULONG protect; new protection bits
		    */
		    dir=((struct filehandle *)iofs->IOFS.io_Unit)->node;
		    error=findname(rambase,(STRPTR *)&iofs->io_Args[0],&dir);
		    if(!error)
			dir->protect=iofs->io_Args[1];
		    break;

		case FSA_SET_OWNER:
		    /*
			Set owner and group of the file or directory
			Unit *current; current directory
			STRPTR name;   filename
			ULONG UID;
			ULONG GID;
		    */
		    dir=((struct filehandle *)iofs->IOFS.io_Unit)->node;
		    error=findname(rambase,(STRPTR *)&iofs->io_Args[0],&dir);
		    if(!error)
		    {
		    }
		    break;

		case FSA_SET_DATE:
		    /*
			Set creation date of the file
			Unit *current; current directory
			STRPTR name;   filename
			ULONG days;
			ULONG mins;
			ULONG ticks;   timestamp
		    */
		    dir=((struct filehandle *)iofs->IOFS.io_Unit)->node;
		    error=findname(rambase,(STRPTR *)&iofs->io_Args[0],&dir);
		    if(!error)
		    {
		    }
		    break;

		case FSA_SET_COMMENT:
		    /*
			Set a comment for the file or directory;
			Unit *current; current directory
			STRPTR name;   filename
			STRPTR comment; NUL terminated C string or NULL.
		    */
		    dir=((struct filehandle *)iofs->IOFS.io_Unit)->node;
		    error=findname(rambase,(STRPTR *)&iofs->io_Args[0],&dir);
		    if(!error)
		    {
			if(iofs->io_Args[1])
			{
			    STRPTR s=Strdup(rambase,(STRPTR)iofs->io_Args[0]);
			    if(s!=NULL)
			    {
				Strfree(rambase,dir->comment);
				dir->comment=s;
			    }else
				error=ERROR_NO_FREE_STORE;
			}else
			{
			    Strfree(rambase,dir->comment);
			    dir->comment=NULL;
			}
		    }
		    break;

		case FSA_SET_FILE_SIZE:
		    /*
			Set a new size for the file.
			Unit *file;    filehandle
			LONG offh;
			LONG offl;     offset to current position/
				       new size on return
			LONG mode;     relative to what (see Seek)
		    */
		    error=set_file_size(rambase,(struct filehandle *)iofs->IOFS.io_Unit,
					&iofs->io_Args[0],&iofs->io_Args[1],iofs->io_Args[2]);
		    break;

		default:
		    error=ERROR_NOT_IMPLEMENTED;
		    break;
/*
  FSA_FILE_MODE
    Change or read the mode of a single filehandle
    Unit *current; filehandle to change
    ULONG newmode; new mode/old mode on return
    ULONG mask;    bits affected

  FSA_MOUNT_MODE
    Change or read the mode of the filesystem
    Unit *current; filesystem to change
    ULONG newmode; new mode/old mode on return
    ULONG mask;    bits affected
    STRPTR passwd; password for MMF_LOCKED

  FSA_MAKE_HARDLINK
    Create a hard link on a file, directory or soft link
    Unit *current; current directory
    STRPTR name;   softlink name
    Unit *target;  target handle

  FSA_MAKE_SOFTLINK
    Create a soft link to another object
    Unit *current; current directory
    STRPTR name;   softlink name
    STRPTR target; target name

  FSA_RENAME
  FSA_READ_LINK
  FSA_DISK_INFO
  FSA_SERIALIZE_DISK
  FSA_WAIT_CHAR
  FSA_INFO
  FSA_TIMER
  FSA_DISK_TYPE
  FSA_DISK_CHANGE
  FSA_SAME_LOCK
  FSA_CHANGE_SIGNAL
  FSA_FORMAT
  FSA_IS_FILESYSTEM
  FSA_EXAMINE_ALL
  FSA_EXAMINE_FH
  FSA_ADD_NOTIFY
  FSA_REMOVE_NOTIFY
  FSA_EXAMINE_ALL_END

*/
	    }
	    iofs->io_DosError=error;
	    ReplyMsg(&iofs->IOFS.io_Message);
	}
#if 0
	if(rambase->iofs!=NULL)
	{
	    iofs=rambase->iofs;
	    if(iofs->IOFS.io_Message.mn_Node.ln_Type==NT_MESSAGE)
	    {
		abort_notify(rambase,iofs);
		iofs->io_DosError=ERROR_BREAK;
		rambase->iofs=NULL;
		ReplyMsg(&iofs->IOFS.io_Message);
	    }else
	    {
		rambase->iofs=NULL;
		Signal(1,0);
	    }
	}
#endif
	Wait(1<<rambase->port->mp_SigBit);
    }
}

const char end=0;
