From patchwork Fri Dec 17 20:48:53 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Trond Myklebust X-Patchwork-Id: 12685699 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 9E4B9C433FE for ; Fri, 17 Dec 2021 20:55:06 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S237403AbhLQUzG (ORCPT ); Fri, 17 Dec 2021 15:55:06 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:48684 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S237386AbhLQUzF (ORCPT ); Fri, 17 Dec 2021 15:55:05 -0500 Received: from dfw.source.kernel.org (dfw.source.kernel.org [IPv6:2604:1380:4641:c500::1]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 16299C061574 for ; Fri, 17 Dec 2021 12:55:05 -0800 (PST) Received: from smtp.kernel.org (relay.kernel.org [52.25.139.140]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by dfw.source.kernel.org (Postfix) with ESMTPS id 8426A623A7 for ; Fri, 17 Dec 2021 20:55:04 +0000 (UTC) Received: by smtp.kernel.org (Postfix) with ESMTPSA id 0E5CCC36AEA; Fri, 17 Dec 2021 20:55:03 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1639774504; bh=2v/BNHfyecAaPqaOXb5DZjeL+9gQjyCcOzQMqBWzSt0=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=dB7G8wr+8BsZbAdXJJfcpljMsvUxLrQOU6x3OHwzWnRXBaHKJchLwyqYZ/KKSK+7S PuKHrOojY2rZVkvOwDo3Dvv/8jFozhhZMRwDKsp/ZmzUH6QM87fzEWWoK3UWTO9ZHL z98xPjCugzPdqNHP3GcUrMrWyVyADtYsOfxUyz1ElVhdqobeecD6ZHUavyWIARFwcQ /ENX1LyQgJ5ljF4rVS9QFNyAiyLXMLmp+z5FNNGhPSZ1SUOgmjkzEDhUtEOhY+PB/o YH5itlfg/adpj5ORwRCFJxmo5Nn08eie97XygZLBHR5QlNbzKpuLp3ONxNRS/lb9RK ktt0j9OlQuT3A== From: trondmy@kernel.org To: Anna Schumaker Cc: linux-nfs@vger.kernel.org Subject: [PATCH 7/8] NFS: Support statx_get and statx_set ioctls Date: Fri, 17 Dec 2021 15:48:53 -0500 Message-Id: <20211217204854.439578-8-trondmy@kernel.org> X-Mailer: git-send-email 2.33.1 In-Reply-To: <20211217204854.439578-7-trondmy@kernel.org> References: <20211217204854.439578-1-trondmy@kernel.org> <20211217204854.439578-2-trondmy@kernel.org> <20211217204854.439578-3-trondmy@kernel.org> <20211217204854.439578-4-trondmy@kernel.org> <20211217204854.439578-5-trondmy@kernel.org> <20211217204854.439578-6-trondmy@kernel.org> <20211217204854.439578-7-trondmy@kernel.org> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-nfs@vger.kernel.org From: Richard Sharpe 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 Signed-off-by: Lance Shelton Signed-off-by: Trond Myklebust --- 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 #include #include +#include +#include #include #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 +#include +#include #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