/*
 * fh		This module handles the file-handle cache.
 *		FILE HANDLE PACKAGE FOR USER-LEVEL NFS SERVER
 *
 *		Interfaces:
 *		    pseudo_inode
 *			mostly used internally, but also called from unfsd.c
 *			when reporting directory contents.
 *		    fh_init
 *			Initializes the queues and 'flush' timer
 *		    fh_pr
 *			debugging primitive; converts file handle into a
 *			printable text string
 *		    fh_create
 *			establishes initial file handle; called from mount
 *			daemon
 *		    fh_path
 *			returns unix path corresponding to fh
 *		    fh_fd
 *			returns open file descriptor for given file handle;
 *			provides caching of open files
 *		    fd_idle
 *			provides mututal exclusion of normal file descriptor
 *			cache use, and alarm-driven cache flushing
 *		    fh_compose
 *			construct new file handle from existing file handle
 *			and directory entry
 *		    fh_psi
 *			returns pseudo_inode corresponding to file handle
 *		    fh_remove (new, by Don Becker)
 *			delete the file handle associated with PATH from the
 *			cache
 *
 * Authors:	Mark A. Shand, May 1988
 *		Donald J. Becker <becker@super.org>
 *		Rick Sladkey <jrs@world.std.com>
 *		Patrick	Sweeney <pjs@raster.Kodak.COM>
 *		Orest Zborowski <obz@raster.Kodak.COM>
 *		Fred N. van Kempen, <waltje@uWalt.NL.Mugnet.ORG>
 *
 *		Copyright 1988 Mark A. Shand
 *		This software maybe be used for any purpose provided
 *		the above copyright notice is retained.  It is supplied
 *		as is, with no warranty expressed or implied.
 */

#include "nfsd.h"

#define hash_psi(psi) (((psi)^((psi)>>8)^((psi)>>16)^((psi)>>24)) & 0xff)

static mutex ex_state = inactive;
static mutex io_state = inactive;
static fhcache fh_head, fh_tail, *last_flushable;
static fhcache *fh_hashed[HASH_TAB_SIZE];
static int fh_list_size;
static time_t curtime;

#ifndef FOPEN_MAX
#define FOPEN_MAX 256
#endif

static fhcache *fd_cache[FOPEN_MAX] = { NULL };
static int fd_cache_size = 0;

#ifndef NFSERR_INVAL			/* that Sun forgot */
#define NFSERR_INVAL	22
#endif

struct {
	enum nfsstat error;
	int errno;
} nfs_errtbl[]= {
	{ NFS_OK,		0		},
	{ NFSERR_PERM,		EPERM		},
	{ NFSERR_NOENT,		ENOENT		},
	{ NFSERR_IO,		EIO		},
	{ NFSERR_NXIO,		ENXIO		},
	{ NFSERR_ACCES,		EACCES		},
	{ NFSERR_EXIST,		EEXIST		},
	{ NFSERR_NODEV,		ENODEV		},
	{ NFSERR_NOTDIR,	ENOTDIR		},
	{ NFSERR_ISDIR,		EISDIR		},
	{ NFSERR_INVAL,		EINVAL		},
	{ NFSERR_FBIG,		EFBIG		},
	{ NFSERR_NOSPC,		ENOSPC		},
	{ NFSERR_ROFS,		EROFS		},
	{ NFSERR_NAMETOOLONG,	ENAMETOOLONG	},
	{ NFSERR_NOTEMPTY,	ENOTEMPTY	},
#ifdef EDQUOT
	{ NFSERR_DQUOT,		EDQUOT		},
#endif
	{ NFSERR_STALE,		ESTALE		},
	{ NFSERR_WFLUSH,	EIO		},
	{ -1,			EIO		}
};
int fh_initialized = 0;

/* Forward declared local functions */
static _PRO(int path_psi, (char *, nfsstat *, struct stat *, struct stat *));
static _PRO(void flush_cache, (int));

void mallocfailed()
{
	dprintf(0, "malloc failed -- exiting\n");
	exit(1);
}

static void fh_move_to_front(fhc)
fhcache *fhc;
{
	/* Remove from current posn */
	fhc->prev->next = fhc->next;
	fhc->next->prev = fhc->prev;

	/* Insert at head */
	fhc->prev = &fh_head;
	fhc->next = fh_head.next;
	fhc->prev->next = fhc;
	fhc->next->prev = fhc;
}

