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>
|
2019-10-27 11:46:18 +01:00
|
|
|
#include <unistd.h>
|
|
|
|
|
#include <errno.h>
|
|
|
|
|
#include <assert.h>
|
|
|
|
|
#include <sys/epoll.h>
|
|
|
|
|
|
|
|
|
|
#define LOG_MODULE "fdm"
|
|
|
|
|
#define LOG_ENABLE_DBG 0
|
|
|
|
|
#include "log.h"
|
|
|
|
|
#include "tllist.h"
|
|
|
|
|
|
|
|
|
|
struct fd {
|
|
|
|
|
int fd;
|
|
|
|
|
void *data;
|
|
|
|
|
fdm_handler_t 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
|
|
|
bool deleted;
|
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;
|
|
|
|
|
tll(struct fd *) fds;
|
|
|
|
|
tll(struct fd *) deferred_delete;
|
2019-10-27 11:46:18 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct fdm *
|
|
|
|
|
fdm_init(void)
|
|
|
|
|
{
|
|
|
|
|
int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
|
|
|
|
|
if (epoll_fd == -1) {
|
|
|
|
|
LOG_ERRNO("failed to create epoll FD");
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct fdm *fdm = malloc(sizeof(*fdm));
|
|
|
|
|
*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(),
|
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");
|
|
|
|
|
|
|
|
|
|
assert(tll_length(fdm->fds) == 0);
|
|
|
|
|
|
|
|
|
|
tll_free(fdm->fds);
|
|
|
|
|
close(fdm->epoll_fd);
|
|
|
|
|
free(fdm);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool
|
|
|
|
|
fdm_add(struct fdm *fdm, int fd, int events, fdm_handler_t handler, void *data)
|
|
|
|
|
{
|
|
|
|
|
#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) {
|
2019-10-27 11:46:18 +01:00
|
|
|
LOG_ERR("FD=%d already registered", fd);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
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
|
|
|
struct fd *fd_data = malloc(sizeof(*fd_data));
|
|
|
|
|
*fd_data = (struct fd) {
|
|
|
|
|
.fd = fd,
|
|
|
|
|
.data = data,
|
|
|
|
|
.handler = handler,
|
|
|
|
|
.deleted = false,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
tll_push_back(fdm->fds, fd_data);
|
2019-10-27 11:46:18 +01:00
|
|
|
|
|
|
|
|
struct epoll_event ev = {
|
|
|
|
|
.events = events,
|
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
|
|
|
.data = {.ptr = fd_data},
|
2019-10-27 11:46:18 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (epoll_ctl(fdm->epoll_fd, EPOLL_CTL_ADD, fd, &ev) < 0) {
|
|
|
|
|
LOG_ERRNO("failed to register FD with epoll");
|
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
|
|
|
free(fd_data);
|
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) {
|
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) {
|
2019-10-27 11:46:18 +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
|
|
|
if (close_fd)
|
|
|
|
|
close(it->item->fd);
|
|
|
|
|
|
|
|
|
|
it->item->deleted = true;
|
|
|
|
|
if (fdm->is_polling)
|
|
|
|
|
tll_push_back(fdm->deferred_delete, it->item);
|
|
|
|
|
else
|
|
|
|
|
free(it->item);
|
|
|
|
|
|
2019-10-27 11:46:18 +01:00
|
|
|
tll_remove(fdm->fds, it);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
LOG_ERR("no such FD: %d", fd);
|
|
|
|
|
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-10-27 11:46:18 +01:00
|
|
|
bool
|
|
|
|
|
fdm_poll(struct fdm *fdm)
|
|
|
|
|
{
|
|
|
|
|
struct epoll_event events[tll_length(fdm->fds)];
|
|
|
|
|
int ret = epoll_wait(fdm->epoll_fd, events, tll_length(fdm->fds), -1);
|
|
|
|
|
if (ret == -1) {
|
|
|
|
|
if (errno == EINTR)
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
LOG_ERRNO("failed to epoll");
|
|
|
|
|
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
|
|
|
fdm->is_polling = true;
|
2019-10-27 11:46:18 +01:00
|
|
|
for (int i = 0; i < ret; i++) {
|
|
|
|
|
struct fd *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;
|
|
|
|
|
|
|
|
|
|
if (!fd->handler(fdm, fd->fd, events[i].events, fd->data)) {
|
|
|
|
|
fdm->is_polling = false;
|
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
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
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
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|