patch-2.2.18 linux/fs/nfsd/nfsfh.c

Next file: linux/fs/nfsd/nfsproc.c
Previous file: linux/fs/nfsd/nfsctl.c
Back to the patch index
Back to the overall index

diff -u --new-file --recursive --exclude-from /usr/src/exclude v2.2.17/fs/nfsd/nfsfh.c linux/fs/nfsd/nfsfh.c
@@ -5,6 +5,7 @@
  *
  * Copyright (C) 1995, 1996 Olaf Kirch <okir@monad.swb.de>
  * Portions Copyright (C) 1999 G. Allen Morris III <gam3@acm.org>
+ * Extensive rewrite by Neil Brown <neilb@cse.unsw.edu.au> Southern-Spring 1999
  */
 
 #include <linux/sched.h>
@@ -22,334 +23,50 @@
 #define NFSDDBG_FACILITY		NFSDDBG_FH
 #define NFSD_PARANOIA 1
 /* #define NFSD_DEBUG_VERBOSE 1 */
-/* #define NFSD_DEBUG_VERY_VERBOSE 1 */
 
-extern unsigned long max_mapnr;
-
-#define NFSD_FILE_CACHE 0
-#define NFSD_DIR_CACHE  1
-struct fh_entry {
-	struct dentry * dentry;
-	unsigned long reftime;
-	ino_t	ino;
-	kdev_t	dev;
-};
-
-#define NFSD_MAXFH \
-  (((nfsd_nservers + 1) >> 1) * PAGE_SIZE/sizeof(struct fh_entry))
-static struct fh_entry *filetable = NULL;
-static struct fh_entry *dirstable = NULL;
 
 static int nfsd_nr_verified = 0;
 static int nfsd_nr_put = 0;
-static unsigned long nfsd_next_expire = 0;
-
-static int add_to_fhcache(struct dentry *, int);
-struct dentry * lookup_inode(kdev_t, ino_t, ino_t);
-
-static LIST_HEAD(fixup_head);
-static LIST_HEAD(path_inuse);
-static int nfsd_nr_fixups = 0;
-static int nfsd_nr_paths = 0;
-#define NFSD_MAX_PATHS 500
-#define NFSD_MAX_FIXUPS 500
-#define NFSD_MAX_FIXUP_AGE 30*HZ
-
-struct nfsd_fixup {
-	struct list_head lru;
-	unsigned long reftime;
-	ino_t	dirino;
-	ino_t	ino;
-	kdev_t	dev;
-	ino_t	new_dirino;
-};
-
-struct nfsd_path {
-	struct list_head lru;
-	unsigned long reftime;
-	int	users;
-	ino_t	ino;
-	kdev_t	dev;
-	char	name[1];
-};
-
-static struct nfsd_fixup *
-find_cached_lookup(kdev_t dev, ino_t dirino, ino_t ino)
-{
-	struct list_head *tmp = fixup_head.next;
-
-	for (; tmp != &fixup_head; tmp = tmp->next) {
-		struct nfsd_fixup *fp;
-
-		fp = list_entry(tmp, struct nfsd_fixup, lru);
-#ifdef NFSD_DEBUG_VERY_VERBOSE
-printk("fixup %lu %lu, %lu %lu %s %s\n",
-        fp->ino, ino,
-	fp->dirino, dirino,
-	kdevname(fp->dev), kdevname(dev));
-#endif
-		if (fp->ino != ino)
-			continue;
-		if (fp->dirino != dirino)
-			continue;
-		if (fp->dev != dev)
-			continue;
-		fp->reftime = jiffies;	
-		list_del(tmp);
-		list_add(tmp, &fixup_head);
-		return fp;
-	}
-	return NULL;
-}
-
-/*
- * Save the dirino from a rename.
- */
-void
-add_to_rename_cache(ino_t new_dirino,
-                    kdev_t dev, ino_t dirino, ino_t ino)
-{
-	struct nfsd_fixup *fp;
-
-	if (dirino == new_dirino)
-		return;
-
-	fp = find_cached_lookup(dev, 
-				dirino,
-				ino);
-	if (fp) {
-		fp->new_dirino = new_dirino;
-		return;
-	}
-
-	/*
-	 * Add a new entry. The small race here is unimportant:
-	 * if another task adds the same lookup, both entries
-	 * will be consistent.
-	 */
-	fp = kmalloc(sizeof(struct nfsd_fixup), GFP_KERNEL);
-	if (fp) {
-		fp->dirino = dirino;
-		fp->ino = ino;
-		fp->dev = dev;
-		fp->new_dirino = new_dirino;
-		list_add(&fp->lru, &fixup_head);
-		nfsd_nr_fixups++;
-	}
-}
-
-/*
- * Save the dentry pointer from a successful lookup.
- */
-
-static void free_fixup_entry(struct nfsd_fixup *fp)
-{
-	list_del(&fp->lru);
-#ifdef NFSD_DEBUG_VERY_VERBOSE
-printk("free_rename_entry: %lu->%lu %lu/%s\n",
-		fp->dirino,
-		fp->new_dirino,
-		fp->ino,
-		kdevname(fp->dev),
-		(jiffies - fp->reftime));
-#endif
-	kfree(fp);
-	nfsd_nr_fixups--;
-}
-
-/*
- * Copy a dentry's path into the specified buffer.
- */
-static int copy_path(char *buffer, struct dentry *dentry, int namelen)
-{
-	char *p, *b = buffer;
-	int result = 0, totlen = 0, len; 
-
-	while (1) {
-		struct dentry *parent;
-		dentry = dentry->d_covers;
-		parent = dentry->d_parent;
-		len = dentry->d_name.len;
-		p = (char *) dentry->d_name.name + len;
-		totlen += len;
-		if (totlen > namelen)
-			goto out;
-		while (len--)
-			*b++ = *(--p);
-		if (dentry == parent)
-			break;
-		dentry = parent;
-		totlen++;
-		if (totlen > namelen)
-			goto out;
-		*b++ = '/';
-	}
-	*b = 0;
-
-	/*
-	 * Now reverse in place ...
-	 */
-	p = buffer;
-	while (p < b) {
-		char c = *(--b);
-		*b = *p;
-		*p++ = c;
-	} 
-	result = 1;
-out:
-	return result;
-}
-
-/*
- * Add a dentry's path to the path cache.
- */
-static int add_to_path_cache(struct dentry *dentry)
-{
-	struct inode *inode = dentry->d_inode;
-	struct dentry *this;
-	struct nfsd_path *new;
-	int len, result = 0;
-
-#ifdef NFSD_DEBUG_VERBOSE
-printk("add_to_path_cache: caching %s/%s\n",
-dentry->d_parent->d_name.name, dentry->d_name.name);
-#endif
-	/*
-	 * Get the length of the full pathname.
-	 */
-restart:
-	len = 0;
-	this = dentry;
-	while (1) {
-		struct dentry *parent;
-		this = this->d_covers;
-		parent = this->d_parent;
-		len += this->d_name.len;
-		if (this == parent)
-			break;
-		this = parent;
-		len++;
-	}
-	/*
-	 * Allocate a structure to hold the path.
-	 */
-	new = kmalloc(sizeof(struct nfsd_path) + len, GFP_KERNEL);
-	if (new) {
-		new->users = 0;	
-		new->reftime = jiffies;	
-		new->ino = inode->i_ino;
-		new->dev = inode->i_dev;
-		result = copy_path(new->name, dentry, len);
-		if (!result)
-			goto retry;
-		list_add(&new->lru, &path_inuse);
-		nfsd_nr_paths++;
-#ifdef NFSD_DEBUG_VERBOSE
-printk("add_to_path_cache: added %s, paths=%d\n", new->name, nfsd_nr_paths);
-#endif
-	}
-	return result;
-
-	/*
-	 * If the dentry's path length changed, just try again.
-	 */
-retry:
-	kfree(new);
-	printk(KERN_DEBUG "add_to_path_cache: path length changed, retrying\n");
-	goto restart;
-}
-
-/*
- * Search for a path entry for the specified (dev, inode).
- */
-static struct nfsd_path *get_path_entry(kdev_t dev, ino_t ino)
-{
-	struct nfsd_path *pe;
-	struct list_head *tmp;
 
-	for (tmp = path_inuse.next; tmp != &path_inuse; tmp = tmp->next) {
-		pe = list_entry(tmp, struct nfsd_path, lru);
-		if (pe->ino != ino)
-			continue;
-		if (pe->dev != dev)
-			continue;
-		list_del(tmp);
-		list_add(tmp, &path_inuse);
-		pe->users++;
-		pe->reftime = jiffies;
-#ifdef NFSD_PARANOIA
-printk("get_path_entry: found %s for %s/%ld\n", pe->name, kdevname(dev), ino);
-#endif
-		return pe;
-	}
-	return NULL;
-}
-
-static void put_path(struct nfsd_path *pe)
-{
-	pe->users--;
-}
-
-static void free_path_entry(struct nfsd_path *pe)
-{
-	if (pe->users)
-		printk(KERN_DEBUG "free_path_entry: %s in use, users=%d\n",
-			pe->name, pe->users);
-	list_del(&pe->lru);
-	kfree(pe);
-	nfsd_nr_paths--;
-}
 
 struct nfsd_getdents_callback {
-	struct nfsd_dirent *dirent;
-	ino_t dirino;		/* parent inode number */
-	int found;		/* dirent inode matched? */
+	struct qstr *name;	/* name that was found. name->name already points to a buffer */
+	unsigned long ino;	/* the inum we are looking for */
+	int found;		/* inode matched? */
 	int sequence;		/* sequence counter */
 };
 