static void fh_inserthead(fhc)
fhcache *fhc;
{
	register fhcache **hash_slot;

	/* Insert at head. */
	fhc->prev = &fh_head;
	fhc->next = fh_head.next;
	fhc->prev->next = fhc;
	fhc->next->prev = fhc;
	fh_list_size++;

	/* Insert into hash tab. */
	hash_slot = &(fh_hashed[fhc->h.psi % HASH_TAB_SIZE]);
	fhc->hash_next = *hash_slot;
	*hash_slot = fhc;
}

static fhcache *fh_lookup(psi)
u_long psi;
{
	register fhcache *fhc;

	fhc = fh_hashed[psi % HASH_TAB_SIZE];
	while (fhc != NULL && fhc->h.psi != psi)
		fhc = fhc->hash_next;
	return (fhc);
}

static void fh_close(fhc)
fhcache *fhc;
{
	if (fhc->fd >= 0) {
		dprintf(1, "fh_close: closing handle %x ('%s', fd=%d)\n",
		      fhc, fhc->path ? fhc->path : "<unnamed>", fhc->fd);
#if 0
		DBASSERT(fd_cache[fhc->fd] == fhc);
#endif
		fd_cache[fhc->fd] = NULL;
		--fd_cache_size;
		close(fhc->fd);
		fhc->fd = -1;
	}
}

static void fh_delete(fhc)
fhcache *fhc;
{
	register fhcache **hash_slot;

	dprintf(1, "fh_delete: deleting handle %x ('%s', fd=%d)\n", fhc,
		fhc->path ? fhc->path : "<unnamed>", fhc->fd);

	/* Remove from current posn */
	fhc->prev->next = fhc->next;
	fhc->next->prev = fhc->prev;
	fh_list_size--;

	/* Remove from hash tab */
	hash_slot = &(fh_hashed[fhc->h.psi % HASH_TAB_SIZE]);
	while (*hash_slot != NULL && *hash_slot != fhc)
		hash_slot = &((*hash_slot)->hash_next);
	if (*hash_slot == NULL)
		dprintf(0, "internal inconsistency -- fhc(%x) not in hash table\n",
			fhc);
	else
		*hash_slot = fhc->hash_next;

	fh_close(fhc);

	/* Free storage. */
	if (fhc->path != NULL)
		free(fhc->path);
	free(fhc);
}

/* Lookup an NFS error code and return UNIX equivalent. */
enum nfsstat nfs_errno()
{
	int i;

	for (i = 0; nfs_errtbl[i].error != -1; i++) {
		if (nfs_errtbl[i].errno == errno)
			return (nfs_errtbl[i].error);
	}
	dprintf(0, "non-standard errno: %d (%s)\n", errno, strerror(errno));
	return (NFSERR_IO);
}

/*
 * INODES and DEVICES.  NFS assumes that each file within an NFS mounted
 * file-system has a unique inode number.  Thus to mount an entire file
 * hierarchy, as this server sets out to do, requires mapping from inode/devs
 * to pseudo-inode.  Furthermore mount points must be detected and so that
 *	pseudo-inode("name") == pseudo-inode(direntry("name/../name"))
 * One option is to force the directory entry inode to correspond to the
 * result of the stat call, but this involves stat'ing every directory entry
 * during a readdir.  Instead we force the stat call to corresopnd to the
 * directory entry inode (see inner_getattr).  Of course this technique
 * requires that the parent directory is readable.  If it is not the normal
 * stat call result is used.  There is no chance of conflict because the
 * directory can never be read.
 *
 * In theory unique pseudo-inodes cannot be guaranteed, since inode/dev
 * contains 48 bits of information which must be crammed into an inode
 * number constrained to 32 bits.  Fortunately inodes numbers tend to be
 * small (often < 64k, almost always < 512k)
 */
