diff mbox series

NFS: add atomic_open for NFSv3 to handle O_TRUNC correctly.

Message ID 171134496555.13576.1334297096866165638@noble.neil.brown.name (mailing list archive)
State New
Headers show
Series NFS: add atomic_open for NFSv3 to handle O_TRUNC correctly. | expand

Commit Message

NeilBrown March 25, 2024, 5:36 a.m. UTC
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>
---
 fs/nfs/dir.c           | 54 +++++++++++++++++++++++++++++++++++++++---
 fs/nfs/nfs3proc.c      |  1 +
 fs/nfs/proc.c          |  1 +
 include/linux/nfs_fs.h |  3 +++
 4 files changed, 56 insertions(+), 3 deletions(-)

Comments

NeilBrown April 8, 2024, 2:14 a.m. UTC | #1
Hi,
 any thoughts about this patch and/or the problem it addresses?

Thanks,
NeilBrown

On Mon, 25 Mar 2024, NeilBrown wrote:
> 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>
> ---
>  fs/nfs/dir.c           | 54 +++++++++++++++++++++++++++++++++++++++---
>  fs/nfs/nfs3proc.c      |  1 +
>  fs/nfs/proc.c          |  1 +
>  include/linux/nfs_fs.h |  3 +++
>  4 files changed, 56 insertions(+), 3 deletions(-)
> 
> diff --git a/fs/nfs/dir.c b/fs/nfs/dir.c
> index ac505671efbd..342930996226 100644
> --- a/fs/nfs/dir.c
> +++ b/fs/nfs/dir.c
> @@ -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 loff_t nfs_llseek_dir(struct file *, loff_t, int);
>  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 = {
>  	.llseek		= nfs_llseek_dir,
> @@ -2243,6 +2245,41 @@ static int nfs4_lookup_revalidate(struct dentry *dentry, unsigned int flags)
>  
>  #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 *
>  nfs_add_or_obtain(struct dentry *dentry, struct nfs_fh *fhandle,
>  				struct nfs_fattr *fattr)
> @@ -2303,18 +2340,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 mnt_idmap *idmap, 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);
> @@ -2326,6 +2368,12 @@ int nfs_create(struct mnt_idmap *idmap, struct inode *dir,
>  	d_drop(dentry);
>  	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);
>  
>  /*
> diff --git a/fs/nfs/nfs3proc.c b/fs/nfs/nfs3proc.c
> index cbbe3f0193b8..74bda639a7cf 100644
> --- a/fs/nfs/nfs3proc.c
> +++ b/fs/nfs/nfs3proc.c
> @@ -986,6 +986,7 @@ static int nfs3_have_delegation(struct inode *inode, fmode_t flags)
>  
>  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,
> diff --git a/fs/nfs/proc.c b/fs/nfs/proc.c
> index ad3a321ae997..d105e5b2659d 100644
> --- a/fs/nfs/proc.c
> +++ b/fs/nfs/proc.c
> @@ -695,6 +695,7 @@ static int nfs_have_delegation(struct inode *inode, fmode_t flags)
>  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,
> diff --git a/include/linux/nfs_fs.h b/include/linux/nfs_fs.h
> index d59116ac8209..039898d70954 100644
> --- a/include/linux/nfs_fs.h
> +++ b/include/linux/nfs_fs.h
> @@ -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 int nfs_access_get_cached(struct inode *inode, const struct cred *cred,
>  				 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
> -- 
> 2.44.0
> 
> 
>
diff mbox series

Patch

diff --git a/fs/nfs/dir.c b/fs/nfs/dir.c
index ac505671efbd..342930996226 100644
--- a/fs/nfs/dir.c
+++ b/fs/nfs/dir.c
@@ -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 loff_t nfs_llseek_dir(struct file *, loff_t, int);
 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 = {
 	.llseek		= nfs_llseek_dir,
@@ -2243,6 +2245,41 @@  static int nfs4_lookup_revalidate(struct dentry *dentry, unsigned int flags)
 
 #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 *
 nfs_add_or_obtain(struct dentry *dentry, struct nfs_fh *fhandle,
 				struct nfs_fattr *fattr)
@@ -2303,18 +2340,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 mnt_idmap *idmap, 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);
@@ -2326,6 +2368,12 @@  int nfs_create(struct mnt_idmap *idmap, struct inode *dir,
 	d_drop(dentry);
 	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);
 
 /*
diff --git a/fs/nfs/nfs3proc.c b/fs/nfs/nfs3proc.c
index cbbe3f0193b8..74bda639a7cf 100644
--- a/fs/nfs/nfs3proc.c
+++ b/fs/nfs/nfs3proc.c
@@ -986,6 +986,7 @@  static int nfs3_have_delegation(struct inode *inode, fmode_t flags)
 
 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,
diff --git a/fs/nfs/proc.c b/fs/nfs/proc.c
index ad3a321ae997..d105e5b2659d 100644
--- a/fs/nfs/proc.c
+++ b/fs/nfs/proc.c
@@ -695,6 +695,7 @@  static int nfs_have_delegation(struct inode *inode, fmode_t flags)
 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,
diff --git a/include/linux/nfs_fs.h b/include/linux/nfs_fs.h
index d59116ac8209..039898d70954 100644
--- a/include/linux/nfs_fs.h
+++ b/include/linux/nfs_fs.h
@@ -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 int nfs_access_get_cached(struct inode *inode, const struct cred *cred,
 				 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