root/net/sunrpc/sysfs.c
// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (c) 2020 Anna Schumaker <Anna.Schumaker@Netapp.com>
 */
#include <linux/sunrpc/clnt.h>
#include <linux/kobject.h>
#include <linux/sunrpc/addr.h>
#include <linux/sunrpc/xprtsock.h>

#include "sysfs.h"

struct xprt_addr {
        const char *addr;
        struct rcu_head rcu;
};

static void free_xprt_addr(struct rcu_head *head)
{
        struct xprt_addr *addr = container_of(head, struct xprt_addr, rcu);

        kfree(addr->addr);
        kfree(addr);
}

static struct kset *rpc_sunrpc_kset;
static struct kobject *rpc_sunrpc_client_kobj, *rpc_sunrpc_xprt_switch_kobj;

static void rpc_sysfs_object_release(struct kobject *kobj)
{
        kfree(kobj);
}

static const struct kobj_ns_type_operations *
rpc_sysfs_object_child_ns_type(const struct kobject *kobj)
{
        return &net_ns_type_operations;
}

static const struct kobj_type rpc_sysfs_object_type = {
        .release = rpc_sysfs_object_release,
        .sysfs_ops = &kobj_sysfs_ops,
        .child_ns_type = rpc_sysfs_object_child_ns_type,
};

static struct kobject *rpc_sysfs_object_alloc(const char *name,
                                              struct kset *kset,
                                              struct kobject *parent)
{
        struct kobject *kobj;

        kobj = kzalloc_obj(*kobj);
        if (kobj) {
                kobj->kset = kset;
                if (kobject_init_and_add(kobj, &rpc_sysfs_object_type,
                                         parent, "%s", name) == 0)
                        return kobj;
                kobject_put(kobj);
        }
        return NULL;
}

static inline struct rpc_clnt *
rpc_sysfs_client_kobj_get_clnt(struct kobject *kobj)
{
        struct rpc_sysfs_client *c = container_of(kobj,
                struct rpc_sysfs_client, kobject);
        struct rpc_clnt *ret = c->clnt;

        return refcount_inc_not_zero(&ret->cl_count) ? ret : NULL;
}

static inline struct rpc_xprt *
rpc_sysfs_xprt_kobj_get_xprt(struct kobject *kobj)
{
        struct rpc_sysfs_xprt *x = container_of(kobj,
                struct rpc_sysfs_xprt, kobject);

        return xprt_get(x->xprt);
}

static inline struct rpc_xprt_switch *
rpc_sysfs_xprt_kobj_get_xprt_switch(struct kobject *kobj)
{
        struct rpc_sysfs_xprt *x = container_of(kobj,
                struct rpc_sysfs_xprt, kobject);

        return xprt_switch_get(x->xprt_switch);
}

static inline struct rpc_xprt_switch *
rpc_sysfs_xprt_switch_kobj_get_xprt(struct kobject *kobj)
{
        struct rpc_sysfs_xprt_switch *x = container_of(kobj,
                struct rpc_sysfs_xprt_switch, kobject);

        return xprt_switch_get(x->xprt_switch);
}

static ssize_t rpc_sysfs_clnt_version_show(struct kobject *kobj,
                                           struct kobj_attribute *attr,
                                           char *buf)
{
        struct rpc_clnt *clnt = rpc_sysfs_client_kobj_get_clnt(kobj);
        ssize_t ret;

        if (!clnt)
                return sprintf(buf, "<closed>\n");

        ret = sprintf(buf, "%u", clnt->cl_vers);
        refcount_dec(&clnt->cl_count);
        return ret;
}

static ssize_t rpc_sysfs_clnt_program_show(struct kobject *kobj,
                                           struct kobj_attribute *attr,
                                           char *buf)
{
        struct rpc_clnt *clnt = rpc_sysfs_client_kobj_get_clnt(kobj);
        ssize_t ret;

        if (!clnt)
                return sprintf(buf, "<closed>\n");

        ret = sprintf(buf, "%s", clnt->cl_program->name);
        refcount_dec(&clnt->cl_count);
        return ret;
}

