/*
 * Filename:     fnramfs.c
 * Version:      0.58
 * Author:       Frank Naumann
 * Started:      1998-08-19
 * Last Updated: 1999-05-05
 * Target O/S:   TOS/MiNT
 * Description:  dynamic ramdisk-xfs for MiNT
 * 
 * Note:         Please send suggestions, patches or bug reports to me
 *               or the MiNT mailing list (mint@fishpool.com).
 * 
 * Copying:      Copyright 1998, 1999 Frank Naumann
 *               (fnaumann@cs.uni-magdeburg.de)
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2, or (at your option)
 * any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 * 
 * 
 * changes since last version:
 * 
 * 1999-05-05:	(v0.58)
 * 
 * - fix: bug in __dir_empty; always true returned
 * 
 * 1999-03-03:	(v0.57)
 * 
 * - fix: bug in rename (casesensitive rename)
 * 
 * 1999-02-04:	(v0.56)
 * 
 * - new: xattr->size information for directories (kmalloc'ed memory)
 * - fix: getxattr: nblocks was not calculated
 * - new: new dir entries are inserted on the tail of the linked list
 * - new: "." and ".." emulation
 * 
 * 1999-01-06:	(v0.55)
 * 
 * - fix: real unix like unlink work now like real unix like unlink
 *        (definitly)
 * - fix: added _cdecl keyword to interface functions
 * 
 * 1998-12-22:	(v0.54)
 * 
 * - fix: real unix like unlink
 *        open files can now be unlinked (inodes are deleted on close)
 *        dir entry is always deleted
 * 
 * 1998-10-22:	(v0.53)
 * 
 * - fix: some changes releated to device number
 * - add: Dpathconf (DP_VOLNAMEMAX)
 * - fix: init() fs_descr is now static
 * 
 * 1998-09-24:	(v0.52b)
 * 
 * - fix: init() - dev number is initialized correct now
 * - new: Dcntl(FS_INFO)
 * - new: Dcntl(FS_USAGE)
 * 
 * 1998-09-08:	(v0.51b)
 * 
 * - new: advisotory filelocking
 * 
 * 1998-08-26:	(v0.5b)
 * 
 * - change: xfs is now casepreserving (previous: casesensitiv)
 *           -> this reduce trouble with tools that run in MiNT-Domain
 *              but can't handle with long and casesensitive names
 * 
 * 1998-08-25:	(v0.4b)
 * 
 * - new: better & faster smallblock handling,
 *        smallblock preallocation
 * - add: __FTRUNCATE
 * 
 * 1998-08-23:	(v0.3b)
 * 
 * - inital revision
 * 
 * 
 * known bugs:
 * 
 * - nothing
 * 
 * todo:
 * 
 * - what about a minimum/maximum limit?
 * - fake x-bit?
 * - no name conversion
 * 
 */

/* FreeMiNT header */
# include <atarierr.h>
# include <default.h>
# include <file.h>
# include <fstring.h>
# include <assert.h>

/* libc header */
# include <ctype.h>
# include <string.h>

# define DOM_TOS 0
# define DOM_MINT 1
# define NUM_DRIVES 32

# ifndef IO_Q
# define IO_Q 3
# endif

/*
 * debugging stuff
 */

# if 0
# define FS_DEBUG 1
# endif

/*
 * version
 */

# define VER_MAJOR	0
# define VER_MINOR	58
# define VER_STATUS	

/*
 * startup messages
 */

# define MSG_VERSION	str (VER_MAJOR) "." str (VER_MINOR) str (VER_STATUS) 
# define MSG_BUILDDATE	__DATE__

# define MSG_BOOT	\
	"\033p dynamic RAM filesystem driver version " MSG_VERSION " \033q\r\n"

# define MSG_GREET	\
	"½ " MSG_BUILDDATE " by Frank Naumann.\r\n"

# define MSG_MINT	\
	"\033pMiNT to old!\033q\r\n"

# define MSG_FAILURE	\
	"\7Sorry, fnramfs NOT installed (Dcntl(...) failed)!\r\n\r\n"

/*
 * default settings
 */

# define INSTALL	"u:\\ram"

/*
 * existing FreeMiNT internal memory management
 * works with 8k aligned blocks!
 */
# if 0
# define BLOCK_SIZE	4096L
# define BLOCK_SHIFT	12
# else
# define BLOCK_SIZE	8192L
# define BLOCK_SHIFT	13
# endif
# define BLOCK_MASK	(BLOCK_SIZE - 1)
# define BLOCK_MIN	2	/* preallocate 1/4 smallblock */

# define INITIAL_BLOCKS	16

/*
 * fake for pathconf (no internal limit in real),
 * but some tools doesn't like "Dpathconf(DP_NAMEMAX) == UNLIMITED"
 */
# define MAX_NAME	256	/* include '\0' */

/****************************************************************************/
/* BEGIN kernel interface */

# define KERNEL	kernel
struct kerinfo *KERNEL;

# define c_conws	(*KERNEL->dos_tab[0x09])
# define f_select	(*KERNEL->dos_tab[0x11d])
# define d_cntl		(*KERNEL->dos_tab[0x130])
# define d_lock		(*KERNEL->dos_tab[0x135])

# define timestamp	(*KERNEL->dos_tab[0x2c])  ()
# define datestamp	(*KERNEL->dos_tab[0x2a])  ()
# define domain		(*KERNEL->dos_tab[0x119]) (-1)
# define FreeMemory	(*KERNEL->dos_tab[0x48])  (-1L)
# define Getpid		(*KERNEL->dos_tab[0x10b])
# define Getuid		(*KERNEL->dos_tab[0x10f])
# define Getgid		(*KERNEL->dos_tab[0x114])
# define Geteuid	(*KERNEL->dos_tab[0x138])
# define Getegid	(*KERNEL->dos_tab[0x139])

# define MINT_MAJOR	(KERNEL->maj_version)
# define MINT_MINOR	(KERNEL->min_version)
# define DEFAULT_DMODE	(0777)
# define DEFAULT_MODE	(KERNEL->default_perm)
# define KERNEL_DEBUG	(*KERNEL->debug)
# define ALERT		(*KERNEL->alert)
# define KERNEL_TRACE	(*KERNEL->trace)
# define FATAL		(*KERNEL->fatal)
# define kmalloc	(*KERNEL->kmalloc)
# define kfree		(*KERNEL->kfree)
# define stricmp	(*KERNEL->stricmp)
# define strlwr		(*KERNEL->strlwr)
# define unixtime	(*KERNEL->unixtim)
# define dostime	(*KERNEL->dostim)
# define denyshare	(*KERNEL->denyshare)
# define denylock	(*KERNEL->denylock)
# define changedrive	(*KERNEL->drvchng)
# define sleep		(*KERNEL->sleep)
# define wake		(*KERNEL->wake)
# define wakeselect	(*KERNEL->wakeselect)
# define addroottimeout	(*KERNEL->addroottimeout)

# define bio		(*KERNEL->bio)

/* END kernel interface */
/****************************************************************************/

/****************************************************************************/
/* BEGIN tools */

# define IS_DIR(c)	(((c)->xattr.mode & S_IFMT) == S_IFDIR)
# define IS_REG(c)	(((c)->xattr.mode & S_IFMT) == S_IFREG)
# define IS_SLNK(c)	(((c)->xattr.mode & S_IFMT) == S_IFLNK)

# define IS_SETUID(c)	((c)->xattr.mode & S_ISUID)
# define IS_SETGID(c)	((c)->xattr.mode & S_ISGID)
# define IS_STICKY(c)	((c)->xattr.mode & S_ISVTX)

static ulong memory = 0;

FASTFN void *
ram_kmalloc (register long size)
{
	register void *tmp = kmalloc (size);
	if (tmp) memory += size;
	return tmp;
}

FASTFN void
ram_kfree (void *dst, register long size)
{
	memory -= size;
	kfree (dst);
}

# undef kmalloc
# undef kfree

