NFSD: add write_version to netlink command

Introduce write_version netlink command through a "declarative" interface.
This patch introduces a change in behavior since for version-set userspace
is expected to provide a NFS major/minor version list it wants to enable
while all the other ones will be disabled. (procfs write_version
command implements imperative interface where the admin writes +3/-3 to
enable/disable a single version.

Reviewed-by: Jeff Layton <jlayton@kernel.org>
Tested-by: Jeff Layton <jlayton@kernel.org>
Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
This commit is contained in:
Lorenzo Bianconi 2024-04-23 15:25:41 +02:00 committed by Chuck Lever
parent 924f4fb003
commit 5a939bea25
7 changed files with 236 additions and 2 deletions

View file

@ -78,6 +78,26 @@ attribute-sets:
-
name: scope
type: string
-
name: version
attributes:
-
name: major
type: u32
-
name: minor
type: u32
-
name: enabled
type: flag
-
name: server-proto
attributes:
-
name: version
type: nest
nested-attributes: version
multi-attr: true
operations:
list:
@ -126,3 +146,20 @@ operations:
- gracetime
- leasetime
- scope
-
name: version-set
doc: set nfs enabled versions
attribute-set: server-proto
flags: [ admin-perm ]
do:
request:
attributes:
- version
-
name: version-get
doc: get nfs enabled versions
attribute-set: server-proto
do:
reply:
attributes:
- version

View file

@ -10,6 +10,13 @@
#include <uapi/linux/nfsd_netlink.h>
/* Common nested types */
const struct nla_policy nfsd_version_nl_policy[NFSD_A_VERSION_ENABLED + 1] = {
[NFSD_A_VERSION_MAJOR] = { .type = NLA_U32, },
[NFSD_A_VERSION_MINOR] = { .type = NLA_U32, },
[NFSD_A_VERSION_ENABLED] = { .type = NLA_FLAG, },
};
/* NFSD_CMD_THREADS_SET - do */
static const struct nla_policy nfsd_threads_set_nl_policy[NFSD_A_SERVER_SCOPE + 1] = {
[NFSD_A_SERVER_THREADS] = { .type = NLA_U32, },
@ -18,6 +25,11 @@ static const struct nla_policy nfsd_threads_set_nl_policy[NFSD_A_SERVER_SCOPE +
[NFSD_A_SERVER_SCOPE] = { .type = NLA_NUL_STRING, },
};
/* NFSD_CMD_VERSION_SET - do */
static const struct nla_policy nfsd_version_set_nl_policy[NFSD_A_SERVER_PROTO_VERSION + 1] = {
[NFSD_A_SERVER_PROTO_VERSION] = NLA_POLICY_NESTED(nfsd_version_nl_policy),
};
/* Ops table for nfsd */
static const struct genl_split_ops nfsd_nl_ops[] = {
{
@ -39,6 +51,18 @@ static const struct genl_split_ops nfsd_nl_ops[] = {
.doit = nfsd_nl_threads_get_doit,
.flags = GENL_CMD_CAP_DO,
},
{
.cmd = NFSD_CMD_VERSION_SET,
.doit = nfsd_nl_version_set_doit,
.policy = nfsd_version_set_nl_policy,
.maxattr = NFSD_A_SERVER_PROTO_VERSION,
.flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO,
},
{
.cmd = NFSD_CMD_VERSION_GET,
.doit = nfsd_nl_version_get_doit,
.flags = GENL_CMD_CAP_DO,
},
};
struct genl_family nfsd_nl_family __ro_after_init = {

View file

@ -11,6 +11,9 @@
#include <uapi/linux/nfsd_netlink.h>
/* Common nested types */
extern const struct nla_policy nfsd_version_nl_policy[NFSD_A_VERSION_ENABLED + 1];
int nfsd_nl_rpc_status_get_start(struct netlink_callback *cb);
int nfsd_nl_rpc_status_get_done(struct netlink_callback *cb);
@ -18,6 +21,8 @@ int nfsd_nl_rpc_status_get_dumpit(struct sk_buff *skb,
struct netlink_callback *cb);
int nfsd_nl_threads_set_doit(struct sk_buff *skb, struct genl_info *info);
int nfsd_nl_threads_get_doit(struct sk_buff *skb, struct genl_info *info);
int nfsd_nl_version_set_doit(struct sk_buff *skb, struct genl_info *info);
int nfsd_nl_version_get_doit(struct sk_buff *skb, struct genl_info *info);
extern struct genl_family nfsd_nl_family;

View file

@ -218,6 +218,7 @@ struct nfsd_net {
/* Simple check to find out if a given net was properly initialized */
#define nfsd_netns_ready(nn) ((nn)->sessionid_hashtbl)
extern bool nfsd_support_version(int vers);
extern void nfsd_netns_free_versions(struct nfsd_net *nn);
extern unsigned int nfsd_net_id;

View file

@ -1796,6 +1796,156 @@ int nfsd_nl_threads_get_doit(struct sk_buff *skb, struct genl_info *info)
return err;
}
/**
* nfsd_nl_version_set_doit - set the nfs enabled versions
* @skb: reply buffer
* @info: netlink metadata and command arguments
*
* Return 0 on success or a negative errno.
*/
int nfsd_nl_version_set_doit(struct sk_buff *skb, struct genl_info *info)
{
const struct nlattr *attr;
struct nfsd_net *nn;
int i, rem;
if (GENL_REQ_ATTR_CHECK(info, NFSD_A_SERVER_PROTO_VERSION))
return -EINVAL;
mutex_lock(&nfsd_mutex);
nn = net_generic(genl_info_net(info), nfsd_net_id);
if (nn->nfsd_serv) {
mutex_unlock(&nfsd_mutex);
return -EBUSY;
}
/* clear current supported versions. */
nfsd_vers(nn, 2, NFSD_CLEAR);
nfsd_vers(nn, 3, NFSD_CLEAR);
for (i = 0; i <= NFSD_SUPPORTED_MINOR_VERSION; i++)
nfsd_minorversion(nn, i, NFSD_CLEAR);
nlmsg_for_each_attr(attr, info->nlhdr, GENL_HDRLEN, rem) {
struct nlattr *tb[NFSD_A_VERSION_MAX + 1];
u32 major, minor = 0;
bool enabled;
if (nla_type(attr) != NFSD_A_SERVER_PROTO_VERSION)
continue;
if (nla_parse_nested(tb, NFSD_A_VERSION_MAX, attr,
nfsd_version_nl_policy, info->extack) < 0)
continue;
if (!tb[NFSD_A_VERSION_MAJOR])
continue;
major = nla_get_u32(tb[NFSD_A_VERSION_MAJOR]);
if (tb[NFSD_A_VERSION_MINOR])
minor = nla_get_u32(tb[NFSD_A_VERSION_MINOR]);
enabled = nla_get_flag(tb[NFSD_A_VERSION_ENABLED]);
switch (major) {
case 4:
nfsd_minorversion(nn, minor, enabled ? NFSD_SET : NFSD_CLEAR);
break;
case 3:
case 2:
if (!minor)
nfsd_vers(nn, major, enabled ? NFSD_SET : NFSD_CLEAR);
break;
default:
break;
}
}
mutex_unlock(&nfsd_mutex);
return 0;
}
/**
* nfsd_nl_version_get_doit - get the enabled status for all supported nfs versions
* @skb: reply buffer
* @info: netlink metadata and command arguments
*
* Return 0 on success or a negative errno.
*/
int nfsd_nl_version_get_doit(struct sk_buff *skb, struct genl_info *info)
{
struct nfsd_net *nn;
int i, err;
void *hdr;
skb = genlmsg_new(GENLMSG_DEFAULT_SIZE, GFP_KERNEL);
if (!skb)
return -ENOMEM;
hdr = genlmsg_iput(skb, info);
if (!hdr) {
err = -EMSGSIZE;
goto err_free_msg;
}
mutex_lock(&nfsd_mutex);
nn = net_generic(genl_info_net(info), nfsd_net_id);
for (i = 2; i <= 4; i++) {
int j;
for (j = 0; j <= NFSD_SUPPORTED_MINOR_VERSION; j++) {
struct nlattr *attr;
/* Don't record any versions the kernel doesn't have
* compiled in
*/
if (!nfsd_support_version(i))
continue;
/* NFSv{2,3} does not support minor numbers */
if (i < 4 && j)
continue;
attr = nla_nest_start(skb,
NFSD_A_SERVER_PROTO_VERSION);
if (!attr) {
err = -EINVAL;
goto err_nfsd_unlock;
}
if (nla_put_u32(skb, NFSD_A_VERSION_MAJOR, i) ||
nla_put_u32(skb, NFSD_A_VERSION_MINOR, j)) {
err = -EINVAL;
goto err_nfsd_unlock;
}
/* Set the enabled flag if the version is enabled */
if (nfsd_vers(nn, i, NFSD_TEST) &&
(i < 4 || nfsd_minorversion(nn, j, NFSD_TEST)) &&
nla_put_flag(skb, NFSD_A_VERSION_ENABLED)) {
err = -EINVAL;
goto err_nfsd_unlock;
}
nla_nest_end(skb, attr);
}
}
mutex_unlock(&nfsd_mutex);
genlmsg_end(skb, hdr);
return genlmsg_reply(skb, info);
err_nfsd_unlock:
mutex_unlock(&nfsd_mutex);
err_free_msg:
nlmsg_free(skb);
return err;
}
/**
* nfsd_net_init - Prepare the nfsd_net portion of a new net namespace
* @net: a freshly-created network namespace

View file

@ -133,8 +133,7 @@ struct svc_program nfsd_program = {
.pg_rpcbind_set = nfsd_rpcbind_set,
};
static bool
nfsd_support_version(int vers)
bool nfsd_support_version(int vers)
{
if (vers >= NFSD_MINVERS && vers < NFSD_NRVERS)
return nfsd_version[vers] != NULL;

View file

@ -39,10 +39,28 @@ enum {
NFSD_A_SERVER_MAX = (__NFSD_A_SERVER_MAX - 1)
};
enum {
NFSD_A_VERSION_MAJOR = 1,
NFSD_A_VERSION_MINOR,
NFSD_A_VERSION_ENABLED,
__NFSD_A_VERSION_MAX,
NFSD_A_VERSION_MAX = (__NFSD_A_VERSION_MAX - 1)
};
enum {
NFSD_A_SERVER_PROTO_VERSION = 1,
__NFSD_A_SERVER_PROTO_MAX,
NFSD_A_SERVER_PROTO_MAX = (__NFSD_A_SERVER_PROTO_MAX - 1)
};
enum {
NFSD_CMD_RPC_STATUS_GET = 1,
NFSD_CMD_THREADS_SET,
NFSD_CMD_THREADS_GET,
NFSD_CMD_VERSION_SET,
NFSD_CMD_VERSION_GET,
__NFSD_CMD_MAX,
NFSD_CMD_MAX = (__NFSD_CMD_MAX - 1)