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
- Lines: 1897
- Date:
Sun Oct 15 21:15:17 2000
- Orig file:
v2.2.17/fs/nfsd/nfsfh.c
- Orig date:
Fri Apr 21 12:46:44 2000
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)