// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
#include <errno.h>
#include <poll.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <linux/types.h>
#include <linux/genetlink.h>
#include <sys/socket.h>
#include "ynl.h"
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof(*arr))
#define __yerr_msg(yse, _msg...) \
({ \
struct ynl_error *_yse = (yse); \
\
if (_yse) { \
snprintf(_yse->msg, sizeof(_yse->msg) - 1, _msg); \
_yse->msg[sizeof(_yse->msg) - 1] = 0; \
} \
})
#define __yerr_code(yse, _code...) \
({ \
struct ynl_error *_yse = (yse); \
\
if (_yse) { \
_yse->code = _code; \
} \
})
#define __yerr(yse, _code, _msg...) \
({ \
__yerr_msg(yse, _msg); \
__yerr_code(yse, _code); \
})
#define __perr(yse, _msg) __yerr(yse, errno, _msg)
#define yerr_msg(_ys, _msg...) __yerr_msg(&(_ys)->err, _msg)
#define yerr(_ys, _code, _msg...) __yerr(&(_ys)->err, _code, _msg)
#define perr(_ys, _msg) __yerr(&(_ys)->err, errno, _msg)
/* -- Netlink boiler plate */
static int
ynl_err_walk_report_one(const struct ynl_policy_nest *policy, unsigned int type,
char *str, int str_sz, int *n)
{
if (!policy) {
if (*n < str_sz)
*n += snprintf(str, str_sz, "!policy");
return 1;
}
if (type > policy->max_attr) {
if (*n < str_sz)
*n += snprintf(str, str_sz, "!oob");
return 1;
}
if (!policy->table[type].name) {
if (*n < str_sz)
*n += snprintf(str, str_sz, "!name");
return 1;
}
if (*n < str_sz)
*n += snprintf(str, str_sz - *n,
".%s", policy->table[type].name);
return 0;
}
static int
ynl_err_walk(struct ynl_sock *ys, void *start, void *end, unsigned int off,
const struct ynl_policy_nest *policy, char *str, int str_sz,
const struct ynl_policy_nest **nest_pol)
{
unsigned int astart_off, aend_off;
const struct nlattr *attr;
unsigned int data_len;
unsigned int type;
bool found = false;
int n = 0;
if (!policy) {
if (n < str_sz)
n += snprintf(str, str_sz, "!policy");
return n;
}
data_len = end - start;
ynl_attr_for_each_payload(start, data_len, attr) {
astart_off = (char *)attr - (char *)start;
aend_off = astart_off + ynl_attr_data_len(attr);
if (aend_off <= off)
continue;
found = true;
break;
}
if (!found)
return 0;
off -= astart_off;
type = ynl_attr_type(attr);
if (ynl_err_walk_report_one(policy, type, str, str_sz, &n))
return n;
if (!off) {
if (nest_pol)
*nest_pol = policy->table[type].nest;
return n;
}
if (!policy->table[type].nest) {
if (n < str_sz)
n += snprintf(str, str_sz, "!nest");
return n;
}
off -= sizeof(struct nlattr);
start = ynl_attr_data(attr);
end = start + ynl_attr_data_len(attr);
return n + ynl_err_walk(ys, start, end, off, policy->table[type].nest,
&str[n], str_sz - n, nest_pol);
}
#define NLMSGERR_ATTR_MISS_TYPE (NLMSGERR_ATTR_POLICY + 1)
#define NLMSGERR_ATTR_MISS_NEST (NLMSGERR_ATTR_POLICY + 2)
#define NLMSGERR_ATTR_MAX (NLMSGERR_ATTR_MAX + 2)
static int
ynl_ext_ack_check(struct ynl_sock *ys, const struct nlmsghdr *nlh,
unsigned int hlen)
{
const struct nlattr *tb[NLMSGERR_ATTR_MAX + 1] = {};
char miss_attr[sizeof(ys->err.msg)];
char bad_attr[sizeof(ys->err.msg)];
const struct nlattr *attr;
const char *str = NULL;
if (!(nlh->nlmsg_flags & NLM_F_ACK_TLVS)) {
yerr_msg(ys, "%s", strerror(ys->err.code));
return YNL_PARSE_CB_OK;
}
ynl_attr_for_each(attr, nlh, hlen) {
unsigned int len, type;
len = ynl_attr_data_len(attr);
type = ynl_attr_type(attr);
if (type > NLMSGERR_ATTR_MAX)
continue;
tb[type] = attr;
switch (type) {
case NLMSGERR_ATTR_OFFS:
case NLMSGERR_ATTR_MISS_TYPE:
case NLMSGERR_ATTR_MISS_NEST:
if (len != sizeof(__u32))
return YNL_PARSE_CB_ERROR;
break;
case NLMSGERR_ATTR_MSG:
str = ynl_attr_get_str(attr);
if (str[len - 1])
return YNL_PARSE_CB_ERROR;
break;
default:
break;
}
}
bad_attr[0] = '\0';
miss_attr[0] = '\0';
if (tb[NLMSGERR_ATTR_OFFS]) {
unsigned int n, off;
void *start, *end;
ys->err.attr_offs = ynl_attr_get_u32(tb[NLMSGERR_ATTR_OFFS]);
n = snprintf(bad_attr, sizeof(bad_attr), "%sbad attribute: ",
str ? " (" : "");
start = ynl_nlmsg_data_offset(ys->nlh, ys->family->hdr_len);
end = ynl_nlmsg_end_addr(ys->nlh);
off = ys->err.attr_offs;
off -= sizeof(struct nlmsghdr);
off -= ys->family->hdr_len;
n += ynl_err_walk(ys, start, end, off, ys->