/*
 * Copyright 1993, 1994 by Ulrich Khn. All rights reserved.
 *
 * THIS PROGRAM COMES WITH ABSOLUTELY NO WARRANTY, NOT
 * EVEN THE IMPLIED WARRANTIES OF MERCHANTIBILITY OR
 * FITNESS FOR A PARTICULAR PURPOSE. USE AT YOUR OWN
 * RISK.
 */

/*
 * File : netfs.c
 *        networking filesystem driver
 */


#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <basepage.h>
#include "atarierr.h"
#include "kernel.h"
#include "nfs.h"
#include "xdr.h"
#include "netfs.h"
#include "proto.h"
#include "config.h"




#define LOWTRACE(x) (*kernel->trace) x
#if 0
#undef TRACE
#define TRACE(x) (*kernel->debug) x
#endif


static XATTR root_attr;
static char nfs_label[MAX_LABEL+1] = "Network";
static fcookie root_cookie = { &nfs_filesys, 0, 0, 0 };



FILESYS nfs_filesys = {
	(FILESYS *)0,
	FS_LONGPATH | FS_CASESENSITIVE,
	nfs_root,
	nfs_lookup, nfs_creat, nfs_getdev, nfs_getxattr,
	nfs_chattr, nfs_chown, nfs_chmode,
	nfs_mkdir, nfs_rmdir, nfs_remove, nfs_getname, nfs_rename,
	nfs_opendir, nfs_readdir, nfs_rewinddir, nfs_closedir,
	nfs_pathconf, nfs_dfree, nfs_writelabel, nfs_readlabel,
	nfs_symlink, nfs_readlink, nfs_hardlink, nfs_fscntl, nfs_dskchng,
	nfs_release, nfs_dupcookie
};




long
get_handle(NFS_INDEX *ni)
{
	fcookie fc1, fc2;
	NFS_INDEX *newi;
	long r;

	if (ni->flags & NO_HANDLE)    /* handle not initialised */
	{
		TRACE(("get_handle: file '%s' without handle, looking up",
		                                      (ni) ? ni->name : "root"));
		fc1.fs = &nfs_filesys;
		fc1.dev = nfs_dev;
		fc1.aux = 0;
		fc1.index = (long)ni->dir;
		r = nfs_lookup(&fc1, ni->name, &fc2);
		if (r != 0)
		{
			DEBUG(("get_handle: failed to get handle, -> EFILNF"));
			return EFILNF;
		}
		newi = (NFS_INDEX*)fc2.index;
		if (newi != ni)
		{
			ni->handle = newi->handle;
			ni->attr = newi->attr;
			ni->stamp = newi->stamp;
		}
		nfs_release(&fc2);
		ni->flags &= ~NO_HANDLE;
	}
	return 0;
}




long
nfs_root(_wORD drv, fcookie *fc)
{
	static first = 1;

	TRACE(("nfs_root"));
	if (bios_inst)
	{
		if (first)
		{
			/* rename our directory, but try it only the first time */
			if (--first == 0)
				(void)Frename((_wORD)0, "u:\\w", "u:\\nfs");
		}
	}
	if (drv != nfs_dev)
	{
		DEBUG(("nfs_root(%d) -> EDRIVE", (short)drv));
		return EDRIVE;
	}
	root_cookie.dev = nfs_dev;
	fc->fs = &nfs_filesys;
	fc->dev = nfs_dev;
	fc->aux = 0;
	fc->index = (long)ROOT_INDEX;
	TRACE(("nfs_root(%d) -> OK", drv));
	return 0;
}



