/*
 * 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.
 *
 * Some ideas (especially in the build_path function) are
 * taken from the linux nfs server.
 */

/*
 * File : fh.c
 *        function for dealing with file handles
 */


#include <unistd.h>
#include <dirent.h>
#include <stat.h>
#include <memory.h>
#include <string.h>
#include <limits.h>
#include <errno.h>
#include <signal.h>
#include <osbind.h>
#include <mintbind.h>
#include <types.h>
#include <fcntl.h>
#include "fh.h"
#include "util.h"
#include "auth.h"


#include <syslog.h>



#define TICKS_PER_SEC  200
#define FLUSH_TIME     5   /* in seconds */


FILE_DESCR the_f_handle[NFILEHANDLE];
FILE_DESCR the_d_handle[NDIRHANDLE];
NFSD_CACHE the_index[NINDEX];

FILE_DESCR *file_handle = &the_f_handle[0];
FILE_DESCR *dir_handle = &the_d_handle[0];

NFSD_CACHE *file_index = &the_index[0];

/* These list is used for faster access. The acc list holds the indices as
 * they are used (doubly connected). last_acc points to the last inserted
 * structure. Unused or longer not accessed structures are pointed to by
 * acc_list.
 */
NFSD_CACHE *acc_list, *last_acc;

/* The list of exported files, which is set up at start time. */
NFSD_CACHE *exported = NULL;


#define PATH_SEP  '\\'
#define PATH_SEP_STR  "\\"

/* This macro gives an index in index table from an inode */
#define HASH_INDEX(ino)  ((u_long)ino % NINDEX)

/* This macro gives a mangled inode number for the nfs handle */
#define MANGLE_INO(ino)  (((ino)^((ino)>>8)^((ino)>>16)^((ino)>>24))&0xff)


static char *null_name = "";
static int flush_descr_cache = 0;

extern int debug;

struct error2nfserr
{
	long mint_err;
	long nfs_err;
} err_table[] =
{
	{ E_OK,   NFS_OK },
	{ EERROR, NFSERR_IO },  /* BIOS errors */
	{ EDRVNR, NFSERR_IO },
	{ EUNCMD, NFSERR_IO },
	{ E_CRC,  NFSERR_IO },
	{ EBADRQ, NFSERR_IO },
	{ E_SEEK, NFSERR_IO },
	{ EMEDIA, NFSERR_NXIO },
	{ ESECNF, NFSERR_IO },
	{ EWRITE, NFSERR_IO },
	{ EREAD,  NFSERR_IO },
	{ EGENERIC, NFSERR_IO },
	{ EROFS,  NFSERR_ROFS },
	{ ECHMEDIA, NFSERR_IO },
	{ EUNDEV, NFSERR_NXIO },
	{ EBADSEC, NFSERR_IO },
	{ EIDISK, NFSERR_IO },
	{ EINVAL, NFSERR_IO },   /* GEMDOS errors */
	{ ENOENT, NFSERR_NOENT },
	{ EPATH,  NFSERR_NOENT },
	{ EMFILE, NFSERR_NOENT },
	{ EACCES, NFSERR_ACCES },
	{ EBADF,  NFSERR_STALE },
	{ ENXIO,  NFSERR_NXIO },
	{ EXDEV,  NFSERR_IO },
	{ EBADARG, NFSERR_NAMETOOLONG },
	{ EIO,    NFSERR_IO },
	{ ENOSPC, NFSERR_NOSPC }
};

long
nfs_errno(long err_code)
{
	int i;
	int err;

	if (err_code != -1)
		err = errno;
	else
		err = err_code;
	if (err < 0)
		err = -err;
	for (i = 0;  i < sizeof(err_table)/sizeof(struct error2nfserr);  i++)
		if (err == err_table[i].mint_err)
			return err_table[i].nfs_err;
	return NFSERR_IO;   /* general error */
}



