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

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

diff -u --recursive --new-file v2.3.28/linux/fs/nfsd/nfsfh.c linux/fs/nfsd/nfsfh.c
@@ -5,7 +5,7 @@
  *
  * Copyright (C) 1995, 1996 Olaf Kirch <okir@monad.swb.de>
  * Portions Copyright (C) 1999 G. Allen Morris III <gam3@acm.org>
- * Extensive cleanup by Neil Brown <neilb@cse.unsw.edu.au> Southern-Spring 1999
+ * Extensive rewrite by Neil Brown <neilb@cse.unsw.edu.au> Southern-Spring 1999
  */
 
 #include <linux/sched.h>
@@ -63,7 +63,7 @@
 }
 
 /*
- * Read a directory and return the name of the specified entry.
+ * Read a directory and return the name of the specified entry.  i_sem is already down().
  */
 static int get_ino_name(struct dentry *dentry, struct qstr *name, unsigned long ino)
 {
@@ -94,9 +94,7 @@
 	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;
 
@@ -118,7 +116,7 @@
 /* this should be provided by each filesystem in an nfsd_operations interface as
  * iget isn't really the right interface
  */
-static inline struct dentry *nfsd_iget(struct super_block *sb, unsigned long ino, __u32 generation)
+static struct dentry *nfsd_iget(struct super_block *sb, unsigned long ino, __u32 generation)
 {
 
 	/* iget isn't really right if the inode is currently unallocated!!
@@ -209,6 +207,7 @@
 	 * 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,
@@ -233,10 +232,62 @@
 		if (pdentry == NULL)
 			pdentry = ERR_PTR(-ENOMEM);
 	}
-	dput(tdentry); /* it was never rehashed, it will be discarded */
+	dput(tdentry); /* it is not hashed, it will be discarded */
 	return pdentry;
 }
 
+static struct dentry *splice(struct dentry *child, struct dentry *parent)
+{
+	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);
+
+	/* 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;
+		}
+	}
+	/* well, if we can find a name for child in parent, it should be safe to splice it in */
+	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;
+	}
+	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;
+}
+
 /*
  * This is the basic lookup mechanism for turning an NFS file handle
  * into a dentry.
@@ -248,15 +299,22 @@
 find_fh_dentry(struct super_block *sb, struct knfs_fh *fh, int needpath)
 {
 	struct dentry *dentry, *result = NULL;
-	struct qstr qs;
-	char namebuf[256];
+	struct dentry *tmp;
 	int  found =0;
 	u32 err;
+	/* This 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.  This could sensibly be per-filesystem.
+	 */
+	static DECLARE_MUTEX(free_path_sem);
 
-	qs.name = namebuf;
+	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))
@@ -269,10 +327,11 @@
 	if (!IS_ROOT(result) || result->d_inode->i_sb->s_root ==result)
 		return result;
 
-	/* result is now a "root" dentry, which may be adequate as it stands, or else
+	/* result is now an anonymous dentry, which may be adequate as it stands, or else
 	 * will get spliced into the dcache tree */
 
 	if (!S_ISDIR(result->d_inode->i_mode) && ! needpath) {
+		nfsdstats.fh_anon++;
 		return result;
 	}
 
@@ -280,8 +339,10 @@
 	 * location in the tree.
 	 */
 	dprintk("nfs_fh: need to look harder for %d/%d\n",sb->s_dev,fh->fh_ino);
+	down(&free_path_sem);
 	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 {
@@ -297,22 +358,26 @@
 			}
 			if (!IS_ROOT(dentry) || dentry->d_inode->i_sb->s_root ==dentry)
 				found = 1;
-			err = get_ino_name(dentry, &qs, result->d_inode->i_ino);
-			if (err)
-				goto err_dentry;
-
-			/* OK, we have the name in parent of inode,  lets fill in the dentry */
-			err = d_splice(result, dentry, &qs);
-			if (err)
+			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
+	} else {
+		nfsdstats.fh_nocache_dir++;
 		dentry = dget(result);
+	}
 
 	while(!found) {
 		/* LOOP INVARIANT */
-		/* haven't found a place in the tree yet, but we do have a path
+		/* 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;
@@ -334,23 +399,41 @@
 		if (!IS_ROOT(pdentry) || parent->i_sb->s_root == pdentry)
 			found = 1;
 
-		err = get_ino_name(pdentry, &qs, dentry->d_inode->i_ino);
-		if (err) {
+		tmp = splice(dentry, pdentry);
+		if (tmp != dentry) {
+			/* Something wrong.  We need to drop thw 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;
 		}
-		err = d_splice(dentry, pdentry, &qs);
-		dprintk("nfsd_fh: found name %s for ino %ld\n", dentry->d_name.name, dentry->d_inode->i_ino);
+		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(&free_path_sem);
+			goto retry;
+		}
 		dput(dentry);
 		dentry = pdentry;
 	}
 	dput(dentry);
+	up(&free_path_sem);
 	return result;
 
 err_dentry:
 	dput(dentry);
 err_result:
 	dput(result);
+	up(&free_path_sem);
 err_out:
 	if (err == -ESTALE)
 		nfsdstats.fh_stale++;

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