int pseudo_inode(inode, dev)
u_long inode;
u_short dev;
{
	register dmajor, dminor;

	/*
         * Assuming major and minor numbers are small integers,
         * gravitate bits of dmajor & dminor device number to
         * high-order bits of word, to avoid clash with real inode num.
         */
	/* reverse (byte-wise) */
	dmajor = ((dev & 0xf0f) << 4) | ((dev & 0xf0f0) >> 4);
	dmajor = ((dmajor & 0x3333) << 2) | ((dmajor & 0xcccc) >> 2);
	dmajor = ((dmajor & 0x5555) << 1) | ((dmajor & 0xaaaa) >> 1);

	/* spread low-16 -> 32 with 0's in even posn */
	dmajor = ((dmajor & 0xff00) << 8) | (dmajor & 0xff);
	dmajor = ((dmajor & 0xf000f0) << 4) | (dmajor & 0xf000f);
	dmajor = ((dmajor & 0xc0c0c0c) << 2) | (dmajor & 0x3030303);
	dmajor = ((dmajor & 0x22222222) << 1) | (dmajor & 0x11111111);
	dminor = (dmajor & 0x5555) << 15;
	dmajor = dmajor & 0x55550000;

	dprintf(1, "pseudo_inode: dev=%d, inode=%d, psi=%d\n", dev, inode,
		(dmajor | dminor) ^ inode);
	return ((dmajor | dminor) ^ inode);
}

static char *fh_buildpath(h)
svc_fh *h;
{
	int i;
	int psi;
	char *path;
	struct stat sbuf;
	char pathbuf[PATH_MAX + NAME_MAX + 1];
	long cookie_stack[HP_LEN + 1];
	char *slash_stack[HP_LEN];

	if (stat("/", &sbuf) < 0)
		return (NULL);
	psi = pseudo_inode(sbuf.st_ino, sbuf.st_dev);
	if (h->hash_path[0] == 0) {
		if (psi != h->psi)
			return (NULL);
		if ((path = malloc(sizeof ("/"))) == NULL)
			mallocfailed();
		strcpy(path, "/");
		return (path);
	}
	/* else */
	if (hash_psi(psi) != h->hash_path[1])
		return (NULL);
	strcpy(pathbuf, "/");
	cookie_stack[2] = 0;
	for (i = 2; i <= h->hash_path[0] + 1; i++) {
		DIR *dir;
		struct dirent *dp;

	backtrack:
		if (stat(pathbuf, &sbuf) >= 0
		    && (dir = opendir(pathbuf)) != NULL) {
			if (cookie_stack[i] != 0)
				seekdir(dir, cookie_stack[i]);
			while ((dp = readdir(dir))) {
				if (strcmp(dp->d_name, ".") != 0
				    && strcmp(dp->d_name, "..") != 0) {
					psi = pseudo_inode(dp->d_ino, sbuf.st_dev);
					if (i == h->hash_path[0] + 1) {
						if (psi == h->psi) {
							/*GOT IT*/
							strcat(pathbuf, dp->d_name);
							if ((path = malloc(strlen(pathbuf) + 1)) == NULL)
								mallocfailed();
							strcpy(path, pathbuf);
							closedir(dir);
							return (path);
						}
					} else {
						if (hash_psi(psi) == h->hash_path[i]) {
							/*PERHAPS WE'VE GOT IT */
							cookie_stack[i] = telldir(dir);
							cookie_stack[i + 1] = 0;
							slash_stack[i] = pathbuf + strlen(pathbuf);
							strcpy(slash_stack[i], dp->d_name);
							strcat(pathbuf, "/");

							closedir(dir);
							goto deeper;
						}
					}
				}
			}
			/* dp == NULL */
			closedir(dir);
		} else if (i <= h->hash_path[0] && access(pathbuf, R_OK) != 0
			   && access(pathbuf, X_OK) == 0) {

			/*
			 * Execute-only directory?  Maybe its in the cache.
			 * Note: cache is frozen for duration of fh_buildpath.
			 */
			svc_fh xh;
			fhcache *fhc;

			xh = *h;
			xh.hash_path[0] = i - 1;
			if (cookie_stack[i] == 0)
				fhc = fh_head.next;
			else
				fhc = ((fhcache *) (cookie_stack[i]))->next;
			while (fhc != &fh_tail) {
				if (memcmp(xh.hash_path, fhc->h.hash_path, i) == 0
				    && xh.hash_path[i] == hash_psi(fhc->h.psi))
					break;
				else
					fhc = fhc->next;
			}
			if (fhc != NULL) {
				strcpy(pathbuf, fhc->path);
				cookie_stack[i] = (long) fhc;
				cookie_stack[i + 1] = 0;
				slash_stack[i] = strrchr(pathbuf, '/') + 1;
				strcat(pathbuf, "/");
				goto deeper;
			}
		}
		/* shallower */
		i--;
		if (i < 2)
			return (NULL);	/* SEARCH EXHAUSTED */

		/* Prune path */
		*(slash_stack[i]) = '\0';
		goto backtrack;
	deeper:
		;
	}
	return (NULL);		/* actually not reached */
}

