1 /* vi: set sw=4 ts=4: */
3 * Generic non-forking server infrastructure.
4 * Intended to make writing telnetd-type servers easier.
6 * Copyright (C) 2007 Denys Vlasenko
8 * Licensed under GPLv2, see file LICENSE in this source tree.
17 #define DPRINTF(args...) bb_error_msg(args)
19 #define DPRINTF(args...) ((void)0)
24 /* Opaque structure */
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 */
31 int (*new_peer
)(isrv_state_t
*state
, int fd
);
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)
50 void isrv_want_rd(isrv_state_t
*state
, int fd
)
52 FD_SET(fd
, &state
->rd
);
56 void isrv_want_wr(isrv_state_t
*state
, int fd
)
58 if (!FD_ISSET(fd
, &state
->wr
)) {
60 FD_SET(fd
, &state
->wr
);
65 void isrv_dont_want_rd(isrv_state_t
*state
, int fd
)
67 FD_CLR(fd
, &state
->rd
);
71 void isrv_dont_want_wr(isrv_state_t
*state
, int fd
)
73 if (FD_ISSET(fd
, &state
->wr
)) {
75 FD_CLR(fd
, &state
->wr
);
80 int isrv_register_fd(isrv_state_t
*state
, int peer
, int fd
)
84 DPRINTF("register_fd(peer:%d,fd:%d)", peer
, fd
);
86 if (FD_COUNT
>= FD_SETSIZE
) return -1;
91 DPRINTF("register_fd: FD_COUNT %d", FD_COUNT
);
93 FD2PEER
= xrealloc(FD2PEER
, FD_COUNT
* sizeof(FD2PEER
[0]));
94 while (n
< fd
) FD2PEER
[n
++] = -1;
97 DPRINTF("register_fd: FD2PEER[%d] = %d", fd
, peer
);
104 void isrv_close_fd(isrv_state_t
*state
, int fd
)
106 DPRINTF("close_fd(%d)", fd
);
109 isrv_dont_want_rd(state
, fd
);
110 if (WR_COUNT
) isrv_dont_want_wr(state
, fd
);
113 if (fd
== FD_COUNT
-1) {
114 do fd
--; while (fd
>= 0 && FD2PEER
[fd
] == -1);
117 DPRINTF("close_fd: FD_COUNT %d", FD_COUNT
);
119 FD2PEER
= xrealloc(FD2PEER
, FD_COUNT
* sizeof(FD2PEER
[0]));
124 int isrv_register_peer(isrv_state_t
*state
, void *param
)
128 if (PEER_COUNT
>= FD_SETSIZE
) return -1;
131 DPRINTF("register_peer: PEER_COUNT %d", PEER_COUNT
);
133 PARAM_TBL
= xrealloc(PARAM_TBL
, PEER_COUNT
* sizeof(PARAM_TBL
[0]));
134 PARAM_TBL
[n
] = param
;
136 TIMEO_TBL
= xrealloc(TIMEO_TBL
, PEER_COUNT
* sizeof(TIMEO_TBL
[0]));
137 TIMEO_TBL
[n
] = CURTIME
;
142 static void remove_peer(isrv_state_t
*state
, int peer
)
147 DPRINTF("remove_peer(%d)", peer
);
151 if (FD2PEER
[fd
] == peer
) {
152 isrv_close_fd(state
, fd
);
156 if (FD2PEER
[fd
] > peer
)
162 DPRINTF("remove_peer: PEER_COUNT %d", PEER_COUNT
);
164 movesize
= (PEER_COUNT
- peer
) * sizeof(void*);
166 memcpy(&PARAM_TBL
[peer
], &PARAM_TBL
[peer
+1], movesize
);
168 memcpy(&TIMEO_TBL
[peer
], &TIMEO_TBL
[peer
+1], movesize
);
170 PARAM_TBL
= xrealloc(PARAM_TBL
, PEER_COUNT
* sizeof(PARAM_TBL
[0]));
172 TIMEO_TBL
= xrealloc(TIMEO_TBL
, PEER_COUNT
* sizeof(TIMEO_TBL
[0]));
175 static void handle_accept(isrv_state_t
*state
, int fd
)
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]));
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");
191 DPRINTF("new_peer(%d)", newfd
);
192 n
= state
->new_peer(state
, newfd
);
194 remove_peer(state
, n
); /* unsuccesful peer start */
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 **))
200 enum { LONG_CNT
= sizeof(fd_set
) / sizeof(long) };
203 /* need to know value at _the beginning_ of this routine */
204 int fd_cnt
= FD_COUNT
;
206 if (LONG_CNT
* sizeof(long) != sizeof(fd_set
))
207 BUG_sizeof_fd_set_is_strange();
211 /* Find next nonzero bit */
212 while (fds_pos
< LONG_CNT
) {
213 if (((long*)fds
)[fds_pos
] == 0) {
217 /* Found non-zero word */
218 fd
= fds_pos
* sizeof(long)*8; /* word# -> bit# */
220 if (FD_ISSET(fd
, fds
)) {
227 break; /* all words are zero */
229 if (fd
>= fd_cnt
) { /* paranoia */
230 DPRINTF("handle_fd_set: fd > fd_cnt?? (%d > %d)",
234 DPRINTF("handle_fd_set: fd %d is active", fd
);
237 continue; /* peer is already gone */
239 handle_accept(state
, fd
);
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();
252 static void handle_timeout(isrv_state_t
*state
, int (*do_timeout
)(void **))
256 /* peer 0 is not checked */
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
]);
264 remove_peer(state
, peer
);
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 **),
280 isrv_state_t
*state
= xzalloc(sizeof(*state
));
281 state
->new_peer
= new_peer
;
282 state
->timeout
= timeout
;
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
));
301 tv
.tv_sec
= linger_timeout
;
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
);
316 bb_perror_msg("select");
320 if (n
== 0 && linger_timeout
&& PEER_COUNT
<= 1)
324 time_t t
= monotonic_sec();
327 handle_timeout(state
, do_timeout
);
331 handle_fd_set(state
, &rd
, do_rd
);
333 handle_fd_set(state
, wrp
, do_wr
);
336 DPRINTF("run: bailout");
337 /* NB: accept socket is not closed. Caller is to decide what to do */