static ssize_t rpc_sysfs_clnt_max_connect_show(struct kobject *kobj,
                                               struct kobj_attribute *attr,
                                               char *buf)
{
        struct rpc_clnt *clnt = rpc_sysfs_client_kobj_get_clnt(kobj);
        ssize_t ret;

        if (!clnt)
                return sprintf(buf, "<closed>\n");

        ret = sprintf(buf, "%u\n", clnt->cl_max_connect);
        refcount_dec(&clnt->cl_count);
        return ret;
}

static ssize_t rpc_sysfs_xprt_dstaddr_show(struct kobject *kobj,
                                           struct kobj_attribute *attr,
                                           char *buf)
{
        struct rpc_xprt *xprt = rpc_sysfs_xprt_kobj_get_xprt(kobj);
        ssize_t ret;

        if (!xprt) {
                ret = sprintf(buf, "<closed>\n");
                goto out;
        }
        ret = sprintf(buf, "%s\n", xprt->address_strings[RPC_DISPLAY_ADDR]);
        xprt_put(xprt);
out:
        return ret;
}

static ssize_t rpc_sysfs_xprt_srcaddr_show(struct kobject *kobj,
                                           struct kobj_attribute *attr,
                                           char *buf)
{
        struct rpc_xprt *xprt = rpc_sysfs_xprt_kobj_get_xprt(kobj);
        size_t buflen = PAGE_SIZE;
        ssize_t ret;

        if (!xprt || !xprt_connected(xprt)) {
                ret = sprintf(buf, "<closed>\n");
        } else if (xprt->ops->get_srcaddr) {
                ret = xprt->ops->get_srcaddr(xprt, buf, buflen);
                if (ret > 0) {
                        if (ret < buflen - 1) {
                                buf[ret] = '\n';
                                ret++;
                                buf[ret] = '\0';
                        }
                } else
                        ret = sprintf(buf, "<closed>\n");
        } else
                ret = sprintf(buf, "<not a socket>\n");
        xprt_put(xprt);
        return ret;
}

static const char *xprtsec_strings[] = {
        [RPC_XPRTSEC_NONE] = "none",
        [RPC_XPRTSEC_TLS_ANON] = "tls-anon",
        [RPC_XPRTSEC_TLS_X509] = "tls-x509",
};

static ssize_t rpc_sysfs_xprt_xprtsec_show(struct kobject *kobj,
                                           struct kobj_attribute *attr,
                                           char *buf)
{
        struct rpc_xprt *xprt = rpc_sysfs_xprt_kobj_get_xprt(kobj);
        ssize_t ret;

        if (!xprt) {
                ret = sprintf(buf, "<closed>\n");
                goto out;
        }

        ret = sprintf(buf, "%s\n", xprtsec_strings[xprt->xprtsec.policy]);
        xprt_put(xprt);
out:
        return ret;

}

static ssize_t rpc_sysfs_xprt_info_show(struct kobject *kobj,
                                        struct kobj_attribute *attr, char *buf)
{
        struct rpc_xprt *xprt = rpc_sysfs_xprt_kobj_get_xprt(kobj);
        unsigned short srcport = 0;
        size_t buflen = PAGE_SIZE;
        ssize_t ret;

        if (!xprt || !xprt_connected(xprt)) {
                ret = sprintf(buf, "<closed>\n");
                goto out;
        }

        if (xprt->ops->get_srcport)
                srcport = xprt->ops->get_srcport(xprt);