long
nfs_lookup(fcookie *dir, char *name, fcookie *fc)
{
#ifdef TOSDOMAIN_LOWERCASE
	char lower_buf[PATH_MAX];
#endif
	char req_buf[LOOKUPBUFSIZE];
	NFS_INDEX *ni, *newi;
	long r;
	int dom;
	MESSAGE *mreq, *mrep, m;
	diropargs dirargs;
	diropres dirres;
	xdrs x;


	ni = (NFS_INDEX*)dir->index;
	dom = Pdomain(-1);    /* get process domain */
#ifdef TOSDOMAIN_LOWERCASE
	if (dom == 0)
	{
		/* We are in tos domain, so convert the file name to lower case */
		strcpy(lower_buf, name);
		(*kernel->strlwr)(lower_buf);
		name = lower_buf;
	}
#endif

	DEBUG(("nfs_lookup('%s' in dir '%s')", name, (ni) ? ni->name : "root"));
	if (!*name || !strcmp(name, "."))
	{
		nfs_dupcookie(fc, dir);
		TRACE(("nfs_lookup(%s in itself) -> ok", name));
		return 0;
	}
	if (!strcmp(name, ".."))
	{
		if (ROOT_INDEX == ni)
		{
			nfs_dupcookie(fc, dir);
			TRACE(("nfs_lookup(%s) -> EMOUNT", name));
			return EMOUNT;
		}
		else
		{
			newi = ni->dir;
			newi->link += 1;
			fc->fs = &nfs_filesys;
			fc->dev = nfs_dev;
			fc->aux = 0;
			fc->index = (long)newi;
			TRACE(("nfs_lookup('%s' in '%s' ) -> ok",
			                          name, (ni)?ni->name:"root"));
			return 0;
		}
	}

	/* if we are in the root dir, we have to search in our own data for a
	 * dir with that name
	 */
	if (ROOT_INDEX == ni)
	{
		ni = mounted;
		while (ni)
		{
			if (ni->link == 0)
			{
				/* this one is not used, so skip it. */
				ni = ni->next;
				continue;
			}
			if (dom == 0)
			{
				if(!Stricmp(name, ni->name))
					break;
			}
			if (!strcmp(name, ni->name))
				break;
			ni = ni->next;
		}
		if (ni)
		{
			ni->link += 1;
			ni->dir = (NFS_INDEX*)dir->index;
			fc->fs = &nfs_filesys;
			fc->dev = nfs_dev;
			fc->aux = 0;
			fc->index = (long)ni;
			TRACE(("nfs_lookup(%s) in root dir -> ok", name));
			return 0;
		}
		else
		{
			DEBUG(("nfs_lookup(%s) -> EFILNF", name));
			return EFILNF;
		}
	}

	/* here, we have to ask the nfs server to look up the name */
	 
	ni = (NFS_INDEX*)dir->index;

#ifdef USE_CACHE
	/* first, consult the lookup cache, if we already have looked this one
	 * up, so that we dont have to ask the server again.
	 */
	newi = nfs_cache_lookup(ni, name, dom);
	if (newi)
	{
		newi->link += 1;
		fc->fs = &nfs_filesys;
		fc->dev = nfs_dev;
		fc->aux = 0;
		fc->index = (long)newi;
		DEBUG(("nfs_lookup('%s' in '%s') from cache",
		                          name, (ni)?ni->name:"root"));
		return 0;
	}		
#endif

	/* check if the directory itself has already got a handle from the server
	 * (see nfs_readdir)
	 */
	if (get_handle(ni) != 0)
 	{
		DEBUG(("nfs_lookup(%s): no handle for current dir, -> EPTHNF", name));
		return EPTHNF;
	}

	dirargs.dir = ni->handle;
	dirargs.name = name;
	mreq = alloc_message(&m, req_buf, LOOKUPBUFSIZE, 
	                             xdr_size_diropargs(&dirargs));
	if (!mreq)
	{
		DEBUG(("nfs_lookup(%s): failed to alloc request msg, -> EFILNF", name));
		return EFILNF;
	}
	xdr_init(&x, mreq->data, mreq->data_len, XDR_ENCODE, NULL);
	xdr_diropargs(&x, &dirargs);
	r = rpc_request(&ni->opt->server, mreq, NFSPROC_LOOKUP, &mrep);
	if (r != 0)
	{
		DEBUG(("nfs_lookup(%s): couldn't contact server, -> EFILNF", name));
		return EFILNF;
	}

	xdr_init(&x, mrep->data, mrep->data_len, XDR_DECODE, NULL);
	if (!xdr_diropres(&x, &dirres))
	{
		DEBUG(("nfs_lookup(%s): couldnt decode results, -> EFILNF", name));
		free_message(mrep);
		return EFILNF;
	}
	free_message(mrep);

	if (dirres.status != NFS_OK)
	{
		DEBUG(("nfs_lookup(%s) rpc->%d -> EFILNF", name, dirres.status));
		return EFILNF;
	}

	newi = get_slot(ni, name, dom);
	if (!newi)
		return ENHNDL;

	newi->dir = ni;
	newi->link += 1;
	newi->handle = dirres.diropres_u.diropok.file;
	fattr2xattr(&dirres.diropres_u.diropok.attributes, &newi->attr);

	newi->stamp = get_timestamp();
	fc->fs = &nfs_filesys;
	fc->dev = nfs_dev;
	fc->aux = 0;
	fc->index = (long)newi;

#ifdef USE_CACHE
	nfs_cache_add(ni, newi);
#endif

	DEBUG(("nfs_lookup('%s' in '%s') -> OK", name, (ni)?ni->name:"root"));
	return 0;
}



long
do_create(long nfs_opcode, fcookie *dir, char *name,
                    unsigned mode, _wORD attrib, fcookie *fc)
{
	char req_buf[CREATEBUFSIZE];
	long r;
	MESSAGE *mreq, *mrep, m;
	createargs createarg;
	diropres dirres;
	xdrs x;
	int dom;
	NFS_INDEX *newi, *ni = (NFS_INDEX*)dir->index;

	TRACE(("do_create(%s)", name));

#ifdef USE_MOUNT_OPT
	if (ni->opt->flags & OPT_RO)    /* read-only mount? */
	{
		DEBUG(("do_create: mount is read-only -> EACCDN"));
		return EACCDN;
	}
#endif

	if (get_handle(ni) != 0)
	{
		DEBUG(("do_creat(%s): no handle for current dir, -> EPTHNF", name));
		return EPTHNF;
	}
	createarg.where.dir = ni->handle;
	createarg.where.name = name;
	createarg.attributes.mode = nfs_mode(mode, attrib);
	createarg.attributes.uid = Pgetuid();
	createarg.attributes.gid = Pgetgid();
	createarg.attributes.size = 0;
		/* BUG: nfstime not set */

	mreq = alloc_message(&m, req_buf, CREATEBUFSIZE, 
	                        xdr_size_createargs(&createarg));
	if (!mreq)
	{
		DEBUG(("do_create(%s): failed to alloc request msg, -> EACCDN", name));
		return EACCDN;
	}

	xdr_init(&x, mreq->data, mreq->data_len, XDR_ENCODE, NULL);
	xdr_createargs(&x, &createarg);
	r = rpc_request(&ni->opt->server, mreq, nfs_opcode, &mrep);
	if (r != 0)
	{
		DEBUG(("do_create(%s): couldn't contact server, -> EACCDN", name));
		return EACCDN;
	}
	xdr_init(&x, mrep->data, mrep->data_len, XDR_DECODE, NULL);
	if (!xdr_diropres(&x, &dirres))
	{
		DEBUG(("do_create(%s): couldnt decode results, -> EACCDN", name));
		free_message(mrep);
		return EACCDN;
	}
	free_message(mrep);
	if (dirres.status != NFS_OK)
	{
		DEBUG(("do_create(%s) -> EACCDN", name));
		return EACCDN;
	}
	dom = Pdomain(-1);
	newi = get_slot(ni, name, dom);
	if (!newi)
	{
		DEBUG(("do_create: no slot found -> EACCDN"));
		return EACCDN;
	}
	newi->dir = (NFS_INDEX*)dir->index;
	newi->link += 1;
	newi->handle = dirres.diropres_u.diropok.file;
	fattr2xattr(&dirres.diropres_u.diropok.attributes, &newi->attr);
	newi->stamp = get_timestamp();
	if (fc)
	{
		fc->fs = &nfs_filesys;
		fc->dev = nfs_dev;
		fc->aux = 0;
		fc->index = (long)newi;
	}
	TRACE(("do_create(%s) -> OK", name));
	return 0;
}


