summaryrefslogtreecommitdiffstats
path: root/fs/nfsd
diff options
context:
space:
mode:
Diffstat (limited to 'fs/nfsd')
-rw-r--r--fs/nfsd/export.c140
-rw-r--r--fs/nfsd/nfsctl.c20
-rw-r--r--fs/nfsd/nfsfh.c1244
-rw-r--r--fs/nfsd/nfsproc.c15
-rw-r--r--fs/nfsd/nfsxdr.c17
-rw-r--r--fs/nfsd/stats.c10
-rw-r--r--fs/nfsd/vfs.c203
7 files changed, 1458 insertions, 191 deletions
diff --git a/fs/nfsd/export.c b/fs/nfsd/export.c
index 1a0267abe..4ea31ed33 100644
--- a/fs/nfsd/export.c
+++ b/fs/nfsd/export.c
@@ -26,6 +26,9 @@
#include <linux/nfsd/syscall.h>
#include <linux/lockd/bind.h>
+#define NFSDDBG_FACILITY NFSDDBG_EXPORT
+#define NFSD_PARANOIA 1
+
typedef struct svc_client svc_client;
typedef struct svc_export svc_export;
@@ -37,9 +40,7 @@ static svc_client * exp_getclientbyname(char *name);
static void exp_freeclient(svc_client *clp);
static void exp_unhashclient(svc_client *clp);
static int exp_verify_string(char *cp, int max);
-struct inode * exp_lnamei(char *pathname, int *errp);
-#define NFSDDBG_FACILITY NFSDDBG_EXPORT
#define CLIENT_HASHBITS 6
#define CLIENT_HASHMAX (1 << CLIENT_HASHBITS)
#define CLIENT_HASHMASK (CLIENT_HASHMAX - 1)
@@ -65,7 +66,7 @@ static struct wait_queue * hash_wait = NULL;
#define WRITELOCK 1
/*
- * Find the export entry matching xdev/xino.
+ * Find a client's export for a device.
*/
static inline svc_export *
exp_find(svc_client *clp, dev_t dev)
@@ -78,6 +79,9 @@ exp_find(svc_client *clp, dev_t dev)
return exp;
}
+/*
+ * Find the client's export entry matching xdev/xino.
+ */
svc_export *
exp_get(svc_client *clp, dev_t dev, ino_t ino)
{
@@ -90,8 +94,22 @@ exp_get(svc_client *clp, dev_t dev, ino_t ino)
}
/*
- * Look up the root inode of the parent fs.
- * We have to go through iget in order to allow for wait_on_inode.
+ * Check whether there are any exports for a device.
+ */
+static int
+exp_device_in_use(dev_t dev)
+{
+ struct svc_client *clp;
+
+ for (clp = clients; clp; clp = clp->cl_next) {
+ if (exp_find(clp, dev))
+ return 1;
+ }
+ return 0;
+}
+
+/*
+ * Look up the device of the parent fs.
*/
static inline int
nfsd_parentdev(dev_t *devp)
@@ -153,13 +171,12 @@ exp_export(struct nfsctl_export *nxp)
/* Try to lock the export table for update */
if ((err = exp_writelock()) < 0)
- return err;
+ goto out;
/* Look up client info */
- if (!(clp = exp_getclientbyname(nxp->ex_client))) {
- err = -EINVAL;
- goto finish;
- }
+ err = -EINVAL;
+ if (!(clp = exp_getclientbyname(nxp->ex_client)))
+ goto out_unlock;
/*
* If there's already an export for this file, assume this
@@ -167,54 +184,54 @@ exp_export(struct nfsctl_export *nxp)
*/
if ((exp = exp_find(clp, dev)) != NULL) {
/* Ensure there's only one export per FS. */
- if (exp->ex_ino != ino) {
- err = -EPERM;
- } else {
+ err = -EPERM;
+ if (exp->ex_ino == ino) {
exp->ex_flags = nxp->ex_flags;
exp->ex_anon_uid = nxp->ex_anon_uid;
exp->ex_anon_gid = nxp->ex_anon_gid;
err = 0;
}
- goto finish;
+ goto out_unlock;
}
/* Look up the dentry */
+ err = -EINVAL;
dentry = lookup_dentry(nxp->ex_path, NULL, 0);
- if (IS_ERR(dentry)) {
- err = -EINVAL;
- goto finish;
- }
+ if (IS_ERR(dentry))
+ goto out_unlock;
+
+ err = -ENOENT;
inode = dentry->d_inode;
- if(!inode) {
- err = -ENOENT;
+ if(!inode)
goto finish;
- }
+ err = -EINVAL;
if(inode->i_dev != nxp->ex_dev || inode->i_ino != nxp->ex_ino) {
/* I'm just being paranoid... */
- err = -EINVAL;
goto finish;
}
/* We currently export only dirs. */
- if (!S_ISDIR(inode->i_mode)) {
- err = -ENOTDIR;
+ err = -ENOTDIR;
+ if (!S_ISDIR(inode->i_mode))
goto finish;
- }
/* If this is a sub-export, must be root of FS */
+ err = -EINVAL;
if ((parent = exp_parent(clp, dev)) != NULL) {
struct super_block *sb = inode->i_sb;
- if (sb && (inode != sb->s_root->d_inode)) {
- err = -EINVAL;
+ if (inode != sb->s_root->d_inode) {
+#ifdef NFSD_PARANOIA
+printk("exp_export: sub-export %s not root of device %s\n",
+nxp->ex_path, kdevname(sb->s_dev));
+#endif
goto finish;
}
}
- if (!(exp = kmalloc(sizeof(*exp), GFP_USER))) {
- err = -ENOMEM;
+ err = -ENOMEM;
+ if (!(exp = kmalloc(sizeof(*exp), GFP_USER)))
goto finish;
- }
dprintk("nfsd: created export entry %p for client %p\n", exp, clp);
strcpy(exp->ex_path, nxp->ex_path);
@@ -246,14 +263,16 @@ exp_export(struct nfsctl_export *nxp)
err = 0;
-finish:
- /* Release dentry */
- if (err < 0 && !IS_ERR(dentry))
- dput(dentry);
-
/* Unlock hashtable */
+out_unlock:
exp_unlock();
+out:
return err;
+
+ /* Release the dentry */
+finish:
+ dput(dentry);
+ goto out_unlock;
}
/*
@@ -277,12 +296,21 @@ exp_do_unexport(svc_export *unexp)
exp->ex_parent = unexp->ex_parent;
}
+ /*
+ * Check whether this is the last export for this device,
+ * and if so flush any cached dentries.
+ */
+ if (!exp_device_in_use(unexp->ex_dev)) {
+printk("exp_do_unexport: %s last use, flushing cache\n",
+kdevname(unexp->ex_dev));
+ nfsd_fh_flush(unexp->ex_dev);
+ }
+
dentry = unexp->ex_dentry;
inode = dentry->d_inode;
if (unexp->ex_dev != inode->i_dev || unexp->ex_ino != inode->i_ino)
printk(KERN_WARNING "nfsd: bad dentry in unexport!\n");
- else
- dput(dentry);
+ dput(dentry);
kfree(unexp);
}
@@ -325,15 +353,19 @@ exp_unexport(struct nfsctl_export *nxp)
return -EINVAL;
if ((err = exp_writelock()) < 0)
- return err;
+ goto out;
err = -EINVAL;
- if ((clp = exp_getclientbyname(nxp->ex_client)) != NULL) {
+ clp = exp_getclientbyname(nxp->ex_client);
+ if (clp) {
+printk("exp_unexport: found client %s\n", nxp->ex_client);
expp = clp->cl_export + EXPORT_HASH(nxp->ex_dev);
while ((exp = *expp) != NULL) {
if (exp->ex_dev == nxp->ex_dev) {
- if (exp->ex_ino != nxp->ex_ino)
+ if (exp->ex_ino != nxp->ex_ino) {
+printk("exp_unexport: ino mismatch, %ld not %ld\n", exp->ex_ino, nxp->ex_ino);
break;
+ }
*expp = exp->ex_next;
exp_do_unexport(exp);
err = 0;
@@ -344,6 +376,7 @@ exp_unexport(struct nfsctl_export *nxp)
}
exp_unlock();
+out:
return err;
}
@@ -404,7 +437,7 @@ exp_writelock(void)
while (hash_count || hash_lock)
interruptible_sleep_on(&hash_wait);
want_lock--;
- if (current->signal & ~current->blocked)
+ if (signal_pending(current))
return -EINTR;
hash_lock = 1;
return 0;
@@ -481,27 +514,27 @@ exp_addclient(struct nfsctl_client *ncp)
int i, err, change = 0, ilen;
/* First, consistency check. */
+ err = -EINVAL;
if (!(ilen = exp_verify_string(ncp->cl_ident, NFSCLNT_IDMAX)))
- return -EINVAL;
+ goto out;
if (ncp->cl_naddr > NFSCLNT_ADDRMAX)
- return -EINVAL;
+ goto out;
/* Lock the hashtable */
if ((err = exp_writelock()) < 0)
- return err;
+ goto out;
/* First check if this is a change request for a client. */
for (clp = clients; clp; clp = clp->cl_next)
if (!strcmp(clp->cl_ident, ncp->cl_ident))
break;
+ err = -ENOMEM;
if (clp) {
change = 1;
} else {
- if (!(clp = kmalloc(sizeof(*clp), GFP_KERNEL))) {
- exp_unlock();
- return -ENOMEM;
- }
+ if (!(clp = kmalloc(sizeof(*clp), GFP_KERNEL)))
+ goto out_unlock;
memset(clp, 0, sizeof(*clp));
dprintk("created client %s (%p)\n", ncp->cl_ident, clp);
@@ -512,13 +545,13 @@ exp_addclient(struct nfsctl_client *ncp)
/* Allocate hash buckets */
for (i = 0; i < ncp->cl_naddr; i++) {
- if (!(ch[i] = kmalloc(GFP_KERNEL, sizeof(ch[0])))) {
+ ch[i] = kmalloc(sizeof(struct svc_clnthash), GFP_KERNEL);
+ if (!ch[i]) {
while (i--)
kfree(ch[i]);
if (!change)
kfree(clp);
- exp_unlock();
- return -ENOMEM;
+ goto out_unlock;
}
}
@@ -548,9 +581,12 @@ exp_addclient(struct nfsctl_client *ncp)
clp->cl_next = clients;
clients = clp;
}
+ err = 0;
+out_unlock:
exp_unlock();
- return 0;
+out:
+ return err;
}
/*
diff --git a/fs/nfsd/nfsctl.c b/fs/nfsd/nfsctl.c
index 1db374bc6..d681a92b5 100644
--- a/fs/nfsd/nfsctl.c
+++ b/fs/nfsd/nfsctl.c
@@ -17,11 +17,11 @@
#include <linux/fcntl.h>
#include <linux/net.h>
#include <linux/in.h>
-#include <linux/nfs.h>
#include <linux/version.h>
#include <linux/unistd.h>
#include <linux/malloc.h>
+#include <linux/nfs.h>
#include <linux/sunrpc/svc.h>
#include <linux/nfsd/nfsd.h>
#include <linux/nfsd/cache.h>
@@ -38,6 +38,7 @@
#include <linux/smp.h>
#include <linux/smp_lock.h>
+extern void nfsd_fh_init(void);
extern long sys_call_table[];
static int nfsctl_svc(struct nfsctl_svc *data);
@@ -64,6 +65,7 @@ nfsd_init(void)
nfsd_export_init(); /* Exports table */
nfsd_lockd_init(); /* lockd->nfsd callbacks */
nfsd_racache_init(); /* Readahead param cache */
+ nfsd_fh_init(); /* FH table */
initialized = 1;
}
@@ -141,31 +143,33 @@ asmlinkage handle_sys_nfsservctl(int cmd, void *opaque_argp, void *opaque_resp)
union nfsctl_res * res = NULL;
int err;
+ MOD_INC_USE_COUNT;
lock_kernel ();
if (!initialized)
nfsd_init();
+ err = -EPERM;
if (!suser()) {
- err = -EPERM;
goto done;
}
+ err = -EFAULT;
if (!access_ok(VERIFY_READ, argp, sizeof(*argp))
|| (resp && !access_ok(VERIFY_WRITE, resp, sizeof(*resp)))) {
- err = -EFAULT;
goto done;
}
+
+ err = -ENOMEM; /* ??? */
if (!(arg = kmalloc(sizeof(*arg), GFP_USER)) ||
(resp && !(res = kmalloc(sizeof(*res), GFP_USER)))) {
- err = -ENOMEM; /* ??? */
goto done;
}
+
+ err = -EINVAL;
copy_from_user(arg, argp, sizeof(*argp));
if (arg->ca_version != NFSCTL_VERSION) {
printk(KERN_WARNING "nfsd: incompatible version in syscall.\n");
- err = -EINVAL;
goto done;
}
- MOD_INC_USE_COUNT;
switch(cmd) {
case NFSCTL_SVC:
err = nfsctl_svc(&arg->ca_svc);
@@ -193,7 +197,6 @@ asmlinkage handle_sys_nfsservctl(int cmd, void *opaque_argp, void *opaque_resp)
default:
err = -EINVAL;
}
- MOD_DEC_USE_COUNT;
if (!err && resp)
copy_to_user(resp, res, sizeof(*resp));
@@ -205,6 +208,7 @@ done:
kfree(res);
unlock_kernel ();
+ MOD_DEC_USE_COUNT;
return err;
}
@@ -224,7 +228,6 @@ int
init_module(void)
{
printk("Installing knfsd (copyright (C) 1996 okir@monad.swb.de).\n");
- nfsd_init();
do_nfsservctl = handle_sys_nfsservctl;
return 0;
}
@@ -242,6 +245,7 @@ cleanup_module(void)
do_nfsservctl = NULL;
nfsd_export_shutdown();
nfsd_cache_shutdown();
+ nfsd_fh_free();
#ifdef CONFIG_PROC_FS
nfsd_stat_shutdown();
#endif
diff --git a/fs/nfsd/nfsfh.c b/fs/nfsd/nfsfh.c
index abb2f7fd3..ff341f012 100644
--- a/fs/nfsd/nfsfh.c
+++ b/fs/nfsd/nfsfh.c
@@ -7,18 +7,994 @@
*/
#include <linux/sched.h>
+#include <linux/malloc.h>
#include <linux/fs.h>
#include <linux/unistd.h>
#include <linux/string.h>
#include <linux/stat.h>
+#include <linux/dcache.h>
#include <linux/sunrpc/svc.h>
#include <linux/nfsd/nfsd.h>
#define NFSDDBG_FACILITY NFSDDBG_FH
+#define NFSD_PARANOIA 1
+/* #define NFSD_DEBUG_VERBOSE 1 */
+
+extern unsigned long num_physpages;
+
+#define NFSD_FILE_CACHE 0
+#define NFSD_DIR_CACHE 1
+struct fh_entry {
+ struct dentry * dentry;
+ unsigned long reftime;
+ ino_t ino;
+ dev_t dev;
+};
+
+#define NFSD_MAXFH PAGE_SIZE/sizeof(struct fh_entry)
+static struct fh_entry filetable[NFSD_MAXFH];
+static struct fh_entry dirstable[NFSD_MAXFH];
+
+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);
+static int nfsd_d_validate(struct dentry *);
+struct dentry * lookup_inode(dev_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_FIXUPAGE 60*HZ
+
+struct nfsd_fixup {
+ struct list_head lru;
+ ino_t dir;
+ ino_t ino;
+ dev_t dev;
+ struct dentry *dentry;
+ unsigned long reftime;
+};
+
+struct nfsd_path {
+ struct list_head lru;
+ unsigned long reftime;
+ int users;
+ ino_t ino;
+ dev_t dev;
+ char name[1];
+};
+
+static struct nfsd_fixup * find_cached_lookup(dev_t dev, ino_t dir, 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);
+ if (fp->ino != ino)
+ continue;
+ if (fp->dir != dir)
+ continue;
+ if (fp->dev != dev)
+ continue;
+ list_del(tmp);
+ list_add(tmp, &fixup_head);
+ fp->reftime = jiffies;
+ return fp;
+ }
+ return NULL;
+}
+
+/*
+ * Save the dentry pointer from a successful lookup.
+ */
+static void add_to_lookup_cache(struct dentry *dentry, struct knfs_fh *fh)
+{
+ struct nfsd_fixup *fp;
+
+ fp = find_cached_lookup(fh->fh_dev, fh->fh_dirino, fh->fh_ino);
+ if (fp) {
+ fp->dentry = dentry;
+ 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->dir = fh->fh_dirino;
+ fp->ino = fh->fh_ino;
+ fp->dev = fh->fh_dev;
+ fp->dentry = dentry;
+ fp->reftime = jiffies;
+ list_add(&fp->lru, &fixup_head);
+ nfsd_nr_fixups++;
+ }
+}
+
+static void free_fixup_entry(struct nfsd_fixup *fp)
+{
+ list_del(&fp->lru);
+ 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: cacheing %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("add_to_path_cache: path length changed, retrying\n");
+ goto restart;
+}
+
+/*
+ * Search for a path entry for the specified (dev, inode).
+ */
+struct nfsd_path *get_path_entry(dev_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("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? */
+ 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.
+ */
+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;
+ int result = 0;
+
+ buf->sequence++;
+#ifdef NFSD_DEBUG_VERBOSE
+printk("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;
+ 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.
+ */
+static int get_parent_ino(struct dentry *dentry, struct nfsd_dirent *dirent)
+{
+ struct inode *dir = dentry->d_inode;
+ int error;
+ struct file file;
+ struct nfsd_getdents_callback buffer;
+
+ error = -ENOTDIR;
+ if (!dir || !S_ISDIR(dir->i_mode))
+ goto out;
+ error = -EINVAL;
+ if (!dir->i_op || !dir->i_op->default_file_ops)
+ goto out;
+ /*
+ * Open the directory ...
+ */
+ error = init_private_file(&file, dentry, FMODE_READ);
+ if (error)
+ goto out;
+ error = -EINVAL;
+ if (!file.f_op->readdir)
+ goto out_close;
+
+ buffer.dirent = dirent;
+ buffer.dirino = 0;
+ 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;
+
+ error = 0;
+ if (buffer.found)
+ break;
+ error = -ENOENT;
+ if (old_seq == buffer.sequence)
+ break;
+ }
+ dirent->ino = buffer.dirino;
+
+out_close:
+ if (file.f_op->release)
+ file.f_op->release(dir, &file);
+out:
+ 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.
+ */
+struct dentry * lookup_inode(dev_t dev, ino_t dirino, ino_t ino)
+{
+ 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.
+ */
+ while (1) {
+ if (ino == root_ino) {
+ if (*name == '/')
+ name++;
+ /*
+ * Note: this dput()s the root dentry.
+ */
+ result = lookup_dentry(name, root, 0);
+ break;
+ }
+
+ result = ERR_PTR(-ENOENT);
+ dir = iget(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("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;
+}
+
+/*
+ * 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;
+
+ 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;
+ }
+ }
+ 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, d_count=%d, ino=%ld\n",
+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 list_head *tmp;
+ struct fh_entry *fhe;
+ int i;
+
+#ifdef NFSD_DEBUG_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);
+ }
+
+ /*
+ * Remove old entries from the patch-up cache.
+ */
+ while ((tmp = fixup_head.prev) != &fixup_head) {
+ struct nfsd_fixup *fp;
+ fp = list_entry(tmp, struct nfsd_fixup, lru);
+ if ((jiffies - fp->reftime) < NFSD_MAX_FIXUPAGE)
+ 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 filehandles 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;
+
+ 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;
+ }
+
+repeat:
+ fhe = find_fhe(dentry, cache, &empty);
+ if (fhe) {
+ return 0;
+ }
+
+ /*
+ * Not found ... make a new entry.
+ */
+ if (empty) {
+ empty->dentry = dentry;
+ empty->reftime = jiffies;
+ empty->ino = inode->i_ino;
+ empty->dev = inode->i_dev;
+ return 1;
+ }
+
+ 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(dev_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;
+ }
+ }
+ return NULL;
+}
+
+/*
+ * Find the (directory) dentry with the specified (dev, inode) number.
+ * Note: this leaves the dentry in the cache.
+ */
+static struct dentry *find_dentry_by_ino(dev_t dev, ino_t ino)
+{
+ 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) {
+#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 {
+#ifdef NFSD_PARANOIA
+ printk("find_dentry_by_ino: %s has no root??\n",
+ kdevname(dev));
+#endif
+ }
+ }
+ }
+
+ /*
+ * 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++;
+ }
+ } else {
+ dput(res);
+ }
+ } else {
+#ifdef NFSD_PARANOIA
+printk("find_dentry_by_ino: %s lookup failed\n", pe->name);
+#endif
+ }
+ put_path(pe);
+ }
+out:
+ return dentry;
+}
+
+/*
+ * 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)
+{
+ struct fh_entry * fhe;
+
+ fhe = find_fhe(fh->fh_dcookie, NFSD_FILE_CACHE, NULL);
+ if (fhe) {
+ struct dentry *parent, *dentry = fhe->dentry;
+ struct inode *inode = dentry->d_inode;
+ 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
+ goto out;
+ }
+ if (inode->i_ino != fh->fh_ino || inode->i_dev != 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:
+ 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.
+ */
+ dirent.ino = ino;
+ error = get_parent_ino(parent, &dirent);
+ if (error) {
+#ifdef NFSD_PARANOIA
+printk("lookup_by_inode: ino %ld not found in %s\n", ino, parent->d_name.name);
+#endif
+ goto no_entry;
+ }
+#ifdef NFSD_PARANOIA
+printk("lookup_by_inode: found %s\n", dirent.name);
+#endif
+
+ dentry = lookup_dentry(dirent.name, dget(parent), 0);
+ if (!IS_ERR(dentry)) {
+ if (dentry->d_inode && dentry->d_inode->i_ino == ino)
+ goto out;
+#ifdef NFSD_PARANOIA
+printk("lookup_by_inode: %s/%s inode mismatch??\n",
+parent->d_name.name, dentry->d_name.name);
+#endif
+ dput(dentry);
+ } else {
+#ifdef NFSD_PARANOIA
+printk("lookup_by_inode: %s lookup failed, error=%ld\n",
+dirent.name, PTR_ERR(dentry));
+#endif
+ }
+
+no_entry:
+ dentry = NULL;
+out:
+ return dentry;
+
+}
+
+/*
+ * Search the fix-up list for a dentry from a prior lookup.
+ */
+static struct dentry *nfsd_cached_lookup(struct knfs_fh *fh)
+{
+ struct nfsd_fixup *fp;
+
+ fp = find_cached_lookup(fh->fh_dev, fh->fh_dirino, fh->fh_ino);
+ if (fp)
+ return fp->dentry;
+ return NULL;
+}
+
+/*
+ * The is the basic lookup mechanism for turning an NFS filehandle
+ * 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 filehandle,
+ * 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 dentry *dentry, *parent;
+ int looked_up = 0, retry = 0;
+
+ /*
+ * Stage 1: Look for the dentry in the short-term fhcache.
+ */
+ dentry = find_dentry_in_fhcache(fh);
+ if (dentry) {
+ nfsdstats.fh_cached++;
+ goto out;
+ }
+
+ /*
+ * Stage 2: Attempt to validate the dentry in the filehandle.
+ */
+ dentry = fh->fh_dcookie;
+recheck:
+ if (nfsd_d_validate(dentry)) {
+ struct inode * dir = dentry->d_parent->d_inode;
+
+ if (dir->i_ino == fh->fh_dirino && dir->i_dev == fh->fh_dev) {
+ struct inode * inode = dentry->d_inode;
+ /*
+ * NFS filehandles must always have an inode,
+ * so we won't accept a negative dentry.
+ */
+ if (inode && inode->i_ino == fh->fh_ino) {
+ dget(dentry);
+#ifdef NFSD_DEBUG_VERBOSE
+printk("find_fh_dentry: validated %s/%s, ino=%ld\n",
+dentry->d_parent->d_name.name, dentry->d_name.name, inode->i_ino);
+#endif
+ if (!retry)
+ nfsdstats.fh_valid++;
+ else {
+ nfsdstats.fh_fixup++;
+#ifdef NFSD_DEBUG_VERBOSE
+printk("find_fh_dentry: retried validation successful\n");
+#endif
+ }
+ goto out;
+ }
+ }
+ }
+
+ /*
+ * Before proceeding to a lookup, check whether we cached a
+ * prior lookup. If so, try to validate that dentry ...
+ */
+ if (!retry && (dentry = nfsd_cached_lookup(fh)) != NULL) {
+ retry = 1;
+ goto recheck;
+ }
+
+ /*
+ * 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(fh->fh_dev, fh->fh_dirino, 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 == fh->fh_ino) {
+ nfsdstats.fh_lookup++;
+ goto out;
+ }
+#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);
+ }
+
+ /*
+ * Stage 4: Look for the parent dentry in the fhcache ...
+ */
+ parent = find_dentry_by_ino(fh->fh_dev, fh->fh_dirino);
+ if (parent) {
+ /*
+ * ... then search for the inode in the parent directory.
+ */
+ dentry = lookup_by_inode(parent, fh->fh_ino);
+ dput(parent);
+ if (dentry)
+ goto out;
+ }
+
+ /*
+ * Stage 5: Search the whole volume.
+ */
+#ifdef NFSD_PARANOIA
+printk("find_fh_dentry: %s, %ld/%ld not found -- need full search!\n",
+kdevname(fh->fh_dev), fh->fh_dirino, fh->fh_ino);
+#endif
+ dentry = NULL;
+ nfsdstats.fh_stale++;
+
+out:
+ if (looked_up && dentry) {
+ add_to_lookup_cache(dentry, fh);
+ }
+
+ /*
+ * Perform any needed housekeeping ...
+ * N.B. move this into one of the daemons ...
+ */
+ if (jiffies >= nfsd_next_expire) {
+ expire_old(NFSD_FILE_CACHE, 5*HZ);
+ expire_old(NFSD_DIR_CACHE , 60*HZ);
+ nfsd_next_expire = jiffies + 5*HZ;
+ }
+ return dentry;
+}
/*
* Perform sanity checks on the dentry in a client's file handle.
+ *
+ * Note that the filehandle dentry may need to be freed even after
+ * an error return.
*/
u32
fh_verify(struct svc_rqst *rqstp, struct svc_fh *fhp, int type, int access)
@@ -27,50 +1003,53 @@ fh_verify(struct svc_rqst *rqstp, struct svc_fh *fhp, int type, int access)
struct dentry *dentry;
struct inode *inode;
struct knfs_fh *fh = &fhp->fh_handle;
+ u32 error = 0;
if(fhp->fh_dverified)
- return 0;
+ goto out;
dprintk("nfsd: fh_lookup(exp %x/%ld fh %p)\n",
- fh->fh_xdev, fh->fh_xino, fh->fh_dentry);
+ fh->fh_xdev, fh->fh_xino, fh->fh_dcookie);
- /* Look up the export entry. */
+ /*
+ * Look up the export entry.
+ * N.B. We need to lock this while in use ...
+ */
+ error = nfserr_stale;
exp = exp_get(rqstp->rq_client, fh->fh_xdev, fh->fh_xino);
- if (!exp) {
- /* nfsdstats.fhstale++; */
- return nfserr_stale; /* export entry revoked */
- }
+ if (!exp) /* export entry revoked */
+ 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 (%08lx:%d)!\n",
ntohl(rqstp->rq_addr.sin_addr.s_addr),
ntohs(rqstp->rq_addr.sin_port));
- return nfserr_perm;
+ goto out;
}
/* Set user creds if we haven't done so already. */
nfsd_setuser(rqstp, exp);
- dentry = fh->fh_dentry;
-
- if(!d_validate(dentry, fh->fh_dparent, fh->fh_dhash, fh->fh_dlen) ||
- !(inode = dentry->d_inode) ||
- !inode->i_nlink) {
- /* Currently we cannot tell the difference between
- * a bogus pointer and a true unlink between fh
- * uses. But who cares about accurate error reporting
- * to buggy/malicious clients... -DaveM
- */
-
- /* nfsdstats.fhstale++; */
- return nfserr_stale;
- }
-
- dget(dentry);
- fhp->fh_dverified = 1;
+ /*
+ * Look up the dentry using the NFS fh.
+ */
+ error = nfserr_stale;
+ dentry = find_fh_dentry(fh);
+ if (!dentry)
+ goto out;
+ /*
+ * Note: it's possible that the returned dentry won't be the
+ * one in the filehandle. We can correct the FH for our use,
+ * but unfortunately the client will keep sending the broken
+ * one. Hopefully the lookup will keep patching things up..
+ */
+ fhp->fh_dentry = dentry;
fhp->fh_export = exp;
+ fhp->fh_dverified = 1;
+ nfsd_nr_verified++;
/* Type check. The correct error return for type mismatches
* does not seem to be generally agreed upon. SunOS seems to
@@ -78,33 +1057,226 @@ fh_verify(struct svc_rqst *rqstp, struct svc_fh *fhp, int type, int access)
* spec says this is incorrect (implementation notes for the
* write call).
*/
- if (type > 0 && (inode->i_mode & S_IFMT) != type)
- return (type == S_IFDIR)? nfserr_notdir : nfserr_isdir;
- if (type < 0 && (inode->i_mode & S_IFMT) == -type)
- return (type == -S_IFDIR)? nfserr_notdir : nfserr_isdir;
+ inode = dentry->d_inode;
+ if (type > 0 && (inode->i_mode & S_IFMT) != type) {
+ error = (type == S_IFDIR)? nfserr_notdir : nfserr_isdir;
+ goto out;
+ }
+ if (type < 0 && (inode->i_mode & S_IFMT) == -type) {
+ error = (type == -S_IFDIR)? nfserr_notdir : nfserr_isdir;
+ goto out;
+ }
/* Finally, check access permissions. */
- return nfsd_permission(fhp->fh_export, dentry, access);
+ error = nfsd_permission(fhp->fh_export, dentry, access);
+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);
+
+out:
+ return error;
}
/*
- * Compose file handle for NFS reply.
+ * Compose a filehandle for an NFS reply.
+ *
+ * Note that when first composed, the dentry may not yet have
+ * an inode. In this case a call to fh_update should be made
+ * before the fh goes out on the wire ...
*/
void
fh_compose(struct svc_fh *fhp, struct svc_export *exp, struct dentry *dentry)
{
+ struct inode * inode = dentry->d_inode;
+
dprintk("nfsd: fh_compose(exp %x/%ld dentry %p)\n",
exp->ex_dev, exp->ex_ino, dentry);
- fh_init(fhp); /* initialize empty fh */
- fhp->fh_handle.fh_dentry = dentry;
- fhp->fh_handle.fh_dparent = dentry->d_parent;
- fhp->fh_handle.fh_dhash = dentry->d_name.hash;
- fhp->fh_handle.fh_dlen = dentry->d_name.len;
+ /*
+ * N.B. We shouldn't need to init the fh -- the call to fh_compose
+ * may not be done on error paths, but the cleanup must call fh_put.
+ * Fix this soon!
+ */
+ fh_init(fhp);
+ fhp->fh_handle.fh_dcookie = dentry;
+ if (inode) {
+ fhp->fh_handle.fh_ino = inode->i_ino;
+ }
+ fhp->fh_handle.fh_dirino = dentry->d_parent->d_inode->i_ino;
+ fhp->fh_handle.fh_dev = dentry->d_parent->d_inode->i_dev;
fhp->fh_handle.fh_xdev = exp->ex_dev;
fhp->fh_handle.fh_xino = 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 filehandle information after changing a dentry.
+ */
+void
+fh_update(struct svc_fh *fhp)
+{
+ struct dentry *dentry;
+ struct inode *inode;
+
+ if (!fhp->fh_dverified) {
+ printk("fh_update: fh not verified!\n");
+ goto out;
+ }
+
+ dentry = fhp->fh_dentry;
+ inode = dentry->d_inode;
+ if (!inode) {
+ printk("fh_update: %s/%s still negative!\n",
+ dentry->d_parent->d_name.name, dentry->d_name.name);
+ goto out;
+ }
+ fhp->fh_handle.fh_ino = inode->i_ino;
+out:
+ return;
+}
+
+/*
+ * Release a filehandle. If the filehandle carries a dentry count,
+ * we add the dentry to the short-term cache rather than release it.
+ */
+void
+fh_put(struct svc_fh *fhp)
+{
+ if (fhp->fh_dverified) {
+ struct dentry * dentry = fhp->fh_dentry;
+ fh_unlock(fhp);
+ fhp->fh_dverified = 0;
+ if (!dentry->d_count) {
+ printk("fh_put: %s/%s has d_count 0!\n",
+ dentry->d_parent->d_name.name, dentry->d_name.name);
+ return;
+ }
+ if (!dentry->d_inode || !add_to_fhcache(dentry, 0)) {
+ dput(dentry);
+ nfsd_nr_put++;
+ }
+ }
+}
+
+/*
+ * Verify that the FH dentry is still a valid dentry pointer.
+ * After making some preliminary checks, we ask VFS to verify
+ * that it is indeed a dentry.
+ */
+static int nfsd_d_validate(struct dentry *dentry)
+{
+ unsigned long dent_addr = (unsigned long) dentry;
+ unsigned long min_addr = PAGE_OFFSET;
+ unsigned long max_addr = min_addr + (num_physpages << PAGE_SHIFT);
+ unsigned long align_mask = 0x0F;
+ unsigned int len;
+ int valid = 0;
+
+ if (dent_addr < min_addr)
+ goto bad_addr;
+ if (dent_addr > max_addr - sizeof(struct dentry))
+ goto bad_addr;
+ if ((dent_addr & ~align_mask) != dent_addr)
+ goto bad_align;
+ /*
+ * Looks safe enough to dereference ...
+ */
+ len = dentry->d_name.len;
+ if (len > NFS_MAXNAMLEN)
+ goto out;
+ /*
+ * Note: d_validate doesn't dereference the parent pointer ...
+ * just combines it with the name hash to find the hash chain.
+ */
+ valid = d_validate(dentry, dentry->d_parent, dentry->d_name.hash, len);
+
+out:
+ return valid;
+
+bad_addr:
+ printk("nfsd_d_validate: invalid address %lx\n", dent_addr);
+ goto out;
+bad_align:
+ printk("nfsd_d_validate: unaligned address %lx\n", dent_addr);
+ goto out;
+}
+
+/*
+ * 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(dev_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 dentry 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("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("nfsd_fh_free: %d paths freed\n", i);
+
+ printk("nfsd_fh_free: verified %d, put %d\n",
+ nfsd_nr_verified, nfsd_nr_put);
+}
+
+void nfsd_fh_init(void)
+{
+ 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("nfsd_init: initialized fhcache, entries=%lu\n", NFSD_MAXFH);
}
diff --git a/fs/nfsd/nfsproc.c b/fs/nfsd/nfsproc.c
index 1e9cf788f..6cb65b5ef 100644
--- a/fs/nfsd/nfsproc.c
+++ b/fs/nfsd/nfsproc.c
@@ -78,6 +78,8 @@ nfsd_proc_setattr(struct svc_rqst *rqstp, struct nfsd_sattrargs *argp,
/*
* Look up a path name component
+ * Note: the dentry in the resp->fh may be negative if the file
+ * doesn't exist yet.
* N.B. After this call resp->fh needs an fh_put
*/
static int
@@ -88,10 +90,8 @@ nfsd_proc_lookup(struct svc_rqst *rqstp, struct nfsd_diropargs *argp,
dprintk("nfsd: LOOKUP %p %s\n", SVCFH_DENTRY(&argp->fh), argp->name);
- nfserr = nfsd_lookup(rqstp, &argp->fh,
- argp->name,
- argp->len,
- &resp->fh);
+ nfserr = nfsd_lookup(rqstp, &argp->fh, argp->name, argp->len,
+ &resp->fh);
fh_put(&argp->fh);
RETURN(nfserr);
@@ -209,11 +209,10 @@ nfsd_proc_create(struct svc_rqst *rqstp, struct nfsd_createargs *argp,
nfserr = fh_verify(rqstp, dirfhp, S_IFDIR, MAY_EXEC);
if (nfserr)
goto done; /* must fh_put dirfhp even on error */
- dirp = dirfhp->fh_handle.fh_dentry->d_inode;
+ dirp = dirfhp->fh_dentry->d_inode;
/* Check for MAY_WRITE separately. */
- nfserr = nfsd_permission(dirfhp->fh_export,
- dirfhp->fh_handle.fh_dentry,
+ nfserr = nfsd_permission(dirfhp->fh_export, dirfhp->fh_dentry,
MAY_WRITE);
if (nfserr == nfserr_rofs) {
rdonly = 1; /* Non-fatal error for echo > /dev/null */
@@ -224,7 +223,7 @@ nfsd_proc_create(struct svc_rqst *rqstp, struct nfsd_createargs *argp,
exists = !nfsd_lookup(rqstp, dirfhp, argp->name, argp->len, newfhp);
if (newfhp->fh_dverified)
- inode = newfhp->fh_handle.fh_dentry->d_inode;
+ inode = newfhp->fh_dentry->d_inode;
/* Get rid of this soon... */
if (exists && !inode) {
diff --git a/fs/nfsd/nfsxdr.c b/fs/nfsd/nfsxdr.c
index cec3ba7f3..1d9a9dd5a 100644
--- a/fs/nfsd/nfsxdr.c
+++ b/fs/nfsd/nfsxdr.c
@@ -18,7 +18,7 @@
#define NFSDDBG_FACILITY NFSDDBG_XDR
u32 nfs_ok, nfserr_perm, nfserr_noent, nfserr_io, nfserr_nxio,
- nfserr_acces, nfserr_exist, nfserr_nodev, nfserr_notdir,
+ nfserr_inval, nfserr_acces, nfserr_exist, nfserr_nodev, nfserr_notdir,
nfserr_isdir, nfserr_fbig, nfserr_nospc, nfserr_rofs,
nfserr_nametoolong, nfserr_dquot, nfserr_stale;
@@ -47,10 +47,11 @@ nfsd_xdr_init(void)
if (inited)
return;
- nfs_ok = htonl(NFS_OK);
- nfserr_perm = htonl(NFSERR_PERM);
- nfserr_noent = htonl(NFSERR_NOENT);
- nfserr_io = htonl(NFSERR_IO);
+ nfs_ok = htonl(NFS_OK);
+ nfserr_perm = htonl(NFSERR_PERM);
+ nfserr_noent = htonl(NFSERR_NOENT);
+ nfserr_io = htonl(NFSERR_IO);
+ nfserr_inval = htonl(NFSERR_INVAL);
nfserr_nxio = htonl(NFSERR_NXIO);
nfserr_acces = htonl(NFSERR_ACCES);
nfserr_exist = htonl(NFSERR_EXIST);
@@ -363,7 +364,7 @@ int
nfssvc_encode_attrstat(struct svc_rqst *rqstp, u32 *p,
struct nfsd_attrstat *resp)
{
- if (!(p = encode_fattr(rqstp, p, resp->fh.fh_handle.fh_dentry->d_inode)))
+ if (!(p = encode_fattr(rqstp, p, resp->fh.fh_dentry->d_inode)))
return 0;
return xdr_ressize_check(rqstp, p);
}
@@ -373,7 +374,7 @@ nfssvc_encode_diropres(struct svc_rqst *rqstp, u32 *p,
struct nfsd_diropres *resp)
{
if (!(p = encode_fh(p, &resp->fh))
- || !(p = encode_fattr(rqstp, p, resp->fh.fh_handle.fh_dentry->d_inode)))
+ || !(p = encode_fattr(rqstp, p, resp->fh.fh_dentry->d_inode)))
return 0;
return xdr_ressize_check(rqstp, p);
}
@@ -391,7 +392,7 @@ int
nfssvc_encode_readres(struct svc_rqst *rqstp, u32 *p,
struct nfsd_readres *resp)
{
- if (!(p = encode_fattr(rqstp, p, resp->fh.fh_handle.fh_dentry->d_inode)))
+ if (!(p = encode_fattr(rqstp, p, resp->fh.fh_dentry->d_inode)))
return 0;
*p++ = htonl(resp->count);
p += XDR_QUADLEN(resp->count);
diff --git a/fs/nfsd/stats.c b/fs/nfsd/stats.c
index ee76fcf6d..51a0fbc71 100644
--- a/fs/nfsd/stats.c
+++ b/fs/nfsd/stats.c
@@ -32,11 +32,15 @@ nfsd_proc_read(char *buffer, char **start, off_t offset, int count,
{
int len;
- len = sprintf(buffer,
- "rc %d %d %d\n",
+ len = sprintf(buffer, "rc %d %d %d %d %d %d %d %d\n",
nfsdstats.rchits,
nfsdstats.rcmisses,
- nfsdstats.rcnocache);
+ nfsdstats.rcnocache,
+ nfsdstats.fh_cached,
+ nfsdstats.fh_valid,
+ nfsdstats.fh_fixup,
+ nfsdstats.fh_lookup,
+ nfsdstats.fh_stale);
/* Assume we haven't hit EOF yet. Will be set by svc_proc_read. */
*eof = 0;
diff --git a/fs/nfsd/vfs.c b/fs/nfsd/vfs.c
index df6eb0e2c..4579cddec 100644
--- a/fs/nfsd/vfs.c
+++ b/fs/nfsd/vfs.c
@@ -36,6 +36,8 @@
#include <asm/uaccess.h>
#endif
+extern void fh_update(struct svc_fh*);
+
#define NFSDDBG_FACILITY NFSDDBG_FILEOP
/* Open mode for nfsd_open */
@@ -108,10 +110,11 @@ nfsd_lookup(struct svc_rqst *rqstp, struct svc_fh *fhp, const char *name,
dprintk("nfsd: nfsd_lookup(fh %p, %s)\n", SVCFH_DENTRY(fhp), name);
/* Obtain dentry and export. */
- if ((err = fh_verify(rqstp, fhp, S_IFDIR, MAY_NOP)) != 0)
- return err;
+ err = fh_verify(rqstp, fhp, S_IFDIR, MAY_NOP);
+ if (err)
+ goto out;
- dparent = fhp->fh_handle.fh_dentry;
+ dparent = fhp->fh_dentry;
exp = fhp->fh_export;
/* Fast path... */
@@ -121,11 +124,16 @@ nfsd_lookup(struct svc_rqst *rqstp, struct svc_fh *fhp, const char *name,
!nfsd_iscovered(dparent, exp)) {
struct dentry *dchild;
- dchild = lookup_dentry(name, dget(dparent), 1);
+ /* Lookup the name, but don't follow links */
+ dchild = lookup_dentry(name, dget(dparent), 0);
err = PTR_ERR(dchild);
if (IS_ERR(dchild))
return nfserrno(-err);
+ /*
+ * Note: we compose the filehandle now, but as the
+ * dentry may be negative, it may need to be updated.
+ */
fh_compose(resfh, exp, dchild);
return (dchild->d_inode ? 0 : nfserr_noent);
}
@@ -135,6 +143,7 @@ nfsd_lookup(struct svc_rqst *rqstp, struct svc_fh *fhp, const char *name,
return nfserr_noent;
if (nfsd_iscovered(dparent, exp))
return nfserr_acces;
+out:
return err;
}
@@ -158,10 +167,11 @@ nfsd_setattr(struct svc_rqst *rqstp, struct svc_fh *fhp, struct iattr *iap)
ftype = S_IFREG;
/* Get inode */
- if ((err = fh_verify(rqstp, fhp, ftype, accmode)) != 0)
- return err;
+ err = fh_verify(rqstp, fhp, ftype, accmode);
+ if (err)
+ goto out;
- dentry = fhp->fh_handle.fh_dentry;
+ dentry = fhp->fh_dentry;
inode = dentry->d_inode;
/* The size case is special... */
@@ -169,7 +179,7 @@ nfsd_setattr(struct svc_rqst *rqstp, struct svc_fh *fhp, struct iattr *iap)
if (iap->ia_size < inode->i_size) {
err = nfsd_permission(fhp->fh_export, dentry, MAY_TRUNC);
if (err != 0)
- return err;
+ goto out;
}
if ((err = get_write_access(inode)) != 0)
return nfserrno(-err);
@@ -211,8 +221,9 @@ nfsd_setattr(struct svc_rqst *rqstp, struct svc_fh *fhp, struct iattr *iap)
if (EX_ISSYNC(fhp->fh_export))
write_inode_now(inode);
}
-
- return 0;
+ err = 0;
+out:
+ return err;
}
/*
@@ -230,20 +241,23 @@ nfsd_open(struct svc_rqst *rqstp, struct svc_fh *fhp, int type,
access = wflag? MAY_WRITE : MAY_READ;
if ((err = fh_verify(rqstp, fhp, type, access)) != 0)
- return err;
+ goto out;
- dentry = fhp->fh_handle.fh_dentry;
+ dentry = fhp->fh_dentry;
inode = dentry->d_inode;
/* Disallow access to files with the append-only bit set or
* with mandatory locking enabled */
+ err = nfserr_perm;
if (IS_APPEND(inode) || IS_ISMNDLK(inode))
- return nfserr_perm;
+ goto out;
if (!inode->i_op || !inode->i_op->default_file_ops)
- return nfserr_perm;
+ goto out;
- if (wflag && (err = get_write_access(inode)) != 0)
- return nfserrno(-err);
+ if (wflag && (err = get_write_access(inode)) != 0) {
+ err = nfserrno(-err);
+ goto out;
+ }
memset(filp, 0, sizeof(*filp));
filp->f_op = inode->i_op->default_file_ops;
@@ -252,6 +266,7 @@ nfsd_open(struct svc_rqst *rqstp, struct svc_fh *fhp, int type,
filp->f_mode = wflag? FMODE_WRITE : FMODE_READ;
filp->f_dentry = dentry;
+ err = 0;
if (filp->f_op->open) {
err = filp->f_op->open(inode, filp);
if (err) {
@@ -262,11 +277,11 @@ nfsd_open(struct svc_rqst *rqstp, struct svc_fh *fhp, int type,
* is really on callers stack frame. -DaveM
*/
filp->f_count--;
- return nfserrno(-err);
+ err = nfserrno(-err);
}
}
-
- return 0;
+out:
+ return err;
}
/*
@@ -281,7 +296,8 @@ nfsd_close(struct file *filp)
if (!inode->i_count)
printk(KERN_WARNING "nfsd: inode count == 0!\n");
if (!dentry->d_count)
- printk(KERN_WARNING "nfsd: wheee, dentry count == 0!\n");
+ printk(KERN_WARNING "nfsd: wheee, %s/%s d_count == 0!\n",
+ dentry->d_parent->d_name.name, dentry->d_name.name);
if (filp->f_op && filp->f_op->release)
filp->f_op->release(inode, filp);
if (filp->f_mode & FMODE_WRITE)
@@ -299,6 +315,9 @@ nfsd_sync(struct inode *inode, struct file *filp)
/*
* Obtain the readahead parameters for the given file
+ *
+ * N.B. is raparm cache for a file cleared when the file closes??
+ * (dentry might be reused later.)
*/
static inline struct raparms *
nfsd_get_raparms(struct dentry *dentry)
@@ -364,7 +383,7 @@ nfsd_read(struct svc_rqst *rqstp, struct svc_fh *fhp, loff_t offset, char *buf,
file.f_pos = offset;
oldfs = get_fs(); set_fs(KERNEL_DS);
- err = file.f_op->read(inode, &file, buf, *count);
+ err = file.f_op->read(&file, buf, *count, &file.f_pos);
set_fs(oldfs);
/* Write back readahead params */
@@ -432,7 +451,7 @@ nfsd_write(struct svc_rqst *rqstp, struct svc_fh *fhp, loff_t offset,
/* Write the data. */
oldfs = get_fs(); set_fs(KERNEL_DS);
- err = file.f_op->write(inode, &file, buf, cnt);
+ err = file.f_op->write(&file, buf, cnt, &file.f_pos);
set_fs(oldfs);
/* clear setuid/setgid flag after write */
@@ -506,39 +525,53 @@ nfsd_create(struct svc_rqst *rqstp, struct svc_fh *fhp,
struct inode *dirp;
int err;
+ err = nfserr_perm;
if (!flen)
- return nfserr_perm;
+ goto out;
if (!(iap->ia_valid & ATTR_MODE))
iap->ia_mode = 0;
- if ((err = fh_verify(rqstp, fhp, S_IFDIR, MAY_CREATE)) != 0)
- return err;
+ err = fh_verify(rqstp, fhp, S_IFDIR, MAY_CREATE);
+ if (err)
+ goto out;
- dentry = fhp->fh_handle.fh_dentry;
+ dentry = fhp->fh_dentry;
dirp = dentry->d_inode;
/* Get all the sanity checks out of the way before we lock the parent. */
- if(!dirp->i_op || !dirp->i_op->lookup) {
- return nfserrno(-ENOTDIR);
- } else if(type == S_IFREG) {
+ err = nfserr_notdir;
+ if(!dirp->i_op || !dirp->i_op->lookup)
+ goto out;
+ err = nfserr_perm;
+ if (type == S_IFREG) {
if(!dirp->i_op->create)
- return nfserr_perm;
+ goto out;
} else if(type == S_IFDIR) {
if(!dirp->i_op->mkdir)
- return nfserr_perm;
+ goto out;
} else if((type == S_IFCHR) || (type == S_IFBLK) || (type == S_IFIFO)) {
if(!dirp->i_op->mknod)
- return nfserr_perm;
+ goto out;
} else {
- return nfserr_perm;
+ goto out;
}
- if(!resfhp->fh_dverified) {
+ /*
+ * The response filehandle may have been setup already ...
+ */
+ if (!resfhp->fh_dverified) {
dchild = lookup_dentry(fname, dget(dentry), 0);
err = PTR_ERR(dchild);
if(IS_ERR(dchild))
return nfserrno(-err);
} else
- dchild = resfhp->fh_handle.fh_dentry;
+ dchild = resfhp->fh_dentry;
+ /*
+ * Make sure the child dentry is still negative ...
+ */
+ if (dchild->d_inode) {
+ printk("nfsd_create: dentry %s/%s not negative!\n",
+ dentry->d_parent->d_name.name, dentry->d_name.name);
+ }
/* Looks good, lock the directory. */
fh_lock(fhp);
@@ -562,9 +595,15 @@ nfsd_create(struct svc_rqst *rqstp, struct svc_fh *fhp,
if (EX_ISSYNC(fhp->fh_export))
write_inode_now(dirp);
- /* If needed, assemble the file handle for the newly created file. */
- if(!resfhp->fh_dverified)
+ /*
+ * Assemble the file handle for the newly created file,
+ * or update the filehandle to get the new inode info.
+ */
+ if (!resfhp->fh_dverified) {
fh_compose(resfhp, fhp->fh_export, dchild);
+ } else {
+ fh_update(resfhp);
+ }
/* Set file attributes. Mode has already been set and
* setting uid/gid works only for root. Irix appears to
@@ -574,7 +613,7 @@ nfsd_create(struct svc_rqst *rqstp, struct svc_fh *fhp,
err = 0;
if ((iap->ia_valid &= (ATTR_UID|ATTR_GID|ATTR_MODE)) != 0)
err = nfsd_setattr(rqstp, resfhp, iap);
-
+out:
return err;
}
@@ -597,7 +636,7 @@ nfsd_truncate(struct svc_rqst *rqstp, struct svc_fh *fhp, unsigned long size)
if ((err = fh_verify(rqstp, fhp, S_IFREG, MAY_WRITE|MAY_TRUNC)) != 0)
return err;
- dentry = fhp->fh_handle.fh_dentry;
+ dentry = fhp->fh_dentry;
inode = dentry->d_inode;
if ((err = get_write_access(inode)) != 0)
@@ -633,13 +672,14 @@ nfsd_readlink(struct svc_rqst *rqstp, struct svc_fh *fhp, char *buf, int *lenp)
int err;
if ((err = fh_verify(rqstp, fhp, S_IFLNK, MAY_READ)) != 0)
- return err;
+ goto out;
- dentry = fhp->fh_handle.fh_dentry;
+ dentry = fhp->fh_dentry;
inode = dentry->d_inode;
+ err = nfserr_inval;
if (!inode->i_op || !inode->i_op->readlink)
- return nfserr_io;
+ goto out;
UPDATE_ATIME(inode);
oldfs = get_fs(); set_fs(KERNEL_DS);
@@ -647,10 +687,14 @@ nfsd_readlink(struct svc_rqst *rqstp, struct svc_fh *fhp, char *buf, int *lenp)
set_fs(oldfs);
if (err < 0)
- return nfserrno(-err);
- *lenp = err;
+ err = nfserrno(-err);
+ else {
+ *lenp = err;
+ err = 0;
+ }
- return 0;
+out:
+ return err;
}
/*
@@ -667,41 +711,43 @@ nfsd_symlink(struct svc_rqst *rqstp, struct svc_fh *fhp,
struct inode *dirp;
int err;
+ err = nfserr_noent;
if (!flen || !plen)
- return nfserr_noent;
+ goto out;
if ((err = fh_verify(rqstp, fhp, S_IFDIR, MAY_CREATE)) != 0)
- return err;
+ goto out;
+ dentry = fhp->fh_dentry;
- dentry = fhp->fh_handle.fh_dentry;
+ err = nfserr_perm;
+ if (nfsd_iscovered(dentry, fhp->fh_export))
+ goto out;
dirp = dentry->d_inode;
-
- if (nfsd_iscovered(dentry, fhp->fh_export) ||
- !dirp->i_op ||
- !dirp->i_op->symlink)
- return nfserr_perm;
+ if (!dirp->i_op || !dirp->i_op->symlink)
+ goto out;
dnew = lookup_dentry(fname, dget(dentry), 0);
err = PTR_ERR(dnew);
if (IS_ERR(dnew))
- goto out;
+ goto out_nfserr;
err = -EEXIST;
- if(dnew->d_inode)
- goto compose_and_out;
-
- fh_lock(fhp);
- err = dirp->i_op->symlink(dirp, dnew, path);
- fh_unlock(fhp);
-
- if (!err) {
- if (EX_ISSYNC(fhp->fh_export))
- write_inode_now(dirp);
+ if (!dnew->d_inode) {
+ fh_lock(fhp);
+ err = dirp->i_op->symlink(dirp, dnew, path);
+ fh_unlock(fhp);
+ if (!err) {
+ if (EX_ISSYNC(fhp->fh_export))
+ write_inode_now(dirp);
+ }
}
-compose_and_out:
fh_compose(resfhp, fhp->fh_export, dnew);
+
+out_nfserr:
+ if (err)
+ err = nfserrno(-err);
out:
- return (err ? nfserrno(-err) : 0);
+ return err;
}
/*
@@ -720,7 +766,7 @@ nfsd_link(struct svc_rqst *rqstp, struct svc_fh *ffhp,
(err = fh_verify(rqstp, tfhp, S_IFREG, MAY_NOP)) != 0)
return err;
- ddir = ffhp->fh_handle.fh_dentry;
+ ddir = ffhp->fh_dentry;
dirp = ddir->d_inode;
dnew = lookup_dentry(fname, dget(ddir), 1);
@@ -731,7 +777,7 @@ nfsd_link(struct svc_rqst *rqstp, struct svc_fh *ffhp,
err = -EEXIST;
if (dnew->d_inode)
goto dput_and_out;
- dest = tfhp->fh_handle.fh_dentry->d_inode;
+ dest = tfhp->fh_dentry->d_inode;
err = -EPERM;
if (!len)
@@ -794,10 +840,10 @@ nfsd_rename(struct svc_rqst *rqstp, struct svc_fh *ffhp, char *fname, int flen,
|| (err = fh_verify(rqstp, tfhp, S_IFDIR, MAY_CREATE)) != 0)
return err;
- fdentry = ffhp->fh_handle.fh_dentry;
+ fdentry = ffhp->fh_dentry;
fdir = fdentry->d_inode;
- tdentry = tfhp->fh_handle.fh_dentry;
+ tdentry = tfhp->fh_dentry;
tdir = tdentry->d_inode;
if (!flen || (fname[0] == '.' &&
@@ -816,6 +862,7 @@ nfsd_rename(struct svc_rqst *rqstp, struct svc_fh *ffhp, char *fname, int flen,
if (IS_ERR(ndentry))
goto out_dput_old;
+ /* N.B. check this ... problems in locking?? */
nfsd_double_down(&tdir->i_sem, &fdir->i_sem);
err = -EXDEV;
if (fdir->i_dev != tdir->i_dev)
@@ -856,7 +903,7 @@ nfsd_unlink(struct svc_rqst *rqstp, struct svc_fh *fhp, int type,
if ((err = fh_verify(rqstp, fhp, S_IFDIR, MAY_REMOVE)) != 0)
return err;
- dentry = fhp->fh_handle.fh_dentry;
+ dentry = fhp->fh_dentry;
dirp = dentry->d_inode;
rdentry = lookup_dentry(fname, dget(dentry), 0);
@@ -975,20 +1022,24 @@ nfsd_statfs(struct svc_rqst *rqstp, struct svc_fh *fhp, struct statfs *stat)
unsigned long oldfs;
int err;
- if ((err = fh_verify(rqstp, fhp, 0, MAY_NOP)) != 0)
- return err;
- dentry = fhp->fh_handle.fh_dentry;
+ err = fh_verify(rqstp, fhp, 0, MAY_NOP);
+ if (err)
+ goto out;
+ dentry = fhp->fh_dentry;
inode = dentry->d_inode;
+ err = nfserr_io;
if (!(sb = inode->i_sb) || !sb->s_op->statfs)
- return nfserr_io;
+ goto out;
oldfs = get_fs();
set_fs (KERNEL_DS);
sb->s_op->statfs(sb, stat, sizeof(*stat));
set_fs (oldfs);
+ err = 0;
- return 0;
+out:
+ return err;
}
/*