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 Denis Vlasenko
8 * Licensed under GPL version 2, see file LICENSE in this tarball for details.
17 #define DPRINTF(args...) bb_error_msg(args)
19 #define DPRINTF(args...) ((void)0)
24 #if 0 /*def _POSIX_MONOTONIC_CLOCK*/
25 static time_t monotonic_time(void)
28 if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0)
33 #define monotonic_time() (time(NULL))
36 /* Opaque structure */
39 short *fd2peer; /* one per registered fd */
40 void **param_tbl; /* one per registered peer */
41 /* one per registered peer; doesn't exist if !timeout */
43 int (*new_peer)(isrv_state_t *state, int fd);
52 #define FD2PEER (state->fd2peer)
53 #define PARAM_TBL (state->param_tbl)
54 #define TIMEO_TBL (state->timeo_tbl)
55 #define CURTIME (state->curtime)
56 #define TIMEOUT (state->timeout)
57 #define FD_COUNT (state->fd_count)
58 #define PEER_COUNT (state->peer_count)
59 #define WR_COUNT (state->wr_count)
62 void isrv_want_rd(isrv_state_t *state, int fd)
64 FD_SET(fd, &state->rd);
68 void isrv_want_wr(isrv_state_t *state, int fd)
70 if (!FD_ISSET(fd, &state->wr)) {
72 FD_SET(fd, &state->wr);
77 void isrv_dont_want_rd(isrv_state_t *state, int fd)
79 FD_CLR(fd, &state->rd);
83 void isrv_dont_want_wr(isrv_state_t *state, int fd)
85 if (FD_ISSET(fd, &state->wr)) {
87 FD_CLR(fd, &state->wr);
92 int isrv_register_fd(isrv_state_t *state, int peer, int fd)
96 DPRINTF("register_fd(peer:%d,fd:%d)", peer, fd);
98 if (FD_COUNT >= FD_SETSIZE) return -1;
103 DPRINTF("register_fd: FD_COUNT %d", FD_COUNT);
105 FD2PEER = xrealloc(FD2PEER, FD_COUNT * sizeof(FD2PEER[0]));
106 while (n < fd) FD2PEER[n++] = -1;
109 DPRINTF("register_fd: FD2PEER[%d] = %d", fd, peer);
116 void isrv_close_fd(isrv_state_t *state, int fd)
118 DPRINTF("close_fd(%d)", fd);
121 isrv_dont_want_rd(state, fd);
122 if (WR_COUNT) isrv_dont_want_wr(state, fd);
125 if (fd == FD_COUNT-1) {
126 do fd--; while (fd >= 0 && FD2PEER[fd] == -1);
129 DPRINTF("close_fd: FD_COUNT %d", FD_COUNT);
131 FD2PEER = xrealloc(FD2PEER, FD_COUNT * sizeof(FD2PEER[0]));
136 int isrv_register_peer(isrv_state_t *state, void *param)
140 if (PEER_COUNT >= FD_SETSIZE) return -1;
143 DPRINTF("register_peer: PEER_COUNT %d", PEER_COUNT);
145 PARAM_TBL = xrealloc(PARAM_TBL, PEER_COUNT * sizeof(PARAM_TBL[0]));
146 PARAM_TBL[n] = param;
148 TIMEO_TBL = xrealloc(TIMEO_TBL, PEER_COUNT * sizeof(TIMEO_TBL[0]));
149 TIMEO_TBL[n] = CURTIME;
154 static void remove_peer(isrv_state_t *state, int peer)
159 DPRINTF("remove_peer(%d)", peer);
163 if (FD2PEER[fd] == peer) {
164 isrv_close_fd(state, fd);
168 if (FD2PEER[fd] > peer)
174 DPRINTF("remove_peer: PEER_COUNT %d", PEER_COUNT);
176 movesize = (PEER_COUNT - peer) * sizeof(void*);
178 memcpy(&PARAM_TBL[peer], &PARAM_TBL[peer+1], movesize);
180 memcpy(&TIMEO_TBL[peer], &TIMEO_TBL[peer+1], movesize);
182 PARAM_TBL = xrealloc(PARAM_TBL, PEER_COUNT * sizeof(PARAM_TBL[0]));
184 TIMEO_TBL = xrealloc(TIMEO_TBL, PEER_COUNT * sizeof(TIMEO_TBL[0]));
187 static void handle_accept(isrv_state_t *state, int fd)
191 fcntl(fd, F_SETFL, (int)(PARAM_TBL[0]) | O_NONBLOCK);
192 newfd = accept(fd, NULL, 0);
193 fcntl(fd, F_SETFL, (int)(PARAM_TBL[0]));
195 if (errno == EAGAIN) return;
196 /* Most probably someone gave us wrong fd type
197 * (for example, non-socket) */
198 bb_perror_msg_and_die("accept");
201 DPRINTF("new_peer(%d)", newfd);
202 n = state->new_peer(state, newfd);
204 remove_peer(state, n); /* unsuccesful peer start */
207 void BUG_sizeof_fd_set_is_strange(void);
208 static void handle_fd_set(isrv_state_t *state, fd_set *fds, int (*h)(int, void **))
210 enum { LONG_CNT = sizeof(fd_set) / sizeof(long) };
213 int fd_cnt = FD_COUNT;
215 if (LONG_CNT * sizeof(long) != sizeof(fd_set))
216 BUG_sizeof_fd_set_is_strange();
220 /* Find next nonzero bit */
221 while (fds_pos < LONG_CNT) {
222 if (((long*)fds)[fds_pos] == 0) {
226 /* Found non-zero word */
227 fd = fds_pos * sizeof(long)*8; /* word# -> bit# */
229 if (FD_ISSET(fd, fds)) {
236 break; /* all words are zero */
238 if (fd >= fd_cnt) /* paranoia */
240 DPRINTF("handle_fd_set: fd %d is active", fd);
243 handle_accept(state, fd);
246 DPRINTF("h(fd:%d)", fd);
247 if (h(fd, &PARAM_TBL[peer])) {
248 /* this peer is gone */
249 remove_peer(state, peer);
250 } else if (TIMEOUT) {
251 TIMEO_TBL[peer] = monotonic_time();
256 static void handle_timeout(isrv_state_t *state, int (*do_timeout)(void **))
260 /* peer 0 is not checked */
262 DPRINTF("peer %d: time diff %d", peer, (int)(CURTIME - TIMEO_TBL[peer]));
264 if ((CURTIME - TIMEO_TBL[peer]) > TIMEOUT) {
265 DPRINTF("peer %d: do_timeout()", peer);
266 n = do_timeout(&PARAM_TBL[peer]);
268 remove_peer(state, peer);
277 int (*new_peer)(isrv_state_t *state, int fd),
278 int (*do_rd)(int fd, void **),
279 int (*do_wr)(int fd, void **),
280 int (*do_timeout)(void **),
282 int exit_if_no_clients)
284 isrv_state_t *state = xzalloc(sizeof(*state));
285 state->new_peer = new_peer;
286 state->timeout = timeout;
288 /* register "peer" #0 - it will accept new connections */
289 isrv_register_peer(state, NULL);
290 isrv_register_fd(state, /*peer:*/ 0, listen_fd);
291 isrv_want_rd(state, listen_fd);
292 /* remember flags to make blocking<->nonblocking switch faster */
293 PARAM_TBL[0] = (void*) (fcntl(listen_fd, F_GETFL, 0));
310 DPRINTF("run: select(FD_COUNT:%d,timeout:%d)...", FD_COUNT, timeout);
311 n = select(FD_COUNT, &rd, wrp, NULL, timeout ? &tv : NULL);
312 DPRINTF("run: ...select:%d", n);
316 bb_perror_msg("select");
320 if (exit_if_no_clients && n == 0 && PEER_COUNT <= 1)
324 time_t t = monotonic_time();
327 handle_timeout(state, do_timeout);
331 handle_fd_set(state, &rd, do_rd);
333 handle_fd_set(state, wrp, do_wr);
336 DPRINTF("run: bailout");