long
nfs_creat(fcookie *dir, char *name,
                 unsigned mode, _wORD attrib, fcookie *fc)
{
	long r;

	TRACE(("nfs_creat"));
	if (ROOT_INDEX == (NFS_INDEX*)dir->index)
	{
		/* only mount dcntl() is allowed in the root dir */
		DEBUG(("nfs_creat(%s): no creation in root dir -> EACCDN", name));
		return EACCDN;
	}
	r = do_create(NFSPROC_CREATE, dir, name, mode, attrib, fc);
	return r;
}


long
nfs_mkdir(fcookie *dir, char *name, unsigned mode)
{
	long r;

	TRACE(("nfs_mkdir"));
	if (ROOT_INDEX == (NFS_INDEX*)dir->index)
	{
		/* only mount dcntl() is allowed in the root dir */
		DEBUG(("nfs_mkdir(%s): no creation in root dir -> EACCDN", name));
		return EACCDN;
	}
	r = do_create(NFSPROC_MKDIR, dir, name, mode, FA_DIR, NULL);
	return r;
}



DEVDRV *
nfs_getdev(fcookie *fc, long *devsp)
{
	TRACE(("nfs_getdev"));
	if (nfs_dev != fc->dev)
	{
		*devsp = EIHNDL;
		return NULL;
	}
	*devsp = 0;
	return &nfs_device;
}



long
nfs_getxattr(fcookie *fc, XATTR *xattr)
{
	char req_buf[XATTRBUFSIZE];
	NFS_INDEX *ni = (NFS_INDEX*)fc->index;
	long stamp, r;
	MESSAGE *mreq, *mrep, m;
	attrstat stat_res;
	xdrs x;

	DEBUG(("nfs_getxattr(%s)", (ni) ? ni->name : "root"));
	if (ROOT_INDEX == ni)  /* attributes for the root dir */
	{
		if (xattr)
		{
			*xattr = root_attr;
			xattr->index = (long)ROOT_INDEX;
			xattr->dev = fc->dev;
		}
		TRACE(("nfs_getxattr(root) -> mode 0%o, ok", xattr->mode));
		return 0;
	}

	if (get_handle(ni) != 0)
	{
		DEBUG(("nfs_getxattr(%s): failed to get handle, -> EFILNF", ni->name));
		return EFILNF;
	}

/* look if we have the right attributes already cached, that means that the
 * lifetime of the attributes in the index struct is not exceeded. If so,
 * return the cached values, but only if the mount has not specified not to
 * use the attribute cache.
 */
	if (!(ni->opt->flags & OPT_NOAC))
	{
		stamp = get_timestamp();
		if (after(ni->stamp+ni->opt->actimeo, stamp))
		{
			if (xattr)
			{
				*xattr = ni->attr;
				xattr->dev = fc->dev;
#if 0   /* BUG: which device is this file on???? */
				xattr->index = (long)ni;
#endif
			}

#ifdef USE_MOUNT_OPT
			if (ni->opt->flags & OPT_RO)
			{
				xattr->mode &= ~(S_IWOTH|S_IWGRP|S_IWUSR);
				xattr->attr |= FA_RDONLY;
			}
#endif

			if ((xattr->mode & S_IFMT) == S_IFLNK)
				++xattr->size;  /* fix for buffer size when reading symlinks */

			DEBUG(("nfs_getxattr(%s): from cache -> mode 0%o, ok",
			                                   ni->name, xattr->mode));
			return 0;
		}
	}
	
	mreq = alloc_message(&m, req_buf, XATTRBUFSIZE,
	                               xdr_size_nfsfh(&ni->handle));
	if (!mreq)
	{
		DEBUG(("nfs_getxattr(%s): failed to alloc msg, -> EFILNF", ni->name));
		return EFILNF;
	}
	xdr_init(&x, mreq->data, mreq->data_len, XDR_ENCODE, NULL);
	xdr_nfsfh(&x, &ni->handle);
	
	r = rpc_request(&ni->opt->server, mreq, NFSPROC_GETATTR, &mrep);
	if (r != 0)
	{
		DEBUG(("nfs_getxattr(%s): couldn't contact server, -> EACCDN",ni->name));
		return EACCDN;
	}
	xdr_init(&x, mrep->data, mrep->data_len, XDR_DECODE, NULL);
	if (!xdr_attrstat(&x, &stat_res))
	{
		DEBUG(("nfs_getxattr(%s): couldnt decode results, -> EACCDN", ni->name));
		free_message(mrep);
		return EACCDN;
	}
	free_message(mrep);
	if (stat_res.status != NFS_OK)
	{
		DEBUG(("nfs_getxattr(%s) rpc->%d, -> EACCDN", ni->name, stat_res.status));
		return EACCDN;
	}
	fattr2xattr(&stat_res.attrstat_u.attributes, &ni->attr);
	ni->stamp = get_timestamp();
	if (xattr)
	{
		*xattr = ni->attr;
		xattr->dev = fc->dev;
#if 0   /* BUG: which device is this object on???? */
		xattr->index = (long)ni;
#endif
	}

#ifdef USE_MOUNT_OPT
		if (ni->opt->flags & OPT_RO)
		{
			xattr->mode &= ~(S_IWOTH|S_IWGRP|S_IWUSR);
			xattr->attr |= FA_RDONLY;
		}
		if (ni->opt->flags & OPT_NOSUID)   /* mount with no set uid bit */
		{
			xattr->mode &= ~S_ISUID;
		}
#endif

	if ((xattr->mode & S_IFMT) == S_IFLNK)
		++xattr->size;  /* fix for buffer size when reading symlinks */

	DEBUG(("nfs_getxattr(%s) -> mode 0%o, ok", ni->name, xattr->mode));
	return 0;
}



