Commit 7c6c5249 authored by NeilBrown's avatar NeilBrown Committed by Trond Myklebust

NFS: add atomic_open for NFSv3 to handle O_TRUNC correctly.

With two clients, each with NFSv3 mounts of the same directory, the sequence:

   client1            client2
  ls -l afile
                      echo hello there > afile
  echo HELLO > afile
  cat afile

will show
   HELLO
   there

because the O_TRUNC requested in the final 'echo' doesn't take effect.
This is because the "Negative dentry, just create a file" section in
lookup_open() assumes that the file *does* get created since the dentry
was negative, so it sets FMODE_CREATED, and this causes do_open() to
clear O_TRUNC and so the file doesn't get truncated.

Even mounting with -o lookupcache=none does not help as
nfs_neg_need_reval() always returns false if LOOKUP_CREATE is set.

This patch fixes the problem by providing an atomic_open inode operation
for NFSv3 (and v2).  The code is largely the code from the branch in
lookup_open() when atomic_open is not provided.  The significant change
is that the O_TRUNC flag is passed a new nfs_do_create() which add
'trunc' handling to nfs_create().

With this change we also optimise away an unnecessary LOOKUP before the
file is created.
Signed-off-by: default avatarNeilBrown <neilb@suse.de>
Signed-off-by: default avatarTrond Myklebust <trond.myklebust@hammerspace.com>
parent 464b424f
...@@ -56,6 +56,8 @@ static int nfs_readdir(struct file *, struct dir_context *); ...@@ -56,6 +56,8 @@ static int nfs_readdir(struct file *, struct dir_context *);
static int nfs_fsync_dir(struct file *, loff_t, loff_t, int); static int nfs_fsync_dir(struct file *, loff_t, loff_t, int);
static loff_t nfs_llseek_dir(struct file *, loff_t, int); static loff_t nfs_llseek_dir(struct file *, loff_t, int);
static void nfs_readdir_clear_array(struct folio *); static void nfs_readdir_clear_array(struct folio *);
static int nfs_do_create(struct inode *dir, struct dentry *dentry,
umode_t mode, int open_flags);
const struct file_operations nfs_dir_operations = { const struct file_operations nfs_dir_operations = {
.llseek = nfs_llseek_dir, .llseek = nfs_llseek_dir,
...@@ -2243,6 +2245,41 @@ static int nfs4_lookup_revalidate(struct dentry *dentry, unsigned int flags) ...@@ -2243,6 +2245,41 @@ static int nfs4_lookup_revalidate(struct dentry *dentry, unsigned int flags)
#endif /* CONFIG_NFSV4 */ #endif /* CONFIG_NFSV4 */
int nfs_atomic_open_v23(struct inode *dir, struct dentry *dentry,
struct file *file, unsigned int open_flags,
umode_t mode)
{
/* Same as look+open from lookup_open(), but with different O_TRUNC
* handling.
*/
int error = 0;
if (open_flags & O_CREAT) {
file->f_mode |= FMODE_CREATED;
error = nfs_do_create(dir, dentry, mode, open_flags);
if (error)
return error;
return finish_open(file, dentry, NULL);
} else if (d_in_lookup(dentry)) {
/* The only flags nfs_lookup considers are
* LOOKUP_EXCL and LOOKUP_RENAME_TARGET, and
* we want those to be zero so the lookup isn't skipped.
*/
struct dentry *res = nfs_lookup(dir, dentry, 0);
d_lookup_done(dentry);
if (unlikely(res)) {
if (IS_ERR(res))
return PTR_ERR(res);
return finish_no_open(file, res);
}
}
return finish_no_open(file, NULL);
}
EXPORT_SYMBOL_GPL(nfs_atomic_open_v23);
struct dentry * struct dentry *
nfs_add_or_obtain(struct dentry *dentry, struct nfs_fh *fhandle, nfs_add_or_obtain(struct dentry *dentry, struct nfs_fh *fhandle,
struct nfs_fattr *fattr) struct nfs_fattr *fattr)
...@@ -2303,18 +2340,23 @@ EXPORT_SYMBOL_GPL(nfs_instantiate); ...@@ -2303,18 +2340,23 @@ EXPORT_SYMBOL_GPL(nfs_instantiate);
* that the operation succeeded on the server, but an error in the * that the operation succeeded on the server, but an error in the
* reply path made it appear to have failed. * reply path made it appear to have failed.
*/ */
int nfs_create(struct mnt_idmap *idmap, struct inode *dir, static int nfs_do_create(struct inode *dir, struct dentry *dentry,
struct dentry *dentry, umode_t mode, bool excl) umode_t mode, int open_flags)
{ {
struct iattr attr; struct iattr attr;
int open_flags = excl ? O_CREAT | O_EXCL : O_CREAT;
int error; int error;
open_flags |= O_CREAT;
dfprintk(VFS, "NFS: create(%s/%lu), %pd\n", dfprintk(VFS, "NFS: create(%s/%lu), %pd\n",
dir->i_sb->s_id, dir->i_ino, dentry); dir->i_sb->s_id, dir->i_ino, dentry);
attr.ia_mode = mode; attr.ia_mode = mode;
attr.ia_valid = ATTR_MODE; attr.ia_valid = ATTR_MODE;
if (open_flags & O_TRUNC) {
attr.ia_size = 0;
attr.ia_valid |= ATTR_SIZE;
}
trace_nfs_create_enter(dir, dentry, open_flags); trace_nfs_create_enter(dir, dentry, open_flags);
error = NFS_PROTO(dir)->create(dir, dentry, &attr, open_flags); error = NFS_PROTO(dir)->create(dir, dentry, &attr, open_flags);
...@@ -2326,6 +2368,12 @@ int nfs_create(struct mnt_idmap *idmap, struct inode *dir, ...@@ -2326,6 +2368,12 @@ int nfs_create(struct mnt_idmap *idmap, struct inode *dir,
d_drop(dentry); d_drop(dentry);
return error; return error;
} }
int nfs_create(struct mnt_idmap *idmap, struct inode *dir,
struct dentry *dentry, umode_t mode, bool excl)
{
return nfs_do_create(dir, dentry, mode, excl ? O_EXCL : 0);
}
EXPORT_SYMBOL_GPL(nfs_create); EXPORT_SYMBOL_GPL(nfs_create);
/* /*
......
...@@ -986,6 +986,7 @@ static int nfs3_have_delegation(struct inode *inode, fmode_t flags) ...@@ -986,6 +986,7 @@ static int nfs3_have_delegation(struct inode *inode, fmode_t flags)
static const struct inode_operations nfs3_dir_inode_operations = { static const struct inode_operations nfs3_dir_inode_operations = {
.create = nfs_create, .create = nfs_create,
.atomic_open = nfs_atomic_open_v23,
.lookup = nfs_lookup, .lookup = nfs_lookup,
.link = nfs_link, .link = nfs_link,
.unlink = nfs_unlink, .unlink = nfs_unlink,
......
...@@ -695,6 +695,7 @@ static int nfs_have_delegation(struct inode *inode, fmode_t flags) ...@@ -695,6 +695,7 @@ static int nfs_have_delegation(struct inode *inode, fmode_t flags)
static const struct inode_operations nfs_dir_inode_operations = { static const struct inode_operations nfs_dir_inode_operations = {
.create = nfs_create, .create = nfs_create,
.lookup = nfs_lookup, .lookup = nfs_lookup,
.atomic_open = nfs_atomic_open_v23,
.link = nfs_link, .link = nfs_link,
.unlink = nfs_unlink, .unlink = nfs_unlink,
.symlink = nfs_symlink, .symlink = nfs_symlink,
......
...@@ -561,6 +561,9 @@ extern int nfs_may_open(struct inode *inode, const struct cred *cred, int openfl ...@@ -561,6 +561,9 @@ extern int nfs_may_open(struct inode *inode, const struct cred *cred, int openfl
extern void nfs_access_zap_cache(struct inode *inode); extern void nfs_access_zap_cache(struct inode *inode);
extern int nfs_access_get_cached(struct inode *inode, const struct cred *cred, extern int nfs_access_get_cached(struct inode *inode, const struct cred *cred,
u32 *mask, bool may_block); u32 *mask, bool may_block);
extern int nfs_atomic_open_v23(struct inode *dir, struct dentry *dentry,
struct file *file, unsigned int open_flags,
umode_t mode);
/* /*
* linux/fs/nfs/symlink.c * linux/fs/nfs/symlink.c
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment