busybox/networking/isrv.c
<<
>>
Prefs
   1/* vi: set sw=4 ts=4: */
   2/*
   3 * Generic non-forking server infrastructure.
   4 * Intended to make writing telnetd-type servers easier.
   5 *
   6 * Copyright (C) 2007 Denys Vlasenko
   7 *
   8 * Licensed under GPL version 2, see file LICENSE in this tarball for details.
   9 */
  10
  11#include "libbb.h"
  12#include "isrv.h"
  13
  14#define DEBUG 0
  15
  16#if DEBUG
  17#define DPRINTF(args...) bb_error_msg(args)
  18#else
  19#define DPRINTF(args...) ((void)0)
  20#endif
  21
  22/* Helpers */
  23
  24/* Opaque structure */
  25
  26struct isrv_state_t {
  27        short  *fd2peer; /* one per registered fd */
  28        void  **param_tbl; /* one per registered peer */
  29        /* one per registered peer; doesn't exist if !timeout */
  30        time_t *timeo_tbl;
  31        int   (*new_peer)(isrv_state_t *state, int fd);
  32        time_t  curtime;
  33        int     timeout;
  34        int     fd_count;
  35        int     peer_count;
  36        int     wr_count;
  37        fd_set  rd;
  38        fd_set  wr;
  39};
  40#define FD2PEER    (state->fd2peer)
  41#define PARAM_TBL  (state->param_tbl)
  42#define TIMEO_TBL  (state->timeo_tbl)
  43#define CURTIME    (state->curtime)
  44#define TIMEOUT    (state->timeout)
  45#define FD_COUNT   (state->fd_count)
  46#define PEER_COUNT (state->peer_count)
  47#define WR_COUNT   (state->wr_count)
  48
  49/* callback */
  50void isrv_want_rd(isrv_state_t *state, int fd)
  51{
  52        FD_SET(fd, &state->rd);
  53}
  54
  55/* callback */
  56void isrv_want_wr(isrv_state_t *state, int fd)
  57{
  58        if (!FD_ISSET(fd, &state->wr)) {
  59                WR_COUNT++;
  60                FD_SET(fd, &state->wr);
  61        }
  62}
  63
  64/* callback */
  65void isrv_dont_want_rd(isrv_state_t *state, int fd)
  66{
  67        FD_CLR(fd, &state->rd);
  68}
  69
  70/* callback */
  71void isrv_dont_want_wr(isrv_state_t *state, int fd)
  72{
  73        if (FD_ISSET(fd, &state->wr)) {
  74                WR_COUNT--;
  75                FD_CLR(fd, &state->wr);
  76        }
  77}
  78
  79/* callback */
  80int isrv_register_fd(isrv_state_t *state, int peer, int fd)
  81{
  82        int n;
  83
  84        DPRINTF("register_fd(peer:%d,fd:%d)", peer, fd);
  85
  86        if (FD_COUNT >= FD_SETSIZE) return -1;
  87        if (FD_COUNT <= fd) {
  88                n = FD_COUNT;
  89                FD_COUNT = fd + 1;
  90
  91                DPRINTF("register_fd: FD_COUNT %d", FD_COUNT);
  92
  93                FD2PEER = xrealloc(FD2PEER, FD_COUNT * sizeof(FD2PEER[0]));
  94                while (n < fd) FD2PEER[n++] = -1;
  95        }
  96
  97        DPRINTF("register_fd: FD2PEER[%d] = %d", fd, peer);
  98
  99        FD2PEER[fd] = peer;
 100        return 0;
 101}
 102
 103/* callback */
 104void isrv_close_fd(isrv_state_t *state, int fd)
 105{
 106        DPRINTF("close_fd(%d)", fd);
 107
 108        close(fd);
 109        isrv_dont_want_rd(state, fd);
 110        if (WR_COUNT) isrv_dont_want_wr(state, fd);
 111
 112        FD2PEER[fd] = -1;
 113        if (fd == FD_COUNT-1) {
 114                do fd--; while (fd >= 0 && FD2PEER[fd] == -1);
 115                FD_COUNT = fd + 1;
 116
 117                DPRINTF("close_fd: FD_COUNT %d", FD_COUNT);
 118
 119                FD2PEER = xrealloc(FD2PEER, FD_COUNT * sizeof(FD2PEER[0]));
 120        }
 121}
 122
 123/* callback */
 124int isrv_register_peer(isrv_state_t *state, void *param)
 125{
 126        int n;
 127
 128        if (PEER_COUNT >= FD_SETSIZE) return -1;
 129        n = PEER_COUNT++;
 130
 131        DPRINTF("register_peer: PEER_COUNT %d", PEER_COUNT);
 132
 133        PARAM_TBL = xrealloc(PARAM_TBL, PEER_COUNT * sizeof(PARAM_TBL[0]));
 134        PARAM_TBL[n] = param;
 135        if (TIMEOUT) {
 136                TIMEO_TBL = xrealloc(TIMEO_TBL, PEER_COUNT * sizeof(TIMEO_TBL[0]));
 137                TIMEO_TBL[n] = CURTIME;
 138        }
 139        return n;
 140}
 141
 142static void remove_peer(isrv_state_t *state, int peer)
 143{
 144        int movesize;
 145        int fd;
 146
 147        DPRINTF("remove_peer(%d)", peer);
 148
 149        fd = FD_COUNT - 1;
 150        while (fd >= 0) {
 151                if (FD2PEER[fd] == peer) {
 152                        isrv_close_fd(state, fd);
 153                        fd--;
 154                        continue;
 155                }
 156                if (FD2PEER[fd] > peer)
 157                        FD2PEER[fd]--;
 158                fd--;
 159        }
 160
 161        PEER_COUNT--;
 162        DPRINTF("remove_peer: PEER_COUNT %d", PEER_COUNT);
 163
 164        movesize = (PEER_COUNT - peer) * sizeof(void*);
 165        if (movesize > 0) {
 166                memcpy(&PARAM_TBL[peer], &PARAM_TBL[peer+1], movesize);
 167                if (TIMEOUT)
 168                        memcpy(&TIMEO_TBL[peer], &TIMEO_TBL[peer+1], movesize);
 169        }
 170        PARAM_TBL = xrealloc(PARAM_TBL, PEER_COUNT * sizeof(PARAM_TBL[0]));
 171        if (TIMEOUT)
 172                TIMEO_TBL = xrealloc(TIMEO_TBL, PEER_COUNT * sizeof(TIMEO_TBL[0]));
 173}
 174
 175static void handle_accept(isrv_state_t *state, int fd)
 176{
 177        int n, newfd;
 178
 179        /* suppress gcc warning "cast from ptr to int of different size" */
 180        fcntl(fd, F_SETFL, (int)(ptrdiff_t)(PARAM_TBL[0]) | O_NONBLOCK);
 181        newfd = accept(fd, NULL, 0);
 182        fcntl(fd, F_SETFL, (int)(ptrdiff_t)(PARAM_TBL[0]));
 183        if (newfd < 0) {
 184                if (errno == EAGAIN) return;
 185                /* Most probably someone gave us wrong fd type
 186                 * (for example, non-socket). Don't want
 187                 * to loop forever. */
 188                bb_perror_msg_and_die("accept");
 189        }
 190
 191        DPRINTF("new_peer(%d)", newfd);
 192        n = state->new_peer(state, newfd);
 193        if (n)
 194                remove_peer(state, n); /* unsuccesful peer start */
 195}
 196
 197void BUG_sizeof_fd_set_is_strange(void);
 198static void handle_fd_set(isrv_state_t *state, fd_set *fds, int (*h)(int, void **))
 199{
 200        enum { LONG_CNT = sizeof(fd_set) / sizeof(long) };
 201        int fds_pos;
 202        int fd, peer;
 203        /* need to know value at _the beginning_ of this routine */
 204        int fd_cnt = FD_COUNT;
 205
 206        if (LONG_CNT * sizeof(long) != sizeof(fd_set))
 207                BUG_sizeof_fd_set_is_strange();
 208
 209        fds_pos = 0;
 210        while (1) {
 211                /* Find next nonzero bit */
 212                while (fds_pos < LONG_CNT) {
 213                        if (((long*)fds)[fds_pos] == 0) {
 214                                fds_pos++;
 215                                continue;
 216                        }
 217                        /* Found non-zero word */
 218                        fd = fds_pos * sizeof(long)*8; /* word# -> bit# */
 219                        while (1) {
 220                                if (FD_ISSET(fd, fds)) {
 221                                        FD_CLR(fd, fds);
 222                                        goto found_fd;
 223                                }
 224                                fd++;
 225                        }
 226                }
 227                break; /* all words are zero */
 228 found_fd:
 229                if (fd >= fd_cnt) { /* paranoia */
 230                        DPRINTF("handle_fd_set: fd > fd_cnt?? (%d > %d)",
 231                                        fd, fd_cnt);
 232                        break;
 233                }
 234                DPRINTF("handle_fd_set: fd %d is active", fd);
 235                peer = FD2PEER[fd];
 236                if (peer < 0)
 237                        continue; /* peer is already gone */
 238                if (peer == 0) {
 239                        handle_accept(state, fd);
 240                        continue;
 241                }
 242                DPRINTF("h(fd:%d)", fd);
 243                if (h(fd, &PARAM_TBL[peer])) {
 244                        /* this peer is gone */
 245                        remove_peer(state, peer);
 246                } else if (TIMEOUT) {
 247                        TIMEO_TBL[peer] = monotonic_sec();
 248                }
 249        }
 250}
 251
 252static void handle_timeout(isrv_state_t *state, int (*do_timeout)(void **))
 253{
 254        int n, peer;
 255        peer = PEER_COUNT-1;
 256        /* peer 0 is not checked */
 257        while (peer > 0) {
 258                DPRINTF("peer %d: time diff %d", peer,
 259                                (int)(CURTIME - TIMEO_TBL[peer]));
 260                if ((CURTIME - TIMEO_TBL[peer]) >= TIMEOUT) {
 261                        DPRINTF("peer %d: do_timeout()", peer);
 262                        n = do_timeout(&PARAM_TBL[peer]);
 263                        if (n)
 264                                remove_peer(state, peer);
 265                }
 266                peer--;
 267        }
 268}
 269
 270/* Driver */
 271void isrv_run(
 272        int listen_fd,
 273        int (*new_peer)(isrv_state_t *state, int fd),
 274        int (*do_rd)(int fd, void **),
 275        int (*do_wr)(int fd, void **),
 276        int (*do_timeout)(void **),
 277        int timeout,
 278        int linger_timeout)
 279{
 280        isrv_state_t *state = xzalloc(sizeof(*state));
 281        state->new_peer = new_peer;
 282        state->timeout  = timeout;
 283
 284        /* register "peer" #0 - it will accept new connections */
 285        isrv_register_peer(state, NULL);
 286        isrv_register_fd(state, /*peer:*/ 0, listen_fd);
 287        isrv_want_rd(state, listen_fd);
 288        /* remember flags to make blocking<->nonblocking switch faster */
 289        /* (suppress gcc warning "cast from ptr to int of different size") */
 290        PARAM_TBL[0] = (void*)(ptrdiff_t)(fcntl(listen_fd, F_GETFL));
 291
 292        while (1) {
 293                struct timeval tv;
 294                fd_set rd;
 295                fd_set wr;
 296                fd_set *wrp = NULL;
 297                int n;
 298
 299                tv.tv_sec = timeout;
 300                if (PEER_COUNT <= 1)
 301                        tv.tv_sec = linger_timeout;
 302                tv.tv_usec = 0;
 303                rd = state->rd;
 304                if (WR_COUNT) {
 305                        wr = state->wr;
 306                        wrp = &wr;
 307                }
 308
 309                DPRINTF("run: select(FD_COUNT:%d,timeout:%d)...",
 310                                FD_COUNT, (int)tv.tv_sec);
 311                n = select(FD_COUNT, &rd, wrp, NULL, tv.tv_sec ? &tv : NULL);
 312                DPRINTF("run: ...select:%d", n);
 313
 314                if (n < 0) {
 315                        if (errno != EINTR)
 316                                bb_perror_msg("select");
 317                        continue;
 318                }
 319
 320                if (n == 0 && linger_timeout && PEER_COUNT <= 1)
 321                        break;
 322
 323                if (timeout) {
 324                        time_t t = monotonic_sec();
 325                        if (t != CURTIME) {
 326                                CURTIME = t;
 327                                handle_timeout(state, do_timeout);
 328                        }
 329                }
 330                if (n > 0) {
 331                        handle_fd_set(state, &rd, do_rd);
 332                        if (wrp)
 333                                handle_fd_set(state, wrp, do_wr);
 334                }
 335        }
 336        DPRINTF("run: bailout");
 337        /* NB: accept socket is not closed. Caller is to decide what to do */
 338}
 339