From 4aca87515a5083ae0e31ce3177189fd43b6d05ac Mon Sep 17 00:00:00 2001 From: Andreas Baumann Date: Sat, 3 Jan 2015 13:58:15 +0100 Subject: patch to Vanilla Tomato 1.28 --- release/src/router/busybox/networking/isrv.c | 338 +++++++++++++++++++++++++++ 1 file changed, 338 insertions(+) create mode 100644 release/src/router/busybox/networking/isrv.c (limited to 'release/src/router/busybox/networking/isrv.c') diff --git a/release/src/router/busybox/networking/isrv.c b/release/src/router/busybox/networking/isrv.c new file mode 100644 index 00000000..66bb3718 --- /dev/null +++ b/release/src/router/busybox/networking/isrv.c @@ -0,0 +1,338 @@ +/* vi: set sw=4 ts=4: */ +/* + * Generic non-forking server infrastructure. + * Intended to make writing telnetd-type servers easier. + * + * Copyright (C) 2007 Denys Vlasenko + * + * Licensed under GPL version 2, see file LICENSE in this tarball for details. + */ + +#include "libbb.h" +#include "isrv.h" + +#define DEBUG 0 + +#if DEBUG +#define DPRINTF(args...) bb_error_msg(args) +#else +#define DPRINTF(args...) ((void)0) +#endif + +/* Helpers */ + +/* 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; + + /* suppress gcc warning "cast from ptr to int of different size" */ + fcntl(fd, F_SETFL, (int)(ptrdiff_t)(PARAM_TBL[0]) | O_NONBLOCK); + newfd = accept(fd, NULL, 0); + fcntl(fd, F_SETFL, (int)(ptrdiff_t)(PARAM_TBL[0])); + if (newfd < 0) { + if (errno == EAGAIN) return; + /* Most probably someone gave us wrong fd type + * (for example, non-socket). Don't want + * to loop forever. */ + 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; + /* need to know value at _the beginning_ of this routine */ + 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 */ + DPRINTF("handle_fd_set: fd > fd_cnt?? (%d > %d)", + fd, fd_cnt); + break; + } + DPRINTF("handle_fd_set: fd %d is active", fd); + peer = FD2PEER[fd]; + if (peer < 0) + continue; /* peer is already gone */ + 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_sec(); + } + } +} + +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 linger_timeout) +{ + 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 */ + /* (suppress gcc warning "cast from ptr to int of different size") */ + PARAM_TBL[0] = (void*)(ptrdiff_t)(fcntl(listen_fd, F_GETFL)); + + while (1) { + struct timeval tv; + fd_set rd; + fd_set wr; + fd_set *wrp = NULL; + int n; + + tv.tv_sec = timeout; + if (PEER_COUNT <= 1) + tv.tv_sec = linger_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, (int)tv.tv_sec); + n = select(FD_COUNT, &rd, wrp, NULL, tv.tv_sec ? &tv : NULL); + DPRINTF("run: ...select:%d", n); + + if (n < 0) { + if (errno != EINTR) + bb_perror_msg("select"); + continue; + } + + if (n == 0 && linger_timeout && PEER_COUNT <= 1) + break; + + if (timeout) { + time_t t = monotonic_sec(); + 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"); + /* NB: accept socket is not closed. Caller is to decide what to do */ +} -- cgit v1.2.3-54-g00ecf