long
do_sattr(fcookie *fc, sattr *ap)
{
	char req_buf[SATTRBUFSIZE];
	NFS_INDEX *ni = (NFS_INDEX*)fc->index;
	long r;
	MESSAGE *mreq, *mrep, m;
	sattrargs s_arg;
	attrstat stat_res;
	xdrs x;

	TRACE(("do_sattr(%s)", ni->name));

#ifdef USE_MOUNT_OPT
	if (ni->opt->flags & OPT_RO)
	{
		DEBUG(("do_sattr: mount is read-only -> EACCDN"));
		return EACCDN;
	}
#endif

	if (get_handle(ni) != 0)
	{
		DEBUG(("do_sattr(%s): failed to get handle, -> EFILNF", ni->name));
		return EFILNF;
	}

	s_arg.file = ni->handle;
	s_arg.attributes = *ap;
	mreq = alloc_message(&m, req_buf, SATTRBUFSIZE, xdr_size_sattrargs(&s_arg));
	if (!mreq)
	{
		DEBUG(("do_sattr(%s): failed to allocate request message", ni->name));
		return EFILNF;
	}
	xdr_init(&x, mreq->data, mreq->data_len, XDR_ENCODE, NULL);
	xdr_sattrargs(&x, &s_arg);
	r = rpc_request(&ni->opt->server, mreq, NFSPROC_SETATTR, &mrep);
	if (r != 0)
	{
		DEBUG(("do_sattr(%s): couldn't contact server, -> EACCDN", ni->name));
		return EACCDN;
	}
	xdr_init(&x, mrep->data, mrep->data_len, XDR_DECODE, NULL);
	if (!xdr_attrstat(&x, &stat_res))
	{
		DEBUG(("do_sattr(%s): couldnt decode results, -> EACCDN", ni->name));
		free_message(mrep);
		return EACCDN;
	}
	free_message(mrep);
	if (stat_res.status != NFS_OK)
	{
		DEBUG(("do_sattr(%s) -> EACCDN", ni->name));
		return EACCDN;
	}
	fattr2xattr(&stat_res.attrstat_u.attributes, &ni->attr);
	ni->stamp = get_timestamp();
	TRACE(("do_sattr(%s) -> OK", ni->name));
	return 0;
}


long
nfs_chattr(fcookie *fc, _wORD attrib)
{
	NFS_INDEX *ni = (NFS_INDEX*)fc->index;
	sattr attr;
	long r;
	int wperm;

	TRACE(("nfs_chattr(%d)", attrib));
	if (ROOT_INDEX == ni)
	{
		TRACE(("nfs_chattr on root dir"));
		root_attr.attr = attrib;
		if (attrib & FA_RDONLY)
			root_attr.mode &= ~(S_IWOTH|S_IWGRP|S_IWUSR);
		return 0;
	}

	/* get current attributes */
	r = nfs_getxattr(fc, NULL);
	if (r != 0)
		return r;
		
	wperm = ( (attrib & FA_RDONLY) &&
	               (ni->attr.mode & (S_IWOTH|S_IWGRP|S_IWUSR)) )
	     || (!(attrib & FA_RDONLY) &&
	               !(ni->attr.mode & (S_IWOTH|S_IWGRP|S_IWUSR)));
	if (wperm)
	{
		/* set write permissions correctly */
		attr.mode = ni->attr.mode;
		attr.mode |= S_IWOTH|S_IWGRP|S_IWUSR;
		if (attrib & FA_RDONLY)
			attr.mode &= ~(S_IWOTH|S_IWGRP|S_IWUSR);

		attr.uid = (u_long)-1L;
		attr.gid = (u_long)-1L;
		attr.size = (u_long)-1L;
		attr.atime.seconds = (u_long)-1L;
		attr.atime.useconds = (u_long)-1L;
		attr.mtime.seconds = (u_long)-1L;
		attr.mtime.useconds = (u_long)-1L;
		return do_sattr(fc, &attr);
	}

/* BUG: we should do some time calculations on which the archive attribute
 *      setting could be based. Also, the system and hidden attribute
 *      should be maintained somehow. ANY IDEAS?
 *      we could also try to emulate the system flag by chowning to root, but
 *      is this really a good idea?
 */
	DEBUG(("nfs_chattr: other than readonly attribute not implemented"));

	return EACCDN;
}


long
nfs_chown(fcookie *fc, _wORD uid, _wORD gid)
{
	NFS_INDEX *ni = (NFS_INDEX*)fc->index;
	sattr attr;

	TRACE(("nfs_chown"));
	if (ROOT_INDEX == ni)
	{
		TRACE(("nfs_chown on root dir"));
		root_attr.uid = uid;
		root_attr.gid = gid;
		return 0;
	}
	attr.uid = uid;
	attr.gid = gid;
	attr.mode = (u_long)-1L;
	attr.size = (u_long)-1L;
	attr.atime.seconds = (u_long)-1L;
	attr.atime.useconds = (u_long)-1L;
	attr.mtime.seconds = (u_long)-1L;
	attr.mtime.useconds = (u_long)-1L;
	return do_sattr(fc, &attr);
}


long
nfs_chmode(fcookie *fc, unsigned mode)
{
	NFS_INDEX *ni = (NFS_INDEX*)fc->index;
	sattr attr;

	TRACE(("nfs_chmode"));
	if (ROOT_INDEX == ni)
	{
		TRACE(("nfs_chmode on root dir"));
		/* make sure to perserve the file type */
		root_attr.mode = (root_attr.mode & S_IFMT) | (mode & ~S_IFMT);
		return 0;
	}
	attr.uid = (u_long)-1;
	attr.gid = (u_long)-1;
	attr.mode = mode;
	attr.size = (u_long)-1L;
	attr.atime.seconds = (u_long)-1L;
	attr.atime.useconds = (u_long)-1L;
	attr.mtime.seconds = (u_long)-1L;
	attr.mtime.useconds = (u_long)-1L;
	return do_sattr(fc, &attr);
}