# define kmalloc	ram_kmalloc
# define kfree		ram_kfree

/* END tools */
/****************************************************************************/

/****************************************************************************/
/* BEGIN definition part */

/*
 * filesystem
 */

static long	_cdecl ram_root		(int drv, fcookie *fc);

static long	_cdecl ram_lookup	(fcookie *dir, const char *name, fcookie *fc);
static DEVDRV *	_cdecl ram_getdev	(fcookie *fc, long *devsp);
static long	_cdecl ram_getxattr	(fcookie *fc, XATTR *xattr);

static long	_cdecl ram_chattr	(fcookie *fc, int attrib);
static long	_cdecl ram_chown	(fcookie *fc, int uid, int gid);
static long	_cdecl ram_chmode	(fcookie *fc, unsigned mode);

static long	_cdecl ram_mkdir	(fcookie *dir, const char *name, unsigned mode);
static long	_cdecl ram_rmdir	(fcookie *dir, const char *name);
static long	_cdecl ram_creat	(fcookie *dir, const char *name, unsigned mode, int attrib, fcookie *fc);
static long	_cdecl ram_remove	(fcookie *dir, const char *name);
static long	_cdecl ram_getname	(fcookie *root, fcookie *dir, char *pathname, int size);
static long	_cdecl ram_rename	(fcookie *olddir, char *oldname, fcookie *newdir, const char *newname);

static long	_cdecl ram_opendir	(DIR *dirh, int flags);
static long	_cdecl ram_readdir	(DIR *dirh, char *nm, int nmlen, fcookie *);
static long	_cdecl ram_rewinddir	(DIR *dirh);
static long	_cdecl ram_closedir	(DIR *dirh);

static long	_cdecl ram_pathconf	(fcookie *dir, int which);
static long	_cdecl ram_dfree	(fcookie *dir, long *buf);
static long	_cdecl ram_writelabel	(fcookie *dir, const char *name);
static long	_cdecl ram_readlabel	(fcookie *dir, char *name, int namelen);

static long	_cdecl ram_symlink	(fcookie *dir, const char *name, const char *to);
static long	_cdecl ram_readlink	(fcookie *file, char *buf, int len);
static long	_cdecl ram_hardlink	(fcookie *fromdir, const char *fromname, fcookie *todir, const char *toname);
static long	_cdecl ram_fscntl	(fcookie *dir, const char *name, int cmd, long arg);
static long	_cdecl ram_dskchng	(int drv, int mode);

static long	_cdecl ram_release	(fcookie *fc);
static long	_cdecl ram_dupcookie	(fcookie *dst, fcookie *src);
static long	_cdecl ram_sync		(void);

static FILESYS ftab =
{
	NULL,
	/*
	 * FS_KNOPARSE		kernel shouldn't do parsing
	 * FS_CASESENSITIVE	file names are case sensitive (only for TOS-DOMAIN)
	 * FS_NOXBIT		if a file can be read, it can be executed
	 * FS_LONGPATH		filesystem understands "size" argument to "getname"
	 * FS_NO_C_CACHE	don't cache cookies for this filesystem
	 * FS_DO_SYNC		filesystem has a sync function
	 * FS_OWN_MEDIACHANGE	filesystem control self media change (dskchng)
	 */
	FS_CASESENSITIVE | FS_LONGPATH | FS_NO_C_CACHE,
	ram_root,
	ram_lookup, ram_creat, ram_getdev, ram_getxattr,
	ram_chattr, ram_chown, ram_chmode,
	ram_mkdir, ram_rmdir, ram_remove, ram_getname, ram_rename,
	ram_opendir, ram_readdir, ram_rewinddir, ram_closedir,
	ram_pathconf, ram_dfree, ram_writelabel, ram_readlabel,
	ram_symlink, ram_readlink, ram_hardlink, ram_fscntl, ram_dskchng,
	ram_release, ram_dupcookie,
	ram_sync
};

/*
 * device driver
 */

static long	_cdecl ram_open		(FILEPTR *f);
static long	_cdecl ram_write	(FILEPTR *f, const char *buf, long bytes);
static long	_cdecl ram_read		(FILEPTR *f, char *buf, long bytes);
static long	_cdecl ram_lseek	(FILEPTR *f, long where, int whence);
static long	_cdecl ram_ioctl	(FILEPTR *f, int mode, void *buf);
static long	_cdecl ram_datime	(FILEPTR *f, ushort *time, int rwflag);
static long	_cdecl ram_close	(FILEPTR *f, int pid);
static long	_cdecl null_select	(FILEPTR *f, long int p, int mode);
static void	_cdecl null_unselect	(FILEPTR *f, long int p, int mode);

static DEVDRV devtab =
{
	ram_open,
	ram_write, ram_read, ram_lseek, ram_ioctl, ram_datime,
	ram_close,
	null_select, null_unselect
};


typedef struct blocks BLOCKS;
typedef struct symlnk SYMLNK;
typedef struct dirlst DIRLST;
typedef struct diroot DIROOT;
typedef struct cookie COOKIE;


struct blocks
{
	long	size;
	char 	**table;
	long	small_size;
	char 	*small;
};

struct symlnk
{
	char	*name;
	ushort	len;
};

struct dirlst
{
	DIRLST	*prev;
	DIRLST	*next;
	
	ushort	lock;		/* a reference left */
	ushort	len;		/* sizeof name */
	char	*name;		/* name */
	COOKIE	*cookie;	/* cookie for this file */	
};

struct diroot
{
	DIRLST	*start;
	DIRLST	*end;
};


struct cookie
{
	COOKIE	*parent;	/* parent directory */
	
	ulong 	links;		/* in use counter */
	XATTR	xattr;		/* file attribute */
	
	FILEPTR *open;		/* linked list of open file ptrs */
	LOCK	*locks;		/* locks for this file */
	
	union
	{
		BLOCKS	data;
		SYMLNK	symlnk;
		DIROOT	dir;
	} data;
};


/*
 * debugging stuff
 */

# ifdef FS_DEBUG
#  define DEBUG(x)	KERNEL_DEBUG x
#  define TRACE(x)	KERNEL_TRACE x
#  define ASSERT(x)	assert x
# else
#  define DEBUG(x)
#  define TRACE(x)
#  define ASSERT(x)
# endif


/* directory manipulation */

FASTFN long	__dir_empty	(COOKIE *root);
FASTFN DIRLST *	__dir_next	(COOKIE *root, DIRLST *actual);
static DIRLST *	__dir_search	(COOKIE *root, const char *name);
FASTFN DIRLST *	__dir_searchI	(COOKIE *root, register const COOKIE *searched);
static long	__dir_remove	(COOKIE *root, DIRLST *f);
static long	__dir_insert	(COOKIE *root, COOKIE *cookie, const char *name);


static void	__free_data	(COOKIE *rc);

static long	__creat		(COOKIE *c, const char *name, COOKIE **new, unsigned mode, int attrib);
FASTFN long	__unlink_cookie	(COOKIE *c);
static long	__unlink	(COOKIE *d, const char *name);


FILESYS * _cdecl init		(struct kerinfo *k);


static long	__FUTIME	(COOKIE *rc, ushort *timeptr);
static long	__FTRUNCATE	(COOKIE *rc, long size);

/* END definition part */
/****************************************************************************/

/****************************************************************************/
/* BEGIN global data definition & access implementation */

static COOKIE root_inode;
static COOKIE *root = &root_inode;

static char *label = NULL;

static ushort cookie_bug = 0;

/* END global data & access implementation */
/****************************************************************************/

/****************************************************************************/
/* BEGIN DIR part */

FASTFN long
__dir_empty (COOKIE *root)
{
	DIRLST *list = root->data.dir.start;
	
	while (list)
	{
		if (!((list->name [0] == '.' && list->name [1] == '\0')
			|| (list->name [0] == '.' && list->name [1] == '.' && list->name [2] == '\0')))
		{
			return 0;
		}
		
		list = list->next;
	}
	
	return 1;
}

