fakeidentd: fix daemon mode (was thinking that it is in
[oweals/busybox.git] / networking / isrv.c
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 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
24 /* Even if _POSIX_MONOTONIC_CLOCK is defined, this
25  * may require librt */
26 #if 0 /*def _POSIX_MONOTONIC_CLOCK*/
27 static 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
40 struct 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 */
64 void isrv_want_rd(isrv_state_t *state, int fd)
65 {
66         FD_SET(fd, &state->rd);
67 }
68
69 /* callback */
70 void 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 */
79 void isrv_dont_want_rd(isrv_state_t *state, int fd)
80 {
81         FD_CLR(fd, &state->rd);
82 }
83
84 /* callback */
85 void 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 */
94 int 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 */
118 void 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 */
138 int 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
156 static 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
189 static 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
199                  * (for example, non-socket). Don't want
200                  * to loop forever. */
201                 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
210 void BUG_sizeof_fd_set_is_strange(void);
211 static 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;
216         /* need to know value at _the beginning_ of this routine */
217         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:
242                 if (fd >= fd_cnt) { /* paranoia */
243                         DPRINTF("handle_fd_set: fd > fd_cnt?? (%d > %d)",
244                                         fd, fd_cnt);
245                         break;
246                 }
247                 DPRINTF("handle_fd_set: fd %d is active", fd);
248                 peer = FD2PEER[fd];
249                 if (peer < 0)
250                         continue; /* peer is already gone */
251                 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
265 static 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) {
271                 DPRINTF("peer %d: time diff %d", peer,
272                                 (int)(CURTIME - TIMEO_TBL[peer]));
273                 if ((CURTIME - TIMEO_TBL[peer]) >= TIMEOUT) {
274                         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 */
284 void 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,
291         int linger_timeout)
292 {
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;
312                 if (PEER_COUNT <= 1)
313                         tv.tv_sec = linger_timeout;
314                 tv.tv_usec = 0;
315                 rd = state->rd;
316                 if (WR_COUNT) {
317                         wr = state->wr;
318                         wrp = &wr;
319                 }
320
321                 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);
324                 DPRINTF("run: ...select:%d", n);
325
326                 if (n < 0) {
327                         if (errno != EINTR)
328                                 bb_perror_msg("select");
329                         continue;
330                 }
331
332                 if (n == 0 && linger_timeout && PEER_COUNT <= 1)
333                         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");
349         /* NB: accept socket is not closed. Caller is to decide what to do */
350 }