From f2b4527179dc194e11f612896c67fb5ea28e2832 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Kain" Date: Wed, 1 Dec 2010 12:22:08 -0500 Subject: [PATCH] Use epoll() and signalfd() in ifchd. --- ifchd/TODO | 3 - ifchd/ifchd.c | 269 +++++++++++++++++++++++++++++--------------------- 2 files changed, 156 insertions(+), 116 deletions(-) delete mode 100644 ifchd/TODO diff --git a/ifchd/TODO b/ifchd/TODO deleted file mode 100644 index b927634..0000000 --- a/ifchd/TODO +++ /dev/null @@ -1,3 +0,0 @@ -write documentation... -support BSD - diff --git a/ifchd/ifchd.c b/ifchd/ifchd.c index 3269282..3472fb3 100644 --- a/ifchd/ifchd.c +++ b/ifchd/ifchd.c @@ -1,5 +1,5 @@ /* ifchd.c - interface change daemon - * Time-stamp: <2010-11-13 08:07:54 njk> + * Time-stamp: <2010-12-01 12:19:39 njk> * * (C) 2004-2010 Nicholas J. Kain * @@ -26,6 +26,8 @@ #include #include #include +#include +#include #include #include @@ -71,7 +73,9 @@ enum states { STATE_WINS }; -static volatile sig_atomic_t pending_exit; +static int epollfd, signalFd; +/* Extra two event slots are for signalFd and the listen socket. */ +static struct epoll_event events[SOCK_QUEUE+2]; /* Socket fd, current state, and idle time for connections. */ static int sks[SOCK_QUEUE], state[SOCK_QUEUE], idle_time[SOCK_QUEUE]; @@ -102,23 +106,6 @@ static pid_t peer_pid; static strlist_t *namesvrs[SOCK_QUEUE]; static strlist_t *domains[SOCK_QUEUE]; -static void sighandler(int sig) { - pending_exit = 1; -} - -static void fix_signals(void) { - disable_signal(SIGPIPE); - disable_signal(SIGUSR1); - disable_signal(SIGUSR2); - disable_signal(SIGTSTP); - disable_signal(SIGTTIN); - disable_signal(SIGCHLD); - disable_signal(SIGHUP); - - hook_signal(SIGINT, sighandler, 0); - hook_signal(SIGTERM, sighandler, 0); -} - static void die_nulstr(strlist_t *p) { if (!p) @@ -133,6 +120,29 @@ static void writeordie(int fd, const char *buf, int len) suicide("write returned error"); } +static void epoll_add(int fd) +{ + struct epoll_event ev; + int r; + ev.events = EPOLLIN | EPOLLRDHUP | EPOLLERR | EPOLLHUP; + ev.data.fd = fd; + r = epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &ev); + if (r == -1) + suicide("epoll_add failed %s", strerror(errno)); +} + +static void epoll_del(int fd) +{ + struct epoll_event ev; + int r; + ev.events = EPOLLIN | EPOLLRDHUP | EPOLLERR | EPOLLHUP; + ev.data.fd = fd; + r = epoll_ctl(epollfd, EPOLL_CTL_DEL, fd, &ev); + if (r == -1) + suicide("epoll_del failed %s", strerror(errno)); +} + +/* Abstracts away the details of accept()ing a socket connection. */ /* Writes out each element in a strlist as an argument to a keyword in * a file. */ static void write_resolve_list(const char *keyword, strlist_t *list) @@ -297,9 +307,10 @@ static void add_sk(int sk) int i; if (authorized_peer(sk, peer_pid, peer_uid, peer_gid)) { - for (i=0; i CONN_TIMEOUT) { + epoll_del(sks[i]); close(sks[i]); new_sk(i, -1); } @@ -532,7 +544,6 @@ static int get_listen(void) return lsock; } -/* Abstracts away the details of accept()ing a socket connection. */ static void accept_conns(int *lsock) { int ret; @@ -567,8 +578,12 @@ static void accept_conns(int *lsock) case ENOTSOCK: case EINVAL: log_line("warning: accept returned %s!\n", strerror(errno)); + + epoll_del(*lsock); close(*lsock); + *lsock = get_listen(); + epoll_add(*lsock); return; case ECONNABORTED: @@ -585,110 +600,138 @@ static void accept_conns(int *lsock) } } +static void setup_signals() +{ + sigset_t mask; + sigemptyset(&mask); + sigaddset(&mask, SIGPIPE); + sigaddset(&mask, SIGUSR1); + sigaddset(&mask, SIGUSR2); + sigaddset(&mask, SIGTSTP); + sigaddset(&mask, SIGTTIN); + sigaddset(&mask, SIGCHLD); + sigaddset(&mask, SIGHUP); + sigaddset(&mask, SIGINT); + sigaddset(&mask, SIGTERM); + if (sigprocmask(SIG_BLOCK, &mask, NULL) < 0) + suicide("sigprocmask failed"); + signalFd = signalfd(-1, &mask, SFD_NONBLOCK); + if (signalFd < 0) + suicide("signalfd failed"); +} + +static void signal_dispatch() +{ + int t, off = 0; + struct signalfd_siginfo si; + again: + t = read(signalFd, (char *)&si + off, sizeof si - off); + if (t < sizeof si - off) { + if (t < 0) { + if (t == EAGAIN || t == EWOULDBLOCK || t == EINTR) + goto again; + else + suicide("signalfd read error"); + } + off += t; + } + switch (si.ssi_signo) { + case SIGINT: + case SIGTERM: + exit(EXIT_SUCCESS); + default: + break; + } +} + +static void process_client_fd(int fd) +{ + char buf[MAX_BUF]; + int r, index, sqidx = -1; + for (int j = 0; j < SOCK_QUEUE; ++j) { + if (sks[j] == fd) { + sqidx = j; + break; + } + } + if (sqidx == -1) + suicide("epoll returned pending read for untracked fd"); + + idle_time[sqidx] = time(NULL); + memset(buf, '\0', sizeof buf); + + r = safe_read(sks[sqidx], buf, sizeof buf / 2 - 1); + if (r <= 0) { + if (r != 0) + log_line("error reading from client fd: %s", strerror(errno)); + goto fail; + } + + /* Discard everything and close connection if we risk overflow. + * This approach is maximally conservative... worst case is that + * some client requests will get dropped. */ + index = strlen(ibuf[sqidx]); + if (index + strlen(buf) > sizeof buf - 2) + goto fail; + + /* Append new stream input avoiding overflow. */ + strlcpy(ibuf[sqidx] + index, buf, sizeof ibuf[sqidx] - index); + + /* Decompose ibuf contents onto strlist. */ + index = stream_onto_list(sqidx); + + /* Remove everything that we've parsed into the list. */ + strlcpy(buf, ibuf[sqidx] + index, sizeof buf); + strlcpy(ibuf[sqidx], buf, sizeof ibuf[sqidx]); + + /* Now we have a strlist of commands and arguments. + * Decompose and execute it. */ + if (!head[sqidx]) + return; + curl[sqidx] = head[sqidx]; + execute_list(sqidx); + return; + fail: + epoll_del(sks[sqidx]); + close(sks[sqidx]); + new_sk(sqidx, -1); +} + /* Core function that handles connections, gathers input, and calls * the state machine to do actual work. */ static void dispatch_work(void) { - int lsock, ret, highfd, i, index; - fd_set rfds; - char buf[MAX_BUF]; + int lsock; /* Initialize all structures to blank state. */ - for (i=0; i highfd) - highfd = sks[i]; - } + int r = epoll_wait(epollfd, events, SOCK_QUEUE + 2, -1); + if (r == -1) { + if (errno == EINTR) + continue; + else + suicide("epoll_wait failed"); } - if (lsock > highfd) - highfd = lsock; - - ret = select(highfd + 1, &rfds, NULL, NULL, NULL); - switch (ret) { - case 0: - close_idle_sk(); - break; - case -1: - if (pending_exit == 1) - return; - suicide("dispatch_work - select returned an error!"); - break; - } - - if (pending_exit == 1) - return; - - /* handle pending connections */ - if (FD_ISSET(lsock, &rfds)) - accept_conns(&lsock); - - /* Read in and process data on waiting connections */ - for (i=0; i MAX_BUF - 2) { - close(sks[i]); - new_sk(i, -1); - continue; - } - - /* Append new stream input avoiding overflow. */ - strlcpy(ibuf[i] + index, buf, sizeof(ibuf[i]) - index); - - /* Decompose ibuf contents onto strlist. */ - index = stream_onto_list(i); - - /* Remove everything that we've parsed into the list. */ - strlcpy(buf, ibuf[i] + index, sizeof(buf)); - strlcpy(ibuf[i], buf, sizeof(ibuf[i])); - - /* Now we have a strlist of commands and arguments. - * Decompose and execute it. */ - if (!head[i]) - continue; - curl[i] = head[i]; - execute_list(i); + for (int i = 0; i < r; ++i) { + int fd = events[i].data.fd; + if (fd == lsock) + accept_conns(&lsock); + else if (fd == signalFd) + signal_dispatch(); + else + process_client_fd(fd); } close_idle_sk(); } @@ -875,7 +918,7 @@ int main(int argc, char** argv) { write_pid(pidfile); umask(077); - fix_signals(); + setup_signals(); /* If we are requested to update resolv.conf, preopen the fd before * we drop root privileges, making sure that if we create