FASTFN DIRLST *
__dir_next (COOKIE *root, DIRLST *actual)
{
	if (actual)
		return actual->next;
	
	return root->data.dir.start;
}

static DIRLST *
__dir_search (COOKIE *root, const char *name)
{
	register DIRLST *tmp = root->data.dir.start;
	
	DEBUG (("fnramfs: __dir_search: search: %s!", name));
	
	while (tmp)
	{
		DEBUG (("fnramfs: __dir_search: compare with: %s!", tmp->name));
		
		if (_STRICMP (tmp->name, name) == 0)
		{
			return tmp;
		}
		
		tmp = tmp->next;
	}
	
	return NULL;
}

FASTFN DIRLST *
__dir_searchI (COOKIE *root, register const COOKIE *searched)
{
	register DIRLST *tmp = root->data.dir.start;
	
	while (tmp)
	{
		if (tmp->cookie == searched)
		{
			return tmp;
		}
		
		tmp = tmp->next;
	}
	
	return NULL;
}

static long
__dir_remove (COOKIE *root, DIRLST *element)
{
	if (element)
	{
		if (element->lock)
		{
			return EACCDN;
		}
		
		if (element->prev)
		{
			element->prev->next = element->next;
		}
		if (element->next)
		{
			element->next->prev = element->prev;
		}
		
		if (root->data.dir.start == element)
		{
			root->data.dir.start = element->next;
		}
		if (root->data.dir.end == element)
		{
			root->data.dir.end = element->prev;
		}
		
		root->xattr.size -= element->len;
		root->xattr.size -= sizeof (*element);
		
		kfree (element->name, element->len);
		kfree (element, sizeof (*element));
	}
	
	return E_OK;
}

static long
__dir_insert (COOKIE *root, COOKIE *cookie, const char *name)
{
	DIRLST *new;
	long r = ENSMEM;
	
	new = kmalloc (sizeof (*new));
	if (new)
	{
		new->lock = 0;
		new->len = _STRLEN (name) + 1;
		new->name = kmalloc (new->len);
		new->cookie = cookie;
		
		if (new->name)
		{
			(void) _STRCPY (new->name, name);
			
			root->xattr.size += sizeof (*new);
			root->xattr.size += new->len;
			
			if (root->data.dir.end)
				root->data.dir.end->next = new;
			else
				root->data.dir.start = new;
			
			new->prev = root->data.dir.end;
			new->next = NULL;
			
			root->data.dir.end = new;
			
			r = E_OK;
		}
		else
		{
			kfree (new, sizeof (*new));
		}
	}
	
	return r;
}

/* END DIR part */
/****************************************************************************/

/****************************************************************************/
/* BEGIN misc part */

static void
__free_data (COOKIE *c)
{
	char **table = c->data.data.table;
	
	if (table)
	{
		register long size = c->data.data.size;
		register long i = 0;
		
		while (table [i] && i < size)
		{
			kfree (table [i], BLOCK_SIZE);
			i++;
		}
		
		kfree (table, size * sizeof (*table));
		c->data.data.table = NULL;
	}
	
	if (c->data.data.small)
	{
		kfree (c->data.data.small, c->data.data.small_size);
		c->data.data.small = NULL;
	}
}

FASTFN long
__validate_name (register const char *name)
{
	ushort len = 0;
	
	if (!name
		|| (name [0] == '\0')
		|| (name [0] == '.' && name [1] == '\0')
		|| (name [0] == '.' && name [1] == '.' && name [2] == '\0'))
	{
		return EACCDN;
	}
	
	while (*name)
	{
		register char c = *name++;
		
		len++;
		if (len >= MAX_NAME)
			/* name exceed maximum limit */
			return ENAMETOOLONG;
		
		if (c < 32 || c > 126)
			/* Non-ASCII character met */
			return EACCDN;
		
		if (c == '/' || c == '\\' || c == ':')
			/* Directory separator */
			return EACCDN;
	}
	
	return E_OK;
}

static long
__creat (COOKIE *d, const char *name, COOKIE **new, unsigned mode, int attrib)
{
	DIRLST *f;
	long r;
	
	DEBUG (("fnramfs: __creat: enter"));
	
	if (!IS_DIR (d))
	{
		DEBUG (("fnramfs: __creat: dir not a DIR!"));
		return EACCDN;
	}
	
	r = __validate_name (name);
	if (r)
	{
		DEBUG (("fnramfs: __creat: not a valid name!"));
		return r;
	}
	
	f = __dir_search (d, name);
	if (f)
	{
		DEBUG (("fnramfs: __creat: object already exist"));
		return EACCDN;
	}
	
	*new = kmalloc (sizeof (**new));
	if (*new)
	{
		XATTR *x = &((*new)->xattr);
		
		/* clear all */
		memset (*new, 0, sizeof (**new));
		
		x->mode		= mode;
		x->index	= (long) *new;
		x->dev		= d->xattr.dev;
		x->rdev		= d->xattr.rdev;
		x->nlink	= 1;
		x->uid		= IS_SETUID (d) ? d->xattr.uid : Getuid ();
		x->gid		= IS_SETGID (d) ? d->xattr.gid : Getgid ();
		x->size		= 0;
		x->blksize	= BLOCK_SIZE;
		x->nblocks	= 0;
		x->mtime	= timestamp;
		x->mdate	= datestamp;
		x->atime	= x->mtime;
		x->adate	= x->mdate;
		x->ctime	= x->mtime;
		x->cdate	= x->mdate;
		x->attr		= attrib;
		
		if (IS_DIR (*new))
		{
			(*new)->parent = d;
			
			r = __dir_insert (*new, *new, ".");
			if (r) 
			{
				ASSERT (((*new)->data.dir.start));
				ASSERT (((*new)->data.dir.start == (*new)->data.dir.end));
				(void) __dir_remove (*new, (*new)->data.dir.start);
			}
			else
				r = __dir_insert (*new, d, "..");
		}
		
		if (!r)
		{
			r = __dir_insert (d, *new, name);
		}
		
		if (r)
		{
			DEBUG (("fnramfs: __creat: __dir_insert fail!"));
			kfree (*new, sizeof (**new));
		}
	}
	else
	{
		DEBUG (("fnramfs: __creat: kmalloc fail!"));
		r = ENSMEM;
	}
	
	return r;
}

FASTFN long
__unlink_cookie (COOKIE *c)
{
	/* if no references left, free the memory */
	if (c->xattr.nlink > 0)
	{
		DEBUG (("fnramfs: __unlink_cookie: nlink > 0, inode not deleted"));
	}
	else
	{
		if (c->links > 0)
		{
			DEBUG (("fnramfs: __unlink_cookie: inode in use (%li), not deleted", c->links));
		}
		else
		{
			DEBUG (("fnramfs: __unlink_cookie: deleting unlinked inode"));
			
			if (IS_REG (c))
			{
				__free_data (c);
			}
			else if (IS_DIR (c))
			{
				while (c->data.dir.start)
					__dir_remove (c, c->data.dir.start);
			}
			else if (IS_SLNK (c))
			{
				kfree (c->data.symlnk.name, c->data.symlnk.len);
			}
			kfree (c, sizeof (*c));
		}
	}
	
	return E_OK;
}

static long
__unlink (COOKIE *d, const char *name)
{
	register DIRLST *f;
	register COOKIE *t;
	long r;
	
	if (!IS_DIR (d))
	{
		DEBUG (("fnramfs: __unlink: dir not a DIR!"));
		return EACCDN;
	}
	
	f = __dir_search (d, name);
	if (!f)
	{
		DEBUG (("fnramfs: __unlink: object not found!"));
		return EACCDN;
	}
	
	t = f->cookie;
	
	if (IS_DIR (t))
	{
		if (!__dir_empty (t))
		{
			DEBUG (("fnramfs: __unlink: directory not clear!"));
			return EACCDN;
		}
	}
	
	/* remove from dir */
	r = __dir_remove (d, f);
	if (r == E_OK)
	{
		/* decrement link counter */
		t->xattr.nlink--;
		
		/* unlink cookie if possible */
		r = __unlink_cookie (t);
	}
	
	return r;
}