-struct nfsd_dirent {
-	ino_t ino;		/* preset to desired entry */
-	int len;
-	char name[256];
-};
-
 /*
- * A rather strange filldir function to capture the inode number
- * for the second entry (the parent inode) and the name matching
- * the specified inode number.
+ * A rather strange filldir function to capture
+ * the name matching the specified inode number.
  */
-static int filldir_one(void * __buf, const char * name, int len, 
+static int filldir_one(void * __buf, const char * name, int len,
 			off_t pos, ino_t ino)
 {
 	struct nfsd_getdents_callback *buf = __buf;
-	struct nfsd_dirent *dirent = buf->dirent;
+	struct qstr *qs = buf->name;
+	char *nbuf = (char*)qs->name; /* cast is to get rid of "const" */
 	int result = 0;
 
 	buf->sequence++;
-#ifdef NFSD_DEBUG_VERY_VERBOSE
-printk("filldir_one: seq=%d, ino=%lu, name=%s\n", buf->sequence, ino, name);
+#ifdef NFSD_DEBUG_VERBOSE
+dprintk("filldir_one: seq=%d, ino=%ld, name=%s\n", buf->sequence, ino, name);
 #endif
-	if (buf->sequence == 2) {
-		buf->dirino = ino;
-		goto out;
-	}
-	if (dirent->ino == ino) {
-		dirent->len = len;
-		memcpy(dirent->name, name, len);
-		dirent->name[len] = '\0';
+	if (buf->ino == ino) {
+		qs->len = len;
+		memcpy(nbuf, name, len);
+		nbuf[len] = '\0';
 		buf->found = 1;
 		result = -1;
 	}
-out:
 	return result;
 }
 
 /*
- * Read a directory and return the parent inode number and the name
- * of the specified entry. The dirent must be initialized with the
- * inode number of the desired entry.
+ * Read a directory and return the name of the specified entry.  
+ * i_sem is already down().
  */
-static int get_parent_ino(struct dentry *dentry, struct nfsd_dirent *dirent)
+static int get_ino_name(struct dentry *dentry, struct qstr *name, unsigned long ino)
 {
 	struct inode *dir = dentry->d_inode;
 	int error;
@@ -372,15 +89,13 @@
 	if (!file.f_op->readdir)
 		goto out_close;
 
-	buffer.dirent = dirent;
-	buffer.dirino = 0;
+	buffer.name = name;
+	buffer.ino = ino;
 	buffer.found = 0;
 	buffer.sequence = 0;
 	while (1) {
 		int old_seq = buffer.sequence;
-		down(&dir->i_sem);
 		error = file.f_op->readdir(&file, &buffer, filldir_one);
-		up(&dir->i_sem);
 		if (error < 0)
 			break;
 
@@ -391,7 +106,6 @@
 		if (old_seq == buffer.sequence)
 			break;
 	}
-	dirent->ino = buffer.dirino;
 
 out_close:
 	if (file.f_op->release)
@@ -400,716 +114,380 @@
 	return error;
 }
 
-/*
- * Look up a dentry given inode and parent inode numbers.
- *
- * This relies on the ability of a Unix-like filesystem to return
- * the parent inode of a directory as the ".." (second) entry.
- *
- * This could be further optimized if we had an efficient way of
- * searching for a dentry given the inode: as we walk up the tree,
- * it's likely that a dentry exists before we reach the root.
+/* this should be provided by each filesystem in an nfsd_operations interface as
+ * iget isn't really the right interface
  */
-struct dentry * lookup_inode(kdev_t dev, ino_t dirino, ino_t ino)
+static struct dentry *nfsd_iget(struct super_block *sb, unsigned long ino, __u32 generation)
 {
-	struct super_block *sb;
-	struct dentry *root, *dentry, *result;
-	struct inode *dir;
-	char *name;
-	unsigned long page;
-	ino_t root_ino;
-	int error;
-	struct nfsd_dirent dirent;
 
-	result = ERR_PTR(-ENOMEM);
-	page = __get_free_page(GFP_KERNEL);
-	if (!page)
-		goto out;
-
-	/*
-	 * Get the root dentry for the device.
-	 */
-	result = ERR_PTR(-ENOENT);
-	sb = get_super(dev);
-	if (!sb)
-		goto out_page;
-	root = dget(sb->s_root);
-	root_ino = root->d_inode->i_ino; /* usually 2 */
-
-	name = (char *) page + PAGE_SIZE;
-	*(--name) = 0;
-
-	/*
-	 * Walk up the tree to construct the name string.
-	 * When we reach the root inode, look up the name
-	 * relative to the root dentry.
+	/* 
+	 * ext2fs' read_inode has been strengthed to return a bad_inode if 
+	 * the inode had been deleted.
+	 *
+	 * Currently we don't know the generation for parent directory, 
+	 * so a generation of 0 means "accept any"
 	 */
-	while (1) {
-		if (ino == root_ino) {
-			if (*name == '/')
-				name++;
-			/*
-			 * Note: this dput()s the root dentry.
-			 */
-			result = lookup_dentry(name, root, 0);
-			break;
-		}
-
-		/*
-		 *  Fix for /// bad export bug: if dirino is the root,
-		 *  get the real root dentry rather than creating a temporary
-		 *  "root" dentry.  XXX We could extend this to use
-		 *  any existing dentry for the located 'dir', but all
-		 *  of this code is going to be completely rewritten soon,
-		 *  so I won't bother. 
-		 */
-
-		if (dirino == root_ino) {
-			dentry = dget(root);
-		}
-		else {
-			result = ERR_PTR(-ENOENT);
-			dir = iget_in_use(sb, dirino);
-			if (!dir)
-				goto out_root;
-			dentry = d_alloc_root(dir, NULL);
-			if (!dentry)
-				goto out_iput;
-		}
-
-		/*
-		 * Get the name for this inode and the next parent inode.
-		 */
-		dirent.ino = ino;
-		error = get_parent_ino(dentry, &dirent);
-		result = ERR_PTR(error);
-		dput(dentry);
-		if (error)
-			goto out_root;
-		/*
-		 * Prepend the name to the buffer.
-		 */
-		result = ERR_PTR(-ENAMETOOLONG);
-		name -= (dirent.len + 1);
-		if ((unsigned long) name <= page)
-			goto out_root;
-		memcpy(name + 1, dirent.name, dirent.len);
-		*name = '/';
-
-		/*
-		 * Make sure we can't get caught in a loop ...
-		 */
-		if (dirino == dirent.ino && dirino != root_ino) {
-			printk(KERN_DEBUG 
-			       "lookup_inode: looping?? (ino=%ld, path=%s)\n",
-				dirino, name);	
-			goto out_root;
-		}
-		ino = dirino;
-		dirino = dirent.ino;
-	}
-
-out_page:
-	free_page(page);
-out:
-	return result;
-
-	/*
-	 * Error exits ...
-	 */
-out_iput:
-	result = ERR_PTR(-ENOMEM);
-	iput(dir);
-out_root:
-	dput(root);
-	goto out_page;
-}
-
-/*
- * Find an entry in the cache matching the given dentry pointer.
- */
-static struct fh_entry *find_fhe(struct dentry *dentry, int cache,
-				struct fh_entry **empty)
-{
-	struct fh_entry *fhe;
-	int i, found = (empty == NULL) ? 1 : 0;
-
-	if (!dentry)
-		goto out;
-
-	fhe = (cache == NFSD_FILE_CACHE) ? &filetable[0] : &dirstable[0];
-	for (i = 0; i < NFSD_MAXFH; i++, fhe++) {
-		if (fhe->dentry == dentry) {
-			fhe->reftime = jiffies;
-			return fhe;
-		}
-		if (!found && !fhe->dentry) {
-			found = 1;
-			*empty = fhe;
-		}
-	}
-out:
-	return NULL;
-}
-
-/*
- * Expire a cache entry.
- */
-static void expire_fhe(struct fh_entry *empty, int cache)
-{
-	struct dentry *dentry = empty->dentry;
-
-#ifdef NFSD_DEBUG_VERBOSE
-printk("expire_fhe: expiring %s %s/%s, d_count=%d, ino=%lu\n",
-(cache == NFSD_FILE_CACHE) ? "file" : "dir",
-dentry->d_parent->d_name.name, dentry->d_name.name, dentry->d_count,empty->ino);
-#endif
-	empty->dentry = NULL;	/* no dentry */
-	/*
-	 * Add the parent to the dir cache before releasing the dentry,
-	 * and check whether to save a copy of the dentry's path.
-	 */
-	if (dentry != dentry->d_parent) {
-		struct dentry *parent = dget(dentry->d_parent);
-		if (add_to_fhcache(parent, NFSD_DIR_CACHE))
-			nfsd_nr_verified++;
-		else
-			dput(parent);
-		/*
-		 * If we're expiring a directory, copy its path.
-		 */
-		if (cache == NFSD_DIR_CACHE) {
-			add_to_path_cache(dentry);
-		}
-	}
-	dput(dentry);
-	nfsd_nr_put++;
-}
-
-/*
- * Look for an empty slot, or select one to expire.
- */
-static void expire_slot(int cache)
-{
-	struct fh_entry *fhe, *empty = NULL;
-	unsigned long oldest = -1;
-	int i;
-
-	fhe = (cache == NFSD_FILE_CACHE) ? &filetable[0] : &dirstable[0];
-	for (i = 0; i < NFSD_MAXFH; i++, fhe++) {
-		if (!fhe->dentry)
-			goto out;
-		if (fhe->reftime < oldest) {
-			oldest = fhe->reftime;
-			empty = fhe;
-		}
-	}
-	if (empty)
-		expire_fhe(empty, cache);
-
-out:
-	return;
-}
-
-/*
- * Expire any cache entries older than a certain age.
- */
-static void expire_old(int cache, int age)
-{
-	struct fh_entry *fhe;
-	int i;
-
-#ifdef NFSD_DEBUG_VERY_VERBOSE
-printk("expire_old: expiring %s older than %d\n",
-(cache == NFSD_FILE_CACHE) ? "file" : "dir", age);
-#endif
-	fhe = (cache == NFSD_FILE_CACHE) ? &filetable[0] : &dirstable[0];
-	for (i = 0; i < NFSD_MAXFH; i++, fhe++) {
-		if (!fhe->dentry)
-			continue;
-		if ((jiffies - fhe->reftime) > age)
-			expire_fhe(fhe, cache);
-	}
-
-	/*
-	 * Trim the fixup cache ...
-	 */
-	while (nfsd_nr_fixups > NFSD_MAX_FIXUPS) {
-		struct nfsd_fixup *fp;
-		fp = list_entry(fixup_head.prev, struct nfsd_fixup, lru);
-		if ((jiffies - fp->reftime) < NFSD_MAX_FIXUP_AGE)
-			break;
-		free_fixup_entry(fp);
-	}
-
-	/*
-	 * Trim the path cache ...
-	 */
-	while (nfsd_nr_paths > NFSD_MAX_PATHS) {
-		struct nfsd_path *pe;
-		pe = list_entry(path_inuse.prev, struct nfsd_path, lru);
-		if (pe->users)
-			break;
-		free_path_entry(pe);
-	}
-}
-
-/*
- * Add a dentry to the file or dir cache.
- *
- * Note: As NFS file handles must have an inode, we don't accept
- * negative dentries.
- */
-static int add_to_fhcache(struct dentry *dentry, int cache)
-{
-	struct fh_entry *fhe, *empty = NULL;
-	struct inode *inode = dentry->d_inode;
-
+	struct inode *inode;
+	struct list_head *lp;
+	struct dentry *result;
+	inode = iget_in_use(sb, ino);
 	if (!inode) {
-#ifdef NFSD_PARANOIA
-printk("add_to_fhcache: %s/%s rejected, no inode!\n",
-dentry->d_parent->d_name.name, dentry->d_name.name);
-#endif
-		return 0;
-	}
+		dprintk("nfsd_iget: failed to find ino: %lu on %s\n",
+			ino, bdevname(sb->s_dev));
+		return ERR_PTR(-ESTALE);
+	}
+	if (is_bad_inode(inode)
+	    || (generation && inode->i_generation != generation)
+		) {
+		/* we didn't find the right inode.. */
+		dprintk("fh_verify: Inode %lu, Bad count: %d %d or version  %u %u\n",
+			inode->i_ino,
+			inode->i_nlink, inode->i_count,
+			inode->i_generation,
+			generation);
 
-repeat:
-	fhe = find_fhe(dentry, cache, &empty);
-	if (fhe) {
-		return 0;
+		iput(inode);
+		return ERR_PTR(-ESTALE);
 	}
-
-	/*
-	 * Not found ... make a new entry.
+	/* now to find a dentry.
+	 * If possible, get a well-connected one
 	 */
-	if (empty) {
-		empty->dentry = dentry;
-		empty->reftime = jiffies;
-		empty->ino = inode->i_ino;
-		empty->dev = inode->i_dev;
-		return 1;
-	}
-
-	/* if nfsd_server is zero, NFSD_MAXFH will be zero too, so
-	 * find_fhe() will NEVER find the file handle NOR an empty space,
-	 * and expire_slot will not be able to expire any file handle,
-	 * because NFSD_MAXFH is zero ... */
-
-	if (nfsd_nservers <= 0) {
-		return 0;
-	}
-
-	expire_slot(cache);
-	goto repeat;
-}
-
-/*
- * Find an entry in the dir cache for the specified inode number.
- */
-static struct fh_entry *find_fhe_by_ino(kdev_t dev, ino_t ino)
-{
-	struct fh_entry * fhe = &dirstable[0];
-	int i;
-
-	for (i = 0; i < NFSD_MAXFH; i++, fhe++) {
-		if (fhe->ino == ino && fhe->dev == dev) {
-			fhe->reftime = jiffies;
-			return fhe;
+	for (lp = inode->i_dentry.next; lp != &inode->i_dentry ; lp=lp->next) {
+		result = list_entry(lp,struct dentry, d_alias);
+		if (! (result->d_flags & DCACHE_NFSD_DISCONNECTED)) {
+			dget(result);
+			iput(inode);
+			return result;
 		}
 	}
-	return NULL;
+	result = d_alloc_root(inode, NULL);
+	if (result == NULL) {
+		iput(inode);
+		return ERR_PTR(-ENOMEM);
+	}
+	result->d_flags |= DCACHE_NFSD_DISCONNECTED;
+	d_rehash(result); /* so a dput won't loose it */
+	return result;
 }
 
-/*
- * Find the (directory) dentry with the specified (dev, inode) number.
- * Note: this leaves the dentry in the cache.
+/* this routine links an IS_ROOT dentry into the dcache tree.  It gains "parent"
+ * as a parent and "name" as a name
+ * It should possibly go in dcache.c
  */
-static struct dentry *find_dentry_by_ino(kdev_t dev, ino_t ino)
+int d_splice(struct dentry *target, struct dentry *parent, struct qstr *name)
 {
-	struct fh_entry *fhe;
-	struct nfsd_path *pe;
-	struct dentry * dentry;
-
-#ifdef NFSD_DEBUG_VERBOSE
-printk("find_dentry_by_ino: looking for inode %ld\n", ino);
-#endif
-	/*
-	 * Special case: inode number 2 is the root inode,
-	 * so we can use the root dentry for the device.
-	 */
-	if (ino == 2) {
-		struct super_block *sb = get_super(dev);
-		if (sb) {
+	struct dentry *tdentry;
 #ifdef NFSD_PARANOIA
-printk("find_dentry_by_ino: getting root dentry for %s\n", kdevname(dev));
-#endif
-			if (sb->s_root) {
-				dentry = dget(sb->s_root);
-				goto out;
-			} else {
+	if (!IS_ROOT(target))
+		printk("nfsd: d_splice with no-root target: %s/%s\n", parent->d_name.name, name->name);
+	if (!(target->d_flags & DCACHE_NFSD_DISCONNECTED))
+		printk("nfsd: d_splice with non-DISCONNECTED target: %s/%s\n", parent->d_name.name, name->name);
+#endif
+	name->hash = full_name_hash(name->name, name->len);
+	tdentry = d_alloc(parent, name);
+	if (tdentry == NULL)
+		return -ENOMEM;
+	d_move(target, tdentry);
+
+	/* tdentry will have been made a "child" of target (the parent of target)
+	 * make it an IS_ROOT instead
+	 */
+	list_del(&tdentry->d_child);
+	tdentry->d_parent = tdentry;
+	d_rehash(target);
+	dput(tdentry);
+
+	/* if parent is properly connected, then we can assert that
+	 * the children are connected, but it must be a singluar (non-forking)
+	 * branch
+	 */
+	if (!(parent->d_flags & DCACHE_NFSD_DISCONNECTED)) {
+		while (target) {
+			target->d_flags &= ~DCACHE_NFSD_DISCONNECTED;
+			parent = target;
+			if (list_empty(&parent->d_subdirs))
+				target = NULL;
+			else {
+				target = list_entry(parent->d_subdirs.next, struct dentry, d_child);
 #ifdef NFSD_PARANOIA
-				printk("find_dentry_by_ino: %s has no root??\n",
-					kdevname(dev));
+				/* must be only child */
+				if (target->d_child.next != &parent->d_subdirs
+				    || target->d_child.prev != &parent->d_subdirs)
+					printk("nfsd: d_splice found non-singular disconnected branch: %s/%s\n",
+					       parent->d_name.name, target->d_name.name);
 #endif
 			}
 		}
 	}
+	return 0;
+}
 
-	/*
-	 * Search the dentry cache ...
-	 */
-	fhe = find_fhe_by_ino(dev, ino);
-	if (fhe) {
-		dentry = dget(fhe->dentry);
-		goto out;
-	}
-	/*
-	 * Search the path cache ...
-	 */
-	dentry = NULL;
-	pe = get_path_entry(dev, ino);
-	if (pe) {
-		struct dentry *res;
-		res = lookup_dentry(pe->name, NULL, 0);
-		if (!IS_ERR(res)) {
-			struct inode *inode = res->d_inode;
-			if (inode && inode->i_ino == ino &&
-				     inode->i_dev == dev) {
-				dentry = res;
-#ifdef NFSD_PARANOIA
-printk("find_dentry_by_ino: found %s/%s, ino=%ld\n",
-dentry->d_parent->d_name.name, dentry->d_name.name, ino);
-#endif
-				if (add_to_fhcache(dentry, NFSD_DIR_CACHE)) {
-					dget(dentry);
-					nfsd_nr_verified++;
-				}
-				put_path(pe);
-			} else {
-				dput(res);
-				put_path(pe);
-				/* We should delete it from the cache. */
-				free_path_entry(pe);
+/* this routine finds the dentry of the parent of a given directory
+ * it should be in the filesystem accessed by nfsd_operations
+ * it assumes lookup("..") works.
+ */
+struct dentry *nfsd_findparent(struct dentry *child)
+{
+	struct dentry *tdentry, *pdentry;
+	tdentry = d_alloc(child, &(const struct qstr) {"..", 2, 0});
+	if (!tdentry)
+		return ERR_PTR(-ENOMEM);
+
+	/* I'm going to assume that if the returned dentry is different, then
+	 * it is well connected.  But nobody returns different dentrys do they?
+	 */
+	pdentry = child->d_inode->i_op->lookup(child->d_inode, tdentry);
+	d_drop(tdentry); /* we never want ".." hashed */
+	if (!pdentry) {
+		/* I don't want to return a ".." dentry.
+		 * I would prefer to return an unconnected "IS_ROOT" dentry,
+		 * though a properly connected dentry is even better
+		 */
+		/* if first or last of alias list is not tdentry, use that
+		 * else make a root dentry
+		 */
+		struct list_head *aliases = &tdentry->d_inode->i_dentry;
+		if (aliases->next != aliases) {
+			pdentry = list_entry(aliases->next, struct dentry, d_alias);
+			if (pdentry == tdentry)
+				pdentry = list_entry(aliases->prev, struct dentry, d_alias);
+			if (pdentry == tdentry)
+				pdentry = NULL;
+			if (pdentry) dget(pdentry);
+		}
+		if (pdentry == NULL) {
+			pdentry = d_alloc_root(igrab(tdentry->d_inode), NULL);
+			if (pdentry) {
+				pdentry->d_flags |= DCACHE_NFSD_DISCONNECTED;
+				d_rehash(pdentry);
 			}
-		} else {
-#ifdef NFSD_PARANOIA
-printk("find_dentry_by_ino: %s lookup failed\n", pe->name);
-#endif
-			put_path(pe);
-			/* We should delete it from the cache. */
-			free_path_entry(pe);
 		}
+		if (pdentry == NULL)
+			pdentry = ERR_PTR(-ENOMEM);
 	}
-out:
-	return dentry;
+	dput(tdentry); /* it is not hashed, it will be discarded */
+	return pdentry;
 }
 
-/*
- * Look for an entry in the file cache matching the dentry pointer,
- * and verify that the (dev, inode) numbers are correct. If found,
- * the entry is removed from the cache.
- */
-static struct dentry *find_dentry_in_fhcache(struct knfs_fh *fh)
+static struct dentry *splice(struct dentry *child, struct dentry *parent)
 {
-/* FIXME: this must use the dev/ino/dir_ino triple. */ 
-#if 0
-	struct fh_entry * fhe;
-
-	fhe = find_fhe(fh->fh_dcookie, NFSD_FILE_CACHE, NULL);
-	if (fhe) {
-		struct dentry *parent, *dentry;
-		struct inode *inode;
-
-		dentry = fhe->dentry;
-		inode = dentry->d_inode;
+	int err = 0;
+	struct qstr qs;
+	char namebuf[256];
+	struct list_head *lp;
+	struct dentry *tmp;
+	/* child is an IS_ROOT (anonymous) dentry, but it is hypothesised that
+	 * it should be a child of parent.
+	 * We see if we can find a name and, if we can - splice it in.
+	 * We hold the i_sem on the parent the whole time to try to follow 
+	 * locking protocols.
+	 */
+	qs.name = namebuf;
+	down(&parent->d_inode->i_sem);
 
-		if (!inode) {
-#ifdef NFSD_PARANOIA
-printk("find_dentry_in_fhcache: %s/%s has no inode!\n",
-dentry->d_parent->d_name.name, dentry->d_name.name);
-#endif
+	/* Now, things might have changed while we waited.
+	 * Possibly a friendly filesystem found child and spliced it in in 
+	 * response to a lookup (though nobody does this yet).  
+	 * In this case, just succeed.
+	 */
+	if (child->d_parent == parent) goto out;
+	/* Possibly a new dentry has been made for this child->d_inode in 
+	 * parent by a lookup.  In this case return that dentry. 
+	 * caller must notice and act accordingly
+	 */
+	for (lp = child->d_inode->i_dentry.next; lp != &child->d_inode->i_dentry ; lp=lp->next) {
+		tmp = list_entry(lp,struct dentry, d_alias);
+		if (tmp->d_parent == parent) {
+			child = dget(tmp);
 			goto out;
 		}
-		if (inode->i_ino != u32_to_ino_t(fh->fh_ino))
-			goto out;
- 		if (inode->i_dev != u32_to_kdev_t(fh->fh_dev))
-			goto out;
-
-		fhe->dentry = NULL;
-		fhe->ino = 0;
-		fhe->dev = 0;
-		nfsd_nr_put++;
-		/*
-		 * Make sure the parent is in the dir cache ...
-		 */
-		parent = dget(dentry->d_parent);
-		if (add_to_fhcache(parent, NFSD_DIR_CACHE))
-			nfsd_nr_verified++;
-		else
-			dput(parent);
-		return dentry;
 	}
-out:
-#endif
-	return NULL;
-}
-
-/*
- * Look for an entry in the parent directory with the specified
- * inode number.
- */
-static struct dentry *lookup_by_inode(struct dentry *parent, ino_t ino)
-{
-	struct dentry *dentry;
-	int error;
-	struct nfsd_dirent dirent;
-
-	/*
-	 * Search the directory for the inode number.
+	/* well, if we can find a name for child in parent, it should be 
+	 * safe to splice it in 
 	 */
-	dirent.ino = ino;
-	error = get_parent_ino(parent, &dirent);
-	if (error) {
-#ifdef NFSD_PARANOIA_EXTREME
-printk("lookup_by_inode: ino %ld not found in %s\n", ino, parent->d_name.name);
-#endif
-		goto no_entry;
-	}
-#ifdef NFSD_PARANOIA_EXTREME
-printk("lookup_by_inode: found %s\n", dirent.name);
-#endif
-
-	dentry = lookup_dentry(dirent.name, parent, 0);
-	if (!IS_ERR(dentry)) {
-		if (dentry->d_inode && dentry->d_inode->i_ino == ino)
-			goto out;
-#ifdef NFSD_PARANOIA_EXTREME
-printk("lookup_by_inode: %s/%s inode mismatch??\n",
-parent->d_name.name, dentry->d_name.name);
-#endif
-		dput(dentry);
-	} else {
-#ifdef NFSD_PARANOIA_EXTREME
-printk("lookup_by_inode: %s lookup failed, error=%ld\n",
-dirent.name, PTR_ERR(dentry));
-#endif
+	err = get_ino_name(parent, &qs, child->d_inode->i_ino);
+	if (err)
+		goto out;
+	tmp = d_lookup(parent, &qs);
+	if (tmp) {
+		/* Now that IS odd.  I wonder what it means... */
+		err = -EEXIST;
+		printk("nfsd-fh: found a name that I didn't expect: %s/%s\n", parent->d_name.name, qs.name);
+		dput(tmp);
+		goto out;
 	}
-
-no_entry:
-	dentry = NULL;
-out:
-	return dentry;
+	err = d_splice(child, parent, &qs);
+	dprintk("nfsd_fh: found name %s for ino %ld\n", child->d_name.name, child->d_inode->i_ino);
+ out:
+	up(&parent->d_inode->i_sem);
+	if (err)
+		return ERR_PTR(err);
+	else
+		return child;
 }
 
 /*
- * Search the fix-up list for a dentry from a prior lookup.
+ * This is the basic lookup mechanism for turning an NFS file handle
+ * into a dentry.
+ * We use nfsd_iget and if that doesn't return a suitably connected dentry,
+ * we try to find the parent, and the parent of that and so-on until a
+ * connection if made.
  */
-static ino_t nfsd_cached_lookup(struct knfs_fh *fh)
-{
-	struct nfsd_fixup *fp;
-
-	fp = find_cached_lookup(u32_to_kdev_t(fh->fh_dev),
-				u32_to_ino_t(fh->fh_dirino),
-				u32_to_ino_t(fh->fh_ino));
-	if (fp)
-		return fp->new_dirino;
-	return 0;
-}
-
-void
-expire_all(void)
+static struct dentry *
+find_fh_dentry(struct super_block *sb, struct knfs_fh *fh, int needpath)
 {
- 	if (time_after_eq(jiffies, nfsd_next_expire)) {
- 		expire_old(NFSD_FILE_CACHE,  5*HZ);
- 		expire_old(NFSD_DIR_CACHE , 60*HZ);
- 		nfsd_next_expire = jiffies + 5*HZ;
- 	}
-}
+	struct dentry *dentry, *result = NULL;
+	struct dentry *tmp;
+	int  found =0;
+	int err;
+	/* the sb->s_nfsd_free_path_sem semaphore is needed to make sure that only one unconnected (free)
+	 * dcache path ever exists, as otherwise two partial paths might get
+	 * joined together, which would be very confusing.
+	 * If there is ever an unconnected non-root directory, then this lock
+	 * must be held.
+	 */
 
-/* 
- * Free cache after unlink/rmdir.
- */
-void
-expire_by_dentry(struct dentry *dentry)
-{
-	struct fh_entry *fhe;
 
-	fhe = find_fhe(dentry, NFSD_FILE_CACHE, NULL);
-	if (fhe) {
-		expire_fhe(fhe, NFSD_FILE_CACHE);
-	}
-	fhe = find_fhe(dentry, NFSD_DIR_CACHE, NULL);
-	if (fhe) {
-		expire_fhe(fhe, NFSD_DIR_CACHE);
+	nfsdstats.fh_lookup++;
+	/*
+	 * Attempt to find the inode.
+	 */
+ retry:
+	result = nfsd_iget(sb, fh->fh_ino, fh->fh_generation);
+	err = PTR_ERR(result);
+	if (IS_ERR(result))
+		goto err_out;
+	err = -ESTALE;
+	if (!result) {
+		dprintk("find_fh_dentry: No inode found.\n");
+		goto err_out;
 	}
-}
+	if (! (result->d_flags & DCACHE_NFSD_DISCONNECTED))
+		return result;
 
-/*
- * The is the basic lookup mechanism for turning an NFS file handle 
- * into a dentry. There are several levels to the search:
- * (1) Look for the dentry pointer the short-term fhcache,
- *     and verify that it has the correct inode number.
- *
- * (2) Try to validate the dentry pointer in the file handle,
- *     and verify that it has the correct inode number. If this
- *     fails, check for a cached lookup in the fix-up list and
- *     repeat step (2) using the new dentry pointer.
- *
- * (3) Look up the dentry by using the inode and parent inode numbers
- *     to build the name string. This should succeed for any Unix-like
- *     filesystem.
- *
- * (4) Search for the parent dentry in the dir cache, and then
- *     look for the name matching the inode number.
- *
- * (5) The most general case ... search the whole volume for the inode.
- *
- * If successful, we return a dentry with the use count incremented.
- *
- * Note: steps (4) and (5) above are probably unnecessary now that (3)
- * is working. Remove the code once this is verified ...
- */
-static struct dentry *
-find_fh_dentry(struct knfs_fh *fh)
-{
-	struct super_block *sb;
-	struct dentry *dentry, *parent;
-	struct inode * inode;
-	struct list_head *lst;
-	int looked_up = 0, retry = 0;
-	ino_t dirino;
+	/* result is now an anonymous dentry, which may be adequate as it 
+	 * stands, or else will get spliced into the dcache tree */
 
-	/*
-	 * Stage 1: Look for the dentry in the short-term fhcache.
-	 */
-	dentry = find_dentry_in_fhcache(fh);
-	if (dentry) {
-		nfsdstats.fh_cached++;
-		goto out;
+	if (!S_ISDIR(result->d_inode->i_mode) && ! needpath) {
+		nfsdstats.fh_anon++;
+		return result;
 	}
-	/*
-	 * Stage 2: Attempt to find the inode.
+
+	/* It's a directory, or we are required to confirm the file's
+	 * location in the tree.
 	 */
-	sb = get_super(fh->fh_dev);
-	if (NULL == sb) {
-		printk("find_fh_dentry: No SuperBlock for device %s.",
-		       kdevname(fh->fh_dev));
-		dentry = NULL;
-		goto out;
-	}
+	dprintk("nfs_fh: need to look harder for %d/%d\n",sb->s_dev,fh->fh_ino);
+	down(&sb->s_nfsd_free_path_sem);
 
-	dirino = u32_to_ino_t(fh->fh_dirino);
-	inode = iget_in_use(sb, fh->fh_ino);
-	if (!inode) {
-		dprintk("find_fh_dentry: No inode found.\n");
-		goto out_five;
-	}
-	goto check;
-recheck:
-	if (!inode) {
-		dprintk("find_fh_dentry: No inode found.\n");
-		goto out_three;
+	/* claiming the semaphore might have allow things to get fixed up */
+	if (! (result->d_flags & DCACHE_NFSD_DISCONNECTED)) {
+		up(&sb->s_nfsd_free_path_sem);
+		return result;
 	}
-check:
-	for (lst = inode->i_dentry.next;
-	     lst != &inode->i_dentry;
-	     lst = lst->next) {
-		dentry = list_entry(lst, struct dentry, d_alias);
-
-/* if we are looking up a directory then we don't need the parent! */
-		if (!dentry ||
-		    !dentry->d_parent ||
-		    !dentry->d_parent->d_inode) {
-printk("find_fh_dentry: Found a useless inode %lu\n", inode->i_ino);
-			continue;
-		}
-		if (dentry->d_parent->d_inode->i_ino != dirino)
-			continue;
 
-		dget(dentry);
-		iput(inode);
-#ifdef NFSD_DEBUG_VERBOSE
-		printk("find_fh_dentry: Found%s %s/%s filehandle dirino = %lu, %lu\n",
-		       retry ? " Renamed" : "",
-		       dentry->d_parent->d_name.name,
-		       dentry->d_name.name,
-		       dentry->d_parent->d_inode->i_ino,
-		       dirino);
-#endif
-		goto out;
-	} /* for inode->i_dentry */
 
-	/*
-	 * Before proceeding to a lookup, check for a rename
-	 */
-	if (!retry && (dirino = nfsd_cached_lookup(fh))) {
-		dprintk("find_fh_dentry: retry with %lu\n", dirino);
-		retry = 1;
-		goto recheck;
+	found = 0;
+	if (!S_ISDIR(result->d_inode->i_mode)) {
+		nfsdstats.fh_nocache_nondir++;
+		if (fh->fh_dirino == 0)
+			goto err_result; /* don't know how to find parent */
+		else {
+			/* need to iget fh->fh_dirino and make sure this 
+			 * inode is in that directory 
+			 */
+			dentry = nfsd_iget(sb, fh->fh_dirino, 0);
+			err = PTR_ERR(dentry);
+			if (IS_ERR(dentry))
+				goto err_result;
+			err = -ESTALE;
+			if (!dentry->d_inode
+			    || !S_ISDIR(dentry->d_inode->i_mode)) {
+				goto err_dentry;
+			}
+			if (!(dentry->d_flags & DCACHE_NFSD_DISCONNECTED))
+				found = 1;
+			tmp = splice(result, dentry);
+			err = PTR_ERR(tmp);
+			if (IS_ERR(tmp))
+				goto err_dentry;
+			if (tmp != result) {
+				/* it is safe to just use tmp instead, but 
+				 * we must discard result first 
+				 */
+				d_drop(result);
+				dput(result);
+				result = tmp;
+				/* If !found, then this is really wierd, 
+				 * but it shouldn't hurt */
+			}
+		}
+	} else {
+		nfsdstats.fh_nocache_dir++;
+		dentry = dget(result);
 	}
 
-	iput(inode);
+	while(!found) {
+		/* LOOP INVARIANT */
+		/* haven't found a place in the tree yet, but we do have a 
+		 * free path from dentry down to result, and dentry is a 
+		 * directory.  Have a hold on dentry and result 
+		 */
+		struct dentry *pdentry;
+		struct inode *parent;
 
-	dprintk("find_fh_dentry: dirino not found %lu\n", dirino);
+		pdentry = nfsd_findparent(dentry);
+		err = PTR_ERR(pdentry);
+		if (IS_ERR(pdentry))
+			goto err_dentry;
+		parent = pdentry->d_inode;
+		err = -EACCES;
+		if (!parent) {
+			dput(pdentry);
+			goto err_dentry;
+		}
 
-out_three:
+		if (!(dentry->d_flags & DCACHE_NFSD_DISCONNECTED))
+			found = 1;
 
-	/*
-	 * Stage 3: Look up the dentry based on the inode and parent inode
-	 * numbers. This should work for all Unix-like filesystems.
-	 */
-	looked_up = 1;
-	dentry = lookup_inode(u32_to_kdev_t(fh->fh_dev),
-			      u32_to_ino_t(fh->fh_dirino),
-			      u32_to_ino_t(fh->fh_ino));
-	if (!IS_ERR(dentry)) {
-		struct inode * inode = dentry->d_inode;
-#ifdef NFSD_DEBUG_VERBOSE
-printk("find_fh_dentry: looked up %s/%s\n",
-       dentry->d_parent->d_name.name, dentry->d_name.name);
-#endif
-		if (inode && inode->i_ino == u32_to_ino_t(fh->fh_ino)) {
-			nfsdstats.fh_lookup++;
-			goto out;
+		tmp = splice(dentry, pdentry);
+		if (tmp != dentry) {
+			/* Something wrong.  We need to drop the whole 
+			 * dentry->result path whatever it was
+			 */
+			struct dentry *d;
+			for (d=result ; d ; d=(d->d_parent == d)?NULL:d->d_parent)
+				d_drop(d);
+		}
+		if (IS_ERR(tmp)) {
+			err = PTR_ERR(tmp);
+			dput(pdentry);
+			goto err_dentry;
+		}
+		if (tmp != dentry) {
+			/* we lost a race,  try again
+			 */
+			dput(tmp);
+			dput(dentry);
+			dput(result);	/* this will discard the whole free path, so we can up the semaphore */
+			up(&sb->s_nfsd_free_path_sem);
+			goto retry;
 		}
-#ifdef NFSD_PARANOIA
-printk("find_fh_dentry: %s/%s lookup mismatch!\n",
-       dentry->d_parent->d_name.name, dentry->d_name.name);
-#endif
 		dput(dentry);
+		dentry = pdentry;
 	}
+	dput(dentry);
+	up(&sb->s_nfsd_free_path_sem);
+	return result;
 
-	/*
-	 * Stage 4: Look for the parent dentry in the fhcache ...
-	 */
-	parent = find_dentry_by_ino(u32_to_kdev_t(fh->fh_dev),
-				    u32_to_ino_t(fh->fh_dirino));
-	if (parent) {
-		/*
-		 * ... then search for the inode in the parent directory.
-		 */
-		dget(parent);
-		dentry = lookup_by_inode(parent, u32_to_ino_t(fh->fh_ino));
-		dput(parent);
-		if (dentry)
-			goto out;
-	}
-
-out_five:
-
-	/*
-	 * Stage 5: Search the whole volume, Yea Right.
-	 */
-#ifdef NFSD_PARANOIA_EXTREME
-printk("find_fh_dentry: %s/%u dir/%u not found!\n",
-       kdevname(u32_to_kdev_t(fh->fh_dev)), fh->fh_ino, fh->fh_dirino);
-#endif
-	dentry = NULL;
-	nfsdstats.fh_stale++;
-	
-out:
-	expire_all();
-	return dentry;
+err_dentry:
+	dput(dentry);
+err_result:
+	dput(result);
+	up(&sb->s_nfsd_free_path_sem);
+err_out:
+	if (err == -ESTALE)
+		nfsdstats.fh_stale++;
+	return ERR_PTR(err);
 }
 
 /*
@@ -1117,6 +495,9 @@
  *
  * Note that the file handle dentry may need to be freed even after
  * an error return.
+ *
+ * This is only called at the start of an nfsproc call, so fhp points to
+ * a svc_fh which is all 0 except for the over-the-wire file handle.
  */
 u32
 fh_verify(struct svc_rqst *rqstp, struct svc_fh *fhp, int type, int access)
@@ -1134,57 +515,79 @@
 		fh->fh_ino,
 		fh->fh_dirino);
 
-	if (fhp->fh_dverified)
-		goto check_type;
-	/*
-	 * Look up the export entry.
-	 */
-	error = nfserr_stale;
-	exp = exp_get(rqstp->rq_client,
-		      u32_to_kdev_t(fh->fh_xdev),
-		      u32_to_ino_t(fh->fh_xino));
-	if (!exp) {
-		/* export entry revoked */
-		nfsdstats.fh_stale++;
-		goto out;
-	}
+	if (!fhp->fh_dentry) {
+		/*
+		 * Security: Check that the fh is internally consistant 
+		 * (from <gam3@acm.org>)
+		 */
+		if (fh->fh_dev != fh->fh_xdev) {
+			printk("fh_verify: Security: export on other device (%s, %s).\n",
+			       kdevname(fh->fh_dev), kdevname(fh->fh_xdev));
+			error = nfserr_stale;
+			nfsdstats.fh_stale++;
+			goto out;
+		}
 
-	/* Check if the request originated from a secure port. */
-	error = nfserr_perm;
-	if (!rqstp->rq_secure && EX_SECURE(exp)) {
-		printk(KERN_WARNING
-			"nfsd: request from insecure port (%08x:%d)!\n",
-				ntohl(rqstp->rq_addr.sin_addr.s_addr),
-				ntohs(rqstp->rq_addr.sin_port));
-		goto out;
-	}
+		/*
+		 * Look up the export entry.
+		 */
+		error = nfserr_stale; 
+		exp = exp_get(rqstp->rq_client,
+			      u32_to_kdev_t(fh->fh_xdev),
+			      u32_to_ino_t(fh->fh_xino));
+		if (!exp) {
+			/* export entry revoked */
+			nfsdstats.fh_stale++;
+			goto out;
+		}
+
+		/* Check if the request originated from a secure port. */
+		error = nfserr_perm;
+		if (!rqstp->rq_secure && EX_SECURE(exp)) {
+			printk(KERN_WARNING
+			       "nfsd: request from insecure port (%08x:%d)!\n",
+			       ntohl(rqstp->rq_addr.sin_addr.s_addr),
+			       ntohs(rqstp->rq_addr.sin_port));
+			goto out;
+		}
 
-	/* Set user creds if we haven't done so already. */
-	nfsd_setuser(rqstp, exp);
+		/* Set user creds if we haven't done so already. */
+		nfsd_setuser(rqstp, exp);
 
-	/*
-	 * Look up the dentry using the NFS file handle.
-	 */
-	error = nfserr_noent;
-	dentry = find_fh_dentry(fh);
-	if (!dentry) {
-		goto out;
-	}
-	if (IS_ERR(dentry)) {
-		error = nfserrno(-PTR_ERR(dentry));
-		goto out;
+		/*
+		 * Look up the dentry using the NFS file handle.
+		 */
+
+		dentry = find_fh_dentry(exp->ex_dentry->d_inode->i_sb,
+					fh,
+					!(exp->ex_flags & NFSEXP_NOSUBTREECHECK));
+
+		if (IS_ERR(dentry)) {
+			error = nfserrno(-PTR_ERR(dentry));
+			goto out;
+		}
+#ifdef NFSD_PARANOIA
+		if (S_ISDIR(dentry->d_inode->i_mode) &&
+		    (dentry->d_flags & DCACHE_NFSD_DISCONNECTED)) {
+			printk("nfsd: find_fh_dentry returned a DISCONNECTED directory: %s/%s\n",
+			       dentry->d_parent->d_name.name, dentry->d_name.name);
+		}
+#endif
+
+		fhp->fh_dentry = dentry;
+		fhp->fh_export = exp;
+		nfsd_nr_verified++;
+	} else {
+		/* just rechecking permissions
+		 * (e.g. nfsproc_create calls fh_verify, then nfsd_create 
+		 * does as well)
+		 */
+		dprintk("nfsd: fh_verify - just checking\n");
+		dentry = fhp->fh_dentry;
+		exp = fhp->fh_export;
 	}
 
-	/*
-	 * Note:  it's possible the returned dentry won't be the one in the
-	 * file handle.  We can correct the file handle for our use, but
-	 * unfortunately the client will keep sending the broken one.  Let's
-	 * hope the lookup will keep patching things up.
-	 */
-	fhp->fh_dentry = dentry;
-	fhp->fh_export = exp;
-	fhp->fh_dverified = 1;
-	nfsd_nr_verified++;
+	inode = dentry->d_inode;
 
 	/* Type check. The correct error return for type mismatches
 	 * does not seem to be generally agreed upon. SunOS seems to
@@ -1192,39 +595,7 @@
 	 * spec says this is incorrect (implementation notes for the
 	 * write call).
 	 */
-check_type:
-	dentry = fhp->fh_dentry;
-	inode = dentry->d_inode;
-	error = nfserr_stale;
-	/* On a heavily loaded SMP machine, more than one identical
-	   requests may run at the same time on different processors.
-	   One thread may get here with unfinished fh after another
-	   thread just fetched the inode. It doesn't make any senses
-	   to check fh->fh_generation here since it has not been set
-	   yet. In that case, we shouldn't send back the stale
-	   filehandle to the client. We use fh->fh_dcookie to indicate
-	   if fh->fh_generation is set or not. If fh->fh_dcookie is
-	   not set, don't return stale filehandle. */
-	if (inode->i_generation != fh->fh_generation) {
-		if (fh->fh_dcookie) {
-			dprintk("fh_verify: Bad version %lu %u %u: 0x%x, 0x%x\n",
-				inode->i_ino,
-				inode->i_generation,
-				fh->fh_generation,
-				type, access);
-			nfsdstats.fh_stale++;
-			goto out;
-		}
-		else {
-			/* We get here when inode is fetched by other
-			   threads. We just use what is in there. */
-			fh->fh_ino = ino_t_to_u32(inode->i_ino);
-			fh->fh_generation = inode->i_generation;
-			fh->fh_dcookie = (struct dentry *)0xfeebbaca;
-			nfsdstats.fh_concurrent++;
-		}
-	}
-	exp = fhp->fh_export;
+
 	if (type > 0 && (inode->i_mode & S_IFMT) != type) {
 		error = (type == S_IFDIR)? nfserr_notdir : nfserr_isdir;
 		goto out;
@@ -1238,38 +609,37 @@
 	 * Security: Check that the export is valid for dentry <gam3@acm.org>
 	 */
 	error = 0;
-	if (fh->fh_dev != fh->fh_xdev) {
-		printk("fh_verify: Security: export on other device (%s, %s).\n",
-		       kdevname(fh->fh_dev), kdevname(fh->fh_xdev));
-		error = nfserr_stale;
-		nfsdstats.fh_stale++;
-	} else if (exp->ex_dentry != dentry) {
-		struct dentry *tdentry = dentry;
 
-		do {
-			tdentry = tdentry->d_parent;
-			if (exp->ex_dentry == tdentry)
-				break;
-			/* executable only by root and we can't be root */
-			if (current->fsuid
-			    && !(tdentry->d_inode->i_uid
-			         && (tdentry->d_inode->i_mode & S_IXUSR))
-			    && !(tdentry->d_inode->i_gid
-				 && (tdentry->d_inode->i_mode & S_IXGRP))
-			    && !(tdentry->d_inode->i_mode & S_IXOTH)
-			    && (exp->ex_flags & NFSEXP_ROOTSQUASH)) {
+	if (!(exp->ex_flags & NFSEXP_NOSUBTREECHECK)) {
+		if (exp->ex_dentry != dentry) {
+			struct dentry *tdentry = dentry;
+
+			do {
+				tdentry = tdentry->d_parent;
+				if (exp->ex_dentry == tdentry)
+					break;
+				/* executable only by root and we can't be root */
+				if (current->fsuid
+				    && (exp->ex_flags & NFSEXP_ROOTSQUASH)
+				    && !(tdentry->d_inode->i_uid
+					 && (tdentry->d_inode->i_mode & S_IXUSR))
+				    && !(tdentry->d_inode->i_gid
+					 && (tdentry->d_inode->i_mode & S_IXGRP))
+				    && !(tdentry->d_inode->i_mode & S_IXOTH)
+					) {
+					error = nfserr_stale;
+					nfsdstats.fh_stale++;
+					dprintk("fh_verify: no root_squashed access.\n");
+				}
+			} while ((tdentry != tdentry->d_parent));
+			if (exp->ex_dentry != tdentry) {
 				error = nfserr_stale;
 				nfsdstats.fh_stale++;
-dprintk("fh_verify: no root_squashed access.\n");
+				printk("nfsd Security: %s/%s bad export.\n",
+				       dentry->d_parent->d_name.name,
+				       dentry->d_name.name);
+				goto out;
 			}
-		} while ((tdentry != tdentry->d_parent));
-		if (exp->ex_dentry != tdentry) {
-			error = nfserr_stale;
-			nfsdstats.fh_stale++;
-			printk("nfsd Security: %s/%s bad export.\n",
-			       dentry->d_parent->d_name.name,
-			       dentry->d_name.name);
-			goto out;
 		}
 	}
 
@@ -1277,10 +647,11 @@
 	if (!error) {
 		error = nfsd_permission(exp, dentry, access);
 	}
-#ifdef NFSD_PARANOIA
-if (error)
-printk("fh_verify: %s/%s permission failure, acc=%x, error=%d\n",
-dentry->d_parent->d_name.name, dentry->d_name.name, access, (error >> 24));
+#ifdef NFSD_PARANOIA_EXTREME
+	if (error) {
+		printk("fh_verify: %s/%s permission failure, acc=%x, error=%d\n",
+		       dentry->d_parent->d_name.name, dentry->d_name.name, access, (error >> 24));
+	}
 #endif
 out:
 	return error;
@@ -1300,7 +671,7 @@
 	struct dentry *parent = dentry->d_parent;
 
 	dprintk("nfsd: fh_compose(exp %x/%ld %s/%s, ino=%ld)\n",
-		exp->ex_dev, exp->ex_ino,
+		exp->ex_dev, (long) exp->ex_ino,
 		parent->d_name.name, dentry->d_name.name,
 		(inode ? inode->i_ino : 0));
 
@@ -1309,33 +680,33 @@
 	 * may not be done on error paths, but the cleanup must call fh_put.
 	 * Fix this soon!
 	 */
-	if (fhp->fh_dverified || fhp->fh_locked || fhp->fh_dentry) {
+	if (fhp->fh_dentry || fhp->fh_locked) {
 		printk(KERN_ERR "fh_compose: fh %s/%s not initialized!\n",
 			parent->d_name.name, dentry->d_name.name);
 	}
 	fh_init(fhp);
 
-	fhp->fh_handle.fh_dcookie = dentry;
+	fhp->fh_handle.fh_dirino = ino_t_to_u32(parent->d_inode->i_ino);
+	fhp->fh_handle.fh_dev    = kdev_t_to_u32(parent->d_inode->i_dev);
+	fhp->fh_handle.fh_xdev   = kdev_t_to_u32(exp->ex_dev);
+	fhp->fh_handle.fh_xino   = ino_t_to_u32(exp->ex_ino);
+	fhp->fh_handle.fh_dcookie = (struct dentry *)0xfeebbaca;
 	if (inode) {
 		fhp->fh_handle.fh_ino = ino_t_to_u32(inode->i_ino);
 		fhp->fh_handle.fh_generation = inode->i_generation;
-		fhp->fh_handle.fh_dcookie = (struct dentry *)0xfeebbaca;
+		if (S_ISDIR(inode->i_mode) || (exp->ex_flags & NFSEXP_NOSUBTREECHECK))
+			fhp->fh_handle.fh_dirino = 0;
 	}
-	fhp->fh_handle.fh_dirino = ino_t_to_u32(parent->d_inode->i_ino);
-	fhp->fh_handle.fh_dev	 = kdev_t_to_u32(parent->d_inode->i_dev);
-	fhp->fh_handle.fh_xdev	 = kdev_t_to_u32(exp->ex_dev);
-	fhp->fh_handle.fh_xino	 = ino_t_to_u32(exp->ex_ino);
 
 	fhp->fh_dentry = dentry; /* our internal copy */
 	fhp->fh_export = exp;
 
-	/* We stuck it there, we know it's good. */
-	fhp->fh_dverified = 1;
 	nfsd_nr_verified++;
 }
 
 /*
  * Update file handle information after changing a dentry.
+ * This is only called by nfsd_create
  */
 void
 fh_update(struct svc_fh *fhp)
@@ -1343,7 +714,7 @@
 	struct dentry *dentry;
 	struct inode *inode;
 
-	if (!fhp->fh_dverified)
+	if (!fhp->fh_dentry)
 		goto out_bad;
 
 	dentry = fhp->fh_dentry;
@@ -1352,7 +723,9 @@
 		goto out_negative;
 	fhp->fh_handle.fh_ino = ino_t_to_u32(inode->i_ino);
 	fhp->fh_handle.fh_generation = inode->i_generation;
-	fhp->fh_handle.fh_dcookie = (struct dentry *)0xfeebbaca;
+	if (S_ISDIR(inode->i_mode) || (fhp->fh_export->ex_flags & NFSEXP_NOSUBTREECHECK))
+		fhp->fh_handle.fh_dirino = 0;
+
 out:
 	return;
 
@@ -1366,22 +739,19 @@
 }
 
 /*
- * Release a file handle.  If the file handle carries a dentry count,
- * we add the dentry to the short-term cache rather than release it.
+ * Release a file handle.
  */
 void
 fh_put(struct svc_fh *fhp)
 {
 	struct dentry * dentry = fhp->fh_dentry;
-	if (fhp->fh_dverified) {
+	if (dentry) {
 		fh_unlock(fhp);
-		fhp->fh_dverified = 0;
+		fhp->fh_dentry = NULL;
 		if (!dentry->d_count)
 			goto out_bad;
-		if (!dentry->d_inode || !add_to_fhcache(dentry, 0)) {
-			dput(dentry);
-			nfsd_nr_put++;
-		}
+		dput(dentry);
+		nfsd_nr_put++;
 	}
 	return;
 
@@ -1389,118 +759,4 @@
 	printk(KERN_ERR "fh_put: %s/%s has d_count 0!\n",
 		dentry->d_parent->d_name.name, dentry->d_name.name);
 	return;
-}
-
-/*
- * Flush any cached dentries for the specified device
- * or for all devices.
- *
- * This is called when revoking the last export for a
- * device, so that it can be unmounted cleanly.
- */
-void nfsd_fh_flush(kdev_t dev)
-{
-	struct fh_entry *fhe;
-	int i, pass = 2;
-
-	fhe = &filetable[0];
-	while (pass--) {
-		for (i = 0; i < NFSD_MAXFH; i++, fhe++) {
-			struct dentry *dentry = fhe->dentry;
-			if (!dentry)
-				continue;
-			if (dev && dentry->d_inode->i_dev != dev)
-				continue;
-			fhe->dentry = NULL;
-			dput(dentry);
-			nfsd_nr_put++;
-		}
-		fhe = &dirstable[0];
-	}
-}
-
-/*
- * Free the rename and path caches.
- */
-void nfsd_fh_free(void)
-{
-	struct list_head *tmp;
-	int i;
-
-	/* Flush dentries for all devices */
-	nfsd_fh_flush(0);
-
-	/*
-	 * N.B. write a destructor for these lists ...
-	 */
-	i = 0;
-	while ((tmp = fixup_head.next) != &fixup_head) {
-		struct nfsd_fixup *fp;
-		fp = list_entry(tmp, struct nfsd_fixup, lru);
-		free_fixup_entry(fp);
-		i++;
-	}
-	printk(KERN_DEBUG "nfsd_fh_free: %d fixups freed\n", i);
-
-	i = 0;
-	while ((tmp = path_inuse.next) != &path_inuse) {
-		struct nfsd_path *pe;
-		pe = list_entry(tmp, struct nfsd_path, lru);
-		free_path_entry(pe);
-		i++;
-	}
-	printk(KERN_DEBUG "nfsd_fh_free: %d paths freed\n", i);
-
-	printk(KERN_DEBUG "nfsd_fh_free: verified %d, put %d\n",
-		nfsd_nr_verified, nfsd_nr_put);
-}
-
-void nfsd_fh_init(void)
-{
-	extern void __my_nfsfh_is_too_big(void); 
-
-	if (filetable)
-		return;
-
-	/* Sanity check */ 
-	if (sizeof(struct nfs_fhbase) > 32) 
-		__my_nfsfh_is_too_big(); 
-
-	filetable = kmalloc(sizeof(struct fh_entry) * NFSD_MAXFH,
-			    GFP_KERNEL);
-	dirstable = kmalloc(sizeof(struct fh_entry) * NFSD_MAXFH,
-			    GFP_KERNEL);
-
-	if (filetable == NULL || dirstable == NULL) {
-		printk(KERN_WARNING "nfsd_fh_init : Could not allocate fhcache\n");
-		nfsd_nservers = 0;
-		return;
-	}
-
-	memset(filetable, 0, NFSD_MAXFH*sizeof(struct fh_entry));
-	memset(dirstable, 0, NFSD_MAXFH*sizeof(struct fh_entry));
-	INIT_LIST_HEAD(&path_inuse);
-	INIT_LIST_HEAD(&fixup_head);
-
-	printk(KERN_DEBUG 
-		"nfsd_fh_init : initialized fhcache, entries=%lu\n", NFSD_MAXFH);
-	/*
-	 * Display a warning if the ino_t is larger than 32 bits.
-	 */
-	if (sizeof(ino_t) > sizeof(__u32))
-		printk(KERN_INFO 
-			"NFSD: ino_t is %d bytes, using lower 4 bytes\n",
-			sizeof(ino_t));
-}
-
-void
-nfsd_fh_shutdown(void)
-{
-	if (!filetable)
-		return;
-	printk(KERN_DEBUG 
-		"nfsd_fh_shutdown : freeing %ld fhcache entries.\n", NFSD_MAXFH);
-	kfree(filetable);
-	kfree(dirstable);
-	filetable = dirstable = NULL;
 }

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)