0fa0ad4e17
If the os_time_t variable used for the expiration time (seconds) overflows when the registered timeout value is being added, assume that the event would happen after an infinite time, i.e., would not really happen in practice. This fixes issues with long key timeouts getting converted to immediate expiration due to the overflow.
627 lines
14 KiB
C
627 lines
14 KiB
C
/*
|
|
* Event loop based on select() loop
|
|
* Copyright (c) 2002-2009, Jouni Malinen <j@w1.fi>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
* published by the Free Software Foundation.
|
|
*
|
|
* Alternatively, this software may be distributed under the terms of BSD
|
|
* license.
|
|
*
|
|
* See README and COPYING for more details.
|
|
*/
|
|
|
|
#include "includes.h"
|
|
|
|
#include "common.h"
|
|
#include "trace.h"
|
|
#include "list.h"
|
|
#include "eloop.h"
|
|
|
|
|
|
struct eloop_sock {
|
|
int sock;
|
|
void *eloop_data;
|
|
void *user_data;
|
|
eloop_sock_handler handler;
|
|
WPA_TRACE_REF(eloop);
|
|
WPA_TRACE_REF(user);
|
|
WPA_TRACE_INFO
|
|
};
|
|
|
|
struct eloop_timeout {
|
|
struct dl_list list;
|
|
struct os_time time;
|
|
void *eloop_data;
|
|
void *user_data;
|
|
eloop_timeout_handler handler;
|
|
WPA_TRACE_REF(eloop);
|
|
WPA_TRACE_REF(user);
|
|
WPA_TRACE_INFO
|
|
};
|
|
|
|
struct eloop_signal {
|
|
int sig;
|
|
void *user_data;
|
|
eloop_signal_handler handler;
|
|
int signaled;
|
|
};
|
|
|
|
struct eloop_sock_table {
|
|
int count;
|
|
struct eloop_sock *table;
|
|
int changed;
|
|
};
|
|
|
|
struct eloop_data {
|
|
int max_sock;
|
|
|
|
struct eloop_sock_table readers;
|
|
struct eloop_sock_table writers;
|
|
struct eloop_sock_table exceptions;
|
|
|
|
struct dl_list timeout;
|
|
|
|
int signal_count;
|
|
struct eloop_signal *signals;
|
|
int signaled;
|
|
int pending_terminate;
|
|
|
|
int terminate;
|
|
int reader_table_changed;
|
|
};
|
|
|
|
static struct eloop_data eloop;
|
|
|
|
|
|
#ifdef WPA_TRACE
|
|
|
|
static void eloop_sigsegv_handler(int sig)
|
|
{
|
|
wpa_trace_show("eloop SIGSEGV");
|
|
abort();
|
|
}
|
|
|
|
static void eloop_trace_sock_add_ref(struct eloop_sock_table *table)
|
|
{
|
|
int i;
|
|
if (table == NULL || table->table == NULL)
|
|
return;
|
|
for (i = 0; i < table->count; i++) {
|
|
wpa_trace_add_ref(&table->table[i], eloop,
|
|
table->table[i].eloop_data);
|
|
wpa_trace_add_ref(&table->table[i], user,
|
|
table->table[i].user_data);
|
|
}
|
|
}
|
|
|
|
|
|
static void eloop_trace_sock_remove_ref(struct eloop_sock_table *table)
|
|
{
|
|
int i;
|
|
if (table == NULL || table->table == NULL)
|
|
return;
|
|
for (i = 0; i < table->count; i++) {
|
|
wpa_trace_remove_ref(&table->table[i], eloop,
|
|
table->table[i].eloop_data);
|
|
wpa_trace_remove_ref(&table->table[i], user,
|
|
table->table[i].user_data);
|
|
}
|
|
}
|
|
|
|
#else /* WPA_TRACE */
|
|
|
|
#define eloop_trace_sock_add_ref(table) do { } while (0)
|
|
#define eloop_trace_sock_remove_ref(table) do { } while (0)
|
|
|
|
#endif /* WPA_TRACE */
|
|
|
|
|
|
int eloop_init(void)
|
|
{
|
|
os_memset(&eloop, 0, sizeof(eloop));
|
|
dl_list_init(&eloop.timeout);
|
|
#ifdef WPA_TRACE
|
|
signal(SIGSEGV, eloop_sigsegv_handler);
|
|
#endif /* WPA_TRACE */
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int eloop_sock_table_add_sock(struct eloop_sock_table *table,
|
|
int sock, eloop_sock_handler handler,
|
|
void *eloop_data, void *user_data)
|
|
{
|
|
struct eloop_sock *tmp;
|
|
|
|
if (table == NULL)
|
|
return -1;
|
|
|
|
eloop_trace_sock_remove_ref(table);
|
|
tmp = (struct eloop_sock *)
|
|
os_realloc(table->table,
|
|
(table->count + 1) * sizeof(struct eloop_sock));
|
|
if (tmp == NULL)
|
|
return -1;
|
|
|
|
tmp[table->count].sock = sock;
|
|
tmp[table->count].eloop_data = eloop_data;
|
|
tmp[table->count].user_data = user_data;
|
|
tmp[table->count].handler = handler;
|
|
wpa_trace_record(&tmp[table->count]);
|
|
table->count++;
|
|
table->table = tmp;
|
|
if (sock > eloop.max_sock)
|
|
eloop.max_sock = sock;
|
|
table->changed = 1;
|
|
eloop_trace_sock_add_ref(table);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static void eloop_sock_table_remove_sock(struct eloop_sock_table *table,
|
|
int sock)
|
|
{
|
|
int i;
|
|
|
|
if (table == NULL || table->table == NULL || table->count == 0)
|
|
return;
|
|
|
|
for (i = 0; i < table->count; i++) {
|
|
if (table->table[i].sock == sock)
|
|
break;
|
|
}
|
|
if (i == table->count)
|
|
return;
|
|
eloop_trace_sock_remove_ref(table);
|
|
if (i != table->count - 1) {
|
|
os_memmove(&table->table[i], &table->table[i + 1],
|
|
(table->count - i - 1) *
|
|
sizeof(struct eloop_sock));
|
|
}
|
|
table->count--;
|
|
table->changed = 1;
|
|
eloop_trace_sock_add_ref(table);
|
|
}
|
|
|
|
|
|
static void eloop_sock_table_set_fds(struct eloop_sock_table *table,
|
|
fd_set *fds)
|
|
{
|
|
int i;
|
|
|
|
FD_ZERO(fds);
|
|
|
|
if (table->table == NULL)
|
|
return;
|
|
|
|
for (i = 0; i < table->count; i++)
|
|
FD_SET(table->table[i].sock, fds);
|
|
}
|
|
|
|
|
|
static void eloop_sock_table_dispatch(struct eloop_sock_table *table,
|
|
fd_set *fds)
|
|
{
|
|
int i;
|
|
|
|
if (table == NULL || table->table == NULL)
|
|
return;
|
|
|
|
table->changed = 0;
|
|
for (i = 0; i < table->count; i++) {
|
|
if (FD_ISSET(table->table[i].sock, fds)) {
|
|
table->table[i].handler(table->table[i].sock,
|
|
table->table[i].eloop_data,
|
|
table->table[i].user_data);
|
|
if (table->changed)
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static void eloop_sock_table_destroy(struct eloop_sock_table *table)
|
|
{
|
|
if (table) {
|
|
int i;
|
|
for (i = 0; i < table->count && table->table; i++) {
|
|
wpa_printf(MSG_INFO, "ELOOP: remaining socket: "
|
|
"sock=%d eloop_data=%p user_data=%p "
|
|
"handler=%p",
|
|
table->table[i].sock,
|
|
table->table[i].eloop_data,
|
|
table->table[i].user_data,
|
|
table->table[i].handler);
|
|
wpa_trace_dump_funcname("eloop unregistered socket "
|
|
"handler",
|
|
table->table[i].handler);
|
|
wpa_trace_dump("eloop sock", &table->table[i]);
|
|
}
|
|
os_free(table->table);
|
|
}
|
|
}
|
|
|
|
|
|
int eloop_register_read_sock(int sock, eloop_sock_handler handler,
|
|
void *eloop_data, void *user_data)
|
|
{
|
|
return eloop_register_sock(sock, EVENT_TYPE_READ, handler,
|
|
eloop_data, user_data);
|
|
}
|
|
|
|
|
|
void eloop_unregister_read_sock(int sock)
|
|
{
|
|
eloop_unregister_sock(sock, EVENT_TYPE_READ);
|
|
}
|
|
|
|
|
|
static struct eloop_sock_table *eloop_get_sock_table(eloop_event_type type)
|
|
{
|
|
switch (type) {
|
|
case EVENT_TYPE_READ:
|
|
return &eloop.readers;
|
|
case EVENT_TYPE_WRITE:
|
|
return &eloop.writers;
|
|
case EVENT_TYPE_EXCEPTION:
|
|
return &eloop.exceptions;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
int eloop_register_sock(int sock, eloop_event_type type,
|
|
eloop_sock_handler handler,
|
|
void *eloop_data, void *user_data)
|
|
{
|
|
struct eloop_sock_table *table;
|
|
|
|
table = eloop_get_sock_table(type);
|
|
return eloop_sock_table_add_sock(table, sock, handler,
|
|
eloop_data, user_data);
|
|
}
|
|
|
|
|
|
void eloop_unregister_sock(int sock, eloop_event_type type)
|
|
{
|
|
struct eloop_sock_table *table;
|
|
|
|
table = eloop_get_sock_table(type);
|
|
eloop_sock_table_remove_sock(table, sock);
|
|
}
|
|
|
|
|
|
int eloop_register_timeout(unsigned int secs, unsigned int usecs,
|
|
eloop_timeout_handler handler,
|
|
void *eloop_data, void *user_data)
|
|
{
|
|
struct eloop_timeout *timeout, *tmp;
|
|
os_time_t now_sec;
|
|
|
|
timeout = os_zalloc(sizeof(*timeout));
|
|
if (timeout == NULL)
|
|
return -1;
|
|
if (os_get_time(&timeout->time) < 0) {
|
|
os_free(timeout);
|
|
return -1;
|
|
}
|
|
now_sec = timeout->time.sec;
|
|
timeout->time.sec += secs;
|
|
if (timeout->time.sec < now_sec) {
|
|
/*
|
|
* Integer overflow - assume long enough timeout to be assumed
|
|
* to be infinite, i.e., the timeout would never happen.
|
|
*/
|
|
wpa_printf(MSG_DEBUG, "ELOOP: Too long timeout (secs=%u) to "
|
|
"ever happen - ignore it", secs);
|
|
os_free(timeout);
|
|
return 0;
|
|
}
|
|
timeout->time.usec += usecs;
|
|
while (timeout->time.usec >= 1000000) {
|
|
timeout->time.sec++;
|
|
timeout->time.usec -= 1000000;
|
|
}
|
|
timeout->eloop_data = eloop_data;
|
|
timeout->user_data = user_data;
|
|
timeout->handler = handler;
|
|
wpa_trace_add_ref(timeout, eloop, eloop_data);
|
|
wpa_trace_add_ref(timeout, user, user_data);
|
|
wpa_trace_record(timeout);
|
|
|
|
/* Maintain timeouts in order of increasing time */
|
|
dl_list_for_each(tmp, &eloop.timeout, struct eloop_timeout, list) {
|
|
if (os_time_before(&timeout->time, &tmp->time)) {
|
|
dl_list_add(tmp->list.prev, &timeout->list);
|
|
return 0;
|
|
}
|
|
}
|
|
dl_list_add_tail(&eloop.timeout, &timeout->list);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static void eloop_remove_timeout(struct eloop_timeout *timeout)
|
|
{
|
|
dl_list_del(&timeout->list);
|
|
wpa_trace_remove_ref(timeout, eloop, timeout->eloop_data);
|
|
wpa_trace_remove_ref(timeout, user, timeout->user_data);
|
|
os_free(timeout);
|
|
}
|
|
|
|
|
|
int eloop_cancel_timeout(eloop_timeout_handler handler,
|
|
void *eloop_data, void *user_data)
|
|
{
|
|
struct eloop_timeout *timeout, *prev;
|
|
int removed = 0;
|
|
|
|
dl_list_for_each_safe(timeout, prev, &eloop.timeout,
|
|
struct eloop_timeout, list) {
|
|
if (timeout->handler == handler &&
|
|
(timeout->eloop_data == eloop_data ||
|
|
eloop_data == ELOOP_ALL_CTX) &&
|
|
(timeout->user_data == user_data ||
|
|
user_data == ELOOP_ALL_CTX)) {
|
|
eloop_remove_timeout(timeout);
|
|
removed++;
|
|
}
|
|
}
|
|
|
|
return removed;
|
|
}
|
|
|
|
|
|
int eloop_is_timeout_registered(eloop_timeout_handler handler,
|
|
void *eloop_data, void *user_data)
|
|
{
|
|
struct eloop_timeout *tmp;
|
|
|
|
dl_list_for_each(tmp, &eloop.timeout, struct eloop_timeout, list) {
|
|
if (tmp->handler == handler &&
|
|
tmp->eloop_data == eloop_data &&
|
|
tmp->user_data == user_data)
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
#ifndef CONFIG_NATIVE_WINDOWS
|
|
static void eloop_handle_alarm(int sig)
|
|
{
|
|
wpa_printf(MSG_ERROR, "eloop: could not process SIGINT or SIGTERM in "
|
|
"two seconds. Looks like there\n"
|
|
"is a bug that ends up in a busy loop that "
|
|
"prevents clean shutdown.\n"
|
|
"Killing program forcefully.\n");
|
|
exit(1);
|
|
}
|
|
#endif /* CONFIG_NATIVE_WINDOWS */
|
|
|
|
|
|
static void eloop_handle_signal(int sig)
|
|
{
|
|
int i;
|
|
|
|
#ifndef CONFIG_NATIVE_WINDOWS
|
|
if ((sig == SIGINT || sig == SIGTERM) && !eloop.pending_terminate) {
|
|
/* Use SIGALRM to break out from potential busy loops that
|
|
* would not allow the program to be killed. */
|
|
eloop.pending_terminate = 1;
|
|
signal(SIGALRM, eloop_handle_alarm);
|
|
alarm(2);
|
|
}
|
|
#endif /* CONFIG_NATIVE_WINDOWS */
|
|
|
|
eloop.signaled++;
|
|
for (i = 0; i < eloop.signal_count; i++) {
|
|
if (eloop.signals[i].sig == sig) {
|
|
eloop.signals[i].signaled++;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static void eloop_process_pending_signals(void)
|
|
{
|
|
int i;
|
|
|
|
if (eloop.signaled == 0)
|
|
return;
|
|
eloop.signaled = 0;
|
|
|
|
if (eloop.pending_terminate) {
|
|
#ifndef CONFIG_NATIVE_WINDOWS
|
|
alarm(0);
|
|
#endif /* CONFIG_NATIVE_WINDOWS */
|
|
eloop.pending_terminate = 0;
|
|
}
|
|
|
|
for (i = 0; i < eloop.signal_count; i++) {
|
|
if (eloop.signals[i].signaled) {
|
|
eloop.signals[i].signaled = 0;
|
|
eloop.signals[i].handler(eloop.signals[i].sig,
|
|
eloop.signals[i].user_data);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
int eloop_register_signal(int sig, eloop_signal_handler handler,
|
|
void *user_data)
|
|
{
|
|
struct eloop_signal *tmp;
|
|
|
|
tmp = (struct eloop_signal *)
|
|
os_realloc(eloop.signals,
|
|
(eloop.signal_count + 1) *
|
|
sizeof(struct eloop_signal));
|
|
if (tmp == NULL)
|
|
return -1;
|
|
|
|
tmp[eloop.signal_count].sig = sig;
|
|
tmp[eloop.signal_count].user_data = user_data;
|
|
tmp[eloop.signal_count].handler = handler;
|
|
tmp[eloop.signal_count].signaled = 0;
|
|
eloop.signal_count++;
|
|
eloop.signals = tmp;
|
|
signal(sig, eloop_handle_signal);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int eloop_register_signal_terminate(eloop_signal_handler handler,
|
|
void *user_data)
|
|
{
|
|
int ret = eloop_register_signal(SIGINT, handler, user_data);
|
|
if (ret == 0)
|
|
ret = eloop_register_signal(SIGTERM, handler, user_data);
|
|
return ret;
|
|
}
|
|
|
|
|
|
int eloop_register_signal_reconfig(eloop_signal_handler handler,
|
|
void *user_data)
|
|
{
|
|
#ifdef CONFIG_NATIVE_WINDOWS
|
|
return 0;
|
|
#else /* CONFIG_NATIVE_WINDOWS */
|
|
return eloop_register_signal(SIGHUP, handler, user_data);
|
|
#endif /* CONFIG_NATIVE_WINDOWS */
|
|
}
|
|
|
|
|
|
void eloop_run(void)
|
|
{
|
|
fd_set *rfds, *wfds, *efds;
|
|
int res;
|
|
struct timeval _tv;
|
|
struct os_time tv, now;
|
|
|
|
rfds = os_malloc(sizeof(*rfds));
|
|
wfds = os_malloc(sizeof(*wfds));
|
|
efds = os_malloc(sizeof(*efds));
|
|
if (rfds == NULL || wfds == NULL || efds == NULL)
|
|
goto out;
|
|
|
|
while (!eloop.terminate &&
|
|
(!dl_list_empty(&eloop.timeout) || eloop.readers.count > 0 ||
|
|
eloop.writers.count > 0 || eloop.exceptions.count > 0)) {
|
|
struct eloop_timeout *timeout;
|
|
timeout = dl_list_first(&eloop.timeout, struct eloop_timeout,
|
|
list);
|
|
if (timeout) {
|
|
os_get_time(&now);
|
|
if (os_time_before(&now, &timeout->time))
|
|
os_time_sub(&timeout->time, &now, &tv);
|
|
else
|
|
tv.sec = tv.usec = 0;
|
|
_tv.tv_sec = tv.sec;
|
|
_tv.tv_usec = tv.usec;
|
|
}
|
|
|
|
eloop_sock_table_set_fds(&eloop.readers, rfds);
|
|
eloop_sock_table_set_fds(&eloop.writers, wfds);
|
|
eloop_sock_table_set_fds(&eloop.exceptions, efds);
|
|
res = select(eloop.max_sock + 1, rfds, wfds, efds,
|
|
timeout ? &_tv : NULL);
|
|
if (res < 0 && errno != EINTR && errno != 0) {
|
|
perror("select");
|
|
goto out;
|
|
}
|
|
eloop_process_pending_signals();
|
|
|
|
/* check if some registered timeouts have occurred */
|
|
timeout = dl_list_first(&eloop.timeout, struct eloop_timeout,
|
|
list);
|
|
if (timeout) {
|
|
os_get_time(&now);
|
|
if (!os_time_before(&now, &timeout->time)) {
|
|
void *eloop_data = timeout->eloop_data;
|
|
void *user_data = timeout->user_data;
|
|
eloop_timeout_handler handler =
|
|
timeout->handler;
|
|
eloop_remove_timeout(timeout);
|
|
handler(eloop_data, user_data);
|
|
}
|
|
|
|
}
|
|
|
|
if (res <= 0)
|
|
continue;
|
|
|
|
eloop_sock_table_dispatch(&eloop.readers, rfds);
|
|
eloop_sock_table_dispatch(&eloop.writers, wfds);
|
|
eloop_sock_table_dispatch(&eloop.exceptions, efds);
|
|
}
|
|
|
|
out:
|
|
os_free(rfds);
|
|
os_free(wfds);
|
|
os_free(efds);
|
|
}
|
|
|
|
|
|
void eloop_terminate(void)
|
|
{
|
|
eloop.terminate = 1;
|
|
}
|
|
|
|
|
|
void eloop_destroy(void)
|
|
{
|
|
struct eloop_timeout *timeout, *prev;
|
|
struct os_time now;
|
|
|
|
os_get_time(&now);
|
|
dl_list_for_each_safe(timeout, prev, &eloop.timeout,
|
|
struct eloop_timeout, list) {
|
|
int sec, usec;
|
|
sec = timeout->time.sec - now.sec;
|
|
usec = timeout->time.usec - now.usec;
|
|
if (timeout->time.usec < now.usec) {
|
|
sec--;
|
|
usec += 1000000;
|
|
}
|
|
wpa_printf(MSG_INFO, "ELOOP: remaining timeout: %d.%06d "
|
|
"eloop_data=%p user_data=%p handler=%p",
|
|
sec, usec, timeout->eloop_data, timeout->user_data,
|
|
timeout->handler);
|
|
wpa_trace_dump_funcname("eloop unregistered timeout handler",
|
|
timeout->handler);
|
|
wpa_trace_dump("eloop timeout", timeout);
|
|
eloop_remove_timeout(timeout);
|
|
}
|
|
eloop_sock_table_destroy(&eloop.readers);
|
|
eloop_sock_table_destroy(&eloop.writers);
|
|
eloop_sock_table_destroy(&eloop.exceptions);
|
|
os_free(eloop.signals);
|
|
}
|
|
|
|
|
|
int eloop_terminated(void)
|
|
{
|
|
return eloop.terminate;
|
|
}
|
|
|
|
|
|
void eloop_wait_for_read_sock(int sock)
|
|
{
|
|
fd_set rfds;
|
|
|
|
if (sock < 0)
|
|
return;
|
|
|
|
FD_ZERO(&rfds);
|
|
FD_SET(sock, &rfds);
|
|
select(sock + 1, &rfds, NULL, NULL, NULL);
|
|
}
|