/* END misc part */
/****************************************************************************/

/****************************************************************************/
/* BEGIN init & configuration part */

FILESYS * _cdecl
init (struct kerinfo *k)
{
	static struct fs_descr fs_descriptor = { &ftab };
	
	KERNEL = k;
	
	c_conws (MSG_BOOT);
	c_conws (MSG_GREET);
	c_conws ("\r\n");
	
	DEBUG (("fnramfs.c: init"));
	
	/* version check */
	if (MINT_MAJOR < 1 || (MINT_MAJOR == 1 && MINT_MINOR < 12))
	{
		c_conws (MSG_MINT);
		c_conws (MSG_FAILURE);
		
		return NULL;
	}
	
	/* cookie bug check */
	if (MINT_MAJOR == 1 && MINT_MINOR < 15)
	{
		/* older MiNT versions have a bug in
		 * dupcookie/releasecookie mechanism
		 */
		cookie_bug = 1;
	}
	
	/* install ramdisk */
	if (d_cntl (FS_INSTALL, INSTALL, &fs_descriptor) >= 0
		&& d_cntl (FS_MOUNT, INSTALL, &fs_descriptor) >= 0)
	{
		char initial [] = "dynamic ram-xfs ½ by [fn]";
		XATTR *x;
		
		/*
		 * init internal data:
		 */
		
		/* initial label */
		label = kmalloc (strlen (initial) + 1);
		if (label)
		{
			strcpy (label, initial);
		}
		
		/* clear root cookie */
		memset (root, 0, sizeof (*root));
		
		/* root cookie always in use */
		root->links	= 1;
		
		/* setup root xattr data */
		x		= &(root->xattr);
		x->mode		= S_IFDIR | DEFAULT_DMODE;
		x->index	= (long) root;
		x->dev		= fs_descriptor.dev_no;
		x->rdev		= fs_descriptor.dev_no;
		x->nlink	= 1;
		x->uid		= 0;
		x->gid		= 0;
		x->size		= 0;
		x->blksize	= BLOCK_SIZE;
		x->nblocks	= 0;
		x->mtime	= timestamp;
		x->mdate	= datestamp;
		x->atime	= x->mtime;
		x->adate	= x->mdate;
		x->ctime	= x->mtime;
		x->cdate	= x->mdate;
		x->attr		= FA_DIR;
		
		DEBUG (("fnramfs.c: ok (dev_no = %i, root->xattr.dev = %i)",
			fs_descriptor.dev_no, root->xattr.dev));
		
		/* installed through
		 * Dcntl method
		 */
		return (FILESYS *) 1L;
	}
	
	c_conws (MSG_FAILURE);
	
	return NULL;
}

/* END init & configuration part */
/****************************************************************************/

/****************************************************************************/
/* BEGIN filesystem */

static long _cdecl
ram_root (int drv, fcookie *fc)
{
	if (drv == root->xattr.dev)
	{
		DEBUG (("fnramfs: ram_root E_OK (%i)", drv));
		
		fc->fs = &ftab;
		fc->dev = root->xattr.dev;
		fc->aux = 0;
		fc->index = (long) root;
		
		root->links++;
		
		return E_OK;
	}
	
	fc->fs = NULL;
	return EDRIVE;
}

static long _cdecl
ram_lookup (fcookie *dir, const char *name, fcookie *fc)
{
	COOKIE *c = (COOKIE *) dir->index;
	
	/* sanity checks */
	if (!c || !IS_DIR (c))
	{
		DEBUG (("fnramfs: ram_lookup: bad directory"));
		return EPTHNF;
	}
	
	/* 1 - itself */
	if (!name || (name[0] == '.' && name[1] == '\0'))
	{	
		c->links++;
		*fc = *dir;
		
		DEBUG (("fnramfs: ram_lookup: leave ok, (name = \".\")"));
		return E_OK;
	}
	
	/* 2 - parent dir */
	if (name[0] == '.' && name[1] == '.' && name[2] == '\0')
	{
		if (c->parent)
		{
			c = c->parent;
			
			fc->fs = &ftab;
			fc->dev = c->xattr.dev;
			fc->aux = 0;
			fc->index = (long) c;
			
			c->links++;
			
			return E_OK;
		}
		
		/* no parent, ROOT */
		
		*fc = *dir;
		DEBUG (("fnramfs: ram_lookup: leave ok, EMOUNT, (name = \"..\")"));
		return EMOUNT;
	}
	
	/* 3 - normal name */
	{
		DIRLST *f = __dir_search (c, name);
		
		if (f)
		{
			fc->fs = &ftab;
			fc->dev = f->cookie->xattr.dev;
			fc->aux = 0;
			fc->index = (long) f->cookie;
			
			f->cookie->links++;
			
			return E_OK;
		}
	}
	
	DEBUG (("fnramfs: ram_lookup fail (name = %s, return EFILNF)", name));
	return EFILNF;
}

static DEVDRV * _cdecl
ram_getdev (fcookie *fc, long *devsp)
{
	if (fc->fs == &ftab)
		return &devtab;
	
	*devsp = EINVFN;
	return NULL;
}

static long _cdecl
ram_getxattr (fcookie *fc, XATTR *xattr)
{
	COOKIE *c = (COOKIE *) fc->index;
	
	memcpy (xattr, &(c->xattr), sizeof (*xattr));
	xattr->nblocks = (xattr->size + BLOCK_SIZE) >> BLOCK_SHIFT;
	
	return E_OK;
}

static long _cdecl
ram_chattr (fcookie *fc, int attrib)
{
	COOKIE *c = (COOKIE *) fc->index;
	
# define FA_TOSVALID	(FA_RDONLY | FA_HIDDEN | FA_SYSTEM | FA_DIR | FA_CHANGED)
	c->xattr.attr = attrib & FA_TOSVALID;
	
	if (attrib & FA_RDONLY)
	{
		c->xattr.mode &= ~ (S_IWUSR | S_IWGRP | S_IWOTH);
	}
	else if (!(c->xattr.mode & (S_IWUSR | S_IWGRP | S_IWOTH)))
	{
		c->xattr.mode |= (S_IWUSR | S_IWGRP | S_IWOTH);
	}
	
	return E_OK;
}

static long _cdecl
ram_chown (fcookie *fc, int uid, int gid)
{
	COOKIE *c = (COOKIE *) fc->index;
	
	c->xattr.uid = uid;
	c->xattr.gid = gid;
	
	return E_OK;
}

static long _cdecl
ram_chmode (fcookie *fc, unsigned mode)
{
	COOKIE *c = (COOKIE *) fc->index;
	
	c->xattr.mode = (c->xattr.mode & S_IFMT) | (mode & ~S_IFMT);
	
	return E_OK;
}

static long _cdecl
ram_mkdir (fcookie *dir, const char *name, unsigned mode)
{
	COOKIE *c = (COOKIE *) dir->index;
	COOKIE *new;
	
	return __creat (c, name, &new, mode | S_IFDIR, FA_DIR);
}

static long _cdecl
ram_rmdir (fcookie *dir, const char *name)
{
	COOKIE *c = (COOKIE *) dir->index;
	
	/* check for an empty dir is in __unlink */
	return __unlink (c, name);
}

static long _cdecl
ram_creat (fcookie *dir, const char *name, unsigned mode, int attrib, fcookie *fc)
{
	COOKIE *c = (COOKIE *) dir->index;
	COOKIE *new;
	long r;
	
	r = __creat (c, name, &new, mode | S_IFREG, attrib);
	if (r == E_OK)
	{
		fc->fs = &ftab;
		fc->dev = new->xattr.dev;
		fc->aux = 0;
		fc->index = (long) new;
		
		new->links++;
	}
	
	return r;
}

static long _cdecl
ram_remove (fcookie *dir, const char *name)
{
	COOKIE *c = (COOKIE *) dir->index;
	long r;
	
	if (cookie_bug)
		c->links--;
	
	r = __unlink (c, name);
	
	if (cookie_bug && r)
		c->links++;
	
	return r;
}

