[PATCH] RPC: load RPC transport implementations dynamically Now that we have an RPC transport switch, we have introduced the potential to add new transport capabilities for use by the RPC client at run-time. Allow RPC client transport implementations to be loaded as needed, or as they become available from distributors or third-party vendors. Test-plan: Build kernel with NFS and SUNRPC in a module. Try loading and unloading the IPv4 transport module. Try mounting without the IPv4 module loaded. Destructive testing. Try unloading SUNRPC or the IPv4 transport module while there are active NFS mounts. Reboot the client after mounting NFS shares. Version: Thu, 10 Mar 2005 18:57:35 -0500 Signed-off-by: Chuck Lever --- fs/Kconfig | 4 + include/linux/sunrpc/xprt.h | 16 +++-- net/sunrpc/Makefile | 3 net/sunrpc/pmap_clnt.c | 3 net/sunrpc/socklib.c | 2 net/sunrpc/sunrpc_syms.c | 2 net/sunrpc/sysctl.c | 6 + net/sunrpc/timer.c | 4 + net/sunrpc/xprt.c | 112 +++++++++++++++++++++++++++++++----- net/sunrpc/xprtsock.c | 71 +++++++++++++++++++++- 10 files changed, 197 insertions(+), 26 deletions(-) diff -X /home/cel/src/linux/dont-diff -Naurp 62-xprt-private/fs/Kconfig 63-xprt-modules/fs/Kconfig --- 62-xprt-private/fs/Kconfig 2005-03-07 15:37:34.313208000 -0500 +++ 63-xprt-modules/fs/Kconfig 2005-03-07 17:37:40.620805000 -0500 @@ -1478,6 +1478,10 @@ config EXPORTFS config SUNRPC tristate + select SUNRPC_XPRT_SOCK + +config SUNRPC_XPRT_SOCK + tristate config SUNRPC_GSS tristate diff -X /home/cel/src/linux/dont-diff -Naurp 62-xprt-private/include/linux/sunrpc/xprt.h 63-xprt-modules/include/linux/sunrpc/xprt.h --- 62-xprt-private/include/linux/sunrpc/xprt.h 2005-03-07 17:36:16.939249000 -0500 +++ 63-xprt-modules/include/linux/sunrpc/xprt.h 2005-03-07 17:37:40.637806000 -0500 @@ -139,6 +139,16 @@ struct rpc_xprt_ops { void (*destroy)(struct rpc_xprt *); }; +struct xprt_type { + struct list_head list; + char name[32]; + struct module * owner; + unsigned short family; + int protocol; + int (*setup)(struct rpc_xprt *, + struct rpc_timeout *); +}; + struct rpc_xprt { atomic_t count; /* Reference counter */ struct rpc_xprt_ops * ops; /* transport methods */ @@ -210,10 +220,8 @@ void xprt_receive(struct rpc_task *); int xprt_adjust_timeout(struct rpc_rqst *req); void xprt_release(struct rpc_task *); void xprt_connect(struct rpc_task *); -int xs_setup_udp(struct rpc_xprt *, - struct rpc_timeout *); -int xs_setup_tcp(struct rpc_xprt *, - struct rpc_timeout *); +int xprt_register(struct xprt_type *); +int xprt_unregister(struct xprt_type *); /* * RPC bind helpers diff -X /home/cel/src/linux/dont-diff -Naurp 62-xprt-private/net/sunrpc/Makefile 63-xprt-modules/net/sunrpc/Makefile --- 62-xprt-private/net/sunrpc/Makefile 2005-03-07 15:43:52.221354000 -0500 +++ 63-xprt-modules/net/sunrpc/Makefile 2005-03-07 17:37:40.654806000 -0500 @@ -5,8 +5,9 @@ obj-$(CONFIG_SUNRPC) += sunrpc.o obj-$(CONFIG_SUNRPC_GSS) += auth_gss/ +obj-$(CONFIG_SUNRPC_XPRT_SOCK) += xprtsock.o -sunrpc-y := clnt.o xprt.o socklib.o xprtsock.o sched.o \ +sunrpc-y := clnt.o xprt.o socklib.o sched.o \ auth.o auth_null.o auth_unix.o \ svc.o svcsock.o svcauth.o svcauth_unix.o \ pmap_clnt.o timer.o xdr.o \ diff -X /home/cel/src/linux/dont-diff -Naurp 62-xprt-private/net/sunrpc/pmap_clnt.c 63-xprt-modules/net/sunrpc/pmap_clnt.c --- 62-xprt-private/net/sunrpc/pmap_clnt.c 2005-03-07 17:07:49.295695000 -0500 +++ 63-xprt-modules/net/sunrpc/pmap_clnt.c 2005-03-07 17:37:40.671805000 -0500 @@ -9,6 +9,8 @@ */ #include +#include + #include #include #include @@ -124,6 +126,7 @@ bailout: bailout_nofree: pmap_wake_portmap_waiters(xprt); } +EXPORT_SYMBOL_GPL(pmap_getport); #ifdef CONFIG_ROOT_NFS int pmap_getport_external(struct sockaddr_in *sin, u32 prog, u32 vers, int prot) diff -X /home/cel/src/linux/dont-diff -Naurp 62-xprt-private/net/sunrpc/socklib.c 63-xprt-modules/net/sunrpc/socklib.c --- 62-xprt-private/net/sunrpc/socklib.c 2005-03-07 15:41:19.441765000 -0500 +++ 63-xprt-modules/net/sunrpc/socklib.c 2005-03-07 17:37:40.682806000 -0500 @@ -126,6 +126,7 @@ copy_tail: return 0; } +EXPORT_SYMBOL_GPL(xdr_partial_copy_from_skb); /** * csum_partial_copy_to_xdr - checksum and copy data @@ -166,3 +167,4 @@ no_checksum: return -1; return 0; } +EXPORT_SYMBOL_GPL(csum_partial_copy_to_xdr); diff -X /home/cel/src/linux/dont-diff -Naurp 62-xprt-private/net/sunrpc/sunrpc_syms.c 63-xprt-modules/net/sunrpc/sunrpc_syms.c --- 62-xprt-private/net/sunrpc/sunrpc_syms.c 2005-03-07 16:55:19.965452000 -0500 +++ 63-xprt-modules/net/sunrpc/sunrpc_syms.c 2005-03-07 17:37:40.697805000 -0500 @@ -60,8 +60,6 @@ EXPORT_SYMBOL(rpc_mkpipe); /* Client transport */ EXPORT_SYMBOL(xprt_set_timeout); -EXPORT_SYMBOL(xprt_udp_slot_table_entries); -EXPORT_SYMBOL(xprt_tcp_slot_table_entries); /* Client credential cache */ EXPORT_SYMBOL(rpcauth_register); diff -X /home/cel/src/linux/dont-diff -Naurp 62-xprt-private/net/sunrpc/sysctl.c 63-xprt-modules/net/sunrpc/sysctl.c --- 62-xprt-private/net/sunrpc/sysctl.c 2005-03-07 17:12:20.465853000 -0500 +++ 63-xprt-modules/net/sunrpc/sysctl.c 2005-03-07 17:37:40.712805000 -0500 @@ -120,11 +120,13 @@ done: } unsigned int xprt_udp_slot_table_entries = RPC_DEF_SLOT_TABLE; +EXPORT_SYMBOL_GPL(xprt_udp_slot_table_entries); unsigned int xprt_tcp_slot_table_entries = RPC_DEF_SLOT_TABLE; +EXPORT_SYMBOL_GPL(xprt_tcp_slot_table_entries); unsigned int xprt_min_resvport = RPC_DEF_MIN_RESVPORT; -EXPORT_SYMBOL(xprt_min_resvport); +EXPORT_SYMBOL_GPL(xprt_min_resvport); unsigned int xprt_max_resvport = RPC_DEF_MAX_RESVPORT; -EXPORT_SYMBOL(xprt_max_resvport); +EXPORT_SYMBOL_GPL(xprt_max_resvport); static unsigned int min_slot_table_size = RPC_MIN_SLOT_TABLE; diff -X /home/cel/src/linux/dont-diff -Naurp 62-xprt-private/net/sunrpc/timer.c 63-xprt-modules/net/sunrpc/timer.c --- 62-xprt-private/net/sunrpc/timer.c 2005-03-02 02:38:25.000000000 -0500 +++ 63-xprt-modules/net/sunrpc/timer.c 2005-03-07 17:37:40.723806000 -0500 @@ -17,6 +17,7 @@ #include #include +#include #include #include @@ -42,6 +43,7 @@ rpc_init_rtt(struct rpc_rtt *rt, unsigne rt->ntimeouts[i] = 0; } } +EXPORT_SYMBOL_GPL(rpc_init_rtt); /* * NB: When computing the smoothed RTT and standard deviation, @@ -77,6 +79,7 @@ rpc_update_rtt(struct rpc_rtt *rt, unsig if (*sdrtt < RPC_RTO_MIN) *sdrtt = RPC_RTO_MIN; } +EXPORT_SYMBOL_GPL(rpc_update_rtt); /* * Estimate rto for an nfs rpc sent via. an unreliable datagram. @@ -105,3 +108,4 @@ rpc_calc_rto(struct rpc_rtt *rt, unsigne return res; } +EXPORT_SYMBOL_GPL(rpc_calc_rto); diff -X /home/cel/src/linux/dont-diff -Naurp 62-xprt-private/net/sunrpc/xprt.c 63-xprt-modules/net/sunrpc/xprt.c --- 62-xprt-private/net/sunrpc/xprt.c 2005-03-07 17:34:47.529148000 -0500 +++ 63-xprt-modules/net/sunrpc/xprt.c 2005-03-07 17:37:40.743806000 -0500 @@ -61,6 +61,85 @@ static void xprt_connect_status(struct r static int xprt_clear_backlog(struct rpc_xprt *xprt); +static spinlock_t xprt_list_lock = SPIN_LOCK_UNLOCKED; +static LIST_HEAD(xprt_list); + +/** + * xprt_register - register a transport implementation + * transport: transport to register + * + * If a transport implementation is loaded as a kernel module, it can + * call this interface to make itself known to the RPC client. + * + * Returns: + * 0: transport successfully registered + * -EEXIST: transport already registered + * -EINVAL: transport module being unloaded + */ +int xprt_register(struct xprt_type *transport) +{ + struct xprt_type *t; + int result; + + result = -EEXIST; + spin_lock(&xprt_list_lock); + list_for_each_entry(t, &xprt_list, list) { + /* don't register the same transport class twice */ + if (t == transport) + goto out; + + /* don't register the same proto/family combo twice */ + if (t->protocol == transport->protocol && + t->family == transport->family) + goto out; + } + + result = -EINVAL; + if (try_module_get(THIS_MODULE)) { + list_add(&transport->list, &xprt_list); + printk(KERN_INFO "RPC: Registered %s transport module.\n", + transport->name); + result = 0; + } + +out: + spin_unlock(&xprt_list_lock); + return result; +} +EXPORT_SYMBOL_GPL(xprt_register); + +/** + * xprt_unregister - unregister a transport implementation + * transport: transport to unregister + * + * Returns: + * 0: transport successfully unregistered + * -ENOENT: transport never registered + */ +int xprt_unregister(struct xprt_type *transport) +{ + struct xprt_type *t; + int result; + + result = 0; + spin_lock(&xprt_list_lock); + list_for_each_entry(t, &xprt_list, list) { + if (t == transport) { + printk(KERN_INFO "RPC: Unregistered %s transport module.\n", + transport->name); + list_del_init(&transport->list); + module_put(THIS_MODULE); + goto out; + } + } + result = -ENOENT; + +out: + spin_unlock(&xprt_list_lock); + return result; +} +EXPORT_SYMBOL_GPL(xprt_unregister); + /* * Van Jacobson congestion avoidance. Check if the congestion window * overflowed. The caller will put the task to sleep if this is the case. @@ -224,6 +303,7 @@ void xprt_adjust_cwnd(struct rpc_rqst *r __xprt_put_cong(xprt, req); __xprt_lock_write_next(xprt); } +EXPORT_SYMBOL_GPL(xprt_adjust_cwnd); /* * Reset the major timeout value @@ -299,6 +379,7 @@ void xprt_disconnect(struct rpc_xprt *xp rpc_wake_up_status(&xprt->pending, -ENOTCONN); spin_unlock_bh(&xprt->transport_lock); } +EXPORT_SYMBOL_GPL(xprt_disconnect); /* * Used to allow disconnection when we've been idle @@ -410,6 +491,7 @@ struct rpc_rqst *xprt_lookup_rqst(struct } return req; } +EXPORT_SYMBOL_GPL(xprt_lookup_rqst); /* * Complete reply received. @@ -448,6 +530,7 @@ void xprt_complete_rqst(struct rpc_rqst rpc_wake_up_task(task); return; } +EXPORT_SYMBOL_GPL(xprt_complete_rqst); /* * RPC receive timeout handler. @@ -696,7 +779,22 @@ struct rpc_xprt *xprt_create_transport(i int result; struct rpc_xprt *xprt; struct rpc_rqst *req; + struct xprt_type *t; + spin_lock(&xprt_list_lock); + list_for_each_entry(t, &xprt_list, list) { + if ((t->family == ap->sa_family) && + (t->protocol == proto)) { + spin_unlock(&xprt_list_lock); + goto found; + } + } + spin_unlock(&xprt_list_lock); + printk(KERN_ERR "RPC: transport (%u/%d) not supported\n", + ap->sa_family, proto); + return ERR_PTR(-EIO); + +found: if ((xprt = kmalloc(sizeof(struct rpc_xprt), GFP_KERNEL)) == NULL) { dprintk("RPC: xprt_create_transport: no memory\n"); return ERR_PTR(-ENOMEM); @@ -711,19 +809,7 @@ struct rpc_xprt *xprt_create_transport(i return ERR_PTR(-EBADF); } - switch (proto) { - case IPPROTO_UDP: - result = xs_setup_udp(xprt, to); - break; - case IPPROTO_TCP: - result = xs_setup_tcp(xprt, to); - break; - default: - printk(KERN_ERR "RPC: unrecognized transport protocol: %d\n", - proto); - result = -EIO; - break; - } + result = t->setup(xprt, to); if (result) { kfree(xprt); dprintk("RPC: xprt_create_transport: failed, %d\n", result); diff -X /home/cel/src/linux/dont-diff -Naurp 62-xprt-private/net/sunrpc/xprtsock.c 63-xprt-modules/net/sunrpc/xprtsock.c --- 62-xprt-private/net/sunrpc/xprtsock.c 2005-03-07 17:36:16.954249000 -0500 +++ 63-xprt-modules/net/sunrpc/xprtsock.c 2005-03-07 17:37:40.764805000 -0500 @@ -15,6 +15,7 @@ #include #include +#include #include #include #include @@ -591,6 +592,7 @@ static void xs_destroy(struct rpc_xprt * xs_close(xprt); kfree(xprt->slot); kfree(xprt->transport_data); + module_put(THIS_MODULE); } /** @@ -1509,7 +1511,7 @@ static struct rpc_xprt_ops xs_tcp_ops = * @to: timeout parameters * */ -int xs_setup_udp(struct rpc_xprt *xprt, struct rpc_timeout *to) +static int xs_setup_udp(struct rpc_xprt *xprt, struct rpc_timeout *to) { struct xs_private *priv; size_t slot_table_size; @@ -1553,7 +1555,12 @@ int xs_setup_udp(struct rpc_xprt *xprt, else xprt_set_timeout(&xprt->timeout, 5, 5 * HZ); - return 0; + if (try_module_get(THIS_MODULE)) + return 0; + + kfree(priv); + xprt->transport_data = NULL; + return -EINVAL; } /** @@ -1562,7 +1569,7 @@ int xs_setup_udp(struct rpc_xprt *xprt, * @to: timeout parameters * */ -int xs_setup_tcp(struct rpc_xprt *xprt, struct rpc_timeout *to) +static int xs_setup_tcp(struct rpc_xprt *xprt, struct rpc_timeout *to) { struct xs_private *priv; size_t slot_table_size; @@ -1605,5 +1612,61 @@ int xs_setup_tcp(struct rpc_xprt *xprt, else xprt_set_timeout(&xprt->timeout, 2, 60 * HZ); - return 0; + if (try_module_get(THIS_MODULE)) + return 0; + + kfree(priv); + xprt->transport_data = NULL; + return -EINVAL; } + +static struct xprt_type xs_udp_type = { + .list = LIST_HEAD_INIT(xs_udp_type.list), + .name = "udp-ipv4", + .owner = THIS_MODULE, + .family = AF_INET, + .protocol = IPPROTO_UDP, + .setup = xs_setup_udp, +}; + +static struct xprt_type xs_tcp_type = { + .list = LIST_HEAD_INIT(xs_tcp_type.list), + .name = "tcp-ipv4", + .owner = THIS_MODULE, + .family = AF_INET, + .protocol = IPPROTO_TCP, + .setup = xs_setup_tcp, +}; + +/** + * init_xs_transport - register UDP and TCP over IPv4 RPC transports + * + */ +static int __init init_xs_transport(void) +{ + int error; + + error = xprt_register(&xs_udp_type); + if (error != 0) { + printk(KERN_ERR "Unable to register RPC UDP transport, err=%d\n", error); + return error; + } + error = xprt_register(&xs_tcp_type); + if (error != 0) + printk(KERN_ERR "Unable to register RPC TCP transport, err=%d\n", error); + return error; +} + +/** + * cleanup_xs_transport - unregister UDP and TCP over IPv4 RPC transports + * + */ +static void __exit cleanup_xs_transport(void) +{ + xprt_unregister(&xs_udp_type); + xprt_unregister(&xs_tcp_type); +} + +MODULE_LICENSE("GPL"); +module_init(init_xs_transport); +module_exit(cleanup_xs_transport);