The kernel sets NLM_F_DUMP_INTR on a dump message when the dumped objects changed between two parts of the dump, so the result can miss an object or list it twice. mnl_cb_run2() answers the flag with EINTR on the first such message. mnlu_gen_socket_sndrcv() then prints "kernel answers: Interrupted system call" and returns with the rest of the dump still queued in the socket. The caller has passed a part of the objects to its callback by then and cannot tell an interrupted dump from a request that failed. Add mnlu_gen_socket_sndrcv_dump(). It clears the flag in every received message before the callbacks run, reads the dump to its end and tells the caller through a bool that the dump was interrupted. The caller can then repeat the request on the same socket or warn the user, as rtnl_dump_filter() does for rtnetlink dumps. The receive loop is shared with mnlu_socket_recv_run() and mnlu_gen_socket_sndrcv(), which behave as before. Reviewed-by: Ivan Vecera Signed-off-by: Petr Oros --- include/mnl_utils.h | 5 ++++ lib/mnl_utils.c | 58 +++++++++++++++++++++++++++++++++++++++------ 2 files changed, 56 insertions(+), 7 deletions(-) diff --git a/include/mnl_utils.h b/include/mnl_utils.h index 4df9e398e59165..82c26f05742ce3 100644 --- a/include/mnl_utils.h +++ b/include/mnl_utils.h @@ -2,6 +2,8 @@ #ifndef __MNL_UTILS_H__ #define __MNL_UTILS_H__ 1 +#include + struct mnlu_gen_socket { struct mnl_socket *nl; char *buf; @@ -22,6 +24,9 @@ struct nlmsghdr *mnlu_gen_socket_cmd_prepare(struct mnlu_gen_socket *nlg, uint8_t cmd, uint16_t flags); int mnlu_gen_socket_sndrcv(struct mnlu_gen_socket *nlg, const struct nlmsghdr *nlh, mnl_cb_t data_cb, void *data); +int mnlu_gen_socket_sndrcv_dump(struct mnlu_gen_socket *nlg, + const struct nlmsghdr *nlh, + mnl_cb_t data_cb, void *data, bool *intr); struct mnl_socket *mnlu_socket_open(int bus); int mnl_add_nl_group(struct mnl_socket *nl, unsigned int group); diff --git a/lib/mnl_utils.c b/lib/mnl_utils.c index 43b4cd1a9e40d4..4b971797777cb3 100644 --- a/lib/mnl_utils.c +++ b/lib/mnl_utils.c @@ -102,8 +102,29 @@ static mnl_cb_t mnlu_cb_array[NLMSG_MIN_TYPE] = { [NLMSG_OVERRUN] = mnlu_cb_noop, }; -int mnlu_socket_recv_run(struct mnl_socket *nl, unsigned int seq, void *buf, size_t buf_size, - mnl_cb_t cb, void *data) +/* mnl_cb_run2() fails with EINTR on the first message with NLM_F_DUMP_INTR + * and leaves the rest of the dump in the socket. Clear the flag, so that + * the dump is read to its end, and tell the caller that it was there. + */ +static bool mnlu_dump_intr_clear(void *buf, size_t len) +{ + struct nlmsghdr *nlh = buf; + bool intr = false; + int rem = len; + + for (; mnl_nlmsg_ok(nlh, rem); nlh = mnl_nlmsg_next(nlh, &rem)) { + if (nlh->nlmsg_flags & NLM_F_DUMP_INTR) { + nlh->nlmsg_flags &= ~NLM_F_DUMP_INTR; + intr = true; + } + } + + return intr; +} + +static int __mnlu_socket_recv_run(struct mnl_socket *nl, unsigned int seq, + void *buf, size_t buf_size, + mnl_cb_t cb, void *data, bool *intr) { unsigned int portid = mnl_socket_get_portid(nl); int err; @@ -112,6 +133,8 @@ int mnlu_socket_recv_run(struct mnl_socket *nl, unsigned int seq, void *buf, siz err = mnl_socket_recvfrom(nl, buf, buf_size); if (err <= 0) break; + if (intr) + *intr |= mnlu_dump_intr_clear(buf, err); err = mnl_cb_run2(buf, err, seq, portid, cb, data, mnlu_cb_array, ARRAY_SIZE(mnlu_cb_array)); @@ -120,6 +143,12 @@ int mnlu_socket_recv_run(struct mnl_socket *nl, unsigned int seq, void *buf, siz return err; } +int mnlu_socket_recv_run(struct mnl_socket *nl, unsigned int seq, void *buf, size_t buf_size, + mnl_cb_t cb, void *data) +{ + return __mnlu_socket_recv_run(nl, seq, buf, buf_size, cb, data, NULL); +} + static int ctrl_attrs_cb(const struct nlattr *attr, void *data) { int type = mnl_attr_get_type(attr); @@ -242,20 +271,29 @@ struct nlmsghdr *mnlu_gen_socket_cmd_prepare(struct mnlu_gen_socket *nlg, nlg->version); } -int mnlu_gen_socket_sndrcv(struct mnlu_gen_socket *nlg, const struct nlmsghdr *nlh, - mnl_cb_t data_cb, void *data) +/* Like mnlu_gen_socket_sndrcv(), for a dump request. A dump the kernel + * marked as interrupted is read to its end and reported in @intr. The + * objects passed to @data_cb can then be incomplete or duplicated and it + * is up to the caller to repeat the dump or to warn the user. + */ +int mnlu_gen_socket_sndrcv_dump(struct mnlu_gen_socket *nlg, + const struct nlmsghdr *nlh, + mnl_cb_t data_cb, void *data, bool *intr) { int err; + if (intr) + *intr = false; + err = mnl_socket_sendto(nlg->nl, nlh, nlh->nlmsg_len); if (err < 0) { perror("Failed to send data"); return -errno; } - err = mnlu_socket_recv_run(nlg->nl, nlh->nlmsg_seq, nlg->buf, - MNL_SOCKET_BUFFER_SIZE, - data_cb, data); + err = __mnlu_socket_recv_run(nlg->nl, nlh->nlmsg_seq, nlg->buf, + MNL_SOCKET_BUFFER_SIZE, + data_cb, data, intr); if (err < 0) { fprintf(stderr, "kernel answers: %s\n", strerror(errno)); return -errno; @@ -263,6 +301,12 @@ int mnlu_gen_socket_sndrcv(struct mnlu_gen_socket *nlg, const struct nlmsghdr *n return 0; } +int mnlu_gen_socket_sndrcv(struct mnlu_gen_socket *nlg, const struct nlmsghdr *nlh, + mnl_cb_t data_cb, void *data) +{ + return mnlu_gen_socket_sndrcv_dump(nlg, nlh, data_cb, data, NULL); +} + int mnlu_gen_socket_recv_run(struct mnlu_gen_socket *nlg, mnl_cb_t cb, void *data) { -- 2.56.0