static int path_psi(path, status, sbp, tsbp)
char *path;
nfsstat *status;
struct stat *sbp;
struct stat *tsbp;
{
	struct stat sbuf;

	if (sbp == NULL)
		sbp = &sbuf;
	if (lstat(path, sbp) < 0) {
		*status = nfs_errno();
		return (0);
	}
	if (tsbp)
		*tsbp = *sbp;
	if (S_ISDIR(sbp->st_mode) && strcmp(path, "/") != 0) {
		/* Special case for directories--test for mount point. */
		struct stat ddbuf;
		char *sindx;
		char *fname;
		char squirrel;

		/* Find start of last component of path. */
		if ((sindx = strrchr(path, '/')) == path) {
			sindx++;
			fname = sindx;
		} else
			fname = sindx + 1;

		/* Remove last element of path. */
		squirrel = *sindx;
		*sindx = '\0';
		if (lstat(path, &ddbuf) < 0) {
			*sindx = squirrel;
			*status = nfs_errno();
			return (0);
		}
		/* Sindx now points to directory entry name. */
		if (ddbuf.st_dev != sbp->st_dev) {
			/* Directory is a mount point. */
			DIR *dirp;
			struct dirent *dp;

			errno = 0;
			if ((dirp = opendir(path)) == NULL) {
				*sindx = squirrel;	/* restore path */
				if (errno == EACCES)
					goto unreadable;
				if (errno != 0)
					*status = nfs_errno();
				else
					*status = NFSERR_NOENT;
			} else {
				*sindx = squirrel;	/* restore path */
				*status = NFS_OK;
				do {
					if ((dp = readdir(dirp)) == NULL) {
						*status = NFSERR_NOENT;
						closedir(dirp);
						return (0);
					}
				} while (strcmp(fname, dp->d_name) != 0);
				sbp->st_dev = ddbuf.st_dev;
				sbp->st_ino = dp->d_ino;
				closedir(dirp);
			}
		} else
			*sindx = squirrel;	/* restore path */
	unreadable:
		;
	}
	return (pseudo_inode(sbp->st_ino, sbp->st_dev));
}

fhcache *fh_find(h, create)
svc_fh *h;
int create;
{
	char buff[1024], *sp;
	register fhcache *fhc;

	sprintf(buff, "fh_find: psi=%lu... ", (unsigned long) h->psi);
	sp = buff + strlen(buff);
	ex_state = active;
	time(&curtime);
	while ((fhc = fh_lookup(h->psi)) != NULL) {
		sprintf(sp, "found '%s', fd=%d\n", fhc->path ? fhc->path : "<unnamed>",
			fhc->fd);
		dprintf(1, "%s", buff);

		/* But what if hash_paths are not the same? Something is stale. */
		if (memcmp(h->hash_path, fhc->h.hash_path, HP_LEN) != 0) {
			dprintf(1, "fh_find: path mismatch - stale!\n");
			if (!create) {
				ex_state = inactive;
				return (NULL);
			}
			fh_delete(fhc);
			dprintf(1, "fh_find: deleted old handle... sortof\n");
			break;
		}
		if (fhc != fh_head.next)
			fh_move_to_front(fhc);
		fhc->last_used = curtime;
		ex_state = inactive;
		return (fhc);
	}

	dprintf(1, "not found.\n");
	if (fh_list_size > CACHE_SIZE_LIMIT) {
		/* Don't flush current head. */
		while (last_flushable != fh_head.next) {
			if ((last_flushable->flags & FHC_XONLY_PATH) == 0) {
				fhc = last_flushable;
				last_flushable = last_flushable->prev;
				fh_delete(fhc);
				break;
			}
			last_flushable = last_flushable->prev;
		}
		last_flushable = last_flushable->next;
	}
	if ((fhc = (fhcache *) malloc(sizeof *fhc)) == NULL)
		mallocfailed();
	if (create)
		fhc->path = NULL;
	else {
		/* attempt to contruct from hash_path */
		char *path;

		if ((path = fh_buildpath(h)) == NULL) {
			free(fhc);
			ex_state = inactive;
			return NULL;
		}
		fhc->path = path;
	}
	fhc->flags = 0;
	fhc->fd = -1;
	fhc->last_used = curtime;
	fhc->h = *h;
	fh_inserthead(fhc);
	dprintf(1, "fh_find: created new handle %x ('%s')\n", fhc,
		fhc->path ? fhc->path : "<unnamed>");
	ex_state = inactive;
	if (fh_list_size > HIWAT_CACHE_SIZE)
		flush_cache(0);
	return (fhc);
}

