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
- Lines: 242
- Date:
Mon Nov 22 17:29:23 1999
- Orig file:
v2.3.28/linux/fs/nfsd/nfsfh.c
- Orig date:
Sun Nov 7 16:37:34 1999
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)