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

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

diff -u --recursive --new-file v2.1.31/linux/fs/nfs/dir.c linux/fs/nfs/dir.c
@@ -6,19 +6,38 @@
  *  nfs directory handling functions
  *
  * 10 Apr 1996	Added silly rename for unlink	--okir
+ * 28 Sep 1996	Improved directory cache --okir
  */
 
 #include <linux/sched.h>
 #include <linux/errno.h>
 #include <linux/stat.h>
-#include <linux/nfs_fs.h>
 #include <linux/fcntl.h>
 #include <linux/string.h>
 #include <linux/kernel.h>
 #include <linux/malloc.h>
 #include <linux/mm.h>
+#include <linux/sunrpc/types.h>
+#include <linux/nfs_fs.h>
 
-#include <asm/uaccess.h>	/* for fs functions */
+#include <asm/segment.h>	/* for fs functions */
+
+/*
+ * 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;		/* cooke 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;
+	struct nfs_entry *	entry;
+};
 
 static int nfs_dir_open(struct inode * inode, struct file * file);
 static long nfs_dir_read(struct inode *, struct file *, char *, unsigned long);
@@ -39,7 +58,7 @@
 	nfs_dir_read,		/* read - bad */
 	NULL,			/* write - bad */
 	nfs_readdir,		/* readdir */
-	NULL,			/* poll - default */
+	NULL,			/* select - default */
 	NULL,			/* ioctl - default */
 	NULL,			/* mmap */
 	nfs_dir_open,		/* open - revalidate */
@@ -64,42 +83,26 @@
 	NULL,			/* writepage */
 	NULL,			/* bmap */
 	NULL,			/* truncate */
-	NULL			/* permission */
+	NULL,			/* permission */
+	NULL,			/* smap */
+	NULL,			/* updatepage */
+	nfs_revalidate,		/* revalidate */
 };
 
-static inline void revalidate_dir(struct nfs_server * server, struct inode * dir)
-{
-	struct nfs_fattr fattr;
-
-	if (jiffies - NFS_READTIME(dir) < NFS_ATTRTIMEO(dir))
-		return;
-
-	NFS_READTIME(dir) = jiffies;
-	if (nfs_proc_getattr(server, NFS_FH(dir), &fattr) == 0) {
-		nfs_refresh_inode(dir, &fattr);
-		if (fattr.mtime.seconds == NFS_OLDMTIME(dir)) {
-			if ((NFS_ATTRTIMEO(dir) <<= 1) > server->acdirmax)
-				NFS_ATTRTIMEO(dir) = server->acdirmax;
-			return;
-		}
-		NFS_OLDMTIME(dir) = fattr.mtime.seconds;
-	}
-	/* invalidate directory cache here when we _really_ start caching */
-}
-
-static int nfs_dir_open(struct inode * dir, struct file * file)
+static int
+nfs_dir_open(struct inode *dir, struct file *file)
 {
-	revalidate_dir(NFS_SERVER(dir), dir);
-	return 0;
+	dfprintk(VFS, "NFS: nfs_dir_open(%x/%ld)\n", dir->i_dev, dir->i_ino);
+	return nfs_revalidate_inode(NFS_SERVER(dir), dir);
 }
 
-static long nfs_dir_read(struct inode *inode, struct file *filp,
-	char *buf, unsigned long count)
+static long
+nfs_dir_read(struct inode *inode, struct file *filp, char *buf, unsigned long count)
 {
 	return -EISDIR;
 }
 
-static struct nfs_entry *c_entry = NULL;
+static struct nfs_dirent	dircache[NFS_MAX_DIRCACHE];
 
 /*
  * We need to do caching of directory entries to prevent an
@@ -107,119 +110,222 @@
  * 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.
+ *
+ * XXX: Do proper directory caching by stuffing data into the
+ * page cache (may require some fiddling for rsize < PAGE_SIZE).
  */
 
