2019-10-27 11:46:18 +01:00
|
|
|
#include "fdm.h"
|
|
|
|
|
|
|
|
|
|
#include <stdlib.h>
|
fdm: don't free FD data that may be referenced by epoll return data
It is perfectly possible, and legal, for a FDM handler to delete
another handler. The problem however is when the epoll returned array
of FD events contain the removed FD handler *after* the handler that
removed it.
That is, if the epoll returned array is:
[FD=13, FD=37]
and the handler for FD=13 removes FD=37, then given the current
implementation, the FD user data (our handler callback etc) will point
to a free:d address.
Add support for this situation by deferring FD handler removal *when
called from another handler*.
This is done by "locking" the FDM struct before looping the handlers
for FDs with events (and unlocking afterwards).
In fdm_del(), we check if the FDM has been locked, in which case the
FD is marked as deleted, and put on a deferred list. But
not *actually* deleted.
Meanwhile, in the FDM poll function, we skip calling handlers for
marked-for-deletion FDs.
Then, when all FDs have been processed, we loop the deferred list and
finally deletes the FDs for real.
2019-11-01 19:51:33 +01:00
|
|
|
#include <stdbool.h>
|
2020-08-23 07:50:27 +02:00
|
|
|
#include <inttypes.h>
|
2019-10-27 11:46:18 +01:00
|
|
|
#include <unistd.h>
|
|
|
|
|
#include <errno.h>
|
2020-01-10 19:22:10 +01:00
|
|
|
#include <fcntl.h>
|
2021-02-10 16:17:36 +01:00
|
|
|
#include <signal.h>
|
2020-01-10 19:22:10 +01:00
|
|
|
|
2019-10-27 11:46:18 +01:00
|
|
|
#include <sys/epoll.h>
|
|
|
|
|
|
2019-11-17 19:19:55 +01:00
|
|
|
#include <tllist.h>
|
|
|
|
|
|
2019-10-27 11:46:18 +01:00
|
|
|
#define LOG_MODULE "fdm"
|
|
|
|
|
#define LOG_ENABLE_DBG 0
|
|
|
|
|
#include "log.h"
|
2021-01-15 20:39:45 +00:00
|
|
|
#include "debug.h"
|
2021-02-10 16:17:36 +01:00
|
|
|
#include "xmalloc.h"
|
2019-10-27 11:46:18 +01:00
|
|
|
|
2021-02-10 16:17:36 +01:00
|
|
|
struct fd_handler {
|
2019-10-27 11:46:18 +01:00
|
|
|
int fd;
|
2019-11-03 00:22:22 +01:00
|
|
|
int events;
|
2021-02-10 16:17:36 +01:00
|
|
|
fdm_fd_handler_t callback;
|
2019-11-02 23:37:19 +01:00
|
|
|
void *callback_data;
|
fdm: don't free FD data that may be referenced by epoll return data
It is perfectly possible, and legal, for a FDM handler to delete
another handler. The problem however is when the epoll returned array
of FD events contain the removed FD handler *after* the handler that
removed it.
That is, if the epoll returned array is:
[FD=13, FD=37]
and the handler for FD=13 removes FD=37, then given the current
implementation, the FD user data (our handler callback etc) will point
to a free:d address.
Add support for this situation by deferring FD handler removal *when
called from another handler*.
This is done by "locking" the FDM struct before looping the handlers
for FDs with events (and unlocking afterwards).
In fdm_del(), we check if the FDM has been locked, in which case the
FD is marked as deleted, and put on a deferred list. But
not *actually* deleted.
Meanwhile, in the FDM poll function, we skip calling handlers for
marked-for-deletion FDs.
Then, when all FDs have been processed, we loop the deferred list and
finally deletes the FDs for real.
2019-11-01 19:51:33 +01:00
|
|
|
bool deleted;
|
2019-10-27 11:46:18 +01:00
|
|
|
};
|
|
|
|
|
|
2021-02-10 16:17:36 +01:00
|
|
|
struct sig_handler {
|
|
|
|
|
fdm_signal_handler_t callback;
|
|
|
|
|
void *callback_data;
|
|
|
|
|
};
|
|
|
|
|
|
2020-01-04 19:48:15 +01:00
|
|
|
struct hook {
|
|
|
|
|
fdm_hook_t callback;
|
|
|
|
|
void *callback_data;
|
|
|
|
|
};
|
|
|
|
|
|
2020-01-04 23:26:27 +01:00
|
|
|
typedef tll(struct hook) hooks_t;
|
|
|
|
|
|
2019-10-27 11:46:18 +01:00
|
|
|
struct fdm {
|
|
|
|
|
int epoll_fd;
|
fdm: don't free FD data that may be referenced by epoll return data
It is perfectly possible, and legal, for a FDM handler to delete
another handler. The problem however is when the epoll returned array
of FD events contain the removed FD handler *after* the handler that
removed it.
That is, if the epoll returned array is:
[FD=13, FD=37]
and the handler for FD=13 removes FD=37, then given the current
implementation, the FD user data (our handler callback etc) will point
to a free:d address.
Add support for this situation by deferring FD handler removal *when
called from another handler*.
This is done by "locking" the FDM struct before looping the handlers
for FDs with events (and unlocking afterwards).
In fdm_del(), we check if the FDM has been locked, in which case the
FD is marked as deleted, and put on a deferred list. But
not *actually* deleted.
Meanwhile, in the FDM poll function, we skip calling handlers for
marked-for-deletion FDs.
Then, when all FDs have been processed, we loop the deferred list and
finally deletes the FDs for real.
2019-11-01 19:51:33 +01:00
|
|
|
bool is_polling;
|
2021-02-10 16:17:36 +01:00
|
|
|
tll(struct fd_handler *) fds;
|
|
|
|
|
tll(struct fd_handler *) deferred_delete;
|
|
|
|
|
|
|
|
|
|
sigset_t sigmask;
|
|
|
|
|
struct sig_handler *signal_handlers;
|
|
|
|
|
|
2020-01-04 23:26:27 +01:00
|
|
|
hooks_t hooks_low;
|
|
|
|
|
hooks_t hooks_normal;
|
|
|
|
|
hooks_t hooks_high;
|
2019-10-27 11:46:18 +01:00
|
|
|
};
|
|
|
|
|
|
2021-05-21 20:00:45 +02:00
|
|
|
static volatile sig_atomic_t got_signal = false;
|
2021-02-10 16:17:36 +01:00
|
|
|
static volatile sig_atomic_t *received_signals = NULL;
|
|
|
|
|
|
2019-10-27 11:46:18 +01:00
|
|
|
struct fdm *
|
|
|
|
|
fdm_init(void)
|
|
|
|
|
{
|
2021-02-10 16:17:36 +01:00
|
|
|
sigset_t sigmask;
|
|
|
|
|
if (sigprocmask(0, NULL, &sigmask) < 0) {
|
|
|
|
|
LOG_ERRNO("failed to get process signal mask");
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-27 11:46:18 +01:00
|
|
|
int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
|
|
|
|
|
if (epoll_fd == -1) {
|
|
|
|
|
LOG_ERRNO("failed to create epoll FD");
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-10 16:17:36 +01:00
|
|
|
xassert(received_signals == NULL); /* Only one FDM instance supported */
|
|
|
|
|
received_signals = xcalloc(SIGRTMAX, sizeof(received_signals[0]));
|
2021-05-21 20:00:45 +02:00
|
|
|
got_signal = false;
|
2021-02-10 16:17:36 +01:00
|
|
|
|
2019-10-27 11:46:18 +01:00
|
|
|
struct fdm *fdm = malloc(sizeof(*fdm));
|
2020-08-08 20:34:30 +01:00
|
|
|
if (unlikely(fdm == NULL)) {
|
|
|
|
|
LOG_ERRNO("malloc() failed");
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-10 16:17:36 +01:00
|
|
|
struct sig_handler *sig_handlers = calloc(SIGRTMAX, sizeof(sig_handlers[0]));
|
|
|
|
|
|
|
|
|
|
if (sig_handlers == NULL) {
|
|
|
|
|
LOG_ERRNO("failed to allocate signal handler array");
|
|
|
|
|
free(fdm);
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-27 11:46:18 +01:00
|
|
|
*fdm = (struct fdm){
|
|
|
|
|
.epoll_fd = epoll_fd,
|
fdm: don't free FD data that may be referenced by epoll return data
It is perfectly possible, and legal, for a FDM handler to delete
another handler. The problem however is when the epoll returned array
of FD events contain the removed FD handler *after* the handler that
removed it.
That is, if the epoll returned array is:
[FD=13, FD=37]
and the handler for FD=13 removes FD=37, then given the current
implementation, the FD user data (our handler callback etc) will point
to a free:d address.
Add support for this situation by deferring FD handler removal *when
called from another handler*.
This is done by "locking" the FDM struct before looping the handlers
for FDs with events (and unlocking afterwards).
In fdm_del(), we check if the FDM has been locked, in which case the
FD is marked as deleted, and put on a deferred list. But
not *actually* deleted.
Meanwhile, in the FDM poll function, we skip calling handlers for
marked-for-deletion FDs.
Then, when all FDs have been processed, we loop the deferred list and
finally deletes the FDs for real.
2019-11-01 19:51:33 +01:00
|
|
|
.is_polling = false,
|
2019-10-27 11:46:18 +01:00
|
|
|
.fds = tll_init(),
|
fdm: don't free FD data that may be referenced by epoll return data
It is perfectly possible, and legal, for a FDM handler to delete
another handler. The problem however is when the epoll returned array
of FD events contain the removed FD handler *after* the handler that
removed it.
That is, if the epoll returned array is:
[FD=13, FD=37]
and the handler for FD=13 removes FD=37, then given the current
implementation, the FD user data (our handler callback etc) will point
to a free:d address.
Add support for this situation by deferring FD handler removal *when
called from another handler*.
This is done by "locking" the FDM struct before looping the handlers
for FDs with events (and unlocking afterwards).
In fdm_del(), we check if the FDM has been locked, in which case the
FD is marked as deleted, and put on a deferred list. But
not *actually* deleted.
Meanwhile, in the FDM poll function, we skip calling handlers for
marked-for-deletion FDs.
Then, when all FDs have been processed, we loop the deferred list and
finally deletes the FDs for real.
2019-11-01 19:51:33 +01:00
|
|
|
.deferred_delete = tll_init(),
|
2021-02-10 16:17:36 +01:00
|
|
|
.sigmask = sigmask,
|
|
|
|
|
.signal_handlers = sig_handlers,
|
2020-01-04 23:26:27 +01:00
|
|
|
.hooks_low = tll_init(),
|
|
|
|
|
.hooks_normal = tll_init(),
|
|
|
|
|
.hooks_high = tll_init(),
|
2019-10-27 11:46:18 +01:00
|
|
|
};
|
|
|
|
|
return fdm;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
fdm_destroy(struct fdm *fdm)
|
|
|
|
|
{
|
|
|
|
|
if (fdm == NULL)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if (tll_length(fdm->fds) > 0)
|
|
|
|
|
LOG_WARN("FD list not empty");
|
|
|
|
|
|
2021-02-10 16:17:36 +01:00
|
|
|
for (int i = 0; i < SIGRTMAX; i++) {
|
|
|
|
|
if (fdm->signal_handlers[i].callback != NULL)
|
|
|
|
|
LOG_WARN("handler for signal %d not removed", i);
|
|
|
|
|
}
|
|
|
|
|
|
2020-01-04 23:26:27 +01:00
|
|
|
if (tll_length(fdm->hooks_low) > 0 ||
|
|
|
|
|
tll_length(fdm->hooks_normal) > 0 ||
|
|
|
|
|
tll_length(fdm->hooks_high) > 0)
|
|
|
|
|
{
|
2020-01-04 19:48:15 +01:00
|
|
|
LOG_WARN("hook list not empty");
|
2020-01-04 23:26:27 +01:00
|
|
|
}
|
2020-01-04 19:48:15 +01:00
|
|
|
|
2021-01-16 20:16:00 +00:00
|
|
|
xassert(tll_length(fdm->fds) == 0);
|
|
|
|
|
xassert(tll_length(fdm->deferred_delete) == 0);
|
|
|
|
|
xassert(tll_length(fdm->hooks_low) == 0);
|
|
|
|
|
xassert(tll_length(fdm->hooks_normal) == 0);
|
|
|
|
|
xassert(tll_length(fdm->hooks_high) == 0);
|
2019-10-27 11:46:18 +01:00
|
|
|
|
2021-02-10 16:17:36 +01:00
|
|
|
sigprocmask(SIG_SETMASK, &fdm->sigmask, NULL);
|
|
|
|
|
free(fdm->signal_handlers);
|
|
|
|
|
|
2019-10-27 11:46:18 +01:00
|
|
|
tll_free(fdm->fds);
|
2019-11-02 23:36:02 +01:00
|
|
|
tll_free(fdm->deferred_delete);
|
2020-01-04 23:26:27 +01:00
|
|
|
tll_free(fdm->hooks_low);
|
|
|
|
|
tll_free(fdm->hooks_normal);
|
|
|
|
|
tll_free(fdm->hooks_high);
|
2019-10-27 11:46:18 +01:00
|
|
|
close(fdm->epoll_fd);
|
|
|
|
|
free(fdm);
|
2021-02-10 16:17:36 +01:00
|
|
|
|
|
|
|
|
free((void *)received_signals);
|
|
|
|
|
received_signals = NULL;
|
2019-10-27 11:46:18 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool
|
2021-02-10 16:17:36 +01:00
|
|
|
fdm_add(struct fdm *fdm, int fd, int events, fdm_fd_handler_t cb, void *data)
|
2019-10-27 11:46:18 +01:00
|
|
|
{
|
|
|
|
|
#if defined(_DEBUG)
|
|
|
|
|
tll_foreach(fdm->fds, it) {
|
fdm: don't free FD data that may be referenced by epoll return data
It is perfectly possible, and legal, for a FDM handler to delete
another handler. The problem however is when the epoll returned array
of FD events contain the removed FD handler *after* the handler that
removed it.
That is, if the epoll returned array is:
[FD=13, FD=37]
and the handler for FD=13 removes FD=37, then given the current
implementation, the FD user data (our handler callback etc) will point
to a free:d address.
Add support for this situation by deferring FD handler removal *when
called from another handler*.
This is done by "locking" the FDM struct before looping the handlers
for FDs with events (and unlocking afterwards).
In fdm_del(), we check if the FDM has been locked, in which case the
FD is marked as deleted, and put on a deferred list. But
not *actually* deleted.
Meanwhile, in the FDM poll function, we skip calling handlers for
marked-for-deletion FDs.
Then, when all FDs have been processed, we loop the deferred list and
finally deletes the FDs for real.
2019-11-01 19:51:33 +01:00
|
|
|
if (it->item->fd == fd) {
|
2021-02-10 09:01:51 +00:00
|
|
|
BUG("FD=%d already registered", fd);
|
2019-10-27 11:46:18 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
2021-02-10 16:17:36 +01:00
|
|
|
struct fd_handler *handler = malloc(sizeof(*handler));
|
|
|
|
|
if (unlikely(handler == NULL)) {
|
2020-08-08 20:34:30 +01:00
|
|
|
LOG_ERRNO("malloc() failed");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-10 16:17:36 +01:00
|
|
|
*handler = (struct fd_handler) {
|
fdm: don't free FD data that may be referenced by epoll return data
It is perfectly possible, and legal, for a FDM handler to delete
another handler. The problem however is when the epoll returned array
of FD events contain the removed FD handler *after* the handler that
removed it.
That is, if the epoll returned array is:
[FD=13, FD=37]
and the handler for FD=13 removes FD=37, then given the current
implementation, the FD user data (our handler callback etc) will point
to a free:d address.
Add support for this situation by deferring FD handler removal *when
called from another handler*.
This is done by "locking" the FDM struct before looping the handlers
for FDs with events (and unlocking afterwards).
In fdm_del(), we check if the FDM has been locked, in which case the
FD is marked as deleted, and put on a deferred list. But
not *actually* deleted.
Meanwhile, in the FDM poll function, we skip calling handlers for
marked-for-deletion FDs.
Then, when all FDs have been processed, we loop the deferred list and
finally deletes the FDs for real.
2019-11-01 19:51:33 +01:00
|
|
|
.fd = fd,
|
2019-11-03 00:22:22 +01:00
|
|
|
.events = events,
|
2021-02-10 16:17:36 +01:00
|
|
|
.callback = cb,
|
2019-11-02 23:37:19 +01:00
|
|
|
.callback_data = data,
|
fdm: don't free FD data that may be referenced by epoll return data
It is perfectly possible, and legal, for a FDM handler to delete
another handler. The problem however is when the epoll returned array
of FD events contain the removed FD handler *after* the handler that
removed it.
That is, if the epoll returned array is:
[FD=13, FD=37]
and the handler for FD=13 removes FD=37, then given the current
implementation, the FD user data (our handler callback etc) will point
to a free:d address.
Add support for this situation by deferring FD handler removal *when
called from another handler*.
This is done by "locking" the FDM struct before looping the handlers
for FDs with events (and unlocking afterwards).
In fdm_del(), we check if the FDM has been locked, in which case the
FD is marked as deleted, and put on a deferred list. But
not *actually* deleted.
Meanwhile, in the FDM poll function, we skip calling handlers for
marked-for-deletion FDs.
Then, when all FDs have been processed, we loop the deferred list and
finally deletes the FDs for real.
2019-11-01 19:51:33 +01:00
|
|
|
.deleted = false,
|
|
|
|
|
};
|
|
|
|
|
|
2021-02-10 16:17:36 +01:00
|
|
|
tll_push_back(fdm->fds, handler);
|
2019-10-27 11:46:18 +01:00
|
|
|
|
|
|
|
|
struct epoll_event ev = {
|
|
|
|
|
.events = events,
|
2021-02-10 16:17:36 +01:00
|
|
|
.data = {.ptr = handler},
|
2019-10-27 11:46:18 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (epoll_ctl(fdm->epoll_fd, EPOLL_CTL_ADD, fd, &ev) < 0) {
|
2019-11-03 00:22:22 +01:00
|
|
|
LOG_ERRNO("failed to register FD=%d with epoll", fd);
|
2021-02-10 16:17:36 +01:00
|
|
|
free(handler);
|
2019-10-27 11:46:18 +01:00
|
|
|
tll_pop_back(fdm->fds);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
fdm: don't free FD data that may be referenced by epoll return data
It is perfectly possible, and legal, for a FDM handler to delete
another handler. The problem however is when the epoll returned array
of FD events contain the removed FD handler *after* the handler that
removed it.
That is, if the epoll returned array is:
[FD=13, FD=37]
and the handler for FD=13 removes FD=37, then given the current
implementation, the FD user data (our handler callback etc) will point
to a free:d address.
Add support for this situation by deferring FD handler removal *when
called from another handler*.
This is done by "locking" the FDM struct before looping the handlers
for FDs with events (and unlocking afterwards).
In fdm_del(), we check if the FDM has been locked, in which case the
FD is marked as deleted, and put on a deferred list. But
not *actually* deleted.
Meanwhile, in the FDM poll function, we skip calling handlers for
marked-for-deletion FDs.
Then, when all FDs have been processed, we loop the deferred list and
finally deletes the FDs for real.
2019-11-01 19:51:33 +01:00
|
|
|
static bool
|
|
|
|
|
fdm_del_internal(struct fdm *fdm, int fd, bool close_fd)
|
2019-10-27 11:46:18 +01:00
|
|
|
{
|
fdm: don't free FD data that may be referenced by epoll return data
It is perfectly possible, and legal, for a FDM handler to delete
another handler. The problem however is when the epoll returned array
of FD events contain the removed FD handler *after* the handler that
removed it.
That is, if the epoll returned array is:
[FD=13, FD=37]
and the handler for FD=13 removes FD=37, then given the current
implementation, the FD user data (our handler callback etc) will point
to a free:d address.
Add support for this situation by deferring FD handler removal *when
called from another handler*.
This is done by "locking" the FDM struct before looping the handlers
for FDs with events (and unlocking afterwards).
In fdm_del(), we check if the FDM has been locked, in which case the
FD is marked as deleted, and put on a deferred list. But
not *actually* deleted.
Meanwhile, in the FDM poll function, we skip calling handlers for
marked-for-deletion FDs.
Then, when all FDs have been processed, we loop the deferred list and
finally deletes the FDs for real.
2019-11-01 19:51:33 +01:00
|
|
|
if (fd == -1)
|
|
|
|
|
return true;
|
|
|
|
|
|
2019-10-27 11:46:18 +01:00
|
|
|
tll_foreach(fdm->fds, it) {
|
2019-11-03 00:42:34 +01:00
|
|
|
if (it->item->fd != fd)
|
|
|
|
|
continue;
|
2019-10-27 11:46:18 +01:00
|
|
|
|
2019-11-03 00:42:34 +01:00
|
|
|
if (epoll_ctl(fdm->epoll_fd, EPOLL_CTL_DEL, fd, NULL) < 0)
|
|
|
|
|
LOG_ERRNO("failed to unregister FD=%d from epoll", fd);
|
fdm: don't free FD data that may be referenced by epoll return data
It is perfectly possible, and legal, for a FDM handler to delete
another handler. The problem however is when the epoll returned array
of FD events contain the removed FD handler *after* the handler that
removed it.
That is, if the epoll returned array is:
[FD=13, FD=37]
and the handler for FD=13 removes FD=37, then given the current
implementation, the FD user data (our handler callback etc) will point
to a free:d address.
Add support for this situation by deferring FD handler removal *when
called from another handler*.
This is done by "locking" the FDM struct before looping the handlers
for FDs with events (and unlocking afterwards).
In fdm_del(), we check if the FDM has been locked, in which case the
FD is marked as deleted, and put on a deferred list. But
not *actually* deleted.
Meanwhile, in the FDM poll function, we skip calling handlers for
marked-for-deletion FDs.
Then, when all FDs have been processed, we loop the deferred list and
finally deletes the FDs for real.
2019-11-01 19:51:33 +01:00
|
|
|
|
2019-11-03 00:42:34 +01:00
|
|
|
if (close_fd)
|
|
|
|
|
close(it->item->fd);
|
fdm: don't free FD data that may be referenced by epoll return data
It is perfectly possible, and legal, for a FDM handler to delete
another handler. The problem however is when the epoll returned array
of FD events contain the removed FD handler *after* the handler that
removed it.
That is, if the epoll returned array is:
[FD=13, FD=37]
and the handler for FD=13 removes FD=37, then given the current
implementation, the FD user data (our handler callback etc) will point
to a free:d address.
Add support for this situation by deferring FD handler removal *when
called from another handler*.
This is done by "locking" the FDM struct before looping the handlers
for FDs with events (and unlocking afterwards).
In fdm_del(), we check if the FDM has been locked, in which case the
FD is marked as deleted, and put on a deferred list. But
not *actually* deleted.
Meanwhile, in the FDM poll function, we skip calling handlers for
marked-for-deletion FDs.
Then, when all FDs have been processed, we loop the deferred list and
finally deletes the FDs for real.
2019-11-01 19:51:33 +01:00
|
|
|
|
2019-11-03 00:42:34 +01:00
|
|
|
it->item->deleted = true;
|
|
|
|
|
if (fdm->is_polling)
|
|
|
|
|
tll_push_back(fdm->deferred_delete, it->item);
|
|
|
|
|
else
|
|
|
|
|
free(it->item);
|
|
|
|
|
|
|
|
|
|
tll_remove(fdm->fds, it);
|
|
|
|
|
return true;
|
2019-10-27 11:46:18 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
LOG_ERR("no such FD: %d", fd);
|
2019-12-15 15:06:09 +01:00
|
|
|
close(fd);
|
2019-10-27 11:46:18 +01:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
fdm: don't free FD data that may be referenced by epoll return data
It is perfectly possible, and legal, for a FDM handler to delete
another handler. The problem however is when the epoll returned array
of FD events contain the removed FD handler *after* the handler that
removed it.
That is, if the epoll returned array is:
[FD=13, FD=37]
and the handler for FD=13 removes FD=37, then given the current
implementation, the FD user data (our handler callback etc) will point
to a free:d address.
Add support for this situation by deferring FD handler removal *when
called from another handler*.
This is done by "locking" the FDM struct before looping the handlers
for FDs with events (and unlocking afterwards).
In fdm_del(), we check if the FDM has been locked, in which case the
FD is marked as deleted, and put on a deferred list. But
not *actually* deleted.
Meanwhile, in the FDM poll function, we skip calling handlers for
marked-for-deletion FDs.
Then, when all FDs have been processed, we loop the deferred list and
finally deletes the FDs for real.
2019-11-01 19:51:33 +01:00
|
|
|
bool
|
|
|
|
|
fdm_del(struct fdm *fdm, int fd)
|
|
|
|
|
{
|
|
|
|
|
return fdm_del_internal(fdm, fd, true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool
|
|
|
|
|
fdm_del_no_close(struct fdm *fdm, int fd)
|
|
|
|
|
{
|
|
|
|
|
return fdm_del_internal(fdm, fd, false);
|
|
|
|
|
}
|
|
|
|
|
|
2019-11-03 00:22:22 +01:00
|
|
|
static bool
|
2021-02-10 16:17:36 +01:00
|
|
|
event_modify(struct fdm *fdm, struct fd_handler *fd, int new_events)
|
2019-11-03 00:22:22 +01:00
|
|
|
{
|
|
|
|
|
if (new_events == fd->events)
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
struct epoll_event ev = {
|
|
|
|
|
.events = new_events,
|
|
|
|
|
.data = {.ptr = fd},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (epoll_ctl(fdm->epoll_fd, EPOLL_CTL_MOD, fd->fd, &ev) < 0) {
|
|
|
|
|
LOG_ERRNO("failed to modify FD=%d with epoll (events 0x%08x -> 0x%08x)",
|
|
|
|
|
fd->fd, fd->events, new_events);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fd->events = new_events;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool
|
|
|
|
|
fdm_event_add(struct fdm *fdm, int fd, int events)
|
|
|
|
|
{
|
|
|
|
|
tll_foreach(fdm->fds, it) {
|
|
|
|
|
if (it->item->fd != fd)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
return event_modify(fdm, it->item, it->item->events | events);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
LOG_ERR("FD=%d not registered with the FDM", fd);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool
|
|
|
|
|
fdm_event_del(struct fdm *fdm, int fd, int events)
|
|
|
|
|
{
|
|
|
|
|
tll_foreach(fdm->fds, it) {
|
|
|
|
|
if (it->item->fd != fd)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
return event_modify(fdm, it->item, it->item->events & ~events);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
LOG_ERR("FD=%d not registered with the FDM", fd);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2020-01-04 23:26:27 +01:00
|
|
|
static hooks_t *
|
|
|
|
|
hook_priority_to_list(struct fdm *fdm, enum fdm_hook_priority priority)
|
|
|
|
|
{
|
|
|
|
|
switch (priority) {
|
|
|
|
|
case FDM_HOOK_PRIORITY_LOW: return &fdm->hooks_low;
|
|
|
|
|
case FDM_HOOK_PRIORITY_NORMAL: return &fdm->hooks_normal;
|
|
|
|
|
case FDM_HOOK_PRIORITY_HIGH: return &fdm->hooks_high;
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-09 13:52:33 +00:00
|
|
|
BUG("unhandled priority type");
|
2020-01-04 23:26:27 +01:00
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
2020-01-04 19:48:15 +01:00
|
|
|
bool
|
2020-01-04 23:26:27 +01:00
|
|
|
fdm_hook_add(struct fdm *fdm, fdm_hook_t hook, void *data,
|
|
|
|
|
enum fdm_hook_priority priority)
|
2020-01-04 19:48:15 +01:00
|
|
|
{
|
2020-01-04 23:26:27 +01:00
|
|
|
hooks_t *hooks = hook_priority_to_list(fdm, priority);
|
|
|
|
|
|
2020-01-04 19:48:15 +01:00
|
|
|
#if defined(_DEBUG)
|
2020-01-04 23:26:27 +01:00
|
|
|
tll_foreach(*hooks, it) {
|
2020-01-04 19:48:15 +01:00
|
|
|
if (it->item.callback == hook) {
|
2020-08-23 07:50:27 +02:00
|
|
|
LOG_ERR("hook=0x%" PRIxPTR " already registered", (uintptr_t)hook);
|
2020-01-04 19:48:15 +01:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
2020-01-04 23:26:27 +01:00
|
|
|
tll_push_back(*hooks, ((struct hook){hook, data}));
|
2020-01-04 19:48:15 +01:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool
|
2020-01-04 23:26:27 +01:00
|
|
|
fdm_hook_del(struct fdm *fdm, fdm_hook_t hook, enum fdm_hook_priority priority)
|
2020-01-04 19:48:15 +01:00
|
|
|
{
|
2020-01-04 23:26:27 +01:00
|
|
|
hooks_t *hooks = hook_priority_to_list(fdm, priority);
|
|
|
|
|
|
|
|
|
|
tll_foreach(*hooks, it) {
|
2020-01-04 19:48:15 +01:00
|
|
|
if (it->item.callback != hook)
|
|
|
|
|
continue;
|
|
|
|
|
|
2020-01-04 23:26:27 +01:00
|
|
|
tll_remove(*hooks, it);
|
2020-01-04 19:48:15 +01:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-23 07:50:27 +02:00
|
|
|
LOG_WARN("hook=0x%" PRIxPTR " not registered", (uintptr_t)hook);
|
2020-01-04 19:48:15 +01:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-10 16:17:36 +01:00
|
|
|
static void
|
|
|
|
|
signal_handler(int signo)
|
|
|
|
|
{
|
2021-05-21 20:00:45 +02:00
|
|
|
got_signal = true;
|
2021-02-10 16:17:36 +01:00
|
|
|
received_signals[signo] = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool
|
|
|
|
|
fdm_signal_add(struct fdm *fdm, int signo, fdm_signal_handler_t handler, void *data)
|
|
|
|
|
{
|
|
|
|
|
if (fdm->signal_handlers[signo].callback != NULL) {
|
|
|
|
|
LOG_ERR("signal %d already has a handler", signo);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sigset_t mask, original;
|
|
|
|
|
sigemptyset(&mask);
|
|
|
|
|
sigaddset(&mask, signo);
|
|
|
|
|
|
|
|
|
|
if (sigprocmask(SIG_BLOCK, &mask, &original) < 0) {
|
|
|
|
|
LOG_ERRNO("failed to block signal %d", signo);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct sigaction action = {.sa_handler = &signal_handler};
|
Explicitly initialize sigaction::sa_mask members with sigemptyset(3)
Not doing so before calling sigaction(3) is "undefined" according to
POSIX[1]:
> Applications shall call either sigemptyset() or sigfillset() at least
> once for each object of type sigset_t prior to any other use of that
> object. If such an object is not initialized in this way, but is
> nonetheless supplied as an argument to any of pthread_sigmask(),
> sigaction(), sigaddset(), sigdelset(), sigismember(), sigpending(),
> sigprocmask(), sigsuspend(), sigtimedwait(), sigwait(), or
> sigwaitinfo(), the results are undefined.
The use of designated initializers means that sa_mask members were
still being initialized, but sigset_t is an opaque type and implicit
initialization doesn't necessarily produce the same results as using
sigemptyset(3) (although it typically does on most implementations).
[1]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/sigaddset.html
2022-02-12 12:04:57 +00:00
|
|
|
sigemptyset(&action.sa_mask);
|
2021-02-10 16:17:36 +01:00
|
|
|
if (sigaction(signo, &action, NULL) < 0) {
|
|
|
|
|
LOG_ERRNO("failed to set signal handler for signal %d", signo);
|
|
|
|
|
sigprocmask(SIG_SETMASK, &original, NULL);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
received_signals[signo] = false;
|
|
|
|
|
fdm->signal_handlers[signo].callback = handler;
|
|
|
|
|
fdm->signal_handlers[signo].callback_data = data;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool
|
|
|
|
|
fdm_signal_del(struct fdm *fdm, int signo)
|
|
|
|
|
{
|
|
|
|
|
if (fdm->signal_handlers[signo].callback == NULL)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
struct sigaction action = {.sa_handler = SIG_DFL};
|
Explicitly initialize sigaction::sa_mask members with sigemptyset(3)
Not doing so before calling sigaction(3) is "undefined" according to
POSIX[1]:
> Applications shall call either sigemptyset() or sigfillset() at least
> once for each object of type sigset_t prior to any other use of that
> object. If such an object is not initialized in this way, but is
> nonetheless supplied as an argument to any of pthread_sigmask(),
> sigaction(), sigaddset(), sigdelset(), sigismember(), sigpending(),
> sigprocmask(), sigsuspend(), sigtimedwait(), sigwait(), or
> sigwaitinfo(), the results are undefined.
The use of designated initializers means that sa_mask members were
still being initialized, but sigset_t is an opaque type and implicit
initialization doesn't necessarily produce the same results as using
sigemptyset(3) (although it typically does on most implementations).
[1]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/sigaddset.html
2022-02-12 12:04:57 +00:00
|
|
|
sigemptyset(&action.sa_mask);
|
2021-02-10 16:17:36 +01:00
|
|
|
if (sigaction(signo, &action, NULL) < 0) {
|
|
|
|
|
LOG_ERRNO("failed to restore signal handler for signal %d", signo);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
received_signals[signo] = false;
|
|
|
|
|
fdm->signal_handlers[signo].callback = NULL;
|
|
|
|
|
fdm->signal_handlers[signo].callback_data = NULL;
|
|
|
|
|
|
|
|
|
|
sigset_t mask;
|
|
|
|
|
sigemptyset(&mask);
|
|
|
|
|
sigaddset(&mask, signo);
|
|
|
|
|
if (sigprocmask(SIG_UNBLOCK, &mask, NULL) < 0) {
|
|
|
|
|
LOG_ERRNO("failed to unblock signal %d", signo);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-27 11:46:18 +01:00
|
|
|
bool
|
|
|
|
|
fdm_poll(struct fdm *fdm)
|
|
|
|
|
{
|
2021-01-16 20:16:00 +00:00
|
|
|
xassert(!fdm->is_polling && "nested calls to fdm_poll() not allowed");
|
2019-11-02 13:46:54 +01:00
|
|
|
if (fdm->is_polling) {
|
|
|
|
|
LOG_ERR("nested calls to fdm_poll() not allowed");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2020-01-04 23:26:27 +01:00
|
|
|
tll_foreach(fdm->hooks_high, it) {
|
2020-08-23 07:50:27 +02:00
|
|
|
LOG_DBG(
|
|
|
|
|
"executing high priority hook 0x%" PRIxPTR" (fdm=%p, data=%p)",
|
|
|
|
|
(uintptr_t)it->item.callback, (void *)fdm,
|
|
|
|
|
(void *)it->item.callback_data);
|
2020-01-04 23:26:27 +01:00
|
|
|
it->item.callback(fdm, it->item.callback_data);
|
|
|
|
|
}
|
|
|
|
|
tll_foreach(fdm->hooks_normal, it) {
|
2020-08-23 07:50:27 +02:00
|
|
|
LOG_DBG(
|
|
|
|
|
"executing normal priority hook 0x%" PRIxPTR " (fdm=%p, data=%p)",
|
|
|
|
|
(uintptr_t)it->item.callback, (void *)fdm,
|
|
|
|
|
(void *)it->item.callback_data);
|
2020-01-04 23:26:27 +01:00
|
|
|
it->item.callback(fdm, it->item.callback_data);
|
|
|
|
|
}
|
|
|
|
|
tll_foreach(fdm->hooks_low, it) {
|
2020-08-23 07:50:27 +02:00
|
|
|
LOG_DBG(
|
|
|
|
|
"executing low priority hook 0x%" PRIxPTR " (fdm=%p, data=%p)",
|
|
|
|
|
(uintptr_t)it->item.callback, (void *)fdm,
|
|
|
|
|
(void *)it->item.callback_data);
|
2020-01-04 19:48:15 +01:00
|
|
|
it->item.callback(fdm, it->item.callback_data);
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-27 11:46:18 +01:00
|
|
|
struct epoll_event events[tll_length(fdm->fds)];
|
2019-11-02 23:35:42 +01:00
|
|
|
|
2021-02-10 16:17:36 +01:00
|
|
|
int r = epoll_pwait(
|
|
|
|
|
fdm->epoll_fd, events, tll_length(fdm->fds), -1, &fdm->sigmask);
|
|
|
|
|
|
2021-05-21 20:00:45 +02:00
|
|
|
int errno_copy = errno;
|
2021-02-10 16:17:36 +01:00
|
|
|
|
2021-05-21 20:00:45 +02:00
|
|
|
if (unlikely(got_signal)) {
|
|
|
|
|
got_signal = false;
|
2021-02-10 16:17:36 +01:00
|
|
|
|
2021-05-21 20:00:45 +02:00
|
|
|
for (int i = 0; i < SIGRTMAX; i++) {
|
|
|
|
|
if (received_signals[i]) {
|
|
|
|
|
received_signals[i] = false;
|
|
|
|
|
struct sig_handler *handler = &fdm->signal_handlers[i];
|
2021-02-10 16:17:36 +01:00
|
|
|
|
2021-05-21 20:00:45 +02:00
|
|
|
xassert(handler->callback != NULL);
|
|
|
|
|
if (!handler->callback(fdm, i, handler->callback_data))
|
|
|
|
|
return false;
|
2021-02-10 16:17:36 +01:00
|
|
|
}
|
2021-05-21 20:00:45 +02:00
|
|
|
}
|
|
|
|
|
}
|
2021-02-10 16:17:36 +01:00
|
|
|
|
2021-05-21 20:00:45 +02:00
|
|
|
if (unlikely(r < 0)) {
|
|
|
|
|
if (errno_copy == EINTR)
|
2020-12-04 18:36:53 +01:00
|
|
|
return true;
|
2019-10-27 11:46:18 +01:00
|
|
|
|
2021-05-21 20:00:45 +02:00
|
|
|
LOG_ERRNO_P(errno_copy, "failed to epoll");
|
2019-10-27 11:46:18 +01:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2019-11-02 23:35:42 +01:00
|
|
|
bool ret = true;
|
|
|
|
|
|
fdm: don't free FD data that may be referenced by epoll return data
It is perfectly possible, and legal, for a FDM handler to delete
another handler. The problem however is when the epoll returned array
of FD events contain the removed FD handler *after* the handler that
removed it.
That is, if the epoll returned array is:
[FD=13, FD=37]
and the handler for FD=13 removes FD=37, then given the current
implementation, the FD user data (our handler callback etc) will point
to a free:d address.
Add support for this situation by deferring FD handler removal *when
called from another handler*.
This is done by "locking" the FDM struct before looping the handlers
for FDs with events (and unlocking afterwards).
In fdm_del(), we check if the FDM has been locked, in which case the
FD is marked as deleted, and put on a deferred list. But
not *actually* deleted.
Meanwhile, in the FDM poll function, we skip calling handlers for
marked-for-deletion FDs.
Then, when all FDs have been processed, we loop the deferred list and
finally deletes the FDs for real.
2019-11-01 19:51:33 +01:00
|
|
|
fdm->is_polling = true;
|
2019-11-03 00:51:47 +01:00
|
|
|
for (int i = 0; i < r; i++) {
|
2021-02-10 16:17:36 +01:00
|
|
|
struct fd_handler *fd = events[i].data.ptr;
|
fdm: don't free FD data that may be referenced by epoll return data
It is perfectly possible, and legal, for a FDM handler to delete
another handler. The problem however is when the epoll returned array
of FD events contain the removed FD handler *after* the handler that
removed it.
That is, if the epoll returned array is:
[FD=13, FD=37]
and the handler for FD=13 removes FD=37, then given the current
implementation, the FD user data (our handler callback etc) will point
to a free:d address.
Add support for this situation by deferring FD handler removal *when
called from another handler*.
This is done by "locking" the FDM struct before looping the handlers
for FDs with events (and unlocking afterwards).
In fdm_del(), we check if the FDM has been locked, in which case the
FD is marked as deleted, and put on a deferred list. But
not *actually* deleted.
Meanwhile, in the FDM poll function, we skip calling handlers for
marked-for-deletion FDs.
Then, when all FDs have been processed, we loop the deferred list and
finally deletes the FDs for real.
2019-11-01 19:51:33 +01:00
|
|
|
if (fd->deleted)
|
|
|
|
|
continue;
|
|
|
|
|
|
2019-11-02 23:37:19 +01:00
|
|
|
if (!fd->callback(fdm, fd->fd, events[i].events, fd->callback_data)) {
|
2019-11-02 23:35:42 +01:00
|
|
|
ret = false;
|
|
|
|
|
break;
|
fdm: don't free FD data that may be referenced by epoll return data
It is perfectly possible, and legal, for a FDM handler to delete
another handler. The problem however is when the epoll returned array
of FD events contain the removed FD handler *after* the handler that
removed it.
That is, if the epoll returned array is:
[FD=13, FD=37]
and the handler for FD=13 removes FD=37, then given the current
implementation, the FD user data (our handler callback etc) will point
to a free:d address.
Add support for this situation by deferring FD handler removal *when
called from another handler*.
This is done by "locking" the FDM struct before looping the handlers
for FDs with events (and unlocking afterwards).
In fdm_del(), we check if the FDM has been locked, in which case the
FD is marked as deleted, and put on a deferred list. But
not *actually* deleted.
Meanwhile, in the FDM poll function, we skip calling handlers for
marked-for-deletion FDs.
Then, when all FDs have been processed, we loop the deferred list and
finally deletes the FDs for real.
2019-11-01 19:51:33 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
fdm->is_polling = false;
|
|
|
|
|
|
|
|
|
|
tll_foreach(fdm->deferred_delete, it) {
|
|
|
|
|
free(it->item);
|
|
|
|
|
tll_remove(fdm->deferred_delete, it);
|
2019-10-27 11:46:18 +01:00
|
|
|
}
|
|
|
|
|
|
2019-11-02 23:35:42 +01:00
|
|
|
return ret;
|
2019-10-27 11:46:18 +01:00
|
|
|
}
|