[RFC PATCH ethtool v2 08/23] netlink: add helpers for command line parsing

From: Michal Kubecek
Date: Mon Jul 30 2018 - 08:56:30 EST


Support for easier parsing of subcommand parameters and their values from
command line. Existing parser helpers in ethtool.c are closely tied to
ioctl() interface and using them would be often inconvenient.

Signed-off-by: Michal Kubecek <mkubecek@xxxxxxx>
---
Makefile.am | 1 +
netlink/netlink.h | 4 +
netlink/parser.c | 527 ++++++++++++++++++++++++++++++++++++++++++++++
netlink/parser.h | 45 ++++
4 files changed, 577 insertions(+)
create mode 100644 netlink/parser.c
create mode 100644 netlink/parser.h

diff --git a/Makefile.am b/Makefile.am
index 629f04e1202b..7cc4adc53c59 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -23,6 +23,7 @@ if ETHTOOL_ENABLE_NETLINK
ethtool_SOURCES += \
netlink/netlink.c netlink/netlink.h netlink/extapi.h \
netlink/strset.c netlink/strset.h netlink/monitor.c \
+ netlink/parser.c netlink/parser.h \
netlink/drvinfo.c netlink/settings.c \
uapi/linux/ethtool_netlink.h \
uapi/linux/netlink.h uapi/linux/genetlink.h
diff --git a/netlink/netlink.h b/netlink/netlink.h
index a572a165718e..32c2f9f33ba1 100644
--- a/netlink/netlink.h
+++ b/netlink/netlink.h
@@ -30,6 +30,10 @@ struct nl_context {
void *msg;
const char *devname;
bool is_dump;
+ const char *cmd;
+ const char *param;
+ char **argp;
+ int argc;
int exit_code;
bool is_monitor;
uint8_t filter_cmd;
diff --git a/netlink/parser.c b/netlink/parser.c
new file mode 100644
index 000000000000..589004d1f9f9
--- /dev/null
+++ b/netlink/parser.c
@@ -0,0 +1,527 @@
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <ctype.h>
+
+#include "../internal.h"
+#include "../common.h"
+#include "netlink.h"
+#include "parser.h"
+
+static void parser_err_unknown_param(struct nl_context *nlctx)
+{
+ fprintf(stderr, "ethtool (%s): unknown parameter '%s'\n", nlctx->cmd,
+ nlctx->param);
+}
+
+static void parser_err_dup_param(struct nl_context *nlctx)
+{
+ fprintf(stderr, "ethtool (%s): duplicate parameter '%s'\n", nlctx->cmd,
+ nlctx->param);
+}
+
+static void parser_err_min_argc(struct nl_context *nlctx, unsigned int min_argc)
+{
+ if (min_argc == 1)
+ fprintf(stderr, "ethtool (%s): no value for parameter '%s'\n",
+ nlctx->cmd, nlctx->param);
+ else
+ fprintf(stderr,
+ "ethtool (%s): parameter '%s' requires %u words\n",
+ nlctx->cmd, nlctx->param, min_argc);
+}
+
+static void parser_err_invalid_value(struct nl_context *nlctx, const char *val)
+{
+ fprintf(stderr, "ethtool (%s): invalid value '%s' for parameter '%s'\n",
+ nlctx->cmd, val, nlctx->param);
+}
+
+static void parser_err_invalid_flag(struct nl_context *nlctx, const char *flag)
+{
+ fprintf(stderr, "ethtool (%s): flag '%s' for parameter '%s' is not "
+ "followed by 'on' or 'off'\n",
+ nlctx->cmd, flag, nlctx->param);
+}
+
+static int __parse_u32(const char *arg, u32 *result, u32 min, u32 max, int base)
+{
+ unsigned long long val;
+ char *endptr;
+
+ if (!arg || !arg[0])
+ return -EINVAL;
+ val = strtoul(arg, &endptr, base);
+ if (*endptr || val < min || val > max)
+ return -EINVAL;
+
+ *result = (u32)val;
+ return 0;
+}
+
+static int parse_u32d(const char *arg, u32 *result)
+{
+ return __parse_u32(arg, result, 0, 0xffffffff, 10);
+}
+
+static int parse_x32(const char *arg, u32 *result)
+{
+ return __parse_u32(arg, result, 0, 0xffffffff, 16);
+}
+
+static int parse_u32(const char *arg, u32 *result)
+{
+ if (!arg)
+ return -EINVAL;
+ if ((arg[0] == '0') && (arg[1] == 'x' || arg[1] == 'X'))
+ return parse_x32(arg + 2, result);
+ else
+ return parse_u32d(arg, result);
+}
+
+static int parse_u8(const char *arg, u8 *result)
+{
+ u32 val;
+ int ret = parse_u32(arg, &val);
+
+ if (ret < 0)
+ return ret;
+ if (val > UINT8_MAX)
+ return -EINVAL;
+
+ *result = (u8)val;
+ return 0;
+}
+
+static int lookup_u8(const char *arg, u8 *result,
+ const struct lookup_entry_u8 *tbl)
+{
+ if (!arg)
+ return -EINVAL;
+ while (tbl->arg) {
+ if (!strcmp(tbl->arg, arg)) {
+ *result = tbl->val;
+ return 0;
+ }
+ tbl++;
+ }
+
+ return -EINVAL;
+}
+
+int nl_parse_direct_u32(struct nl_context *nlctx, uint16_t type,
+ const void *data)
+{
+ const char *arg = *nlctx->argp;
+ u32 val;
+ int ret;
+
+ nlctx->argp++;
+ nlctx->argc--;
+ ret = parse_u32(arg, &val);
+ if (ret < 0) {
+ parser_err_invalid_value(nlctx, arg);
+ return ret;
+ }
+
+ return ethnla_put_u32(nlctx, type, val) ? -EMSGSIZE : 0;
+}
+
+int nl_parse_direct_u8(struct nl_context *nlctx, uint16_t type,
+ const void *data)
+{
+ const char *arg = *nlctx->argp;
+ u8 val;
+ int ret;
+
+ nlctx->argp++;
+ nlctx->argc--;
+ ret = parse_u8(arg, &val);
+ if (ret < 0) {
+ parser_err_invalid_value(nlctx, arg);
+ return ret;
+ }
+
+ return ethnla_put_u8(nlctx, type, val) ? -EMSGSIZE : 0;
+}
+
+int nl_parse_bool(struct nl_context *nlctx, uint16_t type, const void *data)
+{
+ const char *arg = *nlctx->argp;
+ int ret;
+
+ nlctx->argp++;
+ nlctx->argc--;
+ if (!strcmp(arg, "on"))
+ ret = ethnla_put_u8(nlctx, type, 1);
+ else if (!strcmp(arg, "off"))
+ ret = ethnla_put_u8(nlctx, type, 0);
+ else {
+ parser_err_invalid_value(nlctx, arg);
+ return -EINVAL;
+ }
+
+ return ret ? -EMSGSIZE : 0;
+}
+
+int nl_parse_lookup_u8(struct nl_context *nlctx, uint16_t type,
+ const void *data)
+{
+ const char *arg = *nlctx->argp;
+ u8 val;
+ int ret;
+
+ nlctx->argp++;
+ nlctx->argc--;
+ ret = lookup_u8(arg, &val, data);
+ if (ret < 0) {
+ parser_err_invalid_value(nlctx, arg);
+ return ret;
+ }
+
+ return ethnla_put_u8(nlctx, type, val) ? -EMSGSIZE : 0;
+}
+
+int nl_parse_bitfield32(struct nl_context *nlctx, uint16_t type,
+ const void *data)
+{
+ const char *arg = *nlctx->argp;
+ const struct flag_info *flags = data;
+ u32 value, selector;
+ int ret;
+
+ ret = parse_u32(arg, &value);
+ if (isdigit(arg[0])) {
+ char *mask = strchr(arg, '/');
+
+ if (mask) {
+ /* numeric value / mask */
+ *mask = '\0';
+ mask++;
+ ret = parse_u32(mask, &selector);
+ if (ret < 0) {
+ parser_err_invalid_value(nlctx, mask);
+ return ret;
+ }
+ } else {
+ /* numeric value */
+ selector = ~(u32)0;
+ }
+ ret = parse_u32(arg, &value);
+ if (ret < 0) {
+ parser_err_invalid_value(nlctx, arg);
+ return ret;
+ }
+ nlctx->argp++;
+ nlctx->argc--;
+ } else {
+ /* flag on/off... [--] */
+ value = 0;
+ selector = 0;
+ while (nlctx->argc > 0) {
+ const struct flag_info *flag = flags;
+
+ if (!strcmp(*nlctx->argp, "--")) {
+ nlctx->argp++;
+ nlctx->argc--;
+ break;
+ }
+ if (nlctx->argc < 2 ||
+ (strcmp(nlctx->argp[1], "on") &&
+ strcmp(nlctx->argp[1], "off"))) {
+ parser_err_invalid_flag(nlctx, *nlctx->argp);
+ return -EINVAL;
+ }
+ while (flag->name && strcmp(*nlctx->argp, flag->name))
+ flag++;
+ if (!flag->name) {
+ parser_err_invalid_value(nlctx, *nlctx->argp);
+ return -EINVAL;
+ }
+ selector |= flag->value;
+ if (!strcmp(nlctx->argp[1], "on"))
+ value |= flag->value;
+
+ nlctx->argp += 2;
+ nlctx->argc -= 2;
+ }
+ }
+
+ return ethnla_put_bitfield32(nlctx, type, value, selector);
+}
+
+static bool is_hex(char c)
+{
+ if (isdigit(c))
+ return true;
+ else
+ return (c >= 'a' && c <= 'f');
+}
+
+/* Return true if a bitset argument should be parsed as numeric, i.e.
+ * (a) it starts with '0x'
+ * (b) it consists only of hex digits and at most one slash which can be
+ * optionally followed by "0x"
+ */
+static bool is_numeric_bitset(const char *arg)
+{
+ const char *p = arg;
+ bool has_slash = false;
+
+ if (arg[0] == '0' && arg[1] == 'x')
+ return true;
+ while (*p) {
+ if (*p == '/') {
+ if (has_slash)
+ return false;
+ has_slash = true;
+ p++;
+ if (p[0] == '0' && p[1] == 'x')
+ p += 2;
+ continue;
+ }
+ if (!is_hex(*p))
+ return false;
+ p++;
+ }
+ return true;
+}
+
+/* number of significant bits */
+static unsigned int nsb(u32 x)
+{
+ unsigned int ret = 0;
+
+ if (!(x & 0xffff0000U)) {
+ x >>= 16;
+ ret += 16;
+ }
+ if (!(x & 0xff00U)) {
+ x >>= 8;
+ ret += 8;
+ }
+ if (!(x & 0xf0U)) {
+ x >>= 4;
+ ret += 4;
+ }
+ if (!(x & 0xcU)) {
+ x >>= 2;
+ ret += 2;
+ }
+ if (!(x & 0x2U)) {
+ x >>= 1;
+ ret += 1;
+ }
+
+ return ret + x;
+}
+
+/* Parse hex string (without leading "0x") into a bitmap consisting of 32-bit
+ * words. Caller must make sure arg is at least len characters long and dst has
+ * place for at least (len + 7) / 8 32-bit words.
+ * Returns number of significant bits in the bitmap on success and negative
+ * value on error.
+ */
+static int parse_hex_string(const char *arg, unsigned int len, u32 *dst)
+{
+ const char *p = arg;
+ unsigned int nwords = (len + 7) / 8;
+ unsigned int nbits = 0;
+ char buff[9];
+
+ memset(dst, '\0', nwords * sizeof(u32));
+ while (len > 0) {
+ unsigned int chunk = (len % 8) ?: 8;
+ unsigned long val;
+ char *endp;
+
+ memcpy(buff, p, chunk);
+ buff[chunk] = '\0';
+ val = strtoul(buff, &endp, 16);
+ if (*endp)
+ return -EINVAL;
+ *dst++ = (u32)val;
+ if (nbits)
+ nbits += 8 * chunk;
+ else
+ nbits = nsb(val);
+
+ p += chunk;
+ len -= chunk;
+ }
+ return nbits;
+}
+
+static int nl_parse_bitset_compact(struct nl_context *nlctx, uint16_t type)
+{
+ const char *arg = *nlctx->argp;
+ unsigned int nwords, len1, len2;
+ bool has_mask = false;
+ struct nlattr *nest;
+ const char *maskptr;
+ u32 *value = NULL;
+ u32 *mask = NULL;
+ int nbits;
+ int ret;
+
+ if (arg[0] == '0' && arg[1] == 'x')
+ arg += 2;
+
+ maskptr = strchr(arg, '/');
+ len1 = maskptr ? ( maskptr - arg) : strlen(arg);
+ nwords = (len1 + 7) / 8;
+ nbits = 0;
+
+ value = malloc(nwords * sizeof(u32));
+ if (!value)
+ return -ENOMEM;
+ ret = -ENOMEM;
+ mask = malloc(nwords * sizeof(u32));
+ if (!mask)
+ goto free_value;
+
+ if (maskptr) {
+ has_mask = true;
+ maskptr++;
+ if (maskptr[0] == '0' && maskptr[1] == 'x')
+ maskptr += 2;
+ len2 = strlen(maskptr);
+ if (len2 > len1)
+ nwords = (len2 + 7) / 8;
+ mask = malloc(nwords * sizeof(u32));
+ if (!mask)
+ return -ENOMEM;
+ ret = parse_hex_string(maskptr, strlen(maskptr),
+ mask);
+ if (ret < 0)
+ goto free_mask;
+ nbits = ret;
+ }
+
+ ret = parse_hex_string(arg, len1, value);
+ if (ret < 0)
+ goto free_value;
+ nbits = nbits ?: ret;
+ nwords = (nbits + 31) / 32;
+
+ ret = -EMSGSIZE;
+ if (!(nest = ethnla_nest_start(nlctx, type)) ||
+ ethnla_put_u32(nlctx, ETHA_BITSET_SIZE, nbits) ||
+ ethnla_put(nlctx, ETHA_BITSET_VALUE, nwords * sizeof(u32), value) ||
+ (has_mask &&
+ ethnla_put(nlctx, ETHA_BITSET_MASK, nwords * sizeof(u32), mask)))
+ goto free_mask;
+ mnl_attr_nest_end(nlctx->nlhdr, nest);
+ ret = 0;
+
+free_mask:
+ free(mask);
+free_value:
+ free(value);
+ nlctx->argp++;
+ nlctx->argc--;
+ return ret;
+}
+
+static int nl_parse_bitset_list(struct nl_context *nlctx, uint16_t type,
+ const void *data)
+{
+ struct nlmsghdr *nlhdr = nlctx->nlhdr;
+ struct nlattr *bitset_attr;
+ struct nlattr *bits_attr;
+ struct nlattr *bit_attr;
+ int ret;
+
+ ret = -EMSGSIZE;
+ bitset_attr = ethnla_nest_start(nlctx, type);
+ if (!bitset_attr)
+ return ret;
+ bits_attr = ethnla_nest_start(nlctx, ETHA_BITSET_BITS);
+ if (!bits_attr)
+ goto err;
+
+ while (nlctx->argc > 0) {
+ if (!strcmp(*nlctx->argp, "--")) {
+ nlctx->argp++;
+ nlctx->argc--;
+ break;
+ }
+ ret = -EINVAL;
+ if (nlctx->argc < 2 ||
+ (strcmp(nlctx->argp[1], "on") &&
+ strcmp(nlctx->argp[1], "off"))) {
+ parser_err_invalid_flag(nlctx, *nlctx->argp);
+ goto err;
+ }
+
+ ret = -EMSGSIZE;
+ bit_attr = ethnla_nest_start(nlctx, ETHA_BITS_BIT);
+ if (!bit_attr)
+ goto err;
+ if (ethnla_put_strz(nlctx, ETHA_BIT_NAME, nlctx->argp[0]))
+ goto err;
+ if (ethnla_put_flag(nlctx, ETHA_BIT_VALUE,
+ !strcmp(nlctx->argp[1], "on")))
+ goto err;
+ mnl_attr_nest_end(nlhdr, bit_attr);
+
+ nlctx->argp += 2;
+ nlctx->argc -= 2;
+ }
+
+ mnl_attr_nest_end(nlhdr, bits_attr);
+ mnl_attr_nest_end(nlhdr, bitset_attr);
+ return 0;
+err:
+ mnl_attr_nest_cancel(nlhdr, bitset_attr);
+ return ret;
+}
+
+int nl_parse_bitset(struct nl_context *nlctx, uint16_t type, const void *data)
+{
+ if (is_numeric_bitset(*nlctx->argp))
+ return nl_parse_bitset_compact(nlctx, type);
+ else
+ return nl_parse_bitset_list(nlctx, type, data);
+}
+
+int nl_parser(struct cmd_context *ctx, const struct param_parser *params,
+ unsigned int max_type)
+{
+ struct nl_context *nlctx = ctx->nlctx;
+ bool attr_set[max_type];
+ int ret;
+
+ memset(attr_set, '\0', max_type * sizeof(attr_set[0]));
+
+ while (nlctx->argc > 0) {
+ const struct param_parser *parser = params;
+
+ nlctx->param = *nlctx->argp;
+ while (parser->arg) {
+ if (!strcmp(nlctx->param, parser->arg))
+ break;
+ parser++;
+ }
+ if (!parser->arg) {
+ parser_err_unknown_param(nlctx);
+ return -EINVAL;
+ }
+ nlctx->argp++;
+ nlctx->argc--;
+ if (attr_set[parser - params]) {
+ parser_err_dup_param(nlctx);
+ return -EINVAL;
+ }
+ if (nlctx->argc < parser->min_argc) {
+ parser_err_min_argc(nlctx, parser->min_argc);
+ return -EINVAL;
+ }
+
+ ret = parser->handler(nlctx, parser->type, parser->handler_data);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
diff --git a/netlink/parser.h b/netlink/parser.h
new file mode 100644
index 000000000000..7308da25f093
--- /dev/null
+++ b/netlink/parser.h
@@ -0,0 +1,45 @@
+#ifndef ETHTOOL_NETLINK_PARSER_H__
+#define ETHTOOL_NETLINK_PARSER_H__
+
+#include "netlink.h"
+
+struct lookup_entry_u32 {
+ const char *arg;
+ u32 val;
+};
+
+struct lookup_entry_u16 {
+ const char *arg;
+ u16 val;
+};
+
+struct lookup_entry_u8{
+ const char *arg;
+ u8 val;
+};
+
+typedef int (*param_parser_cb_t)(struct nl_context *, uint16_t, const void *);
+
+struct param_parser {
+ const char *arg;
+ uint16_t type;
+ param_parser_cb_t handler;
+ const void *handler_data;
+ unsigned int min_argc;
+};
+
+int nl_parse_direct_u32(struct nl_context *nlctx, uint16_t type,
+ const void *data);
+int nl_parse_direct_u8(struct nl_context *nlctx, uint16_t type,
+ const void *data);
+int nl_parse_bool(struct nl_context *nlctx, uint16_t type, const void *data);
+int nl_parse_lookup_u8(struct nl_context *nlctx, uint16_t type,
+ const void *data);
+int nl_parse_bitfield32(struct nl_context *nlctx, uint16_t type,
+ const void *data);
+int nl_parse_bitset(struct nl_context *nlctx, uint16_t type, const void *data);
+
+int nl_parser(struct cmd_context *ctx, const struct param_parser *params,
+ unsigned int max_type);
+
+#endif /* ETHTOOL_NETLINK_PARSER_H__ */
--
2.18.0