On Thu, 2024-04-11 at 18:47 +0200, Lorenzo Bianconi wrote: > 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. > > Tested-by: Jeff Layton <jlayton@xxxxxxxxxx> > Signed-off-by: Lorenzo Bianconi <lorenzo@xxxxxxxxxx> > --- > Documentation/netlink/specs/nfsd.yaml | 37 +++++++ > fs/nfsd/netlink.c | 24 ++++ > fs/nfsd/netlink.h | 5 + > fs/nfsd/netns.h | 1 + > fs/nfsd/nfsctl.c | 153 ++++++++++++++++++++++++++ > fs/nfsd/nfssvc.c | 3 +- > include/uapi/linux/nfsd_netlink.h | 18 +++ > 7 files changed, 239 insertions(+), 2 deletions(-) > > diff --git a/Documentation/netlink/specs/nfsd.yaml b/Documentation/netlink/specs/nfsd.yaml > index c92e1425d316..cb93e3e37119 100644 > --- a/Documentation/netlink/specs/nfsd.yaml > +++ b/Documentation/netlink/specs/nfsd.yaml > @@ -68,6 +68,26 @@ attribute-sets: > - > name: threads > type: u32 > + - > + 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: > @@ -110,3 +130,20 @@ operations: > reply: > attributes: > - threads > + - > + 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 > diff --git a/fs/nfsd/netlink.c b/fs/nfsd/netlink.c > index 1a59a8e6c7e2..75f609b57ceb 100644 > --- a/fs/nfsd/netlink.c > +++ b/fs/nfsd/netlink.c > @@ -10,11 +10,23 @@ > > #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_WORKER_THREADS + 1] = { > [NFSD_A_SERVER_WORKER_THREADS] = { .type = NLA_U32, }, > }; > > +/* 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[] = { > { > @@ -36,6 +48,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 = { > diff --git a/fs/nfsd/netlink.h b/fs/nfsd/netlink.h > index 4137fac477e4..c7c0da275481 100644 > --- a/fs/nfsd/netlink.h > +++ b/fs/nfsd/netlink.h > @@ -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; > > diff --git a/fs/nfsd/netns.h b/fs/nfsd/netns.h > index d4be519b5734..14ec15656320 100644 > --- a/fs/nfsd/netns.h > +++ b/fs/nfsd/netns.h > @@ -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; > diff --git a/fs/nfsd/nfsctl.c b/fs/nfsd/nfsctl.c > index 71608744e67f..341efab4eaa7 100644 > --- a/fs/nfsd/nfsctl.c > +++ b/fs/nfsd/nfsctl.c > @@ -1711,6 +1711,159 @@ 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 nfs enabled 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; > + > + if (i == 4 && !nfsd_minorversion(nn, j, NFSD_TEST)) > + continue; > + I think this is not quite right. We don't want to skip returning a minorversion just because it's not enabled. We want to return the entry with the enabled flag cleared. I'd suggest just dropping the above if statement entirely. > + attr = nla_nest_start_noflag(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 > diff --git a/fs/nfsd/nfssvc.c b/fs/nfsd/nfssvc.c > index c0d17b92b249..4452a9bb9bbb 100644 > --- a/fs/nfsd/nfssvc.c > +++ b/fs/nfsd/nfssvc.c > @@ -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; > diff --git a/include/uapi/linux/nfsd_netlink.h b/include/uapi/linux/nfsd_netlink.h > index 1b6fe1f9ed0e..ccf3e15fe160 100644 > --- a/include/uapi/linux/nfsd_netlink.h > +++ b/include/uapi/linux/nfsd_netlink.h > @@ -36,10 +36,28 @@ enum { > NFSD_A_SERVER_WORKER_MAX = (__NFSD_A_SERVER_WORKER_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) -- Jeff Layton <jlayton@xxxxxxxxxx>