long
do_remove(long nfs_opcode, fcookie *dir, char *name)
{
	char req_buf[REMBUFSIZE];
	long r;
	MESSAGE *mreq, *mrep, m;
	diropargs dirargs;
	nfsstat stat_res;
	xdrs x;
	NFS_INDEX *ni = (NFS_INDEX*)dir->index;

	DEBUG(("do_remove(%s)", name));

#ifdef USE_MOUNT_OPT
	if (ni->opt->flags & OPT_RO)
	{
		DEBUG(("do_remove: mount is read-only ->EACCDN"));
		return EACCDN;
	}
#endif

	if (get_handle(ni) != 0)
	{
		DEBUG(("do_remove(%s): failed to get handle, -> EPTHNF", name));
		return EPTHNF;
	}
	dirargs.dir = ni->handle;
	dirargs.name = name;
	mreq = alloc_message(&m, req_buf, REMBUFSIZE, xdr_size_diropargs(&dirargs));
	if (!mreq)
	{
		DEBUG(("do_remove(%s): failed to allocate buffer -> EACCDN", name));
		return EACCDN;
	}
	xdr_init(&x, mreq->data, mreq->data_len, XDR_ENCODE, NULL);
	xdr_diropargs(&x, &dirargs);
	r = rpc_request(&ni->opt->server, mreq, nfs_opcode, &mrep);
	if (r != 0)
	{
		DEBUG(("do_remove(%s): couldn't contact server, -> EACCDN", name));
		return EACCDN;
	}

	xdr_init(&x, mrep->data, mrep->data_len, XDR_DECODE, NULL);
	if (!xdr_nfsstat(&x, &stat_res))
	{
		DEBUG(("do_remove(%s): couldnt decode results, -> EACCDN", name));
		free_message(mrep);
		return EACCDN;
	}
	free_message(mrep);
	if (stat_res != NFS_OK)
	{
		DEBUG(("do_remove(%s, %ld) -> EACCDN", name, nfs_opcode));
		return EACCDN;
	}

#ifdef USE_CACHE
	nfs_cache_removebyname(ni, name);
#endif

	DEBUG(("do_remove(%s, %ld) -> OK", name, nfs_opcode));
	return 0;
}


long
nfs_rmdir(fcookie *dir, char *name)
{
	NFS_INDEX *ni = (NFS_INDEX*)dir->index;

	TRACE(("nfs_rmdir"));
	if (ROOT_INDEX == ni)
	{
		DEBUG(("nfs_rmdir(%s): no remove from root dir, -> EACCDN", name));
		return EACCDN;
	}

	return do_remove(NFSPROC_RMDIR, dir, name);
}


long
nfs_remove(fcookie *dir, char *name)
{
	NFS_INDEX *ni = (NFS_INDEX*)dir->index;

	TRACE(("nfs_remove"));
	if (ROOT_INDEX == ni)
	{
		DEBUG(("nfs_remove(%s): no remove from root dir, -> EACCDN", name));
		return EACCDN;
	}

	return do_remove(NFSPROC_REMOVE, dir, name);
}



long
nfs_getname(fcookie *relto, fcookie *dir, char *pathname, _wORD size)
{
	NFS_INDEX *ni, *oni, *reli;
	int len, copy_name;

	if (size < 0)
		return ERANGE;
	if (size == 0)
		return 0;

	/* make a linked list of nfs_index using the aux field from the
	 * top directory to the searched dir
	 */
	oni = (NFS_INDEX*)dir->index;
	reli = (NFS_INDEX*)relto->index;
	copy_name = 0;
	if (!oni)
		return EPTHNF;
	TRACE(("nfs_getname: relto = '%s', dir = '%s'",
	           (reli==ROOT_INDEX)?"root":reli->name, oni->name));
	while (oni != (NFS_INDEX*)relto->index)
	{
		ni = oni->dir;
		if (ni == ROOT_INDEX)  /* stop if root dir reached */
			if ((NFS_INDEX*)relto->index != ROOT_INDEX)
				return EPTHNF;
			else
			{
				copy_name = 1;
				break;
			}
		ni->aux = oni;
		oni= ni;
	}

	/* now fill pathname with up to size characters by going down the
	 * directory structure build up above
	 */
	size -= 1;   /* count off the trailing 0 */
	ni = oni;
	*pathname = '\0';
	if (copy_name)
	{
		if (size < (len = 1))
			return ERANGE;
		strcat(pathname, "\\");
		size -= len;
		if (size < (len = strlen(ni->name)))
			return ERANGE;
		strcat(pathname, ni->name);
		size -= len;
	}
	while (ni != (NFS_INDEX*)dir->index)
	{
		ni = ni->aux;
		if (!ni)
			return EPTHNF;
		if (size < (len = 1))
			return ERANGE;
		strcat(pathname, "\\");
		size -= len;
		if (size < (len = strlen(ni->name)))
			return ERANGE;
		strcat(pathname, ni->name);
		size -= len;
	}
	TRACE(("nfs_getname -> '%s'", pathname));
	return 0;
}