-static int nfs_readdir(struct inode *inode, struct file *filp,
-		       void *dirent, filldir_t filldir)
-{
-	static kdev_t c_dev = 0;
-	static int c_ino;
-	static int c_size;
-
-	int result;
-	int i, index = 0;
-	struct nfs_entry *entry;
+static int
+nfs_readdir(struct inode *inode, struct file *filp, void *dirent,
+						filldir_t filldir)
+{
+	static struct wait_queue *readdir_wait = NULL;
+	struct wait_queue	**waitp = NULL;
+	struct nfs_dirent	*cache, *free;
+	struct nfs_entry	*entry;
+	unsigned long		age, dead;
+	u32			cookie;
+	int			ismydir, result;
+	int			i, j, index = 0;
 
+	dfprintk(VFS, "NFS: nfs_readdir(%x/%ld)\n", inode->i_dev, inode->i_ino);
 	if (!inode || !S_ISDIR(inode->i_mode)) {
 		printk("nfs_readdir: inode is NULL or not a directory\n");
 		return -EBADF;
 	}
 
-	revalidate_dir(NFS_SERVER(inode), inode);
+	if ((result = nfs_revalidate_inode(NFS_SERVER(inode), inode)) < 0)
+		return result;
 
-	/* initialize cache memory if it hasn't been used before */
+	/*
+	 * Try to find the entry in the cache
+	 */
+again:
+	if (waitp) {
+		interruptible_sleep_on(waitp);
+		if (current->signal & ~current->blocked)
+			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 (c_entry == NULL) {
-		i = sizeof (struct nfs_entry)*NFS_READDIR_CACHE_SIZE;
-		c_entry = (struct nfs_entry *) kmalloc(i, GFP_KERNEL);
-		if (c_entry == NULL) {
-			printk("nfs_readdir: no MEMORY for cache\n");
-			return -ENOMEM;
-		}
-		for (i = 0; i < NFS_READDIR_CACHE_SIZE; i++) {
-			c_entry[i].name = (char *) kmalloc(NFS_MAXNAMLEN + 1,
-				GFP_KERNEL);
-			if (c_entry[i].name == NULL) {
-				printk("nfs_readdir: no MEMORY for cache\n");
-				while (--i>=0)
-					kfree(c_entry[i].name);
-				kfree(c_entry);
-				c_entry = NULL;
-				return -ENOMEM;
-			}
+		if (ismydir && cache->mtime != NFS_OLDMTIME(inode))
+			cache->valid = 0;
+
+		if (!cache->valid || cache->age < dead) {
+			free = cache;
+			age  = 0;
+		} else if (cache->age < age) {
+			free = cache;
+			age  = cache->age;
 		}
-	}
-	entry = NULL;
 
-	/* try to find it in the cache */
+		if (!ismydir || !cache->valid)
+			continue;
 
-	if (inode->i_dev == c_dev && inode->i_ino == c_ino) {
-		for (i = 0; i < c_size; i++) {
-			if (filp->f_pos == c_entry[i].cookie) {
-				if (i == c_size - 1) {
-					if (c_entry[i].eof)
-						return 0;
-				}
-				else
-					entry = c_entry + (index = i + 1);
-				break;
+		if (cache->cookie == cookie && cache->size > 0) {
+			entry = cache->entry + (index = 0);
+			cache->locked = 1;
+			break;
+		}
+		for (j = 0; j < cache->size; j++) {
+			/*
+			dprintk("NFS: examing entry %.*s @%d\n",
+				(int) cache->entry[j].length,
+				cache->entry[j].name,
+				cache->entry[j].cookie);
+			 */
+			if (cache->entry[j].cookie != cookie)
+				continue;
+			if (j < cache->size - 1) {
+				entry = cache->entry + (index = j + 1);
+			} else if (cache->entry[j].eof) {
+				return 0;
 			}
+			break;
+		}
+		if (entry) {
+			dfprintk(DIRCACHE, "NFS: found dircache entry %d\n",
+						cache - dircache);
+			cache->locked = 1;
+			break;
 		}
 	}
 
-	/* if we didn't find it in the cache, revert to an nfs call */
-
-	if (!entry) {
-		result = nfs_proc_readdir(NFS_SERVER(inode), NFS_FH(inode),
-			filp->f_pos, NFS_READDIR_CACHE_SIZE, c_entry);
-		if (result < 0) {
-			c_dev = 0;
-			return result;
+	/*
+	 * 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;
 		}
-		if (result > 0) {
-			c_dev = inode->i_dev;
-			c_ino = inode->i_ino;
-			c_size = result;
-			entry = c_entry + (index = 0);
+		dfprintk(DIRCACHE, "NFS: using free dircache entry %d\n",
+				free - dircache);
+		cache->cookie = cookie;
+		cache->locked = 1;
+		cache->valid  = 0;
+		cache->dev    = inode->i_dev;
+		cache->ino    = inode->i_ino;
+		if (!cache->entry) {
+			cache->entry = (struct nfs_entry *)
+						get_free_page(GFP_KERNEL);
+			if (!cache->entry) {
+				result = -ENOMEM;
+				goto done;
+			}
 		}
-	}
 
-	/* if we found it in the cache or from an nfs call, return results */
-	if (!entry)
-		return 0;
-	while (index < c_size) {
-		int nextpos = entry->cookie;
-		if (filldir(dirent, entry->name, strlen(entry->name), filp->f_pos, entry->fileid) < 0)
-			break;
-		filp->f_pos = nextpos;
-		/* revalidate the cache if we slept in filldir() */
-		if (inode->i_dev != c_dev)
-			break;
-		if (inode->i_ino != c_ino)
+		result = nfs_proc_readdir(NFS_SERVER(inode), NFS_FH(inode),
+					cookie, PAGE_SIZE, cache->entry);
+		if (result <= 0)
+			goto done;
+		cache->size  = result;
+		cache->valid = 1;
+		entry = cache->entry + (index = 0);
+	}
+	cache->mtime = NFS_OLDMTIME(inode);
+	cache->age = jiffies;
+
+	/*
+	 * Yowza! We have a cache entry...
+	 */
+	while (index < cache->size) {
+		int	nextpos = entry->cookie;
+
+		/*
+		dprintk("NFS: filldir(%p, %.*s, %d, %d, %x, eof %x)\n", entry,
+				(int) entry->length, entry->name, entry->length,
+				(unsigned int) filp->f_pos,
+				entry->fileid, entry->eof);
+		 */
+
+		if (filldir(dirent, entry->name, entry->length, cookie, entry->fileid) < 0)
 			break;
-		if (nextpos != entry->cookie)
+		cookie = nextpos;
+		if (nextpos != entry->cookie) {
+			printk("nfs_readdir: shouldn't happen!\n");
 			break;
+		}
 		index++;
 		entry++;
 	}
-	return 0;
+	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);
+
+	return result;
 }
 
 /*
- * free cache memory
- * called from cleanup_module
+ * Invalidate dircache entries for inode
  */
+void
+nfs_invalidate_dircache(struct inode *inode)
+{
+	struct nfs_dirent *cache;
+	dev_t		dev = inode->i_dev;
+	ino_t		ino = inode->i_ino;
+	int		i;
+
+	dfprintk(DIRCACHE, "NFS: invalidate dircache for %x/%ld\n", dev, ino);
+	for (i = 0, cache = dircache; i < NFS_MAX_DIRCACHE; i++, cache++) {
+		if (!cache->locked && cache->dev == dev && cache->ino == ino)
+			cache->valid = 0;	/* brute force */
+	}
+}
 
-void nfs_kfree_cache(void)
+/*
+ * Free directory cache memory
+ * Called from cleanup_module
+ */
+void
+nfs_free_dircache(void)
 {
-	int i;
+	struct nfs_dirent *cache;
+	int		i;
 
-	if (c_entry == NULL)
-		return;
-	for (i = 0; i < NFS_READDIR_CACHE_SIZE; i++)
-		kfree(c_entry[i].name);
-	kfree(c_entry);
-	c_entry = NULL;
+	dfprintk(DIRCACHE, "NFS: freeing dircache\n");
+	for (i = 0, cache = dircache; i < NFS_MAX_DIRCACHE; i++, cache++) {
+		cache->valid = 0;
+		if (cache->locked) {
+			printk("nfs_kfree_cache: locked entry in dircache!\n");
+			continue;
+		}
+		if (cache->entry)
+			free_page((unsigned long) cache->entry);
+		cache->entry = NULL;
+	}
 }
  
 
@@ -234,15 +340,18 @@
  * Since the cache is not hashed yet, it is a good idea not to make it too
  * large because every lookup looks through the entire cache even
  * though most of them will fail.
+ *
+ * FIXME: The lookup cache should also cache failed lookups. This can
+ * be a considerable win on diskless clients.
  */
 
 static struct nfs_lookup_cache_entry {
-	kdev_t dev;
-	int inode;
-	char filename[NFS_MAXNAMLEN + 1];
-	struct nfs_fh fhandle;
+	kdev_t		dev;
+	ino_t		inode;
+	char		filename[NFS_MAXNAMLEN + 1];
+	struct nfs_fh	fhandle;
 	struct nfs_fattr fattr;
-	int expiration_date;
+	int		expiration_date;
 } nfs_lookup_cache[NFS_LOOKUP_CACHE_SIZE];
 
 static struct nfs_lookup_cache_entry *nfs_lookup_cache_index(struct inode *dir,
@@ -269,6 +378,8 @@
 
 	struct nfs_lookup_cache_entry *entry;
 
+	dfprintk(LOOKUPCACHE, "NFS: lookup_cache_lookup(%x/%ld, %s)\n",
+				dir->i_dev, dir->i_ino, filename);
 	if (!nfs_lookup_cache_in_use) {
 		memset(nfs_lookup_cache, 0, sizeof(nfs_lookup_cache));
 		nfs_lookup_cache_in_use = 1;
@@ -292,6 +403,9 @@
 	static int nfs_lookup_cache_pos = 0;
 	struct nfs_lookup_cache_entry *entry;
 
+	dfprintk(LOOKUPCACHE, "NFS: lookup_cache_add(%x/%ld, %s\n",
+				dir->i_dev, dir->i_ino, filename);
+
 	/* compensate for bug in SGI NFS server */
 	if (fattr->size == -1 || fattr->uid == -1 || fattr->gid == -1
 	    || fattr->atime.seconds == -1 || fattr->mtime.seconds == -1)
@@ -301,6 +415,7 @@
 		if (nfs_lookup_cache_pos == NFS_LOOKUP_CACHE_SIZE)
 			nfs_lookup_cache_pos = 0;
 	}
+
 	entry->dev = dir->i_dev;
 	entry->inode = dir->i_ino;
 	strcpy(entry->filename, filename);
@@ -314,9 +429,9 @@
 				    const char *filename)
 {
 	struct nfs_lookup_cache_entry *entry;
-	kdev_t dev;
-	int fileid;
-	int i;
+	kdev_t	dev;
+	ino_t	fileid;
+	int	i;
 
 	if (inode) {
 		dev = inode->i_dev;
@@ -328,6 +443,10 @@
 	}
 	else
 		return;
+
+	dfprintk(LOOKUPCACHE, "NFS: lookup_cache_remove(%x/%ld)\n",
+				dev, fileid);
+
 	for (i = 0; i < NFS_LOOKUP_CACHE_SIZE; i++) {
 		entry = nfs_lookup_cache + i;
 		if (entry->dev == dev && entry->fattr.fileid == fileid)
@@ -358,6 +477,9 @@
 	char name[len > NFS_MAXNAMLEN? 1 : len+1];
 	int error;
 
+	dfprintk(VFS, "NFS: lookup(%x/%ld, %.*s)\n",
+				dir->i_dev, dir->i_ino, len, __name);
+
 	*result = NULL;
 	if (!dir || !S_ISDIR(dir->i_mode)) {
 		printk("nfs_lookup: inode is NULL or not a directory\n");
@@ -399,6 +521,9 @@
 	struct nfs_fh fhandle;
 	int error;
 
+	dfprintk(VFS, "NFS: create(%x/%ld, %s\n",
+				dir->i_dev, dir->i_ino, name);
+
 	*result = NULL;
 	if (!dir || !S_ISDIR(dir->i_mode)) {
 		printk("nfs_create: inode is NULL or not a directory\n");
@@ -422,6 +547,7 @@
 		return -EACCES;
 	}
 	nfs_lookup_cache_add(dir, name, &fhandle, &fattr);
+	nfs_invalidate_dircache(dir);
 	iput(dir);
 	return 0;
 }
@@ -434,6 +560,9 @@
 	struct nfs_fh fhandle;
 	int error;
 
+	dfprintk(VFS, "NFS: mknod(%x/%ld, %s\n",
+				dir->i_dev, dir->i_ino, name);
+
 	if (!dir || !S_ISDIR(dir->i_mode)) {
 		printk("nfs_mknod: inode is NULL or not a directory\n");
 		iput(dir);
@@ -453,12 +582,8 @@
 	error = nfs_proc_create(NFS_SERVER(dir), NFS_FH(dir),
 		name, &sattr, &fhandle, &fattr);
 	if (!error)
-	{
 		nfs_lookup_cache_add(dir, name, &fhandle, &fattr);
-		/* The parent dir inode count may have changed ! */
-		nfs_lookup_cache_remove( NULL, dir, NULL);
-	}
-		
+	nfs_invalidate_dircache(dir);
 	iput(dir);
 	return error;
 }
@@ -470,6 +595,9 @@
 	struct nfs_fh fhandle;
 	int error;
 
+	dfprintk(VFS, "NFS: mkdir(%x/%ld, %s\n",
+				dir->i_dev, dir->i_ino, name);
+
 	if (!dir || !S_ISDIR(dir->i_mode)) {
 		printk("nfs_mkdir: inode is NULL or not a directory\n");
 		iput(dir);
@@ -484,12 +612,9 @@
 	sattr.atime.seconds = sattr.mtime.seconds = (unsigned) -1;
 	error = nfs_proc_mkdir(NFS_SERVER(dir), NFS_FH(dir),
 		name, &sattr, &fhandle, &fattr);
-	if (!error) {
-		if (fattr.fileid == dir->i_ino)
-			printk("Sony NewsOS 4.1R buggy nfs server?\n");
-		else
-			nfs_lookup_cache_add(dir, name, &fhandle, &fattr);
-	}
+	if (!error)
+		nfs_lookup_cache_add(dir, name, &fhandle, &fattr);
+	nfs_invalidate_dircache(dir);
 	iput(dir);
 	return error;
 }
@@ -498,6 +623,9 @@
 {
 	int error;
 
+	dfprintk(VFS, "NFS: rmdir(%x/%ld, %s\n",
+				dir->i_dev, dir->i_ino, name);
+
 	if (!dir || !S_ISDIR(dir->i_mode)) {
 		printk("nfs_rmdir: inode is NULL or not a directory\n");
 		iput(dir);
@@ -508,7 +636,9 @@
 		return -ENAMETOOLONG;
 	}
 	error = nfs_proc_rmdir(NFS_SERVER(dir), NFS_FH(dir), name);
-	nfs_lookup_cache_remove(dir, NULL, name);
+	if (!error)
+		nfs_lookup_cache_remove(dir, NULL, name);
+	nfs_invalidate_dircache(dir);
 	iput(dir);
 	return error;
 }
@@ -522,46 +652,78 @@
 	dir->i_count++;
 	if (nfs_lookup(dir, name, len, &inode) < 0)
 		return -EIO;		/* arbitrary */
-	if (inode->i_count == 1 || NFS_RENAMED_DIR(inode)) {
+
+	if (inode->i_count == 1) {
+		iput(inode);
+		return -EIO;
+	}
+	if (NFS_RENAMED_DIR(inode)) {
+		iput(NFS_RENAMED_DIR(inode));
+		NFS_RENAMED_DIR(inode) = NULL;
 		iput(inode);
 		return -EIO;
 	}
-	slen = sprintf(silly, ".nfs%ld", inode->i_ino);
 
+	slen = sprintf(silly, ".nfs%ld", inode->i_ino);
 	if (len == slen && !strncmp(name, silly, len)) {
 		iput(inode);
 		return -EIO;		/* DWIM */
 	}
+
+	dfprintk(VFS, "NFS: sillyrename(%x/%ld, %s)\n",
+				dir->i_dev, dir->i_ino, name);
+
 	ret = nfs_proc_rename(NFS_SERVER(dir), NFS_FH(dir), name,
-					       NFS_FH(dir), silly, 0);
+					       NFS_FH(dir), silly);
 	if (ret >= 0) {
 		nfs_lookup_cache_remove(dir, NULL, name);
 		nfs_lookup_cache_remove(dir, NULL, silly);
 		NFS_RENAMED_DIR(inode) = dir;
 		dir->i_count++;
 	}
+	nfs_invalidate_dircache(dir);
 	iput(inode);
 	return ret;
 }
 
+/*
+ * When releasing the inode, finally remove any unlinked but open files.
+ * Note that we have to clear the set of pending signals temporarily;
+ * otherwise the RPC call will fail.
+ */
 void nfs_sillyrename_cleanup(struct inode *inode)
 {
+	unsigned long	oldsig;
 	struct inode	*dir = NFS_RENAMED_DIR(inode);
 	char		silly[14];
 	int		error, slen;
 
+	dfprintk(VFS, "NFS: sillyrename cleanup(%x/%ld)\n",
+				inode->i_dev, inode->i_ino);
+
+	oldsig = current->signal;
+	current->signal = 0;
+
 	slen = sprintf(silly, ".nfs%ld", inode->i_ino);
-	if ((error = nfs_unlink(dir, silly, slen)) < 0) {
-		printk("NFS silly_rename cleanup failed (err = %d)\n",
-					-error);
-	}
+	error = nfs_proc_remove(NFS_SERVER(dir), NFS_FH(dir), silly);
+	if (error < 0)
+		printk("NFS: silly_rename cleanup failed (err %d)\n", -error);
+
+	nfs_lookup_cache_remove(dir, NULL, silly);
+	nfs_invalidate_dircache(dir);
 	NFS_RENAMED_DIR(inode) = NULL;
+	iput(dir);
+
+	current->signal |= oldsig;
 }
 
 static int nfs_unlink(struct inode *dir, const char *name, int len)
 {
 	int error;
 
+	dfprintk(VFS, "NFS: unlink(%x/%ld, %s)\n",
+				dir->i_dev, dir->i_ino, name);
+
 	if (!dir || !S_ISDIR(dir->i_mode)) {
 		printk("nfs_unlink: inode is NULL or not a directory\n");
 		iput(dir);
@@ -573,8 +735,10 @@
 	}
 	if ((error = nfs_sillyrename(dir, name, len)) < 0) {
 		error = nfs_proc_remove(NFS_SERVER(dir), NFS_FH(dir), name);
-		nfs_lookup_cache_remove(dir, NULL, name);
+		if (!error)
+			nfs_lookup_cache_remove(dir, NULL, name);
 	}
+	nfs_invalidate_dircache(dir);
 	iput(dir);
 	return error;
 }
@@ -585,6 +749,9 @@
 	struct nfs_sattr sattr;
 	int error;
 
+	dfprintk(VFS, "NFS: symlink(%x/%ld, %s, %s)\n",
+				dir->i_dev, dir->i_ino, name, symname);
+
 	if (!dir || !S_ISDIR(dir->i_mode)) {
 		printk("nfs_symlink: inode is NULL or not a directory\n");
 		iput(dir);
@@ -603,6 +770,7 @@
 	sattr.atime.seconds = sattr.mtime.seconds = (unsigned) -1;
 	error = nfs_proc_symlink(NFS_SERVER(dir), NFS_FH(dir),
 		name, symname, &sattr);
+	nfs_invalidate_dircache(dir);
 	iput(dir);
 	return error;
 }
@@ -612,6 +780,10 @@
 {
 	int error;
 
+	dfprintk(VFS, "NFS: link(%x/%ld -> %x/%ld, %s)\n",
+				oldinode->i_dev, oldinode->i_ino,
+				dir->i_dev, dir->i_ino, name);
+
 	if (!oldinode) {
 		printk("nfs_link: old inode is NULL\n");
 		iput(oldinode);
@@ -631,19 +803,35 @@
 	}
 	error = nfs_proc_link(NFS_SERVER(oldinode), NFS_FH(oldinode),
 		NFS_FH(dir), name);
-
-	nfs_lookup_cache_remove(dir, oldinode, NULL);
+	if (!error) {
+		nfs_lookup_cache_remove(dir, oldinode, NULL);
+		NFS_READTIME(oldinode) = 0;	/* force getattr */
+	}
+	nfs_invalidate_dircache(dir);
 	iput(oldinode);
 	iput(dir);
 	return error;
 }
 
+/*
+ * RENAME
+ * FIXME: Some nfsds, like the Linux user space nfsd, may generate a
+ * different file handle for the same inode after a rename (e.g. when
+ * moving to a different directory). A fail-safe method to do so would
+ * be to look up old_dir/old_name, create a link to new_dir/new_name and
+ * rename the old file using the silly_rename stuff. This way, the original
+ * file in old_dir will go away when the last process iput()s the inode.
+ */
 static int nfs_rename(struct inode *old_dir, const char *old_name, int old_len,
 		      struct inode *new_dir, const char *new_name, int new_len,
 		      int must_be_dir)
 {
 	int error;
 
+	dfprintk(VFS, "NFS: rename(%x/%ld, %s -> %x/%ld, %s)\n",
+				old_dir->i_dev, old_dir->i_ino, old_name,
+				new_dir->i_dev, new_dir->i_ino, new_name);
+
 	if (!old_dir || !S_ISDIR(old_dir->i_mode)) {
 		printk("nfs_rename: old inode is NULL or not a directory\n");
 		iput(old_dir);
@@ -661,13 +849,20 @@
 		iput(new_dir);
 		return -ENAMETOOLONG;
 	}
+
+	/* We don't do rename() with trailing slashes over NFS now. Hmm. */
+	if (must_be_dir)
+		return -EINVAL;
+
 	error = nfs_proc_rename(NFS_SERVER(old_dir),
 		NFS_FH(old_dir), old_name,
-		NFS_FH(new_dir), new_name,
-		must_be_dir);
-
-	nfs_lookup_cache_remove(old_dir, NULL, old_name);
-	nfs_lookup_cache_remove(new_dir, NULL, new_name);
+		NFS_FH(new_dir), new_name);
+	if (!error) {
+		nfs_lookup_cache_remove(old_dir, NULL, old_name);
+		nfs_lookup_cache_remove(new_dir, NULL, new_name);
+	}
+	nfs_invalidate_dircache(old_dir);
+	nfs_invalidate_dircache(new_dir);
 	iput(old_dir);
 	iput(new_dir);
 	return error;
@@ -683,6 +878,9 @@
 {
 	int was_empty;
 
+	dfprintk(VFS, "NFS: refresh_inode(%x/%ld ct=%d)\n",
+				inode->i_dev, inode->i_ino, inode->i_count);
+
 	if (!inode || !fattr) {
 		printk("nfs_refresh_inode: inode or fattr is NULL\n");
 		return;
@@ -698,10 +896,16 @@
 	inode->i_gid = fattr->gid;
 
 	/* Size changed from outside: invalidate caches on next read */
-	if (inode->i_size != fattr->size)
+	if (inode->i_size != fattr->size) {
+		dfprintk(PAGECACHE, "NFS:      cacheinv(%x/%ld)\n",
+					inode->i_dev, inode->i_ino);
 		NFS_CACHEINV(inode);
-	if (NFS_OLDMTIME(inode) != fattr->mtime.seconds)
+	}
+	if (NFS_OLDMTIME(inode) != fattr->mtime.seconds) {
+		dfprintk(PAGECACHE, "NFS:      mtime change on %x/%ld\n",
+					inode->i_dev, inode->i_ino);
 		NFS_ATTRTIMEO(inode) = NFS_MINATTRTIMEO(inode);
+	}
 	inode->i_size = fattr->size;
 	if (S_ISCHR(inode->i_mode) || S_ISBLK(inode->i_mode))
 		inode->i_rdev = to_kdev_t(fattr->rdev);

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen, slshen@lbl.gov