static long _cdecl
ram_getname (fcookie *root, fcookie *dir, char *pathname, int size)
{
	COOKIE *r = (COOKIE *) root->index;
	COOKIE *d = (COOKIE *) dir->index;
	
	DEBUG (("fnramfs: ram_getname enter"));
	
	pathname [0] = '\0';
	
	/* sanity checks */
	if (!r || !IS_DIR (r))
	{
		DEBUG (("fnramfs: ram_getname: root not a DIR!"));
		return EACCDN;
	}
	if (!d || !IS_DIR (d))
	{
		DEBUG (("fnramfs: ram_getname: dir not a DIR!"));
		return EACCDN;
	}
	
	while (d)
	{
		DIRLST *f;
		char *name;
		
		if (r == d)
		{
			strrev (pathname);
			
			DEBUG (("fnramfs: ram_getname: leave E_OK: %s", pathname));
			return E_OK;
		}
		
		f = __dir_searchI (d->parent, d);
		if (!f)
		{
			DEBUG (("fnramfs: ram_getname: __dir_searchI failed!"));
			return EACCDN;
		}
		
		name = kmalloc (f->len + 1);
		if (!name)
		{
			ALERT ("fnramfs: kmalloc fail in ram_getname!");
			return ENSMEM;
		}
		
		name [0] = '\\';
		strcpy (name + 1, f->name);
		strrev (name);
		
		size -= f->len - 1;
		if (size <= 0)
		{
			DEBUG (("fnramfs: ram_getname: name to long"));
			return ENAMETOOLONG;
		}
		
		strcat (pathname, name);
		kfree (name, f->len + 1);
		
		d = d->parent;
	}
	
	pathname [0] = '\0';
	
	DEBUG (("fnramfs: ram_getname: path not found?"));
	return EPTHNF;
}

static long _cdecl
ram_rename (fcookie *olddir, char *oldname, fcookie *newdir, const char *newname)
{
	COOKIE *oldc = (COOKIE *) olddir->index;
	COOKIE *newc = (COOKIE *) newdir->index;
	DIRLST *old;
	DIRLST *new;
	long r;
	
	DEBUG (("fnramfs: ram_rename: enter (old = %s, new = %s)", oldname, newname));
	
	/* on same device? */
	if (olddir->dev != newdir->dev)
	{
		DEBUG (("fnramfs: ram_rename: leave failure (cross device rename)!"));
		return EACCDN;
	}
	
	/* check if file exist */
	new = __dir_search (newc, newname);
	if (new)
	{
		/* check for rename same file (casepreserving) */
		if (!((oldc == newc) && (_STRICMP (oldname, newname) == 0)))
		{
			DEBUG (("fnramfs: ram_rename: newname already exist!"));
			return EACCDN;
		}
	}
	
	/* search old file */
	old = __dir_search (oldc, oldname);
	if (!old)
	{
		DEBUG (("fnramfs: ram_rename: oldfile not found!"));
		return EFILNF;
	}
	
	if (old->lock)
	{
		DEBUG (("fnramfs: ram_rename: leave failure (file in use)"));
		return EACCDN;
	}
	
	r = __dir_remove (oldc, old);
	if (r == E_OK)
	{
		r = __dir_insert (newc, old->cookie, newname);
		if (r)
		{
			(void) __dir_insert (oldc, old->cookie, oldname);
		}
	}
	
	return r;
}

static long _cdecl
ram_opendir (DIR *dirh, int flags)
{
	COOKIE *c = (COOKIE *) dirh->fc.index;
	DIRLST *l;
	
	if (!IS_DIR (c))
	{
		DEBUG (("fnramfs: ram_opendir: dir not a DIR!"));
		return EACCDN;
	}
	
	l = __dir_next (c, NULL);
	
	*(DIRLST **) (&dirh->fsstuff) = l;
	if (l) l->lock = 1;
	
	c->links++;
	
	dirh->index = 0;
	return E_OK;
}

static long _cdecl
ram_readdir (DIR *dirh, char *nm, int nmlen, fcookie *fc)
{
	DIRLST *l;
	long r = ENMFIL;
	
	l = *(DIRLST **) (&dirh->fsstuff);
	if (l)
	{
		DEBUG (("fnramfs: ram_readdir: %s", l->name));
		
		l->lock = 0;
		
		if ((dirh->flags & TOS_SEARCH) == 0)
		{
			nmlen -= sizeof (long);
			if (nmlen <= 0) return ERANGE;
			*(long *) nm = (long) l->cookie;
			nm += sizeof (long);
		}
		else
		{
			DEBUG (("fnramfs: ram_readdir: TOS_SEARCH!"));
		}
		
		if (l->len <= nmlen)
		{
			(void) _STRCPY (nm, l->name);
			
			fc->fs = &ftab;
			fc->dev = l->cookie->xattr.dev;
			fc->aux = 0;
			fc->index = (long) l->cookie;
			
			l->cookie->links++;
			
			DEBUG (("fnramfs: ram_readdir: leave ok: %s", nm));
			
			r = E_OK;
		}
		else
		{
			r = ENAMETOOLONG;
		}
		
		{
			COOKIE *c = (COOKIE *) dirh->fc.index;
			l = __dir_next (c, l);
		}
		
		*(DIRLST **) (&dirh->fsstuff) = l;
		if (l) l->lock = 1;
	}
	
	return r;
}

static long _cdecl
ram_rewinddir (DIR *dirh)
{
	COOKIE *c = (COOKIE *) dirh->fc.index;
	DIRLST *l;
	
	l = *(DIRLST **) (&dirh->fsstuff);
	if (l) l->lock = 0;
	
	l = __dir_next (c, NULL);
	
	*(DIRLST **) (&dirh->fsstuff) = l;
	if (l) l->lock = 1;
	
	dirh->index = 0;
	return E_OK;
}

static long _cdecl
ram_closedir (DIR *dirh)
{
	COOKIE *c = (COOKIE *) dirh->fc.index;
	DIRLST *l;
	
	l = *(DIRLST **) (&dirh->fsstuff);
	if (l) l->lock = 0;
	
	c->links--;
	
	dirh->index = 0;
	return E_OK;
}

static long _cdecl
ram_pathconf (fcookie *dir, int which)
{
	switch (which)
	{
		case DP_INQUIRE:	return DP_VOLNAMEMAX;
		case DP_IOPEN:		return UNLIMITED;
		case DP_MAXLINKS:	return UNLIMITED;
		case DP_PATHMAX:	return UNLIMITED;
		case DP_NAMEMAX:	return MAX_NAME - 1;
		case DP_ATOMIC:		return BLOCK_SIZE;
		case DP_TRUNC:		return DP_NOTRUNC;
		case DP_CASE:		return DP_CASEINSENS;
		case DP_MODEATTR:	return (DP_ATTRBITS
						| DP_MODEBITS
						| DP_FT_DIR
						| DP_FT_REG
						| DP_FT_LNK
					);
		case DP_XATTRFIELDS:	return (DP_INDEX
						| DP_DEV
						| DP_NLINK
						| DP_UID
						| DP_GID
						| DP_BLKSIZE
						| DP_SIZE
						| DP_NBLOCKS
						| DP_ATIME
						| DP_CTIME
						| DP_MTIME
					);
		case DP_VOLNAMEMAX:	return MAX_NAME - 1;
	}
	
	return EINVFN;
}

static long _cdecl
ram_dfree (fcookie *dir, long *buf)
{
	long memfree;
	long memused;
	
	DEBUG (("fnramfs: ram_dfree called"));
	
	memfree = FreeMemory;
	memused = memory + BLOCK_SIZE - 1;
	
	*buf++	= memfree >> BLOCK_SHIFT;
	*buf++	= (memfree + memused) >> BLOCK_SHIFT;
	*buf++	= BLOCK_SIZE;
	*buf	= 1;
	
	return E_OK;
}

