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 26struct 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 */ 50void isrv_want_rd(isrv_state_t *state, int fd) 51{ 52 FD_SET(fd, &state->rd); 53} 54 55/* callback */ 56void 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 */ 65void isrv_dont_want_rd(isrv_state_t *state, int fd) 66{ 67 FD_CLR(fd, &state->rd); 68} 69 70/* callback */ 71void 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 */ 80int 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 */ 104void 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 */ 124int 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 142static 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 175static 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 197void BUG_sizeof_fd_set_is_strange(void); 198static 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 252static 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 */ 271void 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 = ≀ 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} 339