        ret = snprintf(buf, buflen,
                       "last_used=%lu\ncur_cong=%lu\ncong_win=%lu\n"
                       "max_num_slots=%u\nmin_num_slots=%u\nnum_reqs=%u\n"
                       "binding_q_len=%u\nsending_q_len=%u\npending_q_len=%u\n"
                       "backlog_q_len=%u\nmain_xprt=%d\nsrc_port=%u\n"
                       "tasks_queuelen=%ld\ndst_port=%s\n",
                       xprt->last_used, xprt->cong, xprt->cwnd, xprt->max_reqs,
                       xprt->min_reqs, xprt->num_reqs, xprt->binding.qlen,
                       xprt->sending.qlen, xprt->pending.qlen,
                       xprt->backlog.qlen, xprt->main, srcport,
                       atomic_long_read(&xprt->queuelen),
                       xprt->address_strings[RPC_DISPLAY_PORT]);
out:
        xprt_put(xprt);
        return ret;
}

static ssize_t rpc_sysfs_xprt_state_show(struct kobject *kobj,
                                         struct kobj_attribute *attr,
                                         char *buf)
{
        struct rpc_xprt *xprt = rpc_sysfs_xprt_kobj_get_xprt(kobj);
        ssize_t ret;
        int locked, connected, connecting, close_wait, bound, binding,
            closing, congested, cwnd_wait, write_space, offline, remove;

        if (!(xprt && xprt->state)) {
                ret = sprintf(buf, "state=CLOSED\n");
        } else {
                locked = test_bit(XPRT_LOCKED, &xprt->state);
                connected = test_bit(XPRT_CONNECTED, &xprt->state);
                connecting = test_bit(XPRT_CONNECTING, &xprt->state);
                close_wait = test_bit(XPRT_CLOSE_WAIT, &xprt->state);
                bound = test_bit(XPRT_BOUND, &xprt->state);
                binding = test_bit(XPRT_BINDING, &xprt->state);
                closing = test_bit(XPRT_CLOSING, &xprt->state);
                congested = test_bit(XPRT_CONGESTED, &xprt->state);
                cwnd_wait = test_bit(XPRT_CWND_WAIT, &xprt->state);
                write_space = test_bit(XPRT_WRITE_SPACE, &xprt->state);
                offline = test_bit(XPRT_OFFLINE, &xprt->state);
                remove = test_bit(XPRT_REMOVE, &xprt->state);

                ret = sprintf(buf, "state=%s %s %s %s %s %s %s %s %s %s %s %s\n",
                              locked ? "LOCKED" : "",
                              connected ? "CONNECTED" : "",
                              connecting ? "CONNECTING" : "",
                              close_wait ? "CLOSE_WAIT" : "",
                              bound ? "BOUND" : "",
                              binding ? "BOUNDING" : "",
                              closing ? "CLOSING" : "",
                              congested ? "CONGESTED" : "",
                              cwnd_wait ? "CWND_WAIT" : "",
                              write_space ? "WRITE_SPACE" : "",
                              offline ? "OFFLINE" : "",
                              remove ? "REMOVE" : "");
        }

        xprt_put(xprt);
        return ret;
}

static ssize_t rpc_sysfs_xprt_del_xprt_show(struct kobject *kobj,
                                            struct kobj_attribute *attr,
                                            char *buf)
{
        return sprintf(buf, "# delete this xprt\n");
}


static ssize_t rpc_sysfs_xprt_switch_info_show(struct kobject *kobj,
                                               struct kobj_attribute *attr,
                                               char *buf)
{
        struct rpc_xprt_switch *xprt_switch =
                rpc_sysfs_xprt_switch_kobj_get_xprt(kobj);
        ssize_t ret;

        if (!xprt_switch)
                return 0;
        ret = sprintf(buf, "num_xprts=%u\nnum_active=%u\n"
                      "num_unique_destaddr=%u\nqueue_len=%ld\n",
                      xprt_switch->xps_nxprts, xprt_switch->xps_nactive,
                      xprt_switch->xps_nunique_destaddr_xprts,
                      atomic_long_read(&xprt_switch->xps_queuelen));
        xprt_switch_put(xprt_switch);
        return ret;
}

static ssize_t rpc_sysfs_xprt_switch_add_xprt_show(struct kobject *kobj,
                                                   struct kobj_attribute *attr,
                                                   char *buf)
{
        return sprintf(buf, "# add one xprt to this xprt_switch\n");
}

static ssize_t rpc_sysfs_xprt_switch_add_xprt_store(struct kobject *kobj,
                                                    struct kobj_attribute *attr,
                                                    const char *buf, size_t count)
{
        struct rpc_xprt_switch *xprt_switch =
                rpc_sysfs_xprt_switch_kobj_get_xprt(kobj);
        struct xprt_create xprt_create_args;
        struct rpc_xprt *xprt, *new;

        if (!xprt_switch)
                return 0;

        xprt = rpc_xprt_switch_get_main_xprt(xprt_switch);
        if (!xprt)
                goto out;

        xprt_create_args.ident = xprt->xprt_class->ident;
        xprt_create_args.net = xprt->xprt_net;
        xprt_create_args.dstaddr = (struct sockaddr *)&xprt->addr;
        xprt_create_args.addrlen = xprt->addrlen;
        xprt_create_args.servername = xprt->servername;
        xprt_create_args.bc_xprt = xprt->bc_xprt;
        xprt_create_args.xprtsec = xprt->xprtsec;
        xprt_create_args.connect_timeout = xprt->connect_timeout;
        xprt_create_args.reconnect_timeout = xprt->max_reconnect_timeout;

        new = xprt_create_transport(&xprt_create_args);
        if (IS_ERR_OR_NULL(new)) {
                count = PTR_ERR(new);
                goto out_put_xprt;
        }

        rpc_xprt_switch_add_xprt(xprt_switch, new);
        xprt_put(new);

out_put_xprt:
        xprt_put(xprt);
out:
        xprt_switch_put(xprt_switch);
        return count;
}

static ssize_t rpc_sysfs_xprt_dstaddr_store(struct kobject *kobj,
                                            struct kobj_attribute *attr,
                                            const char *buf, size_t count)
{
        struct rpc_xprt *xprt = rpc_sysfs_xprt_kobj_get_xprt(kobj);
        struct sockaddr *saddr;
        char *dst_addr;
        int port;
        struct xprt_addr *saved_addr;
        size_t buf_len;

        if (!xprt)
                return 0;
        if (!(xprt->xprt_class->ident == XPRT_TRANSPORT_TCP ||
              xprt->xprt_class->ident == XPRT_TRANSPORT_TCP_TLS ||
              xprt->xprt_class->ident == XPRT_TRANSPORT_RDMA)) {
                xprt_put(xprt);
                return -EOPNOTSUPP;
        }

        if (wait_on_bit_lock(&xprt->state, XPRT_LOCKED, TASK_KILLABLE)) {
                count = -EINTR;
                goto out_put;
        }
        saddr = (struct sockaddr *)&xprt->addr;
        port = rpc_get_port(saddr);

        /* buf_len is the len until the first occurrence of either
         * '\n' or '\0'
         */
        buf_len = strcspn(buf, "\n");

        dst_addr = kstrndup(buf, buf_len, GFP_KERNEL);
        if (!dst_addr)
                goto out_err;
        saved_addr = kzalloc_obj(*saved_addr);
        if (!saved_addr)
                goto out_err_free;
        saved_addr->addr =
                rcu_dereference_raw(xprt->address_strings[RPC_DISPLAY_ADDR]);
        rcu_assign_pointer(xprt->address_strings[RPC_DISPLAY_ADDR], dst_addr);
        call_rcu(&saved_addr->rcu, free_xprt_addr);
        xprt->addrlen = rpc_pton(xprt->xprt_net, buf, buf_len, saddr,
                                 sizeof(*saddr));
        rpc_set_port(saddr, port);

        xprt_force_disconnect(xprt);
out:
        xprt_release_write(xprt, NULL);
out_put:
        xprt_put(xprt);
        return count;
out_err_free:
        kfree(dst_addr);
out_err:
        count = -ENOMEM;
        goto out;
}

