From: NeilBrown <neilb@suse.de>
Date: Mon, 4 Mar 2024 10:54:34 +1100
Subject: [PATCH] NFS: add atomic_open for NFSv3 to handle O_TRUNC correctly.
Patch-mainline: Submitted - 05mar2024 linux-nfs
References: bsc#1219847 bsc#1221862
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: NeilBrown <neilb@suse.de>
Acked-by: NeilBrown <neilb@suse.com>
---
fs/nfs/dir.c | 56 ++++++++++++++++++++++++++++++++++++++++++++++---
fs/nfs/nfs3proc.c | 1
fs/nfs/proc.c | 1
include/linux/nfs_fs.h | 3 ++
4 files changed, 58 insertions(+), 3 deletions(-)
--- a/fs/nfs/dir.c
+++ b/fs/nfs/dir.c
@@ -53,6 +53,8 @@ static int nfs_readdir(struct file *, st
static int nfs_fsync_dir(struct file *, loff_t, loff_t, int);
static loff_t nfs_llseek_dir(struct file *, loff_t, int);
static void nfs_readdir_clear_array(struct page*);
+static int nfs_do_create(struct inode *dir, struct dentry *dentry,
+ umode_t mode, int open_flags);
const struct file_operations nfs_dir_operations = {
.llseek = nfs_llseek_dir,
@@ -1721,6 +1723,43 @@ static int nfs4_lookup_revalidate(struct
}
#endif /* CONFIG_NFSV4 */
+int nfs_atomic_open_v23(struct inode *dir, struct dentry *dentry,
+ struct file *file, unsigned int open_flags,
+ umode_t mode, int *opened)
+{
+
+ /* Same as look+open from lookup_open(), but with different O_TRUNC
+ * handling.
+ */
+ int error = 0;
+
+ if (dentry->d_name.len > NFS_SERVER(dir)->namelen)
+ return -ENAMETOOLONG;
+
+ if (open_flags & O_CREAT) {
+ *opened |= FILE_CREATED;
+ error = nfs_do_create(dir, dentry, mode, open_flags);
+ if (error)
+ return error;
+ return finish_open(file, dentry, NULL, opened);
+ }
+
+ if (d_in_lookup(dentry)) {
+ /* The only flag that nfs_lookup uses is LOOKUP_EXCL,
+ * and we don't want that set as it bypasses the lookup.
+ */
+ 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);
/*
* Code common to create, mkdir, and mknod.
@@ -1771,18 +1810,23 @@ EXPORT_SYMBOL_GPL(nfs_instantiate);
* that the operation succeeded on the server, but an error in the
* reply path made it appear to have failed.
*/
-int nfs_create(struct inode *dir, struct dentry *dentry,
- umode_t mode, bool excl)
+static int nfs_do_create(struct inode *dir, struct dentry *dentry,
+ umode_t mode, int open_flags)
{
struct iattr attr;
- int open_flags = excl ? O_CREAT | O_EXCL : O_CREAT;
int error;
+ open_flags |= O_CREAT;
+
dfprintk(VFS, "NFS: create(%s/%lu), %pd\n",
dir->i_sb->s_id, dir->i_ino, dentry);
attr.ia_mode = 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);
error = NFS_PROTO(dir)->create(dir, dentry, &attr, open_flags);
@@ -1794,6 +1838,12 @@ out_err:
d_drop(dentry);
return error;
}
+
+int nfs_create(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);
/*
--- a/fs/nfs/nfs3proc.c
+++ b/fs/nfs/nfs3proc.c
@@ -937,6 +937,7 @@ static int nfs3_return_delegation(struct
static const struct inode_operations nfs3_dir_inode_operations = {
.create = nfs_create,
+ .atomic_open = nfs_atomic_open_v23,
.lookup = nfs_lookup,
.link = nfs_link,
.unlink = nfs_unlink,
--- a/fs/nfs/proc.c
+++ b/fs/nfs/proc.c
@@ -682,6 +682,7 @@ static int nfs_return_delegation(struct
static const struct inode_operations nfs_dir_inode_operations = {
.create = nfs_create,
.lookup = nfs_lookup,
+ .atomic_open = nfs_atomic_open_v23,
.link = nfs_link,
.unlink = nfs_unlink,
.symlink = nfs_symlink,
--- a/include/linux/nfs_fs.h
+++ b/include/linux/nfs_fs.h
@@ -457,6 +457,9 @@ extern int nfs_instantiate(struct dentry
struct nfs_fattr *fattr, struct nfs4_label *label);
extern int nfs_may_open(struct inode *inode, struct rpc_cred *cred, int openflags);
extern void nfs_access_zap_cache(struct inode *inode);
+extern int nfs_atomic_open_v23(struct inode *dir, struct dentry *dentry,
+ struct file *file, unsigned int open_flags,
+ umode_t mode, int *opened);
/*
* linux/fs/nfs/symlink.c