NFSD: add write_version to netlink command
authorLorenzo Bianconi <lorenzo@kernel.org>
Tue, 23 Apr 2024 13:25:41 +0000 (15:25 +0200)
committerChuck Lever <chuck.lever@oracle.com>
Mon, 6 May 2024 13:07:21 +0000 (09:07 -0400)
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>
Documentation/netlink/specs/nfsd.yaml
fs/nfsd/netlink.c
fs/nfsd/netlink.h
fs/nfsd/netns.h
fs/nfsd/nfsctl.c
fs/nfsd/nfssvc.c
include/uapi/linux/nfsd_netlink.h

index 703c2d11d0a9f05d1c7cd085c6822f6e91d78ed4..77adda1425e23e049579d3ffc2e913ad2eb2cea2 100644 (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
index fe9eef5c7f2733d090eccf85a19e805da479bb26..b23b0b84a59aadb45e32ad3a4d69e9b60bd4b012 100644 (file)
 
 #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 = {
index 4137fac477e4f8dd5c5830894efba1deccd6aaaf..c7c0da2754818ae5840a13d5fd6497799ddbb2f4 100644 (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;
 
index d4be519b5734e3e86bd67a59875b3fb9c50bebb4..14ec15656320903b2bdc9b2426b462498050e7d1 100644 (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;
index 5bfdebb2e7e9d097d227da34a19e5ddf4ea04df4..b6b405437aa079da87be02bbcea5350100484905 100644 (file)
@@ -1796,6 +1796,156 @@ err_free_msg:
        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
index e25b9b829749dd97a3749f2811a672f6f43f09d3..cd9a6a1a9fc8f41ee0e87d31681e00523a6fe836 100644 (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;
index 4bbccd5db7cdcf8d0b64a9b984da76c018e7e354..7176cd8e6f09557c3717df83c1a402e3088f274b 100644 (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)