long
nfs_rename(fcookie *olddir, char *oldname,
                       fcookie *newdir, char *newname)
{
	char req_buf[RENBUFSIZE];
	long r;
	MESSAGE *mreq, *mrep, m;
	renameargs renarg;
	nfsstat stat_res;
	xdrs x;
	NFS_INDEX *newi = (NFS_INDEX*)newdir->index;
	NFS_INDEX *oldi = (NFS_INDEX*)olddir->index;

	TRACE(("nfs_rename('%s' -> '%s')", oldname, newname));
	if ((ROOT_INDEX == oldi) || (ROOT_INDEX == newi))
	{
		DEBUG(("nfs_rename(%s): no rename in the root dir, -> EACCDN", oldname));
		return EACCDN;
	}

#ifdef USE_MOUNT_OPT
	if ((oldi->opt->flags & OPT_RO) || (newi->flags & OPT_RO))
	{
		DEBUG(("nfs_rename: mount is read-only -> EACCDN"));
		return EACCDN;
	}
#endif

	if (get_handle(newi) != 0)
	{
		DEBUG(("nfs_rename(%s): no handle for new dir, -> EPTHNF", oldname));
		return EPTHNF;
	}
	if (get_handle(oldi) != 0)
	{
		DEBUG(("nfs_rename(%s): no handle for old dir, -> EPTHNF", oldname));
		return EPTHNF;
	}

	renarg.from.dir = oldi->handle;
	renarg.from.name = oldname;
	renarg.to.dir = newi->handle;
	renarg.to.name = newname;
	mreq = alloc_message(&m, req_buf, RENBUFSIZE, xdr_size_renameargs(&renarg));
	if (!mreq)
	{
		DEBUG(("nfs_rename(%s): failed to allocate buffer -> EACCDN", oldname));
		return EACCDN;
	}
	xdr_init(&x, mreq->data, mreq->data_len, XDR_ENCODE, NULL);
	xdr_renameargs(&x, &renarg);
	r = rpc_request(&oldi->opt->server, mreq, NFSPROC_RENAME, &mrep);
	if (r != 0)
	{
		DEBUG(("nfs_rename(%s): couldn't contact server, -> EACCDN", oldname));
		return EACCDN;
	}

	xdr_init(&x, mrep->data, mrep->data_len, XDR_DECODE, NULL);
	if (!xdr_nfsstat(&x, &stat_res))
	{
		DEBUG(("nfs_rename(%s): couldnt decode results, -> EACCDN", oldname));
		free_message(mrep);
		return EACCDN;
	}
	free_message(mrep);
	if (stat_res != NFS_OK)
	{
		DEBUG(("nfs_rename(%s) -> EACCDN", oldname));
		return EACCDN;
	}

/* BUG: here we should do the rename also in our local data */
	TRACE(("nfs_rename('%s' -> '%s') -> OK", oldname, newname));
	return 0;
}



long
nfs_pathconf(fcookie *dir, _wORD which)
{
	TRACE(("nfs_pathconf(%d)",which));
	switch (which)
	{
		case -1:
			return DP_MAXREQ;
		case DP_IOPEN:
			return 1;
		case DP_MAXLINKS:
			return 1;
		case DP_PATHMAX:
			return 128;    /* has to be MAXPATHLEN */
		case DP_NAMEMAX:
			return 32;     /* has to be MAXNAMLEN */
		case DP_ATOMIC:
			return 512;
		case DP_TRUNC:
			return DP_NOTRUNC;
		case DP_CASE:
			return DP_CASESENS;
		default:
			return EINVFN;
	}
}



long
nfs_dfree(fcookie *dir, long *buf)
{
	char req_buf[DFREEBUFSIZE];
	long r;
	MESSAGE *mreq, *mrep, m;
	xdrs x;
	statfsres stat_res;
	NFS_INDEX *ni = (NFS_INDEX*)dir->index;

	TRACE(("nfs_dfree"));
	if (ROOT_INDEX == ni)
	{
		TRACE(("nfs_dfree(root)"));
		/* these are really silly values; who knows better ones? */
		buf[0] = 0;   /* number of free clusters */
		buf[1] = 0;   /* total number of clusters */
		buf[2] = 0;   /* bytes per sector */
		buf[3] = 0;   /* sectors per cluster */
		return 0;
	}
	if (get_handle(ni) != 0)
	{
		DEBUG(("nfs_dfree: failed to get handle, -> EPTHNF"));
		return EPTHNF;
	}
	mreq = alloc_message(&m, req_buf, DFREEBUFSIZE,
	                             xdr_size_nfsfh(&ni->handle));
	if (!mreq)
	{
		DEBUG(("nfs_dfree: failed to allocate buffer, -> EPTHNF"));
		return EPTHNF;
	}
	xdr_init(&x, mreq->data, mreq->data_len, XDR_ENCODE, NULL);
	xdr_nfsfh(&x, &ni->handle);
	r = rpc_request(&ni->opt->server, mreq, NFSPROC_STATFS, &mrep);
	if (r != 0)
	{
		DEBUG(("nfs_dfree: couldn't contact server, -> EPTHNF"));
		return EPTHNF;
	}
	xdr_init(&x, mrep->data, mrep->data_len, XDR_DECODE, NULL);
	if (!xdr_statfsres(&x, &stat_res))
	{
		DEBUG(("nfs_dfree: couldnt decode results, -> EPTHNF"));
		free_message(mrep);
		return EPTHNF;
	}
	free_message(mrep);
	if (stat_res.status != NFS_OK)
	{
		DEBUG(("nfs_dfree -> EPTHNF"));
		return EPTHNF;
	}
	buf[0] = stat_res.statfsres_u.info.bavail;
	buf[1] = stat_res.statfsres_u.info.blocks;
	buf[2] = stat_res.statfsres_u.info.bsize;
	buf[3] = 1; /* BUG: how to determine the number of sectors per cluster? */
	return 0;
}



long
nfs_writelabel(fcookie *dir, char *name)
{
	TRACE(("nfs_writelabel"));
	if (ROOT_INDEX == (NFS_INDEX*)dir->index)
	{
		if (strlen(name) > MAX_LABEL)   /* this is too long */
			return ERANGE;
		strncpy(nfs_label, name, MAX_LABEL);
		nfs_label[MAX_LABEL+1] = '\0';
		return 0;
	}
	return EACCDN;
}


