diff options
Diffstat (limited to 'networking/isrv.c')
-rw-r--r-- | networking/isrv.c | 337 |
1 files changed, 337 insertions, 0 deletions
diff --git a/networking/isrv.c b/networking/isrv.c new file mode 100644 index 0000000..02ca1d7 --- /dev/null +++ b/networking/isrv.c @@ -0,0 +1,337 @@ +/* vi: set sw=4 ts=4: */ +/* + * Generic non-forking server infrastructure. + * Intended to make writing telnetd-type servers easier. + * + * Copyright (C) 2007 Denis Vlasenko + * + * Licensed under GPL version 2, see file LICENSE in this tarball for details. + */ + +#include "busybox.h" +#include "isrv.h" + +#define DEBUG 0 + +#if DEBUG +#define DPRINTF(args...) bb_error_msg(args) +#else +#define DPRINTF(args...) ((void)0) +#endif + +/* Helpers */ + +#if 0 /*def _POSIX_MONOTONIC_CLOCK*/ +static time_t monotonic_time(void) +{ + struct timespec ts; + if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0) + time(&ts.tv_sec); + return ts.tv_sec; +} +#else +#define monotonic_time() (time(NULL)) +#endif + +/* Opaque structure */ + +struct isrv_state_t { + short *fd2peer; /* one per registered fd */ + void **param_tbl; /* one per registered peer */ + /* one per registered peer; doesn't exist if !timeout */ + time_t *timeo_tbl; + int (*new_peer)(isrv_state_t *state, int fd); + time_t curtime; + int timeout; + int fd_count; + int peer_count; + int wr_count; + fd_set rd; + fd_set wr; +}; +#define FD2PEER (state->fd2peer) +#define PARAM_TBL (state->param_tbl) +#define TIMEO_TBL (state->timeo_tbl) +#define CURTIME (state->curtime) +#define TIMEOUT (state->timeout) +#define FD_COUNT (state->fd_count) +#define PEER_COUNT (state->peer_count) +#define WR_COUNT (state->wr_count) + +/* callback */ +void isrv_want_rd(isrv_state_t *state, int fd) +{ + FD_SET(fd, &state->rd); +} + +/* callback */ +void isrv_want_wr(isrv_state_t *state, int fd) +{ + if (!FD_ISSET(fd, &state->wr)) { + WR_COUNT++; + FD_SET(fd, &state->wr); + } +} + +/* callback */ +void isrv_dont_want_rd(isrv_state_t *state, int fd) +{ + FD_CLR(fd, &state->rd); +} + +/* callback */ +void isrv_dont_want_wr(isrv_state_t *state, int fd) +{ + if (FD_ISSET(fd, &state->wr)) { + WR_COUNT--; + FD_CLR(fd, &state->wr); + } +} + +/* callback */ +int isrv_register_fd(isrv_state_t *state, int peer, int fd) +{ + int n; + + DPRINTF("register_fd(peer:%d,fd:%d)", peer, fd); + + if (FD_COUNT >= FD_SETSIZE) return -1; + if (FD_COUNT <= fd) { + n = FD_COUNT; + FD_COUNT = fd + 1; + + DPRINTF("register_fd: FD_COUNT %d", FD_COUNT); + + FD2PEER = xrealloc(FD2PEER, FD_COUNT * sizeof(FD2PEER[0])); + while (n < fd) FD2PEER[n++] = -1; + } + + DPRINTF("register_fd: FD2PEER[%d] = %d", fd, peer); + + FD2PEER[fd] = peer; + return 0; +} + +/* callback */ +void isrv_close_fd(isrv_state_t *state, int fd) +{ + DPRINTF("close_fd(%d)", fd); + + close(fd); + isrv_dont_want_rd(state, fd); + if (WR_COUNT) isrv_dont_want_wr(state, fd); + + FD2PEER[fd] = -1; + if (fd == FD_COUNT-1) { + do fd--; while (fd >= 0 && FD2PEER[fd] == -1); + FD_COUNT = fd + 1; + + DPRINTF("close_fd: FD_COUNT %d", FD_COUNT); + + FD2PEER = xrealloc(FD2PEER, FD_COUNT * sizeof(FD2PEER[0])); + } +} + +/* callback */ +int isrv_register_peer(isrv_state_t *state, void *param) +{ + int n; + + if (PEER_COUNT >= FD_SETSIZE) return -1; + n = PEER_COUNT++; + + DPRINTF("register_peer: PEER_COUNT %d", PEER_COUNT); + + PARAM_TBL = xrealloc(PARAM_TBL, PEER_COUNT * sizeof(PARAM_TBL[0])); + PARAM_TBL[n] = param; + if (TIMEOUT) { + TIMEO_TBL = xrealloc(TIMEO_TBL, PEER_COUNT * sizeof(TIMEO_TBL[0])); + TIMEO_TBL[n] = CURTIME; + } + return n; +} + +static void remove_peer(isrv_state_t *state, int peer) +{ + int movesize; + int fd; + + DPRINTF("remove_peer(%d)", peer); + + fd = FD_COUNT - 1; + while (fd >= 0) { + if (FD2PEER[fd] == peer) { + isrv_close_fd(state, fd); + fd--; + continue; + } + if (FD2PEER[fd] > peer) + FD2PEER[fd]--; + fd--; + } + + PEER_COUNT--; + DPRINTF("remove_peer: PEER_COUNT %d", PEER_COUNT); + + movesize = (PEER_COUNT - peer) * sizeof(void*); + if (movesize > 0) { + memcpy(&PARAM_TBL[peer], &PARAM_TBL[peer+1], movesize); + if (TIMEOUT) + memcpy(&TIMEO_TBL[peer], &TIMEO_TBL[peer+1], movesize); + } + PARAM_TBL = xrealloc(PARAM_TBL, PEER_COUNT * sizeof(PARAM_TBL[0])); + if (TIMEOUT) + TIMEO_TBL = xrealloc(TIMEO_TBL, PEER_COUNT * sizeof(TIMEO_TBL[0])); +} + +static void handle_accept(isrv_state_t *state, int fd) +{ + int n, newfd; + + fcntl(fd, F_SETFL, (int)(PARAM_TBL[0]) | O_NONBLOCK); + newfd = accept(fd, NULL, 0); + fcntl(fd, F_SETFL, (int)(PARAM_TBL[0])); + if (newfd < 0) { + if (errno == EAGAIN) return; + /* Most probably someone gave us wrong fd type + * (for example, non-socket) */ + bb_perror_msg_and_die("accept"); + } + + DPRINTF("new_peer(%d)", newfd); + n = state->new_peer(state, newfd); + if (n) + remove_peer(state, n); /* unsuccesful peer start */ +} + +void BUG_sizeof_fd_set_is_strange(void); +static void handle_fd_set(isrv_state_t *state, fd_set *fds, int (*h)(int, void **)) +{ + enum { LONG_CNT = sizeof(fd_set) / sizeof(long) }; + int fds_pos; + int fd, peer; + int fd_cnt = FD_COUNT; + + if (LONG_CNT * sizeof(long) != sizeof(fd_set)) + BUG_sizeof_fd_set_is_strange(); + + fds_pos = 0; + while (1) { + /* Find next nonzero bit */ + while (fds_pos < LONG_CNT) { + if (((long*)fds)[fds_pos] == 0) { + fds_pos++; + continue; + } + /* Found non-zero word */ + fd = fds_pos * sizeof(long)*8; /* word# -> bit# */ + while (1) { + if (FD_ISSET(fd, fds)) { + FD_CLR(fd, fds); + goto found_fd; + } + fd++; + } + } + break; /* all words are zero */ + found_fd: + if (fd >= fd_cnt) /* paranoia */ + break; + DPRINTF("handle_fd_set: fd %d is active", fd); + peer = FD2PEER[fd]; + if (peer == 0) { + handle_accept(state, fd); + continue; + } + DPRINTF("h(fd:%d)", fd); + if (h(fd, &PARAM_TBL[peer])) { + /* this peer is gone */ + remove_peer(state, peer); + } else if (TIMEOUT) { + TIMEO_TBL[peer] = monotonic_time(); + } + } +} + +static void handle_timeout(isrv_state_t *state, int (*do_timeout)(void **)) +{ + int n, peer; + peer = PEER_COUNT-1; + /* peer 0 is not checked */ + while (peer > 0) { + DPRINTF("peer %d: time diff %d", peer, (int)(CURTIME - TIMEO_TBL[peer])); + + if ((CURTIME - TIMEO_TBL[peer]) > TIMEOUT) { + DPRINTF("peer %d: do_timeout()", peer); + n = do_timeout(&PARAM_TBL[peer]); + if (n) + remove_peer(state, peer); + } + peer--; + } +} + +/* Driver */ +void isrv_run( + int listen_fd, + int (*new_peer)(isrv_state_t *state, int fd), + int (*do_rd)(int fd, void **), + int (*do_wr)(int fd, void **), + int (*do_timeout)(void **), + int timeout, + int exit_if_no_clients) +{ + isrv_state_t *state = xzalloc(sizeof(*state)); + state->new_peer = new_peer; + state->timeout = timeout; + + /* register "peer" #0 - it will accept new connections */ + isrv_register_peer(state, NULL); + isrv_register_fd(state, /*peer:*/ 0, listen_fd); + isrv_want_rd(state, listen_fd); + /* remember flags to make blocking<->nonblocking switch faster */ + PARAM_TBL[0] = (void*) (fcntl(listen_fd, F_GETFL, 0)); + + while (1) { + struct timeval tv; + fd_set rd; + fd_set wr; + fd_set *wrp = NULL; + int n; + + tv.tv_sec = timeout; + tv.tv_usec = 0; + rd = state->rd; + if (WR_COUNT) { + wr = state->wr; + wrp = ≀ + } + + DPRINTF("run: select(FD_COUNT:%d,timeout:%d)...", FD_COUNT, timeout); + n = select(FD_COUNT, &rd, wrp, NULL, timeout ? &tv : NULL); + DPRINTF("run: ...select:%d", n); + + if (n < 0) { + if (errno != EINTR) + bb_perror_msg("select"); + continue; + } + + if (exit_if_no_clients && n == 0 && PEER_COUNT <= 1) + break; + + if (timeout) { + time_t t = monotonic_time(); + if (t != CURTIME) { + CURTIME = t; + handle_timeout(state, do_timeout); + } + } + if (n > 0) { + handle_fd_set(state, &rd, do_rd); + if (wrp) + handle_fd_set(state, wrp, do_wr); + } + } + DPRINTF("run: bailout"); +} |