static void
init_indices()
{
	int i;
	accesslist *ac;
	struct stat stats;
	long r;
	NFSD_CACHE *index;
	char buf1[PATH_MAX+1], buf2[PATH_MAX+1];

	for (i = 0;  i < NINDEX;  i++)
	{
		file_index[i].fh.inode = -1;
		file_index[i].fh.n_ino = 0;
		file_index[i].flags = 0;
		file_index[i].fd = NULL;
		file_index[i].filename = file_index[i].fullname = NULL;
		file_index[i].next = NULL;
		file_index[i].prev = NULL;
		if (i > 0)
			file_index[i].accnext = &file_index[i-1];
		else
			file_index[i].accnext = NULL;
		file_index[i].accprev = NULL;
	}
	acc_list = &file_index[NINDEX-1];
	last_acc = &file_index[0];

	/* Generate a linked list containing indices for all exported
	 * files/directories; we keep that list, so that we do not have to
	 * flush these indices.
	 */
	for (ac = authlist;  ac;  ac = ac->next)
	{
		int n;

		index = malloc(sizeof(NFSD_CACHE)+strlen(ac->mountpoint)+1);
		if (index == NULL)
			return;

		index->fullname = (char*)index + sizeof(NFSD_CACHE);
		strcpy(index->fullname, ac->mountpoint);
		index->filename = null_name;
		index->flags = EXPORT_ROOT;
		index->ex_index = ac->ex;
		index->fd = NULL;

		n = MAX_CHAIN;
		strcpy(buf1, index->fullname);
		do {
			r = Fxattr(1, buf1, &stats);
			if (r < 0)
			{
				/* an error occured, so skip this entry */
				free(index);
				break;
			}

			if (S_ISLNK(stats.st_mode))
			{
				r = Freadlink(sizeof(buf2), buf2, buf1);
				if (r != 0)
				{
					free(index);
					break;
				}
				strcpy(buf1, buf2);
				n -= 1;
			}
		} while (n && S_ISLNK(stats.st_mode));

		if (r != 0)
			continue;

		index->fh.dev = stats.st_dev;
		index->fh.root_inode = stats.st_ino;  /* this is an exported root */
		index->fh.inode = stats.st_ino;
		index->fh.n_ino = 0;

		/* insert the device and inode number also into the access list */
		ac->dev = stats.st_dev;
		ac->inode = stats.st_ino;

		if (!S_ISDIR(stats.st_mode))
			index->flags |= INDEX_IS_FILE;

		/* Now look if we are on a file system without proper inode numbers.
		 * So stat the file again and check the inode number for equality.
		 */
		r = Fxattr(0, index->fullname, &stats);
		if (r < 0)
		{
			/* an error occured, so skip this entry */
			free(index);
			continue;
		}

		if (stats.st_ino != index->fh.inode)
		{
			index->flags |= IS_ON_TOSFS;   /* oh shit! No proper inodes */
		}

		index->accnext = NULL;
		index->accprev = NULL;
		index->prev = NULL;
		index->next = exported;
		exported = index;
		if (index->next)
			index->next->prev = index;
	}
}




/* These are some utility functions: we keep a doubly linked list of
 * index structures and update after each access. This way we know
 * that elements at the end of it are least recently used.
 */

/* unlink from acc list */
static void
fh_unlink(NFSD_CACHE *index)
{
	if (index->accprev)
		index->accprev->accnext = index->accnext;
	if (index->accnext)
		index->accnext->accprev = index->accprev;
	if (acc_list == index)
		acc_list = index->accnext;
	if (last_acc == index)
		last_acc = index->accprev;
	index->accprev = index->accnext = NULL;
}


/* move an entry to the front of the list */
static void
fh_tofront(NFSD_CACHE *index)
{
	if (index->accnext || index->accprev)
		fh_unlink(index);
	if (last_acc == NULL)
	{
		last_acc = acc_list = index;
		index->accprev = index->accnext = NULL;
	}
	else
	{
		index->accprev = last_acc;
		index->accnext = NULL;
		last_acc->accnext = index;
		last_acc = index;
	}
}



void
fh_delete(NFSD_CACHE *index)
{
	FILE_DESCR *fd;

	if (!index)
		return;
 	fd = index->fd;
	if (fd)
	{
		if (fd->flags & DSCR_OPEN)
			if (fd->flags & DSCR_FILE)
				(void) Fclose(fd->handle.fh);
			else
				(void) Dclosedir(fd->handle.dirh);
		fd->flags &= ~DSCR_OPEN;
		fd->f_holder = NULL;
	}
	if (index->flags & EXPORT_ROOT)
		return;
	if (index->fullname)
		free(index->fullname);
	index->fullname = index->filename = NULL;
	index->fd = NULL;
	index->flags = 0;
	index->fh.inode = -1;
	if (index->prev)
		index->prev->next = index->next;
	if (index->next)
		index->next->prev = index->prev;
	index->next = NULL;
	index->prev = NULL;

	fh_unlink(index);

	/* Now insert it at the free end of the acc list. */
	index->accnext = acc_list;
	acc_list->accprev = index;
	index->accprev = NULL;
	acc_list = index;
}