static int fh_flush_fds()
{
	int i;
	fhcache *discard;

	if (io_state == active) {
		dprintf(1, "fh_flush_fds: not flushing... io active\n");
		return (-1);
	}
	while (fd_cache_size > FD_CACHE_LIMIT) {
		discard = NULL;
		for (i = 0; i < FOPEN_MAX; ++i) {
			if (fd_cache[i] && (discard == NULL ||
			    fd_cache[i]->last_used < discard->last_used))
				discard = fd_cache[i];
		}
		if (discard == NULL) {
			dprintf(1, "fh_flush_fds: can't find oldest??\n");
			return (-1);
		}
		dprintf(1, "fh_flush_fds: discarding old handle %x\n", discard);
		fh_close(discard);
	}
	return (0);
}

/*
 * flush_cache() is invoked periodically from SIGALRM, and on
 * demand from fh_find.  A simple form of mutual exclusion
 * protects this routine from multiple concurrent executions.
 * Since the preemption that occurs when a signal is received
 * is one-sided, we do need an atomic test and set.  If the
 * signal arrives between the test and the set, the first
 * invocation safely stalls until the signal-caused invocation
 * completes.
 */
static void flush_cache(sig)
int sig;
{
	register fhcache *h;

#ifdef DEBUG
	time_t now;
	time(&now);
	dprintf(1, "flushing cache at %s: state = %s\n", ctime(&now),
		(ex_state == inactive) ? "inactive" : "active");
#endif

	if (ex_state == inactive) {
		int cache_size = 0;

		ex_state = active;
		time(&curtime);
		/* Single execution thread */

		/* works in empty case because: fh_tail.next = &fh_tail */
		h = fh_head.next;
		while (h != &fh_tail) {
			if (cache_size > LOWAT_CACHE_SIZE
			    || (cache_size > CACHE_SIZE_LIMIT
				&& (h->flags & FHC_XONLY_PATH) == 0)
			    || curtime > h->last_used + DISCARD_INTERVAL
			    || sig == SIGHUP) {
				h = h->next;
				fh_delete(h->prev);
			} else {
				cache_size++;
				h = h->next;
			}
		}
		if (fh_list_size != cache_size)
			dprintf(0, "internal inconsistency (fh_list_size=%d) != (cache_size=%d)\n",
				fh_list_size, cache_size);
		fh_list_size = cache_size;
		ex_state = inactive;
		if (_rpcpmstart) {
			signal(SIGALRM, flush_cache);
			alarm(FLUSH_INTERVAL);
		}
	} else {
		if (!_rpcpmstart) {
			signal(SIGALRM, flush_cache);
			alarm(BUSY_RETRY_INTERVAL);
		}
	}
}

void fh_init()
{
	if (fh_initialized)
		return;
	fh_initialized = 1;

	fh_head.next = fh_tail.next = &fh_tail;
	fh_head.prev = fh_tail.prev = &fh_head;
	last_flushable = &fh_tail;
	fh_tail.flags = FHC_XONLY_PATH;
	signal(SIGHUP, flush_cache);
	if (!_rpcpmstart) {
		signal(SIGALRM, flush_cache);
		alarm(FLUSH_INTERVAL);
	}
}

char *fh_pr(fh)
nfs_fh *fh;
{
	char *p;
	nfsstat status;

	p = fh_path(fh, &status);
	if (status != NFS_OK)
		return ("///STALE///");
	else
		return (p);
}

/*
 * This routine is only used by the mount daemon.
 * It creates the initial file handle.
 */
