blob: 5193f30f33a8a8665bf388cdadfcefa5754ff7e1 [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
11#include "busybox.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
Denis Vlasenko19250812007-01-14 12:07:25 +000024/* Even if _POSIX_MONOTONIC_CLOCK is defined, this
25 * may require librt */
Denis Vlasenko7a431b32007-01-14 01:29:06 +000026#if 0 /*def _POSIX_MONOTONIC_CLOCK*/
27static time_t monotonic_time(void)
28{
29 struct timespec ts;
30 if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0)
31 time(&ts.tv_sec);
32 return ts.tv_sec;
33}
34#else
35#define monotonic_time() (time(NULL))
36#endif
37
38/* Opaque structure */
39
40struct isrv_state_t {
41 short *fd2peer; /* one per registered fd */
42 void **param_tbl; /* one per registered peer */
43 /* one per registered peer; doesn't exist if !timeout */
44 time_t *timeo_tbl;
45 int (*new_peer)(isrv_state_t *state, int fd);
46 time_t curtime;
47 int timeout;
48 int fd_count;
49 int peer_count;
50 int wr_count;
51 fd_set rd;
52 fd_set wr;
53};
54#define FD2PEER (state->fd2peer)
55#define PARAM_TBL (state->param_tbl)
56#define TIMEO_TBL (state->timeo_tbl)
57#define CURTIME (state->curtime)
58#define TIMEOUT (state->timeout)
59#define FD_COUNT (state->fd_count)
60#define PEER_COUNT (state->peer_count)
61#define WR_COUNT (state->wr_count)
62
63/* callback */
64void isrv_want_rd(isrv_state_t *state, int fd)
65{
66 FD_SET(fd, &state->rd);
67}
68
69/* callback */
70void isrv_want_wr(isrv_state_t *state, int fd)
71{
72 if (!FD_ISSET(fd, &state->wr)) {
73 WR_COUNT++;
74 FD_SET(fd, &state->wr);
75 }
76}
77
78/* callback */
79void isrv_dont_want_rd(isrv_state_t *state, int fd)
80{
81 FD_CLR(fd, &state->rd);
82}
83
84/* callback */
85void isrv_dont_want_wr(isrv_state_t *state, int fd)
86{
87 if (FD_ISSET(fd, &state->wr)) {
88 WR_COUNT--;
89 FD_CLR(fd, &state->wr);
90 }
91}
92
93/* callback */
94int isrv_register_fd(isrv_state_t *state, int peer, int fd)
95{
96 int n;
97
98 DPRINTF("register_fd(peer:%d,fd:%d)", peer, fd);
99
100 if (FD_COUNT >= FD_SETSIZE) return -1;
101 if (FD_COUNT <= fd) {
102 n = FD_COUNT;
103 FD_COUNT = fd + 1;
104
105 DPRINTF("register_fd: FD_COUNT %d", FD_COUNT);
106
107 FD2PEER = xrealloc(FD2PEER, FD_COUNT * sizeof(FD2PEER[0]));
108 while (n < fd) FD2PEER[n++] = -1;
109 }
110
111 DPRINTF("register_fd: FD2PEER[%d] = %d", fd, peer);
112
113 FD2PEER[fd] = peer;
114 return 0;
115}
116
117/* callback */
118void isrv_close_fd(isrv_state_t *state, int fd)
119{
120 DPRINTF("close_fd(%d)", fd);
121
122 close(fd);
123 isrv_dont_want_rd(state, fd);
124 if (WR_COUNT) isrv_dont_want_wr(state, fd);
125
126 FD2PEER[fd] = -1;
127 if (fd == FD_COUNT-1) {
128 do fd--; while (fd >= 0 && FD2PEER[fd] == -1);
129 FD_COUNT = fd + 1;
130
131 DPRINTF("close_fd: FD_COUNT %d", FD_COUNT);
132
133 FD2PEER = xrealloc(FD2PEER, FD_COUNT * sizeof(FD2PEER[0]));
134 }
135}
136
137/* callback */
138int isrv_register_peer(isrv_state_t *state, void *param)
139{
140 int n;
141
142 if (PEER_COUNT >= FD_SETSIZE) return -1;
143 n = PEER_COUNT++;
144
145 DPRINTF("register_peer: PEER_COUNT %d", PEER_COUNT);
146
147 PARAM_TBL = xrealloc(PARAM_TBL, PEER_COUNT * sizeof(PARAM_TBL[0]));
148 PARAM_TBL[n] = param;
149 if (TIMEOUT) {
150 TIMEO_TBL = xrealloc(TIMEO_TBL, PEER_COUNT * sizeof(TIMEO_TBL[0]));
151 TIMEO_TBL[n] = CURTIME;
152 }
153 return n;
154}
155
156static void remove_peer(isrv_state_t *state, int peer)
157{
158 int movesize;
159 int fd;
160
161 DPRINTF("remove_peer(%d)", peer);
162
163 fd = FD_COUNT - 1;
164 while (fd >= 0) {
165 if (FD2PEER[fd] == peer) {
166 isrv_close_fd(state, fd);
167 fd--;
168 continue;
169 }
170 if (FD2PEER[fd] > peer)
171 FD2PEER[fd]--;
172 fd--;
173 }
174
175 PEER_COUNT--;
176 DPRINTF("remove_peer: PEER_COUNT %d", PEER_COUNT);
177
178 movesize = (PEER_COUNT - peer) * sizeof(void*);
179 if (movesize > 0) {
180 memcpy(&PARAM_TBL[peer], &PARAM_TBL[peer+1], movesize);
181 if (TIMEOUT)
182 memcpy(&TIMEO_TBL[peer], &TIMEO_TBL[peer+1], movesize);
183 }
184 PARAM_TBL = xrealloc(PARAM_TBL, PEER_COUNT * sizeof(PARAM_TBL[0]));
185 if (TIMEOUT)
186 TIMEO_TBL = xrealloc(TIMEO_TBL, PEER_COUNT * sizeof(TIMEO_TBL[0]));
187}
188
189static void handle_accept(isrv_state_t *state, int fd)
190{
191 int n, newfd;
192
193 fcntl(fd, F_SETFL, (int)(PARAM_TBL[0]) | O_NONBLOCK);
194 newfd = accept(fd, NULL, 0);
195 fcntl(fd, F_SETFL, (int)(PARAM_TBL[0]));
196 if (newfd < 0) {
197 if (errno == EAGAIN) return;
198 /* Most probably someone gave us wrong fd type
Denis Vlasenko19250812007-01-14 12:07:25 +0000199 * (for example, non-socket). Don't want
200 * to loop forever. */
Denis Vlasenko7a431b32007-01-14 01:29:06 +0000201 bb_perror_msg_and_die("accept");
202 }
203
204 DPRINTF("new_peer(%d)", newfd);
205 n = state->new_peer(state, newfd);
206 if (n)
207 remove_peer(state, n); /* unsuccesful peer start */
208}
209
210void BUG_sizeof_fd_set_is_strange(void);
211static void handle_fd_set(isrv_state_t *state, fd_set *fds, int (*h)(int, void **))
212{
213 enum { LONG_CNT = sizeof(fd_set) / sizeof(long) };
214 int fds_pos;
215 int fd, peer;
Denis Vlasenko19250812007-01-14 12:07:25 +0000216 /* need to know value at _the beginning_ of this routine */
Denis Vlasenko7a431b32007-01-14 01:29:06 +0000217 int fd_cnt = FD_COUNT;
218
219 if (LONG_CNT * sizeof(long) != sizeof(fd_set))
220 BUG_sizeof_fd_set_is_strange();
221
222 fds_pos = 0;
223 while (1) {
224 /* Find next nonzero bit */
225 while (fds_pos < LONG_CNT) {
226 if (((long*)fds)[fds_pos] == 0) {
227 fds_pos++;
228 continue;
229 }
230 /* Found non-zero word */
231 fd = fds_pos * sizeof(long)*8; /* word# -> bit# */
232 while (1) {
233 if (FD_ISSET(fd, fds)) {
234 FD_CLR(fd, fds);
235 goto found_fd;
236 }
237 fd++;
238 }
239 }
240 break; /* all words are zero */
241 found_fd:
Denis Vlasenko19250812007-01-14 12:07:25 +0000242 if (fd >= fd_cnt) { /* paranoia */
243 DPRINTF("handle_fd_set: fd > fd_cnt?? (%d > %d)",
244 fd, fd_cnt);
Denis Vlasenko7a431b32007-01-14 01:29:06 +0000245 break;
Denis Vlasenko19250812007-01-14 12:07:25 +0000246 }
Denis Vlasenko7a431b32007-01-14 01:29:06 +0000247 DPRINTF("handle_fd_set: fd %d is active", fd);
248 peer = FD2PEER[fd];
Denis Vlasenko19250812007-01-14 12:07:25 +0000249 if (peer < 0)
250 continue; /* peer is already gone */
Denis Vlasenko7a431b32007-01-14 01:29:06 +0000251 if (peer == 0) {
252 handle_accept(state, fd);
253 continue;
254 }
255 DPRINTF("h(fd:%d)", fd);
256 if (h(fd, &PARAM_TBL[peer])) {
257 /* this peer is gone */
258 remove_peer(state, peer);
259 } else if (TIMEOUT) {
260 TIMEO_TBL[peer] = monotonic_time();
261 }
262 }
263}
264
265static void handle_timeout(isrv_state_t *state, int (*do_timeout)(void **))
266{
267 int n, peer;
268 peer = PEER_COUNT-1;
269 /* peer 0 is not checked */
270 while (peer > 0) {
Denis Vlasenko19250812007-01-14 12:07:25 +0000271 DPRINTF("peer %d: time diff %d", peer,
272 (int)(CURTIME - TIMEO_TBL[peer]));
273 if ((CURTIME - TIMEO_TBL[peer]) >= TIMEOUT) {
Denis Vlasenko7a431b32007-01-14 01:29:06 +0000274 DPRINTF("peer %d: do_timeout()", peer);
275 n = do_timeout(&PARAM_TBL[peer]);
276 if (n)
277 remove_peer(state, peer);
278 }
279 peer--;
280 }
281}
282
283/* Driver */
284void isrv_run(
285 int listen_fd,
286 int (*new_peer)(isrv_state_t *state, int fd),
287 int (*do_rd)(int fd, void **),
288 int (*do_wr)(int fd, void **),
289 int (*do_timeout)(void **),
290 int timeout,
Denis Vlasenko19250812007-01-14 12:07:25 +0000291 int linger_timeout)
Denis Vlasenko7a431b32007-01-14 01:29:06 +0000292{
293 isrv_state_t *state = xzalloc(sizeof(*state));
294 state->new_peer = new_peer;
295 state->timeout = timeout;
296
297 /* register "peer" #0 - it will accept new connections */
298 isrv_register_peer(state, NULL);
299 isrv_register_fd(state, /*peer:*/ 0, listen_fd);
300 isrv_want_rd(state, listen_fd);
301 /* remember flags to make blocking<->nonblocking switch faster */
302 PARAM_TBL[0] = (void*) (fcntl(listen_fd, F_GETFL, 0));
303
304 while (1) {
305 struct timeval tv;
306 fd_set rd;
307 fd_set wr;
308 fd_set *wrp = NULL;
309 int n;
310
311 tv.tv_sec = timeout;
Denis Vlasenko19250812007-01-14 12:07:25 +0000312 if (PEER_COUNT <= 1)
313 tv.tv_sec = linger_timeout;
Denis Vlasenko7a431b32007-01-14 01:29:06 +0000314 tv.tv_usec = 0;
315 rd = state->rd;
316 if (WR_COUNT) {
317 wr = state->wr;
318 wrp = &wr;
319 }
320
Denis Vlasenko19250812007-01-14 12:07:25 +0000321 DPRINTF("run: select(FD_COUNT:%d,timeout:%d)...",
322 FD_COUNT, (int)tv.tv_sec);
323 n = select(FD_COUNT, &rd, wrp, NULL, tv.tv_sec ? &tv : NULL);
Denis Vlasenko7a431b32007-01-14 01:29:06 +0000324 DPRINTF("run: ...select:%d", n);
325
326 if (n < 0) {
327 if (errno != EINTR)
328 bb_perror_msg("select");
329 continue;
330 }
331
Denis Vlasenko19250812007-01-14 12:07:25 +0000332 if (n == 0 && linger_timeout && PEER_COUNT <= 1)
Denis Vlasenko7a431b32007-01-14 01:29:06 +0000333 break;
334
335 if (timeout) {
336 time_t t = monotonic_time();
337 if (t != CURTIME) {
338 CURTIME = t;
339 handle_timeout(state, do_timeout);
340 }
341 }
342 if (n > 0) {
343 handle_fd_set(state, &rd, do_rd);
344 if (wrp)
345 handle_fd_set(state, wrp, do_wr);
346 }
347 }
348 DPRINTF("run: bailout");
Denis Vlasenko19250812007-01-14 12:07:25 +0000349 /* NB: accept socket is not closed. Caller is to decide what to do */
Denis Vlasenko7a431b32007-01-14 01:29:06 +0000350}