/* When we delete a file or directory we might still have an index describing
 * that thing, so we have to search through the indices and look for it.
 * That way we can make sure that we don't have an open descriptor for it
 * when we really delete it.
 *
 * NOTE: the implementation is not optimal, we make much too much string
 *       comparisons here. We could use the access path in the nfs fh.
 */
int
fh_delete_by_name(NFSD_CACHE *dir, char *name)
{
	int i;
	NFSD_CACHE *index = NULL;
	char buf[MAXPATHLEN+1];
	char *p, *s;

	/* prepare the fullname */
	p = buf;
	s = dir->fullname;
	while (*s)     /* copy without trailing 0 */
		*p++ = *s++;
	*p++ = PATH_SEP;
	s = name;
	while ((*p++ = *s++))  ;   /* copy with trailing 0 */

	for (i = 0;  i < NINDEX;  i++)
	{
		if ( (file_index[i].fh.dev == dir->fh.dev) &&
		     (file_index[i].fh.root_inode == dir->fh.root_inode) )
			if (!strcmp(file_index[i].filename, name))
				if (!strcmp(buf, file_index[i].fullname))
				{
					index = &file_index[i];
					break;
				}
	}

	if (index)
		fh_delete(index);

	return 0;
}



/* The parameter is a file index that must not be reused, we will need
 * access to it later, so it has to remain valid.
 * If there is already an index for the file, just return it.
 */
static NFSD_CACHE *
get_index(NFSD_CACHE *forbidden, u_long ino)
{
	NFSD_CACHE *index, *hash;

	/* First look if we alreay have an index for this file */
	hash = &file_index[HASH_INDEX(ino)];
	while (hash)
	{
		if (hash->fh.inode == ino)
			return hash;
		hash = hash->next;
	}

	/* We have to get a new index */
	if (forbidden && acc_list == forbidden)
	{
		index = acc_list->accnext;
	}
	else
	{
		index = acc_list;
	}
	if (index)
		fh_delete(index);
	else
		return NULL;

	/* Now index should be at the end of the acc_list. So remove it there
	 * and return it.
	 */
	index = acc_list;
	if (index->fullname)
	{
		/* BUG: internal inconsistency, what should we do???? */
		return NULL;
	}

	acc_list = index->accnext;
	if (acc_list)
		acc_list->accprev = NULL;
	index->accnext = NULL;

	/* Here we have index pointing to a free nfsd index. So link it into the
	 * hash chain according to the inode number.
	 */
	hash = &file_index[HASH_INDEX(ino)];
	if (hash == index)
	{
		/* Nothing to be done here, as no collision occurs */
		return index;
	}
	index->next = hash->next;
	hash->next = index;
	index->prev = hash;
	if (index->next)
		index->next->prev = index;

	return index;
}



/* This function works only on file systems with real inode numbers. It
 * takes as parameter the index of the exported root on which the file
 * is expected to be.
 */