static long _cdecl
ram_writelabel (fcookie *dir, const char *name)
{
	char *new;
	
	new = kmalloc (strlen (name) + 1);
	if (new)
	{
		strcpy (new, name);
		
		kfree (label, strlen (label) + 1);
		label = new;
		
		return E_OK;
	}
	
	return EACCDN;
}

static long _cdecl
ram_readlabel (fcookie *dir, char *name, int namelen)
{
	ushort len = strlen (label);
	
	if (len < namelen)
	{
		strcpy (name, label);
		return E_OK;
	}
	
	return ENAMETOOLONG;
}

static long _cdecl
ram_symlink (fcookie *dir, const char *name, const char *to)
{
	COOKIE *c = (COOKIE *) dir->index;
	COOKIE *new;
	long r;
	
	r = __creat (c, name, &new, S_IFLNK, 0);
	if (r == E_OK)
	{
		ushort len = strlen (to) + 1;
		
		new->data.symlnk.len = len;
		new->data.symlnk.name = kmalloc (len);
		if (new->data.symlnk.name)
		{
			strcpy (new->data.symlnk.name, to);
		}
		else
		{
			ALERT ("fnramfs: ram_symlink: kmalloc fail!");
			
			(void) __unlink (c, name);
			r = ENSMEM;
		}
	}
	
	return r;
}

static long _cdecl
ram_readlink (fcookie *file, char *buf, int len)
{
	COOKIE *c = (COOKIE *) file->index;
	long r = EACCDN;
	
	if (IS_SLNK (c))
	{
		if (c->data.symlnk.len <= len)
		{
			strcpy (buf, c->data.symlnk.name);
			r = E_OK;
		}
		else
		{
			r = ENAMETOOLONG;
		}
	}
	
	return r;
}

static long _cdecl
ram_hardlink (fcookie *fromdir, const char *fromname, fcookie *todir, const char *toname)
{
	COOKIE *fromc = (COOKIE *) fromdir->index;
	COOKIE *toc = (COOKIE *) todir->index;
	DIRLST *from;
	DIRLST *to;
	long r;
	
	DEBUG (("fnramfs: ram_hardlink: enter (from = %s, to = %s)", fromname, toname));
	
	/* on same device? */
	if (fromdir->dev != todir->dev)
	{
		DEBUG (("fnramfs: ram_hardlink: leave failure (cross device hardlink)!"));
		return EACCDN;
	}
	
	/* check if name exist */
	to = __dir_search (toc, toname);
	if (to)
	{
		DEBUG (("fnramfs: ram_hardlink: toname already exist!"));
		return EACCDN;
	}
	
	/* search from file */
	from = __dir_search (fromc, fromname);
	if (!from)
	{
		DEBUG (("fnramfs: ram_hardlink: fromname not found!"));
		return EFILNF;
	}
	
	/* create new name */
	r = __dir_insert (toc, from->cookie, toname);
	if (r == E_OK)
	{
		from->cookie->xattr.nlink++;
	}
	
	return r;
}

static long _cdecl
ram_fscntl (fcookie *dir, const char *name, int cmd, long arg)
{
	COOKIE *d = (COOKIE *) dir->index;
	
	switch (cmd)
	{
		case MX_KER_XFSNAME:
		{
			strcpy ((char *) arg, "ram-xfs");
			return E_OK;
		}
		case FS_INFO:
		{
			struct fs_info *info = (struct fs_info *) arg;
			if (info)
			{
				strcpy (info->name, "ram-xfs");
				info->version = ((long) VER_MAJOR << 16) | (long) VER_MINOR;
				info->type = FS_RAMFS;
				strcpy (info->type_asc, "ramdisk");
			}
			return E_OK;
		}
		case FS_USAGE:
		{
			struct fs_usage *usage = (struct fs_usage *) arg;
			if (usage)
			{
				usage->blocksize = BLOCK_SIZE;
				usage->blocks = (FreeMemory + (memory + BLOCK_SIZE - 1)) >> BLOCK_SHIFT;
				usage->free_blocks = usage->blocks - ((memory + BLOCK_SIZE - 1) >> BLOCK_SHIFT);
				usage->inodes = FS_UNLIMITED;
				usage->free_inodes = FS_UNLIMITED;
			}
			return E_OK;
		}
		case FUTIME:
		{
			DIRLST *l;
			long r = EACCDN;
			
			if (!IS_DIR (d)) return EACCDN;
			
			l = __dir_search (d, name);
			if (l) r = __FUTIME (l->cookie, (ushort *) arg);
			
			return r;
		}
		case FTRUNCATE:
		{
			DIRLST *l;
			long r = EACCDN;
			
			if (!IS_DIR (d)) return EACCDN;
			
			l = __dir_search (d, name);
			if (l) r = __FTRUNCATE (l->cookie, arg);
			
			return r;
		}
	}
	
	return EINVFN;
}

static long _cdecl
ram_dskchng (int drv, int mode)
{
	return E_OK;
}

static long _cdecl
ram_release (fcookie *fc)
{
	COOKIE *c = (COOKIE *) fc->index;
	
	c->links--;
	if (!c->links)
	{
		__unlink_cookie (c);
	}
	
	return E_OK;
}

static long _cdecl
ram_dupcookie (fcookie *dst, fcookie *src)
{
	COOKIE *c = (COOKIE *) src->index;
	
	c->links++;
	*dst = *src;
	
	return E_OK;
}

static long _cdecl
ram_sync (void)
{
	return E_OK;
}

/* END filesystem */
/****************************************************************************/

/****************************************************************************/
/* BEGIN device driver */

/*
 * internal
 */

/* Check for access 'access' for given uid/gid/mode
 * return 0 if access allowed.
 */
FASTFN int
check_mode (COOKIE *rc, int euid, int egid, int access)
{
	if (!euid)
		return 0;
	
	if (euid == rc->xattr.uid && (rc->xattr.mode & access))
		return 0;
	
	if (egid == rc->xattr.gid && (rc->xattr.mode & (access >> 3)))
		return 0;
	
	if (rc->xattr.mode & (access >> 6))
		return 0;
	
	return 1;
}

static long
__FUTIME (COOKIE *rc, ushort *timeptr)
{	
	int uid = Geteuid ();
	int gid = Getegid ();
	
	/*
	 * The owner or super-user can always touch,
	 * others only if timeptr == 0 and write
	 * permission.
	 */
	if (uid
		&& uid != rc->xattr.uid
		&& (timeptr || check_mode (rc, uid, gid, S_IWUSR)))
	{
		return EACCDN;
	}
	
	rc->xattr.ctime = timestamp;
	rc->xattr.cdate = datestamp;
	
	if (timeptr)
	{
		rc->xattr.atime = timeptr [0];
		rc->xattr.adate = timeptr [1];
		rc->xattr.mtime = timeptr [2];
		rc->xattr.mdate = timeptr [3];
	}
	else
	{
		rc->xattr.atime = rc->xattr.mtime = rc->xattr.ctime;
		rc->xattr.adate = rc->xattr.mdate = rc->xattr.cdate;
	}
	
	return E_OK;
}

static long
__FTRUNCATE (COOKIE * c, long newsize)
{
	char **table = c->data.data.table;
	
	DEBUG (("fnramfs: __FTRUNCATE: enter (%li)", newsize));
	
	/* sanity checks */
	if (!IS_REG (c))
	{
		return EACCDN;
	}
	if (c->xattr.size <= newsize)
	{
		return E_OK;
	}
	
	/* simple check */
	if (newsize == 0)
	{
		__free_data (c);
		return E_OK;
	}
	
	if (c->data.data.small
		&& (c->xattr.size - newsize) > c->data.data.small_size)
	{
		kfree (c->data.data.small, c->data.data.small_size);
		c->data.data.small = NULL;
	}
	
	c->xattr.size = newsize;
	newsize >>= BLOCK_SHIFT;
	newsize++;
	
	if (table)
	{
		register long size = c->data.data.size;
		register long i = newsize;
		
		while (table [i] && i < size)
		{
			kfree (table [i], BLOCK_SIZE);
			i++;
		}
	}
	
	return E_OK;
}