long
nfs_readlabel(fcookie *dir, char *name, _wORD namelen)
{
	TRACE(("nfs_readlabel"));
	if (ROOT_INDEX == (NFS_INDEX*)dir->index)
	{
		if (namelen <= 0)
			return ERANGE;
		strncpy(name, nfs_label, namelen-1);
		name[namelen-1] = '\0';
		return 0;
	}
	return EACCDN;
}



long
nfs_symlink(fcookie *dir, char *name, char *to)
{
	char req_buf[SYMLNBUFSIZE];
	long r;
	MESSAGE *mreq, *mrep, m;
	symlinkargs symarg;
	nfsstat stat_res;
	xdrs x;
	NFS_INDEX *ni = (NFS_INDEX*)dir->index;

	TRACE(("nfs_symlink(%s -> %s)", name, to));
	if (ROOT_INDEX == ni)
	{
		DEBUG(("nfs_symlink not allowed in root dir"));
		return EACCDN;
	}

#ifdef USE_MOUNT_OPT
	if (ni->opt->flags & OPT_RO)
	{
		DEBUG(("nfs_symlink: mount is read-only -> EACCDN"));
		return EACCDN;
	}
#endif

	if (get_handle(ni) != 0)
	{
		DEBUG(("nfs_symlink: failed to get handle, -> EPTHNF"));
		return EPTHNF;
	}
	symarg.from.dir = ni->handle;
	symarg.from.name = name;
	symarg.to = to;
	symarg.attributes.mode = 0120777;
	symarg.attributes.uid = Pgetuid();
	symarg.attributes.gid = Pgetgid();
	symarg.attributes.size = strlen(to)+1;
	symarg.attributes.atime.seconds = 0;
	symarg.attributes.atime.useconds = 0;
	symarg.attributes.mtime.seconds = 0;
	symarg.attributes.mtime.useconds = 0;
	mreq = alloc_message(&m, req_buf, SYMLNBUFSIZE,
	                               xdr_size_symlinkargs(&symarg));
	if (!mreq)
	{
		DEBUG(("nfs_symlink: failed to allocate buffer, -> EACCDN"));
		return EACCDN;
	}
	xdr_init(&x, mreq->data, mreq->data_len, XDR_ENCODE, NULL);
	xdr_symlinkargs(&x, &symarg);
	r = rpc_request(&ni->opt->server, mreq, NFSPROC_SYMLINK, &mrep);
	if (r != 0)
	{
		DEBUG(("nfs_symlink: couldn't contact server, -> EACCDN"));
		return EACCDN;
	}
	xdr_init(&x, mrep->data, mrep->data_len, XDR_DECODE, NULL);
	if (!xdr_nfsstat(&x, &stat_res))
	{
		DEBUG(("nfs_symlink: couldnt decode results, -> EACCDN"));
		free_message(mrep);
		return EACCDN;
	}
	free_message(mrep);
	if (stat_res != NFS_OK)
	{
		DEBUG(("nfs_symlink -> EACCDN"));
		return EACCDN;
	}
	TRACE(("nfs_symlink -> OK"));
	return 0;
}


long
nfs_readlink(fcookie *dir, char *buf, _wORD len)
{
	char req_buf[READLNBUFSIZE];
	long r;
	MESSAGE *mreq, *mrep, m;
	readlinkres link_res;
	char databuf[MAXPATHLEN+1];
	xdrs x;
	NFS_INDEX *ni = (NFS_INDEX*)dir->index;

	TRACE(("nfs_readlink"));
	if ((ROOT_INDEX == ni) || (ni->flags & IS_MOUNT_DIR))
	{
		DEBUG(("nfs_readlink: no links in root dir"));
		return EFILNF;
	}
	if (get_handle(ni) != 0)
	{
		DEBUG(("nfs_readlink: failed to get handle, -> EPTHNF"));
		return EPTHNF;
	}
	mreq = alloc_message(&m, req_buf, READLNBUFSIZE,
	                               xdr_size_nfsfh(&ni->handle));
	if (!mreq)
	{
		DEBUG(("nfs_readlink: failed to allocate buffer, -> EFILNF"));
		return EFILNF;
	}
	xdr_init(&x, mreq->data, mreq->data_len, XDR_ENCODE, NULL);
	xdr_nfsfh(&x, &ni->handle);
	r = rpc_request(&ni->opt->server, mreq, NFSPROC_READLINK, &mrep);
	if (r != 0)
	{
		DEBUG(("nfs_readlink: couldn't contact server, -> EFILNF"));
		return EFILNF;
	}
	xdr_init(&x, mrep->data, mrep->data_len, XDR_DECODE, NULL);
	link_res.readlinkres_u.data = &databuf[0];
	if (!xdr_readlinkres(&x, &link_res))
	{
		DEBUG(("nfs_readlink: couldnt decode results, -> EFILNF"));
		free_message(mrep);
		return EFILNF;
	}
	free_message(mrep);
	if (link_res.status != NFS_OK)
	{
		DEBUG(("nfs_readlink -> EFILNF"));
		return EFILNF;
	}
	{
		short i = len;
		char *p = buf, *cp = databuf;
		while (--i >= 0 && (*p++ = (*cp != '/' ? *cp : '\\')))
			++cp;
		if (i < 0)
		{
			DEBUG(("nfs_readlink: result too long, -> ERANGE"));
			return ERANGE;
		}
		DEBUG(("nfs_readlink -> `%s'", buf));
	}
	TRACE(("nfs_symlink -> OK"));
	return 0;
}


