Commit 5a939bea authored by Lorenzo Bianconi's avatar Lorenzo Bianconi Committed by Chuck Lever
Browse files

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: default avatarJeff Layton <jlayton@kernel.org>
Tested-by: default avatarJeff Layton <jlayton@kernel.org>
Signed-off-by: default avatarLorenzo Bianconi <lorenzo@kernel.org>
Signed-off-by: default avatarChuck Lever <chuck.lever@oracle.com>
parent 924f4fb0
Loading
Loading
Loading
Loading
+37 −0
Original line number Diff line number Diff line
@@ -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
+24 −0
Original line number Diff line number Diff line
@@ -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 = {
+5 −0
Original line number Diff line number Diff line
@@ -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;

+1 −0
Original line number Diff line number Diff line
@@ -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;
+150 −0
Original line number Diff line number Diff line
@@ -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
Loading