blob: 080c60fbdfaab03a968a492752f270a2c7c20d55 [file] [log] [blame]
Denis Vlasenko7a431b32007-01-14 01:29:06 +00001/* 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 Denis Vlasenko
7 *
8 * Licensed under GPL version 2, see file LICENSE in this tarball for details.
9 */
10
Denis Vlasenkob6adbf12007-05-26 19:00:18 +000011#include "libbb.h"
Denis Vlasenko7a431b32007-01-14 01:29:06 +000012#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
Denis Vlasenko7a431b32007-01-14 01:29:06 +000024/* 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
Denis Vlasenko91f20ab2007-01-20 01:47:44 +0000179 /* suppress gcc warning "cast from ptr to int of different size" */
180 fcntl(fd, F_SETFL, (int)(ptrdiff_t)(PARAM_TBL[0]) | O_NONBLOCK);
Denis Vlasenko7a431b32007-01-14 01:29:06 +0000181 newfd = accept(fd, NULL, 0);
Denis Vlasenko91f20ab2007-01-20 01:47:44 +0000182 fcntl(fd, F_SETFL, (int)(ptrdiff_t)(PARAM_TBL[0]));
Denis Vlasenko7a431b32007-01-14 01:29:06 +0000183 if (newfd < 0) {
184 if (errno == EAGAIN) return;
185 /* Most probably someone gave us wrong fd type
Denis Vlasenko19250812007-01-14 12:07:25 +0000186 * (for example, non-socket). Don't want
187 * to loop forever. */
Denis Vlasenko7a431b32007-01-14 01:29:06 +0000188 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;
Denis Vlasenko19250812007-01-14 12:07:25 +0000203 /* need to know value at _the beginning_ of this routine */
Denis Vlasenko7a431b32007-01-14 01:29:06 +0000204 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:
Denis Vlasenko19250812007-01-14 12:07:25 +0000229 if (fd >= fd_cnt) { /* paranoia */
230 DPRINTF("handle_fd_set: fd > fd_cnt?? (%d > %d)",
231 fd, fd_cnt);
Denis Vlasenko7a431b32007-01-14 01:29:06 +0000232 break;
Denis Vlasenko19250812007-01-14 12:07:25 +0000233 }
Denis Vlasenko7a431b32007-01-14 01:29:06 +0000234 DPRINTF("handle_fd_set: fd %d is active", fd);
235 peer = FD2PEER[fd];
Denis Vlasenko19250812007-01-14 12:07:25 +0000236 if (peer < 0)
237 continue; /* peer is already gone */
Denis Vlasenko7a431b32007-01-14 01:29:06 +0000238 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) {
Denis Vlasenko7bc53602007-08-31 21:45:52 +0000247 TIMEO_TBL[peer] = monotonic_sec();
Denis Vlasenko7a431b32007-01-14 01:29:06 +0000248 }
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) {
Denis Vlasenko19250812007-01-14 12:07:25 +0000258 DPRINTF("peer %d: time diff %d", peer,
259 (int)(CURTIME - TIMEO_TBL[peer]));
260 if ((CURTIME - TIMEO_TBL[peer]) >= TIMEOUT) {
Denis Vlasenko7a431b32007-01-14 01:29:06 +0000261 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,
Denis Vlasenko19250812007-01-14 12:07:25 +0000278 int linger_timeout)
Denis Vlasenko7a431b32007-01-14 01:29:06 +0000279{
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 */
Denis Vlasenko91f20ab2007-01-20 01:47:44 +0000289 /* (suppress gcc warning "cast from ptr to int of different size") */
Denis Vlasenkod37f2222007-08-19 13:42:08 +0000290 PARAM_TBL[0] = (void*)(ptrdiff_t)(fcntl(listen_fd, F_GETFL));
Denis Vlasenko7a431b32007-01-14 01:29:06 +0000291
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;
Denis Vlasenko19250812007-01-14 12:07:25 +0000300 if (PEER_COUNT <= 1)
301 tv.tv_sec = linger_timeout;
Denis Vlasenko7a431b32007-01-14 01:29:06 +0000302 tv.tv_usec = 0;
303 rd = state->rd;
304 if (WR_COUNT) {
305 wr = state->wr;
306 wrp = &wr;
307 }
308
Denis Vlasenko19250812007-01-14 12:07:25 +0000309 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);
Denis Vlasenko7a431b32007-01-14 01:29:06 +0000312 DPRINTF("run: ...select:%d", n);
313
314 if (n < 0) {
315 if (errno != EINTR)
316 bb_perror_msg("select");
317 continue;
318 }
319
Denis Vlasenko19250812007-01-14 12:07:25 +0000320 if (n == 0 && linger_timeout && PEER_COUNT <= 1)
Denis Vlasenko7a431b32007-01-14 01:29:06 +0000321 break;
322
323 if (timeout) {
Denis Vlasenko7bc53602007-08-31 21:45:52 +0000324 time_t t = monotonic_sec();
Denis Vlasenko7a431b32007-01-14 01:29:06 +0000325 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");
Denis Vlasenko19250812007-01-14 12:07:25 +0000337 /* NB: accept socket is not closed. Caller is to decide what to do */
Denis Vlasenko7a431b32007-01-14 01:29:06 +0000338}