fix #>&- syntax for closing fds
[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 Denys Vlasenko
7  *
8  * Licensed under GPL version 2, see file LICENSE in this tarball for details.
9  */
10
11 #include "libbb.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 /* Opaque structure */
25
26 struct 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 */
50 void isrv_want_rd(isrv_state_t *state, int fd)
51 {
52         FD_SET(fd, &state->rd);
53 }
54
55 /* callback */
56 void 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 */
65 void isrv_dont_want_rd(isrv_state_t *state, int fd)
66 {
67         FD_CLR(fd, &state->rd);
68 }
69
70 /* callback */
71 void 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 */
80 int 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 */
104 void 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 */
124 int 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
142 static 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
175 static void handle_accept(isrv_state_t *state, int fd)
176 {
177         int n, newfd;
178
179         /* suppress gcc warning "cast from ptr to int of different size" */
180         fcntl(fd, F_SETFL, (int)(ptrdiff_t)(PARAM_TBL[0]) | O_NONBLOCK);
181         newfd = accept(fd, NULL, 0);
182         fcntl(fd, F_SETFL, (int)(ptrdiff_t)(PARAM_TBL[0]));
183         if (newfd < 0) {
184                 if (errno == EAGAIN) return;
185                 /* Most probably someone gave us wrong fd type
186                  * (for example, non-socket). Don't want
187                  * to loop forever. */
188                 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
197 void BUG_sizeof_fd_set_is_strange(void);
198 static 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;
203         /* need to know value at _the beginning_ of this routine */
204         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:
229                 if (fd >= fd_cnt) { /* paranoia */
230                         DPRINTF("handle_fd_set: fd > fd_cnt?? (%d > %d)",
231                                         fd, fd_cnt);
232                         break;
233                 }
234                 DPRINTF("handle_fd_set: fd %d is active", fd);
235                 peer = FD2PEER[fd];
236                 if (peer < 0)
237                         continue; /* peer is already gone */
238                 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) {
247                         TIMEO_TBL[peer] = monotonic_sec();
248                 }
249         }
250 }
251
252 static 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) {
258                 DPRINTF("peer %d: time diff %d", peer,
259                                 (int)(CURTIME - TIMEO_TBL[peer]));
260                 if ((CURTIME - TIMEO_TBL[peer]) >= TIMEOUT) {
261                         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 */
271 void 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,
278         int linger_timeout)
279 {
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 */
289         /* (suppress gcc warning "cast from ptr to int of different size") */
290         PARAM_TBL[0] = (void*)(ptrdiff_t)(fcntl(listen_fd, F_GETFL));
291
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;
300                 if (PEER_COUNT <= 1)
301                         tv.tv_sec = linger_timeout;
302                 tv.tv_usec = 0;
303                 rd = state->rd;
304                 if (WR_COUNT) {
305                         wr = state->wr;
306                         wrp = &wr;
307                 }
308
309                 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);
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 (n == 0 && linger_timeout && PEER_COUNT <= 1)
321                         break;
322
323                 if (timeout) {
324                         time_t t = monotonic_sec();
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         /* NB: accept socket is not closed. Caller is to decide what to do */
338 }