/* * * Embedded Linux library * * Copyright (C) 2011-2014 Intel Corporation. All rights reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include "signal.h" #include "queue.h" #include "log.h" #include "useful.h" #include "main.h" #include "main-private.h" #include "private.h" #include "timeout.h" /** * SECTION:main * @short_description: Main loop handling * * Main loop handling */ #define MAX_EPOLL_EVENTS 10 #define IDLE_FLAG_DISPATCHING 1 #define IDLE_FLAG_DESTROYED 2 #define WATCH_FLAG_DISPATCHING 1 #define WATCH_FLAG_DESTROYED 2 #define WATCHDOG_TRIGGER_FREQ 2 static int epoll_fd; static bool epoll_running; static bool epoll_terminate; static int idle_id; static int notify_fd; static struct l_timeout *watchdog; static struct l_queue *idle_list; struct watch_data { int fd; uint32_t events; uint32_t flags; watch_event_cb_t callback; watch_destroy_cb_t destroy; void *user_data; }; #define DEFAULT_WATCH_ENTRIES 128 static unsigned int watch_entries; static struct watch_data **watch_list; struct idle_data { idle_event_cb_t callback; idle_destroy_cb_t destroy; void *user_data; uint32_t flags; int id; }; static inline bool __attribute__ ((always_inline)) create_epoll(void) { unsigned int i; epoll_fd = epoll_create1(EPOLL_CLOEXEC); if (epoll_fd < 0) { epoll_fd = 0; return false; } watch_list = malloc(DEFAULT_WATCH_ENTRIES * sizeof(void *)); if (!watch_list) goto close_epoll; idle_list = l_queue_new(); idle_id = 0; watch_entries = DEFAULT_WATCH_ENTRIES; for (i = 0; i < watch_entries; i++) watch_list[i] = NULL; return true; close_epoll: close(epoll_fd); epoll_fd = 0; return false; } int watch_add(int fd, uint32_t events, watch_event_cb_t callback, void *user_data, watch_destroy_cb_t destroy) { struct watch_data *data; struct epoll_event ev; int err; if (unlikely(fd < 0 || !callback)) return -EINVAL; if (!epoll_fd) return -EIO; if ((unsigned int) fd > watch_entries - 1) return -ERANGE; data = l_new(struct watch_data, 1); data->fd = fd; data->events = events; data->flags = 0; data->callback = callback; data->destroy = destroy; data->user_data = user_data; memset(&ev, 0, sizeof(ev)); ev.events = events; ev.data.ptr = data; err = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, data->fd, &ev); if (err < 0) { l_free(data); return -errno; } watch_list[fd] = data; return 0; } int watch_modify(int fd, uint32_t events, bool force) { struct watch_data *data; struct epoll_event ev; int err; if (unlikely(fd < 0)) return -EINVAL; if ((unsigned int) fd > watch_entries - 1) return -ERANGE; data = watch_list[fd]; if (!data) return -ENXIO; if (data->events == events && !force) return 0; memset(&ev, 0, sizeof(ev)); ev.events = events; ev.data.ptr = data; err = epoll_ctl(epoll_fd, EPOLL_CTL_MOD, data->fd, &ev); if (err < 0) return -errno; data->events = events; return 0; } int watch_clear(int fd) { struct watch_data *data; if (unlikely(fd < 0)) return -EINVAL; if ((unsigned int) fd > watch_entries - 1) return -ERANGE; data = watch_list[fd]; if (!data) return -ENXIO; watch_list[fd] = NULL; if (data->destroy) data->destroy(data->user_data); if (data->flags & WATCH_FLAG_DISPATCHING) data->flags |= WATCH_FLAG_DESTROYED; else l_free(data); return 0; } int watch_remove(int fd, bool epoll_del) { int err = watch_clear(fd); if (err < 0) return err; if (!epoll_del) goto done; err = epoll_ctl(epoll_fd, EPOLL_CTL_DEL, fd, NULL); if (err < 0) return -errno; done: return err; } static bool idle_remove_by_id(void *data, void *user_data) { struct idle_data *idle = data; int id = L_PTR_TO_INT(user_data); if (idle->id != id) return false; if (idle->destroy) idle->destroy(idle->user_data); if (idle->flags & IDLE_FLAG_DISPATCHING) { idle->flags |= IDLE_FLAG_DESTROYED; return false; } l_free(idle); return true; } static bool idle_prune(void *data, void *user_data) { struct idle_data *idle = data; if ((idle->flags & IDLE_FLAG_DESTROYED) == 0) return false; l_free(idle); return true; } int idle_add(idle_event_cb_t callback, void *user_data, uint32_t flags, idle_destroy_cb_t destroy) { struct idle_data *data; if (unlikely(!callback)) return -EINVAL; if (!epoll_fd) return -EIO; data = l_new(struct idle_data, 1); data->callback = callback; data->destroy = destroy; data->user_data = user_data; data->flags = flags; if (!l_queue_push_tail(idle_list, data)) { l_free(data); return -ENOMEM; } data->id = idle_id++; if (idle_id == INT_MAX) idle_id = 0; return data->id; } void idle_remove(int id) { l_queue_foreach_remove(idle_list, idle_remove_by_id, L_INT_TO_PTR(id)); } static void idle_destroy(void *data) { struct idle_data *idle = data; if (!(idle->flags & IDLE_FLAG_NO_WARN_DANGLING)) l_error("Dangling idle descriptor %p, %d found", data, idle->id); if (idle->destroy) idle->destroy(idle->user_data); l_free(idle); } static void idle_dispatch(void *data, void *user_data) { struct idle_data *idle = data; if (!idle->callback) return; idle->flags |= IDLE_FLAG_DISPATCHING; idle->callback(idle->user_data); idle->flags &= ~IDLE_FLAG_DISPATCHING; } static int sd_notify(const char *state) { int err; if (notify_fd <= 0) return -ENOTCONN; err = send(notify_fd, state, strlen(state), MSG_NOSIGNAL); if (err < 0) return -errno; return 0; } static void watchdog_callback(struct l_timeout *timeout, void *user_data) { int msec = L_PTR_TO_INT(user_data); sd_notify("WATCHDOG=1"); l_timeout_modify_ms(timeout, msec); } static void create_sd_notify_socket(void) { const char *sock; struct sockaddr_un addr; const char *watchdog_usec; int msec; /* check if NOTIFY_SOCKET has been set */ sock = getenv("NOTIFY_SOCKET"); if (!sock) return; /* check for abstract socket or absolute path */ if (sock[0] != '@' && sock[0] != '/') return; notify_fd = socket(AF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC, 0); if (notify_fd < 0) { notify_fd = 0; return; } memset(&addr, 0, sizeof(addr)); addr.sun_family = AF_UNIX; strncpy(addr.sun_path, sock, sizeof(addr.sun_path) - 1); if (addr.sun_path[0] == '@') addr.sun_path[0] = '\0'; if (bind(notify_fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) { close(notify_fd); notify_fd = 0; return; } watchdog_usec = getenv("WATCHDOG_USEC"); if (!watchdog_usec) return; msec = atoi(watchdog_usec) / 1000; if (msec < WATCHDOG_TRIGGER_FREQ) return; msec /= WATCHDOG_TRIGGER_FREQ; watchdog = l_timeout_create_ms(msec, watchdog_callback, L_INT_TO_PTR(msec), NULL); } /** * l_main_init: * * Initialize the main loop. This must be called before l_main_run() * and any other function that directly or indirectly sets up an idle * or watch. A safe rule-of-thumb is to call it before any function * prefixed with "l_". * * Returns: true if initialization was successful, false otherwise. **/ LIB_EXPORT bool l_main_init(void) { if (unlikely(epoll_running)) return false; if (!create_epoll()) return false; create_sd_notify_socket(); epoll_terminate = false; return true; } /** * l_main_prepare: * * Prepare the iteration of the main loop * * Returns: The timeout to use. This will be 0 if idle-event processing is * currently pending, or -1 otherwise. This value can be used to pass to * l_main_iterate. */ LIB_EXPORT int l_main_prepare(void) { return l_queue_isempty(idle_list) ? -1 : 0; } /** * l_main_iterate: * * Run one iteration of the main event loop */ LIB_EXPORT void l_main_iterate(int timeout) { struct epoll_event events[MAX_EPOLL_EVENTS]; struct watch_data *data; int n, nfds; nfds = epoll_wait(epoll_fd, events, MAX_EPOLL_EVENTS, timeout); for (n = 0; n < nfds; n++) { data = events[n].data.ptr; data->flags |= WATCH_FLAG_DISPATCHING; } for (n = 0; n < nfds; n++) { data = events[n].data.ptr; if (data->flags & WATCH_FLAG_DESTROYED) continue; data->callback(data->fd, events[n].events, data->user_data); } for (n = 0; n < nfds; n++) { data = events[n].data.ptr; if (data->flags & WATCH_FLAG_DESTROYED) l_free(data); else data->flags = 0; } l_queue_foreach(idle_list, idle_dispatch, NULL); l_queue_foreach_remove(idle_list, idle_prune, NULL); } /** * l_main_run: * * Run the main loop * * The loop may be restarted by invoking this function after a * previous invocation returns, provided that l_main_exit() has not * been called first. * * Returns: #EXIT_SUCCESS after successful execution or #EXIT_FAILURE in * case of failure **/ LIB_EXPORT int l_main_run(void) { int timeout; /* Has l_main_init() been called? */ if (unlikely(!epoll_fd)) return EXIT_FAILURE; if (unlikely(epoll_running)) return EXIT_FAILURE; epoll_running = true; for (;;) { if (epoll_terminate) break; timeout = l_main_prepare(); l_main_iterate(timeout); } epoll_running = false; if (notify_fd) { close(notify_fd); notify_fd = 0; l_timeout_remove(watchdog); watchdog = NULL; } return EXIT_SUCCESS; } /** * l_main_exit: * * Clean up after main loop completes. * **/ LIB_EXPORT bool l_main_exit(void) { unsigned int i; if (epoll_running) { l_error("Cleanup attempted on running main loop"); return false; } for (i = 0; i < watch_entries; i++) { struct watch_data *data = watch_list[i]; if (!data) continue; epoll_ctl(epoll_fd, EPOLL_CTL_DEL, data->fd, NULL); if (data->destroy) data->destroy(data->user_data); else l_error("Dangling file descriptor %d found", data->fd); l_free(data); } watch_entries = 0; free(watch_list); watch_list = NULL; l_queue_destroy(idle_list, idle_destroy); idle_list = NULL; close(epoll_fd); epoll_fd = 0; return true; } /** * l_main_quit: * * Teminate the running main loop * * Returns: #true when terminating the main loop or #false in case of failure **/ LIB_EXPORT bool l_main_quit(void) { if (unlikely(!epoll_running)) return false; epoll_terminate = true; return true; } struct signal_data { l_main_signal_cb_t callback; void *user_data; }; static void sigint_handler(void *user_data) { struct signal_data *data = user_data; if (data->callback) data->callback(SIGINT, data->user_data); } static void sigterm_handler(void *user_data) { struct signal_data *data = user_data; if (data->callback) data->callback(SIGTERM, data->user_data); } /** * l_main_run_with_signal: * * Run the main loop with signal handling for SIGINT and SIGTERM * * Returns: #EXIT_SUCCESS after successful execution or #EXIT_FAILURE in * case of failure **/ LIB_EXPORT int l_main_run_with_signal(l_main_signal_cb_t callback, void *user_data) { struct signal_data *data; struct l_signal *sigint; struct l_signal *sigterm; int result; data = l_new(struct signal_data, 1); data->callback = callback; data->user_data = user_data; sigint = l_signal_create(SIGINT, sigint_handler, data, NULL); sigterm = l_signal_create(SIGTERM, sigterm_handler, data, NULL); result = l_main_run(); l_signal_remove(sigint); l_signal_remove(sigterm); l_free(data); return result; } /** * l_main_get_epoll_fd: * * Can be used to obtain the epoll file descriptor in order to integrate * the ell main event loop with other event loops. * * Returns: epoll file descriptor **/ LIB_EXPORT int l_main_get_epoll_fd(void) { return epoll_fd; }