hostap/src/utils/eloop.c
Jouni Malinen 9aca440199 Drop WPA_TRACE reference before eloop timeout handler call
This avoids bogus error reports for cases where the timeout handler
frees the memory that was pointed to by the eloop timeout context.
2009-12-24 12:41:20 +02:00

613 lines
13 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;
timeout = os_malloc(sizeof(*timeout));
if (timeout == NULL)
return -1;
if (os_get_time(&timeout->time) < 0) {
os_free(timeout);
return -1;
}
timeout->time.sec += secs;
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 */
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_remove_timeout(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);
}