int fh_create(fh, path)
nfs_fh *fh;
char *path;
{
	svc_fh *key = (svc_fh *) fh;
	fhcache *h;
	int psi;
	nfsstat status;
	char *s;

	memset((char *) fh, 0, sizeof (nfs_fh));
	key->hash_path[0] = 0;
	status = NFS_OK;
	if ((psi = path_psi("/", &status, NULL, NULL)) == 0)
		return ((int) status);
	s = path;
	while ((s = strchr(s + 1, '/')) != NULL) {
		if (++(key->hash_path[0]) >= HP_LEN)
			return ((int) NFSERR_NAMETOOLONG);
		key->hash_path[key->hash_path[0]] = hash_psi(psi);
		*s = '\0';
		if ((psi = path_psi(path, &status, NULL, NULL)) == 0)
			return ((int) status);
		*s = '/';
	}
	if (*(strrchr(path, '/') + 1) != '\0') {
		if (++(key->hash_path[0]) >= HP_LEN)
			return ((int) NFSERR_NAMETOOLONG);
		key->hash_path[key->hash_path[0]] = hash_psi(psi);
		if ((psi = path_psi(path, &status, NULL, NULL)) == 0)
			return ((int) status);
	}
	key->psi = psi;
	h = fh_find(key, 1);

	/* assert(h != NULL); */
	if (h->path == NULL) {
		h->fd = -1;
		if ((h->path = malloc(strlen(path) + 1)) == NULL)
			mallocfailed();
		strcpy(h->path, path);
		h->flags = 0;
	}
	return ((int) status);
}

char *fh_path(fh, status)
nfs_fh *fh;
nfsstat *status;
{
	fhcache *h;

	if ((h = fh_find((svc_fh *) fh, 0)) == NULL) {
		*status = NFSERR_STALE;
		return (NULL);
	}
	*status = NFS_OK;
	return (h->path);
}

int path_open(path, omode, perm)
char *path;
int omode;
int perm;
{
	int fd;
	int oerrno, euid;
	struct stat buf;

	fh_flush_fds();

	fd = open(path, omode, perm);

	oerrno = errno;

	/* The file must exist at this point. */
	if (lstat(path, &buf) < 0) {
		oerrno = oerrno;
		return -1;
	}

	/* Emulate SunOS NFS server for I/O requests on non-files. */
	if (!S_ISREG(buf.st_mode)) {
		errno = EISDIR;
		return -1;
	}

	/* Do some serious cheating for statelessness. */
	if (fd < 0) {
		if (oerrno == EACCES && (buf.st_uid == (euid = geteuid())
		    || (omode == O_RDONLY && (buf.st_mode & S_IXOTH)))) {
			seteuid(0);
			fd = open(path, omode, perm);
			oerrno = errno;
			seteuid(euid);
		}
	}

	errno = oerrno;
	return (fd);
}

int fh_fd(fh, status, omode)
nfs_fh *fh;
nfsstat *status;
int omode;
{
	fhcache *h;

	if ((h = fh_find((svc_fh *) fh, 0)) == NULL) {
		*status = NFSERR_STALE;
		return (-1);
	}
	if (h->fd >= 0) {
		if (h->omode == omode ||
		    ((omode == O_RDONLY || omode == O_WRONLY) && h->omode == O_RDWR)) {
			dprintf(1, "fh_fd: reusing fd=%d\n", h->fd);
#if 0
			DBASSERT(fd_cache[h->fd] == h);
#endif
			return (h->fd);
		}
		dprintf(1, "fh_fd: mismatch in omode (%d wanted, %d cached)\n",
			omode, h->omode);
		fh_close(h);
	}
	errno = 0;
	if (!h->path)
		return (-1);	/* something is really hosed */
	if ((h->fd = path_open(h->path, omode, 0)) >= 0) {
		io_state = active;
		h->omode = omode;
#if 0
		DBASSERT(fd_cache[h->fd] == NULL);
#endif
		fd_cache[h->fd] = h;
		++fd_cache_size;
		dprintf(1, "fh_fd: new open as fd=%d\n", h->fd);
	} else {
		dprintf(1, "fh_fd: open failed.\n");
		*status = nfs_errno();
	}
	return (h->fd);
}

void fd_inactive(fd)
int fd;
{
	io_state = inactive;
}