/*
 * external
 */

static long _cdecl
ram_open (FILEPTR *f)
{
	COOKIE *c = (COOKIE *) f->fc.index;
	
	DEBUG (("fnramfs: ram_open: enter"));
	
	if (!IS_REG (c))
	{
		DEBUG (("fnramfs: ram_open: leave failure, not a valid file"));
		return EACCDN;
	}
	
	if (c->open)
	{
		if (denyshare (c->open, f))
		{
			DEBUG (("fnramfs: ram_open: file sharing denied"));
			return EACCDN;
		}
	}
	
	if ((f->flags & O_TRUNC) && c->xattr.size)
	{
		__free_data (c);
		c->xattr.size = 0;
	}
	
	if ((f->flags & O_RWMODE) == O_EXEC)
	{
		f->flags = (f->flags ^ O_EXEC) | O_RDONLY;
	}
	
	f->pos = 0;
	f->devinfo = 0;
	f->next = c->open;
	c->open = f;
	
	c->links++;
	
	DEBUG (("fnramfs: ram_open: leave ok"));
	return E_OK;
}

static long _cdecl
ram_write (FILEPTR *f, const char *buf, long bytes)
{
	COOKIE *c = (COOKIE *) f->fc.index;
	char **table = c->data.data.table;
	long size = c->data.data.size;
	
	long todo;
	long temp = c->xattr.size - f->pos;
	long offset;
	
	if (bytes <= 0)
	{
		DEBUG (("fnramfs: ram_write: ERROR (bytes = %li, return 0)", bytes));
		return 0;
	}
	
	todo = bytes;
	
	if (!table)
	{
		DEBUG (("fnramfs: ram_write: set up start table!"));
		
		size = bytes >> BLOCK_SHIFT;
		size += size >> 1;
		size = MAX (size, INITIAL_BLOCKS);
		
		table = kmalloc (size * sizeof (*table));
		if (table)
		{
			c->data.data.table = table;
			c->data.data.size = size;
			
			/* clear it! */
			memset (table, 0, size * sizeof (*table));
		}
		else
		{
			ALERT ("fnramfs: ram_write: kmalloc fail in (1)!");
			return 0;
		}
	}
	
	while (todo > 0)
	{
		temp = f->pos >> BLOCK_SHIFT;
		if (temp >= size)
		{
			DEBUG (("fnramfs: ram_write: resize block array!"));
			
			size <<= 1;
			table = kmalloc (size * sizeof (*table));
			if (table)
			{
				/* clear it! */
				memset (table, 0, size * sizeof (*table));
				
				/* save old data */
				memcpy (table, c->data.data.table, c->data.data.size * sizeof (*table));
				/* free old data */
				kfree (c->data.data.table, c->data.data.size * sizeof (*table));
				
				/* setup new table */
				c->data.data.table = table;
				c->data.data.size = size;
			}
			else
			{
				DEBUG (("fnramfs: ram_write: kmalloc fail in resize array (2)!"));
				goto leave;
			}
		}
		
		offset = f->pos & BLOCK_MASK;
		
		if ((todo >> BLOCK_SHIFT) && (offset == 0))
		{
			char *ptr = table [temp];
			
			DEBUG (("fnramfs: ram_write: aligned (temp = %li)", temp));
			
			if (!ptr)
			{
				ptr = kmalloc (BLOCK_SIZE);
				if (!ptr)
				{
					DEBUG (("fnramfs: ram_write: kmalloc fail (3)!"));
					goto leave;
				}
				
				table [temp] = ptr;
				
				if (c->data.data.small)
				{
					kfree (c->data.data.small, c->data.data.small_size);
					c->data.data.small = NULL;
				}
			}
			
			memcpy (ptr, buf, BLOCK_SIZE);
			
			buf += BLOCK_SIZE;
			todo -= BLOCK_SIZE;
			f->pos += BLOCK_SIZE;
		}
		else
		{
			char *ptr = table [temp];
			long data;
			
			DEBUG (("fnramfs: ram_write: BYTES (todo = %li, pos = %li)", todo, f->pos));
			
			data = BLOCK_SIZE - offset;
			data = MIN (todo, data);
			
			if (!ptr)
			{
				register long size;
				
				ptr = c->data.data.small;
				
				if (!ptr)
				{
					if (offset)
					{
						ALERT ("fnramfs: ram_write: internal error!");
						goto leave;
					}
					
					c->data.data.small_size = 0;
					size = data;
				}
				else
				{
					size = data + offset;
					size = MAX (size, c->data.data.small_size);
				}
				
				if (size > c->data.data.small_size)
				{
					size += size >> BLOCK_MIN;
					size = MIN (size, BLOCK_SIZE);
					
					ptr = kmalloc (size);
					if (!ptr)
					{
						DEBUG (("fnramfs: ram_write: kmalloc fail (6)!"));
						goto leave;
					}
					
					if (c->data.data.small_size)
					{
						memcpy (ptr, c->data.data.small, c->data.data.small_size);
						
						kfree (c->data.data.small, c->data.data.small_size);
						c->data.data.small = NULL;
					}
				}
				
				if (size != BLOCK_SIZE)
				{
					c->data.data.small_size = size;
					c->data.data.small = ptr;
				}
				else
					table [temp] = ptr;
			}
			
			memcpy ((ptr + offset), buf, data);
			
			buf += data;
			todo -= data;
			f->pos += data;
		}
		
		if (f->pos > c->xattr.size)
		{
			c->xattr.size = f->pos;
		}
	}
	
leave:
	c->xattr.attr |= FA_CHANGED;
	c->xattr.mtime = timestamp;
	c->xattr.mdate = datestamp;
	
	return (bytes - todo);
}

static long _cdecl
ram_read (FILEPTR *f, char *buf, long bytes)
{
	COOKIE *c = (COOKIE *) f->fc.index;
	char **table = c->data.data.table;
	
	register long chunk;
	register long done = 0;		/* processed characters */
	
	if (!table)
	{
		DEBUG (("fnramfs: ram_read: table doesn't exist!"));
		return 0;
	}
	
	bytes = MIN (c->xattr.size - f->pos, bytes);
	
	/* At or past EOF */
	if (bytes <= 0)
	{
		DEBUG (("fnramfs: ram_read: At or past EOF (bytes = %li)", bytes));
		return 0;
	}
	
	chunk = f->pos >> BLOCK_SHIFT;
	
	/* Are we block aligned? */
	if (f->pos & BLOCK_MASK)
	{
		char *ptr = table [chunk++];
		
		register long off = f->pos & BLOCK_MASK;
		register long data;
		
		data = BLOCK_SIZE - off;
		data = MIN (bytes, data);
		
		DEBUG (("fnramfs: ram_read: partial to align!"));
		
		if (!ptr)
			ptr = c->data.data.small;
		
		if (ptr)
			memcpy (buf, ptr + off, data);
		else
			memset (buf, 0, data);
		
		buf += data;
		bytes -= data;
		done += data;
		f->pos += data;
	}
	
	/* Any full blocks to read ? */
	if (bytes >> BLOCK_SHIFT)
	{
		register long end = bytes;
		
		while (end >> BLOCK_SHIFT)
		{
			char *ptr = table [chunk++];
			
			DEBUG (("fnramfs: ram_read: aligned transfer!"));
			
			if (!ptr)
				ptr = c->data.data.small;
			
			if (ptr)
				memcpy (buf, ptr, BLOCK_SIZE);
			else
				memset (buf, 0, BLOCK_SIZE);
			
			buf += BLOCK_SIZE;
			end -= BLOCK_SIZE;
			done += BLOCK_SIZE;
			f->pos += BLOCK_SIZE;
		}
		
		bytes &= BLOCK_MASK;
	}
	
	/* Anything left ? */
	if (bytes)
	{
		char *ptr = table [chunk];
		
		DEBUG (("fnramfs: ram_read: small transfer!"));
		
		if (!ptr)
			ptr = c->data.data.small;
		
		if (ptr)
			memcpy (buf, ptr, bytes);
		else
			memset (buf, 0, bytes);
		
		done += bytes;
		f->pos += bytes;
	}
	
	/* update time/datestamp */
	c->xattr.atime = timestamp;
	c->xattr.adate = datestamp;
	
	return done;	
}

