The idea is taken from the nginx web server, but much simplified and almost no copypaste left. This will allow minidlna to use different event dispatcher APIs, which would be defined at compile time. My personal goal is to convert minidlna to kqueue(2) on FreeBSD. This would later allow for kqueue based directory change notification, which won't conflict with select(2) like the current patch does. Other platforms will also benefit from the pluggability of the event system, Linux can switch to epoll(2) or at least to poll(2). Detailed list of changes: * event.h [New] Our internal API to unify different event dispatch systems. * select.c [New] Much simplified version of nginx's ngx_select_module.c. * minidlna.c - Split out listen socket event processing into separate function ProcessListen(), which matches event_process_t type. - Create and initialize struct event for the monitor socket, SSDP socket, HTTP socket and beacon socket. - Simplify and make more precise timeout calculation using helper timeval functions from utils.c. Treat gettimeofday() error as a fatal event. - Rip out all stuff related to select(2). Just call event_module.process(). * upnpevents.c - Embed struct event into upnp_event_notify. - Merge upnp_event_create_notify() with upnp_event_notify_connect(). Start connecting immediately after socket creation. Garbage collect now useless ECreated state. - Make upnp_event_process_notify() of event_process_t type, and use it as process callback for upnp_event_notify event. - Looks like we always create upnp_event_notify with existing subscriber, and never clear it later. Remove checks for obj->sub and assert that it is never NULL. Simplifies things. - When switching obj state, add/del it to event dispatcher accrodingly. - Garbage collect upnpevents_selectfds(). - Garbage collect select(2) related stuff from upnpevents_processfds(). Rename function to upnpevents_gc(), since the remaining functionality is garbage collecting, not file descriptor processing. Actually, this can be simplified even more. We can safely close sockets and free objects immediately, eliminating need for upnpevents_gc(). But this change would be beyond scope of this commit. * upnphttp.c, upnphttp.h Embed struct event into struct upnphttp. Adjust Process_upnphttp() to match event_process_t type. Add/del to event dispatcher once creating/closing a socket. * minissdp.c, minissdp.h Make ProcessSSDPRequest() of event_process_t type. * getifaddr.c, getifaddr.h Make ProcessMonitorEvent() of event_process_t type.
184 lines
3.3 KiB
C
184 lines
3.3 KiB
C
/*
|
|
* Copyright (C) Igor Sysoev
|
|
* Copyright (C) Nginx, Inc.
|
|
*/
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/select.h>
|
|
#include <stdlib.h>
|
|
#include <assert.h>
|
|
#include <errno.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#include "event.h"
|
|
#include "log.h"
|
|
|
|
static event_module_init_t select_init;
|
|
static event_module_fini_t select_fini;
|
|
static event_module_add_t select_add;
|
|
static event_module_add_t select_del;
|
|
static event_module_process_t select_process;
|
|
|
|
static fd_set master_read_fd_set;
|
|
static fd_set master_write_fd_set;
|
|
static fd_set work_read_fd_set;
|
|
static fd_set work_write_fd_set;
|
|
|
|
static struct event **events;
|
|
static int nevents;
|
|
static int max_fd;
|
|
|
|
struct event_module event_module = {
|
|
.add = select_add,
|
|
.del = select_del,
|
|
.enable = select_add,
|
|
.disable = select_del,
|
|
.process = select_process,
|
|
.init = select_init,
|
|
.fini = select_fini,
|
|
};
|
|
|
|
static int
|
|
select_init(void)
|
|
{
|
|
|
|
events = calloc(FD_SETSIZE, sizeof(struct event *));
|
|
if (events == NULL)
|
|
return (ENOMEM);
|
|
|
|
FD_ZERO(&master_read_fd_set);
|
|
FD_ZERO(&master_write_fd_set);
|
|
max_fd = 0;
|
|
nevents = 0;
|
|
|
|
return (0);
|
|
}
|
|
|
|
|
|
static void
|
|
select_fini(void)
|
|
{
|
|
|
|
free(events);
|
|
events = NULL;
|
|
}
|
|
|
|
static int
|
|
select_add(struct event *ev)
|
|
{
|
|
|
|
assert(ev->fd <= FD_SETSIZE);
|
|
|
|
switch (ev->rdwr) {
|
|
case EVENT_READ:
|
|
FD_SET(ev->fd, &master_read_fd_set);
|
|
break;
|
|
case EVENT_WRITE:
|
|
FD_SET(ev->fd, &master_write_fd_set);
|
|
break;
|
|
case EVENT_RDWR:
|
|
FD_SET(ev->fd, &master_read_fd_set);
|
|
FD_SET(ev->fd, &master_write_fd_set);
|
|
break;
|
|
}
|
|
|
|
if (max_fd != -1 && max_fd < ev->fd)
|
|
max_fd = ev->fd;
|
|
|
|
events[nevents] = ev;
|
|
ev->index = nevents++;
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
select_del(struct event *ev)
|
|
{
|
|
|
|
assert(ev->fd <= FD_SETSIZE);
|
|
|
|
switch (ev->rdwr) {
|
|
case EVENT_READ:
|
|
FD_CLR(ev->fd, &master_read_fd_set);
|
|
break;
|
|
case EVENT_WRITE:
|
|
FD_CLR(ev->fd, &master_write_fd_set);
|
|
break;
|
|
case EVENT_RDWR:
|
|
FD_CLR(ev->fd, &master_read_fd_set);
|
|
FD_CLR(ev->fd, &master_write_fd_set);
|
|
break;
|
|
}
|
|
|
|
if (max_fd == ev->fd)
|
|
max_fd = -1;
|
|
|
|
if (ev->index < --nevents) {
|
|
struct event *ev0;
|
|
|
|
ev0 = events[nevents];
|
|
events[ev->index] = ev0;
|
|
ev0->index = ev->index;
|
|
}
|
|
ev->index = -1;
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
select_process(u_long msec)
|
|
{
|
|
struct timeval tv, *tp;
|
|
struct event *ev;
|
|
int ready;
|
|
|
|
/* Need to rescan for max_fd. */
|
|
if (max_fd == -1)
|
|
for (int i = 0; i < nevents; i++) {
|
|
if (max_fd < events[i]->fd)
|
|
max_fd = events[i]->fd;
|
|
}
|
|
|
|
tv.tv_sec = (long) (msec / 1000);
|
|
tv.tv_usec = (long) ((msec % 1000) * 1000);
|
|
tp = &tv;
|
|
|
|
work_read_fd_set = master_read_fd_set;
|
|
work_write_fd_set = master_write_fd_set;
|
|
|
|
ready = select(max_fd + 1, &work_read_fd_set, &work_write_fd_set, NULL, tp);
|
|
|
|
if (ready == -1) {
|
|
if (errno == EINTR)
|
|
return (errno);
|
|
DPRINTF(E_ERROR, L_GENERAL, "select(all): %s\n", strerror(errno));
|
|
DPRINTF(E_FATAL, L_GENERAL, "Failed to select open sockets. EXITING\n");
|
|
}
|
|
|
|
if (ready == 0)
|
|
return (0);
|
|
|
|
for (int i = 0; i < nevents; i++) {
|
|
ev = events[i];
|
|
|
|
switch (ev->rdwr) {
|
|
case EVENT_READ:
|
|
if (FD_ISSET(ev->fd, &work_read_fd_set))
|
|
ev->process(ev);
|
|
break;
|
|
case EVENT_WRITE:
|
|
if (FD_ISSET(ev->fd, &work_write_fd_set))
|
|
ev->process(ev);
|
|
break;
|
|
case EVENT_RDWR:
|
|
if (FD_ISSET(ev->fd, &work_read_fd_set) ||
|
|
FD_ISSET(ev->fd, &work_write_fd_set))
|
|
ev->process(ev);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return (0);
|
|
}
|