By popular request reinstate fakeidentd's standalone mode.
[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 #if 0 /*def _POSIX_MONOTONIC_CLOCK*/
25 static time_t monotonic_time(void)
26 {
27         struct timespec ts;
28         if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0)
29                 time(&ts.tv_sec);
30         return ts.tv_sec;
31 }
32 #else
33 #define monotonic_time() (time(NULL))
34 #endif
35
36 /* Opaque structure */
37
38 struct isrv_state_t {
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 */
42         time_t *timeo_tbl;
43         int   (*new_peer)(isrv_state_t *state, int fd);
44         time_t  curtime;
45         int     timeout;
46         int     fd_count;
47         int     peer_count;
48         int     wr_count;
49         fd_set  rd;
50         fd_set  wr;
51 };
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)
60
61 /* callback */
62 void isrv_want_rd(isrv_state_t *state, int fd)
63 {
64         FD_SET(fd, &state->rd);
65 }
66
67 /* callback */
68 void isrv_want_wr(isrv_state_t *state, int fd)
69 {
70         if (!FD_ISSET(fd, &state->wr)) {
71                 WR_COUNT++;
72                 FD_SET(fd, &state->wr);
73         }
74 }
75
76 /* callback */
77 void isrv_dont_want_rd(isrv_state_t *state, int fd)
78 {
79         FD_CLR(fd, &state->rd);
80 }
81
82 /* callback */
83 void isrv_dont_want_wr(isrv_state_t *state, int fd)
84 {
85         if (FD_ISSET(fd, &state->wr)) {
86                 WR_COUNT--;
87                 FD_CLR(fd, &state->wr);
88         }
89 }
90
91 /* callback */
92 int isrv_register_fd(isrv_state_t *state, int peer, int fd)
93 {
94         int n;
95
96         DPRINTF("register_fd(peer:%d,fd:%d)", peer, fd);
97
98         if (FD_COUNT >= FD_SETSIZE) return -1;
99         if (FD_COUNT <= fd) {
100                 n = FD_COUNT;
101                 FD_COUNT = fd + 1;
102
103                 DPRINTF("register_fd: FD_COUNT %d", FD_COUNT);
104
105                 FD2PEER = xrealloc(FD2PEER, FD_COUNT * sizeof(FD2PEER[0]));
106                 while (n < fd) FD2PEER[n++] = -1;
107         }
108
109         DPRINTF("register_fd: FD2PEER[%d] = %d", fd, peer);
110
111         FD2PEER[fd] = peer;
112         return 0;
113 }
114
115 /* callback */
116 void isrv_close_fd(isrv_state_t *state, int fd)
117 {
118         DPRINTF("close_fd(%d)", fd);
119
120         close(fd);
121         isrv_dont_want_rd(state, fd);
122         if (WR_COUNT) isrv_dont_want_wr(state, fd);
123
124         FD2PEER[fd] = -1;
125         if (fd == FD_COUNT-1) {
126                 do fd--; while (fd >= 0 && FD2PEER[fd] == -1);
127                 FD_COUNT = fd + 1;
128
129                 DPRINTF("close_fd: FD_COUNT %d", FD_COUNT);
130
131                 FD2PEER = xrealloc(FD2PEER, FD_COUNT * sizeof(FD2PEER[0]));
132         }
133 }
134
135 /* callback */
136 int isrv_register_peer(isrv_state_t *state, void *param)
137 {
138         int n;
139
140         if (PEER_COUNT >= FD_SETSIZE) return -1;
141         n = PEER_COUNT++;
142
143         DPRINTF("register_peer: PEER_COUNT %d", PEER_COUNT);
144
145         PARAM_TBL = xrealloc(PARAM_TBL, PEER_COUNT * sizeof(PARAM_TBL[0]));
146         PARAM_TBL[n] = param;
147         if (TIMEOUT) {
148                 TIMEO_TBL = xrealloc(TIMEO_TBL, PEER_COUNT * sizeof(TIMEO_TBL[0]));
149                 TIMEO_TBL[n] = CURTIME;
150         }
151         return n;
152 }
153
154 static void remove_peer(isrv_state_t *state, int peer)
155 {
156         int movesize;
157         int fd;
158
159         DPRINTF("remove_peer(%d)", peer);
160
161         fd = FD_COUNT - 1;
162         while (fd >= 0) {
163                 if (FD2PEER[fd] == peer) {
164                         isrv_close_fd(state, fd);
165                         fd--;
166                         continue;
167                 }
168                 if (FD2PEER[fd] > peer)
169                         FD2PEER[fd]--;
170                 fd--;
171         }
172
173         PEER_COUNT--;
174         DPRINTF("remove_peer: PEER_COUNT %d", PEER_COUNT);
175
176         movesize = (PEER_COUNT - peer) * sizeof(void*);
177         if (movesize > 0) {
178                 memcpy(&PARAM_TBL[peer], &PARAM_TBL[peer+1], movesize);
179                 if (TIMEOUT)
180                         memcpy(&TIMEO_TBL[peer], &TIMEO_TBL[peer+1], movesize);
181         }
182         PARAM_TBL = xrealloc(PARAM_TBL, PEER_COUNT * sizeof(PARAM_TBL[0]));
183         if (TIMEOUT)
184                 TIMEO_TBL = xrealloc(TIMEO_TBL, PEER_COUNT * sizeof(TIMEO_TBL[0]));
185 }
186
187 static void handle_accept(isrv_state_t *state, int fd)
188 {
189         int n, newfd;
190
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]));
194         if (newfd < 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");
199         }
200
201         DPRINTF("new_peer(%d)", newfd);
202         n = state->new_peer(state, newfd);
203         if (n)
204                 remove_peer(state, n); /* unsuccesful peer start */
205 }
206
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 **))
209 {
210         enum { LONG_CNT = sizeof(fd_set) / sizeof(long) };
211         int fds_pos;
212         int fd, peer;
213         int fd_cnt = FD_COUNT;
214
215         if (LONG_CNT * sizeof(long) != sizeof(fd_set))
216                 BUG_sizeof_fd_set_is_strange();
217
218         fds_pos = 0;
219         while (1) {
220                 /* Find next nonzero bit */
221                 while (fds_pos < LONG_CNT) {
222                         if (((long*)fds)[fds_pos] == 0) {
223                                 fds_pos++;
224                                 continue;
225                         }
226                         /* Found non-zero word */
227                         fd = fds_pos * sizeof(long)*8; /* word# -> bit# */
228                         while (1) {
229                                 if (FD_ISSET(fd, fds)) {
230                                         FD_CLR(fd, fds);
231                                         goto found_fd;
232                                 }
233                                 fd++;
234                         }
235                 }
236                 break; /* all words are zero */
237  found_fd:
238                 if (fd >= fd_cnt) /* paranoia */
239                         break;
240                 DPRINTF("handle_fd_set: fd %d is active", fd);
241                 peer = FD2PEER[fd];
242                 if (peer == 0) {
243                         handle_accept(state, fd);
244                         continue;
245                 }
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();
252                 }
253         }
254 }
255
256 static void handle_timeout(isrv_state_t *state, int (*do_timeout)(void **))
257 {
258         int n, peer;
259         peer = PEER_COUNT-1;
260         /* peer 0 is not checked */
261         while (peer > 0) {
262                 DPRINTF("peer %d: time diff %d", peer, (int)(CURTIME - TIMEO_TBL[peer]));
263
264                 if ((CURTIME - TIMEO_TBL[peer]) > TIMEOUT) {
265                         DPRINTF("peer %d: do_timeout()", peer);
266                         n = do_timeout(&PARAM_TBL[peer]);
267                         if (n)
268                                 remove_peer(state, peer);
269                 }
270                 peer--;
271         }
272 }
273
274 /* Driver */
275 void isrv_run(
276         int listen_fd,
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 **),
281         int timeout,
282         int exit_if_no_clients)
283 {
284         isrv_state_t *state = xzalloc(sizeof(*state));
285         state->new_peer = new_peer;
286         state->timeout  = timeout;
287
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));
294
295         while (1) {
296                 struct timeval tv;
297                 fd_set rd;
298                 fd_set wr;
299                 fd_set *wrp = NULL;
300                 int n;
301
302                 tv.tv_sec = timeout;
303                 tv.tv_usec = 0;
304                 rd = state->rd;
305                 if (WR_COUNT) {
306                         wr = state->wr;
307                         wrp = &wr;
308                 }
309
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);
313
314                 if (n < 0) {
315                         if (errno != EINTR)
316                                 bb_perror_msg("select");
317                         continue;
318                 }
319
320                 if (exit_if_no_clients && n == 0 && PEER_COUNT <= 1)
321                         break;
322
323                 if (timeout) {
324                         time_t t = monotonic_time();
325                         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");
337 }