static long _cdecl
ram_lseek (FILEPTR *f, long where, int whence)
{
	COOKIE *c = (COOKIE *) f->fc.index;
	
	DEBUG (("fnramfs: ram_lseek: enter (where = %li, whence = %i)", where, whence));
	
	switch (whence)
	{
		case SEEK_SET:					break;
		case SEEK_CUR:	where += f->pos;		break;
		case SEEK_END:	where = c->xattr.size - where;	break;
		default:	return EINVFN;
	}
	
	if (where > c->xattr.size || where < 0)
	{
		DEBUG (("fnramfs: ram_lseek: leave failure ERANGE (where = %li)", where));
		return ERANGE;
	}
	
	f->pos = where;
	
	DEBUG (("fnramfs: ram_lseek: leave ok (f->pos = %li)", f->pos));
	return where;
}

static long _cdecl
ram_ioctl (FILEPTR *f, int mode, void *buf)
{
	COOKIE *c = (COOKIE *) f->fc.index;
	
	DEBUG (("fnramfs: ram_ioctl: enter (mode = %i)", mode));
	
	switch (mode)
	{
		case FIONREAD:
		{
			*(long *) buf = c->xattr.size - f->pos;
			return E_OK;
		}
		case FIONWRITE:
		{
			*(long *) buf = 1;
			return E_OK;
		}
		case FUTIME:
		{
			return __FUTIME (c, buf);
		}
		case FTRUNCATE:
		{
			long r;
			
			if ((f->flags & O_RWMODE) == O_RDONLY)
				return EACCDN;
			
			r = __FTRUNCATE (c, *(long *) buf);
			if (r == E_OK)
			{
				long pos = f->pos;
				(void) ram_lseek (f, 0, SEEK_SET);
				(void) ram_lseek (f, pos, SEEK_SET);
			}
			
			return r;
		}
		case FIOEXCEPT:
		{
			*(long *) buf = 0;
			return E_OK;
		}
		case F_SETLK:
		case F_SETLKW:
		case F_GETLK:
		{
			struct flock *fl = (struct flock *) buf;
			
			LOCK t;
			LOCK *lck;
			
			int cpid;
				
			t.l = *fl;
			
			switch (t.l.l_whence)
			{
				case SEEK_SET:
				{
					break;
				}
				case SEEK_CUR:
				{
					long r = ram_lseek (f, 0L, SEEK_CUR);
					t.l.l_start += r;
					break;
				}
				case SEEK_END:
				{
					long r = ram_lseek (f, 0L, SEEK_CUR);
					t.l.l_start = ram_lseek (f, t.l.l_start, SEEK_END);
					(void) ram_lseek (f, r, SEEK_SET);
					break;
				}
				default:
				{
					DEBUG (("ram_ioctl: invalid value for l_whence\r\n"));
					return EINVFN;
				}
			}
			
			if (t.l.l_start < 0) t.l.l_start = 0;
			t.l.l_whence = 0;
			
			if (mode == F_GETLK)
			{
				lck = denylock (c->locks, &t);
				if (lck)
					*fl = lck->l;
				else
					fl->l_type = F_UNLCK;
				
				return E_OK;
			}
			
			cpid = Getpid ();
			
			if (t.l.l_type == F_UNLCK)
			{
				/* try to find the lock */
				LOCK **lckptr = &(c->locks);
				
				lck = *lckptr;
				while (lck)
				{
					if (lck->l.l_pid == cpid
						&& lck->l.l_start == t.l.l_start
						&& lck->l.l_len == t.l.l_len)
					{
						/* found it -- remove the lock */
						*lckptr = lck->next;
						
						DEBUG (("ram_ioctl: unlocked %lx: %ld + %ld", c, t.l.l_start, t.l.l_len));
						
						/* wake up anyone waiting on the lock */
						wake (IO_Q, (long) lck);
						kfree (lck, sizeof (*lck));
						
						return E_OK;
					}
					
					lckptr = &(lck->next);
					lck = lck->next;
				}
				
				return ENSLOCK;
			}
			
			DEBUG (("ram_ioctl: lock %lx: %ld + %ld", c, t.l.l_start, t.l.l_len));
			
			/* see if there's a conflicting lock */
			while ((lck = denylock (c->locks, &t)) != 0)
			{
				DEBUG (("ram_ioctl: lock conflicts with one held by %d", lck->l.l_pid));
				if (mode == F_SETLKW)
				{
					/* sleep a while */
					sleep (IO_Q, (long) lck);
				}
				else
					return ELOCKED;
			}
			
			/* if not, add this lock to the list */
			lck = kmalloc (sizeof (*lck));
			if (!lck)
			{
				ALERT ("fnramfs.c: kmalloc fail in: ram_ioctl (%lx)", c);
				return ENSMEM;
			}
			
			lck->l = t.l;
			lck->l.l_pid = cpid;
			
			lck->next = c->locks;
			c->locks = lck;
			
			/* mark the file as being locked */
			f->flags |= O_LOCK;
			return E_OK;
		}
	}
	
	return EINVFN;
}

static long _cdecl
ram_datime (FILEPTR *f, ushort *time, int rwflag)
{
	COOKIE *c = (COOKIE *) f->fc.index;
	
	if (rwflag)
	{
			c->xattr.mtime = time [0];
			c->xattr.mdate = time [1];
			c->xattr.atime = time [0];
			c->xattr.adate = time [1];
			c->xattr.ctime = timestamp;
			c->xattr.cdate = datestamp;
	}
	else
	{
			time [0] = c->xattr.mtime;
			time [1] = c->xattr.mdate;
	}
	
	return E_OK;
}

static long _cdecl
ram_close (FILEPTR *f, int pid)
{
	COOKIE *c = (COOKIE *) f->fc.index;
	
	DEBUG (("fnramfs: ram_close: enter (f->links = %i)", f->links));
	
	/* if a lock was made, remove any locks of the process */
	if (f->flags & O_LOCK)
	{
		LOCK *lock;
		LOCK **oldlock;
		
		DEBUG (("fnramfs: ram_close: remove lock (pid = %i)", pid));
		
		oldlock = &c->locks;
		lock = *oldlock;
		
		while (lock)
		{
			if (lock->l.l_pid == pid)
			{
				*oldlock = lock->next;
				/* (void) ram_lock ((int) f->devinfo, 1, lock->l.l_start, lock->l.l_len); */
				wake (IO_Q, (long) lock);
				kfree (lock, sizeof (*lock));
			}
			else
			{
				oldlock = &lock->next;
			}
			
			lock = *oldlock;
		}
	}
	
	if (f->links <= 0)
	{
		/* remove the FILEPTR from the linked list */
		register FILEPTR **temp = &c->open;
		register long flag = 1;
		
		while (*temp)
		{
			if (*temp == f)
			{
				*temp = f->next;
				f->next = NULL;
				flag = 0;
				break;
			}
			temp = &(*temp)->next;
		}
		
		if (flag)
		{
			ALERT ("fnramfs: ram_close: remove open FILEPTR failed");
		}
		
		c->links--;
	}
	
	DEBUG (("fnramfs: ram_close: leave ok"));
	return E_OK;
}

static long _cdecl
null_select (FILEPTR *f, long int p, int mode)
{
	if ((mode == O_RDONLY) || (mode == O_WRONLY))
		/* we're always ready to read/write */
		return 1;
	
	/* other things we don't care about */
	return E_OK;
}

static void _cdecl
null_unselect (FILEPTR *f, long int p, int mode)
{
}

/* END device driver */
/****************************************************************************/
