blob: cd32752a9ddb429c7ffa5fd51f4f124c98347904 [file] [log] [blame]
/*
* parser.c - netlink command line parser
*
* Implementation of command line parser used by netlink code.
*/
#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 bool __prefix_0x(const char *p)
{
return p[0] == '0' && (p[1] == 'x' || p[1] == 'X');
}
static float parse_float(const char *arg, float *result, float min,
float max)
{
char *endptr;
float val;
if (!arg || !arg[0])
return -EINVAL;
val = strtof(arg, &endptr);
if (*endptr || val < min || val > max)
return -EINVAL;
*result = val;
return 0;
}
static int __parse_u32(const char *arg, uint32_t *result, uint32_t min,
uint32_t 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 = (uint32_t)val;
return 0;
}
static int parse_u32d(const char *arg, uint32_t *result)
{
return __parse_u32(arg, result, 0, 0xffffffff, 10);
}
static int parse_x32(const char *arg, uint32_t *result)
{
return __parse_u32(arg, result, 0, 0xffffffff, 16);
}
int parse_u32(const char *arg, uint32_t *result)
{
if (!arg)
return -EINVAL;
if (__prefix_0x(arg))
return parse_x32(arg + 2, result);
else
return parse_u32d(arg, result);
}
static int parse_u8(const char *arg, uint8_t *result)
{
uint32_t val;
int ret = parse_u32(arg, &val);
if (ret < 0)
return ret;
if (val > UINT8_MAX)
return -EINVAL;
*result = (uint8_t)val;
return 0;
}
static int lookup_u32(const char *arg, uint32_t *result,
const struct lookup_entry_u32 *tbl)
{
if (!arg)
return -EINVAL;
while (tbl->arg) {
if (!strcmp(tbl->arg, arg)) {
*result = tbl->val;
return 0;
}
tbl++;
}
return -EINVAL;
}
static int lookup_u8(const char *arg, uint8_t *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;
}
/* Parser handler for a flag. Expects a name (with no additional argument),
* generates NLA_FLAG or sets a bool (if the name was present).
*/
int nl_parse_flag(struct nl_context *nlctx __maybe_unused, uint16_t type,
const void *data __maybe_unused, struct nl_msg_buff *msgbuff,
void *dest)
{
if (dest)
*(bool *)dest = true;
return (type && ethnla_put_flag(msgbuff, type, true)) ? -EMSGSIZE : 0;
}
/* Parser handler for null terminated string. Expects a string argument,
* generates NLA_NUL_STRING or fills const char *
*/
int nl_parse_string(struct nl_context *nlctx, uint16_t type,
const void *data __maybe_unused,
struct nl_msg_buff *msgbuff, void *dest)
{
const char *arg = *nlctx->argp;
nlctx->argp++;
nlctx->argc--;
if (dest)
*(const char **)dest = arg;
return (type && ethnla_put_strz(msgbuff, type, arg)) ? -EMSGSIZE : 0;
}
/* Parser handler for unsigned 32-bit integer. Expects a numeric argument
* (may use 0x prefix), generates NLA_U32 or fills an uint32_t.
*/
int nl_parse_direct_u32(struct nl_context *nlctx, uint16_t type,
const void *data __maybe_unused,
struct nl_msg_buff *msgbuff, void *dest)
{
const char *arg = *nlctx->argp;
uint32_t val;
int ret;
nlctx->argp++;
nlctx->argc--;
ret = parse_u32(arg, &val);
if (ret < 0) {
parser_err_invalid_value(nlctx, arg);
return ret;
}
if (dest)
*(uint32_t *)dest = val;
return (type && ethnla_put_u32(msgbuff, type, val)) ? -EMSGSIZE : 0;
}
/* Parser handler for unsigned 32-bit integer. Expects a numeric argument
* (may use 0x prefix), generates NLA_U32 or fills an uint32_t.
*/
int nl_parse_direct_u8(struct nl_context *nlctx, uint16_t type,
const void *data __maybe_unused,
struct nl_msg_buff *msgbuff, void *dest)
{
const char *arg = *nlctx->argp;
uint8_t val;
int ret;
nlctx->argp++;
nlctx->argc--;
ret = parse_u8(arg, &val);
if (ret < 0) {
parser_err_invalid_value(nlctx, arg);
return ret;
}
if (dest)
*(uint8_t *)dest = val;
return (type && ethnla_put_u8(msgbuff, type, val)) ? -EMSGSIZE : 0;
}
/* Parser handler for float meters and convert it to cm. Generates
* NLA_U32 or fills an uint32_t.
*/
int nl_parse_direct_m2cm(struct nl_context *nlctx, uint16_t type,
const void *data __maybe_unused,
struct nl_msg_buff *msgbuff, void *dest)
{
const char *arg = *nlctx->argp;
float meters = 0.0;
uint32_t cm;
int ret;
nlctx->argp++;
nlctx->argc--;
ret = parse_float(arg, &meters, 0, 150);
if (ret < 0) {
parser_err_invalid_value(nlctx, arg);
return ret;
}
cm = (uint32_t)(meters * 100 + 0.5);
if (dest)
*(uint32_t *)dest = cm;
return (type && ethnla_put_u32(msgbuff, type, cm)) ? -EMSGSIZE : 0;
}
/* Parser handler for (tri-state) bool. Expects "name on|off", generates
* NLA_U8 which is 1 for "on" and 0 for "off".
*/
int nl_parse_u8bool(struct nl_context *nlctx, uint16_t type,
const void *data __maybe_unused,
struct nl_msg_buff *msgbuff, void *dest)
{
const char *arg = *nlctx->argp;
int ret;
nlctx->argp++;
nlctx->argc--;
if (!strcmp(arg, "on")) {
if (dest)
*(uint8_t *)dest = 1;
ret = type ? ethnla_put_u8(msgbuff, type, 1) : 0;
} else if (!strcmp(arg, "off")) {
if (dest)
*(uint8_t *)dest = 0;
ret = type ? ethnla_put_u8(msgbuff, type, 0) : 0;
} else {
parser_err_invalid_value(nlctx, arg);
return -EINVAL;
}
return ret ? -EMSGSIZE : 0;
}
/* Parser handler for 32-bit lookup value. Expects a string argument, looks it
* up in a table, generates NLA_U32 or fills uint32_t variable. The @data
* parameter is a null terminated array of struct lookup_entry_u32.
*/
int nl_parse_lookup_u32(struct nl_context *nlctx, uint16_t type,
const void *data, struct nl_msg_buff *msgbuff,
void *dest)
{
const char *arg = *nlctx->argp;
uint32_t val;
int ret;
nlctx->argp++;
nlctx->argc--;
ret = lookup_u32(arg, &val, data);
if (ret < 0) {
parser_err_invalid_value(nlctx, arg);
return ret;
}
if (dest)
*(uint32_t *)dest = val;
return (type && ethnla_put_u32(msgbuff, type, val)) ? -EMSGSIZE : 0;
}
/* Parser handler for 8-bit lookup value. Expects a string argument, looks it
* up in a table, generates NLA_U8 or fills uint8_t variable. The @data
* parameter is a null terminated array of struct lookup_entry_u8.
*/
int nl_parse_lookup_u8(struct nl_context *nlctx, uint16_t type,
const void *data, struct nl_msg_buff *msgbuff,
void *dest)
{
const char *arg = *nlctx->argp;
uint8_t val;
int ret;
nlctx->argp++;
nlctx->argc--;
ret = lookup_u8(arg, &val, data);
if (ret < 0) {
parser_err_invalid_value(nlctx, arg);
return ret;
}
if (dest)
*(uint8_t *)dest = val;
return (type && ethnla_put_u8(msgbuff, type, val)) ? -EMSGSIZE : 0;
}
/* number of significant bits */
static unsigned int __nsb(uint32_t 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;
}
static bool __is_hex(char c)
{
if (isdigit(c))
return true;
else
return (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
}
static unsigned int __hex_val(char c)
{
if (c >= '0' && c <= '9')
return c - '0';
if (c >= 'a' && c <= 'f')
return c - 'a' + 0xa;
if (c >= 'A' && c <= 'F')
return c - 'A' + 0xa;
return 0;
}
static bool __bytestr_delim(const char *p, char delim)
{
return !*p || (delim ? (*p == delim) : !__is_hex(*p));
}
/* Parser handler for generic byte string in MAC-like format. Expects string
* argument in the "[[:xdigit:]]{2}(:[[:xdigit:]]{2})*" format, generates
* NLA_BINARY or fills a struct byte_str_value (if @dest is not null and the
* handler succeeds, caller is responsible for freeing the value). The @data
* parameter points to struct byte_str_parser_data.
*/
int nl_parse_byte_str(struct nl_context *nlctx, uint16_t type, const void *data,
struct nl_msg_buff *msgbuff, void *dest)
{
const struct byte_str_parser_data *pdata = data;
struct byte_str_value *dest_value = dest;
const char *arg = *nlctx->argp;
uint8_t *val = NULL;
unsigned int len, i;
const char *p;
int ret;
nlctx->argp++;
nlctx->argc--;
len = 0;
p = arg;
if (!*p)
goto err;
while (true) {
len++;
if (!__bytestr_delim(p, pdata->delim))
p++;
if (!__bytestr_delim(p, pdata->delim))
p++;
if (!__bytestr_delim(p, pdata->delim))
goto err;
if (!*p)
break;
p++;
if (*p && __bytestr_delim(p, pdata->delim))
goto err;
}
if (len < pdata->min_len || (pdata->max_len && len > pdata->max_len))
goto err;
val = malloc(len);
if (!val)
return -ENOMEM;
p = arg;
for (i = 0; i < len; i++) {
uint8_t byte = 0;
if (!__is_hex(*p))
goto err;
while (__is_hex(*p))
byte = 16 * byte + __hex_val(*p++);
if (!__bytestr_delim(p, pdata->delim))
goto err;
val[i] = byte;
if (*p)
p++;
}
ret = type ? ethnla_put(msgbuff, type, len, val) : 0;
if (dest) {
dest_value->len = len;
dest_value->data = val;
} else {
free(val);
}
return ret;
err:
free(val);
fprintf(stderr, "ethtool (%s): invalid value '%s' of parameter '%s'\n",
nlctx->cmd, arg, nlctx->param);
return -EINVAL;
}
/* Parser handler for parameters recognized for backward compatibility but
* supposed to fail without passing to kernel. Does not generate any netlink
* attributes of fill any variable. The @data parameter points to struct
* error_parser_params (error message, return value and number of extra
* arguments to skip).
*/
int nl_parse_error(struct nl_context *nlctx, uint16_t type __maybe_unused,
const void *data, struct nl_msg_buff *msgbuff __maybe_unused,
void *dest __maybe_unused)
{
const struct error_parser_data *parser_data = data;
unsigned int skip = parser_data->extra_args;
fprintf(stderr, "ethtool (%s): ", nlctx->cmd);
fprintf(stderr, parser_data->err_msg, nlctx->param);
if (nlctx->argc < skip) {
fprintf(stderr, "ethtool (%s): too few arguments for parameter '%s' (expected %u)\n",
nlctx->cmd, nlctx->param, skip);
} else {
nlctx->argp += skip;
nlctx->argc -= skip;
}
return parser_data->ret_val;
}
/* bitset parser handlers */
/* 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"; if no_mask is true, slash is not allowed
*/
static bool is_numeric_bitset(const char *arg, bool no_mask)
{
const char *p = arg;
bool has_slash = false;
if (!arg)
return false;
if (__prefix_0x(arg))
return true;
while (*p) {
if (*p == '/') {
if (has_slash || no_mask)
return false;
has_slash = true;
p++;
if (__prefix_0x(p))
p += 2;
continue;
}
if (!__is_hex(*p))
return false;
p++;
}
return true;
}
#define __MAX_U32_DIGITS 10
/* 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. If force_hex is false, allow
* also base 10 unsigned 32-bit value.
*
* Returns number of significant bits in the bitmap on success and negative
* value on error.
*/
static int __parse_num_string(const char *arg, unsigned int len, uint32_t *dst,
bool force_hex)
{
char buff[__MAX_U32_DIGITS + 1] = {};
unsigned int nbits = 0;
const char *p = arg;
if (!len)
return -EINVAL;
if (!force_hex && len <= __MAX_U32_DIGITS) {
strncpy(buff, arg, len);
if (!buff[__MAX_U32_DIGITS]) {
u32 val;
int ret;
ret = parse_u32d(buff, &val);
if (!ret) {
*dst = val;
return __nsb(val);
}
}
}
dst += (len - 1) / 8;
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-- = (uint32_t)val;
if (nbits)
nbits += 4 * chunk;
else
nbits = __nsb(val);
p += chunk;
len -= chunk;
}
return nbits;
}
/* Parse bitset provided as a base 16 numeric value (@no_mask is true) or pair
* of base 16 numeric values separated by '/' (@no_mask is false). The "0x"
* prefix is optional. Generates bitset nested attribute in compact form.
*/
static int parse_numeric_bitset(struct nl_context *nlctx, uint16_t type,
bool no_mask, bool force_hex,
struct nl_msg_buff *msgbuff)
{
unsigned int nwords, len1, len2;
const char *arg = *nlctx->argp;
bool force_hex1 = force_hex;
bool force_hex2 = force_hex;
uint32_t *value = NULL;
uint32_t *mask = NULL;
struct nlattr *nest;
const char *maskptr;
int ret = 0;
int nbits;
if (__prefix_0x(arg)) {
force_hex1 = true;
arg += 2;
}
maskptr = strchr(arg, '/');
if (maskptr && no_mask) {
parser_err_invalid_value(nlctx, arg);
return -EINVAL;
}
len1 = maskptr ? (unsigned int)(maskptr - arg) : strlen(arg);
nwords = DIV_ROUND_UP(len1, 8);
nbits = 0;
if (maskptr) {
maskptr++;
if (__prefix_0x(maskptr)) {
maskptr += 2;
force_hex2 = true;
}
len2 = strlen(maskptr);
if (len2 > len1)
nwords = DIV_ROUND_UP(len2, 8);
mask = calloc(nwords, sizeof(uint32_t));
if (!mask)
return -ENOMEM;
ret = __parse_num_string(maskptr, strlen(maskptr), mask,
force_hex2);
if (ret < 0) {
parser_err_invalid_value(nlctx, arg);
goto out_free;
}
nbits = ret;
}
value = calloc(nwords, sizeof(uint32_t));
if (!value) {
free(mask);
return -ENOMEM;
}
ret = __parse_num_string(arg, len1, value, force_hex1);
if (ret < 0) {
parser_err_invalid_value(nlctx, arg);
goto out_free;
}
nbits = (nbits < ret) ? ret : nbits;
nwords = (nbits + 31) / 32;
ret = 0;
if (!type)
goto out_free;
ret = -EMSGSIZE;
nest = ethnla_nest_start(msgbuff, type);
if (!nest)
goto out_free;
if (ethnla_put_flag(msgbuff, ETHTOOL_A_BITSET_NOMASK, !mask) ||
ethnla_put_u32(msgbuff, ETHTOOL_A_BITSET_SIZE, nbits) ||
ethnla_put(msgbuff, ETHTOOL_A_BITSET_VALUE,
nwords * sizeof(uint32_t), value) ||
(mask &&
ethnla_put(msgbuff, ETHTOOL_A_BITSET_MASK,
nwords * sizeof(uint32_t), mask)))
goto out_free;
ethnla_nest_end(msgbuff, nest);
ret = 0;
out_free:
free(value);
free(mask);
nlctx->argp++;
nlctx->argc--;
return ret;
}
/* Parse bitset provided as series of "name on|off" pairs (@no_mask is false)
* or names (@no_mask is true). Generates bitset nested attribute in verbose
* form with names from command line.
*/
static int parse_name_bitset(struct nl_context *nlctx, uint16_t type,
bool no_mask, struct nl_msg_buff *msgbuff)
{
struct nlattr *bitset_attr;
struct nlattr *bits_attr;
struct nlattr *bit_attr;
int ret;
bitset_attr = ethnla_nest_start(msgbuff, type);
if (!bitset_attr)
return -EMSGSIZE;
ret = -EMSGSIZE;
if (no_mask && ethnla_put_flag(msgbuff, ETHTOOL_A_BITSET_NOMASK, true))
goto err;
bits_attr = ethnla_nest_start(msgbuff, ETHTOOL_A_BITSET_BITS);
if (!bits_attr)
goto err;
while (nlctx->argc > 0) {
bool bit_val = true;
if (!strcmp(*nlctx->argp, "--")) {
nlctx->argp++;
nlctx->argc--;
break;
}
ret = -EINVAL;
if (!no_mask) {
if (nlctx->argc < 2 ||
(strcmp(nlctx->argp[1], "on") &&
strcmp(nlctx->argp[1], "off"))) {
parser_err_invalid_flag(nlctx, *nlctx->argp);
goto err;
}
bit_val = !strcmp(nlctx->argp[1], "on");
}
ret = -EMSGSIZE;
bit_attr = ethnla_nest_start(msgbuff,
ETHTOOL_A_BITSET_BITS_BIT);
if (!bit_attr)
goto err;
if (ethnla_put_strz(msgbuff, ETHTOOL_A_BITSET_BIT_NAME,
nlctx->argp[0]))
goto err;
if (!no_mask &&
ethnla_put_flag(msgbuff, ETHTOOL_A_BITSET_BIT_VALUE,
bit_val))
goto err;
ethnla_nest_end(msgbuff, bit_attr);
nlctx->argp += (no_mask ? 1 : 2);
nlctx->argc -= (no_mask ? 1 : 2);
}
ethnla_nest_end(msgbuff, bits_attr);
ethnla_nest_end(msgbuff, bitset_attr);
return 0;
err:
ethnla_nest_cancel(msgbuff, bitset_attr);
return ret;
}
static bool is_char_bitset(const char *arg,
const struct char_bitset_parser_data *data)
{
bool mask = (arg[0] == '+' || arg[0] == '-');
unsigned int i;
const char *p;
for (p = arg; *p; p++) {
if (*p == data->reset_char)
continue;
if (mask && (*p == '+' || *p == '-'))
continue;
for (i = 0; i < data->nbits; i++)
if (*p == data->bit_chars[i])
goto found;
return false;
found:
;
}
return true;
}
/* Parse bitset provided as a string consisting of characters corresponding to
* bit indices. The "reset character" resets the no-mask bitset to empty. If
* the first character is '+' or '-', generated bitset has mask and '+' and
* '-' switch between enabling and disabling the following bits (i.e. their
* value being true/false). In such case, "reset character" is not allowed.
*/
static int parse_char_bitset(struct nl_context *nlctx, uint16_t type,
const struct char_bitset_parser_data *data,
struct nl_msg_buff *msgbuff)
{
const char *arg = *nlctx->argp;
struct nlattr *bitset_attr;
struct nlattr *saved_pos;
struct nlattr *bits_attr;
struct nlattr *bit_attr;
unsigned int idx;
bool val = true;
const char *p;
bool no_mask;
int ret;
no_mask = data->no_mask || !(arg[0] == '+' || arg[0] == '-');
bitset_attr = ethnla_nest_start(msgbuff, type);
if (!bitset_attr)
return -EMSGSIZE;
ret = -EMSGSIZE;
if (no_mask && ethnla_put_flag(msgbuff, ETHTOOL_A_BITSET_NOMASK, true))
goto err;
bits_attr = ethnla_nest_start(msgbuff, ETHTOOL_A_BITSET_BITS);
if (!bits_attr)
goto err;
saved_pos = mnl_nlmsg_get_payload_tail(msgbuff->nlhdr);
for (p = arg; *p; p++) {
if (*p == '+' || *p == '-') {
if (no_mask) {
parser_err_invalid_value(nlctx, arg);
ret = -EINVAL;
goto err;
}
val = (*p == '+');
continue;
}
if (*p == data->reset_char) {
if (no_mask) {
mnl_attr_nest_cancel(msgbuff->nlhdr, saved_pos);
continue;
}
fprintf(stderr, "ethtool (%s): invalid char '%c' in '%s' for parameter '%s'\n",
nlctx->cmd, *p, arg, nlctx->param);
ret = -EINVAL;
goto err;
}
for (idx = 0; idx < data->nbits; idx++) {
if (data->bit_chars[idx] == *p)
break;
}
if (idx >= data->nbits) {
fprintf(stderr, "ethtool (%s): invalid char '%c' in '%s' for parameter '%s'\n",
nlctx->cmd, *p, arg, nlctx->param);
ret = -EINVAL;
goto err;
}
bit_attr = ethnla_nest_start(msgbuff,
ETHTOOL_A_BITSET_BITS_BIT);
if (!bit_attr)
goto err;
if (ethnla_put_u32(msgbuff, ETHTOOL_A_BITSET_BIT_INDEX, idx))
goto err;
if (!no_mask &&
ethnla_put_flag(msgbuff, ETHTOOL_A_BITSET_BIT_VALUE, val))
goto err;
ethnla_nest_end(msgbuff, bit_attr);
}
ethnla_nest_end(msgbuff, bits_attr);
ethnla_nest_end(msgbuff, bitset_attr);
nlctx->argp++;
nlctx->argc--;
return 0;
err:
ethnla_nest_cancel(msgbuff, bitset_attr);
return ret;
}
/* Parser handler for bitset. Expects either a numeric value (base 16 or 10
* (unless force_hex is set)), optionally followed by '/' and another numeric
* value (mask, unless no_mask is set), or a series of "name on|off" pairs
* (no_mask not set) or names (no_mask set). In the latter case, names are
* passed on as they are and kernel performs their interpretation and
* validation. The @data parameter points to struct bitset_parser_data.
* Generates only a bitset nested attribute. Fails if @type is zero or @dest
* is not null.
*/
int nl_parse_bitset(struct nl_context *nlctx, uint16_t type, const void *data,
struct nl_msg_buff *msgbuff, void *dest)
{
const struct bitset_parser_data *parser_data = data;
if (!type || dest) {
fprintf(stderr, "ethtool (%s): internal error parsing '%s'\n",
nlctx->cmd, nlctx->param);
return -EFAULT;
}
if (is_numeric_bitset(*nlctx->argp, false))
return parse_numeric_bitset(nlctx, type, parser_data->no_mask,
parser_data->force_hex, msgbuff);
else
return parse_name_bitset(nlctx, type, parser_data->no_mask,
msgbuff);
}
/* Parser handler for bitset. Expects either a numeric value (base 10 or 16),
* optionally followed by '/' and another numeric value (mask, unless no_mask
* is set), or a string consisting of characters corresponding to bit indices.
* The @data parameter points to struct char_bitset_parser_data. Generates
* biset nested attribute. Fails if type is zero or if @dest is not null.
*/
int nl_parse_char_bitset(struct nl_context *nlctx, uint16_t type,
const void *data, struct nl_msg_buff *msgbuff,
void *dest)
{
const struct char_bitset_parser_data *parser_data = data;
if (!type || dest) {
fprintf(stderr, "ethtool (%s): internal error parsing '%s'\n",
nlctx->cmd, nlctx->param);
return -EFAULT;
}
if (is_char_bitset(*nlctx->argp, data) ||
!is_numeric_bitset(*nlctx->argp, false))
return parse_char_bitset(nlctx, type, parser_data, msgbuff);
else
return parse_numeric_bitset(nlctx, type, parser_data->no_mask,
false, msgbuff);
}
/* parser implementation */
static const struct param_parser *find_parser(const struct param_parser *params,
const char *arg)
{
const struct param_parser *parser;
for (parser = params; parser->arg; parser++)
if (!strcmp(arg, parser->arg))
return parser;
return NULL;
}
static bool __parser_bit(const uint64_t *map, unsigned int idx)
{
return map[idx / 64] & (1 << (idx % 64));
}
static void __parser_set(uint64_t *map, unsigned int idx)
{
map[idx / 64] |= (1 << (idx % 64));
}
static void __parser_set_group(const struct param_parser *params,
uint64_t *map, unsigned int alt_group)
{
const struct param_parser *parser;
unsigned int idx = 0;
for (parser = params; parser->arg; parser++, idx++)
if (parser->alt_group == alt_group)
__parser_set(map, idx);
}
struct tmp_buff {
struct nl_msg_buff *msgbuff;
unsigned int id;
unsigned int orig_len;
struct tmp_buff *next;
};
static struct tmp_buff *tmp_buff_find(struct tmp_buff *head, unsigned int id)
{
struct tmp_buff *buff;
for (buff = head; buff; buff = buff->next)
if (buff->id == id)
break;
return buff;
}
static struct tmp_buff *tmp_buff_find_or_create(struct tmp_buff **phead,
unsigned int id)
{
struct tmp_buff **pbuff;
struct tmp_buff *new_buff;
for (pbuff = phead; *pbuff; pbuff = &(*pbuff)->next)
if ((*pbuff)->id == id)
return *pbuff;
new_buff = malloc(sizeof(*new_buff));
if (!new_buff)
return NULL;
new_buff->id = id;
new_buff->msgbuff = malloc(sizeof(*new_buff->msgbuff));
if (!new_buff->msgbuff) {
free(new_buff);
return NULL;
}
msgbuff_init(new_buff->msgbuff);
new_buff->next = NULL;
*pbuff = new_buff;
return new_buff;
}
static void tmp_buff_destroy(struct tmp_buff *head)
{
struct tmp_buff *buff = head;
struct tmp_buff *next;
while (buff) {
next = buff->next;
if (buff->msgbuff) {
msgbuff_done(buff->msgbuff);
free(buff->msgbuff);
}
free(buff);
buff = next;
}
}
/* Main entry point of parser implementation.
* @nlctx: netlink context
* @params: array of struct param_parser describing expected arguments
* and their handlers; the array must be terminated by null
* element {}
* @dest: optional destination to copy parsed data to (at
* param_parser::offset); buffer should start with presence bitmap
* @group_style: defines if identifiers in .group represent separate messages,
* nested attributes or are not allowed
* @msgbuffs: (only used for @group_style = PARSER_GROUP_MSG) array to store
* pointers to composed messages; caller must make sure this
* array is sufficient, i.e. that it has at least as many entries
* as the number of different .group values in params array;
* entries are filled from the start, remaining entries are not
* modified; caller should zero initialize the array before
* calling nl_parser()
*/
int nl_parser(struct nl_context *nlctx, const struct param_parser *params,
void *dest, enum parser_group_style group_style,
struct nl_msg_buff **msgbuffs)
{
struct nl_socket *nlsk = nlctx->ethnl_socket;
const struct param_parser *parser;
struct tmp_buff *buffs = NULL;
unsigned int n_msgbuffs = 0;
struct tmp_buff *buff;
unsigned int n_params;
uint64_t *params_seen;
int ret;
n_params = 0;
for (parser = params; parser->arg; parser++) {
struct nl_msg_buff *msgbuff;
struct nlattr *nest;
n_params++;
if (group_style == PARSER_GROUP_NONE || !parser->group)
continue;
ret = -ENOMEM;
buff = tmp_buff_find_or_create(&buffs, parser->group);
if (!buff)
goto out_free_buffs;
msgbuff = buff->msgbuff;
ret = msg_init(nlctx, msgbuff, parser->group,
NLM_F_REQUEST | NLM_F_ACK);
if (ret < 0)
goto out_free_buffs;
switch (group_style) {
case PARSER_GROUP_NEST:
ret = -EMSGSIZE;
nest = ethnla_nest_start(buff->msgbuff, parser->group);
if (!nest)
goto out_free_buffs;
break;
case PARSER_GROUP_MSG:
if (ethnla_fill_header(msgbuff,
ETHTOOL_A_LINKINFO_HEADER,
nlctx->devname, 0))
goto out_free_buffs;
break;
default:
break;
}
buff->orig_len = msgbuff_len(msgbuff);
}
ret = -ENOMEM;
params_seen = calloc(DIV_ROUND_UP(n_params, 64), sizeof(uint64_t));
if (!params_seen)
goto out_free_buffs;
while (nlctx->argc > 0) {
struct nl_msg_buff *msgbuff;
void *param_dest;
nlctx->param = *nlctx->argp;
ret = -EINVAL;
parser = find_parser(params, nlctx->param);
if (!parser) {
parser_err_unknown_param(nlctx);
goto out_free;
}
/* check duplicates and minimum number of arguments */
if (__parser_bit(params_seen, parser - params)) {
parser_err_dup_param(nlctx);
goto out_free;
}
nlctx->argc--;
nlctx->argp++;
if (nlctx->argc < parser->min_argc) {
parser_err_min_argc(nlctx, parser->min_argc);
goto out_free;
}
if (parser->alt_group)
__parser_set_group(params, params_seen,
parser->alt_group);
else
__parser_set(params_seen, parser - params);
buff = NULL;
if (parser->group)
buff = tmp_buff_find(buffs, parser->group);
msgbuff = buff ? buff->msgbuff : &nlsk->msgbuff;
if (dest) {
unsigned long index = parser - params;
param_dest = ((char *)dest + parser->dest_offset);
set_bit(index, (unsigned long *)dest);
} else {
param_dest = NULL;
}
ret = parser->handler(nlctx, parser->type, parser->handler_data,
msgbuff, param_dest);
if (ret < 0)
goto out_free;
}
if (group_style == PARSER_GROUP_MSG) {
ret = -EOPNOTSUPP;
for (buff = buffs; buff; buff = buff->next)
if (msgbuff_len(buff->msgbuff) > buff->orig_len &&
netlink_cmd_check(nlctx->ctx, buff->id, false))
goto out_free;
}
for (buff = buffs; buff; buff = buff->next) {
struct nl_msg_buff *msgbuff = buff->msgbuff;
if (group_style == PARSER_GROUP_NONE ||
msgbuff_len(msgbuff) == buff->orig_len)
continue;
switch (group_style) {
case PARSER_GROUP_NEST:
ethnla_nest_end(msgbuff, msgbuff->payload);
ret = msgbuff_append(&nlsk->msgbuff, msgbuff);
if (ret < 0)
goto out_free;
break;
case PARSER_GROUP_MSG:
msgbuffs[n_msgbuffs++] = msgbuff;
buff->msgbuff = NULL;
break;
default:
break;
}
}
ret = 0;
out_free:
free(params_seen);
out_free_buffs:
tmp_buff_destroy(buffs);
return ret;
}