patch-2.2.18 linux/fs/nfs/dir.c

Next file: linux/fs/nfs/file.c
Previous file: linux/fs/nfs/Makefile
Back to the patch index
Back to the overall index

diff -u --new-file --recursive --exclude-from /usr/src/exclude v2.2.17/fs/nfs/dir.c linux/fs/nfs/dir.c
@@ -14,6 +14,9 @@
  *              Following Linus comments on my original hack, this version
  *              depends only on the dcache stuff and doesn't touch the inode
  *              layer (iput() and friends).
+ *  6 Jun 1999  Cache readdir lookups in the page cache. -DaveM
+ *  7 Oct 1999  Rewrite of Dave's readdir stuff for NFSv3 support, and in order
+ *              to simplify cookie handling. -Trond
  */
 
 #include <linux/sched.h>
@@ -23,32 +26,22 @@
 #include <linux/string.h>
 #include <linux/kernel.h>
 #include <linux/malloc.h>
+#include <asm/pgtable.h>
 #include <linux/mm.h>
 #include <linux/sunrpc/types.h>
+#include <linux/nfs.h>
+#include <linux/nfs2.h>
+#include <linux/nfs3.h>
 #include <linux/nfs_fs.h>
+#include <linux/nfs_mount.h>
+#include <linux/sunrpc/auth.h>
+#include <linux/sunrpc/clnt.h>
 
 #include <asm/segment.h>	/* for fs functions */
 
 #define NFS_PARANOIA 1
 /* #define NFS_DEBUG_VERBOSE 1 */
 
-/*
- * Head for a dircache entry. Currently still very simple; when
- * the cache grows larger, we will need a LRU list.
- */
-struct nfs_dirent {
-	dev_t			dev;		/* device number */
-	ino_t			ino;		/* inode number */
-	u32			cookie;		/* cookie of first entry */
-	unsigned short		valid  : 1,	/* data is valid */
-				locked : 1;	/* entry locked */
-	unsigned int		size;		/* # of entries */
-	unsigned long		age;		/* last used */
-	unsigned long		mtime;		/* last attr stamp */
-	struct wait_queue *	wait;
-	__u32 *			entry;		/* three __u32's per entry */
-};
-
 static int nfs_safe_remove(struct dentry *);
 
 static ssize_t nfs_dir_read(struct file *, char *, size_t, loff_t *);
@@ -92,276 +85,382 @@
 	NULL,			/* readlink */
 	NULL,			/* follow_link */
 	NULL,			/* readpage */
-	NULL,			/* writepage */
-	NULL,			/* bmap */
+ 	NULL,			/* writepage */
+ 	NULL,			/* bmap */
 	NULL,			/* truncate */
-	NULL,			/* permission */
+	nfs_permission,		/* permission */
 	NULL,			/* smap */
 	NULL,			/* updatepage */
 	nfs_revalidate,		/* revalidate */
 };
 
 static ssize_t
-nfs_dir_read(struct file *filp, char *buf, size_t count, loff_t *ppos)
+nfs_dir_read(struct file *file, char *buf, size_t count, loff_t *ppos)
 {
 	return -EISDIR;
 }
 
-static struct nfs_dirent	dircache[NFS_MAX_DIRCACHE];
+typedef u32 * (*decode_dirent_t)(u32 *, struct nfs_entry *, int);
+typedef struct {
+	struct file	*file;
+	struct page	*page;
+	unsigned long	page_index;
+	unsigned	page_offset;
+	u64		target;
+	struct nfs_entry *entry;
+	decode_dirent_t	decode;
+	int		plus;
+	int		error;
+} nfs_readdir_descriptor_t;
 
-/*
- * We need to do caching of directory entries to prevent an
- * incredible amount of RPC traffic.  Only the most recent open
- * directory is cached.  This seems sufficient for most purposes.
- * Technically, we ought to flush the cache on close but this is
- * not a problem in practice.
+/* Now we cache directories properly, by stuffing the dirent
+ * data directly in the page cache.
+ *
+ * Inode invalidation due to refresh etc. takes care of
+ * _everything_, no sloppy entry flushing logic, no extraneous
+ * copying, network direct to page cache, the way it was meant
+ * to be.
  *
- * XXX: Do proper directory caching by stuffing data into the
- * page cache (may require some fiddling for rsize < PAGE_SIZE).
+ * NOTE: Dirent information verification is done always by the
+ *	 page-in of the RPC reply, nowhere else, this simplies
+ *	 things substantially.
  */
