summaryrefslogtreecommitdiff
path: root/fs
diff options
context:
space:
mode:
Diffstat (limited to 'fs')
-rw-r--r--fs/lockd/svc.c6
-rw-r--r--fs/lockd/svclock.c4
-rw-r--r--fs/locks.c17
-rw-r--r--fs/nfs/callback.c10
-rw-r--r--fs/nfsd/Kconfig19
-rw-r--r--fs/nfsd/Makefile10
-rw-r--r--fs/nfsd/acl.h1
-rw-r--r--fs/nfsd/netlink.c5
-rw-r--r--fs/nfsd/netns.h7
-rw-r--r--fs/nfsd/nfs2acl.c2
-rw-r--r--fs/nfsd/nfs4acl.c17
-rw-r--r--fs/nfsd/nfs4idmap.c52
-rw-r--r--fs/nfsd/nfs4proc.c265
-rw-r--r--fs/nfsd/nfs4state.c52
-rw-r--r--fs/nfsd/nfs4xdr.c363
-rw-r--r--fs/nfsd/nfs4xdr_gen.c351
-rw-r--r--fs/nfsd/nfs4xdr_gen.h12
-rw-r--r--fs/nfsd/nfsctl.c7
-rw-r--r--fs/nfsd/nfsd.h24
-rw-r--r--fs/nfsd/nfsproc.c2
-rw-r--r--fs/nfsd/nfssvc.c64
-rw-r--r--fs/nfsd/state.h5
-rw-r--r--fs/nfsd/trace.h54
-rw-r--r--fs/nfsd/vfs.c34
-rw-r--r--fs/nfsd/vfs.h3
-rw-r--r--fs/nfsd/xdr4.h7
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, &copy->cp_flags)) {
kthread_stop(copy->copy_task);
- copy->nfserr = nfs_ok;
+ if (!test_bit(NFSD4_COPY_F_CB_ERROR, &copy->cp_flags))
+ copy->nfserr = nfs_ok;
set_bit(NFSD4_COPY_F_COMPLETED, &copy->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(&copy->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,
+ &copy->cp_flags);
+ list_move(&copy->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(&copy->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, &copy->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 = &copy->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, &copy->cp_flags);
+ return;
+ }
memcpy(&cbo->co_res, &copy->cp_res, sizeof(copy->cp_res));
memcpy(&cbo->co_fh, &copy->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, &copy->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, &copy->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;