nfsstat fh_compose(dopa, new_fh, sbpp, fd, omode)
diropargs *dopa;
nfs_fh *new_fh;
struct stat **sbpp;
int fd;
int omode;
{
	svc_fh *key;
	fhcache *dirh, *h;
	char *sindx;
	int is_dd;
	nfsstat ret;
	struct stat tsbuf;
	char pathbuf[PATH_MAX + NAME_MAX + 1];

	if ((dirh = fh_find((svc_fh *) & (dopa->dir), 0)) == NULL)
		return (NFSERR_STALE);

	/* This allows only single directories to be looked up, could be
	   a bit more sophisticated, but i don't know if that is neccesary */
	if (strchr(dopa->name, '/') != NULL)
		return(NFSERR_ACCES);

	/* Construct path */
	if (strcmp(dopa->name, ".") == 0) {
		*new_fh = dopa->dir;
		*sbpp = NULL;
		return (NFS_OK);
	}
	if (strcmp(dopa->name, "..") == 0) {
		is_dd = 1;
		sindx = strrchr(dirh->path, '/');
		if (sindx == dirh->path)
			strcpy(pathbuf, "/");
		else {
			int len = sindx - dirh->path;
			strncpy(pathbuf, dirh->path, len);
			pathbuf[len] = '\0';
		}
		if (client_authenticate) {
			if (auth_clnt(svc_rqstp, pathbuf) == NULL)
				return (NFSERR_ACCES);
		}
	}
	else if (!re_export && (dirh->flags & FHC_NFSMOUNTED))
		return (NFSERR_NOENT);
	else {
		int len = strlen(dirh->path);

		is_dd = 0;
		if (dirh->path[len - 1] == '/')
			len--;
		strncpy(pathbuf, dirh->path, len);
		pathbuf[len] = '/';
		strcpy(pathbuf + (len + 1), dopa->name);
	}

	*new_fh = dopa->dir;
	key = (svc_fh *) new_fh;
	if ((key->psi = path_psi(pathbuf, &ret, *sbpp, &tsbuf)) == 0)
		return (ret);

	if (is_dd)
		key->hash_path[key->hash_path[0]--] = 0;
	else {
		if (++(key->hash_path[0]) >= HP_LEN)
			return (NFSERR_NAMETOOLONG);
		key->hash_path[key->hash_path[0]] = hash_psi(dirh->h.psi);
	}
	h = fh_find(key, 1);

	/* New code added by Don Becker */
	if ((h->path != NULL) && (strcmp(h->path, pathbuf) != 0)) {
		/* We must have cached an old file under the same inode number. */
		fh_delete(h);
		h = fh_find(key, 1);
		if (h->path)
			dprintf(0, "Internal inconsistency: double entry (path '%s', now '%s').\n",
				h->path, pathbuf);
	}
	dprintf(1, "fh_compose: using  handle %x ('%s', fd=%d)\n",
		h, h->path ? h->path : "<unnamed>", h->fd);
	/* End of new code */

	/* assert(h != NULL); */
	if (h->path == 0) {
		if ((h->path = malloc(strlen(pathbuf) + 1)) == NULL)
			mallocfailed();
		strcpy(h->path, pathbuf);
		h->flags = 0;
		if (!re_export && nfsmounted(pathbuf, &tsbuf))
			h->flags |= FHC_NFSMOUNTED;
		if (!is_dd && access(dirh->path, R_OK) != 0 &&
		    access(dirh->path, X_OK) == 0)
			h->flags |= FHC_XONLY_PATH;
		dprintf(1, "fh_compose: +using  handle %x ('%s', fd=%d)\n",
			h, h->path ? h->path : "<unnamed>", h->fd);
	}
	if (fd >= 0) {
		dprintf(1, "fh_compose: handle %x using passed fd %d\n", h, fd);
#if 0
		DBASSERT(fd_cache[fd] == NULL);
#endif
		if (h->fd >= 0)
			fh_close(h);
		h->fd = fd;
		fd_cache[fd] = h;
		++fd_cache_size;
		dprintf(1, "fh_compose: +using  handle %x ('%s', fd=%d)\n",
			h, h->path ? h->path : "<unnamed>", h->fd);
	}
	if (omode >= 0)
		h->omode = omode;
	return (NFS_OK);
}

int fh_psi(fh)
nfs_fh *fh;
{
	svc_fh *h = (svc_fh *) fh;
	return (h->psi);
}

void fh_remove(path)
char *path;
{
	int psi;
	nfsstat status;
	fhcache *fhc;

	psi = path_psi(path, &status, NULL, NULL);
	if (psi == 0)
		return;
	ex_state = active;
	fhc = fh_lookup(psi);
	if (fhc != NULL)
		fh_delete(fhc);

	ex_state = inactive;
	return;
}
