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@xxxxxxxxxx wrote: > > > From: Richard Sharpe <richard.sharpe@xxxxxxxxxxxxxxx> > > > > > > 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@xxxxxxxxxxxxxxx> > > > Signed-off-by: Lance Shelton <lance.shelton@xxxxxxxxxxxxxxx> > > > Signed-off-by: Trond Myklebust <trond.myklebust@xxxxxxxxxxxxxxx> > > > --- > > > 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@xxxxxxxxxxxxxxx > >