source: branches/3.2/mindi-busybox/networking/isrv.c @ 3232

Last change on this file since 3232 was 2725, checked in by bruno, 8 years ago
  • Update mindi-busybox to 1.18.3 to avoid problems with the tar command which is now failing on recent versions with busybox 1.7.3
  • Property svn:eol-style set to native
File size: 7.4 KB
Line 
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 GPLv2, see file LICENSE in this source tree.
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}
Note: See TracBrowser for help on using the repository browser.