diff mbox series

[7/8] NFS: Support statx_get and statx_set ioctls

Message ID 20211217204854.439578-8-trondmy@kernel.org (mailing list archive)
State New, archived
Headers show
Series Support btime and other NFSv4 specific attributes | expand

Commit Message

Trond Myklebust Dec. 17, 2021, 8:48 p.m. UTC
From: Richard Sharpe <richard.sharpe@primarydata.com>

Add support for returning all of the Windows attributes with a statx
ioctl.
Add support for setting all of the Windows attributes using an ioctl.

Signed-off-by: Richard Sharpe <richard.sharpe@primarydata.com>
Signed-off-by: Lance Shelton <lance.shelton@hammerspace.com>
Signed-off-by: Trond Myklebust <trond.myklebust@hammerspace.com>
---
 fs/nfs/dir.c             |  24 +-
 fs/nfs/getroot.c         |   3 +-
 fs/nfs/inode.c           |  41 +++-
 fs/nfs/internal.h        |   8 +
 fs/nfs/nfs3proc.c        |   1 +
 fs/nfs/nfs4_fs.h         |  31 +++
 fs/nfs/nfs4file.c        | 511 +++++++++++++++++++++++++++++++++++++++
 fs/nfs/nfs4proc.c        | 124 ++++++++++
 fs/nfs/nfs4xdr.c         |  63 ++++-
 fs/nfs/nfstrace.c        |   5 +
 fs/nfs/nfstrace.h        |   5 +
 fs/nfs/proc.c            |   1 +
 include/linux/nfs_fs.h   |   1 +
 include/linux/nfs_xdr.h  |   3 +
 include/uapi/linux/nfs.h |  90 +++++++
 15 files changed, 887 insertions(+), 24 deletions(-)

Comments

J. Bruce Fields Jan. 3, 2022, 8:52 p.m. UTC | #1
On Fri, Dec 17, 2021 at 03:48:53PM -0500, trondmy@kernel.org wrote:
> From: Richard Sharpe <richard.sharpe@primarydata.com>
> 
> Add support for returning all of the Windows attributes with a statx
> ioctl.

I suppose I'm just woodshedding, but "statx ioctl" is a little
confusing--it doesn't have any actual connection with the statx
system call, right?

But, why not add this to statx?

--b.