static ssize_t rpc_sysfs_xprt_state_change(struct kobject *kobj,
                                           struct kobj_attribute *attr,
                                           const char *buf, size_t count)
{
        struct rpc_xprt *xprt = rpc_sysfs_xprt_kobj_get_xprt(kobj);
        int offline = 0, online = 0, remove = 0;
        struct rpc_xprt_switch *xps = rpc_sysfs_xprt_kobj_get_xprt_switch(kobj);

        if (!xprt || !xps) {
                count = 0;
                goto out_put;
        }

        if (!strncmp(buf, "offline", 7))
                offline = 1;
        else if (!strncmp(buf, "online", 6))
                online = 1;
        else if (!strncmp(buf, "remove", 6))
                remove = 1;
        else {
                count = -EINVAL;
                goto out_put;
        }

        if (wait_on_bit_lock(&xprt->state, XPRT_LOCKED, TASK_KILLABLE)) {
                count = -EINTR;
                goto out_put;
        }
        if (xprt->main) {
                count = -EINVAL;
                goto release_tasks;
        }
        if (offline) {
                xprt_set_offline_locked(xprt, xps);
        } else if (online) {
                xprt_set_online_locked(xprt, xps);
        } else if (remove) {
                if (test_bit(XPRT_OFFLINE, &xprt->state))
                        xprt_delete_locked(xprt, xps);
                else
                        count = -EINVAL;
        }

release_tasks:
        xprt_release_write(xprt, NULL);
out_put:
        xprt_put(xprt);
        xprt_switch_put(xps);
        return count;
}

static ssize_t rpc_sysfs_xprt_del_xprt(struct kobject *kobj,
                                       struct kobj_attribute *attr,
                                       const char *buf, size_t count)
{
        struct rpc_xprt *xprt = rpc_sysfs_xprt_kobj_get_xprt(kobj);
        struct rpc_xprt_switch *xps = rpc_sysfs_xprt_kobj_get_xprt_switch(kobj);

        if (!xprt || !xps) {
                count = 0;
                goto out;
        }

        if (xprt->main) {
                count = -EINVAL;
                goto release_tasks;
        }

        if (wait_on_bit_lock(&xprt->state, XPRT_LOCKED, TASK_KILLABLE)) {
                count = -EINTR;
                goto out_put;
        }

        xprt_set_offline_locked(xprt, xps);
        xprt_delete_locked(xprt, xps);

release_tasks:
        xprt_release_write(xprt, NULL);
out_put:
        xprt_put(xprt);
        xprt_switch_put(xps);
out:
        return count;
}

int rpc_sysfs_init(void)
{
        rpc_sunrpc_kset = kset_create_and_add("sunrpc", NULL, kernel_kobj);
        if (!rpc_sunrpc_kset)
                return -ENOMEM;
        rpc_sunrpc_client_kobj =
                rpc_sysfs_object_alloc("rpc-clients", rpc_sunrpc_kset, NULL);
        if (!rpc_sunrpc_client_kobj)
                goto err_client;
        rpc_sunrpc_xprt_switch_kobj =
                rpc_sysfs_object_alloc("xprt-switches", rpc_sunrpc_kset, NULL);
        if (!rpc_sunrpc_xprt_switch_kobj)
                goto err_switch;
        return 0;
err_switch:
        kobject_put(rpc_sunrpc_client_kobj);
        rpc_sunrpc_client_kobj = NULL;
err_client:
        kset_unregister(rpc_sunrpc_kset);
        rpc_sunrpc_kset = NULL;
        return -ENOMEM;
}

static void rpc_sysfs_client_release(struct kobject *kobj)
{
        struct rpc_sysfs_client *c;

        c = container_of(kobj, struct rpc_sysfs_client, kobject);
        kfree(c);
}

static void rpc_sysfs_xprt_switch_release(struct kobject *kobj)
{
        struct rpc_sysfs_xprt_switch *xprt_switch;

        xprt_switch = container_of(kobj, struct rpc_sysfs_xprt_switch, kobject);
        kfree(xprt_switch);
}

