/*
 * Embedded Linux library
 * Copyright (C) 2011-2014  Intel Corporation
 *
 * SPDX-License-Identifier: LGPL-2.1-or-later
 */

#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 = -1;
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)
		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 = -1;

	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 < 0)
		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 < 0)
		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 < 0))
		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 = -1;

	return true;
}

/**
 * l_main_quit:
 *
 * Terminate 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;
}