> Add support for setting all of the Windows attributes using an ioctl.
> 
> Signed-off-by: Richard Sharpe <richard.sharpe@primarydata.com>
> Signed-off-by: Lance Shelton <lance.shelton@hammerspace.com>
> Signed-off-by: Trond Myklebust <trond.myklebust@hammerspace.com>
> ---
>  fs/nfs/dir.c             |  24 +-
>  fs/nfs/getroot.c         |   3 +-
>  fs/nfs/inode.c           |  41 +++-
>  fs/nfs/internal.h        |   8 +
>  fs/nfs/nfs3proc.c        |   1 +
>  fs/nfs/nfs4_fs.h         |  31 +++
>  fs/nfs/nfs4file.c        | 511 +++++++++++++++++++++++++++++++++++++++
>  fs/nfs/nfs4proc.c        | 124 ++++++++++
>  fs/nfs/nfs4xdr.c         |  63 ++++-
>  fs/nfs/nfstrace.c        |   5 +
>  fs/nfs/nfstrace.h        |   5 +
>  fs/nfs/proc.c            |   1 +
>  include/linux/nfs_fs.h   |   1 +
>  include/linux/nfs_xdr.h  |   3 +
>  include/uapi/linux/nfs.h |  90 +++++++
>  15 files changed, 887 insertions(+), 24 deletions(-)
> 
> diff --git a/fs/nfs/dir.c b/fs/nfs/dir.c
> index 731d31015b6a..f6fc60822153 100644
> --- a/fs/nfs/dir.c
> +++ b/fs/nfs/dir.c
> @@ -48,11 +48,6 @@
>  
>  /* #define NFS_DEBUG_VERBOSE 1 */
>  
> -static int nfs_opendir(struct inode *, struct file *);
> -static int nfs_closedir(struct inode *, struct file *);
> -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 page*);
>  
>  const struct file_operations nfs_dir_operations = {
> @@ -63,6 +58,7 @@ const struct file_operations nfs_dir_operations = {
>  	.release	= nfs_closedir,
>  	.fsync		= nfs_fsync_dir,
>  };
> +EXPORT_SYMBOL_GPL(nfs_dir_operations);
>  
>  const struct address_space_operations nfs_dir_aops = {
>  	.freepage = nfs_readdir_clear_array,
> @@ -104,8 +100,7 @@ static void put_nfs_open_dir_context(struct inode *dir, struct nfs_open_dir_cont
>  /*
>   * Open file
>   */
> -static int
> -nfs_opendir(struct inode *inode, struct file *filp)
> +int nfs_opendir(struct inode *inode, struct file *filp)
>  {
>  	int res = 0;
>  	struct nfs_open_dir_context *ctx;
> @@ -123,13 +118,14 @@ nfs_opendir(struct inode *inode, struct file *filp)
>  out:
>  	return res;
>  }
> +EXPORT_SYMBOL_GPL(nfs_opendir);
>  
> -static int
> -nfs_closedir(struct inode *inode, struct file *filp)
> +int nfs_closedir(struct inode *inode, struct file *filp)
>  {
>  	put_nfs_open_dir_context(file_inode(filp), filp->private_data);
>  	return 0;
>  }
> +EXPORT_SYMBOL_GPL(nfs_closedir);
>  
>  struct nfs_cache_array_entry {
>  	u64 cookie;
> @@ -1064,7 +1060,7 @@ static int uncached_readdir(struct nfs_readdir_descriptor *desc)
>     last cookie cache takes care of the common case of reading the
>     whole directory.
>   */
> -static int nfs_readdir(struct file *file, struct dir_context *ctx)
> +int nfs_readdir(struct file *file, struct dir_context *ctx)
>  {
>  	struct dentry	*dentry = file_dentry(file);
>  	struct inode	*inode = d_inode(dentry);
> @@ -1157,8 +1153,9 @@ static int nfs_readdir(struct file *file, struct dir_context *ctx)
>  	dfprintk(FILE, "NFS: readdir(%pD2) returns %d\n", file, res);
>  	return res;
>  }
> +EXPORT_SYMBOL_GPL(nfs_readdir);
>  
> -static loff_t nfs_llseek_dir(struct file *filp, loff_t offset, int whence)
> +loff_t nfs_llseek_dir(struct file *filp, loff_t offset, int whence)
>  {
>  	struct nfs_open_dir_context *dir_ctx = filp->private_data;
>  
> @@ -1196,19 +1193,20 @@ static loff_t nfs_llseek_dir(struct file *filp, loff_t offset, int whence)
>  	spin_unlock(&filp->f_lock);
>  	return offset;
>  }
> +EXPORT_SYMBOL_GPL(nfs_llseek_dir);
>  
>  /*
>   * All directory operations under NFS are synchronous, so fsync()
>   * is a dummy operation.
>   */
> -static int nfs_fsync_dir(struct file *filp, loff_t start, loff_t end,
> -			 int datasync)
> +int nfs_fsync_dir(struct file *filp, loff_t start, loff_t end, int datasync)
>  {
>  	dfprintk(FILE, "NFS: fsync dir(%pD2) datasync %d\n", filp, datasync);
>  
>  	nfs_inc_stats(file_inode(filp), NFSIOS_VFSFSYNC);
>  	return 0;
>  }
> +EXPORT_SYMBOL_GPL(nfs_fsync_dir);
>  
>  /**
>   * nfs_force_lookup_revalidate - Mark the directory as having changed
> diff --git a/fs/nfs/getroot.c b/fs/nfs/getroot.c
> index 11ff2b2e060f..f872970d6240 100644
> --- a/fs/nfs/getroot.c
> +++ b/fs/nfs/getroot.c
> @@ -127,7 +127,8 @@ int nfs_get_root(struct super_block *s, struct fs_context *fc)
>  	if (server->caps & NFS_CAP_SECURITY_LABEL)
>  		kflags |= SECURITY_LSM_NATIVE_LABELS;
>  	if (ctx->clone_data.sb) {
> -		if (d_inode(fc->root)->i_fop != &nfs_dir_operations) {
> +		if (d_inode(fc->root)->i_fop !=
> +		    server->nfs_client->rpc_ops->dir_ops) {
>  			error = -ESTALE;
>  			goto error_splat_root;
>  		}
> diff --git a/fs/nfs/inode.c b/fs/nfs/inode.c
> index 33f4410190b6..8da662a4953d 100644
> --- a/fs/nfs/inode.c
> +++ b/fs/nfs/inode.c
> @@ -108,6 +108,7 @@ u64 nfs_compat_user_ino64(u64 fileid)
>  		ino ^= fileid >> (sizeof(fileid)-sizeof(ino)) * 8;
>  	return ino;
>  }
> +EXPORT_SYMBOL_GPL(nfs_compat_user_ino64);
>  
>  int nfs_drop_inode(struct inode *inode)
>  {
> @@ -501,7 +502,7 @@ nfs_fhget(struct super_block *sb, struct nfs_fh *fh, struct nfs_fattr *fattr)
>  			nfs_inode_init_regular(nfsi);
>  		} else if (S_ISDIR(inode->i_mode)) {
>  			inode->i_op = NFS_SB(sb)->nfs_client->rpc_ops->dir_inode_ops;
> -			inode->i_fop = &nfs_dir_operations;
> +			inode->i_fop = NFS_SB(sb)->nfs_client->rpc_ops->dir_ops;
>  			inode->i_data.a_ops = &nfs_dir_aops;
>  			nfs_inode_init_dir(nfsi);
>  			/* Deal with crossing mountpoints */
> @@ -867,6 +868,44 @@ static u32 nfs_get_valid_attrmask(struct inode *inode)
>  	return reply_mask;
>  }
>  
> +static int nfs_getattr_revalidate_force(struct dentry *dentry)
> +{
> +	struct inode *inode = d_inode(dentry);
> +	struct nfs_server *server = NFS_SERVER(inode);
> +
> +	if (!(server->flags & NFS_MOUNT_NOAC))
> +		nfs_readdirplus_parent_cache_miss(dentry);
> +	else
> +		nfs_readdirplus_parent_cache_hit(dentry);
> +	return __nfs_revalidate_inode(server, inode);
> +}
> +
> +static int nfs_getattr_revalidate_none(struct dentry *dentry)
> +{
> +	nfs_readdirplus_parent_cache_hit(dentry);
> +	return NFS_STALE(d_inode(dentry)) ? -ESTALE : 0;
> +}
> +
> +static int nfs_getattr_revalidate_maybe(struct dentry *dentry,
> +					unsigned long flags)
> +{
> +	if (nfs_check_cache_invalid(d_inode(dentry), flags))
> +		return nfs_getattr_revalidate_force(dentry);
> +	return nfs_getattr_revalidate_none(dentry);
> +}
> +
> +int nfs_getattr_revalidate(const struct path *path,
> +			   unsigned long flags,
> +			   unsigned int query_flags)
> +{
> +	if (query_flags & AT_STATX_FORCE_SYNC)
> +		return nfs_getattr_revalidate_force(path->dentry);
> +	if (!(query_flags & AT_STATX_DONT_SYNC))
> +		return nfs_getattr_revalidate_maybe(path->dentry, flags);
> +	return nfs_getattr_revalidate_none(path->dentry);
> +}
> +EXPORT_SYMBOL_GPL(nfs_getattr_revalidate);
> +
>  int nfs_getattr(struct user_namespace *mnt_userns, const struct path *path,
>  		struct kstat *stat, u32 request_mask, unsigned int query_flags)
>  {
> diff --git a/fs/nfs/internal.h b/fs/nfs/internal.h
> index 12f6acb483bb..9602a886f0f0 100644
> --- a/fs/nfs/internal.h
> +++ b/fs/nfs/internal.h
> @@ -366,6 +366,12 @@ extern struct nfs_client *nfs_init_client(struct nfs_client *clp,
>  			   const struct nfs_client_initdata *);
>  
>  /* dir.c */
> +int nfs_opendir(struct inode *, struct file *);
> +int nfs_closedir(struct inode *, struct file *);
> +int nfs_readdir(struct file *file, struct dir_context *ctx);
> +int nfs_fsync_dir(struct file *, loff_t, loff_t, int);
> +loff_t nfs_llseek_dir(struct file *filp, loff_t offset, int whence);
> +
>  extern void nfs_advise_use_readdirplus(struct inode *dir);
>  extern void nfs_force_use_readdirplus(struct inode *dir);
>  extern unsigned long nfs_access_cache_count(struct shrinker *shrink,
> @@ -411,6 +417,8 @@ extern void nfs_set_cache_invalid(struct inode *inode, unsigned long flags);
>  extern bool nfs_check_cache_invalid(struct inode *, unsigned long);
>  extern int nfs_wait_bit_killable(struct wait_bit_key *key, int mode);
>  extern int nfs_wait_atomic_killable(atomic_t *p, unsigned int mode);
> +extern int nfs_getattr_revalidate(const struct path *path, unsigned long flags,
> +				  unsigned int query_flags);
>  
>  /* super.c */
>  extern const struct super_operations nfs_sops;
> diff --git a/fs/nfs/nfs3proc.c b/fs/nfs/nfs3proc.c
> index 7100514d306b..091005e169b7 100644
> --- a/fs/nfs/nfs3proc.c
> +++ b/fs/nfs/nfs3proc.c
> @@ -1018,6 +1018,7 @@ const struct nfs_rpc_ops nfs_v3_clientops = {
>  	.dir_inode_ops	= &nfs3_dir_inode_operations,
>  	.file_inode_ops	= &nfs3_file_inode_operations,
>  	.file_ops	= &nfs_file_operations,
> +	.dir_ops	= &nfs_dir_operations,
>  	.nlmclnt_ops	= &nlmclnt_fl_close_lock_ops,
>  	.getroot	= nfs3_proc_get_root,
>  	.submount	= nfs_submount,
> diff --git a/fs/nfs/nfs4_fs.h b/fs/nfs/nfs4_fs.h
> index ed5eaca6801e..9f21d8520e99 100644
> --- a/fs/nfs/nfs4_fs.h
> +++ b/fs/nfs/nfs4_fs.h
> @@ -248,6 +248,34 @@ struct nfs4_opendata {
>  	int rpc_status;
>  };
>  
> +struct nfs4_statx {
> +	int		real_fd;		/* real FD to use,
> +						   -1 means use current file */
> +	__u32		fa_options;		/* statx flags */
> +	__u64		fa_request[2];		/* Attributes requested */
> +	__u64		fa_valid[2];		/* Attributes set */
> +
> +	struct timespec64 fa_time_backup;	/* Backup time */
> +	struct timespec64 fa_btime;		/* Birth time */
> +	/* Flag attributes */
> +	__u64 fa_flags;
> +	struct timespec64 fa_atime;		/* Access time */
> +	struct timespec64 fa_mtime;		/* Modify time */
> +	struct timespec64 fa_ctime;		/* Change time */
> +	kuid_t		fa_owner_uid;		/* Owner User ID */
> +	kgid_t		fa_group_gid;		/* Primary Group ID */
> +        /* Normal stat fields after this */
> +	__u32	 	fa_mode;		/* Mode */
> +	unsigned int 	fa_nlink;
> +	__u32		fa_blksize;
> +	__u32		fa_spare;		/* Alignment */
> +	__u64		fa_ino;
> +	dev_t		fa_dev;
> +	dev_t		fa_rdev;
> +	loff_t		fa_size;
> +	__u64		fa_blocks;
> +};
> +
>  struct nfs4_add_xprt_data {
>  	struct nfs_client	*clp;
>  	const struct cred	*cred;
> @@ -315,6 +343,9 @@ extern int nfs4_set_rw_stateid(nfs4_stateid *stateid,
>  		const struct nfs_open_context *ctx,
>  		const struct nfs_lock_context *l_ctx,
>  		fmode_t fmode);
> +int nfs4_set_nfs4_statx(struct inode *inode,
> +		struct nfs4_statx *statx,
> +		struct nfs_fattr *fattr);
>  extern int nfs4_proc_getattr(struct nfs_server *server, struct nfs_fh *fhandle,
>  			     struct nfs_fattr *fattr, struct inode *inode);
>  extern int update_open_stateid(struct nfs4_state *state,
> diff --git a/fs/nfs/nfs4file.c b/fs/nfs/nfs4file.c
> index e79ae4cbc395..494ebc7cd1c0 100644
> --- a/fs/nfs/nfs4file.c
> +++ b/fs/nfs/nfs4file.c
> @@ -9,6 +9,8 @@
>  #include <linux/falloc.h>
>  #include <linux/mount.h>
>  #include <linux/nfs_fs.h>
> +#include <linux/time32.h>
> +#include <linux/compat.h>
>  #include <linux/nfs_ssc.h>
>  #include "delegation.h"
>  #include "internal.h"
> @@ -132,6 +134,503 @@ nfs4_file_flush(struct file *file, fl_owner_t id)
>  	return filemap_check_wb_err(file->f_mapping, since);
>  }
>  
> +static int nfs_get_timespec64(struct timespec64 *ts,
> +			      const struct nfs_ioctl_timespec __user *uts)
> +{
> +	__s64 dummy;
> +	if (unlikely(get_user(dummy, &uts->tv_sec) != 0))
> +		return EFAULT;
> +	ts->tv_sec = dummy;
> +	if (unlikely(get_user(dummy, &uts->tv_nsec) != 0))
> +		return EFAULT;
> +	ts->tv_nsec = dummy;
> +	return 0;
> +}
> +
> +static int nfs_put_timespec64(const struct timespec64 *ts,
> +			      struct nfs_ioctl_timespec __user *uts)
> +{
> +	__s64 dummy;
> +
> +	dummy = ts->tv_sec;
> +	if (unlikely(put_user(dummy, &uts->tv_sec) != 0))
> +		return EFAULT;
> +	dummy = ts->tv_nsec;
> +	if (unlikely(put_user(dummy, &uts->tv_nsec) != 0))
> +		return EFAULT;
> +	return 0;
> +}
> +
> +static struct file *nfs4_get_real_file(struct file *src, unsigned int fd)
> +{
> +	struct file *filp = fget_raw(fd);
> +	int ret = -EBADF;
> +
> +	if (!filp)
> +		goto out;
> +	/* Validate that the files share the same underlying filesystem */
> +	ret = -EXDEV;
> +	if (file_inode(filp)->i_sb != file_inode(src)->i_sb)
> +		goto out_put;
> +	return filp;
> +out_put:
> +	fput(filp);
> +out:
> +	return ERR_PTR(ret);
> +}
> +
> +static unsigned long nfs4_statx_request_to_cache_validity(__u64 request,
> +							  u64 fattr_supported)
> +{
> +	unsigned long ret = 0;
> +
> +	if (request & NFS_FA_VALID_ATIME)
> +		ret |= NFS_INO_INVALID_ATIME;
> +	if (request & NFS_FA_VALID_CTIME)
> +		ret |= NFS_INO_INVALID_CTIME;
> +	if (request & NFS_FA_VALID_MTIME)
> +		ret |= NFS_INO_INVALID_MTIME;
> +	if (request & NFS_FA_VALID_SIZE)
> +		ret |= NFS_INO_INVALID_SIZE;
> +
> +	if (request & NFS_FA_VALID_MODE)
> +		ret |= NFS_INO_INVALID_MODE;
> +	if (request & (NFS_FA_VALID_OWNER | NFS_FA_VALID_OWNER_GROUP))
> +		ret |= NFS_INO_INVALID_OTHER;
> +
> +	if (request & NFS_FA_VALID_NLINK)
> +		ret |= NFS_INO_INVALID_NLINK;
> +	if (request & NFS_FA_VALID_BLOCKS)
> +		ret |= NFS_INO_INVALID_BLOCKS;
> +
> +	if (request & NFS_FA_VALID_TIME_CREATE)
> +		ret |= NFS_INO_INVALID_BTIME;
> +
> +	if (request & NFS_FA_VALID_ARCHIVE) {
> +		if (fattr_supported & NFS_ATTR_FATTR_ARCHIVE)
> +			ret |= NFS_INO_INVALID_WINATTR;
> +		else if (fattr_supported & NFS_ATTR_FATTR_TIME_BACKUP)
> +			ret |= NFS_INO_INVALID_WINATTR | NFS_INO_INVALID_MTIME;
> +	}
> +	if (request & (NFS_FA_VALID_TIME_BACKUP | NFS_FA_VALID_HIDDEN |
> +		       NFS_FA_VALID_SYSTEM | NFS_FA_VALID_OFFLINE))
> +		ret |= NFS_INO_INVALID_WINATTR;
> +
> +	return ret ? (ret | NFS_INO_INVALID_CHANGE) : 0;
> +}
> +
> +static long nfs4_ioctl_file_statx_get(struct file *dst_file,
> +				      struct nfs_ioctl_nfs4_statx __user *uarg)
> +{
> +	struct nfs4_statx args = {
> +		.real_fd = -1,
> +		.fa_valid = { 0 },
> +	};
> +	struct inode *inode;
> +	struct nfs_inode *nfsi;
> +	struct nfs_server *server;
> +	u64 fattr_supported;
> +	unsigned long reval_attr;
> +	unsigned int reval_flags;
> +	__u32 tmp;
> +	int ret;
> +
> +	/*
> +	 * We get the first word from the uarg as it tells us whether
> +	 * to use the passed in struct file or use that fd to find the
> +	 * struct file.
> +	 */
> +	if (get_user(args.real_fd, &uarg->real_fd))
> +		return -EFAULT;
> +
> +	if (get_user(args.fa_options, &uarg->fa_options))
> +		return -EFAULT;
> +
> +	if (get_user(args.fa_request[0], &uarg->fa_request[0]))
> +		return -EFAULT;
> +
> +	if (args.real_fd >= 0) {
> +		dst_file = nfs4_get_real_file(dst_file, args.real_fd);
> +		if (IS_ERR(dst_file))
> +			return PTR_ERR(dst_file);
> +	}
> +
> +	/*
> +	 * Backward compatibility: we stole the top 32 bits of 'real_fd'
> +	 * to create the fa_options field, so if its value is -1, then
> +	 * assume it is the high word of (__s64)real_fd == -1, and just
> +	 * set it to zero.
> +	 */
> +	if (args.fa_options == 0xFFFF)
> +		args.fa_options = 0;
> +
> +	inode = file_inode(dst_file);
> +	nfsi = NFS_I(inode);
> +	server = NFS_SERVER(inode);
> +	fattr_supported = server->fattr_valid;
> +
> +	trace_nfs_ioctl_file_statx_get_enter(inode);
> +
> +	if (args.fa_options & NFS_FA_OPTIONS_FORCE_SYNC)
> +		reval_flags = AT_STATX_FORCE_SYNC;
> +	else if (args.fa_options & NFS_FA_OPTIONS_DONT_SYNC)
> +		reval_flags = AT_STATX_DONT_SYNC;
> +	else
> +		reval_flags = AT_STATX_SYNC_AS_STAT;
> +
> +	reval_attr = nfs4_statx_request_to_cache_validity(args.fa_request[0],
> +							  fattr_supported);
> +
> +	if ((reval_attr & (NFS_INO_INVALID_CTIME | NFS_INO_INVALID_MTIME)) &&
> +	    reval_flags != AT_STATX_DONT_SYNC && S_ISREG(inode->i_mode)) {
> +		ret = filemap_write_and_wait(inode->i_mapping);
> +		if (ret)
> +			goto out;
> +	}
> +
> +	if ((dst_file->f_path.mnt->mnt_flags & MNT_NOATIME) ||
> +	    ((dst_file->f_path.mnt->mnt_flags & MNT_NODIRATIME) &&
> +	     S_ISDIR(inode->i_mode)))
> +		reval_attr &= ~NFS_INO_INVALID_ATIME;
> +
> +	ret = nfs_getattr_revalidate(&dst_file->f_path, reval_attr,
> +				     reval_flags);
> +	if (ret != 0)
> +		goto out;
> +
> +	ret = -EFAULT;
> +	if ((fattr_supported & NFS_ATTR_FATTR_OWNER) &&
> +	    (args.fa_request[0] & NFS_FA_VALID_OWNER)) {
> +		tmp = from_kuid_munged(current_user_ns(), inode->i_uid);
> +		if (unlikely(put_user(tmp, &uarg->fa_owner_uid) != 0))
> +			goto out;
> +		args.fa_valid[0] |= NFS_FA_VALID_OWNER;
> +	}
> +
> +	if ((fattr_supported & NFS_ATTR_FATTR_GROUP) &&
> +	    (args.fa_request[0] & NFS_FA_VALID_OWNER_GROUP)) {
> +		tmp = from_kgid_munged(current_user_ns(), inode->i_gid);
> +		if (unlikely(put_user(tmp, &uarg->fa_group_gid) != 0))
> +			goto out;
> +		args.fa_valid[0] |= NFS_FA_VALID_OWNER_GROUP;
> +	}
> +
> +	if ((fattr_supported & NFS_ATTR_FATTR_TIME_BACKUP) &&
> +	    (args.fa_request[0] & NFS_FA_VALID_TIME_BACKUP)) {
> +		if (nfs_put_timespec64(&nfsi->timebackup, &uarg->fa_time_backup))
> +			goto out;
> +		args.fa_valid[0] |= NFS_FA_VALID_TIME_BACKUP;
> +	}
> +
> +	if ((fattr_supported & NFS_ATTR_FATTR_BTIME) &&
> +	    (args.fa_request[0] & NFS_FA_VALID_TIME_CREATE)) {
> +		if (nfs_put_timespec64(&nfsi->btime, &uarg->fa_btime))
> +			goto out;
> +		args.fa_valid[0] |= NFS_FA_VALID_TIME_CREATE;
> +	}
> +
> +	/* atime, mtime, and ctime are all stored in the regular inode,
> +	 * not the nfs inode.
> +	 */
> +	if ((fattr_supported & NFS_ATTR_FATTR_ATIME) &&
> +	    (args.fa_request[0] & NFS_FA_VALID_ATIME)) {
> +		if (nfs_put_timespec64(&inode->i_atime, &uarg->fa_atime))
> +			goto out;
> +		args.fa_valid[0] |= NFS_FA_VALID_ATIME;
> +	}
> +
> +	if ((fattr_supported & NFS_ATTR_FATTR_MTIME) &&
> +	    (args.fa_request[0] & NFS_FA_VALID_MTIME)) {
> +		if (nfs_put_timespec64(&inode->i_mtime, &uarg->fa_mtime))
> +                        goto out;
> +		args.fa_valid[0] |= NFS_FA_VALID_MTIME;
> +	}
> +
> +	if ((fattr_supported & NFS_ATTR_FATTR_CTIME) &&
> +	    (args.fa_request[0] & NFS_FA_VALID_CTIME)) {
> +		if (nfs_put_timespec64(&inode->i_ctime, &uarg->fa_ctime))
> +			goto out;
> +		args.fa_valid[0] |= NFS_FA_VALID_CTIME;
> +	}
> +
> +        /*
> +         * It looks like PDFS does not support or properly handle the
> +         * archive bit.
> +         */
> +	if ((fattr_supported & NFS_ATTR_FATTR_ARCHIVE) &&
> +	    (args.fa_request[0] & NFS_FA_VALID_ARCHIVE)) {
> +		if (nfsi->archive)
> +			args.fa_flags |= NFS_FA_FLAG_ARCHIVE;
> +		args.fa_valid[0] |= NFS_FA_VALID_ARCHIVE;
> +	}
> +
> +	if ((fattr_supported & NFS_ATTR_FATTR_TIME_BACKUP) &&
> +	    (args.fa_request[0] & NFS_FA_VALID_ARCHIVE)) {
> +		if (timespec64_compare(&inode->i_mtime, &nfsi->timebackup) > 0)
> +			args.fa_flags |= NFS_FA_FLAG_ARCHIVE;
> +		args.fa_valid[0] |= NFS_FA_VALID_ARCHIVE;
> +	}
> +
> +	if ((fattr_supported & NFS_ATTR_FATTR_HIDDEN) &&
> +	    (args.fa_request[0] & NFS_FA_VALID_HIDDEN)) {
> +		if (nfsi->hidden)
> +			args.fa_flags |= NFS_FA_FLAG_HIDDEN;
> +		args.fa_valid[0] |= NFS_FA_VALID_HIDDEN;
> +	}
> +	if ((fattr_supported & NFS_ATTR_FATTR_SYSTEM) &&
> +	    (args.fa_request[0] & NFS_FA_VALID_SYSTEM)) {
> +		if (nfsi->system)
> +			args.fa_flags |= NFS_FA_FLAG_SYSTEM;
> +		args.fa_valid[0] |= NFS_FA_VALID_SYSTEM;
> +	}
> +
> +	if ((fattr_supported & NFS_ATTR_FATTR_OFFLINE) &&
> +	    (args.fa_request[0] & NFS_FA_VALID_OFFLINE)) {
> +		if (nfsi->offline)
> +			args.fa_flags |= NFS_FA_FLAG_OFFLINE;
> +		args.fa_valid[0] |= NFS_FA_VALID_OFFLINE;
> +	}
> +
> +	if ((args.fa_valid[0] & (NFS_FA_VALID_ARCHIVE |
> +				NFS_FA_VALID_HIDDEN |
> +				NFS_FA_VALID_SYSTEM |
> +				NFS_FA_VALID_OFFLINE)) &&
> +	    put_user(args.fa_flags, &uarg->fa_flags))
> +		goto out;
> +
> +	if ((fattr_supported & NFS_ATTR_FATTR_MODE) &&
> +	    (args.fa_request[0] & NFS_FA_VALID_MODE)) {
> +		tmp = inode->i_mode;
> +		/* This is an unsigned short we put into an __u32 */
> +		if (unlikely(put_user(tmp, &uarg->fa_mode) != 0))
> +			goto out;
> +		args.fa_valid[0] |= NFS_FA_VALID_MODE;
> +	}
> +
> +	if ((fattr_supported & NFS_ATTR_FATTR_NLINK) &&
> +	    (args.fa_request[0] & NFS_FA_VALID_NLINK)) {
> +		tmp = inode->i_nlink;
> +		if (unlikely(put_user(tmp, &uarg->fa_nlink) != 0))
> +			goto out;
> +		args.fa_valid[0] |= NFS_FA_VALID_NLINK;
> +	}
> +
> +	if (args.fa_request[0] & NFS_FA_VALID_BLKSIZE) {
> +		tmp = i_blocksize(inode);
> +		if (S_ISDIR(inode->i_mode))
> +			tmp = NFS_SERVER(inode)->dtsize;
> +		if (unlikely(put_user(tmp, &uarg->fa_blksize) != 0))
> +			goto out;
> +		args.fa_valid[0] |= NFS_FA_VALID_BLKSIZE;
> +	}
> +
> +	if (args.fa_request[0] & NFS_FA_VALID_INO) {
> +		__u64 ino = nfs_compat_user_ino64(NFS_FILEID(inode));
> +		if (unlikely(put_user(ino, &uarg->fa_ino) != 0))
> +			goto out;
> +		args.fa_valid[0] |= NFS_FA_VALID_INO;
> +	}
> +
> +	if (args.fa_request[0] & NFS_FA_VALID_DEV) {
> +		tmp = inode->i_sb->s_dev;
> +		if (unlikely(put_user(tmp, &uarg->fa_dev) != 0))
> +			goto out;
> +		args.fa_valid[0] |= NFS_FA_VALID_DEV;
> +	}
> +
> +	if ((fattr_supported & NFS_ATTR_FATTR_RDEV) &&
> +	    (args.fa_request[0] & NFS_FA_VALID_RDEV)) {
> +		tmp = inode->i_rdev;
> +		if (unlikely(put_user(tmp, &uarg->fa_rdev) != 0))
> +			goto out;
> +		args.fa_valid[0] |= NFS_FA_VALID_RDEV;
> +	}
> +
> +	if ((fattr_supported & NFS_ATTR_FATTR_SIZE) &&
> +	    (args.fa_request[0] & NFS_FA_VALID_SIZE)) {
> +		__s64 size = i_size_read(inode);
> +		if (unlikely(put_user(size, &uarg->fa_size) != 0))
> +			goto out;
> +		args.fa_valid[0] |= NFS_FA_VALID_SIZE;
> +	}
> +
> +	if ((fattr_supported &
> +	     (NFS_ATTR_FATTR_BLOCKS_USED | NFS_ATTR_FATTR_SPACE_USED)) &&
> +	    (args.fa_request[0] & NFS_FA_VALID_BLOCKS)) {
> +		__s64 blocks = inode->i_blocks;
> +		if (unlikely(put_user(blocks, &uarg->fa_blocks) != 0))
> +			goto out;
> +		args.fa_valid[0] |= NFS_FA_VALID_BLOCKS;
> +	}
> +
> +	if (unlikely(put_user(args.fa_valid[0], &uarg->fa_valid[0]) != 0))
> +		goto out;
> +	if (unlikely(put_user(args.fa_valid[1], &uarg->fa_valid[1]) != 0))
> +		goto out;
> +
> +	ret = 0;
> +out:
> +	if (args.real_fd >= 0)
> +		fput(dst_file);
> +	trace_nfs_ioctl_file_statx_get_exit(inode, ret);
> +	return ret;
> +}
> +
> +static long nfs4_ioctl_file_statx_set(struct file *dst_file,
> +				      struct nfs_ioctl_nfs4_statx __user *uarg)
> +{
> +	struct nfs4_statx args = {
> +		.real_fd = -1,
> +		.fa_valid = { 0 },
> +	};
> +	struct nfs_fattr *fattr = nfs_alloc_fattr();
> +	struct inode *inode;
> +	/*
> +	 * If you need a different error code below, you need to set it
> +	 */
> +	int ret = -EFAULT;
> +
> +	if (fattr == NULL)
> +		return -ENOMEM;
> +
> +	/*
> +	 * We get the first u64 word from the uarg as it tells us whether
> +	 * to use the passed in struct file or use that fd to find the
> +	 * struct file.
> +	 */
> +	if (get_user(args.real_fd, &uarg->real_fd))
> +		goto out_free;
> +
> +	if (args.real_fd >= 0) {
> +		dst_file = nfs4_get_real_file(dst_file, args.real_fd);
> +		if (IS_ERR(dst_file)) {
> +			ret = PTR_ERR(dst_file);
> +			goto out_free;
> +		}
> +	}
> +	inode = file_inode(dst_file);
> +	trace_nfs_ioctl_file_statx_set_enter(inode);
> +
> +	inode_lock(inode);
> +
> +	/* Write all dirty data */
> +	if (S_ISREG(inode->i_mode)) {
> +		ret = nfs_sync_inode(inode);
> +		if (ret)
> +			goto out;
> +	}
> +
> +	ret = -EFAULT;
> +	if (get_user(args.fa_valid[0], &uarg->fa_valid[0]))
> +		goto out;
> +	args.fa_valid[0] &= NFS_FA_VALID_ALL_ATTR_0;
> +
> +	if (args.fa_valid[0] & NFS_FA_VALID_OWNER) {
> +		uid_t uid;
> +
> +		if (unlikely(get_user(uid, &uarg->fa_owner_uid) != 0))
> +			goto out;
> +		args.fa_owner_uid = make_kuid(current_user_ns(), uid);
> +		if (!uid_valid(args.fa_owner_uid)) {
> +			ret = -EINVAL;
> +			goto out;
> +		}
> +	}
> +
> +	if (args.fa_valid[0] & NFS_FA_VALID_OWNER_GROUP) {
> +		gid_t gid;
> +
> +		if (unlikely(get_user(gid, &uarg->fa_group_gid) != 0))
> +			goto out;
> +		args.fa_group_gid = make_kgid(current_user_ns(), gid);
> +		if (!gid_valid(args.fa_group_gid)) {
> +			ret = -EINVAL;
> +			goto out;
> +		}
> +	}
> +
> +	if ((args.fa_valid[0] & (NFS_FA_VALID_ARCHIVE |
> +					NFS_FA_VALID_HIDDEN |
> +					NFS_FA_VALID_SYSTEM)) &&
> +	    get_user(args.fa_flags, &uarg->fa_flags))
> +		goto out;
> +
> +	if ((args.fa_valid[0] & NFS_FA_VALID_TIME_CREATE) &&
> +	    nfs_get_timespec64(&args.fa_btime, &uarg->fa_btime))
> +		goto out;
> +
> +	if ((args.fa_valid[0] & NFS_FA_VALID_ATIME) &&
> +	    nfs_get_timespec64(&args.fa_atime, &uarg->fa_atime))
> +		goto out;
> +
> +	if ((args.fa_valid[0] & NFS_FA_VALID_MTIME) &&
> +	    nfs_get_timespec64(&args.fa_mtime, &uarg->fa_mtime))
> +		goto out;
> +
> +	if (args.fa_valid[0] & NFS_FA_VALID_TIME_BACKUP) {
> +		if (nfs_get_timespec64(&args.fa_time_backup, &uarg->fa_time_backup))
> +			goto out;
> +	} else if ((args.fa_valid[0] & NFS_FA_VALID_ARCHIVE) &&
> +			!(NFS_SERVER(inode)->fattr_valid & NFS_ATTR_FATTR_ARCHIVE)) {
> +		args.fa_valid[0] |= NFS_FA_VALID_TIME_BACKUP;
> +		if (!(args.fa_flags & NFS_FA_FLAG_ARCHIVE)) {
> +			nfs_revalidate_inode(inode, NFS_INO_INVALID_MTIME);
> +			args.fa_time_backup.tv_sec = inode->i_mtime.tv_sec;
> +			args.fa_time_backup.tv_nsec = inode->i_mtime.tv_nsec;
> +		} else if (args.fa_valid[0] & NFS_FA_VALID_TIME_CREATE)
> +			args.fa_time_backup = args.fa_btime;
> +		else {
> +			nfs_revalidate_inode(inode, NFS_INO_INVALID_BTIME);
> +			args.fa_time_backup = NFS_I(inode)->btime;
> +		}
> +	}
> +
> +        if (args.fa_valid[0] & NFS_FA_VALID_SIZE) {
> +		if (copy_from_user(&args.fa_size, &uarg->fa_size,
> +					sizeof(args.fa_size)))
> +			goto out;
> +		ret = inode_newsize_ok(inode,args.fa_size);
> +		if (ret)
> +			goto out;
> +		if (args.fa_size == i_size_read(inode))
> +			args.fa_valid[0] &= ~NFS_FA_VALID_SIZE;
> +	}
> +
> +	/*
> +	 * No need to update the inode because that is done in nfs4_set_nfs4_statx
> +	 */
> +	ret = nfs4_set_nfs4_statx(inode, &args, fattr);
> +
> +out:
> +	inode_unlock(inode);
> +	if (args.real_fd >= 0)
> +		fput(dst_file);
> +	trace_nfs_ioctl_file_statx_set_exit(inode, ret);
> +out_free:
> +	nfs_free_fattr(fattr);
> +	return ret;
> +}
> +
> +static long nfs4_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
> +{
> +	void __user *argp = (void __user *)arg;
> +	long ret;
> +
> +	switch (cmd) {
> +	case NFS_IOC_FILE_STATX_GET:
> +		ret = nfs4_ioctl_file_statx_get(file, argp);
> +		break;
> +	case NFS_IOC_FILE_STATX_SET:
> +		ret = nfs4_ioctl_file_statx_set(file, argp);
> +		break;
> +	default:
> +		ret = -ENOIOCTLCMD;
> +	}
> +
> +	dprintk("%s: file=%pD2, cmd=%u, ret=%ld\n", __func__, file, cmd, ret);
> +	return ret;
> +}
> +
>  #ifdef CONFIG_NFS_V4_2
>  static ssize_t __nfs4_copy_file_range(struct file *file_in, loff_t pos_in,
>  				      struct file *file_out, loff_t pos_out,
> @@ -187,6 +686,7 @@ static ssize_t __nfs4_copy_file_range(struct file *file_in, loff_t pos_in,
>  	return ret;
>  }
>  
> +
>  static ssize_t nfs4_copy_file_range(struct file *file_in, loff_t pos_in,
>  				    struct file *file_out, loff_t pos_out,
>  				    size_t count, unsigned int flags)
> @@ -461,4 +961,15 @@ const struct file_operations nfs4_file_operations = {
>  #else
>  	.llseek		= nfs_file_llseek,
>  #endif
> +	.unlocked_ioctl	= nfs4_ioctl,
> +};
> +
> +const struct file_operations nfs4_dir_operations = {
> +	.llseek		= nfs_llseek_dir,
> +	.read		= generic_read_dir,
> +	.iterate_shared	= nfs_readdir,
> +	.open		= nfs_opendir,
> +	.release	= nfs_closedir,
> +	.fsync		= nfs_fsync_dir,
> +	.unlocked_ioctl	= nfs4_ioctl,
>  };
> diff --git a/fs/nfs/nfs4proc.c b/fs/nfs/nfs4proc.c
> index d497616ca149..7c032583ffa2 100644
> --- a/fs/nfs/nfs4proc.c
> +++ b/fs/nfs/nfs4proc.c
> @@ -7959,6 +7959,129 @@ static int _nfs41_proc_get_locations(struct inode *inode,
>  
>  #endif	/* CONFIG_NFS_V4_1 */
>  
> +static int _nfs4_set_nfs4_statx(struct inode *inode,
> +		struct nfs4_statx *statx,
> +		struct nfs_fattr *fattr)
> +{
> +	const __u64 statx_win = NFS_FA_VALID_TIME_CREATE |
> +				NFS_FA_VALID_TIME_BACKUP |
> +				NFS_FA_VALID_ARCHIVE | NFS_FA_VALID_HIDDEN |
> +				NFS_FA_VALID_SYSTEM;
> +	struct iattr sattr = {0};
> +	struct nfs_server *server = NFS_SERVER(inode);
> +	__u32 bitmask[3];
> +	struct nfs_setattrargs arg = {
> +		.fh             = NFS_FH(inode),
> +		.iap            = &sattr,
> +		.server		= server,
> +		.bitmask	= bitmask,
> +		.statx		= statx,
> +	};
> +	struct nfs_setattrres res = {
> +		.fattr		= fattr,
> +		.server		= server,
> +	};
> +	struct rpc_message msg = {
> +		.rpc_proc       = &nfs4_procedures[NFSPROC4_CLNT_SETATTR],
> +		.rpc_argp       = &arg,
> +		.rpc_resp       = &res,
> +	};
> +	int status;
> +
> +	nfs4_bitmap_copy_adjust(
> +		bitmask, server->attr_bitmask, inode,
> +		NFS_INO_INVALID_CHANGE | NFS_INO_INVALID_CTIME |
> +			NFS_INO_INVALID_SIZE | NFS_INO_INVALID_OTHER |
> +			NFS_INO_INVALID_BTIME | NFS_INO_INVALID_WINATTR);
> +	/* Use the iattr structure to set atime and mtime since handling already
> +	 * exists for them using the iattr struct in the encode_attrs()
> +	 * (xdr encoding) routine.
> +	 */
> +	if (statx && (statx->fa_valid[0] & NFS_FA_VALID_MTIME)) {
> +		sattr.ia_valid |= ATTR_MTIME_SET;
> +		sattr.ia_mtime.tv_sec = statx->fa_mtime.tv_sec;
> +		sattr.ia_mtime.tv_nsec = statx->fa_mtime.tv_nsec;
> +	}
> +
> +	if (statx && (statx->fa_valid[0] & NFS_FA_VALID_ATIME)) {
> +		sattr.ia_valid |= ATTR_ATIME_SET;
> +		sattr.ia_atime.tv_sec = statx->fa_atime.tv_sec;
> +		sattr.ia_atime.tv_nsec = statx->fa_atime.tv_nsec;
> +	}
> +
> +	if (statx && (statx->fa_valid[0] & NFS_FA_VALID_OWNER)) {
> +		sattr.ia_valid |= ATTR_UID;
> +		sattr.ia_uid = statx->fa_owner_uid;
> +	}
> +
> +	if (statx && (statx->fa_valid[0] & NFS_FA_VALID_OWNER_GROUP)) {
> +		sattr.ia_valid |= ATTR_GID;
> +		sattr.ia_gid = statx->fa_group_gid;
> +	}
> +
> +	if (statx && (statx->fa_valid[0] & NFS_FA_VALID_SIZE)) {
> +		sattr.ia_valid |= ATTR_SIZE;
> +		sattr.ia_size = statx->fa_size;
> +	}
> +
> +	nfs4_stateid_copy(&arg.stateid, &zero_stateid);
> +
> +	status = nfs4_call_sync(server->client, server, &msg, &arg.seq_args, &res.seq_res, 1);
> +	if (!status) {
> +		if (statx->fa_valid[0] & statx_win) {
> +			struct nfs_inode *nfsi = NFS_I(inode);
> +
> +			spin_lock(&inode->i_lock);
> +			if (statx->fa_valid[0] & NFS_FA_VALID_TIME_CREATE)
> +				nfsi->btime = statx->fa_btime;
> +			if (statx->fa_valid[0] & NFS_FA_VALID_TIME_BACKUP)
> +				nfsi->timebackup = statx->fa_time_backup;
> +			if (statx->fa_valid[0] & NFS_FA_VALID_ARCHIVE)
> +				nfsi->archive = (statx->fa_flags &
> +						 NFS_FA_FLAG_ARCHIVE) != 0;
> +			if (statx->fa_valid[0] & NFS_FA_VALID_SYSTEM)
> +				nfsi->system = (statx->fa_flags &
> +						NFS_FA_FLAG_SYSTEM) != 0;
> +			if (statx->fa_valid[0] & NFS_FA_VALID_HIDDEN)
> +				nfsi->hidden = (statx->fa_flags &
> +						NFS_FA_FLAG_HIDDEN) != 0;
> +			if (statx->fa_valid[0] & NFS_FA_VALID_OFFLINE)
> +				nfsi->offline = (statx->fa_flags &
> +						 NFS_FA_FLAG_OFFLINE) != 0;
> +
> +			nfsi->cache_validity &= ~NFS_INO_INVALID_CTIME;
> +			if (fattr->valid & NFS_ATTR_FATTR_CTIME)
> +				inode->i_ctime = fattr->ctime;
> +			else
> +				nfs_set_cache_invalid(
> +					inode, NFS_INO_INVALID_CHANGE |
> +						   NFS_INO_INVALID_CTIME);
> +			spin_unlock(&inode->i_lock);
> +		}
> +
> +		nfs_setattr_update_inode(inode, &sattr, fattr);
> +	} else
> +		dprintk("%s failed: %d\n", __func__, status);
> +
> +	return status;
> +}
> +
> +int nfs4_set_nfs4_statx(struct inode *inode,
> +		struct nfs4_statx *statx,
> +		struct nfs_fattr *fattr)
> +{
> +	struct nfs4_exception exception = { };
> +	struct nfs_server *server = NFS_SERVER(inode);
> +	int err;
> +
> +	do {
> +		err = nfs4_handle_exception(server,
> +				_nfs4_set_nfs4_statx(inode, statx, fattr),
> +				&exception);
> +	} while (exception.retry);
> +	return err;
> +}
> +
>  /**
>   * nfs4_proc_get_locations - discover locations for a migrated FSID
>   * @inode: inode on FSID that is migrating
> @@ -10419,6 +10542,7 @@ const struct nfs_rpc_ops nfs_v4_clientops = {
>  	.dir_inode_ops	= &nfs4_dir_inode_operations,
>  	.file_inode_ops	= &nfs4_file_inode_operations,
>  	.file_ops	= &nfs4_file_operations,
> +	.dir_ops	= &nfs4_dir_operations,
>  	.getroot	= nfs4_proc_get_root,
>  	.submount	= nfs4_submount,
>  	.try_get_tree	= nfs4_try_get_tree,
> diff --git a/fs/nfs/nfs4xdr.c b/fs/nfs/nfs4xdr.c
> index d2c240effc87..e5300d7ed712 100644
> --- a/fs/nfs/nfs4xdr.c
> +++ b/fs/nfs/nfs4xdr.c
> @@ -129,12 +129,15 @@ static int decode_layoutget(struct xdr_stream *xdr, struct rpc_rqst *req,
>  				nfs4_fattr_value_maxsz)
>  #define decode_getattr_maxsz    (op_decode_hdr_maxsz + nfs4_fattr_maxsz)
>  #define encode_attrs_maxsz	(nfs4_fattr_bitmap_maxsz + \
> -				 1 + 2 + 1 + \
> +				 1 + 2 + 1 + 1 + 1 + \
>  				nfs4_owner_maxsz + \
>  				nfs4_group_maxsz + \
> -				nfs4_label_maxsz + \
> +				1 + \
> +				1 + nfstime4_maxsz + \
> +				nfstime4_maxsz + nfstime4_maxsz + \
>  				1 + nfstime4_maxsz + \
> -				1 + nfstime4_maxsz)
> +				nfs4_label_maxsz + \
> +				2)
>  #define encode_savefh_maxsz     (op_encode_hdr_maxsz)
>  #define decode_savefh_maxsz     (op_decode_hdr_maxsz)
>  #define encode_restorefh_maxsz  (op_encode_hdr_maxsz)
> @@ -1081,6 +1084,7 @@ xdr_encode_nfstime4(__be32 *p, const struct timespec64 *t)
>  static void encode_attrs(struct xdr_stream *xdr, const struct iattr *iap,
>  				const struct nfs4_label *label,
>  				const umode_t *umask,
> +				const struct nfs4_statx *statx,
>  				const struct nfs_server *server,
>  				const uint32_t attrmask[])
>  {
> @@ -1153,6 +1157,34 @@ static void encode_attrs(struct xdr_stream *xdr, const struct iattr *iap,
>  		}
>  	}
>  
> +	if (statx && (statx->fa_valid[0] & NFS_FA_VALID_TIME_BACKUP) &&
> +	    (attrmask[1] & FATTR4_WORD1_TIME_BACKUP)) {
> +		bmval[1] |= FATTR4_WORD1_TIME_BACKUP;
> +		len += (nfstime4_maxsz << 2);
> +	}
> +	if (statx && (statx->fa_valid[0] & NFS_FA_VALID_TIME_CREATE) &&
> +	    (attrmask[1] & FATTR4_WORD1_TIME_CREATE)) {
> +		bmval[1] |= FATTR4_WORD1_TIME_CREATE;
> +		len += (nfstime4_maxsz << 2);
> +	}
> +
> +	if (statx && (statx->fa_valid[0] & NFS_FA_VALID_ARCHIVE) &&
> +	   (attrmask[0] & FATTR4_WORD0_ARCHIVE)) {
> +		bmval[0] |= FATTR4_WORD0_ARCHIVE;
> +		len += 4;
> +	}
> +	if (statx && (statx->fa_valid[0] & NFS_FA_VALID_HIDDEN) &&
> +	   (attrmask[0] & FATTR4_WORD0_HIDDEN)) {
> +		bmval[0] |= FATTR4_WORD0_HIDDEN;
> +		len += 4;
> +	}
> +
> +	if (statx && (statx->fa_valid[0] & NFS_FA_VALID_SYSTEM) &&
> +	   (attrmask[1] & FATTR4_WORD1_SYSTEM)) {
> +		bmval[1] |= FATTR4_WORD1_SYSTEM;
> +		len += 4;
> +	}
> +
>  	if (label && (attrmask[2] & FATTR4_WORD2_SECURITY_LABEL)) {
>  		len += 4 + 4 + 4 + (XDR_QUADLEN(label->len) << 2);
>  		bmval[2] |= FATTR4_WORD2_SECURITY_LABEL;
> @@ -1163,12 +1195,21 @@ static void encode_attrs(struct xdr_stream *xdr, const struct iattr *iap,
>  
>  	if (bmval[0] & FATTR4_WORD0_SIZE)
>  		p = xdr_encode_hyper(p, iap->ia_size);
> +	if (bmval[0] & FATTR4_WORD0_ARCHIVE)
> +		*p++ = (statx->fa_flags & NFS_FA_FLAG_ARCHIVE) ?
> +			cpu_to_be32(1) : cpu_to_be32(0);
> +	if (bmval[0] & FATTR4_WORD0_HIDDEN)
> +		*p++ = (statx->fa_flags & NFS_FA_FLAG_HIDDEN) ?
> +			cpu_to_be32(1) : cpu_to_be32(0);
>  	if (bmval[1] & FATTR4_WORD1_MODE)
>  		*p++ = cpu_to_be32(iap->ia_mode & S_IALLUGO);
>  	if (bmval[1] & FATTR4_WORD1_OWNER)
>  		p = xdr_encode_opaque(p, owner_name, owner_namelen);
>  	if (bmval[1] & FATTR4_WORD1_OWNER_GROUP)
>  		p = xdr_encode_opaque(p, owner_group, owner_grouplen);
> +	if (bmval[1] & FATTR4_WORD1_SYSTEM)
> +		*p++ = (statx->fa_flags & NFS_FA_FLAG_SYSTEM) ?
> +			cpu_to_be32(1) : cpu_to_be32(0);
>  	if (bmval[1] & FATTR4_WORD1_TIME_ACCESS_SET) {
>  		if (iap->ia_valid & ATTR_ATIME_SET) {
>  			*p++ = cpu_to_be32(NFS4_SET_TO_CLIENT_TIME);
> @@ -1176,6 +1217,10 @@ static void encode_attrs(struct xdr_stream *xdr, const struct iattr *iap,
>  		} else
>  			*p++ = cpu_to_be32(NFS4_SET_TO_SERVER_TIME);
>  	}
> +	if (bmval[1] & FATTR4_WORD1_TIME_BACKUP)
> +		p = xdr_encode_nfstime4(p, &statx->fa_time_backup);
> +	if (bmval[1] & FATTR4_WORD1_TIME_CREATE)
> +		p = xdr_encode_nfstime4(p, &statx->fa_btime);
>  	if (bmval[1] & FATTR4_WORD1_TIME_MODIFY_SET) {
>  		if (iap->ia_valid & ATTR_MTIME_SET) {
>  			*p++ = cpu_to_be32(NFS4_SET_TO_CLIENT_TIME);
> @@ -1248,7 +1293,7 @@ static void encode_create(struct xdr_stream *xdr, const struct nfs4_create_arg *
>  
>  	encode_string(xdr, create->name->len, create->name->name);
>  	encode_attrs(xdr, create->attrs, create->label, &create->umask,
> -			create->server, create->server->attr_bitmask);
> +		     NULL, create->server, create->server->attr_bitmask);
>  }
>  
>  static void encode_getattr(struct xdr_stream *xdr,
> @@ -1434,12 +1479,12 @@ static inline void encode_createmode(struct xdr_stream *xdr, const struct nfs_op
>  	case NFS4_CREATE_UNCHECKED:
>  		*p = cpu_to_be32(NFS4_CREATE_UNCHECKED);
>  		encode_attrs(xdr, arg->u.attrs, arg->label, &arg->umask,
> -				arg->server, arg->server->attr_bitmask);
> +			     NULL, arg->server, arg->server->attr_bitmask);
>  		break;
>  	case NFS4_CREATE_GUARDED:
>  		*p = cpu_to_be32(NFS4_CREATE_GUARDED);
>  		encode_attrs(xdr, arg->u.attrs, arg->label, &arg->umask,
> -				arg->server, arg->server->attr_bitmask);
> +			     NULL, arg->server, arg->server->attr_bitmask);
>  		break;
>  	case NFS4_CREATE_EXCLUSIVE:
>  		*p = cpu_to_be32(NFS4_CREATE_EXCLUSIVE);
> @@ -1449,7 +1494,7 @@ static inline void encode_createmode(struct xdr_stream *xdr, const struct nfs_op
>  		*p = cpu_to_be32(NFS4_CREATE_EXCLUSIVE4_1);
>  		encode_nfs4_verifier(xdr, &arg->u.verifier);
>  		encode_attrs(xdr, arg->u.attrs, arg->label, &arg->umask,
> -				arg->server, arg->server->exclcreat_bitmask);
> +			     NULL, arg->server, arg->server->exclcreat_bitmask);
>  	}
>  }
>  
> @@ -1712,8 +1757,8 @@ static void encode_setattr(struct xdr_stream *xdr, const struct nfs_setattrargs
>  {
>  	encode_op_hdr(xdr, OP_SETATTR, decode_setattr_maxsz, hdr);
>  	encode_nfs4_stateid(xdr, &arg->stateid);
> -	encode_attrs(xdr, arg->iap, arg->label, NULL, server,
> -			server->attr_bitmask);
> +	encode_attrs(xdr, arg->iap, arg->label, NULL, arg->statx, server,
> +		     server->attr_bitmask);
>  }
>  
>  static void encode_setclientid(struct xdr_stream *xdr, const struct nfs4_setclientid *setclientid, struct compound_hdr *hdr)
> diff --git a/fs/nfs/nfstrace.c b/fs/nfs/nfstrace.c
> index 5d1bfccbb4da..0b88deb0216e 100644
> --- a/fs/nfs/nfstrace.c
> +++ b/fs/nfs/nfstrace.c
> @@ -9,6 +9,11 @@
>  #define CREATE_TRACE_POINTS
>  #include "nfstrace.h"
>  
> +EXPORT_TRACEPOINT_SYMBOL_GPL(nfs_ioctl_file_statx_get_enter);
> +EXPORT_TRACEPOINT_SYMBOL_GPL(nfs_ioctl_file_statx_get_exit);
> +EXPORT_TRACEPOINT_SYMBOL_GPL(nfs_ioctl_file_statx_set_enter);
> +EXPORT_TRACEPOINT_SYMBOL_GPL(nfs_ioctl_file_statx_set_exit);
> +
>  EXPORT_TRACEPOINT_SYMBOL_GPL(nfs_fsync_enter);
>  EXPORT_TRACEPOINT_SYMBOL_GPL(nfs_fsync_exit);
>  EXPORT_TRACEPOINT_SYMBOL_GPL(nfs_xdr_status);
> diff --git a/fs/nfs/nfstrace.h b/fs/nfs/nfstrace.h
> index 2ef7cff8a4ba..b67dd087fb47 100644
> --- a/fs/nfs/nfstrace.h
> +++ b/fs/nfs/nfstrace.h
> @@ -166,6 +166,11 @@ DEFINE_NFS_INODE_EVENT_DONE(nfs_fsync_exit);
>  DEFINE_NFS_INODE_EVENT(nfs_access_enter);
>  DEFINE_NFS_INODE_EVENT_DONE(nfs_set_cache_invalid);
>  
> +DEFINE_NFS_INODE_EVENT(nfs_ioctl_file_statx_get_enter);
> +DEFINE_NFS_INODE_EVENT_DONE(nfs_ioctl_file_statx_get_exit);
> +DEFINE_NFS_INODE_EVENT(nfs_ioctl_file_statx_set_enter);
> +DEFINE_NFS_INODE_EVENT_DONE(nfs_ioctl_file_statx_set_exit);
> +
>  TRACE_EVENT(nfs_access_exit,
>  		TP_PROTO(
>  			const struct inode *inode,
> diff --git a/fs/nfs/proc.c b/fs/nfs/proc.c
> index 73dcaa99fa9b..8fd96d93630a 100644
> --- a/fs/nfs/proc.c
> +++ b/fs/nfs/proc.c
> @@ -717,6 +717,7 @@ const struct nfs_rpc_ops nfs_v2_clientops = {
>  	.dir_inode_ops	= &nfs_dir_inode_operations,
>  	.file_inode_ops	= &nfs_file_inode_operations,
>  	.file_ops	= &nfs_file_operations,
> +	.dir_ops	= &nfs_dir_operations,
>  	.getroot	= nfs_proc_get_root,
>  	.submount	= nfs_submount,
>  	.try_get_tree	= nfs_try_get_tree,
> diff --git a/include/linux/nfs_fs.h b/include/linux/nfs_fs.h
> index 058fc11338d9..0c3a5859f7f3 100644
> --- a/include/linux/nfs_fs.h
> +++ b/include/linux/nfs_fs.h
> @@ -501,6 +501,7 @@ extern __be32 root_nfs_parse_addr(char *name); /*__init*/
>  extern const struct file_operations nfs_file_operations;
>  #if IS_ENABLED(CONFIG_NFS_V4)
>  extern const struct file_operations nfs4_file_operations;
> +extern const struct file_operations nfs4_dir_operations;
>  #endif /* CONFIG_NFS_V4 */
>  extern const struct address_space_operations nfs_file_aops;
>  extern const struct address_space_operations nfs_dir_aops;
> diff --git a/include/linux/nfs_xdr.h b/include/linux/nfs_xdr.h
> index 0d5b11c1bfec..9ce61f680a13 100644
> --- a/include/linux/nfs_xdr.h
> +++ b/include/linux/nfs_xdr.h
> @@ -812,6 +812,7 @@ struct nfs_createargs {
>  	struct iattr *		sattr;
>  };
>  
> +struct nfs4_statx;
>  struct nfs_setattrargs {
>  	struct nfs4_sequence_args 	seq_args;
>  	struct nfs_fh *                 fh;
> @@ -820,6 +821,7 @@ struct nfs_setattrargs {
>  	const struct nfs_server *	server; /* Needed for name mapping */
>  	const u32 *			bitmask;
>  	const struct nfs4_label		*label;
> +	const struct nfs4_statx		*statx;
>  };
>  
>  struct nfs_setaclargs {
> @@ -1744,6 +1746,7 @@ struct nfs_rpc_ops {
>  	const struct inode_operations *dir_inode_ops;
>  	const struct inode_operations *file_inode_ops;
>  	const struct file_operations *file_ops;
> +	const struct file_operations *dir_ops;
>  	const struct nlmclnt_operations *nlmclnt_ops;
>  
>  	int	(*getroot) (struct nfs_server *, struct nfs_fh *,
> diff --git a/include/uapi/linux/nfs.h b/include/uapi/linux/nfs.h
> index 946cb62d64b0..df87da39bc43 100644
> --- a/include/uapi/linux/nfs.h
> +++ b/include/uapi/linux/nfs.h
> @@ -9,6 +9,8 @@
>  #define _UAPI_LINUX_NFS_H
>  
>  #include <linux/types.h>
> +#include <asm/byteorder.h>
> +#include <linux/time.h>
>  
>  #define NFS_PROGRAM	100003
>  #define NFS_PORT	2049
> @@ -35,6 +37,94 @@
>  
>  #define NFS_PIPE_DIRNAME "nfs"
>  
> +/* NFS ioctls */
> +#define NFS_IOC_FILE_STATX_GET	_IOR('N', 2, struct nfs_ioctl_nfs4_statx)
> +#define NFS_IOC_FILE_STATX_SET	_IOW('N', 3, struct nfs_ioctl_nfs4_statx)
> +
> +/* Options for struct nfs_ioctl_nfs4_statx */
> +#define NFS_FA_OPTIONS_SYNC_AS_STAT			0x0000
> +#define NFS_FA_OPTIONS_FORCE_SYNC			0x2000 /* See statx */
> +#define NFS_FA_OPTIONS_DONT_SYNC			0x4000 /* See statx */
> +
> +#define NFS_FA_VALID_TIME_CREATE			0x00001UL
> +#define NFS_FA_VALID_TIME_BACKUP			0x00002UL
> +#define NFS_FA_VALID_ARCHIVE				0x00004UL
> +#define NFS_FA_VALID_HIDDEN				0x00008UL
> +#define NFS_FA_VALID_SYSTEM				0x00010UL
> +#define NFS_FA_VALID_OWNER				0x00020UL
> +#define NFS_FA_VALID_OWNER_GROUP			0x00040UL
> +#define NFS_FA_VALID_ATIME				0x00080UL
> +#define NFS_FA_VALID_MTIME				0x00100UL
> +#define NFS_FA_VALID_CTIME				0x00200UL
> +#define NFS_FA_VALID_OFFLINE				0x00400UL
> +#define NFS_FA_VALID_MODE				0x00800UL
> +#define NFS_FA_VALID_NLINK				0x01000UL
> +#define NFS_FA_VALID_BLKSIZE				0x02000UL
> +#define NFS_FA_VALID_INO				0x04000UL
> +#define NFS_FA_VALID_DEV				0x08000UL
> +#define NFS_FA_VALID_RDEV				0x10000UL
> +#define NFS_FA_VALID_SIZE				0x20000UL
> +#define NFS_FA_VALID_BLOCKS				0x40000UL
> +
> +#define NFS_FA_VALID_ALL_ATTR_0 ( NFS_FA_VALID_TIME_CREATE | \
> +		NFS_FA_VALID_TIME_BACKUP | \
> +		NFS_FA_VALID_ARCHIVE | \
> +		NFS_FA_VALID_HIDDEN | \
> +		NFS_FA_VALID_SYSTEM | \
> +		NFS_FA_VALID_OWNER | \
> +		NFS_FA_VALID_OWNER_GROUP | \
> +		NFS_FA_VALID_ATIME | \
> +		NFS_FA_VALID_MTIME | \
> +		NFS_FA_VALID_CTIME | \
> +		NFS_FA_VALID_OFFLINE | \
> +		NFS_FA_VALID_MODE | \
> +		NFS_FA_VALID_NLINK | \
> +		NFS_FA_VALID_BLKSIZE | \
> +		NFS_FA_VALID_INO | \
> +		NFS_FA_VALID_DEV | \
> +		NFS_FA_VALID_RDEV | \
> +		NFS_FA_VALID_SIZE | \
> +		NFS_FA_VALID_BLOCKS)
> +
> +#define NFS_FA_FLAG_ARCHIVE				(1UL << 0)
> +#define NFS_FA_FLAG_HIDDEN				(1UL << 1)
> +#define NFS_FA_FLAG_SYSTEM				(1UL << 2)
> +#define NFS_FA_FLAG_OFFLINE				(1UL << 3)
> +
> +struct nfs_ioctl_timespec {
> +	__s64		tv_sec;
> +	__s64		tv_nsec;
> +};
> +
> +struct nfs_ioctl_nfs4_statx {
> +	__s32		real_fd;		/* real FD to use,
> +						   -1 means use current file */
> +	__u32		fa_options;
> +
> +	__u64		fa_request[2];		/* Attributes to retrieve */
> +	__u64		fa_valid[2];		/* Attributes set */
> +
> +	struct nfs_ioctl_timespec fa_time_backup;/* Backup time */
> +	struct nfs_ioctl_timespec fa_btime;     /* Birth time */
> +	__u64		fa_flags;		/* Flag attributes */
> +	/* Ordinary attributes follow */
> +	struct nfs_ioctl_timespec fa_atime;	/* Access time */
> +	struct nfs_ioctl_timespec fa_mtime;	/* Modify time */
> +	struct nfs_ioctl_timespec fa_ctime;	/* Change time */
> +	__u32		fa_owner_uid;		/* Owner User ID */
> +	__u32		fa_group_gid;		/* Primary Group ID */
> +	__u32		fa_mode;		/* Mode */
> +	__u32	 	fa_nlink;
> +	__u32		fa_blksize;
> +	__u32		fa_spare;		/* Alignment */
> +	__u64		fa_ino;
> +	__u32		fa_dev;
> +	__u32		fa_rdev;
> +	__s64		fa_size;
> +	__s64		fa_blocks;
> +	__u64 		fa_padding[4];
> +};
> +
>  /*
>   * NFS stats. The good thing with these values is that NFSv3 errors are
>   * a superset of NFSv2 errors (with the exception of NFSERR_WFLUSH which
> -- 
> 2.33.1
Trond Myklebust Jan. 3, 2022, 8:56 p.m. UTC | #2
On Mon, 2022-01-03 at 15:52 -0500, J. Bruce Fields wrote:
> On Fri, Dec 17, 2021 at 03:48:53PM -0500, trondmy@kernel.org wrote:
> > From: Richard Sharpe <richard.sharpe@primarydata.com>
> > 
> > Add support for returning all of the Windows attributes with a
> > statx
> > ioctl.
> 
> I suppose I'm just woodshedding, but "statx ioctl" is a little
> confusing--it doesn't have any actual connection with the statx
> system call, right?
> 
> But, why not add this to statx?

We could definitely add the attribute retrieval to the statx() system
call. I believe that Steve French did suggest that at one point. There
was push back because the number of applications that care is limited.
Perhaps there might be more interest now that we have more extensive
support for NTFS in the kernel.

However there is no statx() call to actually _set_ these attributes,
and that is a reason to stick with the ioctl() approach for now.


> 
> --b.
> 
> > Add support for setting all of the Windows attributes using an
> > ioctl.
> > 
> > Signed-off-by: Richard Sharpe <richard.sharpe@primarydata.com>
> > Signed-off-by: Lance Shelton <lance.shelton@hammerspace.com>
> > Signed-off-by: Trond Myklebust <trond.myklebust@hammerspace.com>
> > ---
> >  fs/nfs/dir.c             |  24 +-
> >  fs/nfs/getroot.c         |   3 +-
> >  fs/nfs/inode.c           |  41 +++-
> >  fs/nfs/internal.h        |   8 +
> >  fs/nfs/nfs3proc.c        |   1 +
> >  fs/nfs/nfs4_fs.h         |  31 +++
> >  fs/nfs/nfs4file.c        | 511
> > +++++++++++++++++++++++++++++++++++++++
> >  fs/nfs/nfs4proc.c        | 124 ++++++++++
> >  fs/nfs/nfs4xdr.c         |  63 ++++-
> >  fs/nfs/nfstrace.c        |   5 +
> >  fs/nfs/nfstrace.h        |   5 +
> >  fs/nfs/proc.c            |   1 +
> >  include/linux/nfs_fs.h   |   1 +
> >  include/linux/nfs_xdr.h  |   3 +
> >  include/uapi/linux/nfs.h |  90 +++++++
> >  15 files changed, 887 insertions(+), 24 deletions(-)
> > 
> > diff --git a/fs/nfs/dir.c b/fs/nfs/dir.c
> > index 731d31015b6a..f6fc60822153 100644
> > --- a/fs/nfs/dir.c
> > +++ b/fs/nfs/dir.c
> > @@ -48,11 +48,6 @@
> >  
> >  /* #define NFS_DEBUG_VERBOSE 1 */
> >  
> > -static int nfs_opendir(struct inode *, struct file *);
> > -static int nfs_closedir(struct inode *, struct file *);
> > -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 page*);
> >  
> >  const struct file_operations nfs_dir_operations = {
> > @@ -63,6 +58,7 @@ const struct file_operations nfs_dir_operations =
> > {
> >         .release        = nfs_closedir,
> >         .fsync          = nfs_fsync_dir,
> >  };
> > +EXPORT_SYMBOL_GPL(nfs_dir_operations);
> >  
> >  const struct address_space_operations nfs_dir_aops = {
> >         .freepage = nfs_readdir_clear_array,
> > @@ -104,8 +100,7 @@ static void put_nfs_open_dir_context(struct
> > inode *dir, struct nfs_open_dir_cont
> >  /*
> >   * Open file
> >   */
> > -static int
> > -nfs_opendir(struct inode *inode, struct file *filp)
> > +int nfs_opendir(struct inode *inode, struct file *filp)
> >  {
> >         int res = 0;
> >         struct nfs_open_dir_context *ctx;
> > @@ -123,13 +118,14 @@ nfs_opendir(struct inode *inode, struct file
> > *filp)
> >  out:
> >         return res;
> >  }
> > +EXPORT_SYMBOL_GPL(nfs_opendir);
> >  
> > -static int
> > -nfs_closedir(struct inode *inode, struct file *filp)
> > +int nfs_closedir(struct inode *inode, struct file *filp)
> >  {
> >         put_nfs_open_dir_context(file_inode(filp), filp-
> > >private_data);
> >         return 0;
> >  }
> > +EXPORT_SYMBOL_GPL(nfs_closedir);
> >  
> >  struct nfs_cache_array_entry {
> >         u64 cookie;
> > @@ -1064,7 +1060,7 @@ static int uncached_readdir(struct
> > nfs_readdir_descriptor *desc)
> >     last cookie cache takes care of the common case of reading the
> >     whole directory.
> >   */
> > -static int nfs_readdir(struct file *file, struct dir_context *ctx)
> > +int nfs_readdir(struct file *file, struct dir_context *ctx)
> >  {
> >         struct dentry   *dentry = file_dentry(file);
> >         struct inode    *inode = d_inode(dentry);
> > @@ -1157,8 +1153,9 @@ static int nfs_readdir(struct file *file,
> > struct dir_context *ctx)
> >         dfprintk(FILE, "NFS: readdir(%pD2) returns %d\n", file,
> > res);
> >         return res;
> >  }
> > +EXPORT_SYMBOL_GPL(nfs_readdir);
> >  
> > -static loff_t nfs_llseek_dir(struct file *filp, loff_t offset, int
> > whence)
> > +loff_t nfs_llseek_dir(struct file *filp, loff_t offset, int
> > whence)
> >  {
> >         struct nfs_open_dir_context *dir_ctx = filp->private_data;
> >  
> > @@ -1196,19 +1193,20 @@ static loff_t nfs_llseek_dir(struct file
> > *filp, loff_t offset, int whence)
> >         spin_unlock(&filp->f_lock);
> >         return offset;
> >  }
> > +EXPORT_SYMBOL_GPL(nfs_llseek_dir);
> >  
> >  /*
> >   * All directory operations under NFS are synchronous, so fsync()
> >   * is a dummy operation.
> >   */
> > -static int nfs_fsync_dir(struct file *filp, loff_t start, loff_t
> > end,
> > -                        int datasync)
> > +int nfs_fsync_dir(struct file *filp, loff_t start, loff_t end, int
> > datasync)
> >  {
> >         dfprintk(FILE, "NFS: fsync dir(%pD2) datasync %d\n", filp,
> > datasync);
> >  
> >         nfs_inc_stats(file_inode(filp), NFSIOS_VFSFSYNC);
> >         return 0;
> >  }
> > +EXPORT_SYMBOL_GPL(nfs_fsync_dir);
> >  
> >  /**
> >   * nfs_force_lookup_revalidate - Mark the directory as having
> > changed
> > diff --git a/fs/nfs/getroot.c b/fs/nfs/getroot.c
> > index 11ff2b2e060f..f872970d6240 100644
> > --- a/fs/nfs/getroot.c
> > +++ b/fs/nfs/getroot.c
> > @@ -127,7 +127,8 @@ int nfs_get_root(struct super_block *s, struct
> > fs_context *fc)
> >         if (server->caps & NFS_CAP_SECURITY_LABEL)
> >                 kflags |= SECURITY_LSM_NATIVE_LABELS;
> >         if (ctx->clone_data.sb) {
> > -               if (d_inode(fc->root)->i_fop !=
> > &nfs_dir_operations) {
> > +               if (d_inode(fc->root)->i_fop !=
> > +                   server->nfs_client->rpc_ops->dir_ops) {
> >                         error = -ESTALE;
> >                         goto error_splat_root;
> >                 }
> > diff --git a/fs/nfs/inode.c b/fs/nfs/inode.c
> > index 33f4410190b6..8da662a4953d 100644
> > --- a/fs/nfs/inode.c
> > +++ b/fs/nfs/inode.c
> > @@ -108,6 +108,7 @@ u64 nfs_compat_user_ino64(u64 fileid)
> >                 ino ^= fileid >> (sizeof(fileid)-sizeof(ino)) * 8;
> >         return ino;
> >  }
> > +EXPORT_SYMBOL_GPL(nfs_compat_user_ino64);
> >  
> >  int nfs_drop_inode(struct inode *inode)
> >  {
> > @@ -501,7 +502,7 @@ nfs_fhget(struct super_block *sb, struct nfs_fh
> > *fh, struct nfs_fattr *fattr)
> >                         nfs_inode_init_regular(nfsi);
> >                 } else if (S_ISDIR(inode->i_mode)) {
> >                         inode->i_op = NFS_SB(sb)->nfs_client-
> > >rpc_ops->dir_inode_ops;
> > -                       inode->i_fop = &nfs_dir_operations;
> > +                       inode->i_fop = NFS_SB(sb)->nfs_client-
> > >rpc_ops->dir_ops;
> >                         inode->i_data.a_ops = &nfs_dir_aops;
> >                         nfs_inode_init_dir(nfsi);
> >                         /* Deal with crossing mountpoints */
> > @@ -867,6 +868,44 @@ static u32 nfs_get_valid_attrmask(struct inode
> > *inode)
> >         return reply_mask;
> >  }
> >  
> > +static int nfs_getattr_revalidate_force(struct dentry *dentry)
> > +{
> > +       struct inode *inode = d_inode(dentry);
> > +       struct nfs_server *server = NFS_SERVER(inode);
> > +
> > +       if (!(server->flags & NFS_MOUNT_NOAC))
> > +               nfs_readdirplus_parent_cache_miss(dentry);
> > +       else
> > +               nfs_readdirplus_parent_cache_hit(dentry);
> > +       return __nfs_revalidate_inode(server, inode);
> > +}
> > +
> > +static int nfs_getattr_revalidate_none(struct dentry *dentry)
> > +{
> > +       nfs_readdirplus_parent_cache_hit(dentry);
> > +       return NFS_STALE(d_inode(dentry)) ? -ESTALE : 0;
> > +}
> > +
> > +static int nfs_getattr_revalidate_maybe(struct dentry *dentry,
> > +                                       unsigned long flags)
> > +{
> > +       if (nfs_check_cache_invalid(d_inode(dentry), flags))
> > +               return nfs_getattr_revalidate_force(dentry);
> > +       return nfs_getattr_revalidate_none(dentry);
> > +}
> > +
> > +int nfs_getattr_revalidate(const struct path *path,
> > +                          unsigned long flags,
> > +                          unsigned int query_flags)
> > +{
> > +       if (query_flags & AT_STATX_FORCE_SYNC)
> > +               return nfs_getattr_revalidate_force(path->dentry);
> > +       if (!(query_flags & AT_STATX_DONT_SYNC))
> > +               return nfs_getattr_revalidate_maybe(path->dentry,
> > flags);
> > +       return nfs_getattr_revalidate_none(path->dentry);
> > +}
> > +EXPORT_SYMBOL_GPL(nfs_getattr_revalidate);
> > +
> >  int nfs_getattr(struct user_namespace *mnt_userns, const struct
> > path *path,
> >                 struct kstat *stat, u32 request_mask, unsigned int
> > query_flags)
> >  {
> > diff --git a/fs/nfs/internal.h b/fs/nfs/internal.h
> > index 12f6acb483bb..9602a886f0f0 100644
> > --- a/fs/nfs/internal.h
> > +++ b/fs/nfs/internal.h
> > @@ -366,6 +366,12 @@ extern struct nfs_client
> > *nfs_init_client(struct nfs_client *clp,
> >                            const struct nfs_client_initdata *);
> >  
> >  /* dir.c */
> > +int nfs_opendir(struct inode *, struct file *);
> > +int nfs_closedir(struct inode *, struct file *);
> > +int nfs_readdir(struct file *file, struct dir_context *ctx);
> > +int nfs_fsync_dir(struct file *, loff_t, loff_t, int);
> > +loff_t nfs_llseek_dir(struct file *filp, loff_t offset, int
> > whence);
> > +
> >  extern void nfs_advise_use_readdirplus(struct inode *dir);
> >  extern void nfs_force_use_readdirplus(struct inode *dir);
> >  extern unsigned long nfs_access_cache_count(struct shrinker
> > *shrink,
> > @@ -411,6 +417,8 @@ extern void nfs_set_cache_invalid(struct inode
> > *inode, unsigned long flags);
> >  extern bool nfs_check_cache_invalid(struct inode *, unsigned
> > long);
> >  extern int nfs_wait_bit_killable(struct wait_bit_key *key, int
> > mode);
> >  extern int nfs_wait_atomic_killable(atomic_t *p, unsigned int
> > mode);
> > +extern int nfs_getattr_revalidate(const struct path *path,
> > unsigned long flags,
> > +                                 unsigned int query_flags);
> >  
> >  /* super.c */
> >  extern const struct super_operations nfs_sops;
> > diff --git a/fs/nfs/nfs3proc.c b/fs/nfs/nfs3proc.c
> > index 7100514d306b..091005e169b7 100644
> > --- a/fs/nfs/nfs3proc.c
> > +++ b/fs/nfs/nfs3proc.c
> > @@ -1018,6 +1018,7 @@ const struct nfs_rpc_ops nfs_v3_clientops = {
> >         .dir_inode_ops  = &nfs3_dir_inode_operations,
> >         .file_inode_ops = &nfs3_file_inode_operations,
> >         .file_ops       = &nfs_file_operations,
> > +       .dir_ops        = &nfs_dir_operations,
> >         .nlmclnt_ops    = &nlmclnt_fl_close_lock_ops,
> >         .getroot        = nfs3_proc_get_root,
> >         .submount       = nfs_submount,
> > diff --git a/fs/nfs/nfs4_fs.h b/fs/nfs/nfs4_fs.h
> > index ed5eaca6801e..9f21d8520e99 100644
> > --- a/fs/nfs/nfs4_fs.h
> > +++ b/fs/nfs/nfs4_fs.h
> > @@ -248,6 +248,34 @@ struct nfs4_opendata {
> >         int rpc_status;
> >  };
> >  
> > +struct nfs4_statx {
> > +       int             real_fd;                /* real FD to use,
> > +                                                  -1 means use
> > current file */
> > +       __u32           fa_options;             /* statx flags */
> > +       __u64           fa_request[2];          /* Attributes
> > requested */
> > +       __u64           fa_valid[2];            /* Attributes set
> > */
> > +
> > +       struct timespec64 fa_time_backup;       /* Backup time */
> > +       struct timespec64 fa_btime;             /* Birth time */
> > +       /* Flag attributes */
> > +       __u64 fa_flags;
> > +       struct timespec64 fa_atime;             /* Access time */
> > +       struct timespec64 fa_mtime;             /* Modify time */
> > +       struct timespec64 fa_ctime;             /* Change time */
> > +       kuid_t          fa_owner_uid;           /* Owner User ID */
> > +       kgid_t          fa_group_gid;           /* Primary Group ID
> > */
> > +        /* Normal stat fields after this */
> > +       __u32           fa_mode;                /* Mode */
> > +       unsigned int    fa_nlink;
> > +       __u32           fa_blksize;
> > +       __u32           fa_spare;               /* Alignment */
> > +       __u64           fa_ino;
> > +       dev_t           fa_dev;
> > +       dev_t           fa_rdev;
> > +       loff_t          fa_size;
> > +       __u64           fa_blocks;
> > +};
> > +
> >  struct nfs4_add_xprt_data {
> >         struct nfs_client       *clp;
> >         const struct cred       *cred;
> > @@ -315,6 +343,9 @@ extern int nfs4_set_rw_stateid(nfs4_stateid
> > *stateid,
> >                 const struct nfs_open_context *ctx,
> >                 const struct nfs_lock_context *l_ctx,
> >                 fmode_t fmode);
> > +int nfs4_set_nfs4_statx(struct inode *inode,
> > +               struct nfs4_statx *statx,
> > +               struct nfs_fattr *fattr);
> >  extern int nfs4_proc_getattr(struct nfs_server *server, struct
> > nfs_fh *fhandle,
> >                              struct nfs_fattr *fattr, struct inode
> > *inode);
> >  extern int update_open_stateid(struct nfs4_state *state,
> > diff --git a/fs/nfs/nfs4file.c b/fs/nfs/nfs4file.c
> > index e79ae4cbc395..494ebc7cd1c0 100644
> > --- a/fs/nfs/nfs4file.c
> > +++ b/fs/nfs/nfs4file.c
> > @@ -9,6 +9,8 @@
> >  #include <linux/falloc.h>
> >  #include <linux/mount.h>
> >  #include <linux/nfs_fs.h>
> > +#include <linux/time32.h>
> > +#include <linux/compat.h>
> >  #include <linux/nfs_ssc.h>
> >  #include "delegation.h"
> >  #include "internal.h"
> > @@ -132,6 +134,503 @@ nfs4_file_flush(struct file *file, fl_owner_t
> > id)
> >         return filemap_check_wb_err(file->f_mapping, since);
> >  }
> >  
> > +static int nfs_get_timespec64(struct timespec64 *ts,
> > +                             const struct nfs_ioctl_timespec
> > __user *uts)
> > +{
> > +       __s64 dummy;
> > +       if (unlikely(get_user(dummy, &uts->tv_sec) != 0))
> > +               return EFAULT;
> > +       ts->tv_sec = dummy;
> > +       if (unlikely(get_user(dummy, &uts->tv_nsec) != 0))
> > +               return EFAULT;
> > +       ts->tv_nsec = dummy;
> > +       return 0;
> > +}
> > +
> > +static int nfs_put_timespec64(const struct timespec64 *ts,
> > +                             struct nfs_ioctl_timespec __user
> > *uts)
> > +{
> > +       __s64 dummy;
> > +
> > +       dummy = ts->tv_sec;
> > +       if (unlikely(put_user(dummy, &uts->tv_sec) != 0))
> > +               return EFAULT;
> > +       dummy = ts->tv_nsec;
> > +       if (unlikely(put_user(dummy, &uts->tv_nsec) != 0))
> > +               return EFAULT;
> > +       return 0;
> > +}
> > +
> > +static struct file *nfs4_get_real_file(struct file *src, unsigned
> > int fd)
> > +{
> > +       struct file *filp = fget_raw(fd);
> > +       int ret = -EBADF;
> > +
> > +       if (!filp)
> > +               goto out;
> > +       /* Validate that the files share the same underlying
> > filesystem */
> > +       ret = -EXDEV;
> > +       if (file_inode(filp)->i_sb != file_inode(src)->i_sb)
> > +               goto out_put;
> > +       return filp;
> > +out_put:
> > +       fput(filp);
> > +out:
> > +       return ERR_PTR(ret);
> > +}
> > +
> > +static unsigned long nfs4_statx_request_to_cache_validity(__u64
> > request,
> > +                                                         u64
> > fattr_supported)
> > +{
> > +       unsigned long ret = 0;
> > +
> > +       if (request & NFS_FA_VALID_ATIME)
> > +               ret |= NFS_INO_INVALID_ATIME;
> > +       if (request & NFS_FA_VALID_CTIME)
> > +               ret |= NFS_INO_INVALID_CTIME;
> > +       if (request & NFS_FA_VALID_MTIME)
> > +               ret |= NFS_INO_INVALID_MTIME;
> > +       if (request & NFS_FA_VALID_SIZE)
> > +               ret |= NFS_INO_INVALID_SIZE;
> > +
> > +       if (request & NFS_FA_VALID_MODE)
> > +               ret |= NFS_INO_INVALID_MODE;
> > +       if (request & (NFS_FA_VALID_OWNER |
> > NFS_FA_VALID_OWNER_GROUP))
> > +               ret |= NFS_INO_INVALID_OTHER;
> > +
> > +       if (request & NFS_FA_VALID_NLINK)
> > +               ret |= NFS_INO_INVALID_NLINK;
> > +       if (request & NFS_FA_VALID_BLOCKS)
> > +               ret |= NFS_INO_INVALID_BLOCKS;
> > +
> > +       if (request & NFS_FA_VALID_TIME_CREATE)
> > +               ret |= NFS_INO_INVALID_BTIME;
> > +
> > +       if (request & NFS_FA_VALID_ARCHIVE) {
> > +               if (fattr_supported & NFS_ATTR_FATTR_ARCHIVE)
> > +                       ret |= NFS_INO_INVALID_WINATTR;
> > +               else if (fattr_supported &
> > NFS_ATTR_FATTR_TIME_BACKUP)
> > +                       ret |= NFS_INO_INVALID_WINATTR |
> > NFS_INO_INVALID_MTIME;
> > +       }
> > +       if (request & (NFS_FA_VALID_TIME_BACKUP |
> > NFS_FA_VALID_HIDDEN |
> > +                      NFS_FA_VALID_SYSTEM | NFS_FA_VALID_OFFLINE))
> > +               ret |= NFS_INO_INVALID_WINATTR;
> > +
> > +       return ret ? (ret | NFS_INO_INVALID_CHANGE) : 0;
> > +}
> > +
> > +static long nfs4_ioctl_file_statx_get(struct file *dst_file,
> > +                                     struct nfs_ioctl_nfs4_statx
> > __user *uarg)
> > +{
> > +       struct nfs4_statx args = {
> > +               .real_fd = -1,
> > +               .fa_valid = { 0 },
> > +       };
> > +       struct inode *inode;
> > +       struct nfs_inode *nfsi;
> > +       struct nfs_server *server;
> > +       u64 fattr_supported;
> > +       unsigned long reval_attr;
> > +       unsigned int reval_flags;
> > +       __u32 tmp;
> > +       int ret;
> > +
> > +       /*
> > +        * We get the first word from the uarg as it tells us
> > whether
> > +        * to use the passed in struct file or use that fd to find
> > the
> > +        * struct file.
> > +        */
> > +       if (get_user(args.real_fd, &uarg->real_fd))
> > +               return -EFAULT;
> > +
> > +       if (get_user(args.fa_options, &uarg->fa_options))
> > +               return -EFAULT;
> > +
> > +       if (get_user(args.fa_request[0], &uarg->fa_request[0]))
> > +               return -EFAULT;
> > +
> > +       if (args.real_fd >= 0) {
> > +               dst_file = nfs4_get_real_file(dst_file,
> > args.real_fd);
> > +               if (IS_ERR(dst_file))
> > +                       return PTR_ERR(dst_file);
> > +       }
> > +
> > +       /*
> > +        * Backward compatibility: we stole the top 32 bits of
> > 'real_fd'
> > +        * to create the fa_options field, so if its value is -1,
> > then
> > +        * assume it is the high word of (__s64)real_fd == -1, and
> > just
> > +        * set it to zero.
> > +        */
> > +       if (args.fa_options == 0xFFFF)
> > +               args.fa_options = 0;
> > +
> > +       inode = file_inode(dst_file);
> > +       nfsi = NFS_I(inode);
> > +       server = NFS_SERVER(inode);
> > +       fattr_supported = server->fattr_valid;
> > +
> > +       trace_nfs_ioctl_file_statx_get_enter(inode);
> > +
> > +       if (args.fa_options & NFS_FA_OPTIONS_FORCE_SYNC)
> > +               reval_flags = AT_STATX_FORCE_SYNC;
> > +       else if (args.fa_options & NFS_FA_OPTIONS_DONT_SYNC)
> > +               reval_flags = AT_STATX_DONT_SYNC;
> > +       else
> > +               reval_flags = AT_STATX_SYNC_AS_STAT;
> > +
> > +       reval_attr =
> > nfs4_statx_request_to_cache_validity(args.fa_request[0],
> > +                                                        
> > fattr_supported);
> > +
> > +       if ((reval_attr & (NFS_INO_INVALID_CTIME |
> > NFS_INO_INVALID_MTIME)) &&
> > +           reval_flags != AT_STATX_DONT_SYNC && S_ISREG(inode-
> > >i_mode)) {
> > +               ret = filemap_write_and_wait(inode->i_mapping);
> > +               if (ret)
> > +                       goto out;
> > +       }
> > +
> > +       if ((dst_file->f_path.mnt->mnt_flags & MNT_NOATIME) ||
> > +           ((dst_file->f_path.mnt->mnt_flags & MNT_NODIRATIME) &&
> > +            S_ISDIR(inode->i_mode)))
> > +               reval_attr &= ~NFS_INO_INVALID_ATIME;
> > +
> > +       ret = nfs_getattr_revalidate(&dst_file->f_path, reval_attr,
> > +                                    reval_flags);
> > +       if (ret != 0)
> > +               goto out;
> > +
> > +       ret = -EFAULT;
> > +       if ((fattr_supported & NFS_ATTR_FATTR_OWNER) &&
> > +           (args.fa_request[0] & NFS_FA_VALID_OWNER)) {
> > +               tmp = from_kuid_munged(current_user_ns(), inode-
> > >i_uid);
> > +               if (unlikely(put_user(tmp, &uarg->fa_owner_uid) !=
> > 0))
> > +                       goto out;
> > +               args.fa_valid[0] |= NFS_FA_VALID_OWNER;
> > +       }
> > +
> > +       if ((fattr_supported & NFS_ATTR_FATTR_GROUP) &&
> > +           (args.fa_request[0] & NFS_FA_VALID_OWNER_GROUP)) {
> > +               tmp = from_kgid_munged(current_user_ns(), inode-
> > >i_gid);
> > +               if (unlikely(put_user(tmp, &uarg->fa_group_gid) !=
> > 0))
> > +                       goto out;
> > +               args.fa_valid[0] |= NFS_FA_VALID_OWNER_GROUP;
> > +       }
> > +
> > +       if ((fattr_supported & NFS_ATTR_FATTR_TIME_BACKUP) &&
> > +           (args.fa_request[0] & NFS_FA_VALID_TIME_BACKUP)) {
> > +               if (nfs_put_timespec64(&nfsi->timebackup, &uarg-
> > >fa_time_backup))
> > +                       goto out;
> > +               args.fa_valid[0] |= NFS_FA_VALID_TIME_BACKUP;
> > +       }
> > +
> > +       if ((fattr_supported & NFS_ATTR_FATTR_BTIME) &&
> > +           (args.fa_request[0] & NFS_FA_VALID_TIME_CREATE)) {
> > +               if (nfs_put_timespec64(&nfsi->btime, &uarg-
> > >fa_btime))
> > +                       goto out;
> > +               args.fa_valid[0] |= NFS_FA_VALID_TIME_CREATE;
> > +       }
> > +
> > +       /* atime, mtime, and ctime are all stored in the regular
> > inode,
> > +        * not the nfs inode.
> > +        */
> > +       if ((fattr_supported & NFS_ATTR_FATTR_ATIME) &&
> > +           (args.fa_request[0] & NFS_FA_VALID_ATIME)) {
> > +               if (nfs_put_timespec64(&inode->i_atime, &uarg-
> > >fa_atime))
> > +                       goto out;
> > +               args.fa_valid[0] |= NFS_FA_VALID_ATIME;
> > +       }
> > +
> > +       if ((fattr_supported & NFS_ATTR_FATTR_MTIME) &&
> > +           (args.fa_request[0] & NFS_FA_VALID_MTIME)) {
> > +               if (nfs_put_timespec64(&inode->i_mtime, &uarg-
> > >fa_mtime))
> > +                        goto out;
> > +               args.fa_valid[0] |= NFS_FA_VALID_MTIME;
> > +       }
> > +
> > +       if ((fattr_supported & NFS_ATTR_FATTR_CTIME) &&
> > +           (args.fa_request[0] & NFS_FA_VALID_CTIME)) {
> > +               if (nfs_put_timespec64(&inode->i_ctime, &uarg-
> > >fa_ctime))
> > +                       goto out;
> > +               args.fa_valid[0] |= NFS_FA_VALID_CTIME;
> > +       }
> > +
> > +        /*
> > +         * It looks like PDFS does not support or properly handle
> > the
> > +         * archive bit.
> > +         */
> > +       if ((fattr_supported & NFS_ATTR_FATTR_ARCHIVE) &&
> > +           (args.fa_request[0] & NFS_FA_VALID_ARCHIVE)) {
> > +               if (nfsi->archive)
> > +                       args.fa_flags |= NFS_FA_FLAG_ARCHIVE;
> > +               args.fa_valid[0] |= NFS_FA_VALID_ARCHIVE;
> > +       }
> > +
> > +       if ((fattr_supported & NFS_ATTR_FATTR_TIME_BACKUP) &&
> > +           (args.fa_request[0] & NFS_FA_VALID_ARCHIVE)) {
> > +               if (timespec64_compare(&inode->i_mtime, &nfsi-
> > >timebackup) > 0)
> > +                       args.fa_flags |= NFS_FA_FLAG_ARCHIVE;
> > +               args.fa_valid[0] |= NFS_FA_VALID_ARCHIVE;
> > +       }
> > +
> > +       if ((fattr_supported & NFS_ATTR_FATTR_HIDDEN) &&
> > +           (args.fa_request[0] & NFS_FA_VALID_HIDDEN)) {
> > +               if (nfsi->hidden)
> > +                       args.fa_flags |= NFS_FA_FLAG_HIDDEN;
> > +               args.fa_valid[0] |= NFS_FA_VALID_HIDDEN;
> > +       }
> > +       if ((fattr_supported & NFS_ATTR_FATTR_SYSTEM) &&
> > +           (args.fa_request[0] & NFS_FA_VALID_SYSTEM)) {
> > +               if (nfsi->system)
> > +                       args.fa_flags |= NFS_FA_FLAG_SYSTEM;
> > +               args.fa_valid[0] |= NFS_FA_VALID_SYSTEM;
> > +       }
> > +
> > +       if ((fattr_supported & NFS_ATTR_FATTR_OFFLINE) &&
> > +           (args.fa_request[0] & NFS_FA_VALID_OFFLINE)) {
> > +               if (nfsi->offline)
> > +                       args.fa_flags |= NFS_FA_FLAG_OFFLINE;
> > +               args.fa_valid[0] |= NFS_FA_VALID_OFFLINE;
> > +       }
> > +
> > +       if ((args.fa_valid[0] & (NFS_FA_VALID_ARCHIVE |
> > +                               NFS_FA_VALID_HIDDEN |
> > +                               NFS_FA_VALID_SYSTEM |
> > +                               NFS_FA_VALID_OFFLINE)) &&
> > +           put_user(args.fa_flags, &uarg->fa_flags))
> > +               goto out;
> > +
> > +       if ((fattr_supported & NFS_ATTR_FATTR_MODE) &&
> > +           (args.fa_request[0] & NFS_FA_VALID_MODE)) {
> > +               tmp = inode->i_mode;
> > +               /* This is an unsigned short we put into an __u32
> > */
> > +               if (unlikely(put_user(tmp, &uarg->fa_mode) != 0))
> > +                       goto out;
> > +               args.fa_valid[0] |= NFS_FA_VALID_MODE;
> > +       }
> > +
> > +       if ((fattr_supported & NFS_ATTR_FATTR_NLINK) &&
> > +           (args.fa_request[0] & NFS_FA_VALID_NLINK)) {
> > +               tmp = inode->i_nlink;
> > +               if (unlikely(put_user(tmp, &uarg->fa_nlink) != 0))
> > +                       goto out;
> > +               args.fa_valid[0] |= NFS_FA_VALID_NLINK;
> > +       }
> > +
> > +       if (args.fa_request[0] & NFS_FA_VALID_BLKSIZE) {
> > +               tmp = i_blocksize(inode);
> > +               if (S_ISDIR(inode->i_mode))
> > +                       tmp = NFS_SERVER(inode)->dtsize;
> > +               if (unlikely(put_user(tmp, &uarg->fa_blksize) !=
> > 0))
> > +                       goto out;
> > +               args.fa_valid[0] |= NFS_FA_VALID_BLKSIZE;
> > +       }
> > +
> > +       if (args.fa_request[0] & NFS_FA_VALID_INO) {
> > +               __u64 ino =
> > nfs_compat_user_ino64(NFS_FILEID(inode));
> > +               if (unlikely(put_user(ino, &uarg->fa_ino) != 0))
> > +                       goto out;
> > +               args.fa_valid[0] |= NFS_FA_VALID_INO;
> > +       }
> > +
> > +       if (args.fa_request[0] & NFS_FA_VALID_DEV) {
> > +               tmp = inode->i_sb->s_dev;
> > +               if (unlikely(put_user(tmp, &uarg->fa_dev) != 0))
> > +                       goto out;
> > +               args.fa_valid[0] |= NFS_FA_VALID_DEV;
> > +       }
> > +
> > +       if ((fattr_supported & NFS_ATTR_FATTR_RDEV) &&
> > +           (args.fa_request[0] & NFS_FA_VALID_RDEV)) {
> > +               tmp = inode->i_rdev;
> > +               if (unlikely(put_user(tmp, &uarg->fa_rdev) != 0))
> > +                       goto out;
> > +               args.fa_valid[0] |= NFS_FA_VALID_RDEV;
> > +       }
> > +
> > +       if ((fattr_supported & NFS_ATTR_FATTR_SIZE) &&
> > +           (args.fa_request[0] & NFS_FA_VALID_SIZE)) {
> > +               __s64 size = i_size_read(inode);
> > +               if (unlikely(put_user(size, &uarg->fa_size) != 0))
> > +                       goto out;
> > +               args.fa_valid[0] |= NFS_FA_VALID_SIZE;
> > +       }
> > +
> > +       if ((fattr_supported &
> > +            (NFS_ATTR_FATTR_BLOCKS_USED |
> > NFS_ATTR_FATTR_SPACE_USED)) &&
> > +           (args.fa_request[0] & NFS_FA_VALID_BLOCKS)) {
> > +               __s64 blocks = inode->i_blocks;
> > +               if (unlikely(put_user(blocks, &uarg->fa_blocks) !=
> > 0))
> > +                       goto out;
> > +               args.fa_valid[0] |= NFS_FA_VALID_BLOCKS;
> > +       }
> > +
> > +       if (unlikely(put_user(args.fa_valid[0], &uarg->fa_valid[0])
> > != 0))
> > +               goto out;
> > +       if (unlikely(put_user(args.fa_valid[1], &uarg->fa_valid[1])
> > != 0))
> > +               goto out;
> > +
> > +       ret = 0;
> > +out:
> > +       if (args.real_fd >= 0)
> > +               fput(dst_file);
> > +       trace_nfs_ioctl_file_statx_get_exit(inode, ret);
> > +       return ret;
> > +}
> > +
> > +static long nfs4_ioctl_file_statx_set(struct file *dst_file,
> > +                                     struct nfs_ioctl_nfs4_statx
> > __user *uarg)
> > +{
> > +       struct nfs4_statx args = {
> > +               .real_fd = -1,
> > +               .fa_valid = { 0 },
> > +       };
> > +       struct nfs_fattr *fattr = nfs_alloc_fattr();
> > +       struct inode *inode;
> > +       /*
> > +        * If you need a different error code below, you need to
> > set it
> > +        */
> > +       int ret = -EFAULT;
> > +
> > +       if (fattr == NULL)
> > +               return -ENOMEM;
> > +
> > +       /*
> > +        * We get the first u64 word from the uarg as it tells us
> > whether
> > +        * to use the passed in struct file or use that fd to find
> > the
> > +        * struct file.
> > +        */
> > +       if (get_user(args.real_fd, &uarg->real_fd))
> > +               goto out_free;
> > +
> > +       if (args.real_fd >= 0) {
> > +               dst_file = nfs4_get_real_file(dst_file,
> > args.real_fd);
> > +               if (IS_ERR(dst_file)) {
> > +                       ret = PTR_ERR(dst_file);
> > +                       goto out_free;
> > +               }
> > +       }
> > +       inode = file_inode(dst_file);
> > +       trace_nfs_ioctl_file_statx_set_enter(inode);
> > +
> > +       inode_lock(inode);
> > +
> > +       /* Write all dirty data */
> > +       if (S_ISREG(inode->i_mode)) {
> > +               ret = nfs_sync_inode(inode);
> > +               if (ret)
> > +                       goto out;
> > +       }
> > +
> > +       ret = -EFAULT;
> > +       if (get_user(args.fa_valid[0], &uarg->fa_valid[0]))
> > +               goto out;
> > +       args.fa_valid[0] &= NFS_FA_VALID_ALL_ATTR_0;
> > +
> > +       if (args.fa_valid[0] & NFS_FA_VALID_OWNER) {
> > +               uid_t uid;
> > +
> > +               if (unlikely(get_user(uid, &uarg->fa_owner_uid) !=
> > 0))
> > +                       goto out;
> > +               args.fa_owner_uid = make_kuid(current_user_ns(),
> > uid);
> > +               if (!uid_valid(args.fa_owner_uid)) {
> > +                       ret = -EINVAL;
> > +                       goto out;
> > +               }
> > +       }
> > +
> > +       if (args.fa_valid[0] & NFS_FA_VALID_OWNER_GROUP) {
> > +               gid_t gid;
> > +
> > +               if (unlikely(get_user(gid, &uarg->fa_group_gid) !=
> > 0))
> > +                       goto out;
> > +               args.fa_group_gid = make_kgid(current_user_ns(),
> > gid);
> > +               if (!gid_valid(args.fa_group_gid)) {
> > +                       ret = -EINVAL;
> > +                       goto out;
> > +               }
> > +       }
> > +
> > +       if ((args.fa_valid[0] & (NFS_FA_VALID_ARCHIVE |
> > +                                       NFS_FA_VALID_HIDDEN |
> > +                                       NFS_FA_VALID_SYSTEM)) &&
> > +           get_user(args.fa_flags, &uarg->fa_flags))
> > +               goto out;
> > +
> > +       if ((args.fa_valid[0] & NFS_FA_VALID_TIME_CREATE) &&
> > +           nfs_get_timespec64(&args.fa_btime, &uarg->fa_btime))
> > +               goto out;
> > +
> > +       if ((args.fa_valid[0] & NFS_FA_VALID_ATIME) &&
> > +           nfs_get_timespec64(&args.fa_atime, &uarg->fa_atime))
> > +               goto out;
> > +
> > +       if ((args.fa_valid[0] & NFS_FA_VALID_MTIME) &&
> > +           nfs_get_timespec64(&args.fa_mtime, &uarg->fa_mtime))
> > +               goto out;
> > +
> > +       if (args.fa_valid[0] & NFS_FA_VALID_TIME_BACKUP) {
> > +               if (nfs_get_timespec64(&args.fa_time_backup, &uarg-
> > >fa_time_backup))
> > +                       goto out;
> > +       } else if ((args.fa_valid[0] & NFS_FA_VALID_ARCHIVE) &&
> > +                       !(NFS_SERVER(inode)->fattr_valid &
> > NFS_ATTR_FATTR_ARCHIVE)) {
> > +               args.fa_valid[0] |= NFS_FA_VALID_TIME_BACKUP;
> > +               if (!(args.fa_flags & NFS_FA_FLAG_ARCHIVE)) {
> > +                       nfs_revalidate_inode(inode,
> > NFS_INO_INVALID_MTIME);
> > +                       args.fa_time_backup.tv_sec = inode-
> > >i_mtime.tv_sec;
> > +                       args.fa_time_backup.tv_nsec = inode-
> > >i_mtime.tv_nsec;
> > +               } else if (args.fa_valid[0] &
> > NFS_FA_VALID_TIME_CREATE)
> > +                       args.fa_time_backup = args.fa_btime;
> > +               else {
> > +                       nfs_revalidate_inode(inode,
> > NFS_INO_INVALID_BTIME);
> > +                       args.fa_time_backup = NFS_I(inode)->btime;
> > +               }
> > +       }
> > +
> > +        if (args.fa_valid[0] & NFS_FA_VALID_SIZE) {
> > +               if (copy_from_user(&args.fa_size, &uarg->fa_size,
> > +                                       sizeof(args.fa_size)))
> > +                       goto out;
> > +               ret = inode_newsize_ok(inode,args.fa_size);
> > +               if (ret)
> > +                       goto out;
> > +               if (args.fa_size == i_size_read(inode))
> > +                       args.fa_valid[0] &= ~NFS_FA_VALID_SIZE;
> > +       }
> > +
> > +       /*
> > +        * No need to update the inode because that is done in
> > nfs4_set_nfs4_statx
> > +        */
> > +       ret = nfs4_set_nfs4_statx(inode, &args, fattr);
> > +
> > +out:
> > +       inode_unlock(inode);
> > +       if (args.real_fd >= 0)
> > +               fput(dst_file);
> > +       trace_nfs_ioctl_file_statx_set_exit(inode, ret);
> > +out_free:
> > +       nfs_free_fattr(fattr);
> > +       return ret;
> > +}
> > +
> > +static long nfs4_ioctl(struct file *file, unsigned int cmd,
> > unsigned long arg)
> > +{
> > +       void __user *argp = (void __user *)arg;
> > +       long ret;
> > +
> > +       switch (cmd) {
> > +       case NFS_IOC_FILE_STATX_GET:
> > +               ret = nfs4_ioctl_file_statx_get(file, argp);
> > +               break;
> > +       case NFS_IOC_FILE_STATX_SET:
> > +               ret = nfs4_ioctl_file_statx_set(file, argp);
> > +               break;
> > +       default:
> > +               ret = -ENOIOCTLCMD;
> > +       }
> > +
> > +       dprintk("%s: file=%pD2, cmd=%u, ret=%ld\n", __func__, file,
> > cmd, ret);
> > +       return ret;
> > +}
> > +
> >  #ifdef CONFIG_NFS_V4_2
> >  static ssize_t __nfs4_copy_file_range(struct file *file_in, loff_t
> > pos_in,
> >                                       struct file *file_out, loff_t
> > pos_out,
> > @@ -187,6 +686,7 @@ static ssize_t __nfs4_copy_file_range(struct
> > file *file_in, loff_t pos_in,
> >         return ret;
> >  }
> >  
> > +
> >  static ssize_t nfs4_copy_file_range(struct file *file_in, loff_t
> > pos_in,
> >                                     struct file *file_out, loff_t
> > pos_out,
> >                                     size_t count, unsigned int
> > flags)
> > @@ -461,4 +961,15 @@ const struct file_operations
> > nfs4_file_operations = {
> >  #else
> >         .llseek         = nfs_file_llseek,
> >  #endif
> > +       .unlocked_ioctl = nfs4_ioctl,
> > +};
> > +
> > +const struct file_operations nfs4_dir_operations = {
> > +       .llseek         = nfs_llseek_dir,
> > +       .read           = generic_read_dir,
> > +       .iterate_shared = nfs_readdir,
> > +       .open           = nfs_opendir,
> > +       .release        = nfs_closedir,
> > +       .fsync          = nfs_fsync_dir,
> > +       .unlocked_ioctl = nfs4_ioctl,
> >  };
> > diff --git a/fs/nfs/nfs4proc.c b/fs/nfs/nfs4proc.c
> > index d497616ca149..7c032583ffa2 100644
> > --- a/fs/nfs/nfs4proc.c
> > +++ b/fs/nfs/nfs4proc.c
> > @@ -7959,6 +7959,129 @@ static int _nfs41_proc_get_locations(struct
> > inode *inode,
> >  
> >  #endif /* CONFIG_NFS_V4_1 */
> >  
> > +static int _nfs4_set_nfs4_statx(struct inode *inode,
> > +               struct nfs4_statx *statx,
> > +               struct nfs_fattr *fattr)
> > +{
> > +       const __u64 statx_win = NFS_FA_VALID_TIME_CREATE |
> > +                               NFS_FA_VALID_TIME_BACKUP |
> > +                               NFS_FA_VALID_ARCHIVE |
> > NFS_FA_VALID_HIDDEN |
> > +                               NFS_FA_VALID_SYSTEM;
> > +       struct iattr sattr = {0};
> > +       struct nfs_server *server = NFS_SERVER(inode);
> > +       __u32 bitmask[3];
> > +       struct nfs_setattrargs arg = {
> > +               .fh             = NFS_FH(inode),
> > +               .iap            = &sattr,
> > +               .server         = server,
> > +               .bitmask        = bitmask,
> > +               .statx          = statx,
> > +       };
> > +       struct nfs_setattrres res = {
> > +               .fattr          = fattr,
> > +               .server         = server,
> > +       };
> > +       struct rpc_message msg = {
> > +               .rpc_proc       =
> > &nfs4_procedures[NFSPROC4_CLNT_SETATTR],
> > +               .rpc_argp       = &arg,
> > +               .rpc_resp       = &res,
> > +       };
> > +       int status;
> > +
> > +       nfs4_bitmap_copy_adjust(
> > +               bitmask, server->attr_bitmask, inode,
> > +               NFS_INO_INVALID_CHANGE | NFS_INO_INVALID_CTIME |
> > +                       NFS_INO_INVALID_SIZE |
> > NFS_INO_INVALID_OTHER |
> > +                       NFS_INO_INVALID_BTIME |
> > NFS_INO_INVALID_WINATTR);
> > +       /* Use the iattr structure to set atime and mtime since
> > handling already
> > +        * exists for them using the iattr struct in the
> > encode_attrs()
> > +        * (xdr encoding) routine.
> > +        */
> > +       if (statx && (statx->fa_valid[0] & NFS_FA_VALID_MTIME)) {
> > +               sattr.ia_valid |= ATTR_MTIME_SET;
> > +               sattr.ia_mtime.tv_sec = statx->fa_mtime.tv_sec;
> > +               sattr.ia_mtime.tv_nsec = statx->fa_mtime.tv_nsec;
> > +       }
> > +
> > +       if (statx && (statx->fa_valid[0] & NFS_FA_VALID_ATIME)) {
> > +               sattr.ia_valid |= ATTR_ATIME_SET;
> > +               sattr.ia_atime.tv_sec = statx->fa_atime.tv_sec;
> > +               sattr.ia_atime.tv_nsec = statx->fa_atime.tv_nsec;
> > +       }
> > +
> > +       if (statx && (statx->fa_valid[0] & NFS_FA_VALID_OWNER)) {
> > +               sattr.ia_valid |= ATTR_UID;
> > +               sattr.ia_uid = statx->fa_owner_uid;
> > +       }
> > +
> > +       if (statx && (statx->fa_valid[0] &
> > NFS_FA_VALID_OWNER_GROUP)) {
> > +               sattr.ia_valid |= ATTR_GID;
> > +               sattr.ia_gid = statx->fa_group_gid;
> > +       }
> > +
> > +       if (statx && (statx->fa_valid[0] & NFS_FA_VALID_SIZE)) {
> > +               sattr.ia_valid |= ATTR_SIZE;
> > +               sattr.ia_size = statx->fa_size;
> > +       }
> > +
> > +       nfs4_stateid_copy(&arg.stateid, &zero_stateid);
> > +
> > +       status = nfs4_call_sync(server->client, server, &msg,
> > &arg.seq_args, &res.seq_res, 1);
> > +       if (!status) {
> > +               if (statx->fa_valid[0] & statx_win) {
> > +                       struct nfs_inode *nfsi = NFS_I(inode);
> > +
> > +                       spin_lock(&inode->i_lock);
> > +                       if (statx->fa_valid[0] &
> > NFS_FA_VALID_TIME_CREATE)
> > +                               nfsi->btime = statx->fa_btime;
> > +                       if (statx->fa_valid[0] &
> > NFS_FA_VALID_TIME_BACKUP)
> > +                               nfsi->timebackup = statx-
> > >fa_time_backup;
> > +                       if (statx->fa_valid[0] &
> > NFS_FA_VALID_ARCHIVE)
> > +                               nfsi->archive = (statx->fa_flags &
> > +                                               
> > NFS_FA_FLAG_ARCHIVE) != 0;
> > +                       if (statx->fa_valid[0] &
> > NFS_FA_VALID_SYSTEM)
> > +                               nfsi->system = (statx->fa_flags &
> > +                                               NFS_FA_FLAG_SYSTEM)
> > != 0;
> > +                       if (statx->fa_valid[0] &
> > NFS_FA_VALID_HIDDEN)
> > +                               nfsi->hidden = (statx->fa_flags &
> > +                                               NFS_FA_FLAG_HIDDEN)
> > != 0;
> > +                       if (statx->fa_valid[0] &
> > NFS_FA_VALID_OFFLINE)
> > +                               nfsi->offline = (statx->fa_flags &
> > +                                               
> > NFS_FA_FLAG_OFFLINE) != 0;
> > +
> > +                       nfsi->cache_validity &=
> > ~NFS_INO_INVALID_CTIME;
> > +                       if (fattr->valid & NFS_ATTR_FATTR_CTIME)
> > +                               inode->i_ctime = fattr->ctime;
> > +                       else
> > +                               nfs_set_cache_invalid(
> > +                                       inode,
> > NFS_INO_INVALID_CHANGE |
> > +                                                 
> > NFS_INO_INVALID_CTIME);
> > +                       spin_unlock(&inode->i_lock);
> > +               }
> > +
> > +               nfs_setattr_update_inode(inode, &sattr, fattr);
> > +       } else
> > +               dprintk("%s failed: %d\n", __func__, status);
> > +
> > +       return status;
> > +}
> > +
> > +int nfs4_set_nfs4_statx(struct inode *inode,
> > +               struct nfs4_statx *statx,
> > +               struct nfs_fattr *fattr)
> > +{
> > +       struct nfs4_exception exception = { };
> > +       struct nfs_server *server = NFS_SERVER(inode);
> > +       int err;
> > +
> > +       do {
> > +               err = nfs4_handle_exception(server,
> > +                               _nfs4_set_nfs4_statx(inode, statx,
> > fattr),
> > +                               &exception);
> > +       } while (exception.retry);
> > +       return err;
> > +}
> > +
> >  /**
> >   * nfs4_proc_get_locations - discover locations for a migrated
> > FSID
> >   * @inode: inode on FSID that is migrating
> > @@ -10419,6 +10542,7 @@ const struct nfs_rpc_ops nfs_v4_clientops =
> > {
> >         .dir_inode_ops  = &nfs4_dir_inode_operations,
> >         .file_inode_ops = &nfs4_file_inode_operations,
> >         .file_ops       = &nfs4_file_operations,
> > +       .dir_ops        = &nfs4_dir_operations,
> >         .getroot        = nfs4_proc_get_root,
> >         .submount       = nfs4_submount,
> >         .try_get_tree   = nfs4_try_get_tree,
> > diff --git a/fs/nfs/nfs4xdr.c b/fs/nfs/nfs4xdr.c
> > index d2c240effc87..e5300d7ed712 100644
> > --- a/fs/nfs/nfs4xdr.c
> > +++ b/fs/nfs/nfs4xdr.c
> > @@ -129,12 +129,15 @@ static int decode_layoutget(struct xdr_stream
> > *xdr, struct rpc_rqst *req,
> >                                 nfs4_fattr_value_maxsz)
> >  #define decode_getattr_maxsz    (op_decode_hdr_maxsz +
> > nfs4_fattr_maxsz)
> >  #define encode_attrs_maxsz     (nfs4_fattr_bitmap_maxsz + \
> > -                                1 + 2 + 1 + \
> > +                                1 + 2 + 1 + 1 + 1 + \
> >                                 nfs4_owner_maxsz + \
> >                                 nfs4_group_maxsz + \
> > -                               nfs4_label_maxsz + \
> > +                               1 + \
> > +                               1 + nfstime4_maxsz + \
> > +                               nfstime4_maxsz + nfstime4_maxsz + \
> >                                 1 + nfstime4_maxsz + \
> > -                               1 + nfstime4_maxsz)
> > +                               nfs4_label_maxsz + \
> > +                               2)
> >  #define encode_savefh_maxsz     (op_encode_hdr_maxsz)
> >  #define decode_savefh_maxsz     (op_decode_hdr_maxsz)
> >  #define encode_restorefh_maxsz  (op_encode_hdr_maxsz)
> > @@ -1081,6 +1084,7 @@ xdr_encode_nfstime4(__be32 *p, const struct
> > timespec64 *t)
> >  static void encode_attrs(struct xdr_stream *xdr, const struct
> > iattr *iap,
> >                                 const struct nfs4_label *label,
> >                                 const umode_t *umask,
> > +                               const struct nfs4_statx *statx,
> >                                 const struct nfs_server *server,
> >                                 const uint32_t attrmask[])
> >  {
> > @@ -1153,6 +1157,34 @@ static void encode_attrs(struct xdr_stream
> > *xdr, const struct iattr *iap,
> >                 }
> >         }
> >  
> > +       if (statx && (statx->fa_valid[0] &
> > NFS_FA_VALID_TIME_BACKUP) &&
> > +           (attrmask[1] & FATTR4_WORD1_TIME_BACKUP)) {
> > +               bmval[1] |= FATTR4_WORD1_TIME_BACKUP;
> > +               len += (nfstime4_maxsz << 2);
> > +       }
> > +       if (statx && (statx->fa_valid[0] &
> > NFS_FA_VALID_TIME_CREATE) &&
> > +           (attrmask[1] & FATTR4_WORD1_TIME_CREATE)) {
> > +               bmval[1] |= FATTR4_WORD1_TIME_CREATE;
> > +               len += (nfstime4_maxsz << 2);
> > +       }
> > +
> > +       if (statx && (statx->fa_valid[0] & NFS_FA_VALID_ARCHIVE) &&
> > +          (attrmask[0] & FATTR4_WORD0_ARCHIVE)) {
> > +               bmval[0] |= FATTR4_WORD0_ARCHIVE;
> > +               len += 4;
> > +       }
> > +       if (statx && (statx->fa_valid[0] & NFS_FA_VALID_HIDDEN) &&
> > +          (attrmask[0] & FATTR4_WORD0_HIDDEN)) {
> > +               bmval[0] |= FATTR4_WORD0_HIDDEN;
> > +               len += 4;
> > +       }
> > +
> > +       if (statx && (statx->fa_valid[0] & NFS_FA_VALID_SYSTEM) &&
> > +          (attrmask[1] & FATTR4_WORD1_SYSTEM)) {
> > +               bmval[1] |= FATTR4_WORD1_SYSTEM;
> > +               len += 4;
> > +       }
> > +
> >         if (label && (attrmask[2] & FATTR4_WORD2_SECURITY_LABEL)) {
> >                 len += 4 + 4 + 4 + (XDR_QUADLEN(label->len) << 2);
> >                 bmval[2] |= FATTR4_WORD2_SECURITY_LABEL;
> > @@ -1163,12 +1195,21 @@ static void encode_attrs(struct xdr_stream
> > *xdr, const struct iattr *iap,
> >  
> >         if (bmval[0] & FATTR4_WORD0_SIZE)
> >                 p = xdr_encode_hyper(p, iap->ia_size);
> > +       if (bmval[0] & FATTR4_WORD0_ARCHIVE)
> > +               *p++ = (statx->fa_flags & NFS_FA_FLAG_ARCHIVE) ?
> > +                       cpu_to_be32(1) : cpu_to_be32(0);
> > +       if (bmval[0] & FATTR4_WORD0_HIDDEN)
> > +               *p++ = (statx->fa_flags & NFS_FA_FLAG_HIDDEN) ?
> > +                       cpu_to_be32(1) : cpu_to_be32(0);
> >         if (bmval[1] & FATTR4_WORD1_MODE)
> >                 *p++ = cpu_to_be32(iap->ia_mode & S_IALLUGO);
> >         if (bmval[1] & FATTR4_WORD1_OWNER)
> >                 p = xdr_encode_opaque(p, owner_name,
> > owner_namelen);
> >         if (bmval[1] & FATTR4_WORD1_OWNER_GROUP)
> >                 p = xdr_encode_opaque(p, owner_group,
> > owner_grouplen);
> > +       if (bmval[1] & FATTR4_WORD1_SYSTEM)
> > +               *p++ = (statx->fa_flags & NFS_FA_FLAG_SYSTEM) ?
> > +                       cpu_to_be32(1) : cpu_to_be32(0);
> >         if (bmval[1] & FATTR4_WORD1_TIME_ACCESS_SET) {
> >                 if (iap->ia_valid & ATTR_ATIME_SET) {
> >                         *p++ =
> > cpu_to_be32(NFS4_SET_TO_CLIENT_TIME);
> > @@ -1176,6 +1217,10 @@ static void encode_attrs(struct xdr_stream
> > *xdr, const struct iattr *iap,
> >                 } else
> >                         *p++ =
> > cpu_to_be32(NFS4_SET_TO_SERVER_TIME);
> >         }
> > +       if (bmval[1] & FATTR4_WORD1_TIME_BACKUP)
> > +               p = xdr_encode_nfstime4(p, &statx->fa_time_backup);
> > +       if (bmval[1] & FATTR4_WORD1_TIME_CREATE)
> > +               p = xdr_encode_nfstime4(p, &statx->fa_btime);
> >         if (bmval[1] & FATTR4_WORD1_TIME_MODIFY_SET) {
> >                 if (iap->ia_valid & ATTR_MTIME_SET) {
> >                         *p++ =
> > cpu_to_be32(NFS4_SET_TO_CLIENT_TIME);
> > @@ -1248,7 +1293,7 @@ static void encode_create(struct xdr_stream
> > *xdr, const struct nfs4_create_arg *
> >  
> >         encode_string(xdr, create->name->len, create->name->name);
> >         encode_attrs(xdr, create->attrs, create->label, &create-
> > >umask,
> > -                       create->server, create->server-
> > >attr_bitmask);
> > +                    NULL, create->server, create->server-
> > >attr_bitmask);
> >  }
> >  
> >  static void encode_getattr(struct xdr_stream *xdr,
> > @@ -1434,12 +1479,12 @@ static inline void encode_createmode(struct
> > xdr_stream *xdr, const struct nfs_op
> >         case NFS4_CREATE_UNCHECKED:
> >                 *p = cpu_to_be32(NFS4_CREATE_UNCHECKED);
> >                 encode_attrs(xdr, arg->u.attrs, arg->label, &arg-
> > >umask,
> > -                               arg->server, arg->server-
> > >attr_bitmask);
> > +                            NULL, arg->server, arg->server-
> > >attr_bitmask);
> >                 break;
> >         case NFS4_CREATE_GUARDED:
> >                 *p = cpu_to_be32(NFS4_CREATE_GUARDED);
> >                 encode_attrs(xdr, arg->u.attrs, arg->label, &arg-
> > >umask,
> > -                               arg->server, arg->server-
> > >attr_bitmask);
> > +                            NULL, arg->server, arg->server-
> > >attr_bitmask);
> >                 break;
> >         case NFS4_CREATE_EXCLUSIVE:
> >                 *p = cpu_to_be32(NFS4_CREATE_EXCLUSIVE);
> > @@ -1449,7 +1494,7 @@ static inline void encode_createmode(struct
> > xdr_stream *xdr, const struct nfs_op
> >                 *p = cpu_to_be32(NFS4_CREATE_EXCLUSIVE4_1);
> >                 encode_nfs4_verifier(xdr, &arg->u.verifier);
> >                 encode_attrs(xdr, arg->u.attrs, arg->label, &arg-
> > >umask,
> > -                               arg->server, arg->server-
> > >exclcreat_bitmask);
> > +                            NULL, arg->server, arg->server-
> > >exclcreat_bitmask);
> >         }
> >  }
> >  
> > @@ -1712,8 +1757,8 @@ static void encode_setattr(struct xdr_stream
> > *xdr, const struct nfs_setattrargs
> >  {
> >         encode_op_hdr(xdr, OP_SETATTR, decode_setattr_maxsz, hdr);
> >         encode_nfs4_stateid(xdr, &arg->stateid);
> > -       encode_attrs(xdr, arg->iap, arg->label, NULL, server,
> > -                       server->attr_bitmask);
> > +       encode_attrs(xdr, arg->iap, arg->label, NULL, arg->statx,
> > server,
> > +                    server->attr_bitmask);
> >  }
> >  
> >  static void encode_setclientid(struct xdr_stream *xdr, const
> > struct nfs4_setclientid *setclientid, struct compound_hdr *hdr)
> > diff --git a/fs/nfs/nfstrace.c b/fs/nfs/nfstrace.c
> > index 5d1bfccbb4da..0b88deb0216e 100644
> > --- a/fs/nfs/nfstrace.c
> > +++ b/fs/nfs/nfstrace.c
> > @@ -9,6 +9,11 @@
> >  #define CREATE_TRACE_POINTS
> >  #include "nfstrace.h"
> >  
> > +EXPORT_TRACEPOINT_SYMBOL_GPL(nfs_ioctl_file_statx_get_enter);
> > +EXPORT_TRACEPOINT_SYMBOL_GPL(nfs_ioctl_file_statx_get_exit);
> > +EXPORT_TRACEPOINT_SYMBOL_GPL(nfs_ioctl_file_statx_set_enter);
> > +EXPORT_TRACEPOINT_SYMBOL_GPL(nfs_ioctl_file_statx_set_exit);
> > +
> >  EXPORT_TRACEPOINT_SYMBOL_GPL(nfs_fsync_enter);
> >  EXPORT_TRACEPOINT_SYMBOL_GPL(nfs_fsync_exit);
> >  EXPORT_TRACEPOINT_SYMBOL_GPL(nfs_xdr_status);
> > diff --git a/fs/nfs/nfstrace.h b/fs/nfs/nfstrace.h
> > index 2ef7cff8a4ba..b67dd087fb47 100644
> > --- a/fs/nfs/nfstrace.h
> > +++ b/fs/nfs/nfstrace.h
> > @@ -166,6 +166,11 @@ DEFINE_NFS_INODE_EVENT_DONE(nfs_fsync_exit);
> >  DEFINE_NFS_INODE_EVENT(nfs_access_enter);
> >  DEFINE_NFS_INODE_EVENT_DONE(nfs_set_cache_invalid);
> >  
> > +DEFINE_NFS_INODE_EVENT(nfs_ioctl_file_statx_get_enter);
> > +DEFINE_NFS_INODE_EVENT_DONE(nfs_ioctl_file_statx_get_exit);
> > +DEFINE_NFS_INODE_EVENT(nfs_ioctl_file_statx_set_enter);
> > +DEFINE_NFS_INODE_EVENT_DONE(nfs_ioctl_file_statx_set_exit);
> > +
> >  TRACE_EVENT(nfs_access_exit,
> >                 TP_PROTO(
> >                         const struct inode *inode,
> > diff --git a/fs/nfs/proc.c b/fs/nfs/proc.c
> > index 73dcaa99fa9b..8fd96d93630a 100644
> > --- a/fs/nfs/proc.c
> > +++ b/fs/nfs/proc.c
> > @@ -717,6 +717,7 @@ const struct nfs_rpc_ops nfs_v2_clientops = {
> >         .dir_inode_ops  = &nfs_dir_inode_operations,
> >         .file_inode_ops = &nfs_file_inode_operations,
> >         .file_ops       = &nfs_file_operations,
> > +       .dir_ops        = &nfs_dir_operations,
> >         .getroot        = nfs_proc_get_root,
> >         .submount       = nfs_submount,
> >         .try_get_tree   = nfs_try_get_tree,
> > diff --git a/include/linux/nfs_fs.h b/include/linux/nfs_fs.h
> > index 058fc11338d9..0c3a5859f7f3 100644
> > --- a/include/linux/nfs_fs.h
> > +++ b/include/linux/nfs_fs.h
> > @@ -501,6 +501,7 @@ extern __be32 root_nfs_parse_addr(char *name);
> > /*__init*/
> >  extern const struct file_operations nfs_file_operations;
> >  #if IS_ENABLED(CONFIG_NFS_V4)
> >  extern const struct file_operations nfs4_file_operations;
> > +extern const struct file_operations nfs4_dir_operations;
> >  #endif /* CONFIG_NFS_V4 */
> >  extern const struct address_space_operations nfs_file_aops;
> >  extern const struct address_space_operations nfs_dir_aops;
> > diff --git a/include/linux/nfs_xdr.h b/include/linux/nfs_xdr.h
> > index 0d5b11c1bfec..9ce61f680a13 100644
> > --- a/include/linux/nfs_xdr.h
> > +++ b/include/linux/nfs_xdr.h
> > @@ -812,6 +812,7 @@ struct nfs_createargs {
> >         struct iattr *          sattr;
> >  };
> >  
> > +struct nfs4_statx;
> >  struct nfs_setattrargs {
> >         struct nfs4_sequence_args       seq_args;
> >         struct nfs_fh *                 fh;
> > @@ -820,6 +821,7 @@ struct nfs_setattrargs {
> >         const struct nfs_server *       server; /* Needed for name
> > mapping */
> >         const u32 *                     bitmask;
> >         const struct nfs4_label         *label;
> > +       const struct nfs4_statx         *statx;
> >  };
> >  
> >  struct nfs_setaclargs {
> > @@ -1744,6 +1746,7 @@ struct nfs_rpc_ops {
> >         const struct inode_operations *dir_inode_ops;
> >         const struct inode_operations *file_inode_ops;
> >         const struct file_operations *file_ops;
> > +       const struct file_operations *dir_ops;
> >         const struct nlmclnt_operations *nlmclnt_ops;
> >  
> >         int     (*getroot) (struct nfs_server *, struct nfs_fh *,
> > diff --git a/include/uapi/linux/nfs.h b/include/uapi/linux/nfs.h
> > index 946cb62d64b0..df87da39bc43 100644
> > --- a/include/uapi/linux/nfs.h
> > +++ b/include/uapi/linux/nfs.h
> > @@ -9,6 +9,8 @@
> >  #define _UAPI_LINUX_NFS_H
> >  
> >  #include <linux/types.h>
> > +#include <asm/byteorder.h>
> > +#include <linux/time.h>
> >  
> >  #define NFS_PROGRAM    100003
> >  #define NFS_PORT       2049
> > @@ -35,6 +37,94 @@
> >  
> >  #define NFS_PIPE_DIRNAME "nfs"
> >  
> > +/* NFS ioctls */
> > +#define NFS_IOC_FILE_STATX_GET _IOR('N', 2, struct
> > nfs_ioctl_nfs4_statx)
> > +#define NFS_IOC_FILE_STATX_SET _IOW('N', 3, struct
> > nfs_ioctl_nfs4_statx)
> > +
> > +/* Options for struct nfs_ioctl_nfs4_statx */
> > +#define NFS_FA_OPTIONS_SYNC_AS_STAT                    0x0000
> > +#define NFS_FA_OPTIONS_FORCE_SYNC                      0x2000 /*
> > See statx */
> > +#define NFS_FA_OPTIONS_DONT_SYNC                       0x4000 /*
> > See statx */
> > +
> > +#define NFS_FA_VALID_TIME_CREATE                       0x00001UL
> > +#define NFS_FA_VALID_TIME_BACKUP                       0x00002UL
> > +#define NFS_FA_VALID_ARCHIVE                           0x00004UL
> > +#define NFS_FA_VALID_HIDDEN                            0x00008UL
> > +#define NFS_FA_VALID_SYSTEM                            0x00010UL
> > +#define NFS_FA_VALID_OWNER                             0x00020UL
> > +#define NFS_FA_VALID_OWNER_GROUP                       0x00040UL
> > +#define NFS_FA_VALID_ATIME                             0x00080UL
> > +#define NFS_FA_VALID_MTIME                             0x00100UL
> > +#define NFS_FA_VALID_CTIME                             0x00200UL
> > +#define NFS_FA_VALID_OFFLINE                           0x00400UL
> > +#define NFS_FA_VALID_MODE                              0x00800UL
> > +#define NFS_FA_VALID_NLINK                             0x01000UL
> > +#define NFS_FA_VALID_BLKSIZE                           0x02000UL
> > +#define NFS_FA_VALID_INO                               0x04000UL
> > +#define NFS_FA_VALID_DEV                               0x08000UL
> > +#define NFS_FA_VALID_RDEV                              0x10000UL
> > +#define NFS_FA_VALID_SIZE                              0x20000UL
> > +#define NFS_FA_VALID_BLOCKS                            0x40000UL
> > +
> > +#define NFS_FA_VALID_ALL_ATTR_0 ( NFS_FA_VALID_TIME_CREATE | \
> > +               NFS_FA_VALID_TIME_BACKUP | \
> > +               NFS_FA_VALID_ARCHIVE | \
> > +               NFS_FA_VALID_HIDDEN | \
> > +               NFS_FA_VALID_SYSTEM | \
> > +               NFS_FA_VALID_OWNER | \
> > +               NFS_FA_VALID_OWNER_GROUP | \
> > +               NFS_FA_VALID_ATIME | \
> > +               NFS_FA_VALID_MTIME | \
> > +               NFS_FA_VALID_CTIME | \
> > +               NFS_FA_VALID_OFFLINE | \
> > +               NFS_FA_VALID_MODE | \
> > +               NFS_FA_VALID_NLINK | \
> > +               NFS_FA_VALID_BLKSIZE | \
> > +               NFS_FA_VALID_INO | \
> > +               NFS_FA_VALID_DEV | \
> > +               NFS_FA_VALID_RDEV | \
> > +               NFS_FA_VALID_SIZE | \
> > +               NFS_FA_VALID_BLOCKS)
> > +
> > +#define NFS_FA_FLAG_ARCHIVE                            (1UL << 0)
> > +#define NFS_FA_FLAG_HIDDEN                             (1UL << 1)
> > +#define NFS_FA_FLAG_SYSTEM                             (1UL << 2)
> > +#define NFS_FA_FLAG_OFFLINE                            (1UL << 3)
> > +
> > +struct nfs_ioctl_timespec {
> > +       __s64           tv_sec;
> > +       __s64           tv_nsec;
> > +};
> > +
> > +struct nfs_ioctl_nfs4_statx {
> > +       __s32           real_fd;                /* real FD to use,
> > +                                                  -1 means use
> > current file */
> > +       __u32           fa_options;
> > +
> > +       __u64           fa_request[2];          /* Attributes to
> > retrieve */
> > +       __u64           fa_valid[2];            /* Attributes set
> > */
> > +
> > +       struct nfs_ioctl_timespec fa_time_backup;/* Backup time */
> > +       struct nfs_ioctl_timespec fa_btime;     /* Birth time */
> > +       __u64           fa_flags;               /* Flag attributes
> > */
> > +       /* Ordinary attributes follow */
> > +       struct nfs_ioctl_timespec fa_atime;     /* Access time */
> > +       struct nfs_ioctl_timespec fa_mtime;     /* Modify time */
> > +       struct nfs_ioctl_timespec fa_ctime;     /* Change time */
> > +       __u32           fa_owner_uid;           /* Owner User ID */
> > +       __u32           fa_group_gid;           /* Primary Group ID
> > */
> > +       __u32           fa_mode;                /* Mode */
> > +       __u32           fa_nlink;
> > +       __u32           fa_blksize;
> > +       __u32           fa_spare;               /* Alignment */
> > +       __u64           fa_ino;
> > +       __u32           fa_dev;
> > +       __u32           fa_rdev;
> > +       __s64           fa_size;
> > +       __s64           fa_blocks;
> > +       __u64           fa_padding[4];
> > +};
> > +
> >  /*
> >   * NFS stats. The good thing with these values is that NFSv3
> > errors are
> >   * a superset of NFSv2 errors (with the exception of NFSERR_WFLUSH
> > which
> > -- 
> > 2.33.1
J. Bruce Fields Jan. 3, 2022, 9:47 p.m. UTC | #3
On Mon, Jan 03, 2022 at 08:56:22PM +0000, Trond Myklebust wrote:
> On Mon, 2022-01-03 at 15:52 -0500, J. Bruce Fields wrote:
> > On Fri, Dec 17, 2021 at 03:48:53PM -0500, trondmy@kernel.org wrote:
> > > From: Richard Sharpe <richard.sharpe@primarydata.com>
> > > 
> > > Add support for returning all of the Windows attributes with a
> > > statx
> > > ioctl.
> > 
> > I suppose I'm just woodshedding, but "statx ioctl" is a little
> > confusing--it doesn't have any actual connection with the statx
> > system call, right?
> > 
> > But, why not add this to statx?
> 
> We could definitely add the attribute retrieval to the statx() system
> call. I believe that Steve French did suggest that at one point. There
> was push back because the number of applications that care is limited.
> Perhaps there might be more interest now that we have more extensive
> support for NTFS in the kernel.
> 
> However there is no statx() call to actually _set_ these attributes,
> and that is a reason to stick with the ioctl() approach for now.

Oh, got it, makes sense.

--b.

> 
> 
> > 
> > --b.
> > 
> > > Add support for setting all of the Windows attributes using an
> > > ioctl.
> > > 
> > > Signed-off-by: Richard Sharpe <richard.sharpe@primarydata.com>
> > > Signed-off-by: Lance Shelton <lance.shelton@hammerspace.com>
> > > Signed-off-by: Trond Myklebust <trond.myklebust@hammerspace.com>
> > > ---
> > >  fs/nfs/dir.c             |  24 +-
> > >  fs/nfs/getroot.c         |   3 +-
> > >  fs/nfs/inode.c           |  41 +++-
> > >  fs/nfs/internal.h        |   8 +
> > >  fs/nfs/nfs3proc.c        |   1 +
> > >  fs/nfs/nfs4_fs.h         |  31 +++
> > >  fs/nfs/nfs4file.c        | 511
> > > +++++++++++++++++++++++++++++++++++++++
> > >  fs/nfs/nfs4proc.c        | 124 ++++++++++
> > >  fs/nfs/nfs4xdr.c         |  63 ++++-
> > >  fs/nfs/nfstrace.c        |   5 +
> > >  fs/nfs/nfstrace.h        |   5 +
> > >  fs/nfs/proc.c            |   1 +
> > >  include/linux/nfs_fs.h   |   1 +
> > >  include/linux/nfs_xdr.h  |   3 +
> > >  include/uapi/linux/nfs.h |  90 +++++++
> > >  15 files changed, 887 insertions(+), 24 deletions(-)
> > > 
> > > diff --git a/fs/nfs/dir.c b/fs/nfs/dir.c
> > > index 731d31015b6a..f6fc60822153 100644
> > > --- a/fs/nfs/dir.c
> > > +++ b/fs/nfs/dir.c
> > > @@ -48,11 +48,6 @@
> > >  
> > >  /* #define NFS_DEBUG_VERBOSE 1 */
> > >  
> > > -static int nfs_opendir(struct inode *, struct file *);
> > > -static int nfs_closedir(struct inode *, struct file *);
> > > -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 page*);
> > >  
> > >  const struct file_operations nfs_dir_operations = {
> > > @@ -63,6 +58,7 @@ const struct file_operations nfs_dir_operations =
> > > {
> > >         .release        = nfs_closedir,
> > >         .fsync          = nfs_fsync_dir,
> > >  };
> > > +EXPORT_SYMBOL_GPL(nfs_dir_operations);
> > >  
> > >  const struct address_space_operations nfs_dir_aops = {
> > >         .freepage = nfs_readdir_clear_array,
> > > @@ -104,8 +100,7 @@ static void put_nfs_open_dir_context(struct
> > > inode *dir, struct nfs_open_dir_cont
> > >  /*
> > >   * Open file
> > >   */
> > > -static int
> > > -nfs_opendir(struct inode *inode, struct file *filp)
> > > +int nfs_opendir(struct inode *inode, struct file *filp)
> > >  {
> > >         int res = 0;
> > >         struct nfs_open_dir_context *ctx;
> > > @@ -123,13 +118,14 @@ nfs_opendir(struct inode *inode, struct file
> > > *filp)
> > >  out:
> > >         return res;
> > >  }
> > > +EXPORT_SYMBOL_GPL(nfs_opendir);
> > >  
> > > -static int
> > > -nfs_closedir(struct inode *inode, struct file *filp)
> > > +int nfs_closedir(struct inode *inode, struct file *filp)
> > >  {
> > >         put_nfs_open_dir_context(file_inode(filp), filp-
> > > >private_data);
> > >         return 0;
> > >  }
> > > +EXPORT_SYMBOL_GPL(nfs_closedir);
> > >  
> > >  struct nfs_cache_array_entry {
> > >         u64 cookie;
> > > @@ -1064,7 +1060,7 @@ static int uncached_readdir(struct
> > > nfs_readdir_descriptor *desc)
> > >     last cookie cache takes care of the common case of reading the
> > >     whole directory.
> > >   */
> > > -static int nfs_readdir(struct file *file, struct dir_context *ctx)
> > > +int nfs_readdir(struct file *file, struct dir_context *ctx)
> > >  {
> > >         struct dentry   *dentry = file_dentry(file);
> > >         struct inode    *inode = d_inode(dentry);
> > > @@ -1157,8 +1153,9 @@ static int nfs_readdir(struct file *file,
> > > struct dir_context *ctx)
> > >         dfprintk(FILE, "NFS: readdir(%pD2) returns %d\n", file,
> > > res);
> > >         return res;
> > >  }
> > > +EXPORT_SYMBOL_GPL(nfs_readdir);
> > >  
> > > -static loff_t nfs_llseek_dir(struct file *filp, loff_t offset, int
> > > whence)
> > > +loff_t nfs_llseek_dir(struct file *filp, loff_t offset, int
> > > whence)
> > >  {
> > >         struct nfs_open_dir_context *dir_ctx = filp->private_data;
> > >  
> > > @@ -1196,19 +1193,20 @@ static loff_t nfs_llseek_dir(struct file
> > > *filp, loff_t offset, int whence)
> > >         spin_unlock(&filp->f_lock);
> > >         return offset;
> > >  }
> > > +EXPORT_SYMBOL_GPL(nfs_llseek_dir);
> > >  
> > >  /*
> > >   * All directory operations under NFS are synchronous, so fsync()
> > >   * is a dummy operation.
> > >   */
> > > -static int nfs_fsync_dir(struct file *filp, loff_t start, loff_t
> > > end,
> > > -                        int datasync)
> > > +int nfs_fsync_dir(struct file *filp, loff_t start, loff_t end, int
> > > datasync)
> > >  {
> > >         dfprintk(FILE, "NFS: fsync dir(%pD2) datasync %d\n", filp,
> > > datasync);
> > >  
> > >         nfs_inc_stats(file_inode(filp), NFSIOS_VFSFSYNC);
> > >         return 0;
> > >  }
> > > +EXPORT_SYMBOL_GPL(nfs_fsync_dir);
> > >  
> > >  /**
> > >   * nfs_force_lookup_revalidate - Mark the directory as having
> > > changed
> > > diff --git a/fs/nfs/getroot.c b/fs/nfs/getroot.c
> > > index 11ff2b2e060f..f872970d6240 100644
> > > --- a/fs/nfs/getroot.c
> > > +++ b/fs/nfs/getroot.c
> > > @@ -127,7 +127,8 @@ int nfs_get_root(struct super_block *s, struct
> > > fs_context *fc)
> > >         if (server->caps & NFS_CAP_SECURITY_LABEL)
> > >                 kflags |= SECURITY_LSM_NATIVE_LABELS;
> > >         if (ctx->clone_data.sb) {
> > > -               if (d_inode(fc->root)->i_fop !=
> > > &nfs_dir_operations) {
> > > +               if (d_inode(fc->root)->i_fop !=
> > > +                   server->nfs_client->rpc_ops->dir_ops) {
> > >                         error = -ESTALE;
> > >                         goto error_splat_root;
> > >                 }
> > > diff --git a/fs/nfs/inode.c b/fs/nfs/inode.c
> > > index 33f4410190b6..8da662a4953d 100644
> > > --- a/fs/nfs/inode.c
> > > +++ b/fs/nfs/inode.c
> > > @@ -108,6 +108,7 @@ u64 nfs_compat_user_ino64(u64 fileid)
> > >                 ino ^= fileid >> (sizeof(fileid)-sizeof(ino)) * 8;
> > >         return ino;
> > >  }
> > > +EXPORT_SYMBOL_GPL(nfs_compat_user_ino64);
> > >  
> > >  int nfs_drop_inode(struct inode *inode)
> > >  {
> > > @@ -501,7 +502,7 @@ nfs_fhget(struct super_block *sb, struct nfs_fh
> > > *fh, struct nfs_fattr *fattr)
> > >                         nfs_inode_init_regular(nfsi);
> > >                 } else if (S_ISDIR(inode->i_mode)) {
> > >                         inode->i_op = NFS_SB(sb)->nfs_client-
> > > >rpc_ops->dir_inode_ops;
> > > -                       inode->i_fop = &nfs_dir_operations;
> > > +                       inode->i_fop = NFS_SB(sb)->nfs_client-
> > > >rpc_ops->dir_ops;
> > >                         inode->i_data.a_ops = &nfs_dir_aops;
> > >                         nfs_inode_init_dir(nfsi);
> > >                         /* Deal with crossing mountpoints */
> > > @@ -867,6 +868,44 @@ static u32 nfs_get_valid_attrmask(struct inode
> > > *inode)
> > >         return reply_mask;
> > >  }
> > >  
> > > +static int nfs_getattr_revalidate_force(struct dentry *dentry)
> > > +{
> > > +       struct inode *inode = d_inode(dentry);
> > > +       struct nfs_server *server = NFS_SERVER(inode);
> > > +
> > > +       if (!(server->flags & NFS_MOUNT_NOAC))
> > > +               nfs_readdirplus_parent_cache_miss(dentry);
> > > +       else
> > > +               nfs_readdirplus_parent_cache_hit(dentry);
> > > +       return __nfs_revalidate_inode(server, inode);
> > > +}
> > > +
> > > +static int nfs_getattr_revalidate_none(struct dentry *dentry)
> > > +{
> > > +       nfs_readdirplus_parent_cache_hit(dentry);
> > > +       return NFS_STALE(d_inode(dentry)) ? -ESTALE : 0;
> > > +}
> > > +
> > > +static int nfs_getattr_revalidate_maybe(struct dentry *dentry,
> > > +                                       unsigned long flags)
> > > +{
> > > +       if (nfs_check_cache_invalid(d_inode(dentry), flags))
> > > +               return nfs_getattr_revalidate_force(dentry);
> > > +       return nfs_getattr_revalidate_none(dentry);
> > > +}
> > > +
> > > +int nfs_getattr_revalidate(const struct path *path,
> > > +                          unsigned long flags,
> > > +                          unsigned int query_flags)
> > > +{
> > > +       if (query_flags & AT_STATX_FORCE_SYNC)
> > > +               return nfs_getattr_revalidate_force(path->dentry);
> > > +       if (!(query_flags & AT_STATX_DONT_SYNC))
> > > +               return nfs_getattr_revalidate_maybe(path->dentry,
> > > flags);
> > > +       return nfs_getattr_revalidate_none(path->dentry);
> > > +}
> > > +EXPORT_SYMBOL_GPL(nfs_getattr_revalidate);
> > > +
> > >  int nfs_getattr(struct user_namespace *mnt_userns, const struct
> > > path *path,
> > >                 struct kstat *stat, u32 request_mask, unsigned int
> > > query_flags)
> > >  {
> > > diff --git a/fs/nfs/internal.h b/fs/nfs/internal.h
> > > index 12f6acb483bb..9602a886f0f0 100644
> > > --- a/fs/nfs/internal.h
> > > +++ b/fs/nfs/internal.h
> > > @@ -366,6 +366,12 @@ extern struct nfs_client
> > > *nfs_init_client(struct nfs_client *clp,
> > >                            const struct nfs_client_initdata *);
> > >  
> > >  /* dir.c */
> > > +int nfs_opendir(struct inode *, struct file *);
> > > +int nfs_closedir(struct inode *, struct file *);
> > > +int nfs_readdir(struct file *file, struct dir_context *ctx);
> > > +int nfs_fsync_dir(struct file *, loff_t, loff_t, int);
> > > +loff_t nfs_llseek_dir(struct file *filp, loff_t offset, int
> > > whence);
> > > +
> > >  extern void nfs_advise_use_readdirplus(struct inode *dir);
> > >  extern void nfs_force_use_readdirplus(struct inode *dir);
> > >  extern unsigned long nfs_access_cache_count(struct shrinker
> > > *shrink,
> > > @@ -411,6 +417,8 @@ extern void nfs_set_cache_invalid(struct inode
> > > *inode, unsigned long flags);
> > >  extern bool nfs_check_cache_invalid(struct inode *, unsigned
> > > long);
> > >  extern int nfs_wait_bit_killable(struct wait_bit_key *key, int
> > > mode);
> > >  extern int nfs_wait_atomic_killable(atomic_t *p, unsigned int
> > > mode);
> > > +extern int nfs_getattr_revalidate(const struct path *path,
> > > unsigned long flags,
> > > +                                 unsigned int query_flags);
> > >  
> > >  /* super.c */
> > >  extern const struct super_operations nfs_sops;
> > > diff --git a/fs/nfs/nfs3proc.c b/fs/nfs/nfs3proc.c
> > > index 7100514d306b..091005e169b7 100644
> > > --- a/fs/nfs/nfs3proc.c
> > > +++ b/fs/nfs/nfs3proc.c
> > > @@ -1018,6 +1018,7 @@ const struct nfs_rpc_ops nfs_v3_clientops = {
> > >         .dir_inode_ops  = &nfs3_dir_inode_operations,
> > >         .file_inode_ops = &nfs3_file_inode_operations,
> > >         .file_ops       = &nfs_file_operations,
> > > +       .dir_ops        = &nfs_dir_operations,
> > >         .nlmclnt_ops    = &nlmclnt_fl_close_lock_ops,
> > >         .getroot        = nfs3_proc_get_root,
> > >         .submount       = nfs_submount,
> > > diff --git a/fs/nfs/nfs4_fs.h b/fs/nfs/nfs4_fs.h
> > > index ed5eaca6801e..9f21d8520e99 100644
> > > --- a/fs/nfs/nfs4_fs.h
> > > +++ b/fs/nfs/nfs4_fs.h
> > > @@ -248,6 +248,34 @@ struct nfs4_opendata {
> > >         int rpc_status;
> > >  };
> > >  
> > > +struct nfs4_statx {
> > > +       int             real_fd;                /* real FD to use,
> > > +                                                  -1 means use
> > > current file */
> > > +       __u32           fa_options;             /* statx flags */
> > > +       __u64           fa_request[2];          /* Attributes
> > > requested */
> > > +       __u64           fa_valid[2];            /* Attributes set
> > > */
> > > +
> > > +       struct timespec64 fa_time_backup;       /* Backup time */
> > > +       struct timespec64 fa_btime;             /* Birth time */
> > > +       /* Flag attributes */
> > > +       __u64 fa_flags;
> > > +       struct timespec64 fa_atime;             /* Access time */
> > > +       struct timespec64 fa_mtime;             /* Modify time */
> > > +       struct timespec64 fa_ctime;             /* Change time */
> > > +       kuid_t          fa_owner_uid;           /* Owner User ID */
> > > +       kgid_t          fa_group_gid;           /* Primary Group ID
> > > */
> > > +        /* Normal stat fields after this */
> > > +       __u32           fa_mode;                /* Mode */
> > > +       unsigned int    fa_nlink;
> > > +       __u32           fa_blksize;
> > > +       __u32           fa_spare;               /* Alignment */
> > > +       __u64           fa_ino;
> > > +       dev_t           fa_dev;
> > > +       dev_t           fa_rdev;
> > > +       loff_t          fa_size;
> > > +       __u64           fa_blocks;
> > > +};
> > > +
> > >  struct nfs4_add_xprt_data {
> > >         struct nfs_client       *clp;
> > >         const struct cred       *cred;
> > > @@ -315,6 +343,9 @@ extern int nfs4_set_rw_stateid(nfs4_stateid
> > > *stateid,
> > >                 const struct nfs_open_context *ctx,
> > >                 const struct nfs_lock_context *l_ctx,
> > >                 fmode_t fmode);
> > > +int nfs4_set_nfs4_statx(struct inode *inode,
> > > +               struct nfs4_statx *statx,
> > > +               struct nfs_fattr *fattr);
> > >  extern int nfs4_proc_getattr(struct nfs_server *server, struct
> > > nfs_fh *fhandle,
> > >                              struct nfs_fattr *fattr, struct inode
> > > *inode);
> > >  extern int update_open_stateid(struct nfs4_state *state,
> > > diff --git a/fs/nfs/nfs4file.c b/fs/nfs/nfs4file.c
> > > index e79ae4cbc395..494ebc7cd1c0 100644
> > > --- a/fs/nfs/nfs4file.c
> > > +++ b/fs/nfs/nfs4file.c
> > > @@ -9,6 +9,8 @@
> > >  #include <linux/falloc.h>
> > >  #include <linux/mount.h>
> > >  #include <linux/nfs_fs.h>
> > > +#include <linux/time32.h>
> > > +#include <linux/compat.h>
> > >  #include <linux/nfs_ssc.h>
> > >  #include "delegation.h"
> > >  #include "internal.h"
> > > @@ -132,6 +134,503 @@ nfs4_file_flush(struct file *file, fl_owner_t
> > > id)
> > >         return filemap_check_wb_err(file->f_mapping, since);
> > >  }
> > >  
> > > +static int nfs_get_timespec64(struct timespec64 *ts,
> > > +                             const struct nfs_ioctl_timespec
> > > __user *uts)
> > > +{
> > > +       __s64 dummy;
> > > +       if (unlikely(get_user(dummy, &uts->tv_sec) != 0))
> > > +               return EFAULT;
> > > +       ts->tv_sec = dummy;
> > > +       if (unlikely(get_user(dummy, &uts->tv_nsec) != 0))
> > > +               return EFAULT;
> > > +       ts->tv_nsec = dummy;
> > > +       return 0;
> > > +}
> > > +
> > > +static int nfs_put_timespec64(const struct timespec64 *ts,
> > > +                             struct nfs_ioctl_timespec __user
> > > *uts)
> > > +{
> > > +       __s64 dummy;
> > > +
> > > +       dummy = ts->tv_sec;
> > > +       if (unlikely(put_user(dummy, &uts->tv_sec) != 0))
> > > +               return EFAULT;
> > > +       dummy = ts->tv_nsec;
> > > +       if (unlikely(put_user(dummy, &uts->tv_nsec) != 0))
> > > +               return EFAULT;
> > > +       return 0;
> > > +}
> > > +
> > > +static struct file *nfs4_get_real_file(struct file *src, unsigned
> > > int fd)
> > > +{
> > > +       struct file *filp = fget_raw(fd);
> > > +       int ret = -EBADF;
> > > +
> > > +       if (!filp)
> > > +               goto out;
> > > +       /* Validate that the files share the same underlying
> > > filesystem */
> > > +       ret = -EXDEV;
> > > +       if (file_inode(filp)->i_sb != file_inode(src)->i_sb)
> > > +               goto out_put;
> > > +       return filp;
> > > +out_put:
> > > +       fput(filp);
> > > +out:
> > > +       return ERR_PTR(ret);
> > > +}
> > > +
> > > +static unsigned long nfs4_statx_request_to_cache_validity(__u64
> > > request,
> > > +                                                         u64
> > > fattr_supported)
> > > +{
> > > +       unsigned long ret = 0;
> > > +
> > > +       if (request & NFS_FA_VALID_ATIME)
> > > +               ret |= NFS_INO_INVALID_ATIME;
> > > +       if (request & NFS_FA_VALID_CTIME)
> > > +               ret |= NFS_INO_INVALID_CTIME;
> > > +       if (request & NFS_FA_VALID_MTIME)
> > > +               ret |= NFS_INO_INVALID_MTIME;
> > > +       if (request & NFS_FA_VALID_SIZE)
> > > +               ret |= NFS_INO_INVALID_SIZE;
> > > +
> > > +       if (request & NFS_FA_VALID_MODE)
> > > +               ret |= NFS_INO_INVALID_MODE;
> > > +       if (request & (NFS_FA_VALID_OWNER |
> > > NFS_FA_VALID_OWNER_GROUP))
> > > +               ret |= NFS_INO_INVALID_OTHER;
> > > +
> > > +       if (request & NFS_FA_VALID_NLINK)
> > > +               ret |= NFS_INO_INVALID_NLINK;
> > > +       if (request & NFS_FA_VALID_BLOCKS)
> > > +               ret |= NFS_INO_INVALID_BLOCKS;
> > > +
> > > +       if (request & NFS_FA_VALID_TIME_CREATE)
> > > +               ret |= NFS_INO_INVALID_BTIME;
> > > +
> > > +       if (request & NFS_FA_VALID_ARCHIVE) {
> > > +               if (fattr_supported & NFS_ATTR_FATTR_ARCHIVE)
> > > +                       ret |= NFS_INO_INVALID_WINATTR;
> > > +               else if (fattr_supported &
> > > NFS_ATTR_FATTR_TIME_BACKUP)
> > > +                       ret |= NFS_INO_INVALID_WINATTR |
> > > NFS_INO_INVALID_MTIME;
> > > +       }
> > > +       if (request & (NFS_FA_VALID_TIME_BACKUP |
> > > NFS_FA_VALID_HIDDEN |
> > > +                      NFS_FA_VALID_SYSTEM | NFS_FA_VALID_OFFLINE))
> > > +               ret |= NFS_INO_INVALID_WINATTR;
> > > +
> > > +       return ret ? (ret | NFS_INO_INVALID_CHANGE) : 0;
> > > +}
> > > +
> > > +static long nfs4_ioctl_file_statx_get(struct file *dst_file,
> > > +                                     struct nfs_ioctl_nfs4_statx
> > > __user *uarg)
> > > +{
> > > +       struct nfs4_statx args = {
> > > +               .real_fd = -1,
> > > +               .fa_valid = { 0 },
> > > +       };
> > > +       struct inode *inode;
> > > +       struct nfs_inode *nfsi;
> > > +       struct nfs_server *server;
> > > +       u64 fattr_supported;
> > > +       unsigned long reval_attr;
> > > +       unsigned int reval_flags;
> > > +       __u32 tmp;
> > > +       int ret;
> > > +
> > > +       /*
> > > +        * We get the first word from the uarg as it tells us
> > > whether
> > > +        * to use the passed in struct file or use that fd to find
> > > the
> > > +        * struct file.
> > > +        */
> > > +       if (get_user(args.real_fd, &uarg->real_fd))
> > > +               return -EFAULT;
> > > +
> > > +       if (get_user(args.fa_options, &uarg->fa_options))
> > > +               return -EFAULT;
> > > +
> > > +       if (get_user(args.fa_request[0], &uarg->fa_request[0]))
> > > +               return -EFAULT;
> > > +
> > > +       if (args.real_fd >= 0) {
> > > +               dst_file = nfs4_get_real_file(dst_file,
> > > args.real_fd);
> > > +               if (IS_ERR(dst_file))
> > > +                       return PTR_ERR(dst_file);
> > > +       }
> > > +
> > > +       /*
> > > +        * Backward compatibility: we stole the top 32 bits of
> > > 'real_fd'
> > > +        * to create the fa_options field, so if its value is -1,
> > > then
> > > +        * assume it is the high word of (__s64)real_fd == -1, and
> > > just
> > > +        * set it to zero.
> > > +        */
> > > +       if (args.fa_options == 0xFFFF)
> > > +               args.fa_options = 0;
> > > +
> > > +       inode = file_inode(dst_file);
> > > +       nfsi = NFS_I(inode);
> > > +       server = NFS_SERVER(inode);
> > > +       fattr_supported = server->fattr_valid;
> > > +
> > > +       trace_nfs_ioctl_file_statx_get_enter(inode);
> > > +
> > > +       if (args.fa_options & NFS_FA_OPTIONS_FORCE_SYNC)
> > > +               reval_flags = AT_STATX_FORCE_SYNC;
> > > +       else if (args.fa_options & NFS_FA_OPTIONS_DONT_SYNC)
> > > +               reval_flags = AT_STATX_DONT_SYNC;
> > > +       else
> > > +               reval_flags = AT_STATX_SYNC_AS_STAT;
> > > +
> > > +       reval_attr =
> > > nfs4_statx_request_to_cache_validity(args.fa_request[0],
> > > +                                                        
> > > fattr_supported);
> > > +
> > > +       if ((reval_attr & (NFS_INO_INVALID_CTIME |
> > > NFS_INO_INVALID_MTIME)) &&
> > > +           reval_flags != AT_STATX_DONT_SYNC && S_ISREG(inode-
> > > >i_mode)) {
> > > +               ret = filemap_write_and_wait(inode->i_mapping);
> > > +               if (ret)
> > > +                       goto out;
> > > +       }
> > > +
> > > +       if ((dst_file->f_path.mnt->mnt_flags & MNT_NOATIME) ||
> > > +           ((dst_file->f_path.mnt->mnt_flags & MNT_NODIRATIME) &&
> > > +            S_ISDIR(inode->i_mode)))
> > > +               reval_attr &= ~NFS_INO_INVALID_ATIME;
> > > +
> > > +       ret = nfs_getattr_revalidate(&dst_file->f_path, reval_attr,
> > > +                                    reval_flags);
> > > +       if (ret != 0)
> > > +               goto out;
> > > +
> > > +       ret = -EFAULT;
> > > +       if ((fattr_supported & NFS_ATTR_FATTR_OWNER) &&
> > > +           (args.fa_request[0] & NFS_FA_VALID_OWNER)) {
> > > +               tmp = from_kuid_munged(current_user_ns(), inode-
> > > >i_uid);
> > > +               if (unlikely(put_user(tmp, &uarg->fa_owner_uid) !=
> > > 0))
> > > +                       goto out;
> > > +               args.fa_valid[0] |= NFS_FA_VALID_OWNER;
> > > +       }
> > > +
> > > +       if ((fattr_supported & NFS_ATTR_FATTR_GROUP) &&
> > > +           (args.fa_request[0] & NFS_FA_VALID_OWNER_GROUP)) {
> > > +               tmp = from_kgid_munged(current_user_ns(), inode-
> > > >i_gid);
> > > +               if (unlikely(put_user(tmp, &uarg->fa_group_gid) !=
> > > 0))
> > > +                       goto out;
> > > +               args.fa_valid[0] |= NFS_FA_VALID_OWNER_GROUP;
> > > +       }
> > > +
> > > +       if ((fattr_supported & NFS_ATTR_FATTR_TIME_BACKUP) &&
> > > +           (args.fa_request[0] & NFS_FA_VALID_TIME_BACKUP)) {
> > > +               if (nfs_put_timespec64(&nfsi->timebackup, &uarg-
> > > >fa_time_backup))
> > > +                       goto out;
> > > +               args.fa_valid[0] |= NFS_FA_VALID_TIME_BACKUP;
> > > +       }
> > > +
> > > +       if ((fattr_supported & NFS_ATTR_FATTR_BTIME) &&
> > > +           (args.fa_request[0] & NFS_FA_VALID_TIME_CREATE)) {
> > > +               if (nfs_put_timespec64(&nfsi->btime, &uarg-
> > > >fa_btime))
> > > +                       goto out;
> > > +               args.fa_valid[0] |= NFS_FA_VALID_TIME_CREATE;
> > > +       }
> > > +
> > > +       /* atime, mtime, and ctime are all stored in the regular
> > > inode,
> > > +        * not the nfs inode.
> > > +        */
> > > +       if ((fattr_supported & NFS_ATTR_FATTR_ATIME) &&
> > > +           (args.fa_request[0] & NFS_FA_VALID_ATIME)) {
> > > +               if (nfs_put_timespec64(&inode->i_atime, &uarg-
> > > >fa_atime))
> > > +                       goto out;
> > > +               args.fa_valid[0] |= NFS_FA_VALID_ATIME;
> > > +       }
> > > +
> > > +       if ((fattr_supported & NFS_ATTR_FATTR_MTIME) &&
> > > +           (args.fa_request[0] & NFS_FA_VALID_MTIME)) {
> > > +               if (nfs_put_timespec64(&inode->i_mtime, &uarg-
> > > >fa_mtime))
> > > +                        goto out;
> > > +               args.fa_valid[0] |= NFS_FA_VALID_MTIME;
> > > +       }
> > > +
> > > +       if ((fattr_supported & NFS_ATTR_FATTR_CTIME) &&
> > > +           (args.fa_request[0] & NFS_FA_VALID_CTIME)) {
> > > +               if (nfs_put_timespec64(&inode->i_ctime, &uarg-
> > > >fa_ctime))
> > > +                       goto out;
> > > +               args.fa_valid[0] |= NFS_FA_VALID_CTIME;
> > > +       }
> > > +
> > > +        /*
> > > +         * It looks like PDFS does not support or properly handle
> > > the
> > > +         * archive bit.
> > > +         */
> > > +       if ((fattr_supported & NFS_ATTR_FATTR_ARCHIVE) &&
> > > +           (args.fa_request[0] & NFS_FA_VALID_ARCHIVE)) {
> > > +               if (nfsi->archive)
> > > +                       args.fa_flags |= NFS_FA_FLAG_ARCHIVE;
> > > +               args.fa_valid[0] |= NFS_FA_VALID_ARCHIVE;
> > > +       }
> > > +
> > > +       if ((fattr_supported & NFS_ATTR_FATTR_TIME_BACKUP) &&
> > > +           (args.fa_request[0] & NFS_FA_VALID_ARCHIVE)) {
> > > +               if (timespec64_compare(&inode->i_mtime, &nfsi-
> > > >timebackup) > 0)
> > > +                       args.fa_flags |= NFS_FA_FLAG_ARCHIVE;
> > > +               args.fa_valid[0] |= NFS_FA_VALID_ARCHIVE;
> > > +       }
> > > +
> > > +       if ((fattr_supported & NFS_ATTR_FATTR_HIDDEN) &&
> > > +           (args.fa_request[0] & NFS_FA_VALID_HIDDEN)) {
> > > +               if (nfsi->hidden)
> > > +                       args.fa_flags |= NFS_FA_FLAG_HIDDEN;
> > > +               args.fa_valid[0] |= NFS_FA_VALID_HIDDEN;
> > > +       }
> > > +       if ((fattr_supported & NFS_ATTR_FATTR_SYSTEM) &&
> > > +           (args.fa_request[0] & NFS_FA_VALID_SYSTEM)) {
> > > +               if (nfsi->system)
> > > +                       args.fa_flags |= NFS_FA_FLAG_SYSTEM;
> > > +               args.fa_valid[0] |= NFS_FA_VALID_SYSTEM;
> > > +       }
> > > +
> > > +       if ((fattr_supported & NFS_ATTR_FATTR_OFFLINE) &&
> > > +           (args.fa_request[0] & NFS_FA_VALID_OFFLINE)) {
> > > +               if (nfsi->offline)
> > > +                       args.fa_flags |= NFS_FA_FLAG_OFFLINE;
> > > +               args.fa_valid[0] |= NFS_FA_VALID_OFFLINE;
> > > +       }
> > > +
> > > +       if ((args.fa_valid[0] & (NFS_FA_VALID_ARCHIVE |
> > > +                               NFS_FA_VALID_HIDDEN |
> > > +                               NFS_FA_VALID_SYSTEM |
> > > +                               NFS_FA_VALID_OFFLINE)) &&
> > > +           put_user(args.fa_flags, &uarg->fa_flags))
> > > +               goto out;
> > > +
> > > +       if ((fattr_supported & NFS_ATTR_FATTR_MODE) &&
> > > +           (args.fa_request[0] & NFS_FA_VALID_MODE)) {
> > > +               tmp = inode->i_mode;
> > > +               /* This is an unsigned short we put into an __u32
> > > */
> > > +               if (unlikely(put_user(tmp, &uarg->fa_mode) != 0))
> > > +                       goto out;
> > > +               args.fa_valid[0] |= NFS_FA_VALID_MODE;
> > > +       }
> > > +
> > > +       if ((fattr_supported & NFS_ATTR_FATTR_NLINK) &&
> > > +           (args.fa_request[0] & NFS_FA_VALID_NLINK)) {
> > > +               tmp = inode->i_nlink;
> > > +               if (unlikely(put_user(tmp, &uarg->fa_nlink) != 0))
> > > +                       goto out;
> > > +               args.fa_valid[0] |= NFS_FA_VALID_NLINK;
> > > +       }
> > > +
> > > +       if (args.fa_request[0] & NFS_FA_VALID_BLKSIZE) {
> > > +               tmp = i_blocksize(inode);
> > > +               if (S_ISDIR(inode->i_mode))
> > > +                       tmp = NFS_SERVER(inode)->dtsize;
> > > +               if (unlikely(put_user(tmp, &uarg->fa_blksize) !=
> > > 0))
> > > +                       goto out;
> > > +               args.fa_valid[0] |= NFS_FA_VALID_BLKSIZE;
> > > +       }
> > > +
> > > +       if (args.fa_request[0] & NFS_FA_VALID_INO) {
> > > +               __u64 ino =
> > > nfs_compat_user_ino64(NFS_FILEID(inode));
> > > +               if (unlikely(put_user(ino, &uarg->fa_ino) != 0))
> > > +                       goto out;
> > > +               args.fa_valid[0] |= NFS_FA_VALID_INO;
> > > +       }
> > > +
> > > +       if (args.fa_request[0] & NFS_FA_VALID_DEV) {
> > > +               tmp = inode->i_sb->s_dev;
> > > +               if (unlikely(put_user(tmp, &uarg->fa_dev) != 0))
> > > +                       goto out;
> > > +               args.fa_valid[0] |= NFS_FA_VALID_DEV;
> > > +       }
> > > +
> > > +       if ((fattr_supported & NFS_ATTR_FATTR_RDEV) &&
> > > +           (args.fa_request[0] & NFS_FA_VALID_RDEV)) {
> > > +               tmp = inode->i_rdev;
> > > +               if (unlikely(put_user(tmp, &uarg->fa_rdev) != 0))
> > > +                       goto out;
> > > +               args.fa_valid[0] |= NFS_FA_VALID_RDEV;
> > > +       }
> > > +
> > > +       if ((fattr_supported & NFS_ATTR_FATTR_SIZE) &&
> > > +           (args.fa_request[0] & NFS_FA_VALID_SIZE)) {
> > > +               __s64 size = i_size_read(inode);
> > > +               if (unlikely(put_user(size, &uarg->fa_size) != 0))
> > > +                       goto out;
> > > +               args.fa_valid[0] |= NFS_FA_VALID_SIZE;
> > > +       }
> > > +
> > > +       if ((fattr_supported &
> > > +            (NFS_ATTR_FATTR_BLOCKS_USED |
> > > NFS_ATTR_FATTR_SPACE_USED)) &&
> > > +           (args.fa_request[0] & NFS_FA_VALID_BLOCKS)) {
> > > +               __s64 blocks = inode->i_blocks;
> > > +               if (unlikely(put_user(blocks, &uarg->fa_blocks) !=
> > > 0))
> > > +                       goto out;
> > > +               args.fa_valid[0] |= NFS_FA_VALID_BLOCKS;
> > > +       }
> > > +
> > > +       if (unlikely(put_user(args.fa_valid[0], &uarg->fa_valid[0])
> > > != 0))
> > > +               goto out;
> > > +       if (unlikely(put_user(args.fa_valid[1], &uarg->fa_valid[1])
> > > != 0))
> > > +               goto out;
> > > +
> > > +       ret = 0;
> > > +out:
> > > +       if (args.real_fd >= 0)
> > > +               fput(dst_file);
> > > +       trace_nfs_ioctl_file_statx_get_exit(inode, ret);
> > > +       return ret;
> > > +}
> > > +
> > > +static long nfs4_ioctl_file_statx_set(struct file *dst_file,
> > > +                                     struct nfs_ioctl_nfs4_statx
> > > __user *uarg)
> > > +{
> > > +       struct nfs4_statx args = {
> > > +               .real_fd = -1,
> > > +               .fa_valid = { 0 },
> > > +       };
> > > +       struct nfs_fattr *fattr = nfs_alloc_fattr();
> > > +       struct inode *inode;
> > > +       /*
> > > +        * If you need a different error code below, you need to
> > > set it
> > > +        */
> > > +       int ret = -EFAULT;
> > > +
> > > +       if (fattr == NULL)
> > > +               return -ENOMEM;
> > > +
> > > +       /*
> > > +        * We get the first u64 word from the uarg as it tells us
> > > whether
> > > +        * to use the passed in struct file or use that fd to find
> > > the
> > > +        * struct file.
> > > +        */
> > > +       if (get_user(args.real_fd, &uarg->real_fd))
> > > +               goto out_free;
> > > +
> > > +       if (args.real_fd >= 0) {
> > > +               dst_file = nfs4_get_real_file(dst_file,
> > > args.real_fd);
> > > +               if (IS_ERR(dst_file)) {
> > > +                       ret = PTR_ERR(dst_file);
> > > +                       goto out_free;
> > > +               }
> > > +       }
> > > +       inode = file_inode(dst_file);
> > > +       trace_nfs_ioctl_file_statx_set_enter(inode);
> > > +
> > > +       inode_lock(inode);
> > > +
> > > +       /* Write all dirty data */
> > > +       if (S_ISREG(inode->i_mode)) {
> > > +               ret = nfs_sync_inode(inode);
> > > +               if (ret)
> > > +                       goto out;
> > > +       }
> > > +
> > > +       ret = -EFAULT;
> > > +       if (get_user(args.fa_valid[0], &uarg->fa_valid[0]))
> > > +               goto out;
> > > +       args.fa_valid[0] &= NFS_FA_VALID_ALL_ATTR_0;
> > > +
> > > +       if (args.fa_valid[0] & NFS_FA_VALID_OWNER) {
> > > +               uid_t uid;
> > > +
> > > +               if (unlikely(get_user(uid, &uarg->fa_owner_uid) !=
> > > 0))
> > > +                       goto out;
> > > +               args.fa_owner_uid = make_kuid(current_user_ns(),
> > > uid);
> > > +               if (!uid_valid(args.fa_owner_uid)) {
> > > +                       ret = -EINVAL;
> > > +                       goto out;
> > > +               }
> > > +       }
> > > +
> > > +       if (args.fa_valid[0] & NFS_FA_VALID_OWNER_GROUP) {
> > > +               gid_t gid;
> > > +
> > > +               if (unlikely(get_user(gid, &uarg->fa_group_gid) !=
> > > 0))
> > > +                       goto out;
> > > +               args.fa_group_gid = make_kgid(current_user_ns(),
> > > gid);
> > > +               if (!gid_valid(args.fa_group_gid)) {
> > > +                       ret = -EINVAL;
> > > +                       goto out;
> > > +               }
> > > +       }
> > > +
> > > +       if ((args.fa_valid[0] & (NFS_FA_VALID_ARCHIVE |
> > > +                                       NFS_FA_VALID_HIDDEN |
> > > +                                       NFS_FA_VALID_SYSTEM)) &&
> > > +           get_user(args.fa_flags, &uarg->fa_flags))
> > > +               goto out;
> > > +
> > > +       if ((args.fa_valid[0] & NFS_FA_VALID_TIME_CREATE) &&
> > > +           nfs_get_timespec64(&args.fa_btime, &uarg->fa_btime))
> > > +               goto out;
> > > +
> > > +       if ((args.fa_valid[0] & NFS_FA_VALID_ATIME) &&
> > > +           nfs_get_timespec64(&args.fa_atime, &uarg->fa_atime))
> > > +               goto out;
> > > +
> > > +       if ((args.fa_valid[0] & NFS_FA_VALID_MTIME) &&
> > > +           nfs_get_timespec64(&args.fa_mtime, &uarg->fa_mtime))
> > > +               goto out;
> > > +
> > > +       if (args.fa_valid[0] & NFS_FA_VALID_TIME_BACKUP) {
> > > +               if (nfs_get_timespec64(&args.fa_time_backup, &uarg-
> > > >fa_time_backup))
> > > +                       goto out;
> > > +       } else if ((args.fa_valid[0] & NFS_FA_VALID_ARCHIVE) &&
> > > +                       !(NFS_SERVER(inode)->fattr_valid &
> > > NFS_ATTR_FATTR_ARCHIVE)) {
> > > +               args.fa_valid[0] |= NFS_FA_VALID_TIME_BACKUP;
> > > +               if (!(args.fa_flags & NFS_FA_FLAG_ARCHIVE)) {
> > > +                       nfs_revalidate_inode(inode,
> > > NFS_INO_INVALID_MTIME);
> > > +                       args.fa_time_backup.tv_sec = inode-
> > > >i_mtime.tv_sec;
> > > +                       args.fa_time_backup.tv_nsec = inode-
> > > >i_mtime.tv_nsec;
> > > +               } else if (args.fa_valid[0] &
> > > NFS_FA_VALID_TIME_CREATE)
> > > +                       args.fa_time_backup = args.fa_btime;
> > > +               else {
> > > +                       nfs_revalidate_inode(inode,
> > > NFS_INO_INVALID_BTIME);
> > > +                       args.fa_time_backup = NFS_I(inode)->btime;
> > > +               }
> > > +       }
> > > +
> > > +        if (args.fa_valid[0] & NFS_FA_VALID_SIZE) {
> > > +               if (copy_from_user(&args.fa_size, &uarg->fa_size,
> > > +                                       sizeof(args.fa_size)))
> > > +                       goto out;
> > > +               ret = inode_newsize_ok(inode,args.fa_size);
> > > +               if (ret)
> > > +                       goto out;
> > > +               if (args.fa_size == i_size_read(inode))
> > > +                       args.fa_valid[0] &= ~NFS_FA_VALID_SIZE;
> > > +       }
> > > +
> > > +       /*
> > > +        * No need to update the inode because that is done in
> > > nfs4_set_nfs4_statx
> > > +        */
> > > +       ret = nfs4_set_nfs4_statx(inode, &args, fattr);
> > > +
> > > +out:
> > > +       inode_unlock(inode);
> > > +       if (args.real_fd >= 0)
> > > +               fput(dst_file);
> > > +       trace_nfs_ioctl_file_statx_set_exit(inode, ret);
> > > +out_free:
> > > +       nfs_free_fattr(fattr);
> > > +       return ret;
> > > +}
> > > +
> > > +static long nfs4_ioctl(struct file *file, unsigned int cmd,
> > > unsigned long arg)
> > > +{
> > > +       void __user *argp = (void __user *)arg;
> > > +       long ret;
> > > +
> > > +       switch (cmd) {
> > > +       case NFS_IOC_FILE_STATX_GET:
> > > +               ret = nfs4_ioctl_file_statx_get(file, argp);
> > > +               break;
> > > +       case NFS_IOC_FILE_STATX_SET:
> > > +               ret = nfs4_ioctl_file_statx_set(file, argp);
> > > +               break;
> > > +       default:
> > > +               ret = -ENOIOCTLCMD;
> > > +       }
> > > +
> > > +       dprintk("%s: file=%pD2, cmd=%u, ret=%ld\n", __func__, file,
> > > cmd, ret);
> > > +       return ret;
> > > +}
> > > +
> > >  #ifdef CONFIG_NFS_V4_2
> > >  static ssize_t __nfs4_copy_file_range(struct file *file_in, loff_t
> > > pos_in,
> > >                                       struct file *file_out, loff_t
> > > pos_out,
> > > @@ -187,6 +686,7 @@ static ssize_t __nfs4_copy_file_range(struct
> > > file *file_in, loff_t pos_in,
> > >         return ret;
> > >  }
> > >  
> > > +
> > >  static ssize_t nfs4_copy_file_range(struct file *file_in, loff_t
> > > pos_in,
> > >                                     struct file *file_out, loff_t
> > > pos_out,
> > >                                     size_t count, unsigned int
> > > flags)
> > > @@ -461,4 +961,15 @@ const struct file_operations
> > > nfs4_file_operations = {
> > >  #else
> > >         .llseek         = nfs_file_llseek,
> > >  #endif
> > > +       .unlocked_ioctl = nfs4_ioctl,
> > > +};
> > > +
> > > +const struct file_operations nfs4_dir_operations = {
> > > +       .llseek         = nfs_llseek_dir,
> > > +       .read           = generic_read_dir,
> > > +       .iterate_shared = nfs_readdir,
> > > +       .open           = nfs_opendir,
> > > +       .release        = nfs_closedir,
> > > +       .fsync          = nfs_fsync_dir,
> > > +       .unlocked_ioctl = nfs4_ioctl,
> > >  };
> > > diff --git a/fs/nfs/nfs4proc.c b/fs/nfs/nfs4proc.c
> > > index d497616ca149..7c032583ffa2 100644
> > > --- a/fs/nfs/nfs4proc.c
> > > +++ b/fs/nfs/nfs4proc.c
> > > @@ -7959,6 +7959,129 @@ static int _nfs41_proc_get_locations(struct
> > > inode *inode,
> > >  
> > >  #endif /* CONFIG_NFS_V4_1 */
> > >  
> > > +static int _nfs4_set_nfs4_statx(struct inode *inode,
> > > +               struct nfs4_statx *statx,
> > > +               struct nfs_fattr *fattr)
> > > +{
> > > +       const __u64 statx_win = NFS_FA_VALID_TIME_CREATE |
> > > +                               NFS_FA_VALID_TIME_BACKUP |
> > > +                               NFS_FA_VALID_ARCHIVE |
> > > NFS_FA_VALID_HIDDEN |
> > > +                               NFS_FA_VALID_SYSTEM;
> > > +       struct iattr sattr = {0};
> > > +       struct nfs_server *server = NFS_SERVER(inode);
> > > +       __u32 bitmask[3];
> > > +       struct nfs_setattrargs arg = {
> > > +               .fh             = NFS_FH(inode),
> > > +               .iap            = &sattr,
> > > +               .server         = server,
> > > +               .bitmask        = bitmask,
> > > +               .statx          = statx,
> > > +       };
> > > +       struct nfs_setattrres res = {
> > > +               .fattr          = fattr,
> > > +               .server         = server,
> > > +       };
> > > +       struct rpc_message msg = {
> > > +               .rpc_proc       =
> > > &nfs4_procedures[NFSPROC4_CLNT_SETATTR],
> > > +               .rpc_argp       = &arg,
> > > +               .rpc_resp       = &res,
> > > +       };
> > > +       int status;
> > > +
> > > +       nfs4_bitmap_copy_adjust(
> > > +               bitmask, server->attr_bitmask, inode,
> > > +               NFS_INO_INVALID_CHANGE | NFS_INO_INVALID_CTIME |
> > > +                       NFS_INO_INVALID_SIZE |
> > > NFS_INO_INVALID_OTHER |
> > > +                       NFS_INO_INVALID_BTIME |
> > > NFS_INO_INVALID_WINATTR);
> > > +       /* Use the iattr structure to set atime and mtime since
> > > handling already
> > > +        * exists for them using the iattr struct in the
> > > encode_attrs()
> > > +        * (xdr encoding) routine.
> > > +        */
> > > +       if (statx && (statx->fa_valid[0] & NFS_FA_VALID_MTIME)) {
> > > +               sattr.ia_valid |= ATTR_MTIME_SET;
> > > +               sattr.ia_mtime.tv_sec = statx->fa_mtime.tv_sec;
> > > +               sattr.ia_mtime.tv_nsec = statx->fa_mtime.tv_nsec;
> > > +       }
> > > +
> > > +       if (statx && (statx->fa_valid[0] & NFS_FA_VALID_ATIME)) {
> > > +               sattr.ia_valid |= ATTR_ATIME_SET;
> > > +               sattr.ia_atime.tv_sec = statx->fa_atime.tv_sec;
> > > +               sattr.ia_atime.tv_nsec = statx->fa_atime.tv_nsec;
> > > +       }
> > > +
> > > +       if (statx && (statx->fa_valid[0] & NFS_FA_VALID_OWNER)) {
> > > +               sattr.ia_valid |= ATTR_UID;
> > > +               sattr.ia_uid = statx->fa_owner_uid;
> > > +       }
> > > +
> > > +       if (statx && (statx->fa_valid[0] &
> > > NFS_FA_VALID_OWNER_GROUP)) {
> > > +               sattr.ia_valid |= ATTR_GID;
> > > +               sattr.ia_gid = statx->fa_group_gid;
> > > +       }
> > > +
> > > +       if (statx && (statx->fa_valid[0] & NFS_FA_VALID_SIZE)) {
> > > +               sattr.ia_valid |= ATTR_SIZE;
> > > +               sattr.ia_size = statx->fa_size;
> > > +       }
> > > +
> > > +       nfs4_stateid_copy(&arg.stateid, &zero_stateid);
> > > +
> > > +       status = nfs4_call_sync(server->client, server, &msg,
> > > &arg.seq_args, &res.seq_res, 1);
> > > +       if (!status) {
> > > +               if (statx->fa_valid[0] & statx_win) {
> > > +                       struct nfs_inode *nfsi = NFS_I(inode);
> > > +
> > > +                       spin_lock(&inode->i_lock);
> > > +                       if (statx->fa_valid[0] &
> > > NFS_FA_VALID_TIME_CREATE)
> > > +                               nfsi->btime = statx->fa_btime;
> > > +                       if (statx->fa_valid[0] &
> > > NFS_FA_VALID_TIME_BACKUP)
> > > +                               nfsi->timebackup = statx-
> > > >fa_time_backup;
> > > +                       if (statx->fa_valid[0] &
> > > NFS_FA_VALID_ARCHIVE)
> > > +                               nfsi->archive = (statx->fa_flags &
> > > +                                               
> > > NFS_FA_FLAG_ARCHIVE) != 0;
> > > +                       if (statx->fa_valid[0] &
> > > NFS_FA_VALID_SYSTEM)
> > > +                               nfsi->system = (statx->fa_flags &
> > > +                                               NFS_FA_FLAG_SYSTEM)
> > > != 0;
> > > +                       if (statx->fa_valid[0] &
> > > NFS_FA_VALID_HIDDEN)
> > > +                               nfsi->hidden = (statx->fa_flags &
> > > +                                               NFS_FA_FLAG_HIDDEN)
> > > != 0;
> > > +                       if (statx->fa_valid[0] &
> > > NFS_FA_VALID_OFFLINE)
> > > +                               nfsi->offline = (statx->fa_flags &
> > > +                                               
> > > NFS_FA_FLAG_OFFLINE) != 0;
> > > +
> > > +                       nfsi->cache_validity &=
> > > ~NFS_INO_INVALID_CTIME;
> > > +                       if (fattr->valid & NFS_ATTR_FATTR_CTIME)
> > > +                               inode->i_ctime = fattr->ctime;
> > > +                       else
> > > +                               nfs_set_cache_invalid(
> > > +                                       inode,
> > > NFS_INO_INVALID_CHANGE |
> > > +                                                 
> > > NFS_INO_INVALID_CTIME);
> > > +                       spin_unlock(&inode->i_lock);
> > > +               }
> > > +
> > > +               nfs_setattr_update_inode(inode, &sattr, fattr);
> > > +       } else
> > > +               dprintk("%s failed: %d\n", __func__, status);
> > > +
> > > +       return status;
> > > +}
> > > +
> > > +int nfs4_set_nfs4_statx(struct inode *inode,
> > > +               struct nfs4_statx *statx,
> > > +               struct nfs_fattr *fattr)
> > > +{
> > > +       struct nfs4_exception exception = { };
> > > +       struct nfs_server *server = NFS_SERVER(inode);
> > > +       int err;
> > > +
> > > +       do {
> > > +               err = nfs4_handle_exception(server,
> > > +                               _nfs4_set_nfs4_statx(inode, statx,
> > > fattr),
> > > +                               &exception);
> > > +       } while (exception.retry);
> > > +       return err;
> > > +}
> > > +
> > >  /**
> > >   * nfs4_proc_get_locations - discover locations for a migrated
> > > FSID
> > >   * @inode: inode on FSID that is migrating
> > > @@ -10419,6 +10542,7 @@ const struct nfs_rpc_ops nfs_v4_clientops =
> > > {
> > >         .dir_inode_ops  = &nfs4_dir_inode_operations,
> > >         .file_inode_ops = &nfs4_file_inode_operations,
> > >         .file_ops       = &nfs4_file_operations,
> > > +       .dir_ops        = &nfs4_dir_operations,
> > >         .getroot        = nfs4_proc_get_root,
> > >         .submount       = nfs4_submount,
> > >         .try_get_tree   = nfs4_try_get_tree,
> > > diff --git a/fs/nfs/nfs4xdr.c b/fs/nfs/nfs4xdr.c
> > > index d2c240effc87..e5300d7ed712 100644
> > > --- a/fs/nfs/nfs4xdr.c
> > > +++ b/fs/nfs/nfs4xdr.c
> > > @@ -129,12 +129,15 @@ static int decode_layoutget(struct xdr_stream
> > > *xdr, struct rpc_rqst *req,
> > >                                 nfs4_fattr_value_maxsz)
> > >  #define decode_getattr_maxsz    (op_decode_hdr_maxsz +
> > > nfs4_fattr_maxsz)
> > >  #define encode_attrs_maxsz     (nfs4_fattr_bitmap_maxsz + \
> > > -                                1 + 2 + 1 + \
> > > +                                1 + 2 + 1 + 1 + 1 + \
> > >                                 nfs4_owner_maxsz + \
> > >                                 nfs4_group_maxsz + \
> > > -                               nfs4_label_maxsz + \
> > > +                               1 + \
> > > +                               1 + nfstime4_maxsz + \
> > > +                               nfstime4_maxsz + nfstime4_maxsz + \
> > >                                 1 + nfstime4_maxsz + \
> > > -                               1 + nfstime4_maxsz)
> > > +                               nfs4_label_maxsz + \
> > > +                               2)
> > >  #define encode_savefh_maxsz     (op_encode_hdr_maxsz)
> > >  #define decode_savefh_maxsz     (op_decode_hdr_maxsz)
> > >  #define encode_restorefh_maxsz  (op_encode_hdr_maxsz)
> > > @@ -1081,6 +1084,7 @@ xdr_encode_nfstime4(__be32 *p, const struct
> > > timespec64 *t)
> > >  static void encode_attrs(struct xdr_stream *xdr, const struct
> > > iattr *iap,
> > >                                 const struct nfs4_label *label,
> > >                                 const umode_t *umask,
> > > +                               const struct nfs4_statx *statx,
> > >                                 const struct nfs_server *server,
> > >                                 const uint32_t attrmask[])
> > >  {
> > > @@ -1153,6 +1157,34 @@ static void encode_attrs(struct xdr_stream
> > > *xdr, const struct iattr *iap,
> > >                 }
> > >         }
> > >  
> > > +       if (statx && (statx->fa_valid[0] &
> > > NFS_FA_VALID_TIME_BACKUP) &&
> > > +           (attrmask[1] & FATTR4_WORD1_TIME_BACKUP)) {
> > > +               bmval[1] |= FATTR4_WORD1_TIME_BACKUP;
> > > +               len += (nfstime4_maxsz << 2);
> > > +       }
> > > +       if (statx && (statx->fa_valid[0] &
> > > NFS_FA_VALID_TIME_CREATE) &&
> > > +           (attrmask[1] & FATTR4_WORD1_TIME_CREATE)) {
> > > +               bmval[1] |= FATTR4_WORD1_TIME_CREATE;
> > > +               len += (nfstime4_maxsz << 2);
> > > +       }
> > > +
> > > +       if (statx && (statx->fa_valid[0] & NFS_FA_VALID_ARCHIVE) &&
> > > +          (attrmask[0] & FATTR4_WORD0_ARCHIVE)) {
> > > +               bmval[0] |= FATTR4_WORD0_ARCHIVE;
> > > +               len += 4;
> > > +       }
> > > +       if (statx && (statx->fa_valid[0] & NFS_FA_VALID_HIDDEN) &&
> > > +          (attrmask[0] & FATTR4_WORD0_HIDDEN)) {
> > > +               bmval[0] |= FATTR4_WORD0_HIDDEN;
> > > +               len += 4;
> > > +       }
> > > +
> > > +       if (statx && (statx->fa_valid[0] & NFS_FA_VALID_SYSTEM) &&
> > > +          (attrmask[1] & FATTR4_WORD1_SYSTEM)) {
> > > +               bmval[1] |= FATTR4_WORD1_SYSTEM;
> > > +               len += 4;
> > > +       }
> > > +
> > >         if (label && (attrmask[2] & FATTR4_WORD2_SECURITY_LABEL)) {
> > >                 len += 4 + 4 + 4 + (XDR_QUADLEN(label->len) << 2);
> > >                 bmval[2] |= FATTR4_WORD2_SECURITY_LABEL;
> > > @@ -1163,12 +1195,21 @@ static void encode_attrs(struct xdr_stream
> > > *xdr, const struct iattr *iap,
> > >  
> > >         if (bmval[0] & FATTR4_WORD0_SIZE)
> > >                 p = xdr_encode_hyper(p, iap->ia_size);
> > > +       if (bmval[0] & FATTR4_WORD0_ARCHIVE)
> > > +               *p++ = (statx->fa_flags & NFS_FA_FLAG_ARCHIVE) ?
> > > +                       cpu_to_be32(1) : cpu_to_be32(0);
> > > +       if (bmval[0] & FATTR4_WORD0_HIDDEN)
> > > +               *p++ = (statx->fa_flags & NFS_FA_FLAG_HIDDEN) ?
> > > +                       cpu_to_be32(1) : cpu_to_be32(0);
> > >         if (bmval[1] & FATTR4_WORD1_MODE)
> > >                 *p++ = cpu_to_be32(iap->ia_mode & S_IALLUGO);
> > >         if (bmval[1] & FATTR4_WORD1_OWNER)
> > >                 p = xdr_encode_opaque(p, owner_name,
> > > owner_namelen);
> > >         if (bmval[1] & FATTR4_WORD1_OWNER_GROUP)
> > >                 p = xdr_encode_opaque(p, owner_group,
> > > owner_grouplen);
> > > +       if (bmval[1] & FATTR4_WORD1_SYSTEM)
> > > +               *p++ = (statx->fa_flags & NFS_FA_FLAG_SYSTEM) ?
> > > +                       cpu_to_be32(1) : cpu_to_be32(0);
> > >         if (bmval[1] & FATTR4_WORD1_TIME_ACCESS_SET) {
> > >                 if (iap->ia_valid & ATTR_ATIME_SET) {
> > >                         *p++ =
> > > cpu_to_be32(NFS4_SET_TO_CLIENT_TIME);
> > > @@ -1176,6 +1217,10 @@ static void encode_attrs(struct xdr_stream
> > > *xdr, const struct iattr *iap,
> > >                 } else
> > >                         *p++ =
> > > cpu_to_be32(NFS4_SET_TO_SERVER_TIME);
> > >         }
> > > +       if (bmval[1] & FATTR4_WORD1_TIME_BACKUP)
> > > +               p = xdr_encode_nfstime4(p, &statx->fa_time_backup);
> > > +       if (bmval[1] & FATTR4_WORD1_TIME_CREATE)
> > > +               p = xdr_encode_nfstime4(p, &statx->fa_btime);
> > >         if (bmval[1] & FATTR4_WORD1_TIME_MODIFY_SET) {
> > >                 if (iap->ia_valid & ATTR_MTIME_SET) {
> > >                         *p++ =
> > > cpu_to_be32(NFS4_SET_TO_CLIENT_TIME);
> > > @@ -1248,7 +1293,7 @@ static void encode_create(struct xdr_stream
> > > *xdr, const struct nfs4_create_arg *
> > >  
> > >         encode_string(xdr, create->name->len, create->name->name);
> > >         encode_attrs(xdr, create->attrs, create->label, &create-
> > > >umask,
> > > -                       create->server, create->server-
> > > >attr_bitmask);
> > > +                    NULL, create->server, create->server-
> > > >attr_bitmask);
> > >  }
> > >  
> > >  static void encode_getattr(struct xdr_stream *xdr,
> > > @@ -1434,12 +1479,12 @@ static inline void encode_createmode(struct
> > > xdr_stream *xdr, const struct nfs_op
> > >         case NFS4_CREATE_UNCHECKED:
> > >                 *p = cpu_to_be32(NFS4_CREATE_UNCHECKED);
> > >                 encode_attrs(xdr, arg->u.attrs, arg->label, &arg-
> > > >umask,
> > > -                               arg->server, arg->server-
> > > >attr_bitmask);
> > > +                            NULL, arg->server, arg->server-
> > > >attr_bitmask);
> > >                 break;
> > >         case NFS4_CREATE_GUARDED:
> > >                 *p = cpu_to_be32(NFS4_CREATE_GUARDED);
> > >                 encode_attrs(xdr, arg->u.attrs, arg->label, &arg-
> > > >umask,
> > > -                               arg->server, arg->server-
> > > >attr_bitmask);
> > > +                            NULL, arg->server, arg->server-
> > > >attr_bitmask);
> > >                 break;
> > >         case NFS4_CREATE_EXCLUSIVE:
> > >                 *p = cpu_to_be32(NFS4_CREATE_EXCLUSIVE);
> > > @@ -1449,7 +1494,7 @@ static inline void encode_createmode(struct
> > > xdr_stream *xdr, const struct nfs_op
> > >                 *p = cpu_to_be32(NFS4_CREATE_EXCLUSIVE4_1);
> > >                 encode_nfs4_verifier(xdr, &arg->u.verifier);
> > >                 encode_attrs(xdr, arg->u.attrs, arg->label, &arg-
> > > >umask,
> > > -                               arg->server, arg->server-
> > > >exclcreat_bitmask);
> > > +                            NULL, arg->server, arg->server-
> > > >exclcreat_bitmask);
> > >         }
> > >  }
> > >  
> > > @@ -1712,8 +1757,8 @@ static void encode_setattr(struct xdr_stream
> > > *xdr, const struct nfs_setattrargs
> > >  {
> > >         encode_op_hdr(xdr, OP_SETATTR, decode_setattr_maxsz, hdr);
> > >         encode_nfs4_stateid(xdr, &arg->stateid);
> > > -       encode_attrs(xdr, arg->iap, arg->label, NULL, server,
> > > -                       server->attr_bitmask);
> > > +       encode_attrs(xdr, arg->iap, arg->label, NULL, arg->statx,
> > > server,
> > > +                    server->attr_bitmask);
> > >  }
> > >  
> > >  static void encode_setclientid(struct xdr_stream *xdr, const
> > > struct nfs4_setclientid *setclientid, struct compound_hdr *hdr)
> > > diff --git a/fs/nfs/nfstrace.c b/fs/nfs/nfstrace.c
> > > index 5d1bfccbb4da..0b88deb0216e 100644
> > > --- a/fs/nfs/nfstrace.c
> > > +++ b/fs/nfs/nfstrace.c
> > > @@ -9,6 +9,11 @@
> > >  #define CREATE_TRACE_POINTS
> > >  #include "nfstrace.h"
> > >  
> > > +EXPORT_TRACEPOINT_SYMBOL_GPL(nfs_ioctl_file_statx_get_enter);
> > > +EXPORT_TRACEPOINT_SYMBOL_GPL(nfs_ioctl_file_statx_get_exit);
> > > +EXPORT_TRACEPOINT_SYMBOL_GPL(nfs_ioctl_file_statx_set_enter);
> > > +EXPORT_TRACEPOINT_SYMBOL_GPL(nfs_ioctl_file_statx_set_exit);
> > > +
> > >  EXPORT_TRACEPOINT_SYMBOL_GPL(nfs_fsync_enter);
> > >  EXPORT_TRACEPOINT_SYMBOL_GPL(nfs_fsync_exit);
> > >  EXPORT_TRACEPOINT_SYMBOL_GPL(nfs_xdr_status);
> > > diff --git a/fs/nfs/nfstrace.h b/fs/nfs/nfstrace.h
> > > index 2ef7cff8a4ba..b67dd087fb47 100644
> > > --- a/fs/nfs/nfstrace.h
> > > +++ b/fs/nfs/nfstrace.h
> > > @@ -166,6 +166,11 @@ DEFINE_NFS_INODE_EVENT_DONE(nfs_fsync_exit);
> > >  DEFINE_NFS_INODE_EVENT(nfs_access_enter);
> > >  DEFINE_NFS_INODE_EVENT_DONE(nfs_set_cache_invalid);
> > >  
> > > +DEFINE_NFS_INODE_EVENT(nfs_ioctl_file_statx_get_enter);
> > > +DEFINE_NFS_INODE_EVENT_DONE(nfs_ioctl_file_statx_get_exit);
> > > +DEFINE_NFS_INODE_EVENT(nfs_ioctl_file_statx_set_enter);
> > > +DEFINE_NFS_INODE_EVENT_DONE(nfs_ioctl_file_statx_set_exit);
> > > +
> > >  TRACE_EVENT(nfs_access_exit,
> > >                 TP_PROTO(
> > >                         const struct inode *inode,
> > > diff --git a/fs/nfs/proc.c b/fs/nfs/proc.c
> > > index 73dcaa99fa9b..8fd96d93630a 100644
> > > --- a/fs/nfs/proc.c
> > > +++ b/fs/nfs/proc.c
> > > @@ -717,6 +717,7 @@ const struct nfs_rpc_ops nfs_v2_clientops = {
> > >         .dir_inode_ops  = &nfs_dir_inode_operations,
> > >         .file_inode_ops = &nfs_file_inode_operations,
> > >         .file_ops       = &nfs_file_operations,
> > > +       .dir_ops        = &nfs_dir_operations,
> > >         .getroot        = nfs_proc_get_root,
> > >         .submount       = nfs_submount,
> > >         .try_get_tree   = nfs_try_get_tree,
> > > diff --git a/include/linux/nfs_fs.h b/include/linux/nfs_fs.h
> > > index 058fc11338d9..0c3a5859f7f3 100644
> > > --- a/include/linux/nfs_fs.h
> > > +++ b/include/linux/nfs_fs.h
> > > @@ -501,6 +501,7 @@ extern __be32 root_nfs_parse_addr(char *name);
> > > /*__init*/
> > >  extern const struct file_operations nfs_file_operations;
> > >  #if IS_ENABLED(CONFIG_NFS_V4)
> > >  extern const struct file_operations nfs4_file_operations;
> > > +extern const struct file_operations nfs4_dir_operations;
> > >  #endif /* CONFIG_NFS_V4 */
> > >  extern const struct address_space_operations nfs_file_aops;
> > >  extern const struct address_space_operations nfs_dir_aops;
> > > diff --git a/include/linux/nfs_xdr.h b/include/linux/nfs_xdr.h
> > > index 0d5b11c1bfec..9ce61f680a13 100644
> > > --- a/include/linux/nfs_xdr.h
> > > +++ b/include/linux/nfs_xdr.h
> > > @@ -812,6 +812,7 @@ struct nfs_createargs {
> > >         struct iattr *          sattr;
> > >  };
> > >  
> > > +struct nfs4_statx;
> > >  struct nfs_setattrargs {
> > >         struct nfs4_sequence_args       seq_args;
> > >         struct nfs_fh *                 fh;
> > > @@ -820,6 +821,7 @@ struct nfs_setattrargs {
> > >         const struct nfs_server *       server; /* Needed for name
> > > mapping */
> > >         const u32 *                     bitmask;
> > >         const struct nfs4_label         *label;
> > > +       const struct nfs4_statx         *statx;
> > >  };
> > >  
> > >  struct nfs_setaclargs {
> > > @@ -1744,6 +1746,7 @@ struct nfs_rpc_ops {
> > >         const struct inode_operations *dir_inode_ops;
> > >         const struct inode_operations *file_inode_ops;
> > >         const struct file_operations *file_ops;
> > > +       const struct file_operations *dir_ops;
> > >         const struct nlmclnt_operations *nlmclnt_ops;
> > >  
> > >         int     (*getroot) (struct nfs_server *, struct nfs_fh *,
> > > diff --git a/include/uapi/linux/nfs.h b/include/uapi/linux/nfs.h
> > > index 946cb62d64b0..df87da39bc43 100644
> > > --- a/include/uapi/linux/nfs.h
> > > +++ b/include/uapi/linux/nfs.h
> > > @@ -9,6 +9,8 @@
> > >  #define _UAPI_LINUX_NFS_H
> > >  
> > >  #include <linux/types.h>
> > > +#include <asm/byteorder.h>
> > > +#include <linux/time.h>
> > >  
> > >  #define NFS_PROGRAM    100003
> > >  #define NFS_PORT       2049
> > > @@ -35,6 +37,94 @@
> > >  
> > >  #define NFS_PIPE_DIRNAME "nfs"
> > >  
> > > +/* NFS ioctls */
> > > +#define NFS_IOC_FILE_STATX_GET _IOR('N', 2, struct
> > > nfs_ioctl_nfs4_statx)
> > > +#define NFS_IOC_FILE_STATX_SET _IOW('N', 3, struct
> > > nfs_ioctl_nfs4_statx)
> > > +
> > > +/* Options for struct nfs_ioctl_nfs4_statx */
> > > +#define NFS_FA_OPTIONS_SYNC_AS_STAT                    0x0000
> > > +#define NFS_FA_OPTIONS_FORCE_SYNC                      0x2000 /*
> > > See statx */
> > > +#define NFS_FA_OPTIONS_DONT_SYNC                       0x4000 /*
> > > See statx */
> > > +
> > > +#define NFS_FA_VALID_TIME_CREATE                       0x00001UL
> > > +#define NFS_FA_VALID_TIME_BACKUP                       0x00002UL
> > > +#define NFS_FA_VALID_ARCHIVE                           0x00004UL
> > > +#define NFS_FA_VALID_HIDDEN                            0x00008UL
> > > +#define NFS_FA_VALID_SYSTEM                            0x00010UL
> > > +#define NFS_FA_VALID_OWNER                             0x00020UL
> > > +#define NFS_FA_VALID_OWNER_GROUP                       0x00040UL
> > > +#define NFS_FA_VALID_ATIME                             0x00080UL
> > > +#define NFS_FA_VALID_MTIME                             0x00100UL
> > > +#define NFS_FA_VALID_CTIME                             0x00200UL
> > > +#define NFS_FA_VALID_OFFLINE                           0x00400UL
> > > +#define NFS_FA_VALID_MODE                              0x00800UL
> > > +#define NFS_FA_VALID_NLINK                             0x01000UL
> > > +#define NFS_FA_VALID_BLKSIZE                           0x02000UL
> > > +#define NFS_FA_VALID_INO                               0x04000UL
> > > +#define NFS_FA_VALID_DEV                               0x08000UL
> > > +#define NFS_FA_VALID_RDEV                              0x10000UL
> > > +#define NFS_FA_VALID_SIZE                              0x20000UL
> > > +#define NFS_FA_VALID_BLOCKS                            0x40000UL
> > > +
> > > +#define NFS_FA_VALID_ALL_ATTR_0 ( NFS_FA_VALID_TIME_CREATE | \
> > > +               NFS_FA_VALID_TIME_BACKUP | \
> > > +               NFS_FA_VALID_ARCHIVE | \
> > > +               NFS_FA_VALID_HIDDEN | \
> > > +               NFS_FA_VALID_SYSTEM | \
> > > +               NFS_FA_VALID_OWNER | \
> > > +               NFS_FA_VALID_OWNER_GROUP | \
> > > +               NFS_FA_VALID_ATIME | \
> > > +               NFS_FA_VALID_MTIME | \
> > > +               NFS_FA_VALID_CTIME | \
> > > +               NFS_FA_VALID_OFFLINE | \
> > > +               NFS_FA_VALID_MODE | \
> > > +               NFS_FA_VALID_NLINK | \
> > > +               NFS_FA_VALID_BLKSIZE | \
> > > +               NFS_FA_VALID_INO | \
> > > +               NFS_FA_VALID_DEV | \
> > > +               NFS_FA_VALID_RDEV | \
> > > +               NFS_FA_VALID_SIZE | \
> > > +               NFS_FA_VALID_BLOCKS)
> > > +
> > > +#define NFS_FA_FLAG_ARCHIVE                            (1UL << 0)
> > > +#define NFS_FA_FLAG_HIDDEN                             (1UL << 1)
> > > +#define NFS_FA_FLAG_SYSTEM                             (1UL << 2)
> > > +#define NFS_FA_FLAG_OFFLINE                            (1UL << 3)
> > > +
> > > +struct nfs_ioctl_timespec {
> > > +       __s64           tv_sec;
> > > +       __s64           tv_nsec;
> > > +};
> > > +
> > > +struct nfs_ioctl_nfs4_statx {
> > > +       __s32           real_fd;                /* real FD to use,
> > > +                                                  -1 means use
> > > current file */
> > > +       __u32           fa_options;
> > > +
> > > +       __u64           fa_request[2];          /* Attributes to
> > > retrieve */
> > > +       __u64           fa_valid[2];            /* Attributes set
> > > */
> > > +
> > > +       struct nfs_ioctl_timespec fa_time_backup;/* Backup time */
> > > +       struct nfs_ioctl_timespec fa_btime;     /* Birth time */
> > > +       __u64           fa_flags;               /* Flag attributes
> > > */
> > > +       /* Ordinary attributes follow */
> > > +       struct nfs_ioctl_timespec fa_atime;     /* Access time */
> > > +       struct nfs_ioctl_timespec fa_mtime;     /* Modify time */
> > > +       struct nfs_ioctl_timespec fa_ctime;     /* Change time */
> > > +       __u32           fa_owner_uid;           /* Owner User ID */
> > > +       __u32           fa_group_gid;           /* Primary Group ID
> > > */
> > > +       __u32           fa_mode;                /* Mode */
> > > +       __u32           fa_nlink;
> > > +       __u32           fa_blksize;
> > > +       __u32           fa_spare;               /* Alignment */
> > > +       __u64           fa_ino;
> > > +       __u32           fa_dev;
> > > +       __u32           fa_rdev;
> > > +       __s64           fa_size;
> > > +       __s64           fa_blocks;
> > > +       __u64           fa_padding[4];
> > > +};
> > > +
> > >  /*
> > >   * NFS stats. The good thing with these values is that NFSv3
> > > errors are
> > >   * a superset of NFSv2 errors (with the exception of NFSERR_WFLUSH
> > > which
> > > -- 
> > > 2.33.1
> 
> -- 
> Trond Myklebust
> Linux NFS client maintainer, Hammerspace
> trond.myklebust@hammerspace.com
> 
>
diff mbox series

Patch

diff --git a/fs/nfs/dir.c b/fs/nfs/dir.c
index 731d31015b6a..f6fc60822153 100644
--- a/fs/nfs/dir.c
+++ b/fs/nfs/dir.c
@@ -48,11 +48,6 @@ 
 
 /* #define NFS_DEBUG_VERBOSE 1 */
 
-static int nfs_opendir(struct inode *, struct file *);
-static int nfs_closedir(struct inode *, struct file *);
-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 page*);
 
 const struct file_operations nfs_dir_operations = {
@@ -63,6 +58,7 @@  const struct file_operations nfs_dir_operations = {
 	.release	= nfs_closedir,
 	.fsync		= nfs_fsync_dir,
 };
+EXPORT_SYMBOL_GPL(nfs_dir_operations);
 
 const struct address_space_operations nfs_dir_aops = {
 	.freepage = nfs_readdir_clear_array,
@@ -104,8 +100,7 @@  static void put_nfs_open_dir_context(struct inode *dir, struct nfs_open_dir_cont
 /*
  * Open file
  */
-static int
-nfs_opendir(struct inode *inode, struct file *filp)
+int nfs_opendir(struct inode *inode, struct file *filp)
 {
 	int res = 0;
 	struct nfs_open_dir_context *ctx;
@@ -123,13 +118,14 @@  nfs_opendir(struct inode *inode, struct file *filp)
 out:
 	return res;
 }
+EXPORT_SYMBOL_GPL(nfs_opendir);
 
-static int
-nfs_closedir(struct inode *inode, struct file *filp)
+int nfs_closedir(struct inode *inode, struct file *filp)
 {
 	put_nfs_open_dir_context(file_inode(filp), filp->private_data);
 	return 0;
 }
+EXPORT_SYMBOL_GPL(nfs_closedir);
 
 struct nfs_cache_array_entry {
 	u64 cookie;
@@ -1064,7 +1060,7 @@  static int uncached_readdir(struct nfs_readdir_descriptor *desc)
    last cookie cache takes care of the common case of reading the
    whole directory.
  */
-static int nfs_readdir(struct file *file, struct dir_context *ctx)
+int nfs_readdir(struct file *file, struct dir_context *ctx)
 {
 	struct dentry	*dentry = file_dentry(file);
 	struct inode	*inode = d_inode(dentry);
@@ -1157,8 +1153,9 @@  static int nfs_readdir(struct file *file, struct dir_context *ctx)
 	dfprintk(FILE, "NFS: readdir(%pD2) returns %d\n", file, res);
 	return res;
 }
+EXPORT_SYMBOL_GPL(nfs_readdir);
 
-static loff_t nfs_llseek_dir(struct file *filp, loff_t offset, int whence)
+loff_t nfs_llseek_dir(struct file *filp, loff_t offset, int whence)
 {
 	struct nfs_open_dir_context *dir_ctx = filp->private_data;
 
@@ -1196,19 +1193,20 @@  static loff_t nfs_llseek_dir(struct file *filp, loff_t offset, int whence)
 	spin_unlock(&filp->f_lock);
 	return offset;
 }
+EXPORT_SYMBOL_GPL(nfs_llseek_dir);
 
 /*
  * All directory operations under NFS are synchronous, so fsync()
  * is a dummy operation.
  */
-static int nfs_fsync_dir(struct file *filp, loff_t start, loff_t end,
-			 int datasync)
+int nfs_fsync_dir(struct file *filp, loff_t start, loff_t end, int datasync)
 {
 	dfprintk(FILE, "NFS: fsync dir(%pD2) datasync %d\n", filp, datasync);
 
 	nfs_inc_stats(file_inode(filp), NFSIOS_VFSFSYNC);
 	return 0;
 }
+EXPORT_SYMBOL_GPL(nfs_fsync_dir);
 
 /**
  * nfs_force_lookup_revalidate - Mark the directory as having changed
diff --git a/fs/nfs/getroot.c b/fs/nfs/getroot.c
index 11ff2b2e060f..f872970d6240 100644
--- a/fs/nfs/getroot.c
+++ b/fs/nfs/getroot.c
@@ -127,7 +127,8 @@  int nfs_get_root(struct super_block *s, struct fs_context *fc)
 	if (server->caps & NFS_CAP_SECURITY_LABEL)
 		kflags |= SECURITY_LSM_NATIVE_LABELS;
 	if (ctx->clone_data.sb) {
-		if (d_inode(fc->root)->i_fop != &nfs_dir_operations) {
+		if (d_inode(fc->root)->i_fop !=
+		    server->nfs_client->rpc_ops->dir_ops) {
 			error = -ESTALE;
 			goto error_splat_root;
 		}
diff --git a/fs/nfs/inode.c b/fs/nfs/inode.c
index 33f4410190b6..8da662a4953d 100644
--- a/fs/nfs/inode.c
+++ b/fs/nfs/inode.c
@@ -108,6 +108,7 @@  u64 nfs_compat_user_ino64(u64 fileid)
 		ino ^= fileid >> (sizeof(fileid)-sizeof(ino)) * 8;
 	return ino;
 }
+EXPORT_SYMBOL_GPL(nfs_compat_user_ino64);
 
 int nfs_drop_inode(struct inode *inode)
 {
@@ -501,7 +502,7 @@  nfs_fhget(struct super_block *sb, struct nfs_fh *fh, struct nfs_fattr *fattr)
 			nfs_inode_init_regular(nfsi);
 		} else if (S_ISDIR(inode->i_mode)) {
 			inode->i_op = NFS_SB(sb)->nfs_client->rpc_ops->dir_inode_ops;
-			inode->i_fop = &nfs_dir_operations;
+			inode->i_fop = NFS_SB(sb)->nfs_client->rpc_ops->dir_ops;
 			inode->i_data.a_ops = &nfs_dir_aops;
 			nfs_inode_init_dir(nfsi);
 			/* Deal with crossing mountpoints */
@@ -867,6 +868,44 @@  static u32 nfs_get_valid_attrmask(struct inode *inode)
 	return reply_mask;
 }
 
+static int nfs_getattr_revalidate_force(struct dentry *dentry)
+{
+	struct inode *inode = d_inode(dentry);
+	struct nfs_server *server = NFS_SERVER(inode);
+
+	if (!(server->flags & NFS_MOUNT_NOAC))
+		nfs_readdirplus_parent_cache_miss(dentry);
+	else
+		nfs_readdirplus_parent_cache_hit(dentry);
+	return __nfs_revalidate_inode(server, inode);
+}
+
+static int nfs_getattr_revalidate_none(struct dentry *dentry)
+{
+	nfs_readdirplus_parent_cache_hit(dentry);
+	return NFS_STALE(d_inode(dentry)) ? -ESTALE : 0;
+}
+
+static int nfs_getattr_revalidate_maybe(struct dentry *dentry,
+					unsigned long flags)
+{
+	if (nfs_check_cache_invalid(d_inode(dentry), flags))
+		return nfs_getattr_revalidate_force(dentry);
+	return nfs_getattr_revalidate_none(dentry);
+}
+
+int nfs_getattr_revalidate(const struct path *path,
+			   unsigned long flags,
+			   unsigned int query_flags)
+{
+	if (query_flags & AT_STATX_FORCE_SYNC)
+		return nfs_getattr_revalidate_force(path->dentry);
+	if (!(query_flags & AT_STATX_DONT_SYNC))
+		return nfs_getattr_revalidate_maybe(path->dentry, flags);
+	return nfs_getattr_revalidate_none(path->dentry);
+}
+EXPORT_SYMBOL_GPL(nfs_getattr_revalidate);
+
 int nfs_getattr(struct user_namespace *mnt_userns, const struct path *path,
 		struct kstat *stat, u32 request_mask, unsigned int query_flags)
 {
diff --git a/fs/nfs/internal.h b/fs/nfs/internal.h
index 12f6acb483bb..9602a886f0f0 100644
--- a/fs/nfs/internal.h
+++ b/fs/nfs/internal.h
@@ -366,6 +366,12 @@  extern struct nfs_client *nfs_init_client(struct nfs_client *clp,
 			   const struct nfs_client_initdata *);
 
 /* dir.c */
+int nfs_opendir(struct inode *, struct file *);
+int nfs_closedir(struct inode *, struct file *);
+int nfs_readdir(struct file *file, struct dir_context *ctx);
+int nfs_fsync_dir(struct file *, loff_t, loff_t, int);
+loff_t nfs_llseek_dir(struct file *filp, loff_t offset, int whence);
+
 extern void nfs_advise_use_readdirplus(struct inode *dir);
 extern void nfs_force_use_readdirplus(struct inode *dir);
 extern unsigned long nfs_access_cache_count(struct shrinker *shrink,
@@ -411,6 +417,8 @@  extern void nfs_set_cache_invalid(struct inode *inode, unsigned long flags);
 extern bool nfs_check_cache_invalid(struct inode *, unsigned long);
 extern int nfs_wait_bit_killable(struct wait_bit_key *key, int mode);
 extern int nfs_wait_atomic_killable(atomic_t *p, unsigned int mode);
+extern int nfs_getattr_revalidate(const struct path *path, unsigned long flags,
+				  unsigned int query_flags);
 
 /* super.c */
 extern const struct super_operations nfs_sops;
diff --git a/fs/nfs/nfs3proc.c b/fs/nfs/nfs3proc.c
index 7100514d306b..091005e169b7 100644
--- a/fs/nfs/nfs3proc.c
+++ b/fs/nfs/nfs3proc.c
@@ -1018,6 +1018,7 @@  const struct nfs_rpc_ops nfs_v3_clientops = {
 	.dir_inode_ops	= &nfs3_dir_inode_operations,
 	.file_inode_ops	= &nfs3_file_inode_operations,
 	.file_ops	= &nfs_file_operations,
+	.dir_ops	= &nfs_dir_operations,
 	.nlmclnt_ops	= &nlmclnt_fl_close_lock_ops,
 	.getroot	= nfs3_proc_get_root,
 	.submount	= nfs_submount,
diff --git a/fs/nfs/nfs4_fs.h b/fs/nfs/nfs4_fs.h
index ed5eaca6801e..9f21d8520e99 100644
--- a/fs/nfs/nfs4_fs.h
+++ b/fs/nfs/nfs4_fs.h
@@ -248,6 +248,34 @@  struct nfs4_opendata {
 	int rpc_status;
 };
 
+struct nfs4_statx {
+	int		real_fd;		/* real FD to use,
+						   -1 means use current file */
+	__u32		fa_options;		/* statx flags */
+	__u64		fa_request[2];		/* Attributes requested */
+	__u64		fa_valid[2];		/* Attributes set */
+
+	struct timespec64 fa_time_backup;	/* Backup time */
+	struct timespec64 fa_btime;		/* Birth time */
+	/* Flag attributes */
+	__u64 fa_flags;
+	struct timespec64 fa_atime;		/* Access time */
+	struct timespec64 fa_mtime;		/* Modify time */
+	struct timespec64 fa_ctime;		/* Change time */
+	kuid_t		fa_owner_uid;		/* Owner User ID */
+	kgid_t		fa_group_gid;		/* Primary Group ID */
+        /* Normal stat fields after this */
+	__u32	 	fa_mode;		/* Mode */
+	unsigned int 	fa_nlink;
+	__u32		fa_blksize;
+	__u32		fa_spare;		/* Alignment */
+	__u64		fa_ino;
+	dev_t		fa_dev;
+	dev_t		fa_rdev;
+	loff_t		fa_size;
+	__u64		fa_blocks;
+};
+
 struct nfs4_add_xprt_data {
 	struct nfs_client	*clp;
 	const struct cred	*cred;
@@ -315,6 +343,9 @@  extern int nfs4_set_rw_stateid(nfs4_stateid *stateid,
 		const struct nfs_open_context *ctx,
 		const struct nfs_lock_context *l_ctx,
 		fmode_t fmode);
+int nfs4_set_nfs4_statx(struct inode *inode,
+		struct nfs4_statx *statx,
+		struct nfs_fattr *fattr);
 extern int nfs4_proc_getattr(struct nfs_server *server, struct nfs_fh *fhandle,
 			     struct nfs_fattr *fattr, struct inode *inode);
 extern int update_open_stateid(struct nfs4_state *state,
diff --git a/fs/nfs/nfs4file.c b/fs/nfs/nfs4file.c
index e79ae4cbc395..494ebc7cd1c0 100644
--- a/fs/nfs/nfs4file.c
+++ b/fs/nfs/nfs4file.c
@@ -9,6 +9,8 @@ 
 #include <linux/falloc.h>
 #include <linux/mount.h>
 #include <linux/nfs_fs.h>
+#include <linux/time32.h>
+#include <linux/compat.h>
 #include <linux/nfs_ssc.h>
 #include "delegation.h"
 #include "internal.h"
@@ -132,6 +134,503 @@  nfs4_file_flush(struct file *file, fl_owner_t id)
 	return filemap_check_wb_err(file->f_mapping, since);
 }
 
+static int nfs_get_timespec64(struct timespec64 *ts,
+			      const struct nfs_ioctl_timespec __user *uts)
+{
+	__s64 dummy;
+	if (unlikely(get_user(dummy, &uts->tv_sec) != 0))
+		return EFAULT;
+	ts->tv_sec = dummy;
+	if (unlikely(get_user(dummy, &uts->tv_nsec) != 0))
+		return EFAULT;
+	ts->tv_nsec = dummy;
+	return 0;
+}
+
+static int nfs_put_timespec64(const struct timespec64 *ts,
+			      struct nfs_ioctl_timespec __user *uts)
+{
+	__s64 dummy;
+
+	dummy = ts->tv_sec;
+	if (unlikely(put_user(dummy, &uts->tv_sec) != 0))
+		return EFAULT;
+	dummy = ts->tv_nsec;
+	if (unlikely(put_user(dummy, &uts->tv_nsec) != 0))
+		return EFAULT;
+	return 0;
+}
+
+static struct file *nfs4_get_real_file(struct file *src, unsigned int fd)
+{
+	struct file *filp = fget_raw(fd);
+	int ret = -EBADF;
+
+	if (!filp)
+		goto out;
+	/* Validate that the files share the same underlying filesystem */
+	ret = -EXDEV;
+	if (file_inode(filp)->i_sb != file_inode(src)->i_sb)
+		goto out_put;
+	return filp;
+out_put:
+	fput(filp);
+out:
+	return ERR_PTR(ret);
+}
+
+static unsigned long nfs4_statx_request_to_cache_validity(__u64 request,
+							  u64 fattr_supported)
+{
+	unsigned long ret = 0;
+
+	if (request & NFS_FA_VALID_ATIME)
+		ret |= NFS_INO_INVALID_ATIME;
+	if (request & NFS_FA_VALID_CTIME)
+		ret |= NFS_INO_INVALID_CTIME;
+	if (request & NFS_FA_VALID_MTIME)
+		ret |= NFS_INO_INVALID_MTIME;
+	if (request & NFS_FA_VALID_SIZE)
+		ret |= NFS_INO_INVALID_SIZE;
+
+	if (request & NFS_FA_VALID_MODE)
+		ret |= NFS_INO_INVALID_MODE;
+	if (request & (NFS_FA_VALID_OWNER | NFS_FA_VALID_OWNER_GROUP))
+		ret |= NFS_INO_INVALID_OTHER;
+
+	if (request & NFS_FA_VALID_NLINK)
+		ret |= NFS_INO_INVALID_NLINK;
+	if (request & NFS_FA_VALID_BLOCKS)
+		ret |= NFS_INO_INVALID_BLOCKS;
+
+	if (request & NFS_FA_VALID_TIME_CREATE)
+		ret |= NFS_INO_INVALID_BTIME;
+
+	if (request & NFS_FA_VALID_ARCHIVE) {
+		if (fattr_supported & NFS_ATTR_FATTR_ARCHIVE)
+			ret |= NFS_INO_INVALID_WINATTR;
+		else if (fattr_supported & NFS_ATTR_FATTR_TIME_BACKUP)
+			ret |= NFS_INO_INVALID_WINATTR | NFS_INO_INVALID_MTIME;
+	}
+	if (request & (NFS_FA_VALID_TIME_BACKUP | NFS_FA_VALID_HIDDEN |
+		       NFS_FA_VALID_SYSTEM | NFS_FA_VALID_OFFLINE))
+		ret |= NFS_INO_INVALID_WINATTR;
+
+	return ret ? (ret | NFS_INO_INVALID_CHANGE) : 0;
+}
+
+static long nfs4_ioctl_file_statx_get(struct file *dst_file,
+				      struct nfs_ioctl_nfs4_statx __user *uarg)
+{
+	struct nfs4_statx args = {
+		.real_fd = -1,
+		.fa_valid = { 0 },
+	};
+	struct inode *inode;
+	struct nfs_inode *nfsi;
+	struct nfs_server *server;
+	u64 fattr_supported;
+	unsigned long reval_attr;
+	unsigned int reval_flags;
+	__u32 tmp;
+	int ret;
+
+	/*
+	 * We get the first word from the uarg as it tells us whether
+	 * to use the passed in struct file or use that fd to find the
+	 * struct file.
+	 */
+	if (get_user(args.real_fd, &uarg->real_fd))
+		return -EFAULT;
+
+	if (get_user(args.fa_options, &uarg->fa_options))
+		return -EFAULT;
+
+	if (get_user(args.fa_request[0], &uarg->fa_request[0]))
+		return -EFAULT;
+
+	if (args.real_fd >= 0) {
+		dst_file = nfs4_get_real_file(dst_file, args.real_fd);
+		if (IS_ERR(dst_file))
+			return PTR_ERR(dst_file);
+	}
+
+	/*
+	 * Backward compatibility: we stole the top 32 bits of 'real_fd'
+	 * to create the fa_options field, so if its value is -1, then
+	 * assume it is the high word of (__s64)real_fd == -1, and just
+	 * set it to zero.
+	 */
+	if (args.fa_options == 0xFFFF)
+		args.fa_options = 0;
+
+	inode = file_inode(dst_file);
+	nfsi = NFS_I(inode);
+	server = NFS_SERVER(inode);
+	fattr_supported = server->fattr_valid;
+
+	trace_nfs_ioctl_file_statx_get_enter(inode);
+
+	if (args.fa_options & NFS_FA_OPTIONS_FORCE_SYNC)
+		reval_flags = AT_STATX_FORCE_SYNC;
+	else if (args.fa_options & NFS_FA_OPTIONS_DONT_SYNC)
+		reval_flags = AT_STATX_DONT_SYNC;
+	else
+		reval_flags = AT_STATX_SYNC_AS_STAT;
+
+	reval_attr = nfs4_statx_request_to_cache_validity(args.fa_request[0],
+							  fattr_supported);
+
+	if ((reval_attr & (NFS_INO_INVALID_CTIME | NFS_INO_INVALID_MTIME)) &&
+	    reval_flags != AT_STATX_DONT_SYNC && S_ISREG(inode->i_mode)) {
+		ret = filemap_write_and_wait(inode->i_mapping);
+		if (ret)
+			goto out;
+	}
+
+	if ((dst_file->f_path.mnt->mnt_flags & MNT_NOATIME) ||
+	    ((dst_file->f_path.mnt->mnt_flags & MNT_NODIRATIME) &&
+	     S_ISDIR(inode->i_mode)))
+		reval_attr &= ~NFS_INO_INVALID_ATIME;
+
+	ret = nfs_getattr_revalidate(&dst_file->f_path, reval_attr,
+				     reval_flags);
+	if (ret != 0)
+		goto out;
+
+	ret = -EFAULT;
+	if ((fattr_supported & NFS_ATTR_FATTR_OWNER) &&
+	    (args.fa_request[0] & NFS_FA_VALID_OWNER)) {
+		tmp = from_kuid_munged(current_user_ns(), inode->i_uid);
+		if (unlikely(put_user(tmp, &uarg->fa_owner_uid) != 0))
+			goto out;
+		args.fa_valid[0] |= NFS_FA_VALID_OWNER;
+	}
+
+	if ((fattr_supported & NFS_ATTR_FATTR_GROUP) &&
+	    (args.fa_request[0] & NFS_FA_VALID_OWNER_GROUP)) {
+		tmp = from_kgid_munged(current_user_ns(), inode->i_gid);
+		if (unlikely(put_user(tmp, &uarg->fa_group_gid) != 0))
+			goto out;
+		args.fa_valid[0] |= NFS_FA_VALID_OWNER_GROUP;
+	}
+
+	if ((fattr_supported & NFS_ATTR_FATTR_TIME_BACKUP) &&
+	    (args.fa_request[0] & NFS_FA_VALID_TIME_BACKUP)) {
+		if (nfs_put_timespec64(&nfsi->timebackup, &uarg->fa_time_backup))
+			goto out;
+		args.fa_valid[0] |= NFS_FA_VALID_TIME_BACKUP;
+	}
+
+	if ((fattr_supported & NFS_ATTR_FATTR_BTIME) &&
+	    (args.fa_request[0] & NFS_FA_VALID_TIME_CREATE)) {
+		if (nfs_put_timespec64(&nfsi->btime, &uarg->fa_btime))
+			goto out;
+		args.fa_valid[0] |= NFS_FA_VALID_TIME_CREATE;
+	}
+
+	/* atime, mtime, and ctime are all stored in the regular inode,
+	 * not the nfs inode.
+	 */
+	if ((fattr_supported & NFS_ATTR_FATTR_ATIME) &&
+	    (args.fa_request[0] & NFS_FA_VALID_ATIME)) {
+		if (nfs_put_timespec64(&inode->i_atime, &uarg->fa_atime))
+			goto out;
+		args.fa_valid[0] |= NFS_FA_VALID_ATIME;
+	}
+
+	if ((fattr_supported & NFS_ATTR_FATTR_MTIME) &&
+	    (args.fa_request[0] & NFS_FA_VALID_MTIME)) {
+		if (nfs_put_timespec64(&inode->i_mtime, &uarg->fa_mtime))
+                        goto out;
+		args.fa_valid[0] |= NFS_FA_VALID_MTIME;
+	}
+
+	if ((fattr_supported & NFS_ATTR_FATTR_CTIME) &&
+	    (args.fa_request[0] & NFS_FA_VALID_CTIME)) {
+		if (nfs_put_timespec64(&inode->i_ctime, &uarg->fa_ctime))
+			goto out;
+		args.fa_valid[0] |= NFS_FA_VALID_CTIME;
+	}
+
+        /*
+         * It looks like PDFS does not support or properly handle the
+         * archive bit.
+         */
+	if ((fattr_supported & NFS_ATTR_FATTR_ARCHIVE) &&
+	    (args.fa_request[0] & NFS_FA_VALID_ARCHIVE)) {
+		if (nfsi->archive)
+			args.fa_flags |= NFS_FA_FLAG_ARCHIVE;
+		args.fa_valid[0] |= NFS_FA_VALID_ARCHIVE;
+	}
+
+	if ((fattr_supported & NFS_ATTR_FATTR_TIME_BACKUP) &&
+	    (args.fa_request[0] & NFS_FA_VALID_ARCHIVE)) {
+		if (timespec64_compare(&inode->i_mtime, &nfsi->timebackup) > 0)
+			args.fa_flags |= NFS_FA_FLAG_ARCHIVE;
+		args.fa_valid[0] |= NFS_FA_VALID_ARCHIVE;
+	}
+
+	if ((fattr_supported & NFS_ATTR_FATTR_HIDDEN) &&
+	    (args.fa_request[0] & NFS_FA_VALID_HIDDEN)) {
+		if (nfsi->hidden)
+			args.fa_flags |= NFS_FA_FLAG_HIDDEN;
+		args.fa_valid[0] |= NFS_FA_VALID_HIDDEN;
+	}
+	if ((fattr_supported & NFS_ATTR_FATTR_SYSTEM) &&
+	    (args.fa_request[0] & NFS_FA_VALID_SYSTEM)) {
+		if (nfsi->system)
+			args.fa_flags |= NFS_FA_FLAG_SYSTEM;
+		args.fa_valid[0] |= NFS_FA_VALID_SYSTEM;
+	}
+
+	if ((fattr_supported & NFS_ATTR_FATTR_OFFLINE) &&
+	    (args.fa_request[0] & NFS_FA_VALID_OFFLINE)) {
+		if (nfsi->offline)
+			args.fa_flags |= NFS_FA_FLAG_OFFLINE;
+		args.fa_valid[0] |= NFS_FA_VALID_OFFLINE;
+	}
+
+	if ((args.fa_valid[0] & (NFS_FA_VALID_ARCHIVE |
+				NFS_FA_VALID_HIDDEN |
+				NFS_FA_VALID_SYSTEM |
+				NFS_FA_VALID_OFFLINE)) &&
+	    put_user(args.fa_flags, &uarg->fa_flags))
+		goto out;
+
+	if ((fattr_supported & NFS_ATTR_FATTR_MODE) &&
+	    (args.fa_request[0] & NFS_FA_VALID_MODE)) {
+		tmp = inode->i_mode;
+		/* This is an unsigned short we put into an __u32 */
+		if (unlikely(put_user(tmp, &uarg->fa_mode) != 0))
+			goto out;
+		args.fa_valid[0] |= NFS_FA_VALID_MODE;
+	}
+
+	if ((fattr_supported & NFS_ATTR_FATTR_NLINK) &&
+	    (args.fa_request[0] & NFS_FA_VALID_NLINK)) {
+		tmp = inode->i_nlink;
+		if (unlikely(put_user(tmp, &uarg->fa_nlink) != 0))
+			goto out;
+		args.fa_valid[0] |= NFS_FA_VALID_NLINK;
+	}
+
+	if (args.fa_request[0] & NFS_FA_VALID_BLKSIZE) {
+		tmp = i_blocksize(inode);
+		if (S_ISDIR(inode->i_mode))
+			tmp = NFS_SERVER(inode)->dtsize;
+		if (unlikely(put_user(tmp, &uarg->fa_blksize) != 0))
+			goto out;
+		args.fa_valid[0] |= NFS_FA_VALID_BLKSIZE;
+	}
+
+	if (args.fa_request[0] & NFS_FA_VALID_INO) {
+		__u64 ino = nfs_compat_user_ino64(NFS_FILEID(inode));
+		if (unlikely(put_user(ino, &uarg->fa_ino) != 0))
+			goto out;
+		args.fa_valid[0] |= NFS_FA_VALID_INO;
+	}
+
+	if (args.fa_request[0] & NFS_FA_VALID_DEV) {
+		tmp = inode->i_sb->s_dev;
+		if (unlikely(put_user(tmp, &uarg->fa_dev) != 0))
+			goto out;
+		args.fa_valid[0] |= NFS_FA_VALID_DEV;
+	}
+
+	if ((fattr_supported & NFS_ATTR_FATTR_RDEV) &&
+	    (args.fa_request[0] & NFS_FA_VALID_RDEV)) {
+		tmp = inode->i_rdev;
+		if (unlikely(put_user(tmp, &uarg->fa_rdev) != 0))
+			goto out;
+		args.fa_valid[0] |= NFS_FA_VALID_RDEV;
+	}
+
+	if ((fattr_supported & NFS_ATTR_FATTR_SIZE) &&
+	    (args.fa_request[0] & NFS_FA_VALID_SIZE)) {
+		__s64 size = i_size_read(inode);
+		if (unlikely(put_user(size, &uarg->fa_size) != 0))
+			goto out;
+		args.fa_valid[0] |= NFS_FA_VALID_SIZE;
+	}
+
+	if ((fattr_supported &
+	     (NFS_ATTR_FATTR_BLOCKS_USED | NFS_ATTR_FATTR_SPACE_USED)) &&
+	    (args.fa_request[0] & NFS_FA_VALID_BLOCKS)) {
+		__s64 blocks = inode->i_blocks;
+		if (unlikely(put_user(blocks, &uarg->fa_blocks) != 0))
+			goto out;
+		args.fa_valid[0] |= NFS_FA_VALID_BLOCKS;
+	}
+
+	if (unlikely(put_user(args.fa_valid[0], &uarg->fa_valid[0]) != 0))
+		goto out;
+	if (unlikely(put_user(args.fa_valid[1], &uarg->fa_valid[1]) != 0))
+		goto out;
+
+	ret = 0;
+out:
+	if (args.real_fd >= 0)
+		fput(dst_file);
+	trace_nfs_ioctl_file_statx_get_exit(inode, ret);
+	return ret;
+}
+
+static long nfs4_ioctl_file_statx_set(struct file *dst_file,
+				      struct nfs_ioctl_nfs4_statx __user *uarg)
+{
+	struct nfs4_statx args = {
+		.real_fd = -1,
+		.fa_valid = { 0 },
+	};
+	struct nfs_fattr *fattr = nfs_alloc_fattr();
+	struct inode *inode;
+	/*
+	 * If you need a different error code below, you need to set it
+	 */
+	int ret = -EFAULT;
+
+	if (fattr == NULL)
+		return -ENOMEM;
+
+	/*
+	 * We get the first u64 word from the uarg as it tells us whether
+	 * to use the passed in struct file or use that fd to find the
+	 * struct file.
+	 */
+	if (get_user(args.real_fd, &uarg->real_fd))
+		goto out_free;
+
+	if (args.real_fd >= 0) {
+		dst_file = nfs4_get_real_file(dst_file, args.real_fd);
+		if (IS_ERR(dst_file)) {
+			ret = PTR_ERR(dst_file);
+			goto out_free;
+		}
+	}
+	inode = file_inode(dst_file);
+	trace_nfs_ioctl_file_statx_set_enter(inode);
+
+	inode_lock(inode);
+
+	/* Write all dirty data */
+	if (S_ISREG(inode->i_mode)) {
+		ret = nfs_sync_inode(inode);
+		if (ret)
+			goto out;
+	}
+
+	ret = -EFAULT;
+	if (get_user(args.fa_valid[0], &uarg->fa_valid[0]))
+		goto out;
+	args.fa_valid[0] &= NFS_FA_VALID_ALL_ATTR_0;
+
+	if (args.fa_valid[0] & NFS_FA_VALID_OWNER) {
+		uid_t uid;
+
+		if (unlikely(get_user(uid, &uarg->fa_owner_uid) != 0))
+			goto out;
+		args.fa_owner_uid = make_kuid(current_user_ns(), uid);
+		if (!uid_valid(args.fa_owner_uid)) {
+			ret = -EINVAL;
+			goto out;
+		}
+	}
+
+	if (args.fa_valid[0] & NFS_FA_VALID_OWNER_GROUP) {
+		gid_t gid;
+
+		if (unlikely(get_user(gid, &uarg->fa_group_gid) != 0))
+			goto out;
+		args.fa_group_gid = make_kgid(current_user_ns(), gid);
+		if (!gid_valid(args.fa_group_gid)) {
+			ret = -EINVAL;
+			goto out;
+		}
+	}
+
+	if ((args.fa_valid[0] & (NFS_FA_VALID_ARCHIVE |
+					NFS_FA_VALID_HIDDEN |
+					NFS_FA_VALID_SYSTEM)) &&
+	    get_user(args.fa_flags, &uarg->fa_flags))
+		goto out;
+
+	if ((args.fa_valid[0] & NFS_FA_VALID_TIME_CREATE) &&
+	    nfs_get_timespec64(&args.fa_btime, &uarg->fa_btime))
+		goto out;
+
+	if ((args.fa_valid[0] & NFS_FA_VALID_ATIME) &&
+	    nfs_get_timespec64(&args.fa_atime, &uarg->fa_atime))
+		goto out;
+
+	if ((args.fa_valid[0] & NFS_FA_VALID_MTIME) &&
+	    nfs_get_timespec64(&args.fa_mtime, &uarg->fa_mtime))
+		goto out;
+
+	if (args.fa_valid[0] & NFS_FA_VALID_TIME_BACKUP) {
+		if (nfs_get_timespec64(&args.fa_time_backup, &uarg->fa_time_backup))
+			goto out;
+	} else if ((args.fa_valid[0] & NFS_FA_VALID_ARCHIVE) &&
+			!(NFS_SERVER(inode)->fattr_valid & NFS_ATTR_FATTR_ARCHIVE)) {
+		args.fa_valid[0] |= NFS_FA_VALID_TIME_BACKUP;
+		if (!(args.fa_flags & NFS_FA_FLAG_ARCHIVE)) {
+			nfs_revalidate_inode(inode, NFS_INO_INVALID_MTIME);
+			args.fa_time_backup.tv_sec = inode->i_mtime.tv_sec;
+			args.fa_time_backup.tv_nsec = inode->i_mtime.tv_nsec;
+		} else if (args.fa_valid[0] & NFS_FA_VALID_TIME_CREATE)
+			args.fa_time_backup = args.fa_btime;
+		else {
+			nfs_revalidate_inode(inode, NFS_INO_INVALID_BTIME);
+			args.fa_time_backup = NFS_I(inode)->btime;
+		}
+	}
+
+        if (args.fa_valid[0] & NFS_FA_VALID_SIZE) {
+		if (copy_from_user(&args.fa_size, &uarg->fa_size,
+					sizeof(args.fa_size)))
+			goto out;
+		ret = inode_newsize_ok(inode,args.fa_size);
+		if (ret)
+			goto out;
+		if (args.fa_size == i_size_read(inode))
+			args.fa_valid[0] &= ~NFS_FA_VALID_SIZE;
+	}
+
+	/*
+	 * No need to update the inode because that is done in nfs4_set_nfs4_statx
+	 */
+	ret = nfs4_set_nfs4_statx(inode, &args, fattr);
+
+out:
+	inode_unlock(inode);
+	if (args.real_fd >= 0)
+		fput(dst_file);
+	trace_nfs_ioctl_file_statx_set_exit(inode, ret);
+out_free:
+	nfs_free_fattr(fattr);
+	return ret;
+}
+
+static long nfs4_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+	void __user *argp = (void __user *)arg;
+	long ret;
+
+	switch (cmd) {
+	case NFS_IOC_FILE_STATX_GET:
+		ret = nfs4_ioctl_file_statx_get(file, argp);
+		break;
+	case NFS_IOC_FILE_STATX_SET:
+		ret = nfs4_ioctl_file_statx_set(file, argp);
+		break;
+	default:
+		ret = -ENOIOCTLCMD;
+	}
+
+	dprintk("%s: file=%pD2, cmd=%u, ret=%ld\n", __func__, file, cmd, ret);
+	return ret;
+}
+
 #ifdef CONFIG_NFS_V4_2
 static ssize_t __nfs4_copy_file_range(struct file *file_in, loff_t pos_in,
 				      struct file *file_out, loff_t pos_out,
@@ -187,6 +686,7 @@  static ssize_t __nfs4_copy_file_range(struct file *file_in, loff_t pos_in,
 	return ret;
 }
 
+
 static ssize_t nfs4_copy_file_range(struct file *file_in, loff_t pos_in,
 				    struct file *file_out, loff_t pos_out,
 				    size_t count, unsigned int flags)
@@ -461,4 +961,15 @@  const struct file_operations nfs4_file_operations = {
 #else
 	.llseek		= nfs_file_llseek,
 #endif
+	.unlocked_ioctl	= nfs4_ioctl,
+};
+
+const struct file_operations nfs4_dir_operations = {
+	.llseek		= nfs_llseek_dir,
+	.read		= generic_read_dir,
+	.iterate_shared	= nfs_readdir,
+	.open		= nfs_opendir,
+	.release	= nfs_closedir,
+	.fsync		= nfs_fsync_dir,
+	.unlocked_ioctl	= nfs4_ioctl,
 };
diff --git a/fs/nfs/nfs4proc.c b/fs/nfs/nfs4proc.c
index d497616ca149..7c032583ffa2 100644
--- a/fs/nfs/nfs4proc.c
+++ b/fs/nfs/nfs4proc.c
@@ -7959,6 +7959,129 @@  static int _nfs41_proc_get_locations(struct inode *inode,
 
 #endif	/* CONFIG_NFS_V4_1 */
 
+static int _nfs4_set_nfs4_statx(struct inode *inode,
+		struct nfs4_statx *statx,
+		struct nfs_fattr *fattr)
+{
+	const __u64 statx_win = NFS_FA_VALID_TIME_CREATE |
+				NFS_FA_VALID_TIME_BACKUP |
+				NFS_FA_VALID_ARCHIVE | NFS_FA_VALID_HIDDEN |
+				NFS_FA_VALID_SYSTEM;
+	struct iattr sattr = {0};
+	struct nfs_server *server = NFS_SERVER(inode);
+	__u32 bitmask[3];
+	struct nfs_setattrargs arg = {
+		.fh             = NFS_FH(inode),
+		.iap            = &sattr,
+		.server		= server,
+		.bitmask	= bitmask,
+		.statx		= statx,
+	};
+	struct nfs_setattrres res = {
+		.fattr		= fattr,
+		.server		= server,
+	};
+	struct rpc_message msg = {
+		.rpc_proc       = &nfs4_procedures[NFSPROC4_CLNT_SETATTR],
+		.rpc_argp       = &arg,
+		.rpc_resp       = &res,
+	};
+	int status;
+
+	nfs4_bitmap_copy_adjust(
+		bitmask, server->attr_bitmask, inode,
+		NFS_INO_INVALID_CHANGE | NFS_INO_INVALID_CTIME |
+			NFS_INO_INVALID_SIZE | NFS_INO_INVALID_OTHER |
+			NFS_INO_INVALID_BTIME | NFS_INO_INVALID_WINATTR);
+	/* Use the iattr structure to set atime and mtime since handling already
+	 * exists for them using the iattr struct in the encode_attrs()
+	 * (xdr encoding) routine.
+	 */
+	if (statx && (statx->fa_valid[0] & NFS_FA_VALID_MTIME)) {
+		sattr.ia_valid |= ATTR_MTIME_SET;
+		sattr.ia_mtime.tv_sec = statx->fa_mtime.tv_sec;
+		sattr.ia_mtime.tv_nsec = statx->fa_mtime.tv_nsec;
+	}
+
+	if (statx && (statx->fa_valid[0] & NFS_FA_VALID_ATIME)) {
+		sattr.ia_valid |= ATTR_ATIME_SET;
+		sattr.ia_atime.tv_sec = statx->fa_atime.tv_sec;
+		sattr.ia_atime.tv_nsec = statx->fa_atime.tv_nsec;
+	}
+
+	if (statx && (statx->fa_valid[0] & NFS_FA_VALID_OWNER)) {
+		sattr.ia_valid |= ATTR_UID;
+		sattr.ia_uid = statx->fa_owner_uid;
+	}
+
+	if (statx && (statx->fa_valid[0] & NFS_FA_VALID_OWNER_GROUP)) {
+		sattr.ia_valid |= ATTR_GID;
+		sattr.ia_gid = statx->fa_group_gid;
+	}
+
+	if (statx && (statx->fa_valid[0] & NFS_FA_VALID_SIZE)) {
+		sattr.ia_valid |= ATTR_SIZE;
+		sattr.ia_size = statx->fa_size;
+	}
+
+	nfs4_stateid_copy(&arg.stateid, &zero_stateid);
+
+	status = nfs4_call_sync(server->client, server, &msg, &arg.seq_args, &res.seq_res, 1);
+	if (!status) {
+		if (statx->fa_valid[0] & statx_win) {
+			struct nfs_inode *nfsi = NFS_I(inode);
+
+			spin_lock(&inode->i_lock);
+			if (statx->fa_valid[0] & NFS_FA_VALID_TIME_CREATE)
+				nfsi->btime = statx->fa_btime;
+			if (statx->fa_valid[0] & NFS_FA_VALID_TIME_BACKUP)
+				nfsi->timebackup = statx->fa_time_backup;
+			if (statx->fa_valid[0] & NFS_FA_VALID_ARCHIVE)
+				nfsi->archive = (statx->fa_flags &
+						 NFS_FA_FLAG_ARCHIVE) != 0;
+			if (statx->fa_valid[0] & NFS_FA_VALID_SYSTEM)
+				nfsi->system = (statx->fa_flags &
+						NFS_FA_FLAG_SYSTEM) != 0;
+			if (statx->fa_valid[0] & NFS_FA_VALID_HIDDEN)
+				nfsi->hidden = (statx->fa_flags &
+						NFS_FA_FLAG_HIDDEN) != 0;
+			if (statx->fa_valid[0] & NFS_FA_VALID_OFFLINE)
+				nfsi->offline = (statx->fa_flags &
+						 NFS_FA_FLAG_OFFLINE) != 0;
+
+			nfsi->cache_validity &= ~NFS_INO_INVALID_CTIME;
+			if (fattr->valid & NFS_ATTR_FATTR_CTIME)
+				inode->i_ctime = fattr->ctime;
+			else
+				nfs_set_cache_invalid(
+					inode, NFS_INO_INVALID_CHANGE |
+						   NFS_INO_INVALID_CTIME);
+			spin_unlock(&inode->i_lock);
+		}
+
+		nfs_setattr_update_inode(inode, &sattr, fattr);
+	} else
+		dprintk("%s failed: %d\n", __func__, status);
+
+	return status;
+}
+
+int nfs4_set_nfs4_statx(struct inode *inode,
+		struct nfs4_statx *statx,
+		struct nfs_fattr *fattr)
+{
+	struct nfs4_exception exception = { };
+	struct nfs_server *server = NFS_SERVER(inode);
+	int err;
+
+	do {
+		err = nfs4_handle_exception(server,
+				_nfs4_set_nfs4_statx(inode, statx, fattr),
+				&exception);
+	} while (exception.retry);
+	return err;
+}
+
 /**
  * nfs4_proc_get_locations - discover locations for a migrated FSID
  * @inode: inode on FSID that is migrating
@@ -10419,6 +10542,7 @@  const struct nfs_rpc_ops nfs_v4_clientops = {
 	.dir_inode_ops	= &nfs4_dir_inode_operations,
 	.file_inode_ops	= &nfs4_file_inode_operations,
 	.file_ops	= &nfs4_file_operations,
+	.dir_ops	= &nfs4_dir_operations,
 	.getroot	= nfs4_proc_get_root,
 	.submount	= nfs4_submount,
 	.try_get_tree	= nfs4_try_get_tree,
diff --git a/fs/nfs/nfs4xdr.c b/fs/nfs/nfs4xdr.c
index d2c240effc87..e5300d7ed712 100644
--- a/fs/nfs/nfs4xdr.c
+++ b/fs/nfs/nfs4xdr.c
@@ -129,12 +129,15 @@  static int decode_layoutget(struct xdr_stream *xdr, struct rpc_rqst *req,
 				nfs4_fattr_value_maxsz)
 #define decode_getattr_maxsz    (op_decode_hdr_maxsz + nfs4_fattr_maxsz)
 #define encode_attrs_maxsz	(nfs4_fattr_bitmap_maxsz + \
-				 1 + 2 + 1 + \
+				 1 + 2 + 1 + 1 + 1 + \
 				nfs4_owner_maxsz + \
 				nfs4_group_maxsz + \
-				nfs4_label_maxsz + \
+				1 + \
+				1 + nfstime4_maxsz + \
+				nfstime4_maxsz + nfstime4_maxsz + \
 				1 + nfstime4_maxsz + \
-				1 + nfstime4_maxsz)
+				nfs4_label_maxsz + \
+				2)
 #define encode_savefh_maxsz     (op_encode_hdr_maxsz)
 #define decode_savefh_maxsz     (op_decode_hdr_maxsz)
 #define encode_restorefh_maxsz  (op_encode_hdr_maxsz)
@@ -1081,6 +1084,7 @@  xdr_encode_nfstime4(__be32 *p, const struct timespec64 *t)
 static void encode_attrs(struct xdr_stream *xdr, const struct iattr *iap,
 				const struct nfs4_label *label,
 				const umode_t *umask,
+				const struct nfs4_statx *statx,
 				const struct nfs_server *server,
 				const uint32_t attrmask[])
 {
@@ -1153,6 +1157,34 @@  static void encode_attrs(struct xdr_stream *xdr, const struct iattr *iap,
 		}
 	}
 
+	if (statx && (statx->fa_valid[0] & NFS_FA_VALID_TIME_BACKUP) &&
+	    (attrmask[1] & FATTR4_WORD1_TIME_BACKUP)) {
+		bmval[1] |= FATTR4_WORD1_TIME_BACKUP;
+		len += (nfstime4_maxsz << 2);
+	}
+	if (statx && (statx->fa_valid[0] & NFS_FA_VALID_TIME_CREATE) &&
+	    (attrmask[1] & FATTR4_WORD1_TIME_CREATE)) {
+		bmval[1] |= FATTR4_WORD1_TIME_CREATE;
+		len += (nfstime4_maxsz << 2);
+	}
+
+	if (statx && (statx->fa_valid[0] & NFS_FA_VALID_ARCHIVE) &&
+	   (attrmask[0] & FATTR4_WORD0_ARCHIVE)) {
+		bmval[0] |= FATTR4_WORD0_ARCHIVE;
+		len += 4;
+	}
+	if (statx && (statx->fa_valid[0] & NFS_FA_VALID_HIDDEN) &&
+	   (attrmask[0] & FATTR4_WORD0_HIDDEN)) {
+		bmval[0] |= FATTR4_WORD0_HIDDEN;
+		len += 4;
+	}
+
+	if (statx && (statx->fa_valid[0] & NFS_FA_VALID_SYSTEM) &&
+	   (attrmask[1] & FATTR4_WORD1_SYSTEM)) {
+		bmval[1] |= FATTR4_WORD1_SYSTEM;
+		len += 4;
+	}
+
 	if (label && (attrmask[2] & FATTR4_WORD2_SECURITY_LABEL)) {
 		len += 4 + 4 + 4 + (XDR_QUADLEN(label->len) << 2);
 		bmval[2] |= FATTR4_WORD2_SECURITY_LABEL;
@@ -1163,12 +1195,21 @@  static void encode_attrs(struct xdr_stream *xdr, const struct iattr *iap,
 
 	if (bmval[0] & FATTR4_WORD0_SIZE)
 		p = xdr_encode_hyper(p, iap->ia_size);
+	if (bmval[0] & FATTR4_WORD0_ARCHIVE)
+		*p++ = (statx->fa_flags & NFS_FA_FLAG_ARCHIVE) ?
+			cpu_to_be32(1) : cpu_to_be32(0);
+	if (bmval[0] & FATTR4_WORD0_HIDDEN)
+		*p++ = (statx->fa_flags & NFS_FA_FLAG_HIDDEN) ?
+			cpu_to_be32(1) : cpu_to_be32(0);
 	if (bmval[1] & FATTR4_WORD1_MODE)
 		*p++ = cpu_to_be32(iap->ia_mode & S_IALLUGO);
 	if (bmval[1] & FATTR4_WORD1_OWNER)
 		p = xdr_encode_opaque(p, owner_name, owner_namelen);
 	if (bmval[1] & FATTR4_WORD1_OWNER_GROUP)
 		p = xdr_encode_opaque(p, owner_group, owner_grouplen);
+	if (bmval[1] & FATTR4_WORD1_SYSTEM)
+		*p++ = (statx->fa_flags & NFS_FA_FLAG_SYSTEM) ?
+			cpu_to_be32(1) : cpu_to_be32(0);
 	if (bmval[1] & FATTR4_WORD1_TIME_ACCESS_SET) {
 		if (iap->ia_valid & ATTR_ATIME_SET) {
 			*p++ = cpu_to_be32(NFS4_SET_TO_CLIENT_TIME);
@@ -1176,6 +1217,10 @@  static void encode_attrs(struct xdr_stream *xdr, const struct iattr *iap,
 		} else
 			*p++ = cpu_to_be32(NFS4_SET_TO_SERVER_TIME);
 	}
+	if (bmval[1] & FATTR4_WORD1_TIME_BACKUP)
+		p = xdr_encode_nfstime4(p, &statx->fa_time_backup);
+	if (bmval[1] & FATTR4_WORD1_TIME_CREATE)
+		p = xdr_encode_nfstime4(p, &statx->fa_btime);
 	if (bmval[1] & FATTR4_WORD1_TIME_MODIFY_SET) {
 		if (iap->ia_valid & ATTR_MTIME_SET) {
 			*p++ = cpu_to_be32(NFS4_SET_TO_CLIENT_TIME);
@@ -1248,7 +1293,7 @@  static void encode_create(struct xdr_stream *xdr, const struct nfs4_create_arg *
 
 	encode_string(xdr, create->name->len, create->name->name);
 	encode_attrs(xdr, create->attrs, create->label, &create->umask,
-			create->server, create->server->attr_bitmask);
+		     NULL, create->server, create->server->attr_bitmask);
 }
 
 static void encode_getattr(struct xdr_stream *xdr,
@@ -1434,12 +1479,12 @@  static inline void encode_createmode(struct xdr_stream *xdr, const struct nfs_op
 	case NFS4_CREATE_UNCHECKED:
 		*p = cpu_to_be32(NFS4_CREATE_UNCHECKED);
 		encode_attrs(xdr, arg->u.attrs, arg->label, &arg->umask,
-				arg->server, arg->server->attr_bitmask);
+			     NULL, arg->server, arg->server->attr_bitmask);
 		break;
 	case NFS4_CREATE_GUARDED:
 		*p = cpu_to_be32(NFS4_CREATE_GUARDED);
 		encode_attrs(xdr, arg->u.attrs, arg->label, &arg->umask,
-				arg->server, arg->server->attr_bitmask);
+			     NULL, arg->server, arg->server->attr_bitmask);
 		break;
 	case NFS4_CREATE_EXCLUSIVE:
 		*p = cpu_to_be32(NFS4_CREATE_EXCLUSIVE);
@@ -1449,7 +1494,7 @@  static inline void encode_createmode(struct xdr_stream *xdr, const struct nfs_op
 		*p = cpu_to_be32(NFS4_CREATE_EXCLUSIVE4_1);
 		encode_nfs4_verifier(xdr, &arg->u.verifier);
 		encode_attrs(xdr, arg->u.attrs, arg->label, &arg->umask,
-				arg->server, arg->server->exclcreat_bitmask);
+			     NULL, arg->server, arg->server->exclcreat_bitmask);
 	}
 }
 
@@ -1712,8 +1757,8 @@  static void encode_setattr(struct xdr_stream *xdr, const struct nfs_setattrargs
 {
 	encode_op_hdr(xdr, OP_SETATTR, decode_setattr_maxsz, hdr);
 	encode_nfs4_stateid(xdr, &arg->stateid);
-	encode_attrs(xdr, arg->iap, arg->label, NULL, server,
-			server->attr_bitmask);
+	encode_attrs(xdr, arg->iap, arg->label, NULL, arg->statx, server,
+		     server->attr_bitmask);
 }
 
 static void encode_setclientid(struct xdr_stream *xdr, const struct nfs4_setclientid *setclientid, struct compound_hdr *hdr)
diff --git a/fs/nfs/nfstrace.c b/fs/nfs/nfstrace.c
index 5d1bfccbb4da..0b88deb0216e 100644
--- a/fs/nfs/nfstrace.c
+++ b/fs/nfs/nfstrace.c
@@ -9,6 +9,11 @@ 
 #define CREATE_TRACE_POINTS
 #include "nfstrace.h"
 
+EXPORT_TRACEPOINT_SYMBOL_GPL(nfs_ioctl_file_statx_get_enter);
+EXPORT_TRACEPOINT_SYMBOL_GPL(nfs_ioctl_file_statx_get_exit);
+EXPORT_TRACEPOINT_SYMBOL_GPL(nfs_ioctl_file_statx_set_enter);
+EXPORT_TRACEPOINT_SYMBOL_GPL(nfs_ioctl_file_statx_set_exit);
+
 EXPORT_TRACEPOINT_SYMBOL_GPL(nfs_fsync_enter);
 EXPORT_TRACEPOINT_SYMBOL_GPL(nfs_fsync_exit);
 EXPORT_TRACEPOINT_SYMBOL_GPL(nfs_xdr_status);
diff --git a/fs/nfs/nfstrace.h b/fs/nfs/nfstrace.h
index 2ef7cff8a4ba..b67dd087fb47 100644
--- a/fs/nfs/nfstrace.h
+++ b/fs/nfs/nfstrace.h
@@ -166,6 +166,11 @@  DEFINE_NFS_INODE_EVENT_DONE(nfs_fsync_exit);
 DEFINE_NFS_INODE_EVENT(nfs_access_enter);
 DEFINE_NFS_INODE_EVENT_DONE(nfs_set_cache_invalid);
 
+DEFINE_NFS_INODE_EVENT(nfs_ioctl_file_statx_get_enter);
+DEFINE_NFS_INODE_EVENT_DONE(nfs_ioctl_file_statx_get_exit);
+DEFINE_NFS_INODE_EVENT(nfs_ioctl_file_statx_set_enter);
+DEFINE_NFS_INODE_EVENT_DONE(nfs_ioctl_file_statx_set_exit);
+
 TRACE_EVENT(nfs_access_exit,
 		TP_PROTO(
 			const struct inode *inode,
diff --git a/fs/nfs/proc.c b/fs/nfs/proc.c
index 73dcaa99fa9b..8fd96d93630a 100644
--- a/fs/nfs/proc.c
+++ b/fs/nfs/proc.c
@@ -717,6 +717,7 @@  const struct nfs_rpc_ops nfs_v2_clientops = {
 	.dir_inode_ops	= &nfs_dir_inode_operations,
 	.file_inode_ops	= &nfs_file_inode_operations,
 	.file_ops	= &nfs_file_operations,
+	.dir_ops	= &nfs_dir_operations,
 	.getroot	= nfs_proc_get_root,
 	.submount	= nfs_submount,
 	.try_get_tree	= nfs_try_get_tree,
diff --git a/include/linux/nfs_fs.h b/include/linux/nfs_fs.h
index 058fc11338d9..0c3a5859f7f3 100644
--- a/include/linux/nfs_fs.h
+++ b/include/linux/nfs_fs.h
@@ -501,6 +501,7 @@  extern __be32 root_nfs_parse_addr(char *name); /*__init*/
 extern const struct file_operations nfs_file_operations;
 #if IS_ENABLED(CONFIG_NFS_V4)
 extern const struct file_operations nfs4_file_operations;
+extern const struct file_operations nfs4_dir_operations;
 #endif /* CONFIG_NFS_V4 */
 extern const struct address_space_operations nfs_file_aops;
 extern const struct address_space_operations nfs_dir_aops;
diff --git a/include/linux/nfs_xdr.h b/include/linux/nfs_xdr.h
index 0d5b11c1bfec..9ce61f680a13 100644
--- a/include/linux/nfs_xdr.h
+++ b/include/linux/nfs_xdr.h
@@ -812,6 +812,7 @@  struct nfs_createargs {
 	struct iattr *		sattr;
 };
 
+struct nfs4_statx;
 struct nfs_setattrargs {
 	struct nfs4_sequence_args 	seq_args;
 	struct nfs_fh *                 fh;
@@ -820,6 +821,7 @@  struct nfs_setattrargs {
 	const struct nfs_server *	server; /* Needed for name mapping */
 	const u32 *			bitmask;
 	const struct nfs4_label		*label;
+	const struct nfs4_statx		*statx;
 };
 
 struct nfs_setaclargs {
@@ -1744,6 +1746,7 @@  struct nfs_rpc_ops {
 	const struct inode_operations *dir_inode_ops;
 	const struct inode_operations *file_inode_ops;
 	const struct file_operations *file_ops;
+	const struct file_operations *dir_ops;
 	const struct nlmclnt_operations *nlmclnt_ops;
 
 	int	(*getroot) (struct nfs_server *, struct nfs_fh *,
diff --git a/include/uapi/linux/nfs.h b/include/uapi/linux/nfs.h
index 946cb62d64b0..df87da39bc43 100644
--- a/include/uapi/linux/nfs.h
+++ b/include/uapi/linux/nfs.h
@@ -9,6 +9,8 @@ 
 #define _UAPI_LINUX_NFS_H
 
 #include <linux/types.h>
+#include <asm/byteorder.h>
+#include <linux/time.h>
 
 #define NFS_PROGRAM	100003
 #define NFS_PORT	2049
@@ -35,6 +37,94 @@ 
 
 #define NFS_PIPE_DIRNAME "nfs"
 
+/* NFS ioctls */
+#define NFS_IOC_FILE_STATX_GET	_IOR('N', 2, struct nfs_ioctl_nfs4_statx)
+#define NFS_IOC_FILE_STATX_SET	_IOW('N', 3, struct nfs_ioctl_nfs4_statx)
+
+/* Options for struct nfs_ioctl_nfs4_statx */
+#define NFS_FA_OPTIONS_SYNC_AS_STAT			0x0000
+#define NFS_FA_OPTIONS_FORCE_SYNC			0x2000 /* See statx */
+#define NFS_FA_OPTIONS_DONT_SYNC			0x4000 /* See statx */
+
+#define NFS_FA_VALID_TIME_CREATE			0x00001UL
+#define NFS_FA_VALID_TIME_BACKUP			0x00002UL
+#define NFS_FA_VALID_ARCHIVE				0x00004UL
+#define NFS_FA_VALID_HIDDEN				0x00008UL
+#define NFS_FA_VALID_SYSTEM				0x00010UL
+#define NFS_FA_VALID_OWNER				0x00020UL
+#define NFS_FA_VALID_OWNER_GROUP			0x00040UL
+#define NFS_FA_VALID_ATIME				0x00080UL
+#define NFS_FA_VALID_MTIME				0x00100UL
+#define NFS_FA_VALID_CTIME				0x00200UL
+#define NFS_FA_VALID_OFFLINE				0x00400UL
+#define NFS_FA_VALID_MODE				0x00800UL
+#define NFS_FA_VALID_NLINK				0x01000UL
+#define NFS_FA_VALID_BLKSIZE				0x02000UL
+#define NFS_FA_VALID_INO				0x04000UL
+#define NFS_FA_VALID_DEV				0x08000UL
+#define NFS_FA_VALID_RDEV				0x10000UL
+#define NFS_FA_VALID_SIZE				0x20000UL
+#define NFS_FA_VALID_BLOCKS				0x40000UL
+
+#define NFS_FA_VALID_ALL_ATTR_0 ( NFS_FA_VALID_TIME_CREATE | \
+		NFS_FA_VALID_TIME_BACKUP | \
+		NFS_FA_VALID_ARCHIVE | \
+		NFS_FA_VALID_HIDDEN | \
+		NFS_FA_VALID_SYSTEM | \
+		NFS_FA_VALID_OWNER | \
+		NFS_FA_VALID_OWNER_GROUP | \
+		NFS_FA_VALID_ATIME | \
+		NFS_FA_VALID_MTIME | \
+		NFS_FA_VALID_CTIME | \
+		NFS_FA_VALID_OFFLINE | \
+		NFS_FA_VALID_MODE | \
+		NFS_FA_VALID_NLINK | \
+		NFS_FA_VALID_BLKSIZE | \
+		NFS_FA_VALID_INO | \
+		NFS_FA_VALID_DEV | \
+		NFS_FA_VALID_RDEV | \
+		NFS_FA_VALID_SIZE | \
+		NFS_FA_VALID_BLOCKS)
+
+#define NFS_FA_FLAG_ARCHIVE				(1UL << 0)
+#define NFS_FA_FLAG_HIDDEN				(1UL << 1)
+#define NFS_FA_FLAG_SYSTEM				(1UL << 2)
+#define NFS_FA_FLAG_OFFLINE				(1UL << 3)
+
+struct nfs_ioctl_timespec {
+	__s64		tv_sec;
+	__s64		tv_nsec;
+};
+
+struct nfs_ioctl_nfs4_statx {
+	__s32		real_fd;		/* real FD to use,
+						   -1 means use current file */
+	__u32		fa_options;
+
+	__u64		fa_request[2];		/* Attributes to retrieve */
+	__u64		fa_valid[2];		/* Attributes set */
+
+	struct nfs_ioctl_timespec fa_time_backup;/* Backup time */
+	struct nfs_ioctl_timespec fa_btime;     /* Birth time */
+	__u64		fa_flags;		/* Flag attributes */
+	/* Ordinary attributes follow */
+	struct nfs_ioctl_timespec fa_atime;	/* Access time */
+	struct nfs_ioctl_timespec fa_mtime;	/* Modify time */
+	struct nfs_ioctl_timespec fa_ctime;	/* Change time */
+	__u32		fa_owner_uid;		/* Owner User ID */
+	__u32		fa_group_gid;		/* Primary Group ID */
+	__u32		fa_mode;		/* Mode */
+	__u32	 	fa_nlink;
+	__u32		fa_blksize;
+	__u32		fa_spare;		/* Alignment */
+	__u64		fa_ino;
+	__u32		fa_dev;
+	__u32		fa_rdev;
+	__s64		fa_size;
+	__s64		fa_blocks;
+	__u64 		fa_padding[4];
+};
+
 /*
  * NFS stats. The good thing with these values is that NFSv3 errors are
  * a superset of NFSv2 errors (with the exception of NFSERR_WFLUSH which