From: Christian Brauner Date: Fri, 4 Jun 2021 16:21:04 +0000 (+0200) Subject: mainloop: add io_uring support X-Git-Tag: lxc-5.0.0~119^2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=543d2f838c6fe37d5f36ed7299c109df38504018;p=thirdparty%2Flxc.git mainloop: add io_uring support Users can choose to compile liblxc with io_uring support. This will cause LXC to use io_uring instead of epoll. We're using both, io_uring's one-shot and multi-shot poll mode depending on the type of handler. Signed-off-by: Christian Brauner --- diff --git a/configure.ac b/configure.ac index 00cdaf081..0e4cbf92d 100644 --- a/configure.ac +++ b/configure.ac @@ -671,6 +671,22 @@ AC_CHECK_HEADER([ifaddrs.h], AC_DEFINE(HAVE_IFADDRS_H, 1, [Have ifaddrs.h]), AM_CONDITIONAL(HAVE_IFADDRS_H, false)) +AC_ARG_ENABLE([liburing], + [AS_HELP_STRING([--enable-liburing], [enable liburing support [default=auto]])], + [enable_liburing=$enableval], [enable_liburing=auto]) + +if test "x$enable_liburing" = "auto"; then + AC_CHECK_LIB([uring],[__io_uring_sqring_wait],[enable_liburing=yes],[enable_liburing=no]) +fi + +AM_CONDITIONAL([ENABLE_LIBURING], [test "x$enable_liburing" = "xyes"]) + +AM_COND_IF([ENABLE_LIBURING], + [AC_CHECK_HEADER([liburing.h],[],[AC_MSG_ERROR([You must install the liburing development package in order to compile lxc])]) + # We use __io_uring_sqring_wait as an indicator whether liburing is new enough to support poll. + AC_CHECK_LIB([uring],[__io_uring_sqring_wait],[],[AC_MSG_ERROR([The liburing development package in order to compile lxc])]) + AC_SUBST([LIBURING_LIBS], [-luring])]) + # lookup major()/minor()/makedev() AC_HEADER_MAJOR diff --git a/src/lxc/Makefile.am b/src/lxc/Makefile.am index da6806f87..438592e23 100644 --- a/src/lxc/Makefile.am +++ b/src/lxc/Makefile.am @@ -274,7 +274,8 @@ liblxc_la_LIBADD = $(CAP_LIBS) \ $(OPENSSL_LIBS) \ $(SELINUX_LIBS) \ $(SECCOMP_LIBS) \ - $(DLOG_LIBS) + $(DLOG_LIBS) \ + $(LIBURING_LIBS) bin_SCRIPTS= @@ -333,7 +334,8 @@ LDADD = liblxc.la \ @OPENSSL_LIBS@ \ @SECCOMP_LIBS@ \ @SELINUX_LIBS@ \ - @DLOG_LIBS@ + @DLOG_LIBS@ \ + @LIBURING_LIBS@ if ENABLE_TOOLS lxc_attach_SOURCES = tools/lxc_attach.c \ diff --git a/src/lxc/cgroups/cgfsng.c b/src/lxc/cgroups/cgfsng.c index 923be1b88..46754217c 100644 --- a/src/lxc/cgroups/cgfsng.c +++ b/src/lxc/cgroups/cgfsng.c @@ -1987,7 +1987,11 @@ static int cg_unified_freeze_do(struct cgroup_ops *ops, int timeout, /* automatically cleaned up now */ descr_ptr = &descr; - ret = lxc_mainloop_add_handler_events(&descr, fd, EPOLLPRI, freezer_cgroup_events_cb, INT_TO_PTR(state_num)); + ret = lxc_mainloop_add_handler_events(&descr, fd, EPOLLPRI, + freezer_cgroup_events_cb, + default_cleanup_handler, + INT_TO_PTR(state_num), + "freezer_cgroup_events_cb"); if (ret < 0) return log_error_errno(-1, errno, "Failed to add cgroup.events fd handler to mainloop"); } @@ -3669,7 +3673,11 @@ static int do_cgroup_freeze(int unified_fd, if (events_fd < 0) return log_error_errno(-errno, errno, "Failed to open cgroup.events file"); - ret = lxc_mainloop_add_handler_events(&descr, events_fd, EPOLLPRI, freezer_cgroup_events_cb, INT_TO_PTR(state_num)); + ret = lxc_mainloop_add_handler_events(&descr, events_fd, EPOLLPRI, + freezer_cgroup_events_cb, + default_cleanup_handler, + INT_TO_PTR(state_num), + "freezer_cgroup_events_cb"); if (ret < 0) return log_error_errno(-1, errno, "Failed to add cgroup.events fd handler to mainloop"); } diff --git a/src/lxc/cmd/lxc_monitord.c b/src/lxc/cmd/lxc_monitord.c index 50f9ac150..a18712b64 100644 --- a/src/lxc/cmd/lxc_monitord.c +++ b/src/lxc/cmd/lxc_monitord.c @@ -37,8 +37,6 @@ lxc_log_define(lxc_monitord, lxc); sigjmp_buf mark; -static void lxc_monitord_cleanup(void); - /* * Defines the structure to store the monitor information * @lxcpath : the path being monitored @@ -113,27 +111,23 @@ static int lxc_monitord_fifo_delete(struct lxc_monitor *mon) return 0; } -static void lxc_monitord_sockfd_remove(struct lxc_monitor *mon, int fd) +static int lxc_monitord_sockfd_remove(struct lxc_monitor *mon, int fd) { int i; - if (lxc_mainloop_del_handler(&mon->descr, fd)) - CRIT("File descriptor %d not found in mainloop", fd); - close(fd); - for (i = 0; i < mon->clientfds_cnt; i++) if (mon->clientfds[i] == fd) break; if (i >= mon->clientfds_cnt) { CRIT("File descriptor %d not found in clients array", fd); - lxc_monitord_cleanup(); - exit(EXIT_FAILURE); + return LXC_MAINLOOP_ERROR; } memmove(&mon->clientfds[i], &mon->clientfds[i+1], (mon->clientfds_cnt - i - 1) * sizeof(mon->clientfds[0])); mon->clientfds_cnt--; + return LXC_MAINLOOP_DISARM; } static int lxc_monitord_sock_handler(int fd, uint32_t events, void *data, @@ -146,12 +140,14 @@ static int lxc_monitord_sock_handler(int fd, uint32_t events, void *data, char buf[4]; rc = lxc_read_nointr(fd, buf, sizeof(buf)); - if (rc > 0 && !strncmp(buf, "quit", 4)) + if (rc > 0 && !strncmp(buf, "quit", 4)) { quit = LXC_MAINLOOP_CLOSE; + return LXC_MAINLOOP_CLOSE; + } } if (events & EPOLLHUP) - lxc_monitord_sockfd_remove(mon, fd); + return lxc_monitord_sockfd_remove(mon, fd); return quit; } @@ -202,7 +198,9 @@ static int lxc_monitord_sock_accept(int fd, uint32_t events, void *data, } ret = lxc_mainloop_add_handler(&mon->descr, clientfd, - lxc_monitord_sock_handler, mon); + lxc_monitord_sock_handler, + default_cleanup_handler, + mon, "lxc_monitord_sock_handler"); if (ret < 0) { ERROR("Failed to add socket handler"); goto err1; @@ -264,20 +262,14 @@ static int lxc_monitord_create(struct lxc_monitor *mon) static void lxc_monitord_delete(struct lxc_monitor *mon) { - int i; - - lxc_mainloop_del_handler(&mon->descr, mon->listenfd); lxc_abstract_unix_close(mon->listenfd); lxc_monitord_sock_delete(mon); - lxc_mainloop_del_handler(&mon->descr, mon->fifofd); lxc_monitord_fifo_delete(mon); close(mon->fifofd); - for (i = 0; i < mon->clientfds_cnt; i++) { - lxc_mainloop_del_handler(&mon->descr, mon->clientfds[i]); + for (int i = 0; i < mon->clientfds_cnt; i++) close(mon->clientfds[i]); - } mon->clientfds_cnt = 0; } @@ -310,14 +302,18 @@ static int lxc_monitord_mainloop_add(struct lxc_monitor *mon) int ret; ret = lxc_mainloop_add_handler(&mon->descr, mon->fifofd, - lxc_monitord_fifo_handler, mon); + lxc_monitord_fifo_handler, + default_cleanup_handler, + mon, "lxc_monitord_fifo_handler"); if (ret < 0) { ERROR("Failed to add to mainloop monitor handler for fifo"); return -1; } ret = lxc_mainloop_add_handler(&mon->descr, mon->listenfd, - lxc_monitord_sock_accept, mon); + lxc_monitord_sock_accept, + default_cleanup_handler, + mon, "lxc_monitord_sock_accept"); if (ret < 0) { ERROR("Failed to add to mainloop monitor handler for listen socket"); return -1; @@ -326,11 +322,6 @@ static int lxc_monitord_mainloop_add(struct lxc_monitor *mon) return 0; } -static void lxc_monitord_cleanup(void) -{ - lxc_monitord_delete(&monitor); -} - static void lxc_monitord_sig_handler(int sig) { siglongjmp(mark, 1); @@ -453,11 +444,11 @@ on_signal: ret = EXIT_SUCCESS; on_error: - if (monitord_created) - lxc_monitord_cleanup(); - if (mainloop_opened) lxc_mainloop_close(&monitor.descr); + if (monitord_created) + lxc_monitord_delete(&monitor); + exit(ret); } diff --git a/src/lxc/commands.c b/src/lxc/commands.c index f8fb91f33..124b25cde 100644 --- a/src/lxc/commands.c +++ b/src/lxc/commands.c @@ -1587,8 +1587,10 @@ static int lxc_cmd_seccomp_notify_add_listener_callback(int fd, goto out; } - ret = lxc_mainloop_add_handler(descr, recv_fd, seccomp_notify_handler, - handler); + ret = lxc_mainloop_add_handler(descr, recv_fd, + seccomp_notify_handler, + seccomp_notify_cleanup_handler, + handler, "seccomp_notify_handler"); if (ret < 0) { rsp.ret = -errno; goto out; @@ -1900,11 +1902,8 @@ static int lxc_cmd_process(int fd, struct lxc_cmd_req *req, } static void lxc_cmd_fd_cleanup(int fd, struct lxc_handler *handler, - struct lxc_async_descr *descr, const lxc_cmd_t cmd) + const lxc_cmd_t cmd) { - lxc_terminal_free(handler->conf, fd); - lxc_mainloop_del_handler(descr, fd); - if (cmd == LXC_CMD_ADD_STATE_CLIENT) { struct lxc_list *cur, *next; @@ -1937,11 +1936,25 @@ static void lxc_cmd_fd_cleanup(int fd, struct lxc_handler *handler, * was already reached by the time we were ready to add it. So * fallthrough and clean it up. */ - TRACE("Closing state client fd %d for command \"%s\"", fd, lxc_cmd_str(cmd)); + TRACE("Deleted state client fd %d for command \"%s\"", fd, lxc_cmd_str(cmd)); } - TRACE("Closing client fd %d for command \"%s\"", fd, lxc_cmd_str(cmd)); + /* + * We're not closing the client fd here. They will instead be notified + * from the mainloop when it calls the cleanup handler. This will cause + * a slight delay but is semantically cleaner then what we used to do. + */ +} + +static int lxc_cmd_cleanup_handler(int fd, void *data) +{ + struct lxc_handler *handler = data; + + lxc_terminal_free(handler->conf, fd); close(fd); + TRACE("Closing client fd %d for \"%s\"", fd, __FUNCTION__); + return 0; + } static int lxc_cmd_handler(int fd, uint32_t events, void *data, @@ -1965,20 +1978,20 @@ static int lxc_cmd_handler(int fd, uint32_t events, void *data, __lxc_cmd_rsp_send(fd, &rsp); } - goto out_close; + goto out; } if (ret == 0) - goto out_close; + goto out; if (ret != sizeof(req)) { WARN("Failed to receive full command request. Ignoring request for \"%s\"", lxc_cmd_str(req.cmd)); - goto out_close; + goto out; } if ((req.datalen > LXC_CMD_DATA_MAX) && (req.cmd != LXC_CMD_CONSOLE_LOG)) { ERROR("Received command data length %d is too large for command \"%s\"", req.datalen, lxc_cmd_str(req.cmd)); - goto out_close; + goto out; } if (req.datalen > 0) { @@ -1986,7 +1999,7 @@ static int lxc_cmd_handler(int fd, uint32_t events, void *data, ret = lxc_recv_nointr(fd, reqdata, req.datalen, 0); if (ret != req.datalen) { WARN("Failed to receive full command request. Ignoring request for \"%s\"", lxc_cmd_str(req.cmd)); - goto out_close; + goto out; } req.data = reqdata; @@ -1995,20 +2008,20 @@ static int lxc_cmd_handler(int fd, uint32_t events, void *data, ret = lxc_cmd_process(fd, &req, handler, descr); if (ret < 0) { DEBUG("Failed to process command %s; cleaning up client fd %d", lxc_cmd_str(req.cmd), fd); - goto out_close; - } else if (ret == LXC_CMD_REAP_CLIENT_FD) { + goto out; + } + + if (ret == LXC_CMD_REAP_CLIENT_FD) { TRACE("Processed command %s; cleaning up client fd %d", lxc_cmd_str(req.cmd), fd); - goto out_close; - } else { - TRACE("Processed command %s; keeping client fd %d", lxc_cmd_str(req.cmd), fd); + goto out; } -out: + TRACE("Processed command %s; keeping client fd %d", lxc_cmd_str(req.cmd), fd); return LXC_MAINLOOP_CONTINUE; -out_close: - lxc_cmd_fd_cleanup(fd, handler, descr, req.cmd); - goto out; +out: + lxc_cmd_fd_cleanup(fd, handler, req.cmd); + return LXC_MAINLOOP_DISARM; } static int lxc_cmd_accept(int fd, uint32_t events, void *data, @@ -2029,7 +2042,10 @@ static int lxc_cmd_accept(int fd, uint32_t events, void *data, if (ret < 0) return log_error_errno(ret, errno, "Failed to enable necessary credentials on command socket"); - ret = lxc_mainloop_add_handler(descr, connection, lxc_cmd_handler, data); + ret = lxc_mainloop_add_oneshot_handler(descr, connection, + lxc_cmd_handler, + lxc_cmd_cleanup_handler, + data, "lxc_cmd_handler"); if (ret) return log_error(ret, "Failed to add command handler"); @@ -2068,7 +2084,10 @@ int lxc_cmd_mainloop_add(const char *name, struct lxc_async_descr *descr, { int ret; - ret = lxc_mainloop_add_handler(descr, handler->conf->maincmd_fd, lxc_cmd_accept, handler); + ret = lxc_mainloop_add_handler(descr, handler->conf->maincmd_fd, + lxc_cmd_accept, + default_cleanup_handler, + handler, "lxc_cmd_accept"); if (ret < 0) return log_error(ret, "Failed to add handler for command socket fd %d", handler->conf->maincmd_fd); diff --git a/src/lxc/lxcseccomp.h b/src/lxc/lxcseccomp.h index 2ef857dc8..41293bcea 100644 --- a/src/lxc/lxcseccomp.h +++ b/src/lxc/lxcseccomp.h @@ -81,6 +81,7 @@ struct lxc_seccomp { __hidden extern int lxc_seccomp_load(struct lxc_conf *conf); __hidden extern int lxc_read_seccomp_config(struct lxc_conf *conf); __hidden extern void lxc_seccomp_free(struct lxc_seccomp *seccomp); +__hidden extern int seccomp_notify_cleanup_handler(int fd, void *data); __hidden extern int seccomp_notify_handler(int fd, uint32_t events, void *data, struct lxc_async_descr *descr); __hidden extern void seccomp_conf_init(struct lxc_conf *conf); @@ -133,7 +134,12 @@ static inline void lxc_seccomp_free(struct lxc_seccomp *seccomp) static inline int seccomp_notify_handler(int fd, uint32_t events, void *data, struct lxc_async_descr *descr) { - return -ENOSYS; + return ret_errno(ENOSYS); +} + +static inline int seccomp_notify_cleanup_handler(void *data) +{ + return ret_errno(ENOSYS); } static inline void seccomp_conf_init(struct lxc_conf *conf) diff --git a/src/lxc/macro.h b/src/lxc/macro.h index ab4f989be..6cd4fdbbb 100644 --- a/src/lxc/macro.h +++ b/src/lxc/macro.h @@ -747,4 +747,9 @@ enum { #define PER_LINUX32 0x0008 #endif +static inline bool has_exact_flags(__u32 flags, __u32 mask) +{ + return (flags & mask) == mask; +} + #endif /* __LXC_MACRO_H */ diff --git a/src/lxc/mainloop.c b/src/lxc/mainloop.c index bf06d76c2..c5bb5df54 100644 --- a/src/lxc/mainloop.c +++ b/src/lxc/mainloop.c @@ -8,21 +8,292 @@ #include #include #include +#include #include #include #include "config.h" +#include "log.h" +#include "macro.h" #include "mainloop.h" +lxc_log_define(mainloop, lxc); + +#define CANCEL_RAISED (1 << 0) +#define CANCEL_RECEIVED (1 << 1) +#define CANCEL_SUCCESS (1 << 2) + struct mainloop_handler { - lxc_mainloop_callback_t callback; + struct lxc_list *list; int fd; void *data; + lxc_mainloop_callback_t callback; + lxc_mainloop_cleanup_t cleanup; + const char *handler_name; + unsigned int flags; }; #define MAX_EVENTS 10 -int lxc_mainloop(struct lxc_async_descr *descr, int timeout_ms) +static int __io_uring_disarm(struct lxc_async_descr *descr, + struct mainloop_handler *handler); + +static void delete_handler(struct lxc_async_descr *descr, + struct mainloop_handler *handler, bool oneshot) +{ + int ret = 0; + struct lxc_list *list; + + if (descr->type == LXC_MAINLOOP_IO_URING) { + /* + * For a oneshot handler we don't have to do anything. If we + * end up here we know that an event for this handler has been + * generated before and since this is a oneshot handler it + * means that it has been deactivated. So the only thing we + * need to do is to call the registered cleanup handler and + * remove the handlerfrom the list. + */ + if (!oneshot) + ret = __io_uring_disarm(descr, handler); + } else { + ret = epoll_ctl(descr->epfd, EPOLL_CTL_DEL, handler->fd, NULL); + } + if (ret < 0) + SYSWARN("Failed to delete \"%d\" for \"%s\"", handler->fd, handler->handler_name); + + if (handler->cleanup) { + ret = handler->cleanup(handler->fd, handler->data); + if (ret < 0) + SYSWARN("Failed to call cleanup \"%s\" handler", handler->handler_name); + } + + list = move_ptr(handler->list); + lxc_list_del(list); + free(list->elem); + free(list); +} + +#ifndef HAVE_LIBURING +static inline int __lxc_mainloop_io_uring(struct lxc_async_descr *descr, + int timeout_ms) +{ + return ret_errno(ENOSYS); +} + +static int __io_uring_arm(struct lxc_async_descr *descr, + struct mainloop_handler *handler, bool oneshot) +{ + return ret_errno(ENOSYS); +} + +static int __io_uring_disarm(struct lxc_async_descr *descr, + struct mainloop_handler *handler) +{ + return ret_errno(ENOSYS); +} + +static inline int __io_uring_open(struct lxc_async_descr *descr) +{ + return ret_errno(ENOSYS); +} + +#else + +static inline int __io_uring_open(struct lxc_async_descr *descr) +{ + int ret; + *descr = (struct lxc_async_descr){ + .epfd = -EBADF, + }; + + descr->ring = mmap(NULL, sizeof(struct io_uring), PROT_READ | PROT_WRITE, + MAP_SHARED | MAP_POPULATE | MAP_ANONYMOUS, -1, 0); + if (descr->ring == MAP_FAILED) + return syserror("Failed to mmap io_uring memory"); + + ret = io_uring_queue_init(512, descr->ring, IORING_SETUP_SQPOLL); + if (ret) { + SYSERROR("Failed to initialize io_uring instance"); + goto on_error; + } + + ret = io_uring_ring_dontfork(descr->ring); + if (ret) { + SYSERROR("Failed to prevent inheritance of io_uring mmaped region"); + goto on_error; + } + + descr->type = LXC_MAINLOOP_IO_URING; + TRACE("Created io-uring instance"); + return 0; + +on_error: + ret = munmap(descr->ring, sizeof(struct io_uring)); + if (ret < 0) + SYSWARN("Failed to unmap io_uring mmaped memory"); + + return ret_errno(ENOSYS); +} + +static int __io_uring_arm(struct lxc_async_descr *descr, + struct mainloop_handler *handler, bool oneshot) +{ + int ret; + struct io_uring_sqe *sqe; + + sqe = io_uring_get_sqe(descr->ring); + if (!sqe) + return syserror_set(ENOENT, "Failed to get submission queue entry"); + + io_uring_prep_poll_add(sqe, handler->fd, EPOLLIN); + + /* + * Raise IORING_POLL_ADD_MULTI to set up a multishot poll. The same sqe + * will now produce multiple cqes. A cqe produced from a multishot sqe + * will raise IORING_CQE_F_MORE in cqe->flags. + * Some devices can't be used with IORING_POLL_ADD_MULTI. This can only + * be detected at completion time. The IORING_CQE_F_MORE flag will not + * raised in cqe->flags. This includes terminal devices. So + * unfortunately we can't use multishot for them although we really + * would like to. But instead we will need to resubmit them. The + * io_uring based mainloop will deal cases whwere multishot doesn't + * work and resubmit the request. The handler just needs to inform the + * mainloop that it wants to keep the handler. + */ + if (!oneshot) + sqe->len |= IORING_POLL_ADD_MULTI; + + io_uring_sqe_set_data(sqe, handler); + ret = io_uring_submit(descr->ring); + if (ret < 0) { + if (!oneshot && ret == -EINVAL) { + /* The kernel might not yet support multishot. */ + sqe->len &= ~IORING_POLL_ADD_MULTI; + ret = io_uring_submit(descr->ring); + } + } + if (ret < 0) + return syserror_ret(ret, "Failed to add \"%s\" handler", handler->handler_name); + + TRACE("Added \"%s\" handler", handler->handler_name); + return 0; +} + +static int __io_uring_disarm(struct lxc_async_descr *descr, + struct mainloop_handler *handler) +{ + int ret; + struct io_uring_sqe *sqe; + + sqe = io_uring_get_sqe(descr->ring); + if (!sqe) + return syserror_set(ENOENT, + "Failed to get submission queue entry"); + + io_uring_prep_poll_remove(sqe, handler); + handler->flags |= CANCEL_RAISED; + io_uring_sqe_set_data(sqe, handler); + ret = io_uring_submit(descr->ring); + if (ret < 0) { + handler->flags &= ~CANCEL_RAISED; + return syserror_ret(ret, "Failed to remove \"%s\" handler", + handler->handler_name); + } + + TRACE("Removed handler \"%s\"", handler->handler_name); + return ret; +} + +static void msec_to_ts(struct __kernel_timespec *ts, unsigned int timeout_ms) +{ + ts->tv_sec = timeout_ms / 1000; + ts->tv_nsec = (timeout_ms % 1000) * 1000000; +} + +static int __lxc_mainloop_io_uring(struct lxc_async_descr *descr, int timeout_ms) +{ + struct __kernel_timespec ts; + + if (timeout_ms >= 0) + msec_to_ts(&ts, timeout_ms); + + for (;;) { + int ret; + __s32 mask = 0; + bool oneshot = false; + struct io_uring_cqe *cqe = NULL; + struct mainloop_handler *handler = NULL; + + if (timeout_ms >= 0) + ret = io_uring_wait_cqe_timeout(descr->ring, &cqe, &ts); + else + ret = io_uring_wait_cqe(descr->ring, &cqe); + if (ret < 0) { + if (ret == -EINTR) + continue; + + if (ret == -ETIME) + return 0; + + return syserror_ret(ret, "Failed to wait for completion"); + } + + ret = LXC_MAINLOOP_CONTINUE; + oneshot = !(cqe->flags & IORING_CQE_F_MORE); + mask = cqe->res; + handler = io_uring_cqe_get_data(cqe); + io_uring_cqe_seen(descr->ring, cqe); + + switch (mask) { + case -ECANCELED: + handler->flags |= CANCEL_RECEIVED; + TRACE("Canceled \"%s\" handler", handler->handler_name); + goto out; + case -ENOENT: + handler->flags = CANCEL_SUCCESS | CANCEL_RECEIVED; + TRACE("No sqe for \"%s\" handler", handler->handler_name); + goto out; + case -EALREADY: + TRACE("Repeat sqe remove request for \"%s\" handler", handler->handler_name); + goto out; + case 0: + handler->flags |= CANCEL_SUCCESS; + TRACE("Removed \"%s\" handler", handler->handler_name); + goto out; + default: + /* + * We need to always remove the handler for a + * successful oneshot request. + */ + if (oneshot) + handler->flags = CANCEL_SUCCESS | CANCEL_RECEIVED; + } + + ret = handler->callback(handler->fd, mask, handler->data, descr); + switch (ret) { + case LXC_MAINLOOP_CONTINUE: + /* We're operating in oneshot mode so we need to rearm. */ + if (oneshot && __io_uring_arm(descr, handler, true)) + return -1; + break; + case LXC_MAINLOOP_DISARM: + if (has_exact_flags(handler->flags, (CANCEL_SUCCESS | CANCEL_RECEIVED))) + delete_handler(descr, handler, oneshot); + break; + case LXC_MAINLOOP_CLOSE: + return log_trace(0, "Closing from \"%s\"", handler->handler_name); + case LXC_MAINLOOP_ERROR: + return syserror_ret(-1, "Closing with error from \"%s\"", handler->handler_name); + } + + out: + if (lxc_list_empty(&descr->handlers)) + return error_ret(0, "Closing because there are no more handlers"); + } +} +#endif + +static int __lxc_mainloop_epoll(struct lxc_async_descr *descr, int timeout_ms) { int i, nfds, ret; struct mainloop_handler *handler; @@ -45,10 +316,17 @@ int lxc_mainloop(struct lxc_async_descr *descr, int timeout_ms) */ ret = handler->callback(handler->fd, events[i].events, handler->data, descr); - if (ret == LXC_MAINLOOP_ERROR) - return -1; - if (ret == LXC_MAINLOOP_CLOSE) + switch (ret) { + case LXC_MAINLOOP_DISARM: + delete_handler(descr, handler, false); + __fallthrough; + case LXC_MAINLOOP_CONTINUE: + break; + case LXC_MAINLOOP_CLOSE: return 0; + case LXC_MAINLOOP_ERROR: + return -1; + } } if (nfds == 0) @@ -59,76 +337,153 @@ int lxc_mainloop(struct lxc_async_descr *descr, int timeout_ms) } } -int lxc_mainloop_add_handler_events(struct lxc_async_descr *descr, int fd, - int events, - lxc_mainloop_callback_t callback, - void *data) +int lxc_mainloop(struct lxc_async_descr *descr, int timeout_ms) +{ + if (descr->type == LXC_MAINLOOP_IO_URING) + return __lxc_mainloop_io_uring(descr, timeout_ms); + + return __lxc_mainloop_epoll(descr, timeout_ms); +} + +static int __lxc_mainloop_add_handler_events(struct lxc_async_descr *descr, + int fd, int events, + lxc_mainloop_callback_t callback, + lxc_mainloop_cleanup_t cleanup, + void *data, bool oneshot, + const char *handler_name) { __do_free struct mainloop_handler *handler = NULL; - __do_free struct lxc_list *item = NULL; + __do_free struct lxc_list *list = NULL; + int ret; struct epoll_event ev; if (fd < 0) - return -1; + return ret_errno(EBADF); - handler = malloc(sizeof(*handler)); - if (!handler) - return -1; + if (!callback || !cleanup || !events || !handler_name) + return ret_errno(EINVAL); - handler->callback = callback; - handler->fd = fd; - handler->data = data; + handler = zalloc(sizeof(*handler)); + if (!handler) + return ret_errno(ENOMEM); - ev.events = events; - ev.data.ptr = handler; + handler->callback = callback; + handler->cleanup = cleanup; + handler->fd = fd; + handler->data = data; + handler->handler_name = handler_name; - if (epoll_ctl(descr->epfd, EPOLL_CTL_ADD, fd, &ev) < 0) + if (descr->type == LXC_MAINLOOP_IO_URING) { + ret = __io_uring_arm(descr, handler, oneshot); + } else { + ev.events = events; + ev.data.ptr = handler; + ret = epoll_ctl(descr->epfd, EPOLL_CTL_ADD, fd, &ev); + } + if (ret < 0) return -errno; - item = malloc(sizeof(*item)); - if (!item) + list = lxc_list_new(); + if (!list) return ret_errno(ENOMEM); - item->elem = move_ptr(handler); - lxc_list_add(&descr->handlers, move_ptr(item)); + handler->list = list; + lxc_list_add_elem(list, move_ptr(handler));; + lxc_list_add_tail(&descr->handlers, move_ptr(list)); return 0; } +int lxc_mainloop_add_handler_events(struct lxc_async_descr *descr, int fd, + int events, + lxc_mainloop_callback_t callback, + lxc_mainloop_cleanup_t cleanup, + void *data, const char *handler_name) +{ + return __lxc_mainloop_add_handler_events(descr, fd, events, + callback, cleanup, + data, false, handler_name); +} + int lxc_mainloop_add_handler(struct lxc_async_descr *descr, int fd, - lxc_mainloop_callback_t callback, void *data) + lxc_mainloop_callback_t callback, + lxc_mainloop_cleanup_t cleanup, + void *data, const char *handler_name) { - return lxc_mainloop_add_handler_events(descr, fd, EPOLLIN, callback, - data); + return __lxc_mainloop_add_handler_events(descr, fd, EPOLLIN, + callback, cleanup, + data, false, handler_name); +} + +int lxc_mainloop_add_oneshot_handler(struct lxc_async_descr *descr, int fd, + lxc_mainloop_callback_t callback, + lxc_mainloop_cleanup_t cleanup, + void *data, const char *handler_name) +{ + return __lxc_mainloop_add_handler_events(descr, fd, EPOLLIN, + callback, cleanup, + data, true, handler_name); } int lxc_mainloop_del_handler(struct lxc_async_descr *descr, int fd) { - struct mainloop_handler *handler; - struct lxc_list *iterator; + int ret; + struct lxc_list *iterator = NULL; lxc_list_for_each(iterator, &descr->handlers) { - handler = iterator->elem; + struct mainloop_handler *handler = iterator->elem; - if (handler->fd == fd) { - /* found */ - if (epoll_ctl(descr->epfd, EPOLL_CTL_DEL, fd, NULL)) - return -errno; + if (handler->fd != fd) + continue; + if (descr->type == LXC_MAINLOOP_IO_URING) + ret = __io_uring_disarm(descr, handler); + else + ret = epoll_ctl(descr->epfd, EPOLL_CTL_DEL, fd, NULL); + if (ret < 0) + return syserror("Failed to disarm \"%s\"", handler->handler_name); + + /* + * For io_uring the deletion happens at completion time. Either + * we get ENOENT if the request was oneshot and it had already + * triggered or we get ECANCELED for the original sqe and 0 for + * the cancellation request. + */ + if (descr->type == LXC_MAINLOOP_EPOLL) { lxc_list_del(iterator); free(iterator->elem); free(iterator); - return 0; } + + return 0; } return ret_errno(EINVAL); } -int lxc_mainloop_open(struct lxc_async_descr *descr) +static inline int __epoll_open(struct lxc_async_descr *descr) { + *descr = (struct lxc_async_descr){ + .epfd = -EBADF, + }; + descr->epfd = epoll_create1(EPOLL_CLOEXEC); if (descr->epfd < 0) - return -errno; + return syserror("Failed to create epoll instance"); + + descr->type = LXC_MAINLOOP_EPOLL; + TRACE("Created epoll instance"); + return 0; +} + +int lxc_mainloop_open(struct lxc_async_descr *descr) +{ + int ret; + + ret = __io_uring_open(descr); + if (ret == -ENOSYS) + ret = __epoll_open(descr); + if (ret < 0) + return syserror("Failed to create mainloop instance"); lxc_list_init(&descr->handlers); return 0; @@ -148,5 +503,14 @@ void lxc_mainloop_close(struct lxc_async_descr *descr) iterator = next; } - close_prot_errno_disarm(descr->epfd); + if (descr->type == LXC_MAINLOOP_IO_URING) { +#ifdef HAVE_LIBURING + io_uring_queue_exit(descr->ring); + munmap(descr->ring, sizeof(struct io_uring)); +#else + ERROR("Unsupported io_uring mainloop"); +#endif + } else { + close_prot_errno_disarm(descr->epfd); + } } diff --git a/src/lxc/mainloop.h b/src/lxc/mainloop.h index 099167ccc..dee3821e5 100644 --- a/src/lxc/mainloop.h +++ b/src/lxc/mainloop.h @@ -9,24 +9,55 @@ #include "list.h" #include "memory_utils.h" +#ifdef HAVE_LIBURING +#include +#endif + #define LXC_MAINLOOP_ERROR -1 #define LXC_MAINLOOP_CONTINUE 0 #define LXC_MAINLOOP_CLOSE 1 +#define LXC_MAINLOOP_DISARM 2 + +typedef enum { + LXC_MAINLOOP_EPOLL = 1, + LXC_MAINLOOP_IO_URING = 2, +} async_descr_t; struct lxc_async_descr { - int epfd; + async_descr_t type; + union { + int epfd; +#ifdef HAVE_LIBURING + struct io_uring *ring; +#endif + }; struct lxc_list handlers; }; +static inline int default_cleanup_handler(int fd, void *data) +{ + return 0; +} + typedef int (*lxc_mainloop_callback_t)(int fd, uint32_t event, void *data, struct lxc_async_descr *descr); +typedef int (*lxc_mainloop_cleanup_t)(int fd, void *data); + __hidden extern int lxc_mainloop(struct lxc_async_descr *descr, int timeout_ms); __hidden extern int lxc_mainloop_add_handler_events(struct lxc_async_descr *descr, int fd, int events, - lxc_mainloop_callback_t callback, void *data); + lxc_mainloop_callback_t callback, + lxc_mainloop_cleanup_t cleanup, + void *data, const char *handler_name); __hidden extern int lxc_mainloop_add_handler(struct lxc_async_descr *descr, int fd, - lxc_mainloop_callback_t callback, void *data); + lxc_mainloop_callback_t callback, + lxc_mainloop_cleanup_t cleanup, + void *data, const char *handler_name); +__hidden extern int lxc_mainloop_add_oneshot_handler(struct lxc_async_descr *descr, int fd, + lxc_mainloop_callback_t callback, + lxc_mainloop_cleanup_t cleanup, + void *data, const char *handler_name); __hidden extern int lxc_mainloop_del_handler(struct lxc_async_descr *descr, int fd); diff --git a/src/lxc/seccomp.c b/src/lxc/seccomp.c index 3bfd8f1c9..5ef1b0b5b 100644 --- a/src/lxc/seccomp.c +++ b/src/lxc/seccomp.c @@ -1358,6 +1358,23 @@ static void seccomp_notify_default_answer(int fd, struct seccomp_notif *req, } #endif +int seccomp_notify_cleanup_handler(int fd, void *data) +{ + struct lxc_handler *hdlr = data; + struct lxc_conf *conf = hdlr->conf; + + /* TODO: Make sure that we don't need to free any memory in here. */ + if (fd == conf->seccomp.notifier.notify_fd) + fd = move_fd(conf->seccomp.notifier.notify_fd); + + /* + * If this isn't the main notify_fd it means that someone registered a + * seccomp notify handler through the command socket (e.g. for attach) + * and so we won't touch the container's config. + */ + return 0; +} + int seccomp_notify_handler(int fd, uint32_t events, void *data, struct lxc_async_descr *descr) { @@ -1384,11 +1401,8 @@ int seccomp_notify_handler(int fd, uint32_t events, void *data, char *cookie = conf->seccomp.notifier.cookie; __u64 req_id; - if (events & EPOLLHUP) { - lxc_mainloop_del_handler(descr, fd); - close(fd); - return log_trace(0, "Removing seccomp notifier fd %d", fd); - } + if (events & EPOLLHUP) + return log_trace(LXC_MAINLOOP_DISARM, "Removing seccomp notifier fd %d", fd); memset(req, 0, conf->seccomp.notifier.sizes.seccomp_notif); ret = seccomp_notify_receive(fd, req); @@ -1604,9 +1618,11 @@ int lxc_seccomp_setup_proxy(struct lxc_seccomp *seccomp, return -1; } - ret = lxc_mainloop_add_handler(descr, - seccomp->notifier.notify_fd, - seccomp_notify_handler, handler); + ret = lxc_mainloop_add_handler(descr, seccomp->notifier.notify_fd, + seccomp_notify_handler, + seccomp_notify_cleanup_handler, + handler, + "seccomp_notify_handler"); if (ret < 0) { ERROR("Failed to add seccomp notify handler for %d to mainloop", notify_fd); diff --git a/src/lxc/start.c b/src/lxc/start.c index 8e062584b..247dd1a18 100644 --- a/src/lxc/start.c +++ b/src/lxc/start.c @@ -398,6 +398,9 @@ static int signal_handler(int fd, uint32_t events, void *data, if (ret == 0 && info.si_pid == hdlr->pid) hdlr->init_died = true; + TRACE("Received signal ssi_signo(%d) for ssi_pid(%d), si_signo(%d), si_pid(%d)", + siginfo.ssi_signo, siginfo.ssi_pid, info.si_signo, info.si_pid); + /* Try to figure out a reasonable exit status to report. */ if (hdlr->init_died) { switch (info.si_code) { @@ -576,12 +579,11 @@ int lxc_set_state(const char *name, struct lxc_handler *handler, int lxc_poll(const char *name, struct lxc_handler *handler) { int ret; - bool has_console = true; + struct lxc_terminal *console = &handler->conf->console; struct lxc_async_descr descr, descr_console; - if (handler->conf->console.path && - strequal(handler->conf->console.path, "none")) - has_console = false; + if (!wants_console(console)) + console = NULL; ret = lxc_mainloop_open(&descr); if (ret < 0) { @@ -589,7 +591,7 @@ int lxc_poll(const char *name, struct lxc_handler *handler) goto out_sigfd; } - if (has_console) { + if (console) { ret = lxc_mainloop_open(&descr_console); if (ret < 0) { ERROR("Failed to create console mainloop"); @@ -597,7 +599,10 @@ int lxc_poll(const char *name, struct lxc_handler *handler) } } - ret = lxc_mainloop_add_handler(&descr, handler->sigfd, signal_handler, handler); + ret = lxc_mainloop_add_handler(&descr, handler->sigfd, + signal_handler, + default_cleanup_handler, + handler, "signal_handler"); if (ret < 0) { ERROR("Failed to add signal handler for %d to mainloop", handler->sigfd); goto out_mainloop_console; @@ -609,22 +614,12 @@ int lxc_poll(const char *name, struct lxc_handler *handler) goto out_mainloop_console; } - if (has_console) { - struct lxc_terminal *console = &handler->conf->console; - + if (console) { ret = lxc_terminal_mainloop_add(&descr, console); if (ret < 0) { ERROR("Failed to add console handlers to mainloop"); goto out_mainloop_console; } - - ret = lxc_terminal_mainloop_add(&descr_console, console); - if (ret < 0) { - ERROR("Failed to add console handlers to console mainloop"); - goto out_mainloop_console; - } - - handler->conf->console.descr = &descr; } ret = lxc_cmd_mainloop_add(name, &descr, handler); @@ -640,11 +635,14 @@ int lxc_poll(const char *name, struct lxc_handler *handler) if (ret < 0 || !handler->init_died) goto out_mainloop_console; - if (has_console) - ret = lxc_mainloop(&descr_console, 0); + if (console) { + ret = lxc_terminal_mainloop_add(&descr_console, console); + if (ret == 0) + ret = lxc_mainloop(&descr_console, 0); + } out_mainloop_console: - if (has_console) { + if (console) { lxc_mainloop_close(&descr_console); TRACE("Closed console mainloop"); } diff --git a/src/lxc/syscall_wrappers.h b/src/lxc/syscall_wrappers.h index f50875cc1..49c9e0502 100644 --- a/src/lxc/syscall_wrappers.h +++ b/src/lxc/syscall_wrappers.h @@ -27,10 +27,6 @@ #include #endif -#ifdef HAVE_STRUCT_OPEN_HOW -#include -#endif - #if HAVE_SYS_PERSONALITY_H #include #endif @@ -299,11 +295,7 @@ struct lxc_open_how { #ifndef HAVE_OPENAT2 static inline int openat2(int dfd, const char *filename, struct lxc_open_how *how, size_t size) { - /* When struct open_how is updated we should update lxc as well. */ -#ifdef HAVE_STRUCT_OPEN_HOW - BUILD_BUG_ON(sizeof(struct lxc_open_how) != sizeof(struct open_how)); -#endif - return syscall(__NR_openat2, dfd, filename, (struct open_how *)how, size); + return syscall(__NR_openat2, dfd, filename, how, size); } #endif /* HAVE_OPENAT2 */ diff --git a/src/lxc/terminal.c b/src/lxc/terminal.c index b2c5f4633..73e670e8f 100644 --- a/src/lxc/terminal.c +++ b/src/lxc/terminal.c @@ -328,48 +328,27 @@ static int lxc_terminal_write_log_file(struct lxc_terminal *terminal, char *buf, return bytes_read; } -int lxc_terminal_io_cb(int fd, uint32_t events, void *data, - struct lxc_async_descr *descr) +static int lxc_terminal_ptx_io(struct lxc_terminal *terminal) { - struct lxc_terminal *terminal = data; char buf[LXC_TERMINAL_BUFFER_SIZE]; int r, w, w_log, w_rbuf; - w = r = lxc_read_nointr(fd, buf, sizeof(buf)); - if (r <= 0) { - INFO("Terminal client on fd %d has exited", fd); - lxc_mainloop_del_handler(descr, fd); - - if (fd == terminal->ptx) { - terminal->ptx = -EBADF; - } else if (fd == terminal->peer) { - lxc_terminal_signal_fini(terminal); - terminal->peer = -EBADF; - } else { - ERROR("Handler received unexpected file descriptor"); - } - close(fd); + w = r = lxc_read_nointr(terminal->ptx, buf, sizeof(buf)); + if (r <= 0) + return -1; - return LXC_MAINLOOP_CLOSE; - } + w_rbuf = w_log = 0; + /* write to peer first */ + if (terminal->peer >= 0) + w = lxc_write_nointr(terminal->peer, buf, r); - if (fd == terminal->peer) - w = lxc_write_nointr(terminal->ptx, buf, r); + /* write to terminal ringbuffer */ + if (terminal->buffer_size > 0) + w_rbuf = lxc_ringbuf_write(&terminal->ringbuf, buf, r); - w_rbuf = w_log = 0; - if (fd == terminal->ptx) { - /* write to peer first */ - if (terminal->peer >= 0) - w = lxc_write_nointr(terminal->peer, buf, r); - - /* write to terminal ringbuffer */ - if (terminal->buffer_size > 0) - w_rbuf = lxc_ringbuf_write(&terminal->ringbuf, buf, r); - - /* write to terminal log */ - if (terminal->log_fd >= 0) - w_log = lxc_terminal_write_log_file(terminal, buf, r); - } + /* write to terminal log */ + if (terminal->log_fd >= 0) + w_log = lxc_terminal_write_log_file(terminal, buf, r); if (w != r) WARN("Short write on terminal r:%d != w:%d", r, w); @@ -382,6 +361,52 @@ int lxc_terminal_io_cb(int fd, uint32_t events, void *data, if (w_log < 0) TRACE("Failed to write %d bytes to terminal log", r); + return 0; +} + +static int lxc_terminal_peer_io(struct lxc_terminal *terminal) +{ + char buf[LXC_TERMINAL_BUFFER_SIZE]; + int r, w; + + w = r = lxc_read_nointr(terminal->peer, buf, sizeof(buf)); + if (r <= 0) + return -1; + + w = lxc_write_nointr(terminal->ptx, buf, r); + if (w != r) + WARN("Short write on terminal r:%d != w:%d", r, w); + + return 0; +} + +static int lxc_terminal_ptx_io_handler(int fd, uint32_t events, void *data, + struct lxc_async_descr *descr) +{ + struct lxc_terminal *terminal = data; + int ret; + + ret = lxc_terminal_ptx_io(data); + if (ret < 0) + return log_info(LXC_MAINLOOP_CLOSE, + "Terminal client on fd %d has exited", + terminal->ptx); + + return LXC_MAINLOOP_CONTINUE; +} + +static int lxc_terminal_peer_io_handler(int fd, uint32_t events, void *data, + struct lxc_async_descr *descr) +{ + struct lxc_terminal *terminal = data; + int ret; + + ret = lxc_terminal_peer_io(data); + if (ret < 0) + return log_info(LXC_MAINLOOP_CLOSE, + "Terminal client on fd %d has exited", + terminal->peer); + return LXC_MAINLOOP_CONTINUE; } @@ -391,7 +416,9 @@ static int lxc_terminal_mainloop_add_peer(struct lxc_terminal *terminal) if (terminal->peer >= 0) { ret = lxc_mainloop_add_handler(terminal->descr, terminal->peer, - lxc_terminal_io_cb, terminal); + lxc_terminal_peer_io_handler, + default_cleanup_handler, + terminal, "lxc_terminal_peer_io_handler"); if (ret < 0) { WARN("Failed to add terminal peer handler to mainloop"); return -1; @@ -401,8 +428,12 @@ static int lxc_terminal_mainloop_add_peer(struct lxc_terminal *terminal) if (!terminal->tty_state || terminal->tty_state->sigfd < 0) return 0; - ret = lxc_mainloop_add_handler(terminal->descr, terminal->tty_state->sigfd, - lxc_terminal_signalfd_cb, terminal->tty_state); + ret = lxc_mainloop_add_handler(terminal->descr, + terminal->tty_state->sigfd, + lxc_terminal_signalfd_cb, + default_cleanup_handler, + terminal->tty_state, + "lxc_terminal_signalfd_cb"); if (ret < 0) { WARN("Failed to add signal handler to mainloop"); return -1; @@ -422,10 +453,11 @@ int lxc_terminal_mainloop_add(struct lxc_async_descr *descr, } ret = lxc_mainloop_add_handler(descr, terminal->ptx, - lxc_terminal_io_cb, terminal); + lxc_terminal_ptx_io_handler, + default_cleanup_handler, + terminal, "lxc_terminal_ptx_io_handler"); if (ret < 0) { - ERROR("Failed to add handler for terminal ptx fd %d to " - "mainloop", terminal->ptx); + ERROR("Failed to add handler for terminal ptx fd %d to mainloop", terminal->ptx); return -1; } @@ -1221,7 +1253,9 @@ int lxc_console(struct lxc_container *c, int ttynum, if (ts->sigfd != -1) { ret = lxc_mainloop_add_handler(&descr, ts->sigfd, - lxc_terminal_signalfd_cb, ts); + lxc_terminal_signalfd_cb, + default_cleanup_handler, + ts, "lxc_terminal_signalfd_cb"); if (ret < 0) { ERROR("Failed to add signal handler to mainloop"); goto close_mainloop; @@ -1229,14 +1263,18 @@ int lxc_console(struct lxc_container *c, int ttynum, } ret = lxc_mainloop_add_handler(&descr, ts->stdinfd, - lxc_terminal_stdin_cb, ts); + lxc_terminal_stdin_cb, + default_cleanup_handler, + ts, "lxc_terminal_stdin_cb"); if (ret < 0) { ERROR("Failed to add stdin handler"); goto close_mainloop; } ret = lxc_mainloop_add_handler(&descr, ts->ptxfd, - lxc_terminal_ptx_cb, ts); + lxc_terminal_ptx_cb, + default_cleanup_handler, + ts, "lxc_terminal_ptx_cb"); if (ret < 0) { ERROR("Failed to add ptx handler"); goto close_mainloop; diff --git a/src/lxc/terminal.h b/src/lxc/terminal.h index 8c7742f81..816b73630 100644 --- a/src/lxc/terminal.h +++ b/src/lxc/terminal.h @@ -244,8 +244,6 @@ __hidden extern int lxc_terminal_signalfd_cb(int fd, uint32_t events, void *cbda __hidden extern int lxc_terminal_write_ringbuffer(struct lxc_terminal *terminal); __hidden extern int lxc_terminal_create_log_file(struct lxc_terminal *terminal); -__hidden extern int lxc_terminal_io_cb(int fd, uint32_t events, void *data, - struct lxc_async_descr *descr); __hidden extern int lxc_make_controlling_terminal(int fd); __hidden extern int lxc_terminal_prepare_login(int fd); diff --git a/src/lxc/tools/lxc_top.c b/src/lxc/tools/lxc_top.c index 78d872371..9cf2cbbbd 100644 --- a/src/lxc/tools/lxc_top.c +++ b/src/lxc/tools/lxc_top.c @@ -594,7 +594,10 @@ int main(int argc, char *argv[]) goto out; } - ret = lxc_mainloop_add_handler(&descr, 0, stdin_handler, &in_char); + ret = lxc_mainloop_add_handler(&descr, 0, + stdin_handler, + default_cleanup_handler, + &in_char, "stdin_handler"); if (ret) { fprintf(stderr, "Failed to add stdin handler\n"); ret = EXIT_FAILURE; diff --git a/src/tests/Makefile.am b/src/tests/Makefile.am index f22394a2d..75777d11b 100644 --- a/src/tests/Makefile.am +++ b/src/tests/Makefile.am @@ -5,7 +5,8 @@ LDADD = ../lxc/liblxc.la \ @OPENSSL_LIBS@ \ @SECCOMP_LIBS@ \ @SELINUX_LIBS@ \ - @DLOG_LIBS@ + @DLOG_LIBS@ \ + @LIBURING_LIBS@ LSM_SOURCES = ../lxc/lsm/lsm.c \ ../lxc/lsm/lsm.h \