static char *
fh_buildpath(svc_fh *handle, NFSD_CACHE *root_index)
{
	char path[MAXPATHLEN+1], *p;
	long dirh;
	long pos_stack[MAX_INODE+1];
	char *slash_stack[MAX_INODE+1];
	int i, j;
	struct stat stats;
	struct dbuf
	{
		long ino;
		char name[MAXPATHLEN+1];
	} dbuf;


	strcpy(path, root_index->fullname);
	slash_stack[0] = path;
	pos_stack[0] = 0;
	slash_stack[1] = &path[strlen(path)];
	pos_stack[1] = 0;
	if (MANGLE_INO(root_index->fh.inode) != handle->ino[0])
		return NULL;

	for (i = 1; i <= handle->n_ino; )
	{
		dirh = Dopendir(path, 0);
		if ((dirh & 0xff000000) == 0xff000000)
			goto ascend;

		for (j = 0;  j < pos_stack[i];  j++)
			Dreaddir(sizeof(dbuf), dirh, &dbuf);

		while (Dreaddir(sizeof(dbuf), dirh, &dbuf) == 0)
		{
			pos_stack[i]++;

			/* If we read "." or "..", just skip it */
			if (!strcmp(dbuf.name, ".") || !strcmp(dbuf.name, ".."))
				continue;

			if (strlen(path) + strlen(dbuf.name) + 1 > MAXPATHLEN)
				continue;

			*(slash_stack[i]) = '\0';
			strcat(path, PATH_SEP_STR);
			strcat(path, dbuf.name);

			if (Fxattr(1, path, &stats) != 0)
				continue;

			if (i == handle->n_ino)
			{
				if (stats.st_ino == handle->inode)
				{
					/* we got it! */
					Dclosedir(dirh);
					p = malloc(strlen(path)+1);
					if (p) strcpy(p, path);
					return p;
				}
			}
			else if (MANGLE_INO(stats.st_ino) == handle->ino[i] &&
				 (S_ISDIR(stats.st_mode) ||
				  S_ISLNK(stats.st_mode)))
			{
				Dclosedir(dirh);
				++i;
				slash_stack[i] = &path[strlen(path)];
				pos_stack[i] = 0;
				goto descend;
			}
		}
ascend:
		Dclosedir(dirh);
		if (--i < 1) return NULL;
		*(slash_stack[i]) = '\0';
descend:
	}
	return NULL;
}



/* This function must deal with getting a valid index for a given nfs file
 * handle, which might be out of cache. For a file system with proper inodes
 * that is not a serious problem, as we can search for the inode number.
 * But if we do not have such numbers (like on MiNT's current TOSFS) we
 * have no chance to get the file back. In that case, we have to say that
 * the file handle is stale even if the file still exists.
 *
 * KLUDGE: for files on a TOS file system we cannot use the inode, as
 *         the mount daemon and the nfs daemon are different processes
 *         and have therefore different inode numbers for the same
 *         file.
 *         So the inode number contains the number of the exported file.
 */
NFSD_CACHE *
fh_find(svc_fh *handle)
{
	char *path;
	NFSD_CACHE *index, *newi;
	long r, root_inode;
	int i;

	if (handle->n_ino == 0)  /* this one must be exported */
	{
		for (index = exported;  index;  index = index->next)
			if (index->fh.dev == handle->dev && index->fh.inode == handle->inode)
			{
				return index;
			}

		/* Here we know that this handle is stale if it is on a file system
		 * that supports real inode numbers and does not fill in some random
		 * crap there. But we want to check for a TOSFS, so we cannot return
		 * yet.
		 */
		root_inode = MANGLE_INO(handle->inode);
	}
	else
	{
		i = HASH_INDEX(handle->inode);
		for (index = &file_index[i];  index;  index = index->next)
			if (index->fh.dev == handle->dev && index->fh.inode == handle->inode)
			{
			 	return index;
			}
		root_inode = handle->ino[0]; /* this is mangled! */
	}

	/* Now look for an export root with the same inode number as in
	 * the access path given. By the way check if the exporting device
	 * is a TOSFS...
	 */
	for (index = exported;  index;  index = index->next)
	{
		if (index->fh.dev == handle->dev && (index->flags & IS_ON_TOSFS))
		{
			NFSD_CACHE *ni;
			long i;

			/* Only export root handles, please */
			if (handle->n_ino > 0)
				return NULL;

			i = handle->inode;
			for (ni = exported;  ni;  ni = ni->next, i--)
			{
				if (i == 0)
				{
					if (ni->fh.dev == handle->dev)
						return ni;
				}
			}
			return NULL;
		}

		if (index->fh.dev == handle->dev &&
		     root_inode == MANGLE_INO(index->fh.inode))
		{
			struct stat stats;

			path = fh_buildpath(handle, index);
			if (!path)
				continue;

			/* All we have to do here is to install a new index for that file
			 * and return it.
			 */
			newi = get_index(NULL, handle->inode);
			if (!newi)
			{
				free(path);
				return NULL;
			}
			if((r = Fxattr(0, path, &stats)) < 0)
			{
				fh_delete(newi);
				free(path);
				return NULL;
			}

			fh_tofront(newi);
			if (newi->fullname)
			{
				free(path);
				return newi;
			}
			newi->fullname = path;
			newi->fh = *handle;
			newi->ex_index = index->ex_index;
			newi->fd = NULL;
			if (S_ISREG(stats.st_mode))
				newi->flags = INDEX_IS_FILE;
			else
				newi->flags = 0;
			newi->atime = get_timestamp();

			newi->filename = strrchr(newi->fullname, PATH_SEP);
			if (newi->filename)
				newi->filename++;
			else
				newi->filename = newi->fullname;

			return newi;
		}
	}
	return NULL;
}