-
-static int nfs_readdir(struct file *filp, void *dirent, filldir_t filldir)
+static
+int nfs_readdir_filler(nfs_readdir_descriptor_t *desc, struct page *page)
 {
-	struct dentry 		*dentry = filp->f_dentry;
-	struct inode 		*inode = dentry->d_inode;
-	static struct wait_queue *readdir_wait = NULL;
-	struct wait_queue	**waitp = NULL;
-	struct nfs_dirent	*cache, *free;
-	unsigned long		age, dead;
-	u32			cookie;
-	int			ismydir, result;
-	int			i, j, index = 0;
-	__u32			*entry;
-	char			*name, *start;
-
-	dfprintk(VFS, "NFS: nfs_readdir(%s/%s)\n",
-		dentry->d_parent->d_name.name, dentry->d_name.name);
-
-	result = nfs_revalidate_inode(NFS_DSERVER(dentry), dentry);
-	if (result < 0)
-		goto out;
-
-	/*
-	 * Try to find the entry in the cache
+	struct file	*file = desc->file;
+	struct dentry	*dir = file->f_dentry;
+	struct inode	*inode = dir->d_inode;
+	struct nfs_fattr dir_attr;
+	void		*buffer = (void *)page_address(page);
+	int		plus = NFS_USE_READDIRPLUS(inode);
+	int		error;
+
+	dfprintk(VFS, "NFS: nfs_readdir_filler() reading cookie %Lu into page %lu.\n", (long long)desc->entry->cookie, page->offset);
+
+ again:
+	error = NFS_CALL(readdir, inode, (dir, &dir_attr,
+					  nfs_file_cred(file),
+					  desc->entry->cookie, buffer,
+					  NFS_SERVER(inode)->dtsize, plus));
+	nfs_refresh_inode(inode, &dir_attr);
+	/* We requested READDIRPLUS, but the server doesn't grok it */
+	if (desc->plus && error == -ENOTSUPP) {
+		NFS_FLAGS(inode) &= ~NFS_INO_ADVISE_RDPLUS;
+		plus = 0;
+		goto again;
+	}
+	if (error < 0)
+		goto error;
+	flush_dcache_page(page_address(page)); /* Is this correct? */
+	set_bit(PG_uptodate, &page->flags);
+
+	/* Ensure consistent page alignment of the data.
+	 * Note: assumes we have exclusive access to this inode either
+	 *	 throught inode->i_sem or some other mechanism.
 	 */
-again:
-	if (waitp) {
-		interruptible_sleep_on(waitp);
-		if (signal_pending(current))
-			return -ERESTARTSYS;
-		waitp = NULL;
-	}
-
-	cookie = filp->f_pos;
-	entry  = NULL;
-	free   = NULL;
-	age    = ~(unsigned long) 0;
-	dead   = jiffies - NFS_ATTRTIMEO(inode);
-
-	for (i = 0, cache = dircache; i < NFS_MAX_DIRCACHE; i++, cache++) {
-		/*
-		dprintk("NFS: dircache[%d] valid %d locked %d\n",
-					i, cache->valid, cache->locked);
-		 */
-		ismydir = (cache->dev == inode->i_dev
-				&& cache->ino == inode->i_ino);
-		if (cache->locked) {
-			if (!ismydir || cache->cookie != cookie)
-				continue;
-			dfprintk(DIRCACHE, "NFS: waiting on dircache entry\n");
-			waitp = &cache->wait;
-			goto again;
-		}
-
-		if (ismydir && cache->mtime != inode->i_mtime)
-			cache->valid = 0;
-
-		if (!cache->valid || cache->age < dead) {
-			free = cache;
-			age  = 0;
-		} else if (cache->age < age) {
-			free = cache;
-			age  = cache->age;
-		}
-
-		if (!ismydir || !cache->valid)
-			continue;
-
-		if (cache->cookie == cookie && cache->size > 0) {
-			entry = cache->entry + (index = 0);
-			cache->locked = 1;
-			break;
-		}
-		for (j = 0; j < cache->size; j++) {
-			__u32 *this_ent = cache->entry + j*3;
+	if (page->offset == 0)
+		invalidate_inode_pages(inode);
+	nfs_unlock_page(page);
+	return 0;
+ error:
+	set_bit(PG_error, &page->flags);
+	nfs_unlock_page(page);
+	desc->error = error;
+	return -EIO;
+}
 
-			if (*(this_ent+1) != cookie)
-				continue;
-			if (j < cache->size - 1) {
-				index = j + 1;
-				entry = this_ent + 3;
-			} else if (*(this_ent+2) & (1 << 15)) {
-				/* eof */
-				return 0;
-			}
+/*
+ * Given a pointer to a buffer that has already been filled by a call
+ * to readdir, find the next entry.
+ *
+ * If the end of the buffer has been reached, return -EAGAIN, if not,
+ * return the offset within the buffer of the next entry to be
+ * read.
+ */
+static inline
+int find_dirent(nfs_readdir_descriptor_t *desc, struct page *page)
+{
+	struct nfs_entry *entry = desc->entry;
+	char		*start = (char *)page_address(page),
+			*p = start;
+	int		loop_count = 0,
+			status = 0;
+
+	for(;;) {
+		p = (char *)desc->decode((u32*)p, entry, desc->plus);
+		if (IS_ERR(p)) {
+			status = PTR_ERR(p);
 			break;
 		}
-		if (entry) {
-			dfprintk(DIRCACHE, "NFS: found dircache entry %d\n",
-						(int)(cache - dircache));
-			cache->locked = 1;
+		desc->page_offset = p - start;
+		dfprintk(VFS, "NFS: found cookie %Lu\n", (long long)entry->cookie);
+		if (entry->prev_cookie == desc->target)
 			break;
+		if (loop_count++ > 200) {
+			loop_count = 0;
+			schedule();
 		}
 	}
+	dfprintk(VFS, "NFS: find_dirent() returns %d\n", status);
+	return status;
+}
 
-	/*
-	 * Okay, entry not present in cache, or locked and inaccessible.
-	 * Set up the cache entry and attempt a READDIR call.
-	 */
-	if (entry == NULL) {
-		if ((cache = free) == NULL) {
-			dfprintk(DIRCACHE, "NFS: dircache contention\n");
-			waitp = &readdir_wait;
-			goto again;
-		}
-		dfprintk(DIRCACHE, "NFS: using free dircache entry %d\n",
-				(int)(free - dircache));
-		cache->cookie = cookie;
-		cache->locked = 1;
-		cache->valid  = 0;
-		cache->dev    = inode->i_dev;
-		cache->ino    = inode->i_ino;
-		if (!cache->entry) {
-			result = -ENOMEM;
-			cache->entry = (__u32 *) get_free_page(GFP_KERNEL);
-			if (!cache->entry)
-				goto done;
-		}
-
-		result = nfs_proc_readdir(NFS_SERVER(inode), NFS_FH(dentry),
-					cookie, PAGE_SIZE, cache->entry);
-		if (result <= 0)
-			goto done;
-		cache->size  = result;
-		cache->valid = 1;
-		entry = cache->entry + (index = 0);
-	}
-	cache->mtime = inode->i_mtime;
-	cache->age = jiffies;
-
-	/*
-	 * Yowza! We have a cache entry...
-	 */
-	start = (char *) cache->entry;
-	while (index < cache->size) {
-		__u32	fileid  = *entry++;
-		__u32	nextpos = *entry++; /* cookie */
-		__u32	length  = *entry++;
+/*
+ * Find the given page, and call find_dirent() in order to try to
+ * return the next entry.
+ */
+static inline
+int find_dirent_page(nfs_readdir_descriptor_t *desc)
+{
+	struct inode	*inode = desc->file->f_dentry->d_inode;
+	struct page	*page;
+	unsigned long	index = desc->page_index;
+	int		status;
 
-		/*
-		 * Unpack the eof flag, offset, and length
-		 */
-		result = length & (1 << 15); /* eof flag */
-		name = start + ((length >> 16) & 0xFFFF);
-		length &= 0x7FFF;
-		/*
-		dprintk("NFS: filldir(%p, %.*s, %d, %d, %x, eof %x)\n", entry,
-				(int) length, name, length,
-				(unsigned int) filp->f_pos,
-				fileid, result);
-		 */
+	dfprintk(VFS, "NFS: find_dirent_page() searching directory page %ld\n", desc->page_index);
 
-		if (filldir(dirent, name, length, cookie, fileid) < 0)
-			break;
-		cookie = nextpos;
-		index++;
+	if (desc->page) {
+		page_cache_release(desc->page);
+		desc->page = NULL;
 	}
-	filp->f_pos = cookie;
-	result = 0;
 
-	/* XXX: May want to kick async readdir-ahead here. Not too hard
-	 * to do. */
-
-done:
-	dfprintk(DIRCACHE, "NFS: nfs_readdir complete\n");
-	cache->locked = 0;
-	wake_up(&cache->wait);
-	wake_up(&readdir_wait);
+	page = read_cache_page(inode, index,
+			       (filler_t *)nfs_readdir_filler, desc);
+	if (IS_ERR(page)) {
+		status = PTR_ERR(page);
+		goto out;
+	}
 
-out:
-	return result;
+	/* NOTE: Someone else may have changed the READDIRPLUS flag */
+	desc->plus = NFS_USE_READDIRPLUS(inode);
+	status = find_dirent(desc, page);
+	if (status >= 0)
+		desc->page = page;
+	else
+		page_cache_release(page);
+ out:
+	dfprintk(VFS, "NFS: find_dirent_page() returns %d\n", status);
+	return status;
 }
 
 /*
- * Invalidate dircache entries for an inode.
+ * Recurse through the page cache pages, and return a
+ * filled nfs_entry structure of the next directory entry if possible.
+ *
+ * The target for the search is 'desc->target'.
  */
-void
-nfs_invalidate_dircache(struct inode *inode)
+static inline
+int readdir_search_pagecache(nfs_readdir_descriptor_t *desc)
 {
-	struct nfs_dirent *cache = dircache;
-	dev_t		dev = inode->i_dev;
-	ino_t		ino = inode->i_ino;
-	int		i;
-
-	dfprintk(DIRCACHE, "NFS: invalidate dircache for %x/%ld\n", dev, (long)ino);
-	for (i = NFS_MAX_DIRCACHE; i--; cache++) {
-		if (cache->ino != ino)
-			continue;
-		if (cache->dev != dev)
-			continue;
-		if (cache->locked) {
-			printk("NFS: cache locked for %s/%ld\n",
-				kdevname(dev), (long) ino);
-			continue;
+	int		res = 0;
+	int		loop_count = 0;
+
+	dfprintk(VFS, "NFS: readdir_search_pagecache() searching for cookie %Lu\n", (long long)desc->target);
+	for (;;) {
+		res = find_dirent_page(desc);
+		if (res != -EAGAIN)
+			break;
+		/* Align to beginning of next page */
+		desc->page_offset = 0;
+		desc->page_index += PAGE_CACHE_SIZE;
+		if (loop_count++ > 200) {
+			loop_count = 0;
+			schedule();
 		}
-		cache->valid = 0;	/* brute force */
 	}
+	dfprintk(VFS, "NFS: readdir_search_pagecache() returned %d\n", res);
+	return res;
 }
 
 /*
- * Invalidate the dircache for a super block (or all caches),
- * and release the cache memory.
+ * Once we've found the start of the dirent within a page: fill 'er up...
  */
-void
-nfs_invalidate_dircache_sb(struct super_block *sb)
-{
-	struct nfs_dirent *cache = dircache;
-	int		i;
-
-	for (i = NFS_MAX_DIRCACHE; i--; cache++) {
-		if (sb && sb->s_dev != cache->dev)
-			continue;
-		if (cache->locked) {
-			printk("NFS: cache locked at umount %s\n",
-				(cache->entry ? "(lost a page!)" : ""));
-			continue;
+static 
+int nfs_do_filldir(nfs_readdir_descriptor_t *desc, void *dirent,
+		   filldir_t filldir)
+{
+	struct file	*file = desc->file;
+	struct nfs_entry *entry = desc->entry;
+	char		*start = (char *)page_address(desc->page),
+			*p = start + desc->page_offset;
+	unsigned long	fileid;
+	int		loop_count = 0,
+			res = 0;
+
+	dfprintk(VFS, "NFS: nfs_do_filldir() filling starting @ cookie %Lu\n", (long long)desc->target);
+
+	for(;;) {
+		/* Note: entry->prev_cookie contains the cookie for
+		 *	 retrieving the current dirent on the server */
+		fileid = nfs_fileid_to_ino_t(entry->ino);
+		res = filldir(dirent, entry->name, entry->len, 
+			      entry->prev_cookie, fileid);
+		if (res < 0)
+			break;
+		file->f_pos = desc->target = entry->cookie;
+		p = (char *)desc->decode((u32 *)p, entry, desc->plus);
+		if (IS_ERR(p)) {
+			if (PTR_ERR(p) == -EAGAIN) {
+				desc->page_offset = 0;
+				desc->page_index += PAGE_CACHE_SIZE;
+			}
+			break;
 		}
-		cache->valid = 0;	/* brute force */
-		if (cache->entry) {
-			free_page((unsigned long) cache->entry);
-			cache->entry = NULL;
+		desc->page_offset = p - start;
+		if (loop_count++ > 200) {
+			loop_count = 0;
+			schedule();
 		}
 	}
+	page_cache_release(desc->page);
+	desc->page = NULL;
+
+	dfprintk(VFS, "NFS: nfs_do_filldir() filling ended @ cookie %Lu; returning = %d\n", (long long)desc->target, res);
+	return res;
 }
 
 /*
- * Free directory cache memory
- * Called from cleanup_module
+ * If we cannot find a cookie in our cache, we suspect that this is
+ * because it points to a deleted file, so we ask the server to return
+ * whatever it thinks is the next entry. We then feed this to filldir.
+ * If all goes well, we should then be able to find our way round the
+ * cache on the next call to readdir_search_pagecache();
+ *
+ * NOTE: we cannot add the anonymous page to the pagecache because
+ *	 the data it contains might not be page aligned. Besides,
+ *	 we should already have a complete representation of the
+ *	 directory in the page cache by the time we get here.
  */
-void
-nfs_free_dircache(void)
+static inline
+int uncached_readdir(nfs_readdir_descriptor_t *desc, void *dirent,
+		     filldir_t filldir)
+{
+	struct file	*file = desc->file;
+	struct dentry	*dir = file->f_dentry;
+	struct inode	*inode = dir->d_inode;
+	struct nfs_fattr dir_attr;
+	struct page	*page = NULL;
+	unsigned long	cache_page;
+	u32		*p;
+	int		status = -EIO;
+
+	dfprintk(VFS, "NFS: uncached_readdir() searching for cookie %Lu\n", (long long)desc->target);
+	if (desc->page) {
+		page_cache_release(desc->page);
+		desc->page = NULL;
+	}
+
+	cache_page = page_cache_alloc();
+	if (!cache_page) {
+		status = -ENOMEM;
+		goto out;
+	}
+	page = page_cache_entry(cache_page);
+	p = (u32 *)page_address(page);
+	status = NFS_CALL(readdir, inode, (dir, &dir_attr,
+					   nfs_file_cred(file),
+					   desc->target, p,
+					   NFS_SERVER(inode)->dtsize, 0));
+	nfs_refresh_inode(inode, &dir_attr);
+	if (status >= 0) {
+		p = desc->decode(p, desc->entry, 0);
+		if (IS_ERR(p))
+			status = PTR_ERR(p);
+		else
+			desc->entry->prev_cookie = desc->target;
+	}
+	if (status < 0)
+		goto out_release;
+
+	desc->page_index = 0;
+	desc->page_offset = 0;
+	desc->page = page;
+	status = nfs_do_filldir(desc, dirent, filldir);
+
+	/* Reset read descriptor so it searches the page cache from
+	 * the start upon the next call to readdir_search_pagecache() */
+	desc->page_index = 0;
+	desc->page_offset = 0;
+	memset(desc->entry, 0, sizeof(*desc->entry));
+ out:
+	dfprintk(VFS, "NFS: uncached_readdir() returns %d\n", status);
+	return status;
+ out_release:
+	page_cache_release(page);
+	goto out;
+}
+
+/* The file offset position is now represented as a true offset into the
+ * page cache as is the case in most of the other filesystems.
+ */
+static int nfs_readdir(struct file *filp, void *dirent, filldir_t filldir)
 {
-	dfprintk(DIRCACHE, "NFS: freeing dircache\n");
-	nfs_invalidate_dircache_sb(NULL);
+	struct dentry	*dentry = filp->f_dentry;
+	struct inode	*inode = dentry->d_inode;
+	nfs_readdir_descriptor_t my_desc,
+			*desc = &my_desc;
+	struct nfs_entry my_entry;
+	long		res;
+
+	res = nfs_revalidate(dentry);
+	if (res < 0)
+		return res;
+
+	/*
+	 * filp->f_pos points to the file offset in the page cache.
+	 * but if the cache has meanwhile been zapped, we need to
+	 * read from the last dirent to revalidate f_pos
+	 * itself.
+	 */
+	memset(desc, 0, sizeof(*desc));
+	memset(&my_entry, 0, sizeof(my_entry));
+
+	desc->file = filp;
+	desc->target = filp->f_pos;
+	desc->entry = &my_entry;
+	desc->decode = NFS_PROTO(inode)->decode_dirent;
+
+	while(!desc->entry->eof) {
+		res = readdir_search_pagecache(desc);
+		if (res == -EBADCOOKIE) {
+			/* This means either end of directory */
+			if (desc->entry->cookie == desc->target) {
+				res = 0;
+				break;
+			}
+			/* Or that the server has 'lost' a cookie */
+			res = uncached_readdir(desc, dirent, filldir);
+			if (res >= 0)
+				continue;
+		}
+		if (res < 0)
+			break;
+
+		res = nfs_do_filldir(desc, dirent, filldir);
+		if (res < 0) {
+			res = 0;
+			break;
+		}
+	}
+	if (desc->page)
+		page_cache_release(desc->page);
+	if (desc->error < 0)
+		return desc->error;
+	if (res < 0)
+		return res;
+	return 0;
 }
 
+
 /*
  * Whenever an NFS operation succeeds, we know that the dentry
  * is valid, so we update the revalidation timestamp.
  */
-static inline void nfs_renew_times(struct dentry * dentry)
+static inline void
+nfs_renew_times(struct dentry * dentry)
 {
-	dentry->d_time = jiffies;
+		dentry->d_time = jiffies;
 }
 
 static inline int nfs_dentry_force_reval(struct dentry *dentry, int flags)
@@ -384,7 +483,7 @@
 		if (diff < 15*60)
 			timeout = 0;
 	}
-	
+
 	return time_after(jiffies,dentry->d_time + timeout);
 }
 
@@ -398,8 +497,9 @@
 #define NFS_REVALIDATE_NEGATIVE (1 * HZ)
 static inline int nfs_neg_need_reval(struct dentry *dentry)
 {
-	unsigned long timeout = NFS_ATTRTIMEO(dentry->d_parent->d_inode);
-	long diff = CURRENT_TIME - dentry->d_parent->d_inode->i_mtime;
+	struct inode *dir = dentry->d_parent->d_inode;
+	unsigned long timeout = NFS_ATTRTIMEO(dir);
+	long diff = CURRENT_TIME - dir->i_mtime;
 
 	if (diff < 5*60 && timeout > NFS_REVALIDATE_NEGATIVE)
 		timeout = NFS_REVALIDATE_NEGATIVE;
@@ -421,11 +521,12 @@
  */
 static int nfs_lookup_revalidate(struct dentry * dentry, int flags)
 {
-	struct dentry * parent = dentry->d_parent;
-	struct inode * inode = dentry->d_inode;
+	struct dentry		*dir = dentry->d_parent;
+	struct inode		*inode = dentry->d_inode,
+				*dir_i = dir->d_inode;
+	struct nfs_fh		fhandle;
+	struct nfs_fattr	fattr, dir_attr;
 	int error;
-	struct nfs_fh fhandle;
-	struct nfs_fattr fattr;
 
 	/*
 	 * If we don't have an inode, let's look at the parent
@@ -440,39 +541,52 @@
 
 	if (is_bad_inode(inode)) {
 		dfprintk(VFS, "nfs_lookup_validate: %s/%s has dud inode\n",
-			parent->d_name.name, dentry->d_name.name);
+			dir->d_name.name, dentry->d_name.name);
 		goto out_bad;
 	}
 
-	if (IS_ROOT(dentry))
-		goto out_valid;
-
 	if (!nfs_dentry_force_reval(dentry, flags))
 		goto out_valid;
 
+	if (IS_ROOT(dentry)) {
+		__nfs_revalidate_inode(dentry);
+		goto out_valid_renew;
+	}
+
+	if (NFS_FLAGS(inode) & NFS_INO_STALE)
+		goto out_bad;
+
 	/*
 	 * Do a new lookup and check the dentry attributes.
 	 */
-	error = nfs_proc_lookup(NFS_DSERVER(parent), NFS_FH(parent),
-				dentry->d_name.name, &fhandle, &fattr);
-	if (error)
+	error = NFS_CALL(lookup, dir_i, (dir, &dir_attr,
+				  &dentry->d_name, &fhandle, &fattr));
+	if (error < 0)
 		goto out_bad;
 
 	/* Inode number matches? */
-	if (fattr.fileid != inode->i_ino)
+	if (!(fattr.valid & NFS_ATTR_FATTR) ||
+	    NFS_FSID(inode) != fattr.fsid ||
+	    NFS_FILEID(inode) != fattr.fileid)
 		goto out_bad;
 
 	/* Filehandle matches? */
-	if (memcmp(dentry->d_fsdata, &fhandle, sizeof(struct nfs_fh)))
+	if (NFS_FH(dentry)->size == 0)
+		goto out_bad;
+
+	if (NFS_FH(dentry)->size != fhandle.size ||
+	    memcmp(NFS_FH(dentry)->data, fhandle.data, fhandle.size))
 		goto out_bad;
 
 	/* Ok, remeber that we successfully checked it.. */
-	nfs_renew_times(dentry);
 	nfs_refresh_inode(inode, &fattr);
+	nfs_refresh_inode(dir_i, &dir_attr);
 
-out_valid:
+ out_valid_renew:
+	nfs_renew_times(dentry);
+ out_valid:
 	return 1;
-out_bad:
+ out_bad:
 	if (!list_empty(&dentry->d_subdirs))
 		shrink_dcache_parent(dentry);
 	/* If we have submounts, don't unhash ! */
@@ -480,35 +594,37 @@
 		goto out_valid;
 	d_drop(dentry);
 	if (dentry->d_parent->d_inode)
-		nfs_invalidate_dircache(dentry->d_parent->d_inode);
+		NFS_CACHEINV(dentry->d_parent->d_inode);
 	if (inode && S_ISDIR(inode->i_mode))
-		nfs_invalidate_dircache(inode);
+		NFS_CACHEINV(inode);
 	return 0;
 }
 
 /*
  * This is called from dput() when d_count is going to 0.
- * We use it to clean up silly-renamed files.
  */
 static void nfs_dentry_delete(struct dentry *dentry)
 {
-	dfprintk(VFS, "NFS: dentry_delete(%s/%s, %x)\n",
-		dentry->d_parent->d_name.name, dentry->d_name.name,
-		dentry->d_flags);
-
 	if (dentry->d_flags & DCACHE_NFSFS_RENAMED) {
-		int error;
-		
-		dentry->d_flags &= ~DCACHE_NFSFS_RENAMED;
-		/* Unhash it first */
+		/* Unhash it, so that ->d_iput() would be called */
 		d_drop(dentry);
-		error = nfs_safe_remove(dentry);
-		if (error)
-			printk("NFS: can't silly-delete %s/%s, error=%d\n",
-				dentry->d_parent->d_name.name,
-				dentry->d_name.name, error);
 	}
+}
+
+__inline__ struct nfs_fh *nfs_fh_alloc(void)
+{
+	struct nfs_fh *p;
 
+	p = kmalloc(sizeof(*p), GFP_KERNEL);
+	if (!p)
+		return NULL;
+	memset(p, 0, sizeof(*p));
+	return p;
+}
+
+__inline__ void nfs_fh_free(struct nfs_fh *p)
+{
+	kfree(p);
 }
 
 /*
@@ -516,8 +632,21 @@
  */
 static void nfs_dentry_release(struct dentry *dentry)
 {
-	if (dentry->d_fsdata)
-		kfree(dentry->d_fsdata);
+	if (dentry->d_fsdata) {
+		nfs_fh_free(dentry->d_fsdata);
+		dentry->d_fsdata = NULL;
+	}
+}
+
+/*
+ * Called when the dentry loses inode.
+ * We use it to clean up silly-renamed files.
+ */
+static void nfs_dentry_iput(struct dentry *dentry, struct inode *inode)
+{
+	if (dentry->d_flags & DCACHE_NFSFS_RENAMED)
+		nfs_complete_unlink(dentry);
+	iput(inode);
 }
 
 struct dentry_operations nfs_dentry_operations = {
@@ -526,36 +655,47 @@
 	NULL,			/* d_compare */
 	nfs_dentry_delete,	/* d_delete(struct dentry *) */
 	nfs_dentry_release,	/* d_release(struct dentry *) */
-	NULL			/* d_iput */
+	nfs_dentry_iput		/* d_iput */
 };
 
-static struct dentry *nfs_lookup(struct inode *dir, struct dentry * dentry)
+static struct dentry *nfs_lookup(struct inode *dir_i, struct dentry * dentry)
 {
+	struct dentry *dir = dentry->d_parent;
 	struct inode *inode;
 	int error;
 	struct nfs_fh fhandle;
-	struct nfs_fattr fattr;
+	struct nfs_fattr fattr, dir_attr;
 
 	dfprintk(VFS, "NFS: lookup(%s/%s)\n",
 		dentry->d_parent->d_name.name, dentry->d_name.name);
 
 	error = -ENAMETOOLONG;
-	if (dentry->d_name.len > NFS_MAXNAMLEN)
+	if (dentry->d_name.len > NFS_SERVER(dir_i)->namelen)
 		goto out;
 
-	error = -ENOMEM;
+	dentry->d_op = &nfs_dentry_operations;
+
 	if (!dentry->d_fsdata) {
-		dentry->d_fsdata = kmalloc(sizeof(struct nfs_fh), GFP_KERNEL);
-		if (!dentry->d_fsdata)
+		dentry->d_fsdata = nfs_fh_alloc();
+		if (!dentry->d_fsdata) {
+			error = -ENOMEM;
 			goto out;
+		}
 	}
-	dentry->d_op = &nfs_dentry_operations;
 
-	error = nfs_proc_lookup(NFS_SERVER(dir), NFS_FH(dentry->d_parent), 
-				dentry->d_name.name, &fhandle, &fattr);
+#if NFS_FIXME
+	inode = nfs_dircache_lookup(dir_i, dentry);
+	if (inode)
+		goto no_entry;
+#endif
+
+	error = NFS_CALL(lookup, dir_i, (dir, &dir_attr,
+				 &dentry->d_name, &fhandle, &fattr));
+	nfs_refresh_inode(dir_i, &dir_attr);
 	inode = NULL;
 	if (error == -ENOENT)
 		goto no_entry;
+
 	if (!error) {
 		error = -EACCES;
 		inode = nfs_fhget(dentry, &fhandle, &fattr);
@@ -594,29 +734,46 @@
  * that the operation succeeded on the server, but an error in the
  * reply path made it appear to have failed.
  */
-static int nfs_create(struct inode *dir, struct dentry *dentry, int mode)
+static int nfs_create(struct inode *dir_i, struct dentry *dentry, int mode)
 {
-	int error;
-	struct nfs_sattr sattr;
-	struct nfs_fattr fattr;
-	struct nfs_fh fhandle;
+	struct dentry	*dir = dentry->d_parent;
+	struct iattr	 attr;
+	struct nfs_fattr fattr, dir_attr;
+	struct nfs_fh	 fhandle;
+	int		 error;
 
 	dfprintk(VFS, "NFS: create(%x/%ld, %s\n",
-		dir->i_dev, dir->i_ino, dentry->d_name.name);
+		dir_i->i_dev, dir_i->i_ino, dentry->d_name.name);
 
-	sattr.mode = mode;
-	sattr.uid = sattr.gid = sattr.size = (unsigned) -1;
-	sattr.atime.seconds = sattr.mtime.seconds = (unsigned) -1;
+#ifdef NFSD_BROKEN_UID
+	/* We set uid/gid in the request because IBM's broken nfsd
+	 * uses the root uid/gid otherwise. Argh!
+	 * (Hopefully the server will override the gid when the directory
+	 * has the sticky bit set. Irix may have a problem here...)
+	 */
+	attr.ia_mode = mode;
+	attr.ia_valid = ATTR_MODE | ATTR_UID | ATTR_GID;
+	attr.ia_uid = current->fsuid;
+	attr.ia_gid = current->fsgid;
+#else
+	attr.ia_mode = mode;
+	attr.ia_valid = ATTR_MODE;
+#endif
 
 	/*
 	 * Invalidate the dir cache before the operation to avoid a race.
+	 * The 0 argument passed into the create function should one day
+	 * contain the O_EXCL flag if requested. This allows NFSv3 to
+	 * select the appropriate create strategy. Currently open_namei
+	 * does not pass the create flags.
 	 */
-	nfs_invalidate_dircache(dir);
-	error = nfs_proc_create(NFS_SERVER(dir), NFS_FH(dentry->d_parent),
-			dentry->d_name.name, &sattr, &fhandle, &fattr);
-	if (!error)
+	nfs_zap_caches(dir_i);
+	error = NFS_CALL(create, dir_i, (dir, &dir_attr, &dentry->d_name,
+			&attr, 0, &fhandle, &fattr));
+	nfs_refresh_inode(dir_i, &dir_attr);
+	if (!error && fhandle.size != 0)
 		error = nfs_instantiate(dentry, &fhandle, &fattr);
-	if (error)
+	if (error || fhandle.size == 0)
 		d_drop(dentry);
 	return error;
 }
@@ -624,28 +781,35 @@
 /*
  * See comments for nfs_proc_create regarding failed operations.
  */
-static int nfs_mknod(struct inode *dir, struct dentry *dentry, int mode, int rdev)
+static int nfs_mknod(struct inode *dir_i, struct dentry *dentry, int mode, int rdev)
 {
-	int error;
-	struct nfs_sattr sattr;
-	struct nfs_fattr fattr;
-	struct nfs_fh fhandle;
+	struct dentry	*dir = dentry->d_parent;
+	struct iattr	 attr;
+	struct nfs_fattr fattr, dir_attr;
+	struct nfs_fh	 fhandle;
+	int		 error;
 
 	dfprintk(VFS, "NFS: mknod(%x/%ld, %s\n",
-		dir->i_dev, dir->i_ino, dentry->d_name.name);
+		dir_i->i_dev, dir_i->i_ino, dentry->d_name.name);
 
-	sattr.mode = mode;
-	sattr.uid = sattr.gid = sattr.size = (unsigned) -1;
-	if (S_ISCHR(mode) || S_ISBLK(mode))
-		sattr.size = rdev; /* get out your barf bag */
-	sattr.atime.seconds = sattr.mtime.seconds = (unsigned) -1;
-
-	nfs_invalidate_dircache(dir);
-	error = nfs_proc_create(NFS_SERVER(dir), NFS_FH(dentry->d_parent),
-				dentry->d_name.name, &sattr, &fhandle, &fattr);
-	if (!error)
+#ifdef NFSD_BROKEN_UID
+	attr.ia_valid = ATTR_MODE | ATTR_UID | ATTR_GID;
+	attr.ia_mode = mode;
+	attr.ia_uid = current->fsuid;
+	attr.ia_gid = current->fsgid;
+#else
+	attr.ia_valid = ATTR_MODE;
+	attr.ia_mode = mode;
+#endif
+
+
+	nfs_zap_caches(dir_i);
+	error = NFS_CALL(mknod, dir_i, (dir, &dir_attr, &dentry->d_name,
+				&attr, rdev, &fhandle, &fattr));
+	nfs_refresh_inode(dir_i, &dir_attr);
+	if (!error && fhandle.size != 0)
 		error = nfs_instantiate(dentry, &fhandle, &fattr);
-	if (error)
+	if (error || fhandle.size == 0)
 		d_drop(dentry);
 	return error;
 }
@@ -653,52 +817,55 @@
 /*
  * See comments for nfs_proc_create regarding failed operations.
  */
-static int nfs_mkdir(struct inode *dir, struct dentry *dentry, int mode)
+static int nfs_mkdir(struct inode *dir_i, struct dentry *dentry, int mode)
 {
-	int error;
-	struct nfs_sattr sattr;
-	struct nfs_fattr fattr;
-	struct nfs_fh fhandle;
-
-	dfprintk(VFS, "NFS: mkdir(%x/%ld, %s\n",
-		dir->i_dev, dir->i_ino, dentry->d_name.name);
+	struct dentry   *dir = dentry->d_parent;
+	struct iattr	 attr;
+	struct nfs_fattr fattr, dir_attr;
+	struct nfs_fh	 fhandle;
+	int		 error;
+
+	dfprintk(VFS, "NFS: mkdir(%x/%ld, %s)\n",
+		dir_i->i_dev, dir_i->i_ino, dentry->d_name.name);
+
+#ifdef NFSD_BROKEN_UID
+	attr.ia_valid = ATTR_MODE | ATTR_UID | ATTR_GID;
+	attr.ia_mode = mode | S_IFDIR;
+	attr.ia_uid = current->fsuid;
+	attr.ia_gid = current->fsgid;
+#else
+	attr.ia_valid = ATTR_MODE;
+	attr.ia_mode = mode | S_IFDIR;
+#endif
 
-	sattr.mode = mode | S_IFDIR;
-	sattr.uid = sattr.gid = sattr.size = (unsigned) -1;
-	sattr.atime.seconds = sattr.mtime.seconds = (unsigned) -1;
+	nfs_zap_caches(dir_i);
+	error = NFS_CALL(mkdir, dir_i, (dir, &dir_attr,
+				&dentry->d_name, &attr, &fhandle, &fattr));
+	nfs_refresh_inode(dir_i, &dir_attr);
+	if (!error && fhandle.size != 0)
+		error = nfs_instantiate(dentry, &fhandle, &fattr);
 
-	/*
-	 * Always drop the dentry, we can't always depend on
-	 * the fattr returned by the server (AIX seems to be
-	 * broken). We're better off doing another lookup than
-	 * depending on potentially bogus information.
-	 */
-	d_drop(dentry);
-	nfs_invalidate_dircache(dir);
-	error = nfs_proc_mkdir(NFS_DSERVER(dentry), NFS_FH(dentry->d_parent),
-				dentry->d_name.name, &sattr, &fhandle, &fattr);
-	if (!error)
-		dir->i_nlink++;
+	if (error || fhandle.size == 0)
+		d_drop(dentry);
 	return error;
 }
 
-static int nfs_rmdir(struct inode *dir, struct dentry *dentry)
+static int nfs_rmdir(struct inode *dir_i, struct dentry *dentry)
 {
-	int error;
+	struct dentry	*dir = dentry->d_parent;
+	struct nfs_fattr dir_attr;
+	int		 error;
 
 	dfprintk(VFS, "NFS: rmdir(%x/%ld, %s\n",
-		dir->i_dev, dir->i_ino, dentry->d_name.name);
+		dir_i->i_dev, dir_i->i_ino, dentry->d_name.name);
 
-	nfs_invalidate_dircache(dir);
-	error = nfs_proc_rmdir(NFS_SERVER(dir), NFS_FH(dentry->d_parent),
-				dentry->d_name.name);
+	nfs_zap_caches(dir_i);
+	error = NFS_CALL(rmdir, dir_i, (dir, &dir_attr, &dentry->d_name));
+	nfs_refresh_inode(dir_i, &dir_attr);
 
-	/* Update i_nlink and invalidate dentry. */
-	if (!error) {
-		d_drop(dentry);
-		if (dir->i_nlink)
-			dir->i_nlink--;
-	}
+	/* Free the inode */
+	if (!error)
+		d_delete(dentry);
 
 	return error;
 }
@@ -758,15 +925,18 @@
 	return sdentry;
 }
 
-static int nfs_sillyrename(struct inode *dir, struct dentry *dentry)
+static int nfs_sillyrename(struct inode *dir_i, struct dentry *dentry)
 {
+	struct dentry	*dir = dentry->d_parent;
 	static unsigned int sillycounter = 0;
-	const int      i_inosize  = sizeof(dir->i_ino)*2;
-	const int      countersize = sizeof(sillycounter)*2;
-	const int      slen       = strlen(".nfs") + i_inosize + countersize;
-	char           silly[slen+1];
-	struct dentry *sdentry;
-	int            error = -EIO;
+	struct nfs_fattr dir_attr;
+	const int        i_inosize  = sizeof(dir_i->i_ino)*2;
+	const int        countersize = sizeof(sillycounter)*2;
+	const int        slen       = strlen(".nfs") + i_inosize + countersize;
+	struct qstr      qsilly;
+	char             silly[slen+1];
+	struct dentry *  sdentry;
+	int              error = -EIO;
 
 	dfprintk(VFS, "NFS: silly-rename(%s/%s, ct=%d)\n",
 		dentry->d_parent->d_name.name, dentry->d_name.name, 
@@ -776,15 +946,13 @@
 	 * Note that a silly-renamed file can be deleted once it's
 	 * no longer in use -- it's just an ordinary file now.
 	 */
-	if (dentry->d_count == 1) {
-		dentry->d_flags &= ~DCACHE_NFSFS_RENAMED;
+	if (dentry->d_count == 1)
 		goto out;  /* No need to silly rename. */
-	}
 
 #ifdef NFS_PARANOIA
-if (!dentry->d_inode)
-printk("NFS: silly-renaming %s/%s, negative dentry??\n",
-dentry->d_parent->d_name.name, dentry->d_name.name);
+	if (!dentry->d_inode)
+		printk(KERN_ERR "NFS: silly-renaming %s/%s, negative dentry??\n",
+		       dentry->d_parent->d_name.name, dentry->d_name.name);
 #endif
 	/*
 	 * We don't allow a dentry to be silly-renamed twice.
@@ -800,7 +968,8 @@
 	do {
 		char *suffix = silly + slen - countersize;
 
-		dput(sdentry);
+		if (sdentry)
+			dput(sdentry);
 		sillycounter++;
 		sprintf(suffix, "%*.*x", countersize, countersize, sillycounter);
 
@@ -816,14 +985,16 @@
 			goto out;
 	} while(sdentry->d_inode != NULL); /* need negative lookup */
 
-	nfs_invalidate_dircache(dir);
-	error = nfs_proc_rename(NFS_SERVER(dir),
-				NFS_FH(dentry->d_parent), dentry->d_name.name,
-				NFS_FH(dentry->d_parent), silly);
+	nfs_zap_caches(dir_i);
+	qsilly.name = silly;
+	qsilly.len  = strlen(silly);
+	error = NFS_CALL(rename, dir_i, (dir, &dir_attr, &dentry->d_name,
+				  dir, &dir_attr, &qsilly));
+	nfs_refresh_inode(dir_i, &dir_attr);
 	if (!error) {
 		nfs_renew_times(dentry);
 		d_move(dentry, sdentry);
-		dentry->d_flags |= DCACHE_NFSFS_RENAMED;
+		error = nfs_async_unlink(dentry);
  		/* If we return 0 we don't unlink */
 	}
 	dput(sdentry);
@@ -840,54 +1011,63 @@
  */
 static int nfs_safe_remove(struct dentry *dentry)
 {
-	struct inode *dir = dentry->d_parent->d_inode;
-	struct inode *inode = dentry->d_inode;
-	int error, rehash = 0;
+	struct nfs_fattr dir_attr;
+	struct dentry	*dir = dentry->d_parent;
+	struct inode	*dir_i = dir->d_inode,   
+			*inode = dentry->d_inode;
+	int		 error = 0, rehash = 0;
 		
-	dfprintk(VFS, "NFS: safe_remove(%s/%s, %ld)\n",
-		dentry->d_parent->d_name.name, dentry->d_name.name,
-		inode->i_ino);
+	/*
+	 * Unhash the dentry while we remove the file ...
+	 */
+	if (!list_empty(&dentry->d_hash)) {
+		d_drop(dentry);
+		rehash = 1;
+	}
+
+	/* If the dentry was sillyrenamed, we simply call d_delete() */
+	if (dentry->d_flags & DCACHE_NFSFS_RENAMED)
+		goto out_delete;
+
+	dfprintk(VFS, "NFS: safe_remove(%s/%s)\n",
+		dentry->d_parent->d_name.name, dentry->d_name.name);
 
 	/* N.B. not needed now that d_delete is done in advance? */
 	error = -EBUSY;
 	if (!inode) {
 #ifdef NFS_PARANOIA
-printk("nfs_safe_remove: %s/%s already negative??\n",
-dentry->d_parent->d_name.name, dentry->d_name.name);
+		printk(KERN_ERR "nfs_safe_remove: %s/%s already negative??\n",
+		       dentry->d_parent->d_name.name, dentry->d_name.name);
 #endif
 	}
 
 	if (dentry->d_count > 1) {
 #ifdef NFS_PARANOIA
-printk("nfs_safe_remove: %s/%s busy, d_count=%d\n",
-dentry->d_parent->d_name.name, dentry->d_name.name, dentry->d_count);
+		printk(KERN_INFO "nfs_safe_remove: %s/%s busy, d_count=%d\n",
+		       dentry->d_parent->d_name.name, dentry->d_name.name, dentry->d_count);
 #endif
 		goto out;
 	}
+
+	nfs_zap_caches(dir_i);
+	if (inode)
+		NFS_CACHEINV(inode);
+	error = NFS_CALL(remove, dir_i, (dir, &dir_attr, &dentry->d_name));
+	nfs_refresh_inode(dir_i, &dir_attr);
+	if (error < 0)
+		goto out;
+
+ out_delete:
 	/*
-	 * Unhash the dentry while we remove the file ...
-	 */
-	if (!list_empty(&dentry->d_hash)) {
-		d_drop(dentry);
-		rehash = 1;
-	}
-	/*
-	 * Update i_nlink and free the inode before unlinking.
+	 * Free the inode
 	 */
-	if (inode) {
-		if (inode->i_nlink)
-			inode->i_nlink --;
-		d_delete(dentry);
-	}
-	nfs_invalidate_dircache(dir);
-	error = nfs_proc_remove(NFS_SERVER(dir), NFS_FH(dentry->d_parent),
-				dentry->d_name.name);
+	d_delete(dentry);
+out:
 	/*
 	 * Rehash the negative dentry if the operation succeeded.
 	 */
-	if (!error && rehash)
-		d_add(dentry, NULL);
-out:
+	if (rehash)
+		d_rehash(dentry);
 	return error;
 }
 
@@ -903,6 +1083,8 @@
 	dfprintk(VFS, "NFS: unlink(%x/%ld, %s)\n",
 		dir->i_dev, dir->i_ino, dentry->d_name.name);
 
+	if (dentry->d_inode)
+		nfs_wb_all(dentry->d_inode);
 	error = nfs_sillyrename(dir, dentry);
 	if (error && error != -EBUSY) {
 		error = nfs_safe_remove(dentry);
@@ -914,45 +1096,60 @@
 }
 
 static int
-nfs_symlink(struct inode *dir, struct dentry *dentry, const char *symname)
+nfs_symlink(struct inode *dir_i, struct dentry *dentry, const char *symname)
 {
-	struct nfs_sattr sattr;
-	int error;
+	struct dentry	*dir = dentry->d_parent;
+	struct nfs_fattr dir_attr, sym_attr;
+	struct nfs_fh    sym_fh;
+	struct iattr     attr;
+	struct qstr      qsymname;
+	int              error, mode, maxlen;
 
 	dfprintk(VFS, "NFS: symlink(%x/%ld, %s, %s)\n",
-		dir->i_dev, dir->i_ino, dentry->d_name.name, symname);
+		dir_i->i_dev, dir_i->i_ino, dentry->d_name.name, symname);
 
 	error = -ENAMETOOLONG;
-	if (strlen(symname) > NFS_MAXPATHLEN)
+	maxlen = (NFS_PROTO(dir_i)->version==2) ? NFS2_MAXPATHLEN : NFS3_MAXPATHLEN;
+	if (strlen(symname) > maxlen)
 		goto out;
 
 #ifdef NFS_PARANOIA
-if (dentry->d_inode)
-printk("nfs_proc_symlink: %s/%s not negative!\n",
-dentry->d_parent->d_name.name, dentry->d_name.name);
+	if (dentry->d_inode)
+		printk(KERN_WARNING "nfs_proc_symlink: %s/%s not negative!\n",
+		       dentry->d_parent->d_name.name, dentry->d_name.name);
 #endif
 	/*
 	 * Fill in the sattr for the call.
+
  	 * Note: SunOS 4.1.2 crashes if the mode isn't initialized!
 	 */
-	sattr.mode = S_IFLNK | S_IRWXUGO;
-	sattr.uid = sattr.gid = sattr.size = (unsigned) -1;
-	sattr.atime.seconds = sattr.mtime.seconds = (unsigned) -1;
+#ifdef NFSD_BROKEN_UID
+	attr.ia_valid = ATTR_MODE|ATTR_UID|ATTR_GID;
+	attr.ia_mode = mode = S_IFLNK | S_IRWXUGO;
+	attr.ia_uid = current->fsuid;
+	attr.ia_gid = current->fsgid;
+#else
+	attr.ia_valid = ATTR_MODE;
+	attr.ia_mode = mode = S_IFLNK | S_IRWXUGO;
+#endif
 
-	/*
-	 * Drop the dentry in advance to force a new lookup.
-	 * Since nfs_proc_symlink doesn't return a fattr, we
-	 * can't instantiate the new inode.
-	 */
-	d_drop(dentry);
-	nfs_invalidate_dircache(dir);
-	error = nfs_proc_symlink(NFS_SERVER(dir), NFS_FH(dentry->d_parent),
-				dentry->d_name.name, symname, &sattr);
-	if (!error) {
-		nfs_renew_times(dentry->d_parent);
-	} else if (error == -EEXIST) {
-		printk("nfs_proc_symlink: %s/%s already exists??\n",
-			dentry->d_parent->d_name.name, dentry->d_name.name);
+
+	qsymname.name = symname;
+	qsymname.len  = strlen(symname);
+
+	nfs_zap_caches(dir_i);
+	error = NFS_CALL(symlink, dir_i, (dir, &dir_attr,
+				&dentry->d_name, &qsymname, &attr,
+				&sym_fh, &sym_attr));
+	nfs_refresh_inode(dir_i, &dir_attr);
+	if (!error && sym_fh.size != 0 && (sym_attr.valid & NFS_ATTR_FATTR)) {
+		error = nfs_instantiate(dentry, &sym_fh, &sym_attr);
+	} else {
+		if (error == -EEXIST)
+			printk(KERN_INFO "nfs_proc_symlink: %s/%s already exists??\n",
+			       dentry->d_parent->d_name.name,
+			       dentry->d_name.name);
+		d_drop(dentry);
 	}
 
 out:
@@ -960,10 +1157,12 @@
 }
 
 static int 
-nfs_link(struct dentry *old_dentry, struct inode *dir, struct dentry *dentry)
+nfs_link(struct dentry *old_dentry, struct inode *dir_i, struct dentry *dentry)
 {
-	struct inode *inode = old_dentry->d_inode;
-	int error;
+	struct dentry	*dir = dentry->d_parent;
+	struct inode	*inode = old_dentry->d_inode;
+	struct nfs_fattr old_attr, dir_attr;
+	int		 error;
 
 	dfprintk(VFS, "NFS: link(%s/%s -> %s/%s)\n",
 		old_dentry->d_parent->d_name.name, old_dentry->d_name.name,
@@ -975,16 +1174,12 @@
 	 * we can't use the existing dentry.
 	 */
 	d_drop(dentry);
-	nfs_invalidate_dircache(dir);
-	error = nfs_proc_link(NFS_DSERVER(old_dentry), NFS_FH(old_dentry),
-				NFS_FH(dentry->d_parent), dentry->d_name.name);
-	if (!error) {
- 		/*
-		 * Update the link count immediately, as some apps
-		 * (e.g. pine) test this after making a link.
-		 */
-		inode->i_nlink++;
-	}
+	nfs_zap_caches(dir_i);
+	NFS_CACHEINV(inode);
+	error = NFS_CALL(link, inode, (old_dentry, &old_attr,
+				       dir, &dir_attr, &dentry->d_name));
+	nfs_refresh_inode(inode, &old_attr);
+	nfs_refresh_inode(dir_i, &dir_attr);
 	return error;
 }
 
@@ -1011,14 +1206,28 @@
  * no pending writes (if it's a file), and the use count must be 1.
  * If these conditions are met, we can drop the dentries before doing
  * the rename.
+ *
+ * FIXME: Sun seems to take this even one step further. The connectathon
+ * test suite has a file that renames open file A to open file B,
+ * and expects a silly rename to happen for B.
  */
 static int nfs_rename(struct inode *old_dir, struct dentry *old_dentry,
 		      struct inode *new_dir, struct dentry *new_dentry)
 {
-	struct inode *old_inode = old_dentry->d_inode;
-	struct inode *new_inode = new_dentry->d_inode;
-	struct dentry *dentry = NULL;
-	int error, rehash = 0;
+	struct nfs_fattr old_attr, new_attr;
+	struct inode *   old_inode = old_dentry->d_inode;
+	struct inode *   new_inode = new_dentry->d_inode;
+	struct dentry *  dentry = NULL, *rehash = NULL;
+	int              error;
+
+	/*
+	 * To prevent any new references to the target during the rename,
+	 * we unhash the dentry and free the inode in advance.
+	 */
+	if (!list_empty(&new_dentry->d_hash)) {
+		d_drop(new_dentry);
+		rehash = new_dentry;
+	}
 
 	dfprintk(VFS, "NFS: rename(%s/%s -> %s/%s, ct=%d)\n",
 		old_dentry->d_parent->d_name.name, old_dentry->d_name.name,
@@ -1036,6 +1245,8 @@
 	 * With directories check is done in VFS.
 	 */
 	error = -EBUSY;
+	if (new_inode)
+		nfs_wb_all(new_inode);
 	if (new_dentry->d_count > 1 && new_inode) {
 		int err;
 		/* copy the target dentry's name */
@@ -1047,17 +1258,18 @@
 		/* silly-rename the existing target ... */
 		err = nfs_sillyrename(new_dir, new_dentry);
 		if (!err) {
-			new_dentry = dentry;
+			new_dentry = rehash = dentry;
 			new_inode = NULL;
 			/* hash the replacement target */
-			d_add(new_dentry, NULL);
+			d_instantiate(new_dentry, NULL);
 		}
 
 		/* dentry still busy? */
 		if (new_dentry->d_count > 1) {
 #ifdef NFS_PARANOIA
-printk("nfs_rename: target %s/%s busy, d_count=%d\n",
-new_dentry->d_parent->d_name.name,new_dentry->d_name.name,new_dentry->d_count);
+		printk(KERN_INFO "nfs_rename: target %s/%s busy, d_count=%d\n",
+			new_dentry->d_parent->d_name.name,
+			new_dentry->d_name.name,new_dentry->d_count);
 #endif
 			goto out;
 		}
@@ -1073,40 +1285,75 @@
 
 	if (new_dentry->d_count > 1 && new_inode) {
 #ifdef NFS_PARANOIA
-printk("nfs_rename: new dentry %s/%s busy, d_count=%d\n",
-new_dentry->d_parent->d_name.name,new_dentry->d_name.name,new_dentry->d_count);
+		printk(KERN_INFO "nfs_rename: new dentry %s/%s busy, d_count=%d\n",
+			new_dentry->d_parent->d_name.name,new_dentry->d_name.name,new_dentry->d_count);
 #endif
 		goto out;
 	}
 
-	/*
-	 * To prevent any new references to the target during the rename,
-	 * we unhash the dentry and free the inode in advance.
-	 */
-	if (!list_empty(&new_dentry->d_hash)) {
-		d_drop(new_dentry);
-		rehash = 1;
-	}
 	if (new_inode)
 		d_delete(new_dentry);
 
-	nfs_invalidate_dircache(new_dir);
-	nfs_invalidate_dircache(old_dir);
-	error = nfs_proc_rename(NFS_DSERVER(old_dentry),
-			NFS_FH(old_dentry->d_parent), old_dentry->d_name.name,
-			NFS_FH(new_dentry->d_parent), new_dentry->d_name.name);
+	nfs_zap_caches(new_dir);
+	nfs_zap_caches(old_dir);
+	error = NFS_CALL(rename, old_dir,
+			 (old_dentry->d_parent, &old_attr, &old_dentry->d_name,
+			  new_dentry->d_parent, &new_attr, &new_dentry->d_name));
+	nfs_refresh_inode(old_dir, &old_attr);
+	nfs_refresh_inode(new_dir, &new_attr);
 
+out:
 	/* Update the dcache if needed */
 	if (rehash)
-		d_add(new_dentry, NULL);
+		d_rehash(rehash);
 	if (!error && !S_ISDIR(old_inode->i_mode))
 		d_move(old_dentry, new_dentry);
-
-out:
 	/* new dentry created? */
 	if (dentry)
 		dput(dentry);
 	return error;
+}
+
+int
+nfs_permission(struct inode *i, int msk)
+{
+	struct nfs_fattr	fattr;
+	struct dentry		*de = NULL;
+	int			err = vfs_permission(i, msk);
+	struct list_head	*start, *tmp;
+
+	if (!NFS_PROTO(i)->access)
+		goto out;
+	/*
+	 * Trust UNIX mode bits except:
+	 *
+	 * 1) When override capabilities may have been invoked
+	 * 2) When root squashing may be involved
+	 * 3) When ACLs may overturn a negative answer */
+	if (!capable(CAP_DAC_OVERRIDE) && !capable(CAP_DAC_READ_SEARCH)
+	    && (current->fsuid != 0) && (current->fsgid != 0)
+	    && err != -EACCES)
+		goto out;
+
+	tmp = start = &i->i_dentry;
+	while ((tmp = tmp->next) != start) {
+		de = list_entry(tmp, struct dentry, d_alias);
+		if (de->d_inode == i)
+			break;
+	}
+	if (!de || de->d_inode != i)
+		return 0;
+
+	err = NFS_CALL(access, i, (de, msk, &fattr, 0));
+
+	if (err == -EACCES && NFS_CLIENT(i)->cl_droppriv &&
+	    current->uid != 0 && current->gid != 0 &&
+	    (current->fsuid != current->uid || current->fsgid != current->gid))
+		err = NFS_CALL(access, i, (de, msk, &fattr, 1));
+
+	nfs_refresh_inode(i, &fattr);
+ out:
+	return err;
 }
 
 /*

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