static void rpc_sysfs_xprt_release(struct kobject *kobj)
{
        struct rpc_sysfs_xprt *xprt;

        xprt = container_of(kobj, struct rpc_sysfs_xprt, kobject);
        kfree(xprt);
}

static const void *rpc_sysfs_client_namespace(const struct kobject *kobj)
{
        return container_of(kobj, struct rpc_sysfs_client, kobject)->net;
}

static const void *rpc_sysfs_xprt_switch_namespace(const struct kobject *kobj)
{
        return container_of(kobj, struct rpc_sysfs_xprt_switch, kobject)->net;
}

static const void *rpc_sysfs_xprt_namespace(const struct kobject *kobj)
{
        return container_of(kobj, struct rpc_sysfs_xprt,
                            kobject)->xprt->xprt_net;
}

static struct kobj_attribute rpc_sysfs_clnt_version = __ATTR(rpc_version,
        0444, rpc_sysfs_clnt_version_show, NULL);

static struct kobj_attribute rpc_sysfs_clnt_program = __ATTR(program,
        0444, rpc_sysfs_clnt_program_show, NULL);

static struct kobj_attribute rpc_sysfs_clnt_max_connect = __ATTR(max_connect,
        0444, rpc_sysfs_clnt_max_connect_show, NULL);

static struct attribute *rpc_sysfs_rpc_clnt_attrs[] = {
        &rpc_sysfs_clnt_version.attr,
        &rpc_sysfs_clnt_program.attr,
        &rpc_sysfs_clnt_max_connect.attr,
        NULL,
};
ATTRIBUTE_GROUPS(rpc_sysfs_rpc_clnt);

static struct kobj_attribute rpc_sysfs_xprt_dstaddr = __ATTR(dstaddr,
        0644, rpc_sysfs_xprt_dstaddr_show, rpc_sysfs_xprt_dstaddr_store);

static struct kobj_attribute rpc_sysfs_xprt_srcaddr = __ATTR(srcaddr,
        0644, rpc_sysfs_xprt_srcaddr_show, NULL);

static struct kobj_attribute rpc_sysfs_xprt_xprtsec = __ATTR(xprtsec,
        0644, rpc_sysfs_xprt_xprtsec_show, NULL);

static struct kobj_attribute rpc_sysfs_xprt_info = __ATTR(xprt_info,
        0444, rpc_sysfs_xprt_info_show, NULL);

static struct kobj_attribute rpc_sysfs_xprt_change_state = __ATTR(xprt_state,
        0644, rpc_sysfs_xprt_state_show, rpc_sysfs_xprt_state_change);

static struct kobj_attribute rpc_sysfs_xprt_del = __ATTR(del_xprt,
        0644, rpc_sysfs_xprt_del_xprt_show, rpc_sysfs_xprt_del_xprt);

static struct attribute *rpc_sysfs_xprt_attrs[] = {
        &rpc_sysfs_xprt_dstaddr.attr,
        &rpc_sysfs_xprt_srcaddr.attr,
        &rpc_sysfs_xprt_xprtsec.attr,
        &rpc_sysfs_xprt_info.attr,
        &rpc_sysfs_xprt_change_state.attr,
        &rpc_sysfs_xprt_del.attr,
        NULL,
};
ATTRIBUTE_GROUPS(rpc_sysfs_xprt);

static struct kobj_attribute rpc_sysfs_xprt_switch_info =
        __ATTR(xprt_switch_info, 0444, rpc_sysfs_xprt_switch_info_show, NULL);

static struct kobj_attribute rpc_sysfs_xprt_switch_add_xprt =
        __ATTR(add_xprt, 0644, rpc_sysfs_xprt_switch_add_xprt_show,
                rpc_sysfs_xprt_switch_add_xprt_store);

