1 /* vi: set sw=4 ts=4: */
3 * Generic non-forking server infrastructure.
4 * Intended to make writing telnetd-type servers easier.
6 * Copyright (C) 2007 Denys Vlasenko
8 * Licensed under GPLv2, see file LICENSE in this source tree.
17 #define DPRINTF(args...) bb_error_msg(args)
19 #define DPRINTF(args...) ((void)0)
24 /* Opaque structure */
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 */
31 int (*new_peer)(isrv_state_t *state, int fd);
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)
50 void isrv_want_rd(isrv_state_t *state, int fd)
52 FD_SET(fd, &state->rd);
56 void isrv_want_wr(isrv_state_t *state, int fd)
58 if (!FD_ISSET(fd, &state->wr)) {
60 FD_SET(fd, &state->wr);
65 void isrv_dont_want_rd(isrv_state_t *state, int fd)
67 FD_CLR(fd, &state->rd);
71 void isrv_dont_want_wr(isrv_state_t *state, int fd)
73 if (FD_ISSET(fd, &state->wr)) {
75 FD_CLR(fd, &state->wr);
80 int isrv_register_fd(isrv_state_t *state, int peer, int fd)
84 DPRINTF("register_fd(peer:%d,fd:%d)", peer, fd);
86 if (FD_COUNT >= FD_SETSIZE) return -1;
91 DPRINTF("register_fd: FD_COUNT %d", FD_COUNT);
93 FD2PEER = xrealloc(FD2PEER, FD_COUNT * sizeof(FD2PEER[0]));
94 while (n < fd) FD2PEER[n++] = -1;
97 DPRINTF("register_fd: FD2PEER[%d] = %d", fd, peer);
104 void isrv_close_fd(isrv_state_t *state, int fd)
106 DPRINTF("close_fd(%d)", fd);
109 isrv_dont_want_rd(state, fd);
110 if (WR_COUNT) isrv_dont_want_wr(state, fd);
113 if (fd == FD_COUNT-1) {
114 do fd--; while (fd >= 0 && FD2PEER[fd] == -1);
117 DPRINTF("close_fd: FD_COUNT %d", FD_COUNT);
119 FD2PEER = xrealloc(FD2PEER, FD_COUNT * sizeof(FD2PEER[0]));
124 int isrv_register_peer(isrv_state_t *state, void *param)
128 if (PEER_COUNT >= FD_SETSIZE) return -1;
131 DPRINTF("register_peer: PEER_COUNT %d", PEER_COUNT);
133 PARAM_TBL = xrealloc(PARAM_TBL, PEER_COUNT * sizeof(PARAM_TBL[0]));
134 PARAM_TBL[n] = param;
136 TIMEO_TBL = xrealloc(TIMEO_TBL, PEER_COUNT * sizeof(TIMEO_TBL[0]));
137 TIMEO_TBL[n] = CURTIME;
142 static void remove_peer(isrv_state_t *state, int peer)
147 DPRINTF("remove_peer(%d)", peer);
151 if (FD2PEER[fd] == peer) {
152 isrv_close_fd(state, fd);
156 if (FD2PEER[fd] > peer)
162 DPRINTF("remove_peer: PEER_COUNT %d", PEER_COUNT);
164 movesize = (PEER_COUNT - peer) * sizeof(void*);
166 memcpy(&PARAM_TBL[peer], &PARAM_TBL[peer+1], movesize);
168 memcpy(&TIMEO_TBL[peer], &TIMEO_TBL[peer+1], movesize);
170 PARAM_TBL = xrealloc(PARAM_TBL, PEER_COUNT * sizeof(PARAM_TBL[0]));
172 TIMEO_TBL = xrealloc(TIMEO_TBL, PEER_COUNT * sizeof(TIMEO_TBL[0]));
175 static void handle_accept(isrv_state_t *state, int fd)
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]));
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");
191 DPRINTF("new_peer(%d)", newfd);
192 n = state->new_peer(state, newfd);
194 remove_peer(state, n); /* unsuccessful peer start */
197 static void handle_fd_set(isrv_state_t *state, fd_set *fds, int (*h)(int, void **))
199 enum { LONG_CNT = sizeof(fd_set) / sizeof(long) };
202 /* need to know value at _the beginning_ of this routine */
203 int fd_cnt = FD_COUNT;
205 BUILD_BUG_ON(LONG_CNT * sizeof(long) != sizeof(fd_set));
209 /* Find next nonzero bit */
210 while (fds_pos < LONG_CNT) {
211 if (((long*)fds)[fds_pos] == 0) {
215 /* Found non-zero word */
216 fd = fds_pos * sizeof(long)*8; /* word# -> bit# */
218 if (FD_ISSET(fd, fds)) {
225 break; /* all words are zero */
227 if (fd >= fd_cnt) { /* paranoia */
228 DPRINTF("handle_fd_set: fd > fd_cnt?? (%d > %d)",
232 DPRINTF("handle_fd_set: fd %d is active", fd);
235 continue; /* peer is already gone */
237 handle_accept(state, fd);
240 DPRINTF("h(fd:%d)", fd);
241 if (h(fd, &PARAM_TBL[peer])) {
242 /* this peer is gone */
243 remove_peer(state, peer);
244 } else if (TIMEOUT) {
245 TIMEO_TBL[peer] = monotonic_sec();
250 static void handle_timeout(isrv_state_t *state, int (*do_timeout)(void **))
254 /* peer 0 is not checked */
256 DPRINTF("peer %d: time diff %d", peer,
257 (int)(CURTIME - TIMEO_TBL[peer]));
258 if ((CURTIME - TIMEO_TBL[peer]) >= TIMEOUT) {
259 DPRINTF("peer %d: do_timeout()", peer);
260 n = do_timeout(&PARAM_TBL[peer]);
262 remove_peer(state, peer);
271 int (*new_peer)(isrv_state_t *state, int fd),
272 int (*do_rd)(int fd, void **),
273 int (*do_wr)(int fd, void **),
274 int (*do_timeout)(void **),
278 isrv_state_t *state = xzalloc(sizeof(*state));
279 state->new_peer = new_peer;
280 state->timeout = timeout;
282 /* register "peer" #0 - it will accept new connections */
283 isrv_register_peer(state, NULL);
284 isrv_register_fd(state, /*peer:*/ 0, listen_fd);
285 isrv_want_rd(state, listen_fd);
286 /* remember flags to make blocking<->nonblocking switch faster */
287 /* (suppress gcc warning "cast from ptr to int of different size") */
288 PARAM_TBL[0] = (void*)(ptrdiff_t)(fcntl(listen_fd, F_GETFL));
299 tv.tv_sec = linger_timeout;
307 DPRINTF("run: select(FD_COUNT:%d,timeout:%d)...",
308 FD_COUNT, (int)tv.tv_sec);
309 n = select(FD_COUNT, &rd, wrp, NULL, tv.tv_sec ? &tv : NULL);
310 DPRINTF("run: ...select:%d", n);
314 bb_perror_msg("select");
318 if (n == 0 && linger_timeout && PEER_COUNT <= 1)
322 time_t t = monotonic_sec();
325 handle_timeout(state, do_timeout);
329 handle_fd_set(state, &rd, do_rd);
331 handle_fd_set(state, wrp, do_wr);
334 DPRINTF("run: bailout");
335 /* NB: accept socket is not closed. Caller is to decide what to do */