long
nfs_hardlink(fcookie *fromdir, char *fromname,
                           fcookie *todir, char *toname)
{
	char req_buf[HARDLNBUFSIZE];
	long r;
	MESSAGE *mreq, *mrep, m;
	linkargs linkarg;
	nfsstat stat_res;
	xdrs x;
	fcookie fc;
	NFS_INDEX *fromi = (NFS_INDEX*)fromdir->index;
	NFS_INDEX *toi = (NFS_INDEX*)todir->index;

	TRACE(("nfs_hardlink(%s -> %s)", fromname, toname));
	if (ROOT_INDEX == toi)
	{
		DEBUG(("nfs_hardlink not allowed in root dir"));
		return EACCDN;
	}

#ifdef USE_MOUNT_OPT
	if (toi->opt->flags & OPT_RO)
	{
		DEBUG(("nfs_hardlink: mount is read-only -> EACCDN"));
		return EACCDN;
	}
#endif

	if (nfs_lookup(fromdir, fromname, &fc) != 0)
	{
		DEBUG(("nfs_hardlink: file not found, -> EFILNF"));
		return EFILNF;
	}

	if (get_handle(toi) != 0)
	{
		DEBUG(("nfs_hardlink: failed to get handle for dest dir, -> EPTHNF"));
		return EPTHNF;
	}

	fromi = (NFS_INDEX*)fc.index;
	nfs_release(&fc);
	linkarg.to.dir = toi->handle;
	linkarg.to.name = toname;
	linkarg.from = fromi->handle;

	mreq = alloc_message(&m, req_buf, HARDLNBUFSIZE,
	                              xdr_size_linkargs(&linkarg));
	if (!mreq)
	{
		DEBUG(("nfs_hardlink: failed to allocate buffer, -> EACCDN"));
		return EACCDN;
	}
	xdr_init(&x, mreq->data, mreq->data_len, XDR_ENCODE, NULL);
	xdr_linkargs(&x, &linkarg);

	r = rpc_request(&fromi->opt->server, mreq, NFSPROC_LINK, &mrep);
	if (r != 0)
	{
		DEBUG(("nfs_hardlink: couldn't contact server, -> EACCDN"));
		return EACCDN;
	}

	xdr_init(&x, mrep->data, mrep->data_len, XDR_DECODE, NULL);
	if (!xdr_nfsstat(&x, &stat_res))
	{
		DEBUG(("nfs_hardlink: couldnt decode results, -> EACCDN"));
		free_message(mrep);
		return EACCDN;
	}
	free_message(mrep);

	if (stat_res != NFS_OK)
	{
		DEBUG(("nfs_hardlink -> EACCDN"));
		return EACCDN;
	}

	TRACE(("nfs_hardlink -> OK"));
	return 0;
}



#define FS_GETBASEPAGE  (('N' << 8) | 'B')

long
nfs_fscntl(fcookie *dir, char *name, _wORD cmd, long arg)
{
	TRACE(("nfs_fscntl"));
	if (NFS_MOUNT == cmd)
	{
		NFS_MOUNT_INFO *info = (NFS_MOUNT_INFO*)arg;
		NFS_INDEX *ni;

		if (0L == arg)
			return EACCDN;
		if(ROOT_INDEX != (NFS_INDEX*)dir->index)
		{
			DEBUG(("nfs_fscntl: mount only allowed in root dir"));
			return EACCDN;
		}
		ni = get_mount_slot(name, info);
		if (ni->link > 0)   /* this file was mounted before */
		{
			DEBUG(("nfs_fscntl: no remount allowed, -> EACCDN"));
			return EACCDN;
		}
		ni->link = 1;
		ni->handle = info->handle;
		DEBUG(("nfs_fscntl: mounting dir '%s'", ni->name));
		return 0;
	}

	if (NFS_UNMOUNT == cmd)
	{
		fcookie fc;
		NFS_INDEX *ni;
		long r;

		r = nfs_lookup(&root_cookie, name, &fc);
		if (0 == r)
		{
			ni = (NFS_INDEX*)fc.index;
			nfs_release(&fc);
		}
		else
		{
			DEBUG(("nfs_fscntl: unmount on not mounted directory, -> EFILNF"));
			return EFILNF;
		}
		if (!(ni->flags & IS_MOUNT_DIR))
		{
			DEBUG(("nfs_fscntl: unmount failed, not a mounted directory"));
			return EACCDN;
		}

		DEBUG(("nfs_fscntl: unmounting '%s'", ni->name));

#ifdef USE_CACHE
		/* make sure that the cache is coherent */
		nfs_cache_expire();
		nfs_cache_remove(ni);
#endif

		r = release_mount_slot(ni);
		if (r != 0)
			DEBUG(("nfs_fscntl: unmount fialed with %ld", r));
		return r;
	}

	if (NFS_MNTDUMP == cmd)   /* for debugging only */
	{
		return 0;
	}

	if (NFS_DUMPALL == cmd)   /* for debugging only */
	{
		return 0;
	}
	
	if (FS_GETBASEPAGE == cmd)  /* for debugging only */
	{
		extern long _xfs_entry;

		DEBUG(("nfs_fscntl: basepage at 0x%lx",
		                  (long)&_xfs_entry-sizeof(BASEPAGE)));
		return (long)&_xfs_entry-sizeof(BASEPAGE);
	}
	DEBUG(("nfs_fcntl -> EINVFN"));
	return EINVFN;
}


long
nfs_dskchng(_wORD drv)
{
	TRACE(("nfs_dskchng -> 0"));
	return 0;
}


long
nfs_release(fcookie *fc)
{
	NFS_INDEX *ni = (NFS_INDEX*)fc->index;

	if (ni != ROOT_INDEX)
	{
		ni->link -= 1;
		if (ni->link < 0)
			return EIHNDL;    /* this was invalid! */
		if (0 == ni->link)
			free_slot(ni);
	}
	return 0;
}


long
nfs_dupcookie(fcookie *dest, fcookie *src)
{
	NFS_INDEX *ni = (NFS_INDEX*)src->index;

	*dest = *src;
	if (ni != ROOT_INDEX)
	{
		ni->link += 1;   /* index is in use once more */
	}
	return 0;
}




void
init_root()
{
	init_mount_attr(&root_attr);
	init_index();
	init_ipc(NFS_PROGRAM, NFS_VERSION);
	root_attr.blksize = sizeof(NFS_INDEX);
}
