From: Trond Myklebust Date: Fri, 10 Aug 2007 18:34:41 -0400 Subject: NFS: Add a generic stateless open function Signed-off-by: Trond Myklebust --- fs/nfs/dir.c | 133 +++++++++++++++++++++++++++++++++++------------- fs/nfs/nfs4proc.c | 4 + include/linux/nfs_fs.h | 2 - 3 files changed, 101 insertions(+), 38 deletions(-) diff --git a/fs/nfs/dir.c b/fs/nfs/dir.c index 477f81c..ba90573 100644 --- a/fs/nfs/dir.c +++ b/fs/nfs/dir.c @@ -680,7 +680,7 @@ static int nfs_check_verifier(struct inode *dir, struct dentry *dentry) * component of the path. * We check for this using LOOKUP_CONTINUE and LOOKUP_PARENT. */ -static inline unsigned int nfs_lookup_check_intent(struct nameidata *nd, unsigned int mask) +static unsigned int nfs_lookup_check_intent(struct nameidata *nd, unsigned int mask) { if (nd->flags & (LOOKUP_CONTINUE|LOOKUP_PARENT)) return 0; @@ -704,29 +704,16 @@ static int nfs_is_exclusive_create(struct inode *dir, struct nameidata *nd) * Inode and filehandle revalidation for lookups. * * We force revalidation in the cases where the VFS sets LOOKUP_REVAL, - * or if the intent information indicates that we're about to open this - * particular file and the "nocto" mount flag is not set. * */ static int nfs_lookup_verify_inode(struct inode *inode, struct nameidata *nd) { struct nfs_server *server = NFS_SERVER(inode); - if (nd != NULL) { - /* VFS wants an on-the-wire revalidation */ - if (nd->flags & LOOKUP_REVAL) - goto out_force; - /* This is an open(2) */ - if (nfs_lookup_check_intent(nd, LOOKUP_OPEN) != 0 && - !(server->flags & NFS_MOUNT_NOCTO) && - (S_ISREG(inode->i_mode) || - S_ISDIR(inode->i_mode))) - goto out_force; - return 0; - } + /* VFS wants an on-the-wire revalidation */ + if (nd != NULL && nd->flags & LOOKUP_REVAL) + return !__nfs_revalidate_inode(server, inode); return !nfs_revalidate_inode(server, inode); -out_force: - return !__nfs_revalidate_inode(server, inode); } /* @@ -736,8 +723,7 @@ out_force: * If parent mtime has changed, we revalidate, else we wait for a * period corresponding to the parent's attribute cache timeout value. */ -static inline -int nfs_neg_need_reval(struct inode *dir, struct dentry *dentry, +static int nfs_neg_need_reval(struct inode *dir, struct dentry *dentry, struct nameidata *nd) { /* Don't revalidate a negative dentry if we're creating a new file */ @@ -785,6 +771,53 @@ out: } /* + * Use intent information to determine whether we need to substitute + * an open for the LOOKUP call + */ +static int is_atomic_open(struct inode *dir, struct nameidata *nd) +{ + if (nd == NULL || nfs_lookup_check_intent(nd, LOOKUP_OPEN) == 0) + return 0; + return 1; +} + +static int nfs_inode_may_open(struct inode *inode, struct rpc_cred *cred, struct nameidata *nd) +{ + int force_reval = 0; + /* Are we trying to write to a read only partition? */ + if (IS_RDONLY(inode) && (nd->intent.open.flags & (O_CREAT|O_TRUNC|FMODE_WRITE))) + return -EROFS; + if (nd->flags & LOOKUP_REVAL) + force_reval = 1; + if (!(NFS_SERVER(inode)->flags & NFS_MOUNT_NOCTO)) + force_reval = 1; + return nfs_may_open(inode, cred, nd->intent.open.flags, force_reval); +} + +static int nfs_stateless_open(struct inode *dir, struct dentry *dentry, struct nameidata *nd) +{ + struct file *filp; + struct rpc_cred *cred; + int ret; + + if (dentry->d_inode == NULL || S_ISLNK(dentry->d_inode->i_mode)) + return 0; + + cred = rpcauth_lookupcred(NFS_CLIENT(dentry->d_inode)->cl_auth, 0); + if (IS_ERR(cred)) + return PTR_ERR(cred); + ret = nfs_inode_may_open(dentry->d_inode, cred, nd); + if (ret < 0) + goto out; + filp = nfs_lookup_instantiate_filp(nd, dentry, cred, NULL); + if (IS_ERR(filp)) + ret = PTR_ERR(filp); +out: + put_rpccred(cred); + return ret; +} + +/* * This is called every time the dcache has a lookup hit, * and we should check whether we can really trust that * lookup. @@ -800,6 +833,7 @@ static int nfs_lookup_revalidate(struct dentry * dentry, struct nameidata *nd) struct inode *dir; struct inode *inode; struct dentry *parent; + int ret = 1; parent = dget_parent(dentry); lock_kernel(); @@ -818,13 +852,22 @@ static int nfs_lookup_revalidate(struct dentry * dentry, struct nameidata *nd) /* Force a full look up iff the parent directory has changed */ if (nfs_is_exclusive_create(dir, nd) || !nfs_check_verifier(dir, dentry)) { - if (nfs_full_revalidate_dentry(dir, dentry)) + if (!nfs_full_revalidate_dentry(dir, dentry)) + goto out_bad; + if (!is_atomic_open(dir, nd)) goto out_valid; - goto out_bad; + } else if (!is_atomic_open(dir, nd)) { + if (!nfs_lookup_verify_inode(inode, nd)) + goto out_zap_parent; + goto out_valid; } - if (!nfs_lookup_verify_inode(inode, nd)) - goto out_zap_parent; + ret = nfs_stateless_open(dir, dentry, nd); + if (ret < 0) { + if (ret == -ESTALE) + goto out_bad; + goto out_error; + } out_valid: unlock_kernel(); @@ -840,6 +883,10 @@ out_zap_parent: unlock_kernel(); dput(parent); return 0; +out_error: + unlock_kernel(); + dput(parent); + return ret; } /* @@ -890,7 +937,7 @@ struct dentry_operations nfs_dentry_operations = { .d_iput = nfs_dentry_iput, }; -static struct dentry *nfs_lookup(struct inode *dir, struct dentry * dentry, struct nameidata *nd) +static struct dentry *__nfs_lookup(struct inode *dir, struct dentry * dentry, struct nameidata *nd) { struct dentry *res; struct dentry *parent; @@ -953,6 +1000,23 @@ out: return res; } +static struct dentry *nfs_lookup(struct inode *dir, struct dentry * dentry, struct nameidata *nd) +{ + struct dentry *ret; + int err; + + ret = __nfs_lookup(dir, dentry, nd); + if (IS_ERR(ret) || !is_atomic_open(dir, nd)) + goto out; + if (ret != NULL) + dentry = ret; + err = nfs_stateless_open(dir, dentry, nd); + if (err < 0) + ret = ERR_PTR(err); +out: + return ret; +} + #ifdef CONFIG_NFS_V4 static int nfs_open_revalidate(struct dentry *, struct nameidata *); @@ -966,15 +1030,10 @@ struct dentry_operations nfs4_dentry_operations = { * Use intent information to determine whether we need to substitute * the NFSv4-style stateful OPEN for the LOOKUP call */ -static int is_atomic_open(struct inode *dir, struct nameidata *nd) +static int nfs_is_stateful_atomic_open(struct nameidata *nd) { - if (nd == NULL || nfs_lookup_check_intent(nd, LOOKUP_OPEN) == 0) - return 0; - /* NFS does not (yet) have a stateful open for directories */ - if (nd->flags & LOOKUP_DIRECTORY) - return 0; - /* Are we trying to write to a read only partition? */ - if (IS_RDONLY(dir) && (nd->intent.open.flags & (O_CREAT|O_TRUNC|FMODE_WRITE))) + /* NFSv4 does not (yet) have a stateful open for directories */ + if ((nd->flags & LOOKUP_DIRECTORY) != 0) return 0; return 1; } @@ -991,6 +1050,9 @@ static struct dentry *nfs_atomic_lookup(struct inode *dir, struct dentry *dentry if (!is_atomic_open(dir, nd)) goto no_open; + if (!nfs_is_stateful_atomic_open(nd)) + goto no_open; + if (dentry->d_name.len > NFS_SERVER(dir)->namelen) { res = ERR_PTR(-ENAMETOOLONG); goto out; @@ -1055,7 +1117,7 @@ static int nfs_open_revalidate(struct dentry *dentry, struct nameidata *nd) } /* NFS only supports OPEN on regular files */ - if (!S_ISREG(inode->i_mode)) + if (!S_ISREG(inode->i_mode) || !nfs_is_stateful_atomic_open(nd)) goto no_open; openflags = nd->intent.open.flags; /* We cannot do exclusive creation on a positive dentry */ @@ -1956,10 +2018,11 @@ static int nfs_open_permission_mask(int openflags) return mask; } -int nfs_may_open(struct inode *inode, struct rpc_cred *cred, int openflags) +int nfs_may_open(struct inode *inode, struct rpc_cred *cred, int openflags, + int force_reval) { return NFS_PROTO(inode)->permission(inode, cred, - nfs_open_permission_mask(openflags), 0); + nfs_open_permission_mask(openflags), force_reval); } int nfs_permission(struct inode *inode, int mask, struct nameidata *nd) diff --git a/fs/nfs/nfs4proc.c b/fs/nfs/nfs4proc.c index f2c86a5..9d45c4c 100644 --- a/fs/nfs/nfs4proc.c +++ b/fs/nfs/nfs4proc.c @@ -453,7 +453,7 @@ static struct nfs4_state *nfs4_try_open_cached(struct nfs4_opendata *opendata) memcpy(stateid.data, delegation->stateid.data, sizeof(stateid.data)); rcu_read_unlock(); lock_kernel(); - ret = nfs_may_open(state->inode, state->owner->so_cred, open_mode); + ret = nfs_may_open(state->inode, state->owner->so_cred, open_mode, 0); unlock_kernel(); if (ret != 0) goto out; @@ -1369,7 +1369,7 @@ static int nfs4_intent_set_file(struct nameidata *nd, struct path *path, struct if (nd->intent.open.flags & FMODE_EXEC) { ret = nfs_may_open(state->inode, state->owner->so_cred, - nd->intent.open.flags); + nd->intent.open.flags, 0); if (ret < 0) goto out_close; } diff --git a/include/linux/nfs_fs.h b/include/linux/nfs_fs.h index 7d8b4e7..4b87a82 100644 --- a/include/linux/nfs_fs.h +++ b/include/linux/nfs_fs.h @@ -369,7 +369,7 @@ extern struct dentry_operations nfs_dentry_operations; extern void nfs_force_lookup_revalidate(struct inode *dir); extern int nfs_instantiate(struct dentry *dentry, struct nfs_fh *fh, struct nfs_fattr *fattr); -extern int nfs_may_open(struct inode *inode, struct rpc_cred *cred, int openflags); +extern int nfs_may_open(struct inode *inode, struct rpc_cred *cred, int openflags, int force_reval); extern void nfs_access_zap_cache(struct inode *inode); extern int nfs_generic_permission(struct inode *inode, struct rpc_cred *cred, int mask, int force_reval);