Lorenzo's original tool used getopt_long to indicate the command, but that's somewhat limiting. This switches it to a subcommand-based interface, where each subcommand can take its own getopt options, in the spirit of commands like nmcli or virsh. There are currently 5 different subcommands: listener get/set listener info version get/set supported NFS versions threads get/set nfsd thread settings status get current RPC processing info autostart start server with settings from /etc/nfs.conf Each can take different options, and we can expand this interface later with more commands as necessary. Signed-off-by: Jeff Layton <jlayton@xxxxxxxxxx> --- configure.ac | 1 + utils/nfsdctl/Makefile.am | 4 +- utils/nfsdctl/nfsdctl.c | 1202 +++++++++++++++++++++++++++++++++++++-------- utils/nfsdctl/nfsdctl.h | 1 + 4 files changed, 1000 insertions(+), 208 deletions(-) diff --git a/configure.ac b/configure.ac index ec5eea79a957..f965865465ae 100644 --- a/configure.ac +++ b/configure.ac @@ -261,6 +261,7 @@ AC_ARG_ENABLE(nfsdctl, dnl Check for libnl3 PKG_CHECK_MODULES(LIBNL3, libnl-3.0 >= 3.1) PKG_CHECK_MODULES(LIBNLGENL3, libnl-genl-3.0 >= 3.1) + PKG_CHECK_MODULES(LIBREADLINE, readline) fi AC_ARG_ENABLE(nfsv4server, diff --git a/utils/nfsdctl/Makefile.am b/utils/nfsdctl/Makefile.am index 9b8484a81a3f..c8522f50d4f8 100644 --- a/utils/nfsdctl/Makefile.am +++ b/utils/nfsdctl/Makefile.am @@ -4,7 +4,7 @@ sbin_PROGRAMS = nfsdctl noinst_HEADERS = nfsdctl.h nfsdctl_SOURCES = nfsdctl.c -nfsdctl_CFLAGS = $(LIBNL3_CFLAGS) $(LIBNLGENL3_CFLAGS) -nfsdctl_LDADD = ../../support/nfs/libnfs.la $(LIBNL3_LIBS) $(LIBNLGENL3_LIBS) +nfsdctl_CFLAGS = $(LIBNL3_CFLAGS) $(LIBNLGENL3_CFLAGS) $(LIBREADLINE_CFLAGS) +nfsdctl_LDADD = ../../support/nfs/libnfs.la $(LIBNL3_LIBS) $(LIBNLGENL3_LIBS) $(LIBREADLINE_LIBS) MAINTAINERCLEANFILES = Makefile.in diff --git a/utils/nfsdctl/nfsdctl.c b/utils/nfsdctl/nfsdctl.c index d80a6ef5102b..ad3e1056e090 100644 --- a/utils/nfsdctl/nfsdctl.c +++ b/utils/nfsdctl/nfsdctl.c @@ -1,14 +1,19 @@ +#define _GNU_SOURCE 1 #include <linux/module.h> #include <linux/version.h> #include <netlink/genl/genl.h> #include <errno.h> #include <stdio.h> #include <stdlib.h> +#include <stdarg.h> #include <time.h> #include <stdbool.h> #include <arpa/inet.h> #include <unistd.h> #include <getopt.h> +#include <string.h> +#include <sched.h> +#include <sys/queue.h> #include <netlink/genl/family.h> #include <netlink/genl/ctrl.h> @@ -16,12 +21,68 @@ #include <netlink/attr.h> #include <linux/netlink.h> +#include <readline/readline.h> +#include <readline/history.h> + #include "nfsdctl.h" +#include "conffile.h" +#include "xlog.h" /* compile note: * gcc -I/usr/include/libnl3/ -o <prog-name> <prog-name>.c -lnl-3 -lnl-genl-3 */ +static int debug_level; +static int nl_family_id; + +struct nfs_version { + uint8_t major; + uint8_t minor; + bool enabled; +}; + +/* + * The NFS server should only have around 5 versions or so, so we don't bother + * with memory allocation here, and just use a global array. + */ +#define MAX_NFS_VERSIONS 16 + +struct nfs_version nfsd_versions[MAX_NFS_VERSIONS]; + +/* + * All of the existing netids are short strings (3-4 chars), but let's allow + * for up to 16. + */ +#define MAX_CLASS_NAME_LEN 16 + +struct server_socket { + struct sockaddr_storage ss; + char name[MAX_CLASS_NAME_LEN]; + bool active; +}; + +#define MAX_NFSD_SOCKETS 256 + +int nfsd_socket_count; +struct server_socket nfsd_sockets[MAX_NFSD_SOCKETS]; + +const char *taskname; + +static const struct option help_only_options[] = { + { "help", no_argument, NULL, 'h' }, + { }, +}; + +static void debug(int level, const char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + if (level <= debug_level) + vprintf(fmt, args); + va_end(args); +} + #define NFSD4_OPS_MAX_LEN sizeof(nfsd4_ops) / sizeof(nfsd4_ops[0]) static const char *nfsd4_ops[] = { [OP_ACCESS] = "OP_ACCESS", @@ -168,9 +229,11 @@ static void parse_rpc_status_get(struct genlmsghdr *gnlh) static void parse_version_get(struct genlmsghdr *gnlh) { struct nlattr *attr; - int rem; + int rem, idx = 0; + + /* clear the nfsd_versions array */ + memset(nfsd_versions, '\0', sizeof(*nfsd_versions) * MAX_NFS_VERSIONS); - printf("Server Versions:"); nla_for_each_attr(attr, genlmsg_attrdata(gnlh, 0), genlmsg_attrlen(gnlh, 0), rem) { struct nlattr *a; @@ -179,54 +242,53 @@ static void parse_version_get(struct genlmsghdr *gnlh) nla_for_each_nested(a, attr, i) { switch (nla_type(a)) { case NFSD_A_VERSION_MAJOR: - printf("\t%d", nla_get_u32(a)); + nfsd_versions[idx].major = nla_get_u32(a); break; case NFSD_A_VERSION_MINOR: - printf(":%d", nla_get_u32(a)); + nfsd_versions[idx].minor = nla_get_u32(a); + break; + case NFSD_A_VERSION_ENABLED: + nfsd_versions[idx].enabled = nla_get_flag(a); break; default: break; } } + ++idx; } - printf("\n"); } static void parse_listener_get(struct genlmsghdr *gnlh) { - int rem, major, minor; struct nlattr *attr; + int rem, idx = 0; + + /* clear the nfsd_sockets array */ + memset(nfsd_sockets, '\0', sizeof(*nfsd_sockets) * MAX_NFSD_SOCKETS); - printf("Server Listeners:"); nla_for_each_attr(attr, genlmsg_attrdata(gnlh, 0), genlmsg_attrlen(gnlh, 0), rem) { - unsigned short proto = 0; - const char *name = NULL; - unsigned port = 0; struct nlattr *a; + char *res; int i; nla_for_each_nested(a, attr, i) { switch (nla_type(a)) { - case NFSD_A_LISTENER_TRANSPORT_NAME: - name = nla_data(a); - break; - case NFSD_A_LISTENER_PORT: - port = nla_get_u32(a); + case NFSD_A_SOCK_TRANSPORT_NAME: + res = strncpy(nfsd_sockets[idx].name, nla_data(a), + MAX_CLASS_NAME_LEN); + res[MAX_CLASS_NAME_LEN - 1] = '\0'; // just to be sure break; - case NFSD_A_LISTENER_INET_PROTO: - proto = nla_get_u16(a); - break; - default: + case NFSD_A_SOCK_ADDR: + memcpy(&nfsd_sockets[idx].ss, nla_data(a), + sizeof(nfsd_sockets[idx].ss)); break; } + nfsd_sockets[idx].active = true; } - - if (name && port && proto) - printf("\n\t%s%s:%d", - name, proto == AF_INET6 ? "6" : "4", port); + ++idx; } - printf("\n"); + nfsd_socket_count = idx; } static int recv_handler(struct nl_msg *msg, void *arg) @@ -240,7 +302,7 @@ static int recv_handler(struct nl_msg *msg, void *arg) break; case NFSD_CMD_THREADS_GET: if (nla_type(attr) == NFSD_A_SERVER_WORKER_THREADS) - printf("Running threads\t: %d\n", nla_get_u32(attr)); + printf("%d\n", nla_get_u32(attr)); break; case NFSD_CMD_VERSION_GET: parse_version_get(gnlh); @@ -255,263 +317,669 @@ static int recv_handler(struct nl_msg *msg, void *arg) return NL_SKIP; } -static const struct option long_options[] = { - { "help", no_argument, NULL, 'h' }, - { "rpc-status", no_argument, NULL, 'R' }, - { "set-threads", required_argument, NULL, 't' }, - { "get-threads", no_argument, NULL, 'T' }, - { "set-version", required_argument, NULL, 'v' }, - { "get-versions", no_argument, NULL, 'V' }, - { "set-sockaddr", required_argument, NULL, 's' }, - { "set-listener", required_argument, NULL, 'p' }, - { "get-listeners", no_argument, NULL, 'P' }, - { }, -}; - -static int get_cmd_type(int arg) -{ - switch (arg) { - case 'R': - return NFSD_CMD_RPC_STATUS_GET; - case 't': - return NFSD_CMD_THREADS_SET; - case 'T': - return NFSD_CMD_THREADS_GET; - case 'v': - return NFSD_CMD_VERSION_SET; - case 'V': - return NFSD_CMD_VERSION_GET; - case 'p': - return NFSD_CMD_LISTENER_SET; - case 'P': - return NFSD_CMD_LISTENER_GET; - case 's': - return NFSD_CMD_SOCK_SET; - case 'h': - default: - return -EINVAL; - } -} - -static void usage(char *argv[], const struct option *long_options) -{ - int i; - - printf("\nOption for %s:\n", argv[0]); - for (i = 0; long_options[i].name != 0; i++) { - printf(" --%-15s", long_options[i].name); - if (long_options[i].flag != NULL) - printf(" flag (internal value: %d)", - *long_options[i].flag); - else - printf("\t short-option: -%c", long_options[i].val); - printf("\n"); - } - printf("\n"); -} - #define BUFFER_SIZE 8192 -static struct nl_msg *netlink_sock_and_msg_alloc(struct nl_sock **sock) +static struct nl_sock *netlink_sock_alloc(void) { - struct nl_msg *msg = NULL; - int ret, id; + struct nl_sock *sock; + int ret; - *sock = nl_socket_alloc(); - if (!(*sock)) + sock = nl_socket_alloc(); + if (!sock) return NULL; - if (genl_connect(*sock)) { + if (genl_connect(sock)) { fprintf(stderr, "Failed to connect to generic netlink\n"); - goto error; + nl_socket_free(sock); + return NULL; } - nl_socket_set_buffer_size(*sock, BUFFER_SIZE, BUFFER_SIZE); - setsockopt(nl_socket_get_fd(*sock), SOL_NETLINK, NETLINK_EXT_ACK, + nl_socket_set_buffer_size(sock, BUFFER_SIZE, BUFFER_SIZE); + setsockopt(nl_socket_get_fd(sock), SOL_NETLINK, NETLINK_EXT_ACK, &ret, sizeof(ret)); - id = genl_ctrl_resolve(*sock, NFSD_FAMILY_NAME); + return sock; +} + +static struct nl_msg *netlink_msg_alloc(struct nl_sock *sock) +{ + struct nl_msg *msg; + int id; + + id = genl_ctrl_resolve(sock, NFSD_FAMILY_NAME); if (id < 0) { fprintf(stderr, "%s not found\n", NFSD_FAMILY_NAME); - goto error; + return NULL; } msg = nlmsg_alloc(); if (!msg) { fprintf(stderr, "failed to allocate netlink message\n"); - goto error; + return NULL; } if (!genlmsg_put(msg, 0, 0, id, 0, 0, 0, 0)) { fprintf(stderr, "failed to allocate netlink message\n"); - goto error; + nlmsg_free(msg); + return NULL; } return msg; -error: - nl_socket_free(*sock); - nlmsg_free(msg); - return NULL; } -int main(char argc, char **argv) +static int status_func(struct nl_sock *sock, int argc, char ** argv) { - int port, proto, nl_cmd = 0, longindex = 0, opt, ret = 1; - char transport[64], addr[64]; struct genlmsghdr *ghdr; struct nlmsghdr *nlh; - struct nl_sock *sock; struct nl_msg *msg; struct nl_cb *cb; + int ret; - if (argc == 1) { - usage(argv, long_options); - return -EINVAL; + /* extra args are ignored */ + + msg = netlink_msg_alloc(sock); + if (!msg) { + fprintf(stderr, "Unable to allocate netlink message!"); + return 1; + } + + nlh = nlmsg_hdr(msg); + nlh->nlmsg_flags |= NLM_F_DUMP; + ghdr = nlmsg_data(nlh); + ghdr->cmd = NFSD_CMD_RPC_STATUS_GET; + + cb = nl_cb_alloc(NL_CB_CUSTOM); + if (!cb) { + fprintf(stderr, "failed to allocate netlink callbacks\n"); + ret = 1; + goto out; } - msg = netlink_sock_and_msg_alloc(&sock); - if (!msg) - return -ENOMEM; + ret = nl_send_auto(sock, msg); + if (ret < 0) + goto out_cb; + + ret = 1; + nl_cb_err(cb, NL_CB_CUSTOM, error_handler, &ret); + nl_cb_set(cb, NL_CB_FINISH, NL_CB_CUSTOM, finish_handler, &ret); + nl_cb_set(cb, NL_CB_ACK, NL_CB_CUSTOM, ack_handler, &ret); + nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, recv_handler, NULL); + + while (ret > 0) + nl_recvmsgs(sock, cb); + if (ret < 0) { + fprintf(stderr, "Error: %s\n", strerror(-ret)); + ret = 1; + } +out_cb: + nl_cb_put(cb); +out: + nlmsg_free(msg); + return ret; +} + +static int threads_doit(struct nl_sock *sock, int cmd, int threads) +{ + struct genlmsghdr *ghdr; + struct nlmsghdr *nlh; + struct nl_msg *msg; + struct nl_cb *cb; + int ret; + + msg = netlink_msg_alloc(sock); + if (!msg) { + fprintf(stderr, "Unable to allocate netlink message!"); + return 1; + } - ret = EINVAL; nlh = nlmsg_hdr(msg); + if (cmd == NFSD_CMD_THREADS_SET) + nla_put_u32(msg, NFSD_A_SERVER_WORKER_THREADS, threads); + ghdr = nlmsg_data(nlh); + ghdr->cmd = cmd; - while ((opt = getopt_long(argc, argv, "Rt:Tv:Vp:Ps:h", - long_options, &longindex)) != -1) { - int cmd = get_cmd_type(opt); - struct nlattr *a; + cb = nl_cb_alloc(NL_CB_CUSTOM); + if (!cb) { + fprintf(stderr, "failed to allocate netlink callbacks\n"); + ret = 1; + goto out; + } - if (cmd < 0) { - usage(argv, long_options); - goto out; + ret = nl_send_auto(sock, msg); + if (ret < 0) { + fprintf(stderr, "send failed (%d)!\n", ret); + goto out_cb; + } + + ret = 1; + nl_cb_err(cb, NL_CB_CUSTOM, error_handler, &ret); + nl_cb_set(cb, NL_CB_FINISH, NL_CB_CUSTOM, finish_handler, &ret); + nl_cb_set(cb, NL_CB_ACK, NL_CB_CUSTOM, ack_handler, &ret); + nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, recv_handler, NULL); + + while (ret > 0) + nl_recvmsgs(sock, cb); + if (ret < 0) { + fprintf(stderr, "Error: %s\n", strerror(-ret)); + ret = 1; + } +out_cb: + nl_cb_put(cb); +out: + nlmsg_free(msg); + return ret; +} + +static int threads_func(struct nl_sock *sock, int argc, char ** argv) +{ + uint8_t cmd = NFSD_CMD_THREADS_GET; + char *endptr = NULL; + int threads = 0; + + if (argc > 1) { + /* empty string? */ + if (argv[1][0] == '\0') { + fprintf(stderr, "Invalid threads value %s.\n", argv[1]); + return 1; } - if (nl_cmd && cmd != nl_cmd) { - usage(argv, long_options); - goto out; + threads = strtol(argv[1], &endptr, 0); + if (!endptr || *endptr != '\0') { + fprintf(stderr, "Invalid threads value %s.\n", argv[1]); + return 1; } + cmd = NFSD_CMD_THREADS_SET; + } + return threads_doit(sock, cmd, threads); +} + +/* + * Update the nfsd_versions array with the latest info from the kernel + */ +static int fetch_nfsd_versions(struct nl_sock *sock) +{ + struct genlmsghdr *ghdr; + struct nlmsghdr *nlh; + struct nl_msg *msg; + struct nl_cb *cb; + int ret; + + msg = netlink_msg_alloc(sock); + if (!msg) { + fprintf(stderr, "Unable to allocate netlink message!"); + return 1; + } + + nlh = nlmsg_hdr(msg); + ghdr = nlmsg_data(nlh); + ghdr->cmd = NFSD_CMD_VERSION_GET; + + cb = nl_cb_alloc(NL_CB_CUSTOM); + if (!cb) { + fprintf(stderr, "failed to allocate netlink callbacks\n"); + ret = 1; + goto out; + } + + ret = nl_send_auto(sock, msg); + if (ret < 0) { + fprintf(stderr, "send failed: %d\n", ret); + goto out_cb; + } + + ret = 1; + nl_cb_err(cb, NL_CB_CUSTOM, error_handler, &ret); + nl_cb_set(cb, NL_CB_FINISH, NL_CB_CUSTOM, finish_handler, &ret); + nl_cb_set(cb, NL_CB_ACK, NL_CB_CUSTOM, ack_handler, &ret); + nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, recv_handler, NULL); + + while (ret > 0) + nl_recvmsgs(sock, cb); + if (ret < 0) { + fprintf(stderr, "Error: %s\n", strerror(-ret)); + ret = 1; + } +out_cb: + nl_cb_put(cb); +out: + nlmsg_free(msg); + return ret; +} + +static void print_versions_array(void) +{ + int i; - nl_cmd = cmd; - switch (nl_cmd) { - case NFSD_CMD_RPC_STATUS_GET: - nlh->nlmsg_flags |= NLM_F_DUMP; + for (i = 0; i < MAX_NFS_VERSIONS; ++i) { + /* A major of zero indicates the end of the array */ + if (nfsd_versions[i].major == 0) break; - case NFSD_CMD_THREADS_SET: { - int thread = strtoul(optarg, NULL, 0); + if (i != 0) + printf(" "); + printf("%c%hhd.%hhd", + nfsd_versions[i].enabled ? '+' : '-', + nfsd_versions[i].major, nfsd_versions[i].minor); + } + putchar('\n'); +} + +static int set_nfsd_versions(struct nl_sock *sock) +{ + struct genlmsghdr *ghdr; + struct nlmsghdr *nlh; + struct nl_msg *msg; + struct nl_cb *cb; + int i, ret; + + msg = netlink_msg_alloc(sock); + if (!msg) { + fprintf(stderr, "Unable to allocate netlink message!"); + return 1; + } + + nlh = nlmsg_hdr(msg); + + for (i = 0; i < MAX_NFS_VERSIONS; ++i) { + struct nlattr *a; - nla_put_u32(msg, NFSD_A_SERVER_WORKER_THREADS, thread); + if (nfsd_versions[i].major == 0) break; + + a = nla_nest_start(msg, NLA_F_NESTED | NFSD_A_SERVER_PROTO_VERSION); + if (!a) { + fprintf(stderr, "Unable to allocate version nest!\n"); + ret = 1; + goto out; } - case NFSD_CMD_VERSION_SET: { - int major, minor; - if (sscanf(optarg, "%d.%d", &major, &minor) != 2) { - usage(argv, long_options); - goto out; - } + nla_put_u32(msg, NFSD_A_VERSION_MAJOR, nfsd_versions[i].major); + nla_put_u32(msg, NFSD_A_VERSION_MINOR, nfsd_versions[i].minor); + if (nfsd_versions[i].enabled) + nla_put_flag(msg, NFSD_A_VERSION_ENABLED); + nla_nest_end(msg, a); + } + ghdr = nlmsg_data(nlh); + ghdr->cmd = NFSD_CMD_VERSION_SET; - a = nla_nest_start(msg, - NLA_F_NESTED | NFSD_A_SERVER_PROTO_VERSION); - if (!a) { - ret = -ENOMEM; - goto out; - } + cb = nl_cb_alloc(NL_CB_CUSTOM); + if (!cb) { + fprintf(stderr, "Failed to allocate netlink callbacks\n"); + ret = 1; + goto out; + } + + ret = nl_send_auto(sock, msg); + if (ret < 0) { + fprintf(stderr, "Send failed: %d\n", ret); + goto out_cb; + } - nla_put_u32(msg, NFSD_A_VERSION_MAJOR, major); - nla_put_u32(msg, NFSD_A_VERSION_MINOR, minor); - nla_nest_end(msg, a); + ret = 1; + nl_cb_err(cb, NL_CB_CUSTOM, error_handler, &ret); + nl_cb_set(cb, NL_CB_FINISH, NL_CB_CUSTOM, finish_handler, &ret); + nl_cb_set(cb, NL_CB_ACK, NL_CB_CUSTOM, ack_handler, &ret); + nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, recv_handler, NULL); + + while (ret > 0) + nl_recvmsgs(sock, cb); + if (ret < 0) { + fprintf(stderr, "Error: %s\n", strerror(-ret)); + ret = 1; + } +out_cb: + nl_cb_put(cb); +out: + nlmsg_free(msg); + return ret; +} + +static int update_nfsd_version(int major, int minor, bool enabled) +{ + int i; + + for (i = 0; i < MAX_NFS_VERSIONS; ++i) { + if (nfsd_versions[i].major == 0) break; + if (nfsd_versions[i].major == major && nfsd_versions[i].minor == minor) { + nfsd_versions[i].enabled = enabled; + return 0; } - case NFSD_CMD_LISTENER_SET: - if (sscanf(optarg, "%s.%d.%d", - transport, &port, &proto) != 3) { - usage(argv, long_options); - goto out; - } + } + /* the kernel doesn't support this version */ + if (!enabled) + return 0; + fprintf(stderr, "This kernel does not support NFS version %d.%d\n", major, minor); + return -EINVAL; +} +static int version_func(struct nl_sock *sock, int argc, char ** argv) +{ + char *endptr = NULL; + int ret, threads, i; - a = nla_nest_start(msg, - NLA_F_NESTED | NFSD_A_SERVER_LISTENER_INSTANCE); - if (!a) { - ret = -ENOMEM; - goto out; - } - nla_put_string(msg, NFSD_A_LISTENER_TRANSPORT_NAME, - transport); - nla_put_u32(msg, NFSD_A_LISTENER_PORT, port); - nla_put_u16(msg, NFSD_A_LISTENER_INET_PROTO, proto); - nla_nest_end(msg, a); - break; - case NFSD_CMD_SOCK_SET: { - struct sockaddr_storage sa_storage = {}; + ret = fetch_nfsd_versions(sock); + if (ret) + return ret; + + if (argc > 1) { + for (i = 1; i < argc; ++i) { + int i, ret, major, minor = 0; + char sign = '\0', *str = argv[i]; + bool enabled; - if (sscanf(optarg, "[%s].%s.%d.%d", - addr, &port, transport, &proto) != 4) { - usage(argv, long_options); - goto out; + ret = sscanf(str, "%c%d.%d\n", &sign, &major, &minor); + if (ret < 2) { + fprintf(stderr, "Invalid version string (%d) %s\n", ret, str); + return -EINVAL; } - switch (proto) { - case AF_INET: { - struct sockaddr_in *sin = (void *)&sa_storage; - - sin->sin_family = AF_INET; - sin->sin_port = htons(port); - if (inet_pton(AF_INET, addr, - &sin->sin_addr) != 1) { - ret = -EINVAL; - goto out; - } + switch(sign) { + case '+': + enabled = true; break; - } - case AF_INET6: { - struct sockaddr_in6 *sin6 = (void *)&sa_storage; - - sin6->sin6_family = AF_INET6; - sin6->sin6_port = htons(port); - if (inet_pton(AF_INET6, addr, - &sin6->sin6_addr) != 1) { - ret = -EINVAL; - goto out; - } + case '-': + enabled = false; break; - } default: - ret = -EINVAL; - goto out; + fprintf(stderr, "Invalid version string %s\n", str); + return -EINVAL; } - a = nla_nest_start(msg, - NLA_F_NESTED | NFSD_A_SERVER_SOCK_ADDR); - if (!a) { - ret = -ENOMEM; - goto out_cb; + ret = update_nfsd_version(major, minor, enabled); + if (ret) + return ret; + } + return set_nfsd_versions(sock); + } + + print_versions_array(); + return 0; +} + +static int fetch_current_listeners(struct nl_sock *sock) +{ + struct genlmsghdr *ghdr; + struct nlmsghdr *nlh; + struct nl_msg *msg; + struct nl_cb *cb; + int ret; + + msg = netlink_msg_alloc(sock); + if (!msg) { + fprintf(stderr, "Unable to allocate netlink message!"); + return 1; + } + + nlh = nlmsg_hdr(msg); + ghdr = nlmsg_data(nlh); + ghdr->cmd = NFSD_CMD_LISTENER_GET; + + cb = nl_cb_alloc(NL_CB_CUSTOM); + if (!cb) { + fprintf(stderr, "failed to allocate netlink callbacks\n"); + ret = 1; + goto out; + } + + ret = nl_send_auto(sock, msg); + if (ret < 0) { + fprintf(stderr, "send failed: %d\n", ret); + goto out_cb; + } + + ret = 1; + nl_cb_err(cb, NL_CB_CUSTOM, error_handler, &ret); + nl_cb_set(cb, NL_CB_FINISH, NL_CB_CUSTOM, finish_handler, &ret); + nl_cb_set(cb, NL_CB_ACK, NL_CB_CUSTOM, ack_handler, &ret); + nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, recv_handler, NULL); + + while (ret > 0) + nl_recvmsgs(sock, cb); + if (ret < 0) { + fprintf(stderr, "Error: %s\n", strerror(-ret)); + ret = 1; + } +out_cb: + nl_cb_put(cb); +out: + nlmsg_free(msg); + return ret; +} + +static void print_listeners(void) +{ + int i; + const char *res; + + for (i = 0; i < MAX_NFSD_SOCKETS; ++i) { + struct server_socket *sock = &nfsd_sockets[i]; + char addr[INET6_ADDRSTRLEN + 1]; + in_port_t port = 0; + + if (*sock->name == '\0') + break; + + if (!sock->active) + continue; + + switch(sock->ss.ss_family) { + case AF_INET: + res = inet_ntop(AF_INET, &((struct sockaddr_in *)(&sock->ss))->sin_addr, + addr, INET6_ADDRSTRLEN); + port = ((struct sockaddr_in *)(&sock->ss))->sin_port; + if (res == NULL) + perror("inet_ntop"); + else + printf("%s:%s:%hu\n", sock->name, addr, ntohs(port)); + break; + case AF_INET6: + res = inet_ntop(AF_INET6, &((struct sockaddr_in6 *)(&sock->ss))->sin6_addr, + addr, INET6_ADDRSTRLEN); + port = ((struct sockaddr_in6 *)(&sock->ss))->sin6_port; + if (res == NULL) + perror("inet_ntop"); + else + printf("%s:[%s]:%hu\n", sock->name, addr, ntohs(port)); + break; + default: + snprintf(addr, INET6_ADDRSTRLEN, "Unknown address family: %d\n", + sock->ss.ss_family); + addr[INET6_ADDRSTRLEN - 1] = '\0'; + } + } +} + +#define BUFLEN (INET6_ADDRSTRLEN + 16) + +/* + * Format is <+/-><netid>:<address>:port + * + * + or -: denotes whether we're adding or removing a socket + * netid: tcp, udp, rdma (something else in the future?( + * address: IPv4 or IPv6 address. IPv6 addr should be in square brackets + * port: decimal port value + */ +static int update_listeners(const char *str) +{ + char buf[INET6_ADDRSTRLEN + 16]; + char sign = *str; + char *netid, *addr, *port, *end; + struct addrinfo *res, *ai; + int i, ret; + struct addrinfo hints = { .ai_flags = AI_PASSIVE, + .ai_family = AF_INET, + .ai_socktype = SOCK_STREAM, + .ai_protocol = IPPROTO_TCP }; + + if (sign != '+' && sign != '-') + goto out_inval; + + strcpy(buf, str + 1); + + /* netid is start */ + netid = buf; + + /* find first ':' */ + addr = strchr(buf, ':'); + if (!addr) + goto out_inval; + + if (addr == buf) { + /* empty netid */ + goto out_inval; + } + *addr = '\0'; + ++addr; + + port = strrchr(addr, ':'); + if (!port) + goto out_inval; + if (port == addr) { + /* empty address, give gai a NULL ptr */ + addr = NULL; + } + *port = '\0'; + port++; + + if (*port == '\0') { + /* empty port */ + goto out_inval; + } + + /* IPv6 addrs must be in square brackets */ + if (addr && *addr == '[') { + hints.ai_family = AF_INET6; + ++addr; + end = strchr(addr, ']'); + if (!end) + goto out_inval; + if (end == addr) + addr = NULL; + *end = '\0'; + } + + /* + * If we're looking for wildcard address, look for both + * families. + */ + if (!addr) + hints.ai_family = AF_UNSPEC; + + /* + * Note that we hint for a stream/tcp socket just to limit the number of + * entries that come back. We're only interested in the sockaddrs. + */ + ret = getaddrinfo(addr, port, &hints, &res); + if (ret) { + fprintf(stderr, "getaddrinfo of \"%s\" failed: %s\n", + addr, gai_strerror(ret)); + return -EINVAL; + } + + for ( ; res; res = res->ai_next) { + struct sockaddr_in6 *r6 = (struct sockaddr_in6 *)res->ai_addr; + struct sockaddr_in *r4 = (struct sockaddr_in *)res->ai_addr; + bool found = false; + + for (i = 0; i < MAX_NFSD_SOCKETS; ++i) { + struct server_socket *sock = &nfsd_sockets[i]; + struct sockaddr_in6 *l6 = (struct sockaddr_in6 *)&sock->ss; + struct sockaddr_in *l4 = (struct sockaddr_in *)&sock->ss; + + if (sock->ss.ss_family == AF_UNSPEC) + break; + + if (sock->ss.ss_family != res->ai_addr->sa_family) + continue; + + if (strcmp(sock->name, netid)) + continue; + + switch(sock->ss.ss_family) { + case AF_INET: + if (r4->sin_port != l4->sin_port || + memcmp(&r4->sin_addr, &l4->sin_addr, sizeof(l4->sin_addr))) + continue; + case AF_INET6: + if (r6->sin6_port != l6->sin6_port || + memcmp(&r6->sin6_addr, &l6->sin6_addr, sizeof(l6->sin6_addr))) + continue; + default: + } - nla_put(msg, NFSD_A_SOCK_ADDR, sizeof(sa_storage), - &sa_storage); - nla_put_string(msg, NFSD_A_SOCK_TRANSPORT_NAME, - transport); - nla_nest_end(msg, a); + sock->active = (sign == '+'); + found = true; break; } - default: + if (!found && sign == '+') { + struct server_socket *sock = &nfsd_sockets[nfsd_socket_count]; + + memcpy(&sock->ss, res->ai_addr, res->ai_addrlen); + strncpy(sock->name, netid, MAX_CLASS_NAME_LEN); + sock->name[MAX_CLASS_NAME_LEN - 1] = '\0'; + sock->active = true; + ++nfsd_socket_count; + } + } + return 0; +out_inval: + fprintf(stderr, "Invalid listener update string: %s", str); + return -EINVAL; +} + +static int set_listeners(struct nl_sock *sock) +{ + struct genlmsghdr *ghdr; + struct nlmsghdr *nlh; + struct nl_msg *msg; + struct nl_cb *cb; + int i, ret; + + msg = netlink_msg_alloc(sock); + if (!msg) { + fprintf(stderr, "Unable to allocate netlink message!"); + return 1; + } + + nlh = nlmsg_hdr(msg); + + for (i = 0; i < MAX_NFSD_SOCKETS; ++i) { + struct server_socket *sock = &nfsd_sockets[i]; + struct nlattr *a; + + if (sock->ss.ss_family == 0) break; + + if (!sock->active) + continue; + + a = nla_nest_start(msg, NLA_F_NESTED | NFSD_A_SERVER_SOCK_ADDR); + if (!a) { + fprintf(stderr, "Unable to allocate listener nest!\n"); + ret = 1; + goto out; } + + nla_put(msg, NFSD_A_SOCK_ADDR, sizeof(sock->ss), &sock->ss); + nla_put_string(msg, NFSD_A_SOCK_TRANSPORT_NAME, sock->name); + nla_nest_end(msg, a); } ghdr = nlmsg_data(nlh); - ghdr->cmd = nl_cmd; + ghdr->cmd = NFSD_CMD_LISTENER_SET; cb = nl_cb_alloc(NL_CB_CUSTOM); if (!cb) { - fprintf(stderr, "failed to allocate netlink callbacks\n"); - ret = -ENOMEM; + fprintf(stderr, "Failed to allocate netlink callbacks\n"); + ret = 1; goto out; } - ret = nl_send_auto_complete(sock, msg); - if (ret < 0) + ret = nl_send_auto(sock, msg); + if (ret < 0) { + fprintf(stderr, "Send failed: %d\n", ret); goto out_cb; + } ret = 1; nl_cb_err(cb, NL_CB_CUSTOM, error_handler, &ret); @@ -521,11 +989,333 @@ int main(char argc, char **argv) while (ret > 0) nl_recvmsgs(sock, cb); + if (ret < 0) { + fprintf(stderr, "Error: %s\n", strerror(-ret)); + ret = 1; + } out_cb: nl_cb_put(cb); out: + nlmsg_free(msg); + return ret; +} + +static void listener_usage(void) +{ + printf("Usage: %s listener { {+,-}proto:addr:port } ...\n", taskname); + printf(" + to add a listener, - to remove one\n"); + printf(" proto: protocol (e.g. tcp, udp, rdma)\n"); + printf(" addr: hostname or address to listen on (blank string == wildcard addresses)\n"); + printf(" port: port number or service name to listen on\n\n"); + printf("Examples:\n"); + printf(" Add TCP listener on all addresses (both v4 and v6), port 2049:\n"); + printf(" listener +tcp::2049\n"); + printf(" Add RDMA listener on 1.2.3.4 port 20049:\n"); + printf(" listener +rdma:1.2.3.4:20049\n"); + printf(" Add same listener on IPv6 address f00::ba4 port 20050:\n"); + printf(" listener +rdma:[f00::ba4]:20050\n"); + printf(" Remove UDP listener from nfsserver.example.org, nfs port:\n"); + printf(" listener -udp:nfsserver.example.org:nfs\n\n"); +} + +static int listener_func(struct nl_sock *sock, int argc, char ** argv) +{ + char *endptr = NULL; + int ret, opt, threads, i; + + optind = 1; + while ((opt = getopt_long(argc, argv, "h", help_only_options, NULL)) != -1) { + switch (opt) { + case 'h': + listener_usage(); + return 0; + } + } + + ret = fetch_current_listeners(sock); if (ret) - nlmsg_free(msg); + return ret; + + if (argc - optind > 0) { + for (i = optind; i < argc; ++i) + update_listeners(argv[i]); + return set_listeners(sock); + } + + print_listeners(); + return 0; +} + +#define MAX_LISTENER_LEN (64 * 2 + 16) + +static int +add_listener(const char *netid, const char *addr, const char *port) +{ + char buf[MAX_LISTENER_LEN]; + int ret; + + if (strchr(addr, ':')) + ret = snprintf(buf, MAX_LISTENER_LEN, "+%s:[%s]:%s", + netid, addr, port); + else + ret = snprintf(buf, MAX_LISTENER_LEN, "+%s:%s:%s", + netid, addr, port); + buf[MAX_LISTENER_LEN - 1] = '\0'; + update_listeners(buf); +} + +static void +read_nfsd_conf(void) +{ + conf_init_file(NFS_CONFFILE); + xlog_set_debug("nfsd"); +} + +static void configure_versions(void) +{ + bool v4 = conf_get_bool("nfsd", "vers4", true); + + update_nfsd_version(2, 0, conf_get_bool("nfsd", "vers2", false)); + update_nfsd_version(3, 0, conf_get_bool("nfsd", "vers3", true)); + update_nfsd_version(4, 0, v4 && conf_get_bool("nfsd", "vers4.0", true)); + update_nfsd_version(4, 1, v4 && conf_get_bool("nfsd", "vers4.1", true)); + update_nfsd_version(4, 2, v4 && conf_get_bool("nfsd", "vers4.2", true)); +} + +static void configure_listeners(void) +{ + char *port, *rdma_port; + bool rdma, udp, tcp; + struct conf_list *hosts; + + udp = conf_get_bool("nfsd", "udp", false); + tcp = conf_get_bool("nfsd", "tcp", true); + port = conf_get_str("nfsd", "port"); + if (!port) + port = "nfs"; + + rdma = conf_get_bool("nfsd", "rdma", false); + if (rdma) { + rdma_port = conf_get_str("nfsd", "rdma-port"); + if (!rdma_port) + rdma_port = "nfsrdma"; + } + + /* backward compatibility - nfs.conf used to set rdma port directly */ + if (!rdma_port) + rdma_port = conf_get_str("nfsd", "rdma"); + + hosts = conf_get_list("nfsd", "host"); + if (hosts && hosts->cnt) { + struct conf_list_node *n; + TAILQ_FOREACH(n, &(hosts->fields), link) { + if (udp) + add_listener("udp", n->field, port); + if (tcp) + add_listener("tcp", n->field, port); + if (rdma) + add_listener("rdma", n->field, rdma_port); + } + } else { + if (udp) + add_listener("udp", "", port); + if (tcp) + add_listener("tcp", "", port); + if (rdma) + add_listener("rdma", "", rdma_port); + } +} + +static int autostart_func(struct nl_sock *sock, int argc, char ** argv) +{ + int threads, grace, lease, idx, ret; + char *scope; + + read_nfsd_conf(); + + scope = conf_get_str("nfsd", "scope"); + if (scope) { + if (unshare(CLONE_NEWUTS) < 0 || + sethostname(scope, strlen(scope)) < 0) { + fprintf(stderr, "Unable to set server scope: %m"); + return 1; + } + } + + ret = fetch_nfsd_versions(sock); + if (ret) + return ret; + configure_versions(); + ret = set_nfsd_versions(sock); + if (ret) + return ret; + + configure_listeners(); + ret = set_listeners(sock); + if (ret) + return ret; + + /* FIXME: implement these */ + grace = conf_get_num("nfsd", "grace-time", grace); + if (grace) + fprintf(stderr, "grace-time setting is not yet supported!\n"); + lease = conf_get_num("nfsd", "lease-time", lease); + if (lease) + fprintf(stderr, "lease-time setting is not yet supported!\n"); + + threads = conf_get_num("nfsd", "threads", 128); + return threads_doit(sock, NFSD_CMD_THREADS_SET, threads); +} + +enum nfsdctl_commands { + NFSDCTL_STATUS, + NFSDCTL_THREADS, + NFSDCTL_VERSION, + NFSDCTL_LISTENER, + NFSDCTL_AUTOSTART, +}; + +static int parse_command(char *str) +{ + if (!strcmp(str, "status")) + return NFSDCTL_STATUS; + if (!strcmp(str, "threads")) + return NFSDCTL_THREADS; + if (!strcmp(str, "version")) + return NFSDCTL_VERSION; + if (!strcmp(str, "listener")) + return NFSDCTL_LISTENER; + if (!strcmp(str, "autostart")) + return NFSDCTL_AUTOSTART; + return -1; +} + +typedef int (*nfsdctl_func)(struct nl_sock *sock, int argc, char **argv); + +static nfsdctl_func func[] = { + [NFSDCTL_STATUS] = status_func, + [NFSDCTL_THREADS] = threads_func, + [NFSDCTL_VERSION] = version_func, + [NFSDCTL_LISTENER] = listener_func, + [NFSDCTL_AUTOSTART] = autostart_func, +}; + +static void usage(void) +{ + printf("Usage:\n"); + printf("%s [-hv] [COMMAND] [ARGS]\n", taskname); + printf(" options:\n"); + printf(" -h | --help usage info\n"); + printf(" -d | --debug=NUM enable debugging\n"); + printf(" -V | --version print version info\n"); + printf(" commands:\n"); + printf(" listener get/set listener info\n"); + printf(" version get/set supported NFS versions\n"); + printf(" threads get/set nfsd thread settings\n"); + printf(" status get current RPC processing info\n"); + printf(" autostart start server with settings from /etc/nfs.conf\n"); +} + +/* Options given before the command string */ +static const struct option pre_options[] = { + { "help", no_argument, NULL, 'h' }, + { "debug", required_argument, NULL, 'd' }, + { "version", no_argument, NULL, 'V' }, + { }, +}; + +static int run_one_command(struct nl_sock *sock, int argc, char **argv) +{ + int cmd = parse_command(argv[0]); + + if (cmd < 0) { + usage(); + return 1; + } + return func[cmd](sock, argc, argv); +} + +#define MAX_ARGUMENTS 256 + +static int tokenize_string(char *line, int *argc, char **argv) +{ + int idx = 0; + char *arg, *save; + + memset(argv, '\0', sizeof(*argv) * MAX_ARGUMENTS); + + arg = strtok_r(line, " \t", &save); + while(arg) { + argv[idx] = arg; + ++idx; + if (idx >= MAX_ARGUMENTS) + return -E2BIG; + arg = strtok_r(NULL, " \t", &save); + } + *argc = idx; + return 0; +} + +static int run_commandline(struct nl_sock *sock) +{ + char *argv[MAX_ARGUMENTS]; + char *line; + int ret, argc; + + for (;;) { + line = readline("nfsdctl> "); + if (!line || !strcmp(line, "quit")) + break; + if (*line == '\0') + continue; + add_history(line); + ret = tokenize_string(line, &argc, argv); + if (!ret) + ret = run_one_command(sock, argc, argv); + if (ret) + fprintf(stderr, "Error: %s\n", strerror(ret)); + free(line); + } + return 0; +} + +int main(int argc, char **argv) +{ + int opt, ret; + struct nl_sock *sock = netlink_sock_alloc(); + + if (!sock) { + fprintf(stderr, "Unable to allocate netlink socket!"); + return 1; + } + + taskname = argv[0]; + + /* Parse the preliminary options */ + while ((opt = getopt_long(argc, argv, "+hd:V", pre_options, NULL)) != -1) { + switch (opt) { + case 'h': + usage(); + return 0; + case 'd': + debug_level = atoi(optarg); + break; + case 'V': + // FIXME: print_version(); + return 0; + } + } + + if (optind > argc) { + usage(); + return 1; + } + + if (optind == argc) + ret = run_commandline(sock); + else + ret = run_one_command(sock, argc - optind, &argv[optind]); + nl_socket_free(sock); return ret; } diff --git a/utils/nfsdctl/nfsdctl.h b/utils/nfsdctl/nfsdctl.h index 6084435c05d9..02c6122f807a 100644 --- a/utils/nfsdctl/nfsdctl.h +++ b/utils/nfsdctl/nfsdctl.h @@ -39,6 +39,7 @@ enum { 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) -- 2.44.0