blob: 20fe113c655b77427556ad33b99fbc70ab4e0f16 [file] [log] [blame]
/*
*
* 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 <config.h>
#endif
#define _GNU_SOURCE
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <stddef.h>
#include <limits.h>
#include <signal.h>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <sys/un.h>
#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 (L_WARN_ON((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;
}