From b3213c622823b4591fc92fc9f76ed8bfe09f9f5f Mon Sep 17 00:00:00 2001 From: Richard Alpe Date: Mon, 19 Jun 2023 14:24:14 +0200 Subject: [PATCH] statd: break out operational status into new daemon This patch does two main things: 1) Breaks "statd" free from "confd". 2) Introduces netlink monitoring of interfaces for sysrepo data. Prior to this patch, sysrepo operation status was handled by code named "statd" inside "confd". (Where confd is a sysrepo-plugin and not a real daemon, which its name implies) In this patch, we break out the interface part of the operational sysrepo status, previously "statd", into a new daemon, also called "statd". The main reason for doing this is to avoid sysrepo threading, which allows the new "statd" to have control over sysrepo events in the same event pool as netlink events from the kernel. This in turn allows statd to subscribe and unsubscribe dynamically to interfaces that comes and goes during runtime (such as vlans). It also makes sense that "statd" is in fact a finit daemon. Signed-off-by: Richard Alpe --- configs/aarch64_defconfig | 1 + configs/x86_64_defconfig | 1 + configs/x86_64_minimal_defconfig | 1 + package/Config.in | 1 + package/statd/Config.in | 7 + package/statd/statd.conf | 1 + package/statd/statd.mk | 28 ++ src/confd/configure.ac | 1 - src/confd/src/Makefile.am | 2 +- src/confd/src/lib/helpers.c | 5 + src/confd/src/lib/helpers.h | 4 + src/confd/src/statd/Makefile.am | 13 - src/statd/Makefile | 27 ++ src/{confd/src => }/statd/core.c | 0 src/statd/statd.c | 491 +++++++++++++++++++++++++++++++ 15 files changed, 568 insertions(+), 15 deletions(-) create mode 100644 package/statd/Config.in create mode 100644 package/statd/statd.conf create mode 100644 package/statd/statd.mk delete mode 100644 src/confd/src/statd/Makefile.am create mode 100644 src/statd/Makefile rename src/{confd/src => }/statd/core.c (100%) create mode 100644 src/statd/statd.c diff --git a/configs/aarch64_defconfig b/configs/aarch64_defconfig index 50fd0ece..b7deddfc 100644 --- a/configs/aarch64_defconfig +++ b/configs/aarch64_defconfig @@ -105,6 +105,7 @@ BR2_PACKAGE_HOST_UBOOT_TOOLS=y BR2_PACKAGE_HOST_UBOOT_TOOLS_FIT_SUPPORT=y BR2_PACKAGE_HOST_UBOOT_TOOLS_FIT_SIGNATURE_SUPPORT=y BR2_PACKAGE_CONFD=y +BR2_PACKAGE_STATD=y BR2_PACKAGE_FACTORY=y BR2_PACKAGE_FINIT_SULOGIN=y BR2_PACKAGE_FINIT_PLUGIN_HOTPLUG=y diff --git a/configs/x86_64_defconfig b/configs/x86_64_defconfig index 6082a347..492ae02c 100644 --- a/configs/x86_64_defconfig +++ b/configs/x86_64_defconfig @@ -110,6 +110,7 @@ BR2_PACKAGE_HOST_UBOOT_TOOLS=y BR2_PACKAGE_HOST_UBOOT_TOOLS_FIT_SUPPORT=y BR2_PACKAGE_HOST_UBOOT_TOOLS_FIT_SIGNATURE_SUPPORT=y BR2_PACKAGE_CONFD=y +BR2_PACKAGE_STATD=y BR2_PACKAGE_FACTORY=y BR2_PACKAGE_FINIT_SULOGIN=y BR2_PACKAGE_FINIT_PLUGIN_HOTPLUG=y diff --git a/configs/x86_64_minimal_defconfig b/configs/x86_64_minimal_defconfig index daa3a478..f4d208e8 100644 --- a/configs/x86_64_minimal_defconfig +++ b/configs/x86_64_minimal_defconfig @@ -59,6 +59,7 @@ BR2_PACKAGE_SYSKLOGD_LOGGER=y BR2_TARGET_ROOTFS_SQUASHFS=y # BR2_TARGET_ROOTFS_TAR is not set BR2_PACKAGE_CONFD=y +BR2_PACKAGE_STATD=y BR2_PACKAGE_FACTORY=y BR2_PACKAGE_FINIT_SULOGIN=y BR2_PACKAGE_FINIT_PLUGIN_HOTPLUG=y diff --git a/package/Config.in b/package/Config.in index 252eeeeb..d950f4a8 100644 --- a/package/Config.in +++ b/package/Config.in @@ -1,4 +1,5 @@ source "$BR2_EXTERNAL_INFIX_PATH/package/confd/Config.in" +source "$BR2_EXTERNAL_INFIX_PATH/package/statd/Config.in" source "$BR2_EXTERNAL_INFIX_PATH/package/conmon/Config.in" source "$BR2_EXTERNAL_INFIX_PATH/package/factory/Config.in" source "$BR2_EXTERNAL_INFIX_PATH/package/faux/Config.in" diff --git a/package/statd/Config.in b/package/statd/Config.in new file mode 100644 index 00000000..8504bde5 --- /dev/null +++ b/package/statd/Config.in @@ -0,0 +1,7 @@ +config BR2_PACKAGE_STATD + bool "statd" + help + Operational Status Daemon. Responsible for handling sysrepo + operational run-time info. Such as interface state and address. + All data supplied should be fetched from the running system, + which might differ from data configured through confd. diff --git a/package/statd/statd.conf b/package/statd/statd.conf new file mode 100644 index 00000000..41436a49 --- /dev/null +++ b/package/statd/statd.conf @@ -0,0 +1 @@ +service name:statd log [S12345789] statd -f -p /run/statd.pid -n -- Status daemon diff --git a/package/statd/statd.mk b/package/statd/statd.mk new file mode 100644 index 00000000..c95f4faa --- /dev/null +++ b/package/statd/statd.mk @@ -0,0 +1,28 @@ +################################################################################ +# +# statd +# +################################################################################ +STATD_VERSION = 1.0 +STATD_LICENSE = MIT +STATD_SITE_METHOD = local +STATD_SITE = $(BR2_EXTERNAL_INFIX_PATH)/src/statd +STATD_DEPENDENCIES = sysrepo libev jansson + +define STATD_BUILD_CMDS + $(TARGET_MAKE_ENV) $(TARGET_CONFIGURE_OPTS) $(MAKE) -C $(@D) \ + LDFLAGS="$(TARGET_LDFLAGS)" +endef + +define STATD_INSTALL_TARGET_CMDS + $(TARGET_MAKE_ENV) $(TARGET_CONFIGURE_OPTS) $(MAKE) -C $(@D) \ + DESTDIR="$(TARGET_DIR)" install +endef + +define STATD_INSTALL_EXTRA + cp $(STATD_PKGDIR)/statd.conf $(FINIT_D)/available/ + ln -sf ../available/statd.conf $(FINIT_D)/enabled/statd.conf +endef +STATD_TARGET_FINALIZE_HOOKS += STATD_INSTALL_EXTRA + +$(eval $(generic-package)) diff --git a/src/confd/configure.ac b/src/confd/configure.ac index 0ac88b4f..1e347393 100644 --- a/src/confd/configure.ac +++ b/src/confd/configure.ac @@ -10,7 +10,6 @@ AC_CONFIG_FILES([ Makefile src/Makefile src/confd/Makefile - src/statd/Makefile yang/Makefile ]) diff --git a/src/confd/src/Makefile.am b/src/confd/src/Makefile.am index 4de2649c..fa478382 100644 --- a/src/confd/src/Makefile.am +++ b/src/confd/src/Makefile.am @@ -1,3 +1,3 @@ AUTOMAKE_OPTIONS = subdir-objects -SUBDIRS = confd statd +SUBDIRS = confd diff --git a/src/confd/src/lib/helpers.c b/src/confd/src/lib/helpers.c index 5cf7ca9c..eb67eccc 100644 --- a/src/confd/src/lib/helpers.c +++ b/src/confd/src/lib/helpers.c @@ -13,6 +13,11 @@ #include #include +/* TODO remove once confd / statd lib situation is resolved */ +#ifndef vasprintf +int vasprintf(char **strp, const char *fmt, va_list ap); +#endif + /* * Run cmd in background after delay microseconds. */ diff --git a/src/confd/src/lib/helpers.h b/src/confd/src/lib/helpers.h index f923e0bb..56cb712b 100644 --- a/src/confd/src/lib/helpers.h +++ b/src/confd/src/lib/helpers.h @@ -3,6 +3,10 @@ #ifndef CONFD_HELPERS_H_ #define CONFD_HELPERS_H_ +#include + +int vasprintf(char **strp, const char *fmt, va_list ap); + int runbg(char *const args[], int delay); int run_status(int pid); diff --git a/src/confd/src/statd/Makefile.am b/src/confd/src/statd/Makefile.am deleted file mode 100644 index bc392039..00000000 --- a/src/confd/src/statd/Makefile.am +++ /dev/null @@ -1,13 +0,0 @@ -CFLAGS += -Wall -Wextra -Werror -Wno-unused-parameter -AM_CPPFLAGS = -D_DEFAULT_SOURCE -D_XOPEN_SOURCE -D_GNU_SOURCE -DYANG_PATH_=\"$(YANGDIR)\" - -plugindir = $(srpdplugindir) -plugin_LTLIBRARIES = statd-plugin.la - -statd_plugin_la_CFLAGS = $(augeas_CFLAGS) $(jansson_CFLAGS) $(libite_CFLAGS) $(sysrepo_CFLAGS) $(CFLAGS) -statd_plugin_la_LIBADD = $(augeas_LIBS) $(jansson_LIBS) $(libite_LIBS) $(sysrepo_LIBS) -statd_plugin_la_LDFLAGS = -module -avoid-version -shared -statd_plugin_la_SOURCES = core.c ../lib/common.h \ - ../lib/helpers.h ../lib/helpers.c \ - ../lib/lyx.h ../lib/lyx.c \ - ../lib/srx_module.h ../lib/srx_module.c diff --git a/src/statd/Makefile b/src/statd/Makefile new file mode 100644 index 00000000..56a93499 --- /dev/null +++ b/src/statd/Makefile @@ -0,0 +1,27 @@ +# TODO: Libs are included fron confd. They should be broken out of confd and +# shared between statd and confd in some nice way. +CONFD_LIB = $(BASE_DIR)/../src/confd/lib/ +CONFD_SRC_LIB = $(BASE_DIR)/../src/confd/src/lib + +CFLAGS += -Wall -Wextra -Werror +LDLIBS += -lsysrepo -lev -lyang -ljansson -lite +CPPFLAGS := -I$(CONFD_SRC_LIB) + +TARGET = statd +SRC = statd.c +LIB_SRC = $(CONFD_SRC_LIB)/helpers.c $(CONFD_SRC_LIB)/lyx.c $(CONFD_LIB)/vasprintf.c +LIB_HDR = $(CONFD_SRC_LIB)/helpers.h $(CONFD_SRC_LIB)/lyx.h + +all: $(TARGET) + +$(TARGET): $(SRC) $(LIB_SRC) $(LIB_HDR) + $(CC) $(CPPFLAGS) $(LDLIBS) $(CFLAGS) -o $(TARGET) $(SRC) $(LIB_SRC) + +clean: + -rm statd + +distclean: clean + -rm *~ + +install: + install -D statd $(DESTDIR)/sbin/ diff --git a/src/confd/src/statd/core.c b/src/statd/core.c similarity index 100% rename from src/confd/src/statd/core.c rename to src/statd/core.c diff --git a/src/statd/statd.c b/src/statd/statd.c new file mode 100644 index 00000000..37dbd376 --- /dev/null +++ b/src/statd/statd.c @@ -0,0 +1,491 @@ +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "common.h" +#include "helpers.h" +#include "lyx.h" + +#define XPATH_MAX PATH_MAX +#define XPATH_IFACE_BASE "/ietf-interfaces:interfaces" + +#define SOCK_RMEM_SIZE 1000000 /* Arbitrary chosen, default = 212992 */ +#define NL_BUF_SIZE 4096 /* Arbitrary chosen */ + +TAILQ_HEAD(sub_head, sub); + +/* This should, with some modifications, be able to hold other subscription + * types, not only interfaces. + */ +struct sub { + char ifname[IFNAMSIZ]; + struct ev_io watcher; + sr_subscription_ctx_t *sr_sub; + + TAILQ_ENTRY(sub) entries; +}; + +struct netlink { + int sd; + struct ev_io watcher; +}; + +struct statd { + struct netlink nl; + struct ev_loop *ev_loop; + struct sub_head subs; + sr_session_ctx_t *sr_ses; +}; + +static void set_sock_rcvbuf(int sd, int size) +{ + if (setsockopt(sd, SOL_SOCKET, SO_RCVBUF, &size, sizeof(size)) < 0) { + perror("setsockopt"); + return; + } + DEBUG("Socket receive buffer size increased to: %d bytes\n", size); +} + +static void str_to_lowercase(char *str) +{ + for (int i = 0; str[i]; i++) + str[i] = tolower((unsigned char)str[i]); +} + +static int nl_sock_init(void) +{ + struct sockaddr_nl addr; + int sock; + + sock = socket(PF_NETLINK, SOCK_RAW, NETLINK_ROUTE); + if (sock < 0) { + ERROR("Error, creating netlink socket: %s\n", strerror(errno)); + return -1; + } + + memset(&addr, 0, sizeof(addr)); + addr.nl_family = AF_NETLINK; + addr.nl_groups = RTMGRP_LINK; + + if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + ERROR("Error, binding netlink socket: %s\n", strerror(errno)); + close(sock); + return -1; + } + + return sock; +} + +static struct sub *sub_find_iface(struct sub_head *subs, const char *ifname) +{ + struct sub *sub; + + TAILQ_FOREACH(sub, subs, entries) { + if (strcmp(sub->ifname, ifname) == 0) + return sub; + } + + return NULL; +} + +static void sub_delete(struct ev_loop *loop, struct sub_head *subs, struct sub *sub) +{ + TAILQ_REMOVE(subs, sub, entries); + ev_io_stop(loop, &sub->watcher); + sr_unsubscribe(sub->sr_sub); + free(sub); +} + + +static json_t *json_get_ip_link(char *ifname) +{ + char cmd[512] = {}; /* Size is arbitrary */ + json_error_t j_err; + json_t *j_root; + FILE *proc; + + if (ifname) + snprintf(cmd, sizeof(cmd), "ip -d -j link show dev %s 2>/dev/null", ifname); + else + snprintf(cmd, sizeof(cmd), "ip -d -j link show 2>/dev/null"); + + proc = popenf("re", cmd); + if (!proc) { + ERROR("Error, running ip link command"); + return NULL; + } + + j_root = json_loadf(proc, 0, &j_err); + pclose(proc); + if (!j_root) { + ERROR("Error, parsing ip link JSON"); + return NULL; + } + + if (!json_is_array(j_root)) { + ERROR("Expected a JSON array from ip link"); + json_decref(j_root); + return NULL; + } + + return j_root; +} + +static int ly_add_ip_link_data(const struct ly_ctx *ctx, struct lyd_node **parent, + char *xpath, json_t *iface) +{ + json_t *j_val; + char *val; + int err; + + j_val = json_object_get(iface, "ifindex"); + if (!json_is_integer(j_val)) { + ERROR("Expected a JSON integer for 'ifindex'"); + return SR_ERR_SYS; + } + + err = lydx_new_path(ctx, parent, xpath, "if-index", "%lld", + json_integer_value(j_val)); + if (err) { + ERROR("Error, adding 'if-index' to data tree, libyang error %d", err); + return SR_ERR_LY; + } + + j_val = json_object_get(iface, "operstate"); + if (!json_is_string(j_val)) { + ERROR("Expected a JSON string for 'operstate'"); + return SR_ERR_SYS; + } + + val = strdup(json_string_value(j_val)); + if (!val) + return SR_ERR_SYS; + + str_to_lowercase(val); + + err = lydx_new_path(ctx, parent, xpath, "oper-status", val); + free(val); + if (err) { + ERROR("Error, adding 'oper-status' to data tree, libyang error %d", err); + return SR_ERR_LY; + } + + return SR_ERR_OK; +} + +static int ly_add_ip_link(const struct ly_ctx *ctx, struct lyd_node **parent, char *ifname) +{ + char xpath[XPATH_MAX] = {}; + json_t *j_root; + json_t *j_iface; + int err; + + j_root = json_get_ip_link(ifname); + if (!j_root) { + ERROR("Error, parsing ip-link JSON"); + return SR_ERR_SYS; + } + if (json_array_size(j_root) != 1) { + ERROR("Error, expected JSON array of single iface"); + json_decref(j_root); + return SR_ERR_SYS; + } + + j_iface = json_array_get(j_root, 0); + + snprintf(xpath, sizeof(xpath), "%s/interface[name='%s']", + XPATH_IFACE_BASE, ifname); + + err = ly_add_ip_link_data(ctx, parent, xpath, j_iface); + if (err) { + ERROR("Error, adding ip-link info for %s", ifname); + json_decref(j_root); + return err; + } + json_decref(j_root); + + return SR_ERR_OK; +} + +static int sr_ifaces_cb(sr_session_ctx_t *session, uint32_t, const char *, + const char *, const char *, uint32_t, + struct lyd_node **parent, void *priv) +{ + struct sub *sub = priv; + const struct ly_ctx *ctx; + sr_conn_ctx_t *con; + int err; + + DEBUG("Incoming query for xpath: %s\n", path); + + con = sr_session_get_connection(session); + if (!con) { + ERROR("Error, getting connection\n"); + return SR_ERR_INTERNAL; + } + + ctx = sr_acquire_context(con); + if (!ctx) { + ERROR("Error, acquiring context\n"); + return SR_ERR_INTERNAL; + } + + err = ly_add_ip_link(ctx, parent, sub->ifname); + if (err) + ERROR("Error, adding ip link info\n"); + + sr_release_context(con); + + return err; +} + +static void sig_event_cb(struct ev_loop *loop, struct ev_signal *, int) +{ + ev_break(loop, EVBREAK_ALL); +} + +static void sr_event_cb(struct ev_loop *, struct ev_io *w, int) +{ + struct sub *sub = (struct sub *)w->data; + + sr_subscription_process_events(sub->sr_sub, NULL, NULL); +} + +static int sub_to_iface(struct statd *statd, const char *ifname) +{ + char path[XPATH_MAX] = {}; + struct sub *sub; + int sr_ev_pipe; + int err; + + sub = sub_find_iface(&statd->subs, ifname); + if (sub) { + DEBUG("Interface %s already subscribed\n", ifname); + return SR_ERR_OK; + } + + sub = malloc(sizeof(struct sub)); + if (!sub) + return SR_ERR_INTERNAL; + + memset(sub, 0, sizeof(struct sub)); + + snprintf(sub->ifname, sizeof(sub->ifname), "%s", ifname); + snprintf(path, sizeof(path), "%s/interface[name='%s']", + XPATH_IFACE_BASE, ifname); + + DEBUG("Subscribe to events for \"%s\"\n", path); + err = sr_oper_get_subscribe(statd->sr_ses, "ietf-interfaces", + path, sr_ifaces_cb, sub, + SR_SUBSCR_DEFAULT | SR_SUBSCR_NO_THREAD | SR_SUBSCR_DONE_ONLY, + &sub->sr_sub); + if (err) { + ERROR("Error, subscribing to path \"%s\": %s\n", path, sr_strerror(err)); + free(sub); + return SR_ERR_INTERNAL; + } + + err = sr_get_event_pipe(sub->sr_sub, &sr_ev_pipe); + if (err) { + ERROR("Error, getting sysrepo event pipe: %s\n", sr_strerror(err)); + sr_unsubscribe(sub->sr_sub); + free(sub); + return SR_ERR_INTERNAL; + } + + TAILQ_INSERT_TAIL(&statd->subs, sub, entries); + + ev_io_init(&sub->watcher, sr_event_cb, sr_ev_pipe, EV_READ); + sub->watcher.data = sub; + ev_io_start(statd->ev_loop, &sub->watcher); + + return SR_ERR_OK; +} + +static void unsub_to_ifaces(struct statd *statd) +{ + struct sub *sub; + + while (!TAILQ_EMPTY(&statd->subs)) { + sub = TAILQ_FIRST(&statd->subs); + DEBUG("Unsubscribe from \"%s\" (all)\n", sub->ifname); + sub_delete(statd->ev_loop, &statd->subs, sub); + } +} + +static int unsub_to_iface(struct statd *statd, char *ifname) +{ + struct sub *sub; + + sub = sub_find_iface(&statd->subs, ifname); + if (!sub) { + ERROR("Error, can't find interface to delete (%s)", ifname); + return SR_ERR_INTERNAL; + } + DEBUG("Unsubscribe from \"%s\"\n", sub->ifname); + sub_delete(statd->ev_loop, &statd->subs, sub); + + return SR_ERR_OK; +} + +static int nl_process_msg(struct nlmsghdr *nlh, struct statd *statd) +{ + struct ifinfomsg *iface; + struct rtattr *attr; + int attr_len; + + iface = NLMSG_DATA(nlh); + attr = IFLA_RTA(iface); + attr_len = IFLA_PAYLOAD(nlh); + + for (; RTA_OK(attr, attr_len); attr = RTA_NEXT(attr, attr_len)) { + if (attr->rta_type == IFLA_IFNAME) { + char *ifname = (char *)RTA_DATA(attr); + + if (nlh->nlmsg_type == RTM_NEWLINK) + return sub_to_iface(statd, ifname); + else if (nlh->nlmsg_type == RTM_DELLINK) + return unsub_to_iface(statd, ifname); + else + return SR_ERR_INTERNAL; + } + } + + /* Ignore nl messages with no interface name */ + return SR_ERR_OK; +} + +static void nl_event_cb(struct ev_loop *, struct ev_io *w, int) +{ + struct statd *statd = (struct statd *) w->data; + char buf[NL_BUF_SIZE]; + struct nlmsghdr *nlh; + int err; + int len; + + len = recv(statd->nl.sd, buf, sizeof(buf), 0); + if (len < 0) { + ERROR("Error, netlink recv failed: %s\n", strerror(errno)); + close(statd->nl.sd); + /* NOTE: This is likely caused by a full kernel buffer, which + * means we can't trust our list. So we exit hard and let finit + * respawn us to handle this. + */ + exit(EXIT_FAILURE); + } + + for (nlh = (struct nlmsghdr *)buf; NLMSG_OK(nlh, len); nlh = NLMSG_NEXT(nlh, len)) { + err = nl_process_msg(nlh, statd); + if (err) + ERROR("Error, processing netlink message: %s\n", sr_strerror(err)); + } +} + +static int sub_to_ifaces(struct statd *statd) +{ + json_t *j_iface; + json_t *j_root; + size_t i; + + j_root = json_get_ip_link(NULL); + if (!j_root) { + ERROR("Error, parsing ip-link JSON"); + return SR_ERR_SYS; + } + + json_array_foreach(j_root, i, j_iface) { + json_t *j_ifname; + int err; + + j_ifname = json_object_get(j_iface, "ifname"); + if (!json_is_string(j_ifname)) { + ERROR("Got unexpected JSON type for 'ifname'"); + continue; + } + + err = sub_to_iface(statd, json_string_value(j_ifname)); + if (err) { + ERROR("Unable to subscribe to %s", json_string_value(j_ifname)); + continue; + } + } + json_decref(j_root); + + return SR_ERR_OK; +} + +int main(void) +{ + struct ev_signal sig_watcher; + struct statd statd = {}; + sr_conn_ctx_t *sr_conn; + int err; + + TAILQ_INIT(&statd.subs); + statd.ev_loop = EV_DEFAULT; + + statd.nl.sd = nl_sock_init(); + if (statd.nl.sd < 0) { + ERROR("Error, opening netlink socket\n"); + return EXIT_FAILURE; + } + INFO("Status daemon starting\n"); + + set_sock_rcvbuf(statd.nl.sd, SOCK_RMEM_SIZE); + + err = sr_connect(SR_CONN_DEFAULT, &sr_conn); + if (err) { + ERROR("Error, connecting to sysrepo: %s\n", sr_strerror(err)); + return EXIT_FAILURE; + } + DEBUG("Connected to sysrepo\n"); + + err = sr_session_start(sr_conn, SR_DS_OPERATIONAL, &statd.sr_ses); + if (err) { + ERROR("Error, start sysrepo session: %s\n", sr_strerror(err)); + sr_disconnect(sr_conn); + return EXIT_FAILURE; + } + DEBUG("Session started (%p)\n", statd.sr_ses); + + DEBUG("Attempting to register existing interfaces\n"); + err = sub_to_ifaces(&statd); + if (err) { + ERROR("Error, registering existing interfaces\n"); + sr_disconnect(sr_conn); + return EXIT_FAILURE; + } + + ev_signal_init(&sig_watcher, sig_event_cb, SIGINT); + sig_watcher.data = &statd; + ev_signal_start(statd.ev_loop, &sig_watcher); + + ev_io_init(&statd.nl.watcher, nl_event_cb, statd.nl.sd, EV_READ); + statd.nl.watcher.data = &statd; + ev_io_start(statd.ev_loop, &statd.nl.watcher); + + INFO("Status daemon entering main event loop\n"); + ev_run(statd.ev_loop, 0); + + /* We should never get here during normal operation */ + INFO("Status daemon shutting down\n"); + unsub_to_ifaces(&statd); + sr_session_stop(statd.sr_ses); + sr_disconnect(sr_conn); + + return EXIT_SUCCESS; +}