static struct attribute *rpc_sysfs_xprt_switch_attrs[] = {
        &rpc_sysfs_xprt_switch_info.attr,
        &rpc_sysfs_xprt_switch_add_xprt.attr,
        NULL,
};
ATTRIBUTE_GROUPS(rpc_sysfs_xprt_switch);

static const struct kobj_type rpc_sysfs_client_type = {
        .release = rpc_sysfs_client_release,
        .default_groups = rpc_sysfs_rpc_clnt_groups,
        .sysfs_ops = &kobj_sysfs_ops,
        .namespace = rpc_sysfs_client_namespace,
};

static const struct kobj_type rpc_sysfs_xprt_switch_type = {
        .release = rpc_sysfs_xprt_switch_release,
        .default_groups = rpc_sysfs_xprt_switch_groups,
        .sysfs_ops = &kobj_sysfs_ops,
        .namespace = rpc_sysfs_xprt_switch_namespace,
};

static const struct kobj_type rpc_sysfs_xprt_type = {
        .release = rpc_sysfs_xprt_release,
        .default_groups = rpc_sysfs_xprt_groups,
        .sysfs_ops = &kobj_sysfs_ops,
        .namespace = rpc_sysfs_xprt_namespace,
};

void rpc_sysfs_exit(void)
{
        kobject_put(rpc_sunrpc_client_kobj);
        kobject_put(rpc_sunrpc_xprt_switch_kobj);
        kset_unregister(rpc_sunrpc_kset);
}

static struct rpc_sysfs_client *rpc_sysfs_client_alloc(struct kobject *parent,
                                                       struct net *net,
                                                       int clid)
{
        struct rpc_sysfs_client *p;

        p = kzalloc_obj(*p);
        if (p) {
                p->net = net;
                p->kobject.kset = rpc_sunrpc_kset;
                if (kobject_init_and_add(&p->kobject, &rpc_sysfs_client_type,
                                         parent, "clnt-%d", clid) == 0)
                        return p;
                kobject_put(&p->kobject);
        }
        return NULL;
}

static struct rpc_sysfs_xprt_switch *
rpc_sysfs_xprt_switch_alloc(struct kobject *parent,
                            struct rpc_xprt_switch *xprt_switch,
                            struct net *net,
                            gfp_t gfp_flags)
{
        struct rpc_sysfs_xprt_switch *p;

        p = kzalloc_obj(*p, gfp_flags);
        if (p) {
                p->net = net;
                p->kobject.kset = rpc_sunrpc_kset;
                if (kobject_init_and_add(&p->kobject,
                                         &rpc_sysfs_xprt_switch_type,
                                         parent, "switch-%d",
                                         xprt_switch->xps_id) == 0)
                        return p;
                kobject_put(&p->kobject);
        }
        return NULL;
}

static struct rpc_sysfs_xprt *rpc_sysfs_xprt_alloc(struct kobject *parent,
                                                   struct rpc_xprt *xprt,
                                                   gfp_t gfp_flags)
{
        struct rpc_sysfs_xprt *p;

        p = kzalloc_obj(*p, gfp_flags);
        if (!p)
                goto out;
        p->kobject.kset = rpc_sunrpc_kset;
        if (kobject_init_and_add(&p->kobject, &rpc_sysfs_xprt_type,
                                 parent, "xprt-%d-%s", xprt->id,
                                 xprt->address_strings[RPC_DISPLAY_PROTO]) == 0)
                return p;
        kobject_put(&p->kobject);
out:
        return NULL;
}

void rpc_sysfs_client_setup(struct rpc_clnt *clnt,
                            struct rpc_xprt_switch *xprt_switch,
                            struct net *net)
{
        struct rpc_sysfs_client *rpc_client;
        struct rpc_sysfs_xprt_switch *xswitch =
                (struct rpc_sysfs_xprt_switch *)xprt_switch->xps_sysfs;

        if (!xswitch)
                return;

        rpc_client = rpc_sysfs_client_alloc(rpc_sunrpc_client_kobj,
                                            net, clnt->cl_clid);
        if (rpc_client) {
                char name[] = "switch";
                int ret;

                clnt->cl_sysfs = rpc_client;
                rpc_client->clnt = clnt;
                rpc_client->xprt_switch = xprt_switch;
                kobject_uevent(&rpc_client->kobject, KOBJ_ADD);
                ret = sysfs_create_link_nowarn(&rpc_client->kobject,
                                               &xswitch->kobject, name);
                if (ret)
                        pr_warn("can't create link to %s in sysfs (%d)\n",
                                name, ret);
        }
}

void rpc_sysfs_xprt_switch_setup(struct rpc_xprt_switch *xprt_switch,
                                 struct rpc_xprt *xprt,
                                 gfp_t gfp_flags)
{
        struct rpc_sysfs_xprt_switch *rpc_xprt_switch;
        struct net *net;

        if (xprt_switch->xps_net)
                net = xprt_switch->xps_net;
        else
                net = xprt->xprt_net;
        rpc_xprt_switch =
                rpc_sysfs_xprt_switch_alloc(rpc_sunrpc_xprt_switch_kobj,
                                            xprt_switch, net, gfp_flags);
        if (rpc_xprt_switch) {
                xprt_switch->xps_sysfs = rpc_xprt_switch;
                rpc_xprt_switch->xprt_switch = xprt_switch;
                rpc_xprt_switch->xprt = xprt;
                kobject_uevent(&rpc_xprt_switch->kobject, KOBJ_ADD);
        } else {
                xprt_switch->xps_sysfs = NULL;
        }
}

void rpc_sysfs_xprt_setup(struct rpc_xprt_switch *xprt_switch,
                          struct rpc_xprt *xprt,
                          gfp_t gfp_flags)
{
        struct rpc_sysfs_xprt *rpc_xprt;
        struct rpc_sysfs_xprt_switch *switch_obj =
                (struct rpc_sysfs_xprt_switch *)xprt_switch->xps_sysfs;

        if (!switch_obj)
                return;

        rpc_xprt = rpc_sysfs_xprt_alloc(&switch_obj->kobject, xprt, gfp_flags);
        if (rpc_xprt) {
                xprt->xprt_sysfs = rpc_xprt;
                rpc_xprt->xprt = xprt;
                rpc_xprt->xprt_switch = xprt_switch;
                kobject_uevent(&rpc_xprt->kobject, KOBJ_ADD);
        }
}

void rpc_sysfs_client_destroy(struct rpc_clnt *clnt)
{
        struct rpc_sysfs_client *rpc_client = clnt->cl_sysfs;

        if (rpc_client) {
                char name[] = "switch";

                sysfs_remove_link(&rpc_client->kobject, name);
                kobject_uevent(&rpc_client->kobject, KOBJ_REMOVE);
                kobject_del(&rpc_client->kobject);
                kobject_put(&rpc_client->kobject);
                clnt->cl_sysfs = NULL;
        }
}

void rpc_sysfs_xprt_switch_destroy(struct rpc_xprt_switch *xprt_switch)
{
        struct rpc_sysfs_xprt_switch *rpc_xprt_switch = xprt_switch->xps_sysfs;

        if (rpc_xprt_switch) {
                kobject_uevent(&rpc_xprt_switch->kobject, KOBJ_REMOVE);
                kobject_del(&rpc_xprt_switch->kobject);
                kobject_put(&rpc_xprt_switch->kobject);
                xprt_switch->xps_sysfs = NULL;
        }
}

void rpc_sysfs_xprt_destroy(struct rpc_xprt *xprt)
{
        struct rpc_sysfs_xprt *rpc_xprt = xprt->xprt_sysfs;

        if (rpc_xprt) {
                kobject_uevent(&rpc_xprt->kobject, KOBJ_REMOVE);
                kobject_del(&rpc_xprt->kobject);
                kobject_put(&rpc_xprt->kobject);
                xprt->xprt_sysfs = NULL;
        }
}