diff options
| author | Linus Torvalds <torvalds@linux-foundation.org> | 2026-02-12 08:23:53 -0800 |
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2026-02-12 08:23:53 -0800 |
| commit | 2831fa8b8bcf1083f9526aa0c41fafb0796cf874 (patch) | |
| tree | 3199190762a78a8986f3dbd6e8ad7181be55f3d9 /fs | |
| parent | 37a93dd5c49b5fda807fd204edf2547c3493319c (diff) | |
| parent | e939bd675634fd52d559b90e2cf58333e16afea8 (diff) | |
Merge tag 'nfsd-7.0' of git://git.kernel.org/pub/scm/linux/kernel/git/cel/linux
Pull nfsd updates from Chuck Lever:
"Neil Brown and Jeff Layton contributed a dynamic thread pool sizing
mechanism for NFSD. The sunrpc layer now tracks minimum and maximum
thread counts per pool, and NFSD adjusts running thread counts based
on workload: idle threads exit after a timeout when the pool exceeds
its minimum, and new threads spawn automatically when all threads are
busy. Administrators control this behavior via the nfsdctl netlink
interface.
Rick Macklem, FreeBSD NFS maintainer, generously contributed server-
side support for the POSIX ACL extension to NFSv4, as specified in
draft-ietf-nfsv4-posix-acls. This extension allows NFSv4 clients to
get and set POSIX access and default ACLs using native NFSv4
operations, eliminating the need for sideband protocols. The feature
is gated by a Kconfig option since the IETF draft has not yet been
ratified.
Chuck Lever delivered numerous improvements to the xdrgen tool. Error
reporting now covers parsing, AST transformation, and invalid
declarations. Generated enum decoders validate incoming values against
valid enumerator lists. New features include pass-through line support
for embedding C directives in XDR specifications, 16-bit integer
types, and program number definitions. Several code generation issues
were also addressed.
When an administrator revokes NFSv4 state for a filesystem via the
unlock_fs interface, ongoing async COPY operations referencing that
filesystem are now cancelled, with CB_OFFLOAD callbacks notifying
affected clients.
The remaining patches in this pull request are clean-ups and minor
optimizations. Sincere thanks to all contributors, reviewers, testers,
and bug reporters who participated in the v7.0 NFSD development cycle"
* tag 'nfsd-7.0' of git://git.kernel.org/pub/scm/linux/kernel/git/cel/linux: (45 commits)
NFSD: Add POSIX ACL file attributes to SUPPATTR bitmasks
NFSD: Add POSIX draft ACL support to the NFSv4 SETATTR operation
NFSD: Add support for POSIX draft ACLs for file creation
NFSD: Add support for XDR decoding POSIX draft ACLs
NFSD: Refactor nfsd_setattr()'s ACL error reporting
NFSD: Do not allow NFSv4 (N)VERIFY to check POSIX ACL attributes
NFSD: Add nfsd4_encode_fattr4_posix_access_acl
NFSD: Add nfsd4_encode_fattr4_posix_default_acl
NFSD: Add nfsd4_encode_fattr4_acl_trueform_scope
NFSD: Add nfsd4_encode_fattr4_acl_trueform
Add RPC language definition of NFSv4 POSIX ACL extension
NFSD: Add a Kconfig setting to enable support for NFSv4 POSIX ACLs
xdrgen: Implement pass-through lines in specifications
nfsd: cancel async COPY operations when admin revokes filesystem state
nfsd: add controls to set the minimum number of threads per pool
nfsd: adjust number of running nfsd threads based on activity
sunrpc: allow svc_recv() to return -ETIMEDOUT and -EBUSY
sunrpc: split new thread creation into a separate function
sunrpc: introduce the concept of a minimum number of threads per pool
sunrpc: track the max number of requested threads in a pool
...
Diffstat (limited to 'fs')
| -rw-r--r-- | fs/lockd/svc.c | 6 | ||||
| -rw-r--r-- | fs/lockd/svclock.c | 4 | ||||
| -rw-r--r-- | fs/locks.c | 17 | ||||
| -rw-r--r-- | fs/nfs/callback.c | 10 | ||||
| -rw-r--r-- | fs/nfsd/Kconfig | 19 | ||||
| -rw-r--r-- | fs/nfsd/Makefile | 10 | ||||
| -rw-r--r-- | fs/nfsd/acl.h | 1 | ||||
| -rw-r--r-- | fs/nfsd/netlink.c | 5 | ||||
| -rw-r--r-- | fs/nfsd/netns.h | 7 | ||||
| -rw-r--r-- | fs/nfsd/nfs2acl.c | 2 | ||||
| -rw-r--r-- | fs/nfsd/nfs4acl.c | 17 | ||||
| -rw-r--r-- | fs/nfsd/nfs4idmap.c | 52 | ||||
| -rw-r--r-- | fs/nfsd/nfs4proc.c | 265 | ||||
| -rw-r--r-- | fs/nfsd/nfs4state.c | 52 | ||||
| -rw-r--r-- | fs/nfsd/nfs4xdr.c | 363 | ||||
| -rw-r--r-- | fs/nfsd/nfs4xdr_gen.c | 351 | ||||
| -rw-r--r-- | fs/nfsd/nfs4xdr_gen.h | 12 | ||||
| -rw-r--r-- | fs/nfsd/nfsctl.c | 7 | ||||
| -rw-r--r-- | fs/nfsd/nfsd.h | 24 | ||||
| -rw-r--r-- | fs/nfsd/nfsproc.c | 2 | ||||
| -rw-r--r-- | fs/nfsd/nfssvc.c | 64 | ||||
| -rw-r--r-- | fs/nfsd/state.h | 5 | ||||
| -rw-r--r-- | fs/nfsd/trace.h | 54 | ||||
| -rw-r--r-- | fs/nfsd/vfs.c | 34 | ||||
| -rw-r--r-- | fs/nfsd/vfs.h | 3 | ||||
| -rw-r--r-- | fs/nfsd/xdr4.h | 7 |
26 files changed, 1242 insertions, 151 deletions
diff --git a/fs/lockd/svc.c b/fs/lockd/svc.c index d68afa196535..dcd80c4e74c9 100644 --- a/fs/lockd/svc.c +++ b/fs/lockd/svc.c @@ -141,7 +141,7 @@ lockd(void *vrqstp) */ while (!svc_thread_should_stop(rqstp)) { nlmsvc_retry_blocked(rqstp); - svc_recv(rqstp); + svc_recv(rqstp, 0); } if (nlmsvc_ops) nlmsvc_invalidate_all(); @@ -340,7 +340,7 @@ static int lockd_get(void) return -ENOMEM; } - error = svc_set_num_threads(serv, NULL, 1); + error = svc_set_num_threads(serv, 0, 1); if (error < 0) { svc_destroy(&serv); return error; @@ -368,7 +368,7 @@ static void lockd_put(void) unregister_inet6addr_notifier(&lockd_inet6addr_notifier); #endif - svc_set_num_threads(nlmsvc_serv, NULL, 0); + svc_set_num_threads(nlmsvc_serv, 0, 0); timer_delete_sync(&nlmsvc_retry); svc_destroy(&nlmsvc_serv); dprintk("lockd_down: service destroyed\n"); diff --git a/fs/lockd/svclock.c b/fs/lockd/svclock.c index 6bce19fd024c..712df1e025d8 100644 --- a/fs/lockd/svclock.c +++ b/fs/lockd/svclock.c @@ -641,10 +641,6 @@ nlmsvc_testlock(struct svc_rqst *rqstp, struct nlm_file *file, conflock->fl.c.flc_owner = lock->fl.c.flc_owner; error = vfs_test_lock(file->f_file[mode], &conflock->fl); if (error) { - /* We can't currently deal with deferred test requests */ - if (error == FILE_LOCK_DEFERRED) - WARN_ON_ONCE(1); - ret = nlm_lck_denied_nolocks; goto out; } diff --git a/fs/locks.c b/fs/locks.c index 3ea25d3a780f..d13ec930b7bb 100644 --- a/fs/locks.c +++ b/fs/locks.c @@ -2262,12 +2262,23 @@ SYSCALL_DEFINE2(flock, unsigned int, fd, unsigned int, cmd) */ int vfs_test_lock(struct file *filp, struct file_lock *fl) { + int error = 0; + WARN_ON_ONCE(fl->fl_ops || fl->fl_lmops); WARN_ON_ONCE(filp != fl->c.flc_file); if (filp->f_op->lock) - return filp->f_op->lock(filp, F_GETLK, fl); - posix_test_lock(filp, fl); - return 0; + error = filp->f_op->lock(filp, F_GETLK, fl); + else + posix_test_lock(filp, fl); + + /* + * We don't expect FILE_LOCK_DEFERRED and callers cannot + * handle it. + */ + if (WARN_ON_ONCE(error == FILE_LOCK_DEFERRED)) + error = -EIO; + + return error; } EXPORT_SYMBOL_GPL(vfs_test_lock); diff --git a/fs/nfs/callback.c b/fs/nfs/callback.c index fabda0f6ec1a..701a9ac7363e 100644 --- a/fs/nfs/callback.c +++ b/fs/nfs/callback.c @@ -81,7 +81,7 @@ nfs4_callback_svc(void *vrqstp) set_freezable(); while (!svc_thread_should_stop(rqstp)) - svc_recv(rqstp); + svc_recv(rqstp, 0); svc_exit_thread(rqstp); return 0; @@ -119,9 +119,9 @@ static int nfs_callback_start_svc(int minorversion, struct rpc_xprt *xprt, if (serv->sv_nrthreads == nrservs) return 0; - ret = svc_set_num_threads(serv, NULL, nrservs); + ret = svc_set_num_threads(serv, 0, nrservs); if (ret) { - svc_set_num_threads(serv, NULL, 0); + svc_set_num_threads(serv, 0, 0); return ret; } dprintk("nfs_callback_up: service started\n"); @@ -242,7 +242,7 @@ int nfs_callback_up(u32 minorversion, struct rpc_xprt *xprt) cb_info->users++; err_net: if (!cb_info->users) { - svc_set_num_threads(cb_info->serv, NULL, 0); + svc_set_num_threads(cb_info->serv, 0, 0); svc_destroy(&cb_info->serv); } err_create: @@ -268,7 +268,7 @@ void nfs_callback_down(int minorversion, struct net *net, struct rpc_xprt *xprt) nfs_callback_down_net(minorversion, serv, net); cb_info->users--; if (cb_info->users == 0) { - svc_set_num_threads(serv, NULL, 0); + svc_set_num_threads(serv, 0, 0); dprintk("nfs_callback_down: service destroyed\n"); xprt_svc_destroy_nullify_bc(xprt, &cb_info->serv); } diff --git a/fs/nfsd/Kconfig b/fs/nfsd/Kconfig index 0b5c1a0bf1cf..4fd6e818565e 100644 --- a/fs/nfsd/Kconfig +++ b/fs/nfsd/Kconfig @@ -186,3 +186,22 @@ config NFSD_V4_DELEG_TIMESTAMPS draft-ietf-nfsv4-delstid-08 "Extending the Opening of Files". This is currently an experimental feature and is therefore left disabled by default. + +config NFSD_V4_POSIX_ACLS + bool "Support NFSv4 POSIX draft ACLs" + depends on NFSD_V4 + default n + help + Include experimental support for POSIX Access Control Lists + (ACLs) in NFSv4 as specified in the IETF draft + draft-ietf-nfsv4-posix-acls. This protocol extension enables + NFSv4 clients to retrieve and modify POSIX ACLs on exported + filesystems that support them. + + This feature is based on an unratified IETF draft + specification that may change in ways that impact + interoperability with existing clients. Enable only for + testing environments or when interoperability with specific + clients that implement this draft is required. + + If unsure, say N. diff --git a/fs/nfsd/Makefile b/fs/nfsd/Makefile index 55744bb786c9..f0da4d69dc74 100644 --- a/fs/nfsd/Makefile +++ b/fs/nfsd/Makefile @@ -26,7 +26,15 @@ nfsd-$(CONFIG_NFSD_FLEXFILELAYOUT) += flexfilelayout.o flexfilelayoutxdr.o nfsd-$(CONFIG_NFS_LOCALIO) += localio.o nfsd-$(CONFIG_DEBUG_FS) += debugfs.o - +# +# XDR code generation (requires Python and additional packages) +# +# The generated *xdr_gen.{h,c} files are checked into git. Normal kernel +# builds do not require the xdrgen tool or its Python dependencies. +# +# Developers modifying .x files in Documentation/sunrpc/xdr/ should run +# "make xdrgen" to regenerate the affected files. +# .PHONY: xdrgen xdrgen: ../../include/linux/sunrpc/xdrgen/nfs4_1.h nfs4xdr_gen.h nfs4xdr_gen.c diff --git a/fs/nfsd/acl.h b/fs/nfsd/acl.h index 4b7324458a94..2003523d0e65 100644 --- a/fs/nfsd/acl.h +++ b/fs/nfsd/acl.h @@ -49,5 +49,6 @@ int nfsd4_get_nfs4_acl(struct svc_rqst *rqstp, struct dentry *dentry, struct nfs4_acl **acl); __be32 nfsd4_acl_to_attr(enum nfs_ftype4 type, struct nfs4_acl *acl, struct nfsd_attrs *attr); +void sort_pacl_range(struct posix_acl *pacl, int start, int end); #endif /* LINUX_NFS4_ACL_H */ diff --git a/fs/nfsd/netlink.c b/fs/nfsd/netlink.c index ac51a44e1065..887525964451 100644 --- a/fs/nfsd/netlink.c +++ b/fs/nfsd/netlink.c @@ -24,11 +24,12 @@ const struct nla_policy nfsd_version_nl_policy[NFSD_A_VERSION_ENABLED + 1] = { }; /* NFSD_CMD_THREADS_SET - do */ -static const struct nla_policy nfsd_threads_set_nl_policy[NFSD_A_SERVER_SCOPE + 1] = { +static const struct nla_policy nfsd_threads_set_nl_policy[NFSD_A_SERVER_MIN_THREADS + 1] = { [NFSD_A_SERVER_THREADS] = { .type = NLA_U32, }, [NFSD_A_SERVER_GRACETIME] = { .type = NLA_U32, }, [NFSD_A_SERVER_LEASETIME] = { .type = NLA_U32, }, [NFSD_A_SERVER_SCOPE] = { .type = NLA_NUL_STRING, }, + [NFSD_A_SERVER_MIN_THREADS] = { .type = NLA_U32, }, }; /* NFSD_CMD_VERSION_SET - do */ @@ -57,7 +58,7 @@ static const struct genl_split_ops nfsd_nl_ops[] = { .cmd = NFSD_CMD_THREADS_SET, .doit = nfsd_nl_threads_set_doit, .policy = nfsd_threads_set_nl_policy, - .maxattr = NFSD_A_SERVER_SCOPE, + .maxattr = NFSD_A_SERVER_MIN_THREADS, .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO, }, { diff --git a/fs/nfsd/netns.h b/fs/nfsd/netns.h index fe8338735e7c..9fa600602658 100644 --- a/fs/nfsd/netns.h +++ b/fs/nfsd/netns.h @@ -67,7 +67,6 @@ struct nfsd_net { struct lock_manager nfsd4_manager; bool grace_ended; bool grace_end_forced; - bool client_tracking_active; time64_t boot_time; struct dentry *nfsd_client_dir; @@ -130,6 +129,12 @@ struct nfsd_net { seqlock_t writeverf_lock; unsigned char writeverf[8]; + /* + * Minimum number of threads to run per pool. If 0 then the + * min == max requested number of threads. + */ + unsigned int min_threads; + u32 clientid_base; u32 clientid_counter; u32 clverifier_counter; diff --git a/fs/nfsd/nfs2acl.c b/fs/nfsd/nfs2acl.c index 5fb202acb0fd..0ac538c76180 100644 --- a/fs/nfsd/nfs2acl.c +++ b/fs/nfsd/nfs2acl.c @@ -45,7 +45,7 @@ static __be32 nfsacld_proc_getacl(struct svc_rqst *rqstp) inode = d_inode(fh->fh_dentry); if (argp->mask & ~NFS_ACL_MASK) { - resp->status = nfserr_inval; + resp->status = nfserr_io; goto out; } resp->mask = argp->mask; diff --git a/fs/nfsd/nfs4acl.c b/fs/nfsd/nfs4acl.c index 936ea1ad9586..2c2f2fd89e87 100644 --- a/fs/nfsd/nfs4acl.c +++ b/fs/nfsd/nfs4acl.c @@ -369,12 +369,21 @@ pace_gt(struct posix_acl_entry *pace1, struct posix_acl_entry *pace2) return false; } -static void -sort_pacl_range(struct posix_acl *pacl, int start, int end) { +/** + * sort_pacl_range - sort a range of POSIX ACL entries by tag and id + * @pacl: POSIX ACL containing entries to sort + * @start: starting index of range to sort + * @end: ending index of range to sort (inclusive) + * + * Sorts ACL entries in place so that USER entries are ordered by UID + * and GROUP entries are ordered by GID. Required before calling + * posix_acl_valid(). + */ +void sort_pacl_range(struct posix_acl *pacl, int start, int end) +{ int sorted = 0, i; - /* We just do a bubble sort; easy to do in place, and we're not - * expecting acl's to be long enough to justify anything more. */ + /* Bubble sort: acceptable here because ACLs are typically short. */ while (!sorted) { sorted = 1; for (i = start; i < end; i++) { diff --git a/fs/nfsd/nfs4idmap.c b/fs/nfsd/nfs4idmap.c index 8cca1329f348..c319c31b0f64 100644 --- a/fs/nfsd/nfs4idmap.c +++ b/fs/nfsd/nfs4idmap.c @@ -643,34 +643,74 @@ static __be32 encode_name_from_id(struct xdr_stream *xdr, return idmap_id_to_name(xdr, rqstp, type, id); } -__be32 -nfsd_map_name_to_uid(struct svc_rqst *rqstp, const char *name, size_t namelen, - kuid_t *uid) +/** + * nfsd_map_name_to_uid - Map user@domain to local UID + * @rqstp: RPC execution context + * @name: user@domain name to be mapped + * @namelen: length of name, in bytes + * @uid: OUT: mapped local UID value + * + * Returns nfs_ok on success or an NFSv4 status code on failure. + */ +__be32 nfsd_map_name_to_uid(struct svc_rqst *rqstp, const char *name, + size_t namelen, kuid_t *uid) { __be32 status; u32 id = -1; + /* + * The idmap lookup below triggers an upcall that invokes + * cache_check(). RQ_USEDEFERRAL must be clear to prevent + * cache_check() from setting RQ_DROPME via svc_defer(). + * NFSv4 servers are not permitted to drop requests. Also + * RQ_DROPME will force NFSv4.1 session slot processing to + * be skipped. + */ + WARN_ON_ONCE(test_bit(RQ_USEDEFERRAL, &rqstp->rq_flags)); + if (name == NULL || namelen == 0) return nfserr_inval; status = do_name_to_id(rqstp, IDMAP_TYPE_USER, name, namelen, &id); + if (status) + return status; *uid = make_kuid(nfsd_user_namespace(rqstp), id); if (!uid_valid(*uid)) status = nfserr_badowner; return status; } -__be32 -nfsd_map_name_to_gid(struct svc_rqst *rqstp, const char *name, size_t namelen, - kgid_t *gid) +/** + * nfsd_map_name_to_gid - Map user@domain to local GID + * @rqstp: RPC execution context + * @name: user@domain name to be mapped + * @namelen: length of name, in bytes + * @gid: OUT: mapped local GID value + * + * Returns nfs_ok on success or an NFSv4 status code on failure. + */ +__be32 nfsd_map_name_to_gid(struct svc_rqst *rqstp, const char *name, + size_t namelen, kgid_t *gid) { __be32 status; u32 id = -1; + /* + * The idmap lookup below triggers an upcall that invokes + * cache_check(). RQ_USEDEFERRAL must be clear to prevent + * cache_check() from setting RQ_DROPME via svc_defer(). + * NFSv4 servers are not permitted to drop requests. Also + * RQ_DROPME will force NFSv4.1 session slot processing to + * be skipped. + */ + WARN_ON_ONCE(test_bit(RQ_USEDEFERRAL, &rqstp->rq_flags)); + if (name == NULL || namelen == 0) return nfserr_inval; status = do_name_to_id(rqstp, IDMAP_TYPE_GROUP, name, namelen, &id); + if (status) + return status; *gid = make_kgid(nfsd_user_namespace(rqstp), id); if (!gid_valid(*gid)) status = nfserr_badowner; diff --git a/fs/nfsd/nfs4proc.c b/fs/nfsd/nfs4proc.c index e400f3beef61..37ab3a69c4b6 100644 --- a/fs/nfsd/nfs4proc.c +++ b/fs/nfsd/nfs4proc.c @@ -81,8 +81,8 @@ static u32 nfsd41_ex_attrmask[] = { }; static __be32 -check_attr_support(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate, - u32 *bmval, u32 *writable) +check_attr_support(struct nfsd4_compound_state *cstate, u32 *bmval, + u32 *writable) { struct dentry *dentry = cstate->current_fh.fh_dentry; struct svc_export *exp = cstate->current_fh.fh_export; @@ -91,6 +91,10 @@ check_attr_support(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate, return nfserr_attrnotsupp; if ((bmval[0] & FATTR4_WORD0_ACL) && !IS_POSIXACL(d_inode(dentry))) return nfserr_attrnotsupp; + if ((bmval[2] & (FATTR4_WORD2_POSIX_DEFAULT_ACL | + FATTR4_WORD2_POSIX_ACCESS_ACL)) && + !IS_POSIXACL(d_inode(dentry))) + return nfserr_attrnotsupp; if ((bmval[2] & FATTR4_WORD2_SECURITY_LABEL) && !(exp->ex_flags & NFSEXP_SECURITY_LABEL)) return nfserr_attrnotsupp; @@ -103,21 +107,25 @@ check_attr_support(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate, } static __be32 -nfsd4_check_open_attributes(struct svc_rqst *rqstp, - struct nfsd4_compound_state *cstate, struct nfsd4_open *open) +nfsd4_check_open_attributes(struct nfsd4_compound_state *cstate, + struct nfsd4_open *open) { __be32 status = nfs_ok; - if (open->op_create == NFS4_OPEN_CREATE) { - if (open->op_createmode == NFS4_CREATE_UNCHECKED - || open->op_createmode == NFS4_CREATE_GUARDED) - status = check_attr_support(rqstp, cstate, - open->op_bmval, nfsd_attrmask); - else if (open->op_createmode == NFS4_CREATE_EXCLUSIVE4_1) - status = check_attr_support(rqstp, cstate, - open->op_bmval, nfsd41_ex_attrmask); - } + if (open->op_create != NFS4_OPEN_CREATE) + return status; + switch (open->op_createmode) { + case NFS4_CREATE_UNCHECKED: + case NFS4_CREATE_GUARDED: + status = check_attr_support(cstate, open->op_bmval, + nfsd_attrmask); + break; + case NFS4_CREATE_EXCLUSIVE4_1: + status = check_attr_support(cstate, open->op_bmval, + nfsd41_ex_attrmask); + break; + } return status; } @@ -266,8 +274,20 @@ nfsd4_create_file(struct svc_rqst *rqstp, struct svc_fh *fhp, if (host_err) return nfserrno(host_err); - if (is_create_with_attrs(open)) - nfsd4_acl_to_attr(NF4REG, open->op_acl, &attrs); + if (open->op_acl) { + if (open->op_dpacl || open->op_pacl) { + status = nfserr_inval; + goto out_write; + } + if (is_create_with_attrs(open)) + nfsd4_acl_to_attr(NF4REG, open->op_acl, &attrs); + } else if (is_create_with_attrs(open)) { + /* The dpacl and pacl will get released by nfsd_attrs_free(). */ + attrs.na_dpacl = open->op_dpacl; + attrs.na_pacl = open->op_pacl; + open->op_dpacl = NULL; + open->op_pacl = NULL; + } child = start_creating(&nop_mnt_idmap, parent, &QSTR_LEN(open->op_fname, open->op_fnamelen)); @@ -378,8 +398,12 @@ set_attr: if (attrs.na_labelerr) open->op_bmval[2] &= ~FATTR4_WORD2_SECURITY_LABEL; - if (attrs.na_aclerr) + if (attrs.na_paclerr || attrs.na_dpaclerr) open->op_bmval[0] &= ~FATTR4_WORD0_ACL; + if (attrs.na_dpaclerr) + open->op_bmval[2] &= ~FATTR4_WORD2_POSIX_DEFAULT_ACL; + if (attrs.na_paclerr) + open->op_bmval[2] &= ~FATTR4_WORD2_POSIX_ACCESS_ACL; out: end_creating(child); nfsd_attrs_free(&attrs); @@ -547,8 +571,10 @@ nfsd4_open(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate, open->op_rqstp = rqstp; /* This check required by spec. */ - if (open->op_create && open->op_claim_type != NFS4_OPEN_CLAIM_NULL) - return nfserr_inval; + if (open->op_create && open->op_claim_type != NFS4_OPEN_CLAIM_NULL) { + status = nfserr_inval; + goto out_err; + } open->op_created = false; /* @@ -557,8 +583,10 @@ nfsd4_open(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate, */ if (nfsd4_has_session(cstate) && !test_bit(NFSD4_CLIENT_RECLAIM_COMPLETE, &cstate->clp->cl_flags) && - open->op_claim_type != NFS4_OPEN_CLAIM_PREVIOUS) - return nfserr_grace; + open->op_claim_type != NFS4_OPEN_CLAIM_PREVIOUS) { + status = nfserr_grace; + goto out_err; + } if (nfsd4_has_session(cstate)) copy_clientid(&open->op_clientid, cstate->session); @@ -584,7 +612,7 @@ nfsd4_open(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate, goto out; } - status = nfsd4_check_open_attributes(rqstp, cstate, open); + status = nfsd4_check_open_attributes(cstate, open); if (status) goto out; @@ -645,6 +673,9 @@ out: } nfsd4_cleanup_open_state(cstate, open); nfsd4_bump_seqid(cstate, status); +out_err: + posix_acl_release(open->op_dpacl); + posix_acl_release(open->op_pacl); return status; } @@ -785,23 +816,34 @@ nfsd4_create(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate, struct nfsd_attrs attrs = { .na_iattr = &create->cr_iattr, .na_seclabel = &create->cr_label, + .na_dpacl = create->cr_dpacl, + .na_pacl = create->cr_pacl, }; struct svc_fh resfh; __be32 status; dev_t rdev; + create->cr_dpacl = NULL; + create->cr_pacl = NULL; + fh_init(&resfh, NFS4_FHSIZE); status = fh_verify(rqstp, &cstate->current_fh, S_IFDIR, NFSD_MAY_NOP); if (status) - return status; + goto out_aftermask; - status = check_attr_support(rqstp, cstate, create->cr_bmval, - nfsd_attrmask); + status = check_attr_support(cstate, create->cr_bmval, nfsd_attrmask); if (status) - return status; + goto out_aftermask; - status = nfsd4_acl_to_attr(create->cr_type, create->cr_acl, &attrs); + if (create->cr_acl) { + if (create->cr_dpacl || create->cr_pacl) { + status = nfserr_inval; + goto out_aftermask; + } + status = nfsd4_acl_to_attr(create->cr_type, create->cr_acl, + &attrs); + } current->fs->umask = create->cr_umask; switch (create->cr_type) { case NF4LNK: @@ -860,14 +902,19 @@ nfsd4_create(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate, if (attrs.na_labelerr) create->cr_bmval[2] &= ~FATTR4_WORD2_SECURITY_LABEL; - if (attrs.na_aclerr) + if (attrs.na_paclerr || attrs.na_dpaclerr) create->cr_bmval[0] &= ~FATTR4_WORD0_ACL; + if (attrs.na_dpaclerr) + create->cr_bmval[2] &= ~FATTR4_WORD2_POSIX_DEFAULT_ACL; + if (attrs.na_paclerr) + create->cr_bmval[2] &= ~FATTR4_WORD2_POSIX_ACCESS_ACL; set_change_info(&create->cr_cinfo, &cstate->current_fh); fh_dup2(&cstate->current_fh, &resfh); out: fh_put(&resfh); out_umask: current->fs->umask = 0; +out_aftermask: nfsd_attrs_free(&attrs); return status; } @@ -1172,6 +1219,8 @@ nfsd4_setattr(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate, struct nfsd_attrs attrs = { .na_iattr = &setattr->sa_iattr, .na_seclabel = &setattr->sa_label, + .na_pacl = setattr->sa_pacl, + .na_dpacl = setattr->sa_dpacl, }; bool save_no_wcc, deleg_attrs; struct nfs4_stid *st = NULL; @@ -1179,6 +1228,10 @@ nfsd4_setattr(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate, __be32 status = nfs_ok; int err; + /* Transfer ownership to attrs for cleanup via nfsd_attrs_free() */ + setattr->sa_pacl = NULL; + setattr->sa_dpacl = NULL; + deleg_attrs = setattr->sa_bmval[2] & (FATTR4_WORD2_TIME_DELEG_ACCESS | FATTR4_WORD2_TIME_DELEG_MODIFY); @@ -1192,7 +1245,7 @@ nfsd4_setattr(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate, &cstate->current_fh, &setattr->sa_stateid, flags, NULL, &st); if (status) - return status; + goto out_err; } if (deleg_attrs) { @@ -1210,18 +1263,24 @@ nfsd4_setattr(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate, if (st) nfs4_put_stid(st); if (status) - return status; + goto out_err; err = fh_want_write(&cstate->current_fh); - if (err) - return nfserrno(err); + if (err) { + status = nfserrno(err); + goto out_err; + } status = nfs_ok; - status = check_attr_support(rqstp, cstate, setattr->sa_bmval, - nfsd_attrmask); + status = check_attr_support(cstate, setattr->sa_bmval, nfsd_attrmask); if (status) goto out; + if (setattr->sa_acl && (attrs.na_dpacl || attrs.na_pacl)) { + status = nfserr_inval; + goto out; + } + inode = cstate->current_fh.fh_dentry->d_inode; status = nfsd4_acl_to_attr(S_ISDIR(inode->i_mode) ? NF4DIR : NF4REG, setattr->sa_acl, &attrs); @@ -1235,10 +1294,13 @@ nfsd4_setattr(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate, if (!status) status = nfserrno(attrs.na_labelerr); if (!status) - status = nfserrno(attrs.na_aclerr); + status = nfserrno(attrs.na_dpaclerr); + if (!status) + status = nfserrno(attrs.na_paclerr); out: - nfsd_attrs_free(&attrs); fh_drop_write(&cstate->current_fh); +out_err: + nfsd_attrs_free(&attrs); return status; } @@ -1430,14 +1492,26 @@ static void nfs4_put_copy(struct nfsd4_copy *copy) kfree(copy); } +static void release_copy_files(struct nfsd4_copy *copy); + static void nfsd4_stop_copy(struct nfsd4_copy *copy) { trace_nfsd_copy_async_cancel(copy); if (!test_and_set_bit(NFSD4_COPY_F_STOPPED, ©->cp_flags)) { kthread_stop(copy->copy_task); - copy->nfserr = nfs_ok; + if (!test_bit(NFSD4_COPY_F_CB_ERROR, ©->cp_flags)) + copy->nfserr = nfs_ok; set_bit(NFSD4_COPY_F_COMPLETED, ©->cp_flags); } + + /* + * The copy was removed from async_copies before this function + * was called, so the reaper cannot clean it up. Release files + * here regardless of who won the STOPPED race. If the thread + * set STOPPED, it has finished using the files. If STOPPED + * was set here, kthread_stop() waited for the thread to exit. + */ + release_copy_files(copy); nfs4_put_copy(copy); } @@ -1465,6 +1539,72 @@ void nfsd4_shutdown_copy(struct nfs4_client *clp) while ((copy = nfsd4_unhash_copy(clp)) != NULL) nfsd4_stop_copy(copy); } + +static bool nfsd4_copy_on_sb(const struct nfsd4_copy *copy, + const struct super_block *sb) +{ + if (copy->nf_src && + file_inode(copy->nf_src->nf_file)->i_sb == sb) + return true; + if (copy->nf_dst && + file_inode(copy->nf_dst->nf_file)->i_sb == sb) + return true; + return false; +} + +/** + * nfsd4_cancel_copy_by_sb - cancel async copy operations on @sb + * @net: net namespace containing the copy operations + * @sb: targeted superblock + */ +void nfsd4_cancel_copy_by_sb(struct net *net, struct super_block *sb) +{ + struct nfsd_net *nn = net_generic(net, nfsd_net_id); + struct nfsd4_copy *copy, *tmp; + struct nfs4_client *clp; + unsigned int idhashval; + LIST_HEAD(to_cancel); + + spin_lock(&nn->client_lock); + for (idhashval = 0; idhashval < CLIENT_HASH_SIZE; idhashval++) { + struct list_head *head = &nn->conf_id_hashtbl[idhashval]; + + list_for_each_entry(clp, head, cl_idhash) { + spin_lock(&clp->async_lock); + list_for_each_entry_safe(copy, tmp, + &clp->async_copies, copies) { + if (nfsd4_copy_on_sb(copy, sb)) { + refcount_inc(©->refcount); + /* + * Hold a reference on the client while + * nfsd4_stop_copy() runs. Unlike + * nfsd4_unhash_copy(), cp_clp is not + * NULLed here because nfsd4_send_cb_offload() + * needs a valid client to send CB_OFFLOAD. + * That function takes its own reference to + * survive callback flight. + */ + kref_get(&clp->cl_nfsdfs.cl_ref); + copy->nfserr = nfserr_admin_revoked; + set_bit(NFSD4_COPY_F_CB_ERROR, + ©->cp_flags); + list_move(©->copies, &to_cancel); + } + } + spin_unlock(&clp->async_lock); + } + } + spin_unlock(&nn->client_lock); + + list_for_each_entry_safe(copy, tmp, &to_cancel, copies) { + struct nfs4_client *clp = copy->cp_clp; + + list_del_init(©->copies); + nfsd4_stop_copy(copy); + nfsd4_put_client(clp); + } +} + #ifdef CONFIG_NFSD_V4_2_INTER_SSC extern struct file *nfs42_ssc_open(struct vfsmount *ss_mnt, @@ -1754,6 +1894,7 @@ static void nfsd4_cb_offload_release(struct nfsd4_callback *cb) container_of(cbo, struct nfsd4_copy, cp_cb_offload); set_bit(NFSD4_COPY_F_OFFLOAD_DONE, ©->cp_flags); + nfsd4_put_client(cb->cb_clp); } static int nfsd4_cb_offload_done(struct nfsd4_callback *cb, @@ -1873,10 +2014,14 @@ static void dup_copy_fields(struct nfsd4_copy *src, struct nfsd4_copy *dst) static void release_copy_files(struct nfsd4_copy *copy) { - if (copy->nf_src) + if (copy->nf_src) { nfsd_file_put(copy->nf_src); - if (copy->nf_dst) + copy->nf_src = NULL; + } + if (copy->nf_dst) { nfsd_file_put(copy->nf_dst); + copy->nf_dst = NULL; + } } static void cleanup_async_copy(struct nfsd4_copy *copy) @@ -1895,18 +2040,34 @@ static void cleanup_async_copy(struct nfsd4_copy *copy) static void nfsd4_send_cb_offload(struct nfsd4_copy *copy) { struct nfsd4_cb_offload *cbo = ©->cp_cb_offload; + struct nfs4_client *clp = copy->cp_clp; + + /* + * cp_clp is NULL when called via nfsd4_shutdown_copy() during + * client destruction. Skip the callback; the client is gone. + */ + if (!clp) { + set_bit(NFSD4_COPY_F_OFFLOAD_DONE, ©->cp_flags); + return; + } memcpy(&cbo->co_res, ©->cp_res, sizeof(copy->cp_res)); memcpy(&cbo->co_fh, ©->fh, sizeof(copy->fh)); cbo->co_nfserr = copy->nfserr; cbo->co_retries = 5; - nfsd4_init_cb(&cbo->co_cb, copy->cp_clp, &nfsd4_cb_offload_ops, + /* + * Hold a reference on the client while the callback is in flight. + * Released in nfsd4_cb_offload_release(). + */ + kref_get(&clp->cl_nfsdfs.cl_ref); + + nfsd4_init_cb(&cbo->co_cb, clp, &nfsd4_cb_offload_ops, NFSPROC4_CLNT_CB_OFFLOAD); nfsd41_cb_referring_call(&cbo->co_cb, &cbo->co_referring_sessionid, cbo->co_referring_slotid, cbo->co_referring_seqno); - trace_nfsd_cb_offload(copy->cp_clp, &cbo->co_res.cb_stateid, + trace_nfsd_cb_offload(clp, &cbo->co_res.cb_stateid, &cbo->co_fh, copy->cp_count, copy->nfserr); nfsd4_try_run_cb(&cbo->co_cb); } @@ -1921,6 +2082,7 @@ static void nfsd4_send_cb_offload(struct nfsd4_copy *copy) static int nfsd4_do_async_copy(void *data) { struct nfsd4_copy *copy = (struct nfsd4_copy *)data; + __be32 nfserr = nfs_ok; trace_nfsd_copy_async(copy); if (nfsd4_ssc_is_inter(copy)) { @@ -1931,23 +2093,25 @@ static int nfsd4_do_async_copy(void *data) if (IS_ERR(filp)) { switch (PTR_ERR(filp)) { case -EBADF: - copy->nfserr = nfserr_wrong_type; + nfserr = nfserr_wrong_type; break; default: - copy->nfserr = nfserr_offload_denied; + nfserr = nfserr_offload_denied; } /* ss_mnt will be unmounted by the laundromat */ goto do_callback; } - copy->nfserr = nfsd4_do_copy(copy, filp, copy->nf_dst->nf_file, - false); + nfserr = nfsd4_do_copy(copy, filp, copy->nf_dst->nf_file, + false); nfsd4_cleanup_inter_ssc(copy->ss_nsui, filp, copy->nf_dst); } else { - copy->nfserr = nfsd4_do_copy(copy, copy->nf_src->nf_file, - copy->nf_dst->nf_file, false); + nfserr = nfsd4_do_copy(copy, copy->nf_src->nf_file, + copy->nf_dst->nf_file, false); } do_callback: + if (!test_bit(NFSD4_COPY_F_CB_ERROR, ©->cp_flags)) + copy->nfserr = nfserr; /* The kthread exits forthwith. Ensure that a subsequent * OFFLOAD_CANCEL won't try to kill it again. */ set_bit(NFSD4_COPY_F_STOPPED, ©->cp_flags); @@ -2271,7 +2435,7 @@ _nfsd4_verify(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate, if (status) return status; - status = check_attr_support(rqstp, cstate, verify->ve_bmval, NULL); + status = check_attr_support(cstate, verify->ve_bmval, NULL); if (status) return status; @@ -2281,6 +2445,11 @@ _nfsd4_verify(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate, if (verify->ve_attrlen & 3) return nfserr_inval; + /* The POSIX draft ACLs cannot be tested via (N)VERIFY. */ + if (verify->ve_bmval[2] & (FATTR4_WORD2_POSIX_DEFAULT_ACL | + FATTR4_WORD2_POSIX_ACCESS_ACL)) + return nfserr_inval; + /* count in words: * bitmap_len(1) + bitmap(2) + attr_len(1) = 4 */ @@ -3016,8 +3185,6 @@ encode_op: BUG_ON(cstate->replay_owner); out: cstate->status = status; - /* Reset deferral mechanism for RPC deferrals */ - set_bit(RQ_USEDEFERRAL, &rqstp->rq_flags); return rpc_success; } diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c index d5e0f3a52d4f..f5cb067a1e50 100644 --- a/fs/nfsd/nfs4state.c +++ b/fs/nfsd/nfs4state.c @@ -1253,7 +1253,7 @@ static void nfsd4_finalize_deleg_timestamps(struct nfs4_delegation *dp, struct f if (ret) { struct inode *inode = file_inode(f); - pr_notice_ratelimited("Unable to update timestamps on inode %02x:%02x:%lu: %d\n", + pr_notice_ratelimited("nfsd: Unable to update timestamps on inode %02x:%02x:%lu: %d\n", MAJOR(inode->i_sb->s_dev), MINOR(inode->i_sb->s_dev), inode->i_ino, ret); @@ -2413,7 +2413,13 @@ static void __free_client(struct kref *k) kmem_cache_free(client_slab, clp); } -static void drop_client(struct nfs4_client *clp) +/** + * nfsd4_put_client - release a reference on an nfs4_client + * @clp: the client to be released + * + * When the last reference is released, the client is freed. + */ +void nfsd4_put_client(struct nfs4_client *clp) { kref_put(&clp->cl_nfsdfs.cl_ref, __free_client); } @@ -2435,7 +2441,7 @@ free_client(struct nfs4_client *clp) clp->cl_nfsd_dentry = NULL; wake_up_all(&expiry_wq); } - drop_client(clp); + nfsd4_put_client(clp); } /* must be called under the client_lock */ @@ -2833,7 +2839,7 @@ static int client_info_show(struct seq_file *m, void *v) spin_unlock(&clp->cl_lock); seq_puts(m, "\n"); - drop_client(clp); + nfsd4_put_client(clp); return 0; } @@ -3099,7 +3105,7 @@ static int client_states_open(struct inode *inode, struct file *file) ret = seq_open(file, &states_seq_ops); if (ret) { - drop_client(clp); + nfsd4_put_client(clp); return ret; } s = file->private_data; @@ -3113,7 +3119,7 @@ static int client_opens_release(struct inode *inode, struct file *file) struct nfs4_client *clp = m->private; /* XXX: alternatively, we could get/drop in seq start/stop */ - drop_client(clp); + nfsd4_put_client(clp); return seq_release(inode, file); } @@ -3169,7 +3175,7 @@ static ssize_t client_ctl_write(struct file *file, const char __user *buf, if (!clp) return -ENXIO; force_expire_client(clp); - drop_client(clp); + nfsd4_put_client(clp); return 7; } @@ -3204,7 +3210,7 @@ nfsd4_cb_recall_any_release(struct nfsd4_callback *cb) { struct nfs4_client *clp = cb->cb_clp; - drop_client(clp); + nfsd4_put_client(clp); } static int @@ -6353,7 +6359,8 @@ nfs4_open_delegation(struct svc_rqst *rqstp, struct nfsd4_open *open, dp->dl_ctime = stat.ctime; dp->dl_mtime = stat.mtime; spin_lock(&f->f_lock); - f->f_mode |= FMODE_NOCMTIME; + if (deleg_ts) + f->f_mode |= FMODE_NOCMTIME; spin_unlock(&f->f_lock); trace_nfsd_deleg_write(&dp->dl_stid.sc_stateid); } else { @@ -6637,14 +6644,14 @@ bool nfsd4_force_end_grace(struct nfsd_net *nn) { if (!nn->client_tracking_ops) return false; - spin_lock(&nn->client_lock); - if (nn->grace_ended || !nn->client_tracking_active) { - spin_unlock(&nn->client_lock); + if (READ_ONCE(nn->grace_ended)) return false; - } + /* laundromat_work must be initialised now, though it might be disabled */ WRITE_ONCE(nn->grace_end_forced, true); + /* mod_delayed_work() doesn't queue work after + * nfs4_state_shutdown_net() has called disable_delayed_work_sync() + */ mod_delayed_work(laundry_wq, &nn->laundromat_work, 0); - spin_unlock(&nn->client_lock); return true; } @@ -8980,7 +8987,6 @@ static int nfs4_state_create_net(struct net *net) nn->boot_time = ktime_get_real_seconds(); nn->grace_ended = false; nn->grace_end_forced = false; - nn->client_tracking_active = false; nn->nfsd4_manager.block_opens = true; INIT_LIST_HEAD(&nn->nfsd4_manager.list); INIT_LIST_HEAD(&nn->client_lru); @@ -8995,6 +9001,8 @@ static int nfs4_state_create_net(struct net *net) INIT_LIST_HEAD(&nn->blocked_locks_lru); INIT_DELAYED_WORK(&nn->laundromat_work, laundromat_main); + /* Make sure this cannot run until client tracking is initialised */ + disable_delayed_work(&nn->laundromat_work); INIT_WORK(&nn->nfsd_shrinker_work, nfsd4_state_shrinker_worker); get_net(net); @@ -9062,9 +9070,7 @@ nfs4_state_start_net(struct net *net) locks_start_grace(net, &nn->nfsd4_manager); nfsd4_client_tracking_init(net); /* safe for laundromat to run now */ - spin_lock(&nn->client_lock); - nn->client_tracking_active = true; - spin_unlock(&nn->client_lock); + enable_delayed_work(&nn->laundromat_work); if (nn->track_reclaim_completes && nn->reclaim_str_hashtbl_size == 0) goto skip_grace; printk(KERN_INFO "NFSD: starting %lld-second grace period (net %x)\n", @@ -9113,10 +9119,7 @@ nfs4_state_shutdown_net(struct net *net) shrinker_free(nn->nfsd_client_shrinker); cancel_work_sync(&nn->nfsd_shrinker_work); - spin_lock(&nn->client_lock); - nn->client_tracking_active = false; - spin_unlock(&nn->client_lock); - cancel_delayed_work_sync(&nn->laundromat_work); + disable_delayed_work_sync(&nn->laundromat_work); locks_end_grace(&nn->nfsd4_manager); INIT_LIST_HEAD(&reaplist); @@ -9520,8 +9523,10 @@ nfsd_get_dir_deleg(struct nfsd4_compound_state *cstate, spin_unlock(&clp->cl_lock); spin_unlock(&state_lock); - if (!status) + if (!status) { + put_nfs4_file(fp); return dp; + } /* Something failed. Drop the lease and clean up the stid */ kernel_setlease(fp->fi_deleg_file->nf_file, F_UNLCK, NULL, (void **)&dp); @@ -9529,5 +9534,6 @@ out_put_stid: nfs4_put_stid(&dp->dl_stid); out_delegees: put_deleg_file(fp); + put_nfs4_file(fp); return ERR_PTR(status); } diff --git a/fs/nfsd/nfs4xdr.c b/fs/nfsd/nfs4xdr.c index 51ef97c25456..5172dbd0cb05 100644 --- a/fs/nfsd/nfs4xdr.c +++ b/fs/nfsd/nfs4xdr.c @@ -43,6 +43,7 @@ #include <linux/sunrpc/addr.h> #include <linux/xattr.h> #include <linux/vmalloc.h> +#include <linux/nfsacl.h> #include <uapi/linux/xattr.h> @@ -377,10 +378,111 @@ nfsd4_decode_security_label(struct nfsd4_compoundargs *argp, return nfs_ok; } +#ifdef CONFIG_NFSD_V4_POSIX_ACLS + +static short nfsd4_posixacetag4_to_tag(posixacetag4 tag) +{ + switch (tag) { + case POSIXACE4_TAG_USER_OBJ: return ACL_USER_OBJ; + case POSIXACE4_TAG_GROUP_OBJ: return ACL_GROUP_OBJ; + case POSIXACE4_TAG_USER: return ACL_USER; + case POSIXACE4_TAG_GROUP: return ACL_GROUP; + case POSIXACE4_TAG_MASK: return ACL_MASK; + case POSIXACE4_TAG_OTHER: return ACL_OTHER; + } + return ACL_OTHER; +} + +static __be32 +nfsd4_decode_posixace4(struct nfsd4_compoundargs *argp, + struct posix_acl_entry *ace) +{ + posixaceperm4 perm; + __be32 *p, status; + posixacetag4 tag; + u32 len; + + if (!xdrgen_decode_posixacetag4(argp->xdr, &tag)) + return nfserr_bad_xdr; + ace->e_tag = nfsd4_posixacetag4_to_tag(tag); + + if (!xdrgen_decode_posixaceperm4(argp->xdr, &perm)) + return nfserr_bad_xdr; + if (perm & ~S_IRWXO) + return nfserr_bad_xdr; + ace->e_perm = perm; + + if (xdr_stream_decode_u32(argp->xdr, &len) < 0) + return nfserr_bad_xdr; + p = xdr_inline_decode(argp->xdr, len); + if (!p) + return nfserr_bad_xdr; + switch (tag) { + case POSIXACE4_TAG_USER: + if (len > 0) + status = nfsd_map_name_to_uid(argp->rqstp, + (char *)p, len, &ace->e_uid); + else + status = nfserr_bad_xdr; + break; + case POSIXACE4_TAG_GROUP: + if (len > 0) + status = nfsd_map_name_to_gid(argp->rqstp, + (char *)p, len, &ace->e_gid); + else + status = nfserr_bad_xdr; + break; + default: + status = nfs_ok; + } + + return status; +} + +static noinline __be32 +nfsd4_decode_posixacl(struct nfsd4_compoundargs *argp, struct posix_acl **acl) +{ + struct posix_acl_entry *ace; + __be32 status; + u32 count; + + if (xdr_stream_decode_u32(argp->xdr, &count) < 0) + return nfserr_bad_xdr; + + *acl = posix_acl_alloc(count, GFP_KERNEL); + if (*acl == NULL) + return nfserr_resource; + + (*acl)->a_count = count; + for (ace = (*acl)->a_entries; ace < (*acl)->a_entries + count; ace++) { + status = nfsd4_decode_posixace4(argp, ace); + if (status) { + posix_acl_release(*acl); + *acl = NULL; + return status; + } + } + + /* + * posix_acl_valid() requires the ACEs to be sorted. + * If they are already sorted, sort_pacl_range() will return + * after one pass through the ACEs, since it implements bubble sort. + * Note that a count == 0 is used to delete a POSIX ACL and a count + * of 1 or 2 will always be found invalid by posix_acl_valid(). + */ + if (count >= 3) + sort_pacl_range(*acl, 0, count - 1); + + return nfs_ok; +} + +#endif /* CONFIG_NFSD_V4_POSIX_ACLS */ + static __be32 nfsd4_decode_fattr4(struct nfsd4_compoundargs *argp, u32 *bmval, u32 bmlen, struct iattr *iattr, struct nfs4_acl **acl, - struct xdr_netobj *label, int *umask) + struct xdr_netobj *label, int *umask, + struct posix_acl **dpaclp, struct posix_acl **paclp) { unsigned int starting_pos; u32 attrlist4_count; @@ -543,9 +645,40 @@ nfsd4_decode_fattr4(struct nfsd4_compoundargs *argp, u32 *bmval, u32 bmlen, ATTR_MTIME | ATTR_MTIME_SET | ATTR_DELEG; } + *dpaclp = NULL; + *paclp = NULL; +#ifdef CONFIG_NFSD_V4_POSIX_ACLS + if (bmval[2] & FATTR4_WORD2_POSIX_DEFAULT_ACL) { + struct posix_acl *dpacl; + + status = nfsd4_decode_posixacl(argp, &dpacl); + if (status) + return status; + *dpaclp = dpacl; + } + if (bmval[2] & FATTR4_WORD2_POSIX_ACCESS_ACL) { + struct posix_acl *pacl; + + status = nfsd4_decode_posixacl(argp, &pacl); + if (status) { + posix_acl_release(*dpaclp); + *dpaclp = NULL; + return status; + } + *paclp = pacl; + } +#endif /* CONFIG_NFSD_V4_POSIX_ACLS */ + /* request sanity: did attrlist4 contain the expected number of words? */ - if (attrlist4_count != xdr_stream_pos(argp->xdr) - starting_pos) + if (attrlist4_count != xdr_stream_pos(argp->xdr) - starting_pos) { +#ifdef CONFIG_NFSD_V4_POSIX_ACLS + posix_acl_release(*dpaclp); + posix_acl_release(*paclp); + *dpaclp = NULL; + *paclp = NULL; +#endif return nfserr_bad_xdr; + } return nfs_ok; } @@ -849,7 +982,8 @@ nfsd4_decode_create(struct nfsd4_compoundargs *argp, union nfsd4_op_u *u) status = nfsd4_decode_fattr4(argp, create->cr_bmval, ARRAY_SIZE(create->cr_bmval), &create->cr_iattr, &create->cr_acl, - &create->cr_label, &create->cr_umask); + &create->cr_label, &create->cr_umask, + &create->cr_dpacl, &create->cr_pacl); if (status) return status; @@ -1000,7 +1134,8 @@ nfsd4_decode_createhow4(struct nfsd4_compoundargs *argp, struct nfsd4_open *open status = nfsd4_decode_fattr4(argp, open->op_bmval, ARRAY_SIZE(open->op_bmval), &open->op_iattr, &open->op_acl, - &open->op_label, &open->op_umask); + &open->op_label, &open->op_umask, + &open->op_dpacl, &open->op_pacl); if (status) return status; break; @@ -1018,7 +1153,8 @@ nfsd4_decode_createhow4(struct nfsd4_compoundargs *argp, struct nfsd4_open *open status = nfsd4_decode_fattr4(argp, open->op_bmval, ARRAY_SIZE(open->op_bmval), &open->op_iattr, &open->op_acl, - &open->op_label, &open->op_umask); + &open->op_label, &open->op_umask, + &open->op_dpacl, &open->op_pacl); if (status) return status; break; @@ -1345,7 +1481,8 @@ nfsd4_decode_setattr(struct nfsd4_compoundargs *argp, union nfsd4_op_u *u) return nfsd4_decode_fattr4(argp, setattr->sa_bmval, ARRAY_SIZE(setattr->sa_bmval), &setattr->sa_iattr, &setattr->sa_acl, - &setattr->sa_label, NULL); + &setattr->sa_label, NULL, &setattr->sa_dpacl, + &setattr->sa_pacl); } static __be32 @@ -2849,6 +2986,89 @@ nfsd4_encode_security_label(struct xdr_stream *xdr, struct svc_rqst *rqstp, { return 0; } #endif +#ifdef CONFIG_NFSD_V4_POSIX_ACLS + +static int nfsd4_posix_tagtotype(short tag) +{ + switch (tag) { + case ACL_USER_OBJ: return POSIXACE4_TAG_USER_OBJ; + case ACL_GROUP_OBJ: return POSIXACE4_TAG_GROUP_OBJ; + case ACL_USER: return POSIXACE4_TAG_USER; + case ACL_GROUP: return POSIXACE4_TAG_GROUP; + case ACL_MASK: return POSIXACE4_TAG_MASK; + case ACL_OTHER: return POSIXACE4_TAG_OTHER; + default: return -EINVAL; + } +} + +static __be32 +nfsd4_encode_posixace4(struct xdr_stream *xdr, struct svc_rqst *rqstp, + struct posix_acl_entry *acep) +{ + __be32 status; + int type; + + type = nfsd4_posix_tagtotype(acep->e_tag); + if (type < 0) + return nfserr_resource; + if (!xdrgen_encode_posixacetag4(xdr, type)) + return nfserr_resource; + if (!xdrgen_encode_posixaceperm4(xdr, acep->e_perm)) + return nfserr_resource; + + /* who */ + switch (acep->e_tag) { + case ACL_USER_OBJ: + case ACL_GROUP_OBJ: + case ACL_MASK: + case ACL_OTHER: + if (xdr_stream_encode_u32(xdr, 0) != XDR_UNIT) + return nfserr_resource; + break; + case ACL_USER: + status = nfsd4_encode_user(xdr, rqstp, acep->e_uid); + if (status != nfs_ok) + return status; + break; + case ACL_GROUP: + status = nfsd4_encode_group(xdr, rqstp, acep->e_gid); + if (status != nfs_ok) + return status; + break; + default: + return nfserr_resource; + } + return nfs_ok; +} + +static __be32 +nfsd4_encode_posixacl(struct xdr_stream *xdr, struct svc_rqst *rqstp, + struct posix_acl *acl) +{ + __be32 status; + int i; + + if (!acl) { + if (xdr_stream_encode_u32(xdr, 0) != XDR_UNIT) + return nfserr_resource; + return nfs_ok; + } + + if (acl->a_count > NFS_ACL_MAX_ENTRIES) + return nfserr_resource; + if (xdr_stream_encode_u32(xdr, acl->a_count) != XDR_UNIT) + return nfserr_resource; + for (i = 0; i < acl->a_count; i++) { + status = nfsd4_encode_posixace4(xdr, rqstp, &acl->a_entries[i]); + if (status != nfs_ok) + return status; + } + + return nfs_ok; +} + +#endif /* CONFIG_NFSD_V4_POSIX_ACL */ + static __be32 fattr_handle_absent_fs(u32 *bmval0, u32 *bmval1, u32 *bmval2, u32 *rdattr_err) { /* As per referral draft: */ @@ -2930,6 +3150,10 @@ struct nfsd4_fattr_args { #ifdef CONFIG_NFSD_V4_SECURITY_LABEL struct lsm_context context; #endif +#ifdef CONFIG_NFSD_V4_POSIX_ACLS + struct posix_acl *dpacl; + struct posix_acl *pacl; +#endif u32 rdattr_err; bool contextsupport; bool ignore_crossmnt; @@ -3470,6 +3694,42 @@ static __be32 nfsd4_encode_fattr4_open_arguments(struct xdr_stream *xdr, return nfs_ok; } +#ifdef CONFIG_NFSD_V4_POSIX_ACLS + +static __be32 nfsd4_encode_fattr4_acl_trueform(struct xdr_stream *xdr, + const struct nfsd4_fattr_args *args) +{ + aclmodel4 trueform = ACL_MODEL_NONE; + + if (IS_POSIXACL(d_inode(args->dentry))) + trueform = ACL_MODEL_POSIX_DRAFT; + if (!xdrgen_encode_aclmodel4(xdr, trueform)) + return nfserr_resource; + return nfs_ok; +} + +static __be32 nfsd4_encode_fattr4_acl_trueform_scope(struct xdr_stream *xdr, + const struct nfsd4_fattr_args *args) +{ + if (!xdrgen_encode_aclscope4(xdr, ACL_SCOPE_FILE_SYSTEM)) + return nfserr_resource; + return nfs_ok; +} + +static __be32 nfsd4_encode_fattr4_posix_default_acl(struct xdr_stream *xdr, + const struct nfsd4_fattr_args *args) +{ + return nfsd4_encode_posixacl(xdr, args->rqstp, args->dpacl); +} + +static __be32 nfsd4_encode_fattr4_posix_access_acl(struct xdr_stream *xdr, + const struct nfsd4_fattr_args *args) +{ + return nfsd4_encode_posixacl(xdr, args->rqstp, args->pacl); +} + +#endif /* CONFIG_NFSD_V4_POSIX_ACLS */ + static const nfsd4_enc_attr nfsd4_enc_fattr4_encode_ops[] = { [FATTR4_SUPPORTED_ATTRS] = nfsd4_encode_fattr4_supported_attrs, [FATTR4_TYPE] = nfsd4_encode_fattr4_type, @@ -3573,6 +3833,22 @@ static const nfsd4_enc_attr nfsd4_enc_fattr4_encode_ops[] = { [FATTR4_TIME_DELEG_ACCESS] = nfsd4_encode_fattr4__inval, [FATTR4_TIME_DELEG_MODIFY] = nfsd4_encode_fattr4__inval, [FATTR4_OPEN_ARGUMENTS] = nfsd4_encode_fattr4_open_arguments, + + /* Reserved */ + [87] = nfsd4_encode_fattr4__inval, + [88] = nfsd4_encode_fattr4__inval, + +#ifdef CONFIG_NFSD_V4_POSIX_ACLS + [FATTR4_ACL_TRUEFORM] = nfsd4_encode_fattr4_acl_trueform, + [FATTR4_ACL_TRUEFORM_SCOPE] = nfsd4_encode_fattr4_acl_trueform_scope, + [FATTR4_POSIX_DEFAULT_ACL] = nfsd4_encode_fattr4_posix_default_acl, + [FATTR4_POSIX_ACCESS_ACL] = nfsd4_encode_fattr4_posix_access_acl, +#else + [FATTR4_ACL_TRUEFORM] = nfsd4_encode_fattr4__noop, + [FATTR4_ACL_TRUEFORM_SCOPE] = nfsd4_encode_fattr4__noop, + [FATTR4_POSIX_DEFAULT_ACL] = nfsd4_encode_fattr4__noop, + [FATTR4_POSIX_ACCESS_ACL] = nfsd4_encode_fattr4__noop, +#endif }; /* @@ -3613,6 +3889,10 @@ nfsd4_encode_fattr4(struct svc_rqst *rqstp, struct xdr_stream *xdr, #ifdef CONFIG_NFSD_V4_SECURITY_LABEL args.context.context = NULL; #endif +#ifdef CONFIG_NFSD_V4_POSIX_ACLS + args.dpacl = NULL; + args.pacl = NULL; +#endif /* * Make a local copy of the attribute bitmap that can be modified. @@ -3719,6 +3999,55 @@ nfsd4_encode_fattr4(struct svc_rqst *rqstp, struct xdr_stream *xdr, } #endif /* CONFIG_NFSD_V4_SECURITY_LABEL */ +#ifdef CONFIG_NFSD_V4_POSIX_ACLS + if (attrmask[2] & FATTR4_WORD2_POSIX_DEFAULT_ACL) { + struct inode *inode = d_inode(dentry); + struct posix_acl *dpacl; + + if (S_ISDIR(inode->i_mode)) { + dpacl = get_inode_acl(inode, ACL_TYPE_DEFAULT); + if (IS_ERR(dpacl)) { + switch (PTR_ERR(dpacl)) { + case -EOPNOTSUPP: + attrmask[2] &= ~FATTR4_WORD2_POSIX_DEFAULT_ACL; + break; + case -EINVAL: + status = nfserr_attrnotsupp; + goto out; + default: + err = PTR_ERR(dpacl); + goto out_nfserr; + } + } else { + args.dpacl = dpacl; + } + } + } + if (attrmask[2] & FATTR4_WORD2_POSIX_ACCESS_ACL) { + struct inode *inode = d_inode(dentry); + struct posix_acl *pacl; + + pacl = get_inode_acl(inode, ACL_TYPE_ACCESS); + if (!pacl) + pacl = posix_acl_from_mode(inode->i_mode, GFP_KERNEL); + if (IS_ERR(pacl)) { + switch (PTR_ERR(pacl)) { + case -EOPNOTSUPP: + attrmask[2] &= ~FATTR4_WORD2_POSIX_ACCESS_ACL; + break; + case -EINVAL: + status = nfserr_attrnotsupp; + goto out; + default: + err = PTR_ERR(pacl); + goto out_nfserr; + } + } else { + args.pacl = pacl; + } + } +#endif /* CONFIG_NFSD_V4_POSIX_ACLS */ + /* attrmask */ status = nfsd4_encode_bitmap4(xdr, attrmask[0], attrmask[1], attrmask[2]); @@ -3742,6 +4071,12 @@ nfsd4_encode_fattr4(struct svc_rqst *rqstp, struct xdr_stream *xdr, status = nfs_ok; out: +#ifdef CONFIG_NFSD_V4_POSIX_ACLS + if (args.dpacl) + posix_acl_release(args.dpacl); + if (args.pacl) + posix_acl_release(args.pacl); +#endif /* CONFIG_NFSD_V4_POSIX_ACLS */ #ifdef CONFIG_NFSD_V4_SECURITY_LABEL if (args.context.context) security_release_secctx(&args.context); @@ -6013,6 +6348,22 @@ nfs4svc_decode_compoundargs(struct svc_rqst *rqstp, struct xdr_stream *xdr) args->ops = args->iops; args->rqstp = rqstp; + /* + * NFSv4 operation decoders can invoke svc cache lookups + * that trigger svc_defer() when RQ_USEDEFERRAL is set, + * setting RQ_DROPME. This creates two problems: + * + * 1. Non-idempotency: Compounds make it too hard to avoid + * problems if a request is deferred and replayed. + * + * 2. Session slot leakage (NFSv4.1+): If RQ_DROPME is set + * during decode but SEQUENCE executes successfully, the + * session slot will be marked INUSE. The request is then + * dropped before encoding, so the slot is never released, + * rendering it permanently unusable by the client. + */ + clear_bit(RQ_USEDEFERRAL, &rqstp->rq_flags); + return nfsd4_decode_compound(args); } diff --git a/fs/nfsd/nfs4xdr_gen.c b/fs/nfsd/nfs4xdr_gen.c index a17b5d8e60b3..824497051b87 100644 --- a/fs/nfsd/nfs4xdr_gen.c +++ b/fs/nfsd/nfs4xdr_gen.c @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-2.0 // Generated by xdrgen. Manual edits will be lost. // XDR specification file: ../../Documentation/sunrpc/xdr/nfs4_1.x -// XDR specification modification time: Mon Oct 14 09:10:13 2024 +// XDR specification modification time: Thu Jan 8 23:12:07 2026 #include <linux/sunrpc/svc.h> @@ -11,13 +11,13 @@ static bool __maybe_unused xdrgen_decode_int64_t(struct xdr_stream *xdr, int64_t *ptr) { return xdrgen_decode_hyper(xdr, ptr); -}; +} static bool __maybe_unused xdrgen_decode_uint32_t(struct xdr_stream *xdr, uint32_t *ptr) { return xdrgen_decode_unsigned_int(xdr, ptr); -}; +} static bool __maybe_unused xdrgen_decode_bitmap4(struct xdr_stream *xdr, bitmap4 *ptr) @@ -28,7 +28,31 @@ xdrgen_decode_bitmap4(struct xdr_stream *xdr, bitmap4 *ptr) if (!xdrgen_decode_uint32_t(xdr, &ptr->element[i])) return false; return true; -}; +} + +static bool __maybe_unused +xdrgen_decode_utf8string(struct xdr_stream *xdr, utf8string *ptr) +{ + return xdrgen_decode_opaque(xdr, ptr, 0); +} + +static bool __maybe_unused +xdrgen_decode_utf8str_cis(struct xdr_stream *xdr, utf8str_cis *ptr) +{ + return xdrgen_decode_utf8string(xdr, ptr); +} + +static bool __maybe_unused +xdrgen_decode_utf8str_cs(struct xdr_stream *xdr, utf8str_cs *ptr) +{ + return xdrgen_decode_utf8string(xdr, ptr); +} + +static bool __maybe_unused +xdrgen_decode_utf8str_mixed(struct xdr_stream *xdr, utf8str_mixed *ptr) +{ + return xdrgen_decode_utf8string(xdr, ptr); +} static bool __maybe_unused xdrgen_decode_nfstime4(struct xdr_stream *xdr, struct nfstime4 *ptr) @@ -38,13 +62,13 @@ xdrgen_decode_nfstime4(struct xdr_stream *xdr, struct nfstime4 *ptr) if (!xdrgen_decode_uint32_t(xdr, &ptr->nseconds)) return false; return true; -}; +} static bool __maybe_unused xdrgen_decode_fattr4_offline(struct xdr_stream *xdr, fattr4_offline *ptr) { return xdrgen_decode_bool(xdr, ptr); -}; +} static bool __maybe_unused xdrgen_decode_open_arguments4(struct xdr_stream *xdr, struct open_arguments4 *ptr) @@ -60,7 +84,7 @@ xdrgen_decode_open_arguments4(struct xdr_stream *xdr, struct open_arguments4 *pt if (!xdrgen_decode_bitmap4(xdr, &ptr->oa_create_mode)) return false; return true; -}; +} static bool __maybe_unused xdrgen_decode_open_args_share_access4(struct xdr_stream *xdr, open_args_share_access4 *ptr) @@ -69,6 +93,15 @@ xdrgen_decode_open_args_share_access4(struct xdr_stream *xdr, open_args_share_ac if (xdr_stream_decode_u32(xdr, &val) < 0) return false; + /* Compiler may optimize to a range check for dense enums */ + switch (val) { + case OPEN_ARGS_SHARE_ACCESS_READ: + case OPEN_ARGS_SHARE_ACCESS_WRITE: + case OPEN_ARGS_SHARE_ACCESS_BOTH: + break; + default: + return false; + } *ptr = val; return true; } @@ -80,6 +113,16 @@ xdrgen_decode_open_args_share_deny4(struct xdr_stream *xdr, open_args_share_deny if (xdr_stream_decode_u32(xdr, &val) < 0) return false; + /* Compiler may optimize to a range check for dense enums */ + switch (val) { + case OPEN_ARGS_SHARE_DENY_NONE: + case OPEN_ARGS_SHARE_DENY_READ: + case OPEN_ARGS_SHARE_DENY_WRITE: + case OPEN_ARGS_SHARE_DENY_BOTH: + break; + default: + return false; + } *ptr = val; return true; } @@ -91,6 +134,19 @@ xdrgen_decode_open_args_share_access_want4(struct xdr_stream *xdr, open_args_sha if (xdr_stream_decode_u32(xdr, &val) < 0) return false; + /* Compiler may optimize to a range check for dense enums */ + switch (val) { + case OPEN_ARGS_SHARE_ACCESS_WANT_ANY_DELEG: + case OPEN_ARGS_SHARE_ACCESS_WANT_NO_DELEG: + case OPEN_ARGS_SHARE_ACCESS_WANT_CANCEL: + case OPEN_ARGS_SHARE_ACCESS_WANT_SIGNAL_DELEG_WHEN_RESRC_AVAIL: + case OPEN_ARGS_SHARE_ACCESS_WANT_PUSH_DELEG_WHEN_UNCONTENDED: + case OPEN_ARGS_SHARE_ACCESS_WANT_DELEG_TIMESTAMPS: + case OPEN_ARGS_SHARE_ACCESS_WANT_OPEN_XOR_DELEGATION: + break; + default: + return false; + } *ptr = val; return true; } @@ -102,6 +158,19 @@ xdrgen_decode_open_args_open_claim4(struct xdr_stream *xdr, open_args_open_claim if (xdr_stream_decode_u32(xdr, &val) < 0) return false; + /* Compiler may optimize to a range check for dense enums */ + switch (val) { + case OPEN_ARGS_OPEN_CLAIM_NULL: + case OPEN_ARGS_OPEN_CLAIM_PREVIOUS: + case OPEN_ARGS_OPEN_CLAIM_DELEGATE_CUR: + case OPEN_ARGS_OPEN_CLAIM_DELEGATE_PREV: + case OPEN_ARGS_OPEN_CLAIM_FH: + case OPEN_ARGS_OPEN_CLAIM_DELEG_CUR_FH: + case OPEN_ARGS_OPEN_CLAIM_DELEG_PREV_FH: + break; + default: + return false; + } *ptr = val; return true; } @@ -113,6 +182,16 @@ xdrgen_decode_open_args_createmode4(struct xdr_stream *xdr, open_args_createmode if (xdr_stream_decode_u32(xdr, &val) < 0) return false; + /* Compiler may optimize to a range check for dense enums */ + switch (val) { + case OPEN_ARGS_CREATEMODE_UNCHECKED4: + case OPEN_ARGS_CREATE_MODE_GUARDED: + case OPEN_ARGS_CREATEMODE_EXCLUSIVE4: + case OPEN_ARGS_CREATE_MODE_EXCLUSIVE4_1: + break; + default: + return false; + } *ptr = val; return true; } @@ -121,19 +200,28 @@ bool xdrgen_decode_fattr4_open_arguments(struct xdr_stream *xdr, fattr4_open_arguments *ptr) { return xdrgen_decode_open_arguments4(xdr, ptr); -}; +} + +/* + * Determine what OPEN supports. + */ bool xdrgen_decode_fattr4_time_deleg_access(struct xdr_stream *xdr, fattr4_time_deleg_access *ptr) { return xdrgen_decode_nfstime4(xdr, ptr); -}; +} bool xdrgen_decode_fattr4_time_deleg_modify(struct xdr_stream *xdr, fattr4_time_deleg_modify *ptr) { return xdrgen_decode_nfstime4(xdr, ptr); -}; +} + +/* + * New RECOMMENDED Attribute for + * delegation caching of times + */ static bool __maybe_unused xdrgen_decode_open_delegation_type4(struct xdr_stream *xdr, open_delegation_type4 *ptr) @@ -142,21 +230,152 @@ xdrgen_decode_open_delegation_type4(struct xdr_stream *xdr, open_delegation_type if (xdr_stream_decode_u32(xdr, &val) < 0) return false; + /* Compiler may optimize to a range check for dense enums */ + switch (val) { + case OPEN_DELEGATE_NONE: + case OPEN_DELEGATE_READ: + case OPEN_DELEGATE_WRITE: + case OPEN_DELEGATE_NONE_EXT: + case OPEN_DELEGATE_READ_ATTRS_DELEG: + case OPEN_DELEGATE_WRITE_ATTRS_DELEG: + break; + default: + return false; + } + *ptr = val; + return true; +} + +bool +xdrgen_decode_aclmodel4(struct xdr_stream *xdr, aclmodel4 *ptr) +{ + u32 val; + + if (xdr_stream_decode_u32(xdr, &val) < 0) + return false; + /* Compiler may optimize to a range check for dense enums */ + switch (val) { + case ACL_MODEL_NFS4: + case ACL_MODEL_POSIX_DRAFT: + case ACL_MODEL_NONE: + break; + default: + return false; + } + *ptr = val; + return true; +} + +bool +xdrgen_decode_aclscope4(struct xdr_stream *xdr, aclscope4 *ptr) +{ + u32 val; + + if (xdr_stream_decode_u32(xdr, &val) < 0) + return false; + /* Compiler may optimize to a range check for dense enums */ + switch (val) { + case ACL_SCOPE_FILE_OBJECT: + case ACL_SCOPE_FILE_SYSTEM: + case ACL_SCOPE_SERVER: + break; + default: + return false; + } *ptr = val; return true; } +bool +xdrgen_decode_posixacetag4(struct xdr_stream *xdr, posixacetag4 *ptr) +{ + u32 val; + + if (xdr_stream_decode_u32(xdr, &val) < 0) + return false; + /* Compiler may optimize to a range check for dense enums */ + switch (val) { + case POSIXACE4_TAG_USER_OBJ: + case POSIXACE4_TAG_USER: + case POSIXACE4_TAG_GROUP_OBJ: + case POSIXACE4_TAG_GROUP: + case POSIXACE4_TAG_MASK: + case POSIXACE4_TAG_OTHER: + break; + default: + return false; + } + *ptr = val; + return true; +} + +bool +xdrgen_decode_posixaceperm4(struct xdr_stream *xdr, posixaceperm4 *ptr) +{ + return xdrgen_decode_uint32_t(xdr, ptr); +} + +static bool __maybe_unused +xdrgen_decode_posixace4(struct xdr_stream *xdr, struct posixace4 *ptr) +{ + if (!xdrgen_decode_posixacetag4(xdr, &ptr->tag)) + return false; + if (!xdrgen_decode_posixaceperm4(xdr, &ptr->perm)) + return false; + if (!xdrgen_decode_utf8str_mixed(xdr, &ptr->who)) + return false; + return true; +} + +static bool __maybe_unused +xdrgen_decode_fattr4_acl_trueform(struct xdr_stream *xdr, fattr4_acl_trueform *ptr) +{ + return xdrgen_decode_aclmodel4(xdr, ptr); +} + +static bool __maybe_unused +xdrgen_decode_fattr4_acl_trueform_scope(struct xdr_stream *xdr, fattr4_acl_trueform_scope *ptr) +{ + return xdrgen_decode_aclscope4(xdr, ptr); +} + +static bool __maybe_unused +xdrgen_decode_fattr4_posix_default_acl(struct xdr_stream *xdr, fattr4_posix_default_acl *ptr) +{ + if (xdr_stream_decode_u32(xdr, &ptr->count) < 0) + return false; + for (u32 i = 0; i < ptr->count; i++) + if (!xdrgen_decode_posixace4(xdr, &ptr->element[i])) + return false; + return true; +} + +static bool __maybe_unused +xdrgen_decode_fattr4_posix_access_acl(struct xdr_stream *xdr, fattr4_posix_access_acl *ptr) +{ + if (xdr_stream_decode_u32(xdr, &ptr->count) < 0) + return false; + for (u32 i = 0; i < ptr->count; i++) + if (!xdrgen_decode_posixace4(xdr, &ptr->element[i])) + return false; + return true; +} + +/* + * New for POSIX ACL extension + */ + static bool __maybe_unused xdrgen_encode_int64_t(struct xdr_stream *xdr, const int64_t value) { return xdrgen_encode_hyper(xdr, value); -}; +} static bool __maybe_unused xdrgen_encode_uint32_t(struct xdr_stream *xdr, const uint32_t value) { return xdrgen_encode_unsigned_int(xdr, value); -}; +} static bool __maybe_unused xdrgen_encode_bitmap4(struct xdr_stream *xdr, const bitmap4 value) @@ -167,7 +386,31 @@ xdrgen_encode_bitmap4(struct xdr_stream *xdr, const bitmap4 value) if (!xdrgen_encode_uint32_t(xdr, value.element[i])) return false; return true; -}; +} + +static bool __maybe_unused +xdrgen_encode_utf8string(struct xdr_stream *xdr, const utf8string value) +{ + return xdr_stream_encode_opaque(xdr, value.data, value.len) >= 0; +} + +static bool __maybe_unused +xdrgen_encode_utf8str_cis(struct xdr_stream *xdr, const utf8str_cis value) +{ + return xdrgen_encode_utf8string(xdr, value); +} + +static bool __maybe_unused +xdrgen_encode_utf8str_cs(struct xdr_stream *xdr, const utf8str_cs value) +{ + return xdrgen_encode_utf8string(xdr, value); +} + +static bool __maybe_unused +xdrgen_encode_utf8str_mixed(struct xdr_stream *xdr, const utf8str_mixed value) +{ + return xdrgen_encode_utf8string(xdr, value); +} static bool __maybe_unused xdrgen_encode_nfstime4(struct xdr_stream *xdr, const struct nfstime4 *value) @@ -177,13 +420,13 @@ xdrgen_encode_nfstime4(struct xdr_stream *xdr, const struct nfstime4 *value) if (!xdrgen_encode_uint32_t(xdr, value->nseconds)) return false; return true; -}; +} static bool __maybe_unused xdrgen_encode_fattr4_offline(struct xdr_stream *xdr, const fattr4_offline value) { return xdrgen_encode_bool(xdr, value); -}; +} static bool __maybe_unused xdrgen_encode_open_arguments4(struct xdr_stream *xdr, const struct open_arguments4 *value) @@ -199,7 +442,7 @@ xdrgen_encode_open_arguments4(struct xdr_stream *xdr, const struct open_argument if (!xdrgen_encode_bitmap4(xdr, value->oa_create_mode)) return false; return true; -}; +} static bool __maybe_unused xdrgen_encode_open_args_share_access4(struct xdr_stream *xdr, open_args_share_access4 value) @@ -235,22 +478,92 @@ bool xdrgen_encode_fattr4_open_arguments(struct xdr_stream *xdr, const fattr4_open_arguments *value) { return xdrgen_encode_open_arguments4(xdr, value); -}; +} bool xdrgen_encode_fattr4_time_deleg_access(struct xdr_stream *xdr, const fattr4_time_deleg_access *value) { return xdrgen_encode_nfstime4(xdr, value); -}; +} bool xdrgen_encode_fattr4_time_deleg_modify(struct xdr_stream *xdr, const fattr4_time_deleg_modify *value) { return xdrgen_encode_nfstime4(xdr, value); -}; +} static bool __maybe_unused xdrgen_encode_open_delegation_type4(struct xdr_stream *xdr, open_delegation_type4 value) { return xdr_stream_encode_u32(xdr, value) == XDR_UNIT; } + +bool +xdrgen_encode_aclmodel4(struct xdr_stream *xdr, aclmodel4 value) +{ + return xdr_stream_encode_u32(xdr, value) == XDR_UNIT; +} + +bool +xdrgen_encode_aclscope4(struct xdr_stream *xdr, aclscope4 value) +{ + return xdr_stream_encode_u32(xdr, value) == XDR_UNIT; +} + +bool +xdrgen_encode_posixacetag4(struct xdr_stream *xdr, posixacetag4 value) +{ + return xdr_stream_encode_u32(xdr, value) == XDR_UNIT; +} + +bool +xdrgen_encode_posixaceperm4(struct xdr_stream *xdr, const posixaceperm4 value) +{ + return xdrgen_encode_uint32_t(xdr, value); +} + +static bool __maybe_unused +xdrgen_encode_posixace4(struct xdr_stream *xdr, const struct posixace4 *value) +{ + if (!xdrgen_encode_posixacetag4(xdr, value->tag)) + return false; + if (!xdrgen_encode_posixaceperm4(xdr, value->perm)) + return false; + if (!xdrgen_encode_utf8str_mixed(xdr, value->who)) + return false; + return true; +} + +static bool __maybe_unused +xdrgen_encode_fattr4_acl_trueform(struct xdr_stream *xdr, const fattr4_acl_trueform value) +{ + return xdrgen_encode_aclmodel4(xdr, value); +} + +static bool __maybe_unused +xdrgen_encode_fattr4_acl_trueform_scope(struct xdr_stream *xdr, const fattr4_acl_trueform_scope value) +{ + return xdrgen_encode_aclscope4(xdr, value); +} + +static bool __maybe_unused +xdrgen_encode_fattr4_posix_default_acl(struct xdr_stream *xdr, const fattr4_posix_default_acl value) +{ + if (xdr_stream_encode_u32(xdr, value.count) != XDR_UNIT) + return false; + for (u32 i = 0; i < value.count; i++) + if (!xdrgen_encode_posixace4(xdr, &value.element[i])) + return false; + return true; +} + +static bool __maybe_unused +xdrgen_encode_fattr4_posix_access_acl(struct xdr_stream *xdr, const fattr4_posix_access_acl value) +{ + if (xdr_stream_encode_u32(xdr, value.count) != XDR_UNIT) + return false; + for (u32 i = 0; i < value.count; i++) + if (!xdrgen_encode_posixace4(xdr, &value.element[i])) + return false; + return true; +} diff --git a/fs/nfsd/nfs4xdr_gen.h b/fs/nfsd/nfs4xdr_gen.h index 41a0033b7256..1c487f1a11ab 100644 --- a/fs/nfsd/nfs4xdr_gen.h +++ b/fs/nfsd/nfs4xdr_gen.h @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: GPL-2.0 */ /* Generated by xdrgen. Manual edits will be lost. */ /* XDR specification file: ../../Documentation/sunrpc/xdr/nfs4_1.x */ -/* XDR specification modification time: Mon Oct 14 09:10:13 2024 */ +/* XDR specification modification time: Thu Jan 8 23:12:07 2026 */ #ifndef _LINUX_XDRGEN_NFS4_1_DECL_H #define _LINUX_XDRGEN_NFS4_1_DECL_H @@ -21,5 +21,15 @@ bool xdrgen_encode_fattr4_time_deleg_access(struct xdr_stream *xdr, const fattr4 bool xdrgen_decode_fattr4_time_deleg_modify(struct xdr_stream *xdr, fattr4_time_deleg_modify *ptr); bool xdrgen_encode_fattr4_time_deleg_modify(struct xdr_stream *xdr, const fattr4_time_deleg_modify *value); +bool xdrgen_decode_aclmodel4(struct xdr_stream *xdr, aclmodel4 *ptr); +bool xdrgen_encode_aclmodel4(struct xdr_stream *xdr, aclmodel4 value); +bool xdrgen_decode_aclscope4(struct xdr_stream *xdr, aclscope4 *ptr); +bool xdrgen_encode_aclscope4(struct xdr_stream *xdr, aclscope4 value); +bool xdrgen_decode_posixacetag4(struct xdr_stream *xdr, posixacetag4 *ptr); +bool xdrgen_encode_posixacetag4(struct xdr_stream *xdr, posixacetag4 value); + +bool xdrgen_decode_posixaceperm4(struct xdr_stream *xdr, posixaceperm4 *ptr); +bool xdrgen_encode_posixaceperm4(struct xdr_stream *xdr, const posixaceperm4 value); + #endif /* _LINUX_XDRGEN_NFS4_1_DECL_H */ diff --git a/fs/nfsd/nfsctl.c b/fs/nfsd/nfsctl.c index 084fc517e9e1..89fe2c0e8d44 100644 --- a/fs/nfsd/nfsctl.c +++ b/fs/nfsd/nfsctl.c @@ -285,6 +285,7 @@ static ssize_t write_unlock_fs(struct file *file, char *buf, size_t size) * 2. Is that directory a mount point, or * 3. Is that directory the root of an exported file system? */ + nfsd4_cancel_copy_by_sb(netns(file), path.dentry->d_sb); error = nlmsvc_unlock_all_by_sb(path.dentry->d_sb); mutex_lock(&nfsd_mutex); nn = net_generic(netns(file), nfsd_net_id); @@ -1642,6 +1643,10 @@ int nfsd_nl_threads_set_doit(struct sk_buff *skb, struct genl_info *info) scope = nla_data(attr); } + attr = info->attrs[NFSD_A_SERVER_MIN_THREADS]; + if (attr) + nn->min_threads = nla_get_u32(attr); + ret = nfsd_svc(nrpools, nthreads, net, get_current_cred(), scope); if (ret > 0) ret = 0; @@ -1681,6 +1686,8 @@ int nfsd_nl_threads_get_doit(struct sk_buff *skb, struct genl_info *info) nn->nfsd4_grace) || nla_put_u32(skb, NFSD_A_SERVER_LEASETIME, nn->nfsd4_lease) || + nla_put_u32(skb, NFSD_A_SERVER_MIN_THREADS, + nn->min_threads) || nla_put_string(skb, NFSD_A_SERVER_SCOPE, nn->nfsd_name); if (err) diff --git a/fs/nfsd/nfsd.h b/fs/nfsd/nfsd.h index b0283213a8f5..a01d70953358 100644 --- a/fs/nfsd/nfsd.h +++ b/fs/nfsd/nfsd.h @@ -454,6 +454,16 @@ enum { #define NFSD4_2_SECURITY_ATTRS 0 #endif +#ifdef CONFIG_NFSD_V4_POSIX_ACLS +#define NFSD4_2_POSIX_ACL_ATTRS \ + (FATTR4_WORD2_ACL_TRUEFORM | \ + FATTR4_WORD2_ACL_TRUEFORM_SCOPE | \ + FATTR4_WORD2_POSIX_DEFAULT_ACL | \ + FATTR4_WORD2_POSIX_ACCESS_ACL) +#else +#define NFSD4_2_POSIX_ACL_ATTRS 0 +#endif + #define NFSD4_2_SUPPORTED_ATTRS_WORD2 \ (NFSD4_1_SUPPORTED_ATTRS_WORD2 | \ FATTR4_WORD2_MODE_UMASK | \ @@ -462,7 +472,8 @@ enum { FATTR4_WORD2_XATTR_SUPPORT | \ FATTR4_WORD2_TIME_DELEG_ACCESS | \ FATTR4_WORD2_TIME_DELEG_MODIFY | \ - FATTR4_WORD2_OPEN_ARGUMENTS) + FATTR4_WORD2_OPEN_ARGUMENTS | \ + NFSD4_2_POSIX_ACL_ATTRS) extern const u32 nfsd_suppattrs[3][3]; @@ -530,11 +541,18 @@ static inline bool nfsd_attrs_supported(u32 minorversion, const u32 *bmval) #else #define MAYBE_FATTR4_WORD2_SECURITY_LABEL 0 #endif +#ifdef CONFIG_NFSD_V4_POSIX_ACLS +#define MAYBE_FATTR4_WORD2_POSIX_ACL_ATTRS \ + FATTR4_WORD2_POSIX_DEFAULT_ACL | FATTR4_WORD2_POSIX_ACCESS_ACL +#else +#define MAYBE_FATTR4_WORD2_POSIX_ACL_ATTRS 0 +#endif #define NFSD_WRITEABLE_ATTRS_WORD2 \ (FATTR4_WORD2_MODE_UMASK \ | MAYBE_FATTR4_WORD2_SECURITY_LABEL \ | FATTR4_WORD2_TIME_DELEG_ACCESS \ | FATTR4_WORD2_TIME_DELEG_MODIFY \ + | MAYBE_FATTR4_WORD2_POSIX_ACL_ATTRS \ ) #define NFSD_SUPPATTR_EXCLCREAT_WORD0 \ @@ -550,6 +568,10 @@ static inline bool nfsd_attrs_supported(u32 minorversion, const u32 *bmval) * The FATTR4_WORD2_TIME_DELEG attributes are not to be allowed for * OPEN(create) with EXCLUSIVE4_1. It doesn't make sense to set a * delegated timestamp on a new file. + * + * This mask includes NFSv4.2-only attributes (e.g., POSIX ACLs). + * Version filtering occurs via nfsd_suppattrs[] before this mask + * is applied, so pre-4.2 clients never see unsupported attributes. */ #define NFSD_SUPPATTR_EXCLCREAT_WORD2 \ (NFSD_WRITEABLE_ATTRS_WORD2 & \ diff --git a/fs/nfsd/nfsproc.c b/fs/nfsd/nfsproc.c index 481e789a7697..8873033d1e82 100644 --- a/fs/nfsd/nfsproc.c +++ b/fs/nfsd/nfsproc.c @@ -33,7 +33,7 @@ static __be32 nfsd_map_status(__be32 status) break; case nfserr_symlink: case nfserr_wrong_type: - status = nfserr_inval; + status = nfserr_io; break; } return status; diff --git a/fs/nfsd/nfssvc.c b/fs/nfsd/nfssvc.c index f1cc223ecee2..0887ee601d3c 100644 --- a/fs/nfsd/nfssvc.c +++ b/fs/nfsd/nfssvc.c @@ -580,7 +580,7 @@ void nfsd_shutdown_threads(struct net *net) } /* Kill outstanding nfsd threads */ - svc_set_num_threads(serv, NULL, 0); + svc_set_num_threads(serv, 0, 0); nfsd_destroy_serv(net); mutex_unlock(&nfsd_mutex); } @@ -688,12 +688,9 @@ int nfsd_set_nrthreads(int n, int *nthreads, struct net *net) if (nn->nfsd_serv == NULL || n <= 0) return 0; - /* - * Special case: When n == 1, pass in NULL for the pool, so that the - * change is distributed equally among them. - */ + /* Special case: When n == 1, distribute threads equally among pools. */ if (n == 1) - return svc_set_num_threads(nn->nfsd_serv, NULL, nthreads[0]); + return svc_set_num_threads(nn->nfsd_serv, nn->min_threads, nthreads[0]); if (n > nn->nfsd_serv->sv_nrpools) n = nn->nfsd_serv->sv_nrpools; @@ -719,18 +716,18 @@ int nfsd_set_nrthreads(int n, int *nthreads, struct net *net) /* apply the new numbers */ for (i = 0; i < n; i++) { - err = svc_set_num_threads(nn->nfsd_serv, - &nn->nfsd_serv->sv_pools[i], - nthreads[i]); + err = svc_set_pool_threads(nn->nfsd_serv, + &nn->nfsd_serv->sv_pools[i], + nn->min_threads, nthreads[i]); if (err) goto out; } /* Anything undefined in array is considered to be 0 */ for (i = n; i < nn->nfsd_serv->sv_nrpools; ++i) { - err = svc_set_num_threads(nn->nfsd_serv, - &nn->nfsd_serv->sv_pools[i], - 0); + err = svc_set_pool_threads(nn->nfsd_serv, + &nn->nfsd_serv->sv_pools[i], + 0, 0); if (err) goto out; } @@ -885,9 +882,11 @@ static int nfsd(void *vrqstp) { struct svc_rqst *rqstp = (struct svc_rqst *) vrqstp; + struct svc_pool *pool = rqstp->rq_pool; struct svc_xprt *perm_sock = list_entry(rqstp->rq_server->sv_permsocks.next, typeof(struct svc_xprt), xpt_list); struct net *net = perm_sock->xpt_net; struct nfsd_net *nn = net_generic(net, nfsd_net_id); + bool have_mutex = false; /* At this point, the thread shares current->fs * with the init process. We need to create files with the @@ -905,7 +904,44 @@ nfsd(void *vrqstp) * The main request loop */ while (!svc_thread_should_stop(rqstp)) { - svc_recv(rqstp); + switch (svc_recv(rqstp, 5 * HZ)) { + case -ETIMEDOUT: + /* No work arrived within the timeout window */ + if (mutex_trylock(&nfsd_mutex)) { + if (pool->sp_nrthreads > pool->sp_nrthrmin) { + trace_nfsd_dynthread_kill(net, pool); + set_bit(RQ_VICTIM, &rqstp->rq_flags); + have_mutex = true; + } else { + mutex_unlock(&nfsd_mutex); + } + } else { + trace_nfsd_dynthread_trylock_fail(net, pool); + } + break; + case -EBUSY: + /* No idle threads; consider spawning another */ + if (pool->sp_nrthreads < pool->sp_nrthrmax) { + if (mutex_trylock(&nfsd_mutex)) { + if (pool->sp_nrthreads < pool->sp_nrthrmax) { + int ret; + + trace_nfsd_dynthread_start(net, pool); + ret = svc_new_thread(rqstp->rq_server, pool); + if (ret) + pr_notice_ratelimited("%s: unable to spawn new thread: %d\n", + __func__, ret); + } + mutex_unlock(&nfsd_mutex); + } else { + trace_nfsd_dynthread_trylock_fail(net, pool); + } + } + clear_bit(SP_TASK_STARTING, &pool->sp_flags); + break; + default: + break; + } nfsd_file_net_dispose(nn); } @@ -913,6 +949,8 @@ nfsd(void *vrqstp) /* Release the thread */ svc_exit_thread(rqstp); + if (have_mutex) + mutex_unlock(&nfsd_mutex); return 0; } diff --git a/fs/nfsd/state.h b/fs/nfsd/state.h index 508b7e36d846..6fcbf1e427d4 100644 --- a/fs/nfsd/state.h +++ b/fs/nfsd/state.h @@ -822,6 +822,7 @@ static inline void nfsd4_try_run_cb(struct nfsd4_callback *cb) extern void nfsd4_shutdown_callback(struct nfs4_client *); extern void nfsd4_shutdown_copy(struct nfs4_client *clp); +void nfsd4_put_client(struct nfs4_client *clp); void nfsd4_async_copy_reaper(struct nfsd_net *nn); bool nfsd4_has_active_async_copies(struct nfs4_client *clp); extern struct nfs4_client_reclaim *nfs4_client_to_reclaim(struct xdr_netobj name, @@ -842,10 +843,14 @@ struct nfsd_file *find_any_file(struct nfs4_file *f); #ifdef CONFIG_NFSD_V4 void nfsd4_revoke_states(struct nfsd_net *nn, struct super_block *sb); +void nfsd4_cancel_copy_by_sb(struct net *net, struct super_block *sb); #else static inline void nfsd4_revoke_states(struct nfsd_net *nn, struct super_block *sb) { } +static inline void nfsd4_cancel_copy_by_sb(struct net *net, struct super_block *sb) +{ +} #endif /* grace period management */ diff --git a/fs/nfsd/trace.h b/fs/nfsd/trace.h index 5ae2a611e57f..d1d0b0dd0545 100644 --- a/fs/nfsd/trace.h +++ b/fs/nfsd/trace.h @@ -91,6 +91,41 @@ DEFINE_EVENT(nfsd_xdr_err_class, nfsd_##name##_err, \ DEFINE_NFSD_XDR_ERR_EVENT(garbage_args); DEFINE_NFSD_XDR_ERR_EVENT(cant_encode); +DECLARE_EVENT_CLASS(nfsd_dynthread_class, + TP_PROTO( + const struct net *net, + const struct svc_pool *pool + ), + TP_ARGS(net, pool), + TP_STRUCT__entry( + __field(unsigned int, netns_ino) + __field(unsigned int, pool_id) + __field(unsigned int, nrthreads) + __field(unsigned int, nrthrmin) + __field(unsigned int, nrthrmax) + ), + TP_fast_assign( + __entry->netns_ino = net->ns.inum; + __entry->pool_id = pool->sp_id; + __entry->nrthreads = pool->sp_nrthreads; + __entry->nrthrmin = pool->sp_nrthrmin; + __entry->nrthrmax = pool->sp_nrthrmax; + ), + TP_printk("pool=%u nrthreads=%u nrthrmin=%u nrthrmax=%u", + __entry->pool_id, __entry->nrthreads, + __entry->nrthrmin, __entry->nrthrmax + ) +); + +#define DEFINE_NFSD_DYNTHREAD_EVENT(name) \ +DEFINE_EVENT(nfsd_dynthread_class, nfsd_dynthread_##name, \ + TP_PROTO(const struct net *net, const struct svc_pool *pool), \ + TP_ARGS(net, pool)) + +DEFINE_NFSD_DYNTHREAD_EVENT(start); +DEFINE_NFSD_DYNTHREAD_EVENT(kill); +DEFINE_NFSD_DYNTHREAD_EVENT(trylock_fail); + #define show_nfsd_may_flags(x) \ __print_flags(x, "|", \ { NFSD_MAY_EXEC, "EXEC" }, \ @@ -2129,6 +2164,25 @@ TRACE_EVENT(nfsd_ctl_maxblksize, ) ); +TRACE_EVENT(nfsd_ctl_minthreads, + TP_PROTO( + const struct net *net, + int minthreads + ), + TP_ARGS(net, minthreads), + TP_STRUCT__entry( + __field(unsigned int, netns_ino) + __field(int, minthreads) + ), + TP_fast_assign( + __entry->netns_ino = net->ns.inum; + __entry->minthreads = minthreads + ), + TP_printk("minthreads=%d", + __entry->minthreads + ) +); + TRACE_EVENT(nfsd_ctl_time, TP_PROTO( const struct net *net, diff --git a/fs/nfsd/vfs.c b/fs/nfsd/vfs.c index 168d3ccc8155..c884c3f34afb 100644 --- a/fs/nfsd/vfs.c +++ b/fs/nfsd/vfs.c @@ -596,15 +596,35 @@ nfsd_setattr(struct svc_rqst *rqstp, struct svc_fh *fhp, if (attr->na_seclabel && attr->na_seclabel->len) attr->na_labelerr = security_inode_setsecctx(dentry, attr->na_seclabel->data, attr->na_seclabel->len); - if (IS_ENABLED(CONFIG_FS_POSIX_ACL) && attr->na_pacl) - attr->na_aclerr = set_posix_acl(&nop_mnt_idmap, - dentry, ACL_TYPE_ACCESS, - attr->na_pacl); - if (IS_ENABLED(CONFIG_FS_POSIX_ACL) && - !attr->na_aclerr && attr->na_dpacl && S_ISDIR(inode->i_mode)) - attr->na_aclerr = set_posix_acl(&nop_mnt_idmap, + if (IS_ENABLED(CONFIG_FS_POSIX_ACL) && attr->na_dpacl) { + if (!S_ISDIR(inode->i_mode)) + attr->na_dpaclerr = -EINVAL; + else if (attr->na_dpacl->a_count > 0) + /* a_count == 0 means delete the ACL. */ + attr->na_dpaclerr = set_posix_acl(&nop_mnt_idmap, dentry, ACL_TYPE_DEFAULT, attr->na_dpacl); + else + attr->na_dpaclerr = set_posix_acl(&nop_mnt_idmap, + dentry, ACL_TYPE_DEFAULT, + NULL); + } + if (IS_ENABLED(CONFIG_FS_POSIX_ACL) && attr->na_pacl) { + /* + * For any file system that is not ACL_SCOPE_FILE_OBJECT, + * a_count == 0 MUST reply nfserr_inval. + * For a file system that is ACL_SCOPE_FILE_OBJECT, + * a_count == 0 deletes the ACL. + * XXX File systems that are ACL_SCOPE_FILE_OBJECT + * are not yet supported. + */ + if (attr->na_pacl->a_count > 0) + attr->na_paclerr = set_posix_acl(&nop_mnt_idmap, + dentry, ACL_TYPE_ACCESS, + attr->na_pacl); + else + attr->na_paclerr = -EINVAL; + } out_fill_attrs: /* * RFC 1813 Section 3.3.2 does not mandate that an NFS server diff --git a/fs/nfsd/vfs.h b/fs/nfsd/vfs.h index e192dca4a679..702a844f2106 100644 --- a/fs/nfsd/vfs.h +++ b/fs/nfsd/vfs.h @@ -53,7 +53,8 @@ struct nfsd_attrs { struct posix_acl *na_dpacl; /* input */ int na_labelerr; /* output */ - int na_aclerr; /* output */ + int na_dpaclerr; /* output */ + int na_paclerr; /* output */ }; static inline void nfsd_attrs_free(struct nfsd_attrs *attrs) diff --git a/fs/nfsd/xdr4.h b/fs/nfsd/xdr4.h index ae75846b3cd7..417e9ad9fbb3 100644 --- a/fs/nfsd/xdr4.h +++ b/fs/nfsd/xdr4.h @@ -245,6 +245,8 @@ struct nfsd4_create { int cr_umask; /* request */ struct nfsd4_change_info cr_cinfo; /* response */ struct nfs4_acl *cr_acl; + struct posix_acl *cr_dpacl; + struct posix_acl *cr_pacl; struct xdr_netobj cr_label; }; #define cr_datalen u.link.datalen @@ -397,6 +399,8 @@ struct nfsd4_open { struct nfs4_ol_stateid *op_stp; /* used during processing */ struct nfs4_clnt_odstate *op_odstate; /* used during processing */ struct nfs4_acl *op_acl; + struct posix_acl *op_dpacl; + struct posix_acl *op_pacl; struct xdr_netobj op_label; struct svc_rqst *op_rqstp; }; @@ -483,6 +487,8 @@ struct nfsd4_setattr { struct iattr sa_iattr; /* request */ struct nfs4_acl *sa_acl; struct xdr_netobj sa_label; + struct posix_acl *sa_dpacl; + struct posix_acl *sa_pacl; }; struct nfsd4_setclientid { @@ -732,6 +738,7 @@ struct nfsd4_copy { #define NFSD4_COPY_F_COMMITTED (3) #define NFSD4_COPY_F_COMPLETED (4) #define NFSD4_COPY_F_OFFLOAD_DONE (5) +#define NFSD4_COPY_F_CB_ERROR (6) /* response */ __be32 nfserr; |