/* Install a new file index representating the file name in the directory
 * dir.
 */
NFSD_CACHE *
fh_new(NFSD_CACHE *dir, char *name, struct stat *st)
{
	long r;
	char *p, *s, *fullname, *filename;
	NFSD_CACHE *index;
	struct stat stats;

	if (!st)
		st = &stats;

	fullname = malloc(strlen(dir->fullname)+1+strlen(name)+1);
	if (!fullname)
		return NULL;

	/* Make the fullname for the new file and set up the stripped file name
	 * correctly.
	 * NOTE: make sure that no path component is ".." or "."
	 */
	p = fullname;
	s = dir->fullname;
	while (*s)
		*p++ = *s++;
	if (!strcmp(name, "."))
	{
		*p = 0;
		p = strrchr(fullname, PATH_SEP);
		if (!p)
		{
			free(fullname);
			return NULL;
		}
		filename = p+1;
	}
	else if (!strcmp(name, ".."))
	{
		/* We have to check here, if the access to .. is not an access to
		 * the parent directory of an exported root.
		 */
		if (strlen(fullname) <= strlen(dir->fullname))
			return NULL;
		*p = 0;
		p = strrchr(fullname, PATH_SEP);
		if (!p)
		{
			free(fullname);
			return NULL;
		}
		*p = 0;
		p = strrchr(fullname, PATH_SEP);
		if (!p)
		{
			free(fullname);
			return NULL;
		}
		filename = p+1;
	}
	else
	{
		*p++ = PATH_SEP;
		filename = p;
		s = name;
		while ((*p++ = *s++))   ;
	}

	r = Fxattr(1, fullname, st);
	if (r < 0)
	{
		free(fullname);
		return NULL;
	}

	index = get_index(dir, st->st_ino);
	if (!index)
	{
		free(fullname);
		return NULL;
	}

	fh_tofront(index);

	if (index->fullname)
	{
		free(index->fullname);
	}

	index->fullname = fullname;
	index->filename = filename;
	index->flags = 0;
	index->fd = NULL;
	index->ex_index = dir->ex_index;

	if (st->st_mode & S_IFDIR)
		index->flags &= ~INDEX_IS_FILE;
	else
		index->flags |= INDEX_IS_FILE;

	/* Set up nfs file handle */
	index->fh = dir->fh;   /* copy old one and make some changes */
	index->fh.inode = st->st_ino;
	if (!strcmp(name, ".."))
	{
		if (index->fh.n_ino < MAX_INODE)
			index->fh.n_ino--;
		else
		{
			/* BUG: how do we determine the length of the inode chain? */
		}
	}
	else if (strcmp(name, ".") != 0)
		if (index->fh.n_ino < MAX_INODE)   /* make access path */
			index->fh.ino[index->fh.n_ino++] = MANGLE_INO(dir->fh.inode);

	return index;
}



/* This function is only used by the mount daemon to generate nfs file 
 * handles for exported files.
 */
int
fh_create(char *pathname, svc_fh *handle)
{
	NFSD_CACHE *index, *ni;
	char buf[MAXPATHLEN+1];
	long i, r;
	char *p;
	struct stat st;

	if (path2abs(pathname, buf) != 0)
		return -1;           /* some serious error occured */

	for (index = exported;  index;  index = index->next)
	{
		if (!strncmp(buf, index->fullname, strlen(index->fullname)))
			break;
	}

	if (index)
	{
		*handle = index->fh;
		if (index->flags & IS_ON_TOSFS)
		{
			/* KLUDGE: for TOSFS, the inode number contains the number of the
			 *         index in the exported list. (see also fucntion fh_find)
			 */
			i = 0;
			for (ni = exported;  ni;  ni = ni->next)
			{
				if (ni == index)
					break;
				i += 1;
			}
			if (!ni)
				return -1;
			handle->inode = i;
		}

		/* now go down the rest of the path given to make a handle for the
		 * actual directory
		 */
		p = buf+strlen(index->fullname);
		if (!*p)
		{
			return 0;
		}
		if (*p != PATH_SEP)
		{
			/* the exported diretory is only a prefix of the desired one,
			 * so return an error.
			 */
			return -1;
		}
		r = Fxattr(1, buf, &st);
		if (r < 0)
			return -1;
		handle->inode = st.st_ino;
		while (p && *p)
		{
			char c = *p;
			*p = '\0';

			r = Fxattr(0, buf, &st);
			*p = c;

			if (r < 0)
				return -1;
			if (handle->n_ino < MAX_INODE)
				handle->ino[handle->n_ino++] = MANGLE_INO(st.st_ino);
			p = strchr(p+1, PATH_SEP);
		}
		return 0;
	}
	return -1;
}




/**************************************************************************
 * The following two functions deal with file descriptors which represent
 * a given nfsd index for the file system. The descriptor array is used
 * as a LRU cache so that normaly the most recent accessed file have
 * already an open descriptor.
 **************************************************************************/



/* Get a file descriptor slot for an already open file handle, so that we
 * can do a create more efficiently.
 */
FILE_DESCR *
new_fd(NFSD_CACHE *index, int o_mode, int handle)
{
	int i, least, max;
	FILE_DESCR *fd, *list;
	long least_ac;

	if (index->fd && (index == index->fd->f_holder))
	{
		fd = index->fd;
		goto found;
	}
	if (index->flags & INDEX_IS_FILE)
	{
		max = NFILEHANDLE;
		list = file_handle;
	}
	else
		return NULL;

/* look for an unused file handle and get least recent access time */
	fd = NULL;
	least_ac = list[0].atime;
	least = 0;
	for (i = 0;  i < max;  i++)
	{
		if (!(list[i].flags & DSCR_OPEN))     /* this is not used */
		{
			fd = &list[i];
			fd->open_mode = -1;
			goto found;
		}
		if (after(least_ac,list[i].atime))
		{
			least_ac = list[i].atime;
			least = i;
		}
	}

/* look for the least recent used entry and close it */
	if (!fd)
	{
		fd = &list[least];    /* we know it was open! */
		if (fd->flags & DSCR_FILE)
			(void) Fclose(fd->handle.fh);
		fd->flags &= ~DSCR_OPEN;
		fd->open_mode = -1;
	}

found:
	if (fd->f_holder && (fd->f_holder != index))
		fd->f_holder->fd = NULL;
	fd->f_holder = index;     /* new owner is this one */
	if (index->flags & INDEX_IS_FILE)
	{
		fd->flags |= DSCR_FILE;
		fd->handle.fh = handle;
		fd->flags |= DSCR_OPEN;
		fd->open_mode = o_mode;
		fd->position = 0;
	}
	return fd;
}



/* Get a file descriptor for the given index. As we cache some handles, it
 * is possible that the system runs out of file or directory handles. Then
 * a file handle in use will be reused.
 */
FILE_DESCR *
index_to_fd(NFSD_CACHE *index, int o_mode)
{
	int i, least, max;
	FILE_DESCR *fd, *list;
	long least_ac, r;

	if (index->fd && (index == index->fd->f_holder))
	{
		fd = index->fd;
		goto found;
	}
	if (index->flags & INDEX_IS_FILE)
	{
		max = NFILEHANDLE;
		list = file_handle;
	}
	else
	{
		max = NDIRHANDLE;
		list = dir_handle;
	}

/* look for an unused file handle and get least recent access time */
	fd = NULL;
	least_ac = list[0].atime;
	least = 0;
	for (i = 0;  i < max;  i++)
	{
		if (!(list[i].flags & DSCR_OPEN))     /* this is not used */
		{
			fd = &list[i];
			fd->open_mode = -1;
			goto found;
		}
		if (after(least_ac,list[i].atime))
		{
			least_ac = list[i].atime;
			least = i;
		}
	}

/* look for the least recent used entry and close it */
	if (!fd)
	{
		fd = &list[least];    /* we know it was open! */
		if (fd->flags & DSCR_FILE)
			(void) Fclose(fd->handle.fh);
		else
			(void) Dclosedir(fd->handle.dirh);
		fd->flags &= ~DSCR_OPEN;
		fd->open_mode = -1;
	}

found:
	if (fd->f_holder && (fd->f_holder != index))
		fd->f_holder->fd = NULL;
	if (index->flags & INDEX_IS_FILE)
	{
		fd->flags |= DSCR_FILE;
		if (fd->open_mode != o_mode)
		{
			if (fd->flags & DSCR_OPEN)
				(void) Fclose(fd->handle.fh);

			r = Fopen(index->fullname, o_mode|O_DENYNONE);
			if (r < 0)
				return NULL;
			fd->handle.fh = r;
			fd->flags |= DSCR_OPEN;
			fd->open_mode = o_mode;
			fd->position = 0;
			fd->f_holder = index;     /* new owner is this one */
		}
	}
	else
	{
		fd->flags &= ~DSCR_FILE;
		if (fd->flags & DSCR_OPEN)
			(void) Dclosedir(fd->handle.dirh);

		r = Dopendir(index->fullname, 0);
		if ((r & 0xff000000) == 0xff000000)
			return NULL;

		fd->handle.dirh = r;
		fd->flags |= DSCR_OPEN;
		fd->position = 0;
		fd->f_holder = index;     /* new owner is this one */
	}
	return fd;
}



void
invalidate_fd_cache(long curr_time, FILE_DESCR *fd, int n)
{
	int i;

	for (i = 0;  i < n;  i++)
	{
		if (fd->f_holder &&
		     !after(curr_time, fd->f_holder->atime+FLUSH_TIME*TICKS_PER_SEC))
		{
			if (fd->flags & DSCR_OPEN)
				if (fd->flags & DSCR_FILE)
				{
					(void) Fclose(fd->handle.fh);
				}
				else
				{
					(void) Dclosedir(fd->handle.dirh);
				}
			fd->flags &= ~DSCR_OPEN;
			if (fd->f_holder)
			{
				fd->f_holder->fd = NULL;
				fd->f_holder = NULL;
			}
		}
		fd += 1;
	}
}


static void
init_handles()
{
	int i;

	for (i = 0;  i < NFILEHANDLE;  i++)
	{
		file_handle[i].f_holder = NULL;
		file_handle[i].flags = DSCR_FILE;
		file_handle[i].handle.fh = -1;
		file_handle[i].atime = -1;
	}
	for (i = 0;  i < NDIRHANDLE;  i++)
	{
		dir_handle[i].f_holder = NULL;
		dir_handle[i].flags = 0;
		dir_handle[i].handle.dirh = 0L;
		dir_handle[i].atime = -1;
	}
}



void
fh_update(NFSD_CACHE *index)
{
	long stamp = get_timestamp();

	if (index)
	{
		index->atime = stamp;
		if (index->fd)
			index->fd->atime = stamp;
		if (!(index->flags & EXPORT_ROOT))
		{
			fh_tofront(index);
		}
	}
	if (flush_descr_cache)
	{
		invalidate_fd_cache(stamp, file_handle, NFILEHANDLE);
		invalidate_fd_cache(stamp, dir_handle, NDIRHANDLE);
		flush_descr_cache = 0;
	}
}



static void
do_alarm(int sig)
{
	flush_descr_cache = 1;
	Talarm(FLUSH_TIME);
}




void
fh_init()
{
	init_handles();
	init_indices();
	signal(SIGALRM, do_alarm);
	Talarm(FLUSH_TIME);
}


void
fh_reinit()
{
	long stamp;
	NFSD_CACHE *index;

	/* make sure that the descriptor cache is flushed completely */
	stamp = get_timestamp() + FLUSH_TIME*TICKS_PER_SEC;
	flush_descr_cache = 1;
	fh_update(NULL);

	/* Now free the list of exported roots */
	index = exported;
	while (index)
	{
		exported = index->next;
		free(index->fullname);
		free(index);
		index = exported;
	}
	exported = NULL;

	/* now do the init stuff for the indices again */
	init_handles();
	init_indices();
}
