int main(int argc, char **argv) { struct Timer *tp; flag = 0; tp = tmr_create((struct timeval*) 0, timer_proc, JunkClientData, 3000000, 0); sleep(2); tmr_run((struct timeval*) 0); if (flag) { printf("timer should not have expired\n"); exit(-1); } sleep(1); tmr_run((struct timeval*) 0); if (!flag) { printf("timer should have expired\n"); exit(-2); } tmr_destroy(); exit(0); }
static void clear_connection( connecttab* c, struct timeval* tvP ) { ClientData client_data; /* If we haven't actually sent the buffered response yet, do so now. */ httpd_write_response( c->hc ); if ( c->idle_read_timer != (Timer*) 0 ) { tmr_cancel( c->idle_read_timer ); c->idle_read_timer = 0; } if ( c->idle_send_timer != (Timer*) 0 ) { tmr_cancel( c->idle_send_timer ); c->idle_send_timer = 0; } if ( c->wakeup_timer != (Timer*) 0 ) { tmr_cancel( c->wakeup_timer ); c->wakeup_timer = 0; } /* This is our version of Apache's lingering_close() routine, which is ** their version of the often-broken SO_LINGER socket option. For why ** this is necessary, see http://www.apache.org/docs/misc/fin_wait_2.html ** What we do is delay the actual closing for a few seconds, while reading ** any bytes that come over the connection. However, we don't want to do ** this unless it's necessary, because it ties up a connection slot and ** file descriptor which means our maximum connection-handling rate ** is lower. So, elsewhere we set a flag when we detect the few ** circumstances that make a lingering close necessary. If the flag ** isn't set we do the real close now. */ if ( c->hc->should_linger ) { c->conn_state = CNST_LINGERING; fdwatch_del_fd( c->hc->conn_fd ); fdwatch_add_fd( c->hc->conn_fd, c, FDW_READ ); /* Make sure we are still in no-delay mode. */ httpd_set_ndelay( c->hc->conn_fd ); client_data.p = c; c->linger_timer = tmr_create( tvP, linger_clear_connection, client_data, LINGER_TIME * 1000L, 0 ); if ( c->linger_timer == (Timer*) 0 ) { syslog( LOG_CRIT, "tmr_create(linger_clear_connection) failed" ); exit( 1 ); } } else really_clear_connection( c, tvP ); }
/// Create a timer. /// \param[in] timer_def timer object referenced with \ref osTimer. /// \param[in] type osTimerOnce for one-shot or osTimerPeriodic for periodic behavior. /// \param[in] argument argument to the timer call back function. /// \return timer ID for reference by other functions or NULL in case of error. /// \note MUST REMAIN UNCHANGED: \b osTimerCreate shall be consistent in every CMSIS-RTOS. osTimerId osTimerCreate (const osTimerDef_t *timer_def, os_timer_type type, void *argument) { osTimerId tmr; (void) argument; tmr = tmr_create(); if (tmr) { tmr->period = type; tmr->state = timer_def->ptimer; } return tmr; }
static void clear_connection(struct connect_s *conn, struct timeval *tv) { ClientData client_data; if (conn->wakeup_timer != NULL) { tmr_cancel(conn->wakeup_timer); conn->wakeup_timer = 0; } /* This is our version of Apache's lingering_close() routine, which is * their version of the often-broken SO_LINGER socket option. For why * this is necessary, see http://www.apache.org/docs/misc/fin_wait_2.html * What we do is delay the actual closing for a few seconds, while reading * any bytes that come over the connection. However, we don't want to do * this unless it's necessary, because it ties up a connection slot and * file descriptor which means our maximum connection-handling rateis * lower. So, elsewhere we set a flag when we detect the few * circumstances that make a lingering close necessary. If the flag isn't * set we do the real close now. */ if (conn->conn_state == CNST_LINGERING) { /* If we were already lingering, shut down for real */ tmr_cancel(conn->linger_timer); conn->linger_timer = NULL; conn->hc->should_linger = false; } else if (conn->hc->should_linger) { fdwatch_del_fd(fw, conn->hc->conn_fd); conn->conn_state = CNST_LINGERING; fdwatch_add_fd(fw, conn->hc->conn_fd, conn); client_data.p = conn; conn->linger_timer = tmr_create(tv, linger_clear_connection, client_data, CONFIG_THTTPD_LINGER_MSEC, 0); if (conn->linger_timer != NULL) { return; } nerr("ERROR: tmr_create(linger_clear_connection) failed\n"); } /* Either we are done lingering, we shouldn't linger, or we failed to setup the linger */ really_clear_connection(conn); }
timer_hdl_t timer_start(timer_handler_t *thfunc, time_t duration, timer_arg_t arg) { timer_t *tmr; timer_hdl_t ret = NONE_TIMER; tmr = tmr_create(duration + systime, thfunc); if (tmr != NULL) { ret = tmr->hdl = tmrhdl ++; tmr->arg = arg; tmr_insert(tmr); } return ret; }
void httpd_conn_sleep( httpd_conn *hc, int seconds ) { connecttab* c = hc->conn; ClientData client_data; if ( c->wakeup_timer != (Timer*) 0 ) { tmr_cancel( c->wakeup_timer ); c->wakeup_timer = 0; } fdwatch_del_fd( hc->conn_fd ); c->conn_state = CNST_SLEEPING; client_data.p = c; c->wakeup_timer = tmr_create( 0, httpd_conn_wakeup, client_data, seconds * 1000, 0 ); }
struct Stream* stream_create(unsigned short int port, int stream_id, struct Receiver* receiver) { struct Stream* cls = (struct Stream*) calloc(1, sizeof(struct Stream)); const int requested_buffer_len = 16*1024*1024; cls->port = port; cls->buffer_len = requested_buffer_len; cls->stream_id = stream_id; cls->receiver = receiver; cls->tmr_memcpy = tmr_create(); if ((cls->socket_handle = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { fprintf(stderr, "Cannot create socket.\n"); return cls; } fcntl(cls->socket_handle, F_SETFL, O_NONBLOCK); setsockopt(cls->socket_handle, SOL_SOCKET, SO_RCVBUF, &cls->buffer_len, sizeof(int)); uint32_t int_size = (uint32_t) sizeof(int); getsockopt(cls->socket_handle, SOL_SOCKET, SO_RCVBUF, &cls->buffer_len, &int_size); if (int_size != (uint32_t) sizeof(int)) { fprintf(stderr, "Error at line %d\n", __LINE__); exit(1); } if ((cls->buffer_len / 2) < requested_buffer_len) { printf("Requested socket buffer of %d bytes; actual size is %d bytes\n", requested_buffer_len, cls->buffer_len / 2); } struct sockaddr_in myaddr; myaddr.sin_family = AF_INET; myaddr.sin_addr.s_addr = htonl(INADDR_ANY); myaddr.sin_port = htons(port); if (bind(cls->socket_handle, (struct sockaddr*)&myaddr, sizeof(myaddr)) < 0) { fprintf(stderr, "Bind failed.\n"); return cls; } cls->socket_buffer = (uchar*) malloc(cls->buffer_len); memset(cls->socket_buffer, 0, cls->buffer_len); return cls; }
static void clear_connection( connecttab* c, struct timeval* tvP ) { ClientData client_data; if ( c->wakeup_timer != (Timer*) 0 ) { tmr_cancel( c->wakeup_timer ); c->wakeup_timer = 0; } /* This is our version of Apache's lingering_close() routine, which is ** their version of the often-broken SO_LINGER socket option. For why ** this is necessary, see http://www.apache.org/docs/misc/fin_wait_2.html ** What we do is delay the actual closing for a few seconds, while reading ** any bytes that come over the connection. However, we don't want to do ** this unless it's necessary, because it ties up a connection slot and ** file descriptor which means our maximum connection-handling rate ** is lower. So, elsewhere we set a flag when we detect the few ** circumstances that make a lingering close necessary. If the flag ** isn't set we do the real close now. */ if ( c->conn_state == CNST_LINGERING ) { /* If we were already lingering, shut down for real. */ tmr_cancel( c->linger_timer ); c->linger_timer = (Timer*) 0; c->hc->should_linger = 0; } if ( c->hc->should_linger ) { if ( c->conn_state != CNST_PAUSING && c->conn_state != CNST_SLEEPING ) fdwatch_del_fd( c->hc->conn_fd ); c->conn_state = CNST_LINGERING; shutdown( c->hc->conn_fd, SHUT_WR ); fdwatch_add_fd( c->hc->conn_fd, c, FDW_READ ); client_data.p = c; c->linger_timer = tmr_create( tvP, linger_clear_connection, client_data, LINGER_TIME, 0 ); } else really_clear_connection( c, tvP ); }
/********************************************************* ****************** Main Function ****************** *********************************************************/ int main(int agrc, char *agrv[]) { int rt = 0; /* return value of function main */ tmr_arg_t arg; tmr_arg_t arg1; tmr_t *tmr = NULL; tmr = tmr_create(); arg.wait = 800; arg.arg = "hi tmr 1"; arg.cb = hi; arg.cnt = 10; arg1.wait = 2000; arg1.arg = "hi tmr 2"; arg1.cb = hi; arg1.cnt = 5; tmr->start(tmr, &arg); tmr->start(tmr, &arg1); sleep(10); tmr->destroy(tmr); return 0; timer *t; t = timer_start(hi, "hi", 100); t->start(t); printf("timer state1: %s\n", t->get_state_str(t)); sleep(1); t->pause(t); t->set_interval(t, 1000); printf("timer run times: %d\n", t->get_runtimes(t)); printf("timer state: %s\n", t->get_state_str(t)); sleep(1); t->resume(t); sleep(2); t->destroy(t); return rt; }
static void start_socket( int url_num, int cnum, struct timeval* nowP ) { ClientData client_data; int flags; int sip_num; /* Start filling in the connection slot. */ connections[cnum].url_num = url_num; connections[cnum].started_at = *nowP; client_data.i = cnum; connections[cnum].did_connect = 0; connections[cnum].did_response = 0; connections[cnum].idle_timer = tmr_create( nowP, idle_connection, client_data, idle_secs * 1000L, 0 ); connections[cnum].wakeup_timer = (Timer*) 0; connections[cnum].content_length = -1; connections[cnum].bytes = 0; connections[cnum].checksum = 0; connections[cnum].http_status = -1; /* Make a socket. */ connections[cnum].conn_fd = socket( urls[url_num].sock_family, urls[url_num].sock_type, urls[url_num].sock_protocol ); if ( connections[cnum].conn_fd < 0 ) { perror( urls[url_num].url_str ); return; } /* Set the file descriptor to no-delay mode. */ flags = fcntl( connections[cnum].conn_fd, F_GETFL, 0 ); if ( flags == -1 ) { perror( urls[url_num].url_str ); (void) close( connections[cnum].conn_fd ); return; } if ( fcntl( connections[cnum].conn_fd, F_SETFL, flags | O_NDELAY ) < 0 ) { perror( urls[url_num].url_str ); (void) close( connections[cnum].conn_fd ); return; } if ( num_sips > 0 ) { /* Try a random source IP address. */ sip_num = ( (unsigned long) random() ) % ( (unsigned int) num_sips ); if ( bind( connections[cnum].conn_fd, (struct sockaddr*) &sips[sip_num].sa, sizeof(sips[sip_num].sa) ) < 0 ) { perror( "binding local address" ); (void) close( connections[cnum].conn_fd ); return; } } /* Connect to the host. */ connections[cnum].sa_len = urls[url_num].sa_len; (void) memmove( (void*) &connections[cnum].sa, (void*) &urls[url_num].sa, urls[url_num].sa_len ); connections[cnum].connect_at = *nowP; if ( connect( connections[cnum].conn_fd, (struct sockaddr*) &connections[cnum].sa, connections[cnum].sa_len ) < 0 ) { if ( errno == EINPROGRESS ) { connections[cnum].conn_state = CNST_CONNECTING; return; } else { perror( urls[url_num].url_str ); (void) close( connections[cnum].conn_fd ); return; } } /* Connect succeeded instantly, so handle it now. */ (void) gettimeofday( nowP, (struct timezone*) 0 ); handle_connect( cnum, nowP, 0 ); }
int main( int argc, char** argv ) { int argn; int start; #define START_NONE 0 #define START_PARALLEL 1 #define START_RATE 2 int start_parallel = -1, start_rate = -1; int end; #define END_NONE 0 #define END_FETCHES 1 #define END_SECONDS 2 int end_fetches = -1, end_seconds = -1; int cnum; char* url_file; char* sip_file; #ifdef RLIMIT_NOFILE struct rlimit limits; #endif /* RLIMIT_NOFILE */ fd_set rfdset; fd_set wfdset; struct timeval now; int i, r; max_connections = 64 - RESERVED_FDS; /* a guess */ #ifdef RLIMIT_NOFILE /* Try and increase the limit on # of files to the maximum. */ if ( getrlimit( RLIMIT_NOFILE, &limits ) == 0 ) { if ( limits.rlim_cur != limits.rlim_max ) { if ( limits.rlim_max == RLIM_INFINITY ) limits.rlim_cur = 8192; /* arbitrary */ else if ( limits.rlim_max > limits.rlim_cur ) limits.rlim_cur = limits.rlim_max; (void) setrlimit( RLIMIT_NOFILE, &limits ); } max_connections = limits.rlim_cur - RESERVED_FDS; } #endif /* RLIMIT_NOFILE */ /* Parse args. */ argv0 = argv[0]; argn = 1; do_checksum = do_throttle = do_verbose = do_jitter = do_proxy = 0; throttle = THROTTLE; sip_file = (char*) 0; idle_secs = IDLE_SECS; start = START_NONE; end = END_NONE; while ( argn < argc && argv[argn][0] == '-' && argv[argn][1] != '\0' ) { if ( strncmp( argv[argn], "-checksum", strlen( argv[argn] ) ) == 0 ) do_checksum = 1; else if ( strncmp( argv[argn], "-throttle", strlen( argv[argn] ) ) == 0 ) do_throttle = 1; else if ( strncmp( argv[argn], "-Throttle", strlen( argv[argn] ) ) == 0 && argn + 1 < argc ) { do_throttle = 1; throttle = atoi( argv[++argn] ) / 10.0; } else if ( strncmp( argv[argn], "-verbose", strlen( argv[argn] ) ) == 0 ) do_verbose = 1; else if ( strncmp( argv[argn], "-timeout", strlen( argv[argn] ) ) == 0 && argn + 1 < argc ) idle_secs = atoi( argv[++argn] ); else if ( strncmp( argv[argn], "-jitter", strlen( argv[argn] ) ) == 0 ) do_jitter = 1; else if ( strncmp( argv[argn], "-parallel", strlen( argv[argn] ) ) == 0 && argn + 1 < argc ) { start = START_PARALLEL; start_parallel = atoi( argv[++argn] ); if ( start_parallel < 1 ) { (void) fprintf( stderr, "%s: parallel must be at least 1\n", argv0 ); exit( 1 ); } if ( start_parallel > max_connections ) { (void) fprintf( stderr, "%s: parallel may be at most %d\n", argv0, max_connections ); exit( 1 ); } } else if ( strncmp( argv[argn], "-rate", strlen( argv[argn] ) ) == 0 && argn + 1 < argc ) { start = START_RATE; start_rate = atoi( argv[++argn] ); if ( start_rate < 1 ) { (void) fprintf( stderr, "%s: rate must be at least 1\n", argv0 ); exit( 1 ); } if ( start_rate > 1000 ) { (void) fprintf( stderr, "%s: rate may be at most 1000\n", argv0 ); exit( 1 ); } } else if ( strncmp( argv[argn], "-fetches", strlen( argv[argn] ) ) == 0 && argn + 1 < argc ) { end = END_FETCHES; end_fetches = atoi( argv[++argn] ); if ( end_fetches < 1 ) { (void) fprintf( stderr, "%s: fetches must be at least 1\n", argv0 ); exit( 1 ); } } else if ( strncmp( argv[argn], "-seconds", strlen( argv[argn] ) ) == 0 && argn + 1 < argc ) { end = END_SECONDS; end_seconds = atoi( argv[++argn] ); if ( end_seconds < 1 ) { (void) fprintf( stderr, "%s: seconds must be at least 1\n", argv0 ); exit( 1 ); } } else if ( strncmp( argv[argn], "-sip", strlen( argv[argn] ) ) == 0 && argn + 1 < argc ) sip_file = argv[++argn]; #ifdef USE_SSL else if ( strncmp( argv[argn], "-cipher", strlen( argv[argn] ) ) == 0 && argn + 1 < argc ) { cipher = argv[++argn]; if ( strcasecmp( cipher, "fastsec" ) == 0 ) cipher = "RC4-MD5"; else if ( strcasecmp( cipher, "highsec" ) == 0 ) cipher = "DES-CBC3-SHA"; else if ( strcasecmp( cipher, "paranoid" ) == 0 ) cipher = "AES256-SHA"; } #endif /* USE_SSL */ else if ( strncmp( argv[argn], "-proxy", strlen( argv[argn] ) ) == 0 && argn + 1 < argc ) { char* colon; do_proxy = 1; proxy_hostname = argv[++argn]; colon = strchr( proxy_hostname, ':' ); if ( colon == (char*) 0 ) proxy_port = 80; else { proxy_port = (unsigned short) atoi( colon + 1 ); *colon = '\0'; } } else usage(); ++argn; } if ( argn + 1 != argc ) usage(); if ( start == START_NONE || end == END_NONE ) usage(); if ( do_jitter && start != START_RATE ) usage(); url_file = argv[argn]; /* Read in and parse the URLs. */ printf("Loading url file..."); read_url_file( url_file ); printf("Total %d urls loaded.\n", num_urls); /* Read in the source IP file, if specified. */ if ( sip_file != (char*) 0 ) read_sip_file( sip_file ); /* Initialize the connections table. */ if ( start == START_PARALLEL ) max_connections = start_parallel; connections = (connection*) malloc_check( max_connections * sizeof(connection) ); for ( cnum = 0; cnum < max_connections; ++cnum ) connections[cnum].conn_state = CNST_FREE; num_connections = max_parallel = 0; /* Initialize the HTTP status-code histogram. */ for ( i = 0; i < 1000; ++i ) http_status_counts[i] = 0; /* Initialize the statistics. */ fetches_started = 0; connects_completed = 0; responses_completed = 0; fetches_completed = 0; total_bytes = 0; total_connect_usecs = 0; max_connect_usecs = 0; min_connect_usecs = 1000000000L; total_response_usecs = 0; max_response_usecs = 0; min_response_usecs = 1000000000L; total_timeouts = 0; total_badbytes = 0; total_badchecksums = 0; /* Initialize the random number generator. */ #ifdef HAVE_SRANDOMDEV srandomdev(); #else srandom( (int) time( (time_t*) 0 ) ^ getpid() ); #endif /* Initialize the rest. */ tmr_init(); (void) gettimeofday( &now, (struct timezone*) 0 ); start_at = now; if ( do_verbose ) (void) tmr_create( &now, progress_report, JunkClientData, PROGRESS_SECS * 1000L, 1 ); if ( start == START_RATE ) { start_interval = 1000L / start_rate; if ( do_jitter ) { low_interval = start_interval * 9 / 10; high_interval = start_interval * 11 / 10; range_interval = high_interval - low_interval + 1; } (void) tmr_create( &now, start_timer, JunkClientData, start_interval, ! do_jitter ); } if ( end == END_SECONDS ) (void) tmr_create( &now, end_timer, JunkClientData, end_seconds * 1000L, 0 ); (void) signal( SIGPIPE, SIG_IGN ); /* Main loop. */ for (;;) { if ( end == END_FETCHES && fetches_completed >= end_fetches ) finish( &now ); if ( start == START_PARALLEL ) { /* See if we need to start any new connections; but at most 10. */ for ( i = 0; i < 10 && num_connections < start_parallel && ( end != END_FETCHES || fetches_started < end_fetches ); ++i ) { start_connection( &now ); (void) gettimeofday( &now, (struct timezone*) 0 ); tmr_run( &now ); } } /* Build the fdsets. */ FD_ZERO( &rfdset ); FD_ZERO( &wfdset ); for ( cnum = 0; cnum < max_connections; ++cnum ) switch ( connections[cnum].conn_state ) { case CNST_CONNECTING: FD_SET( connections[cnum].conn_fd, &wfdset ); break; case CNST_HEADERS: case CNST_READING: FD_SET( connections[cnum].conn_fd, &rfdset ); break; } r = select( FD_SETSIZE, &rfdset, &wfdset, (fd_set*) 0, tmr_timeout( &now ) ); if ( r < 0 ) { perror( "select" ); exit( 1 ); } (void) gettimeofday( &now, (struct timezone*) 0 ); /* Service them. */ for ( cnum = 0; cnum < max_connections; ++cnum ) switch ( connections[cnum].conn_state ) { case CNST_CONNECTING: if ( FD_ISSET( connections[cnum].conn_fd, &wfdset ) ) handle_connect( cnum, &now, 1 ); break; case CNST_HEADERS: case CNST_READING: if ( FD_ISSET( connections[cnum].conn_fd, &rfdset ) ) handle_read( cnum, &now ); break; } /* And run the timers. */ tmr_run( &now ); } /* NOT_REACHED */ }
static void handle_read( int cnum, struct timeval* nowP ) { char buf[30000]; /* must be larger than throttle / 2 */ int bytes_to_read, bytes_read, bytes_handled; float elapsed; ClientData client_data; register long checksum; tmr_reset( nowP, connections[cnum].idle_timer ); if ( do_throttle ) bytes_to_read = throttle / 2.0; else bytes_to_read = sizeof(buf); if ( ! connections[cnum].did_response ) { connections[cnum].did_response = 1; connections[cnum].response_at = *nowP; } #ifdef USE_SSL if ( urls[connections[cnum].url_num].protocol == PROTO_HTTPS ) bytes_read = SSL_read( connections[cnum].ssl, buf, bytes_to_read ); else bytes_read = read( connections[cnum].conn_fd, buf, bytes_to_read ); #else bytes_read = read( connections[cnum].conn_fd, buf, bytes_to_read ); #endif if ( bytes_read <= 0 ) { close_connection( cnum ); return; } for ( bytes_handled = 0; bytes_handled < bytes_read; ) { switch ( connections[cnum].conn_state ) { case CNST_HEADERS: /* State machine to read until we reach the file part. Looks for ** Content-Length header too. */ for ( ; bytes_handled < bytes_read && connections[cnum].conn_state == CNST_HEADERS; ++bytes_handled ) { switch ( connections[cnum].header_state ) { case HDST_LINE1_PROTOCOL: switch ( buf[bytes_handled] ) { case ' ': case '\t': connections[cnum].header_state = HDST_LINE1_WHITESPACE; break; case '\n': connections[cnum].header_state = HDST_LF; break; case '\r': connections[cnum].header_state = HDST_CR; break; } break; case HDST_LINE1_WHITESPACE: switch ( buf[bytes_handled] ) { case ' ': case '\t': break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': connections[cnum].http_status = buf[bytes_handled] - '0'; connections[cnum].header_state = HDST_LINE1_STATUS; break; case '\n': connections[cnum].header_state = HDST_LF; break; case '\r': connections[cnum].header_state = HDST_CR; break; default: connections[cnum].header_state = HDST_TEXT; break; } break; case HDST_LINE1_STATUS: switch ( buf[bytes_handled] ) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': connections[cnum].http_status = connections[cnum].http_status * 10 + buf[bytes_handled] - '0'; break; case '\n': connections[cnum].header_state = HDST_LF; break; case '\r': connections[cnum].header_state = HDST_CR; break; default: connections[cnum].header_state = HDST_TEXT; break; } break; case HDST_BOL: switch ( buf[bytes_handled] ) { case '\n': connections[cnum].header_state = HDST_LF; break; case '\r': connections[cnum].header_state = HDST_CR; break; case 'C': case 'c': connections[cnum].header_state = HDST_C; break; default: connections[cnum].header_state = HDST_TEXT; break; } break; case HDST_TEXT: switch ( buf[bytes_handled] ) { case '\n': connections[cnum].header_state = HDST_LF; break; case '\r': connections[cnum].header_state = HDST_CR; break; default: break; } break; case HDST_LF: switch ( buf[bytes_handled] ) { case '\n': connections[cnum].conn_state = CNST_READING; break; case '\r': connections[cnum].header_state = HDST_CR; break; case 'C': case 'c': connections[cnum].header_state = HDST_C; break; default: connections[cnum].header_state = HDST_TEXT; break; } break; case HDST_CR: switch ( buf[bytes_handled] ) { case '\n': connections[cnum].header_state = HDST_CRLF; break; case '\r': connections[cnum].conn_state = CNST_READING; break; case 'C': case 'c': connections[cnum].header_state = HDST_C; break; default: connections[cnum].header_state = HDST_TEXT; break; } break; case HDST_CRLF: switch ( buf[bytes_handled] ) { case '\n': connections[cnum].conn_state = CNST_READING; break; case '\r': connections[cnum].header_state = HDST_CRLFCR; break; case 'C': case 'c': connections[cnum].header_state = HDST_C; break; default: connections[cnum].header_state = HDST_TEXT; break; } break; case HDST_CRLFCR: switch ( buf[bytes_handled] ) { case '\n': case '\r': connections[cnum].conn_state = CNST_READING; break; case 'C': case 'c': connections[cnum].header_state = HDST_C; break; default: connections[cnum].header_state = HDST_TEXT; break; } break; case HDST_C: switch ( buf[bytes_handled] ) { case 'O': case 'o': connections[cnum].header_state = HDST_CO; break; case '\n': connections[cnum].header_state = HDST_LF; break; case '\r': connections[cnum].header_state = HDST_CR; break; default: connections[cnum].header_state = HDST_TEXT; break; } break; case HDST_CO: switch ( buf[bytes_handled] ) { case 'N': case 'n': connections[cnum].header_state = HDST_CON; break; case '\n': connections[cnum].header_state = HDST_LF; break; case '\r': connections[cnum].header_state = HDST_CR; break; default: connections[cnum].header_state = HDST_TEXT; break; } break; case HDST_CON: switch ( buf[bytes_handled] ) { case 'T': case 't': connections[cnum].header_state = HDST_CONT; break; case '\n': connections[cnum].header_state = HDST_LF; break; case '\r': connections[cnum].header_state = HDST_CR; break; default: connections[cnum].header_state = HDST_TEXT; break; } break; case HDST_CONT: switch ( buf[bytes_handled] ) { case 'E': case 'e': connections[cnum].header_state = HDST_CONTE; break; case '\n': connections[cnum].header_state = HDST_LF; break; case '\r': connections[cnum].header_state = HDST_CR; break; default: connections[cnum].header_state = HDST_TEXT; break; } break; case HDST_CONTE: switch ( buf[bytes_handled] ) { case 'N': case 'n': connections[cnum].header_state = HDST_CONTEN; break; case '\n': connections[cnum].header_state = HDST_LF; break; case '\r': connections[cnum].header_state = HDST_CR; break; default: connections[cnum].header_state = HDST_TEXT; break; } break; case HDST_CONTEN: switch ( buf[bytes_handled] ) { case 'T': case 't': connections[cnum].header_state = HDST_CONTENT; break; case '\n': connections[cnum].header_state = HDST_LF; break; case '\r': connections[cnum].header_state = HDST_CR; break; default: connections[cnum].header_state = HDST_TEXT; break; } break; case HDST_CONTENT: switch ( buf[bytes_handled] ) { case '-': connections[cnum].header_state = HDST_CONTENT_; break; case '\n': connections[cnum].header_state = HDST_LF; break; case '\r': connections[cnum].header_state = HDST_CR; break; default: connections[cnum].header_state = HDST_TEXT; break; } break; case HDST_CONTENT_: switch ( buf[bytes_handled] ) { case 'L': case 'l': connections[cnum].header_state = HDST_CONTENT_L; break; case '\n': connections[cnum].header_state = HDST_LF; break; case '\r': connections[cnum].header_state = HDST_CR; break; default: connections[cnum].header_state = HDST_TEXT; break; } break; case HDST_CONTENT_L: switch ( buf[bytes_handled] ) { case 'E': case 'e': connections[cnum].header_state = HDST_CONTENT_LE; break; case '\n': connections[cnum].header_state = HDST_LF; break; case '\r': connections[cnum].header_state = HDST_CR; break; default: connections[cnum].header_state = HDST_TEXT; break; } break; case HDST_CONTENT_LE: switch ( buf[bytes_handled] ) { case 'N': case 'n': connections[cnum].header_state = HDST_CONTENT_LEN; break; case '\n': connections[cnum].header_state = HDST_LF; break; case '\r': connections[cnum].header_state = HDST_CR; break; default: connections[cnum].header_state = HDST_TEXT; break; } break; case HDST_CONTENT_LEN: switch ( buf[bytes_handled] ) { case 'G': case 'g': connections[cnum].header_state = HDST_CONTENT_LENG; break; case '\n': connections[cnum].header_state = HDST_LF; break; case '\r': connections[cnum].header_state = HDST_CR; break; default: connections[cnum].header_state = HDST_TEXT; break; } break; case HDST_CONTENT_LENG: switch ( buf[bytes_handled] ) { case 'T': case 't': connections[cnum].header_state = HDST_CONTENT_LENGT; break; case '\n': connections[cnum].header_state = HDST_LF; break; case '\r': connections[cnum].header_state = HDST_CR; break; default: connections[cnum].header_state = HDST_TEXT; break; } break; case HDST_CONTENT_LENGT: switch ( buf[bytes_handled] ) { case 'H': case 'h': connections[cnum].header_state = HDST_CONTENT_LENGTH; break; case '\n': connections[cnum].header_state = HDST_LF; break; case '\r': connections[cnum].header_state = HDST_CR; break; default: connections[cnum].header_state = HDST_TEXT; break; } break; case HDST_CONTENT_LENGTH: switch ( buf[bytes_handled] ) { case ':': connections[cnum].header_state = HDST_CONTENT_LENGTH_COLON; break; case '\n': connections[cnum].header_state = HDST_LF; break; case '\r': connections[cnum].header_state = HDST_CR; break; default: connections[cnum].header_state = HDST_TEXT; break; } break; case HDST_CONTENT_LENGTH_COLON: switch ( buf[bytes_handled] ) { case ' ': case '\t': connections[cnum].header_state = HDST_CONTENT_LENGTH_COLON_WHITESPACE; break; case '\n': connections[cnum].header_state = HDST_LF; break; case '\r': connections[cnum].header_state = HDST_CR; break; default: connections[cnum].header_state = HDST_TEXT; break; } break; case HDST_CONTENT_LENGTH_COLON_WHITESPACE: switch ( buf[bytes_handled] ) { case ' ': case '\t': break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': connections[cnum].content_length = buf[bytes_handled] - '0'; connections[cnum].header_state = HDST_CONTENT_LENGTH_COLON_WHITESPACE_NUM; break; case '\n': connections[cnum].header_state = HDST_LF; break; case '\r': connections[cnum].header_state = HDST_CR; break; default: connections[cnum].header_state = HDST_TEXT; break; } break; case HDST_CONTENT_LENGTH_COLON_WHITESPACE_NUM: switch ( buf[bytes_handled] ) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': connections[cnum].content_length = connections[cnum].content_length * 10 + buf[bytes_handled] - '0'; break; case '\n': connections[cnum].header_state = HDST_LF; break; case '\r': connections[cnum].header_state = HDST_CR; break; default: connections[cnum].header_state = HDST_TEXT; break; } break; } } break; case CNST_READING: connections[cnum].bytes += bytes_read - bytes_handled; if ( do_throttle ) { /* Check if we're reading too fast. */ elapsed = delta_timeval( &connections[cnum].started_at, nowP ) / 1000000.0; if ( elapsed > 0.01 && connections[cnum].bytes / elapsed > throttle ) { connections[cnum].conn_state = CNST_PAUSING; client_data.i = cnum; connections[cnum].wakeup_timer = tmr_create( nowP, wakeup_connection, client_data, 1000L, 0 ); } } if ( do_checksum ) { checksum = connections[cnum].checksum; for ( ; bytes_handled < bytes_read; ++bytes_handled ) { if ( checksum & 1 ) checksum = ( checksum >> 1 ) + 0x8000; else checksum >>= 1; checksum += buf[bytes_handled]; checksum &= 0xffff; } connections[cnum].checksum = checksum; } else bytes_handled = bytes_read; if ( connections[cnum].content_length != -1 && connections[cnum].bytes >= connections[cnum].content_length ) { close_connection( cnum ); return; } break; }
static int handle_newconnect( struct timeval* tvP, int listen_fd ) { int cnum; connecttab* c; ClientData client_data; /* This loops until the accept() fails, trying to start new ** connections as fast as possible so we don't overrun the ** listen queue. */ for (;;) { /* Is there room in the connection table? */ if ( numconnects >= maxconnects ) { /* Out of connection slots. Run the timers, then the ** existing connections, and maybe we'll free up a slot ** by the time we get back here. **/ syslog( LOG_WARNING, "too many connections!" ); tmr_run( tvP ); return 0; } /* Find a free connection entry. */ for ( cnum = 0; cnum < maxconnects; ++cnum ) if ( connects[cnum].conn_state == CNST_FREE ) break; c = &connects[cnum]; /* Make the httpd_conn if necessary. */ if ( c->hc == (httpd_conn*) 0 ) { c->hc = NEW( httpd_conn, 1 ); if ( c->hc == (httpd_conn*) 0 ) { syslog( LOG_CRIT, "out of memory allocating an httpd_conn" ); exit( 1 ); } c->hc->initialized = 0; ++httpd_conn_count; } /* Get the connection. */ switch ( httpd_get_conn( hs, listen_fd, c->hc ) ) { case GC_FAIL: case GC_NO_MORE: return 1; } c->conn_state = CNST_READING; ++numconnects; client_data.p = c; c->idle_read_timer = tmr_create( tvP, idle_read_connection, client_data, IDLE_READ_TIMELIMIT * 1000L, 0 ); if ( c->idle_read_timer == (Timer*) 0 ) { syslog( LOG_CRIT, "tmr_create(idle_read_connection) failed" ); exit( 1 ); } c->idle_send_timer = (Timer*) 0; c->wakeup_timer = (Timer*) 0; c->linger_timer = (Timer*) 0; c->bytes_sent = 0; c->numtnums = 0; /* Set the connection file descriptor to no-delay mode. */ httpd_set_ndelay( c->hc->conn_fd ); fdwatch_add_fd( c->hc->conn_fd, c, FDW_READ ); ++stats_connections; if ( numconnects > stats_simultaneous ) stats_simultaneous = numconnects; } }
int thttpd_main(int argc, char **argv) #endif { int num_ready; int cnum; FAR struct connect_s *conn; FAR httpd_conn *hc; httpd_sockaddr sa; struct timeval tv; #ifdef CONFIG_THTTPD_DIR int ret; #endif nvdbg("THTTPD started\n"); /* Setup host address */ #ifdef CONFIG_NET_IPv6 # error "IPv6 support not yet implemented" #else sa.sin_family = AF_INET; sa.sin_port = HTONS(CONFIG_THTTPD_PORT); sa.sin_addr.s_addr = HTONL(CONFIG_THTTPD_IPADDR); #endif /* Initialize the fdwatch package to handle all of the configured * socket descriptors */ fw = fdwatch_initialize(CONFIG_NSOCKET_DESCRIPTORS); if (!fw) { ndbg("fdwatch initialization failure\n"); exit(1); } /* Switch directories again if requested */ #ifdef CONFIG_THTTPD_DATADIR if (chdir(CONFIG_THTTPD_DATADIR) < 0) { ndbg("chdir to %s: %d\n", CONFIG_THTTPD_DATADIR, errno); exit(1); } #endif /* Initialize the timer package */ tmr_init(); /* Initialize the HTTP layer */ nvdbg("Calling httpd_initialize()\n"); hs = httpd_initialize(&sa); if (!hs) { ndbg("httpd_initialize() failed\n"); exit(1); } /* Set up the occasional timer */ if (tmr_create(NULL, occasional, JunkClientData, CONFIG_THTTPD_OCCASIONAL_MSEC * 1000L, 1) == NULL) { ndbg("tmr_create(occasional) failed\n"); exit(1); } /* Set up the idle timer */ if (tmr_create(NULL, idle, JunkClientData, 5 * 1000L, 1) == NULL) { ndbg("tmr_create(idle) failed\n"); exit(1); } /* Initialize our connections table */ connects = NEW(struct connect_s, AVAILABLE_FDS); if (connects == NULL) { ndbg("Out of memory allocating a struct connect_s\n"); exit(1); } for (cnum = 0; cnum < AVAILABLE_FDS; ++cnum) { connects[cnum].conn_state = CNST_FREE; connects[cnum].next = &connects[cnum + 1]; connects[cnum].hc = NULL; } connects[AVAILABLE_FDS-1].next = NULL; /* End of link list */ free_connections = connects; /* Beginning of the link list */ if (hs != NULL) { if (hs->listen_fd != -1) { fdwatch_add_fd(fw, hs->listen_fd, NULL); } } /* Main loop */ nvdbg("Entering the main loop\n"); (void)gettimeofday(&tv, NULL); for (;;) { /* Do the fd watch */ num_ready = fdwatch(fw, tmr_mstimeout(&tv)); if (num_ready < 0) { if (errno == EINTR || errno == EAGAIN) { /* Not errors... try again */ continue; } ndbg("fdwatch failed: %d\n", errno); exit(1); } (void)gettimeofday(&tv, NULL); if (num_ready == 0) { /* No fd's are ready - run the timers */ tmr_run(&tv); continue; } /* Is it a new connection? */ if (fdwatch_check_fd(fw, hs->listen_fd)) { if (!handle_newconnect(&tv, hs->listen_fd)) { /* Go around the loop and do another fdwatch, rather than * dropping through and processing existing connections. New * connections always get priority. */ continue; } } /* Find the connections that need servicing */ while ((conn = (struct connect_s*)fdwatch_get_next_client_data(fw)) != (struct connect_s*)-1) { if (conn) { hc = conn->hc; if (fdwatch_check_fd(fw, hc->conn_fd)) { nvdbg("Handle conn_state %d\n", conn->conn_state); switch (conn->conn_state) { case CNST_READING: { handle_read(conn, &tv); /* If a GET request was received and a file is ready to * be sent, then fall through to send the file. */ if (conn->conn_state != CNST_SENDING) { break; } } case CNST_SENDING: { /* Send a file -- this really should be performed on a * separate thread to keep the serve from locking up during * the write. */ handle_send(conn, &tv); } break; case CNST_LINGERING: { /* Linger close the connection */ handle_linger(conn, &tv); } break; } } } } tmr_run(&tv); } /* The main loop terminated */ shut_down(); ndbg("Exiting\n"); exit(0); }
static void handle_read( connecttab* c, struct timeval* tvP ) { int sz; ClientData client_data; httpd_conn* hc = c->hc; /* Is there room in our buffer to read more bytes? */ if ( hc->read_idx >= hc->read_size ) { if ( hc->read_size > 5000 ) { httpd_send_err( hc, 400, httpd_err400title, "", httpd_err400form, "" ); clear_connection( c, tvP ); return; } httpd_realloc_str( &hc->read_buf, &hc->read_size, hc->read_size + 1000 ); } /* Read some more bytes. */ sz = read( hc->conn_fd, &(hc->read_buf[hc->read_idx]), hc->read_size - hc->read_idx ); /* Ignore EWOULDBLOCK errors. At first glance you would think that ** connections returned by fdwatch as readable should never give an ** EWOULDBLOCK; however, this apparently can happen if a packet gets ** garbled. */ if ( sz == 0 || ( sz < 0 && ( errno != EWOULDBLOCK ) ) ) { httpd_send_err( hc, 400, httpd_err400title, "", httpd_err400form, "" ); clear_connection( c, tvP ); return; } hc->read_idx += sz; /* Do we have a complete request yet? */ switch ( httpd_got_request( hc ) ) { case GR_NO_REQUEST: return; case GR_BAD_REQUEST: httpd_send_err( hc, 400, httpd_err400title, "", httpd_err400form, "" ); clear_connection( c, tvP ); return; } /* Yes. Try parsing and resolving it. */ if ( httpd_parse_request( hc ) < 0 ) { clear_connection( c, tvP ); return; } /* Check the throttle table */ if ( ! check_throttles( c ) ) { httpd_send_err( hc, 503, httpd_err503title, "", httpd_err503form, hc->encodedurl ); clear_connection( c, tvP ); return; } /* Start the connection going. */ if ( httpd_start_request( hc, tvP ) < 0 ) { /* Something went wrong. Close down the connection. */ clear_connection( c, tvP ); return; } /* Fill in bytes_to_send. */ if ( hc->got_range ) { c->bytes_sent = hc->init_byte_loc; c->bytes_to_send = hc->end_byte_loc + 1; } else c->bytes_to_send = hc->bytes_to_send; /* Check if it's already handled. */ if ( hc->file_address == (char*) 0 ) { /* No file address means someone else is handling it. */ c->bytes_sent = hc->bytes_sent; clear_connection( c, tvP ); return; } if ( c->bytes_sent >= c->bytes_to_send ) { /* There's nothing to send. */ clear_connection( c, tvP ); return; } /* Cool, we have a valid connection and a file to send to it. */ c->conn_state = CNST_SENDING; c->started_at = tvP->tv_sec; c->wouldblock_delay = 0; client_data.p = c; tmr_cancel( c->idle_read_timer ); c->idle_read_timer = (Timer*) 0; c->idle_send_timer = tmr_create( tvP, idle_send_connection, client_data, IDLE_SEND_TIMELIMIT * 1000L, 0 ); if ( c->idle_send_timer == (Timer*) 0 ) { syslog( LOG_CRIT, "tmr_create(idle_send_connection) failed" ); exit( 1 ); } fdwatch_del_fd( hc->conn_fd ); fdwatch_add_fd( hc->conn_fd, c, FDW_WRITE ); }
static void handle_send( connecttab* c, struct timeval* tvP ) { size_t max_bytes; int sz, coast; ClientData client_data; time_t elapsed; httpd_conn* hc = c->hc; int tind; max_bytes = 1000000000L; /* Do we need to write the headers first? */ if ( hc->responselen == 0 ) { /* No, just write the file. */ sz = write( hc->conn_fd, &(hc->body_data[c->next_byte_index]), MIN( c->end_byte_index - c->next_byte_index, max_bytes ) ); } else { /* Yes. We'll combine headers and file into a single writev(), ** hoping that this generates a single packet. */ struct iovec iv[2]; iv[0].iov_base = hc->response; iv[0].iov_len = hc->responselen; iv[1].iov_base = &(hc->body_data[c->next_byte_index]); iv[1].iov_len = MIN( c->end_byte_index - c->next_byte_index, max_bytes ); sz = writev( hc->conn_fd, iv, 2 ); } if ( sz < 0 && errno == EINTR ) return; if ( sz == 0 || ( sz < 0 && ( errno == EWOULDBLOCK || errno == EAGAIN ) ) ) { /* This shouldn't happen, but some kernels, e.g. ** SunOS 4.1.x, are broken and select() says that ** O_NDELAY sockets are always writable even when ** they're actually not. ** ** Current workaround is to block sending on this ** socket for a brief adaptively-tuned period. ** Fortunately we already have all the necessary ** blocking code, for use with throttling. */ c->wouldblock_delay += MIN_WOULDBLOCK_DELAY; c->conn_state = CNST_PAUSING; fdwatch_del_fd( hc->conn_fd ); client_data.p = c; c->wakeup_timer = tmr_create( tvP, wakeup_connection, client_data, c->wouldblock_delay, 0 ); if ( c->wakeup_timer == (Timer*) 0 ) { return; } return; } if ( sz < 0 ) { /* Something went wrong, close this connection. */ clear_connection( c, tvP ); return; } /* Ok, we wrote something. */ c->active_at = tvP->tv_sec; /* Was this a headers + file writev()? */ if ( hc->responselen > 0 ) { /* Yes; did we write only part of the headers? */ if ( sz < hc->responselen ) { /* Yes; move the unwritten part to the front of the buffer. */ int newlen = hc->responselen - sz; (void) memmove( hc->response, &(hc->response[sz]), newlen ); hc->responselen = newlen; sz = 0; } else { /* Nope, we wrote the full headers, so adjust accordingly. */ sz -= hc->responselen; hc->responselen = 0; } } /* And update how much of the file we wrote. */ c->next_byte_index += sz; c->hc->bytes_sent += sz; /* Are we done? */ if ( c->next_byte_index >= c->end_byte_index ) { /* This connection is finished! */ finish_connection( c, tvP ); return; } /* Tune the (blockheaded) wouldblock delay. */ if ( c->wouldblock_delay > MIN_WOULDBLOCK_DELAY ) c->wouldblock_delay -= MIN_WOULDBLOCK_DELAY; /* (No check on min_limit here, that only controls connection startups.) */ }
int thttpd_run(void) { char* cp; struct passwd* pwd; uid_t uid = 32767; gid_t gid = 32767; int num_ready; int cnum; connecttab* c; httpd_conn* hc; httpd_sockaddr sa4; httpd_sockaddr sa6; int gotv4, gotv6; struct timeval tv; cp = getenv( "GHTTPPORT" ); if ( cp ) port = atoi( cp ); if( port == 0 ) port = 9999; /* Read zone info now, in case we chroot(). */ tzset(); /* Look up hostname now, in case we chroot(). */ lookup_hostname( &sa4, sizeof(sa4), &gotv4, &sa6, sizeof(sa6), &gotv6 ); if ( ! ( gotv4 || gotv6 ) ) { memset(&sa4, 0, sizeof sa4); gotv4 = 1; } /* Initialize the fdwatch package. Have to do this before chroot, ** if /dev/poll is used. */ max_connects = fdwatch_get_nfiles(); if ( max_connects < 0 ) { return; } max_connects -= SPARE_FDS; /* Set up to catch signals. */ #ifdef HAVE_SIGSET (void) sigset( SIGPIPE, SIG_IGN ); /* get EPIPE instead */ #else /* HAVE_SIGSET */ (void) signal( SIGPIPE, SIG_IGN ); /* get EPIPE instead */ #endif /* HAVE_SIGSET */ /* Initialize the timer package. */ tmr_init(); /* Initialize the HTTP layer. Got to do this before giving up root, ** so that we can bind to a privileged port. */ hs = httpd_initialize( hostname, gotv4 ? &sa4 : (httpd_sockaddr*) 0, gotv6 ? &sa6 : (httpd_sockaddr*) 0, port, cgi_pattern, cgi_limit, charset, p3p, max_age, "/", no_log, no_symlink_check, do_vhost, do_global_passwd, url_pattern, local_pattern, no_empty_referers ); if ( hs == (httpd_server*) 0 ) exit( 1 ); /* Set up the occasional timer. */ if ( tmr_create( (struct timeval*) 0, occasional, JunkClientData, OCCASIONAL_TIME * 1000L, 1 ) == (Timer*) 0 ) { return; } /* Set up the idle timer. */ if ( tmr_create( (struct timeval*) 0, idle, JunkClientData, 5 * 1000L, 1 ) == (Timer*) 0 ) { return; } start_time = stats_time = time( (time_t*) 0 ); stats_connections = 0; stats_bytes = 0; stats_simultaneous = 0; /* Initialize our connections table. */ connects = NEW( connecttab, max_connects ); if ( connects == (connecttab*) 0 ) { return; } for ( cnum = 0; cnum < max_connects; ++cnum ) { connects[cnum].conn_state = CNST_FREE; connects[cnum].next_free_connect = cnum + 1; connects[cnum].hc = (httpd_conn*) 0; } connects[max_connects - 1].next_free_connect = -1; /* end of link list */ first_free_connect = 0; num_connects = 0; httpd_conn_count = 0; if ( hs != (httpd_server*) 0 ) { if ( hs->listen4_fd != -1 ) fdwatch_add_fd( hs->listen4_fd, (void*) 0, FDW_READ ); if ( hs->listen6_fd != -1 ) fdwatch_add_fd( hs->listen6_fd, (void*) 0, FDW_READ ); } /* Main loop. */ (void) gettimeofday( &tv, (struct timezone*) 0 ); while ( ( ! terminate ) || num_connects > 0 ) { /* Do the fd watch. */ num_ready = fdwatch( tmr_mstimeout( &tv ) ); if ( num_ready < 0 ) { if ( errno == EINTR || errno == EAGAIN ) continue; /* try again */ return; } (void) gettimeofday( &tv, (struct timezone*) 0 ); if ( num_ready == 0 ) { /* No fd's are ready - run the timers. */ tmr_run( &tv ); continue; } /* Is it a new connection? */ if ( hs != (httpd_server*) 0 && hs->listen6_fd != -1 && fdwatch_check_fd( hs->listen6_fd ) ) { if ( handle_newconnect( &tv, hs->listen6_fd ) ) /* Go around the loop and do another fdwatch, rather than ** dropping through and processing existing connections. ** New connections always get priority. */ continue; } if ( hs != (httpd_server*) 0 && hs->listen4_fd != -1 && fdwatch_check_fd( hs->listen4_fd ) ) { if ( handle_newconnect( &tv, hs->listen4_fd ) ) /* Go around the loop and do another fdwatch, rather than ** dropping through and processing existing connections. ** New connections always get priority. */ continue; } /* Find the connections that need servicing. */ while ( ( c = (connecttab*) fdwatch_get_next_client_data() ) != (connecttab*) -1 ) { if ( c == (connecttab*) 0 ) continue; hc = c->hc; if ( ! fdwatch_check_fd( hc->conn_fd ) ) /* Something went wrong. */ clear_connection( c, &tv ); else switch ( c->conn_state ) { case CNST_READING: handle_read( c, &tv ); break; case CNST_SENDING: handle_send( c, &tv ); break; case CNST_LINGERING: handle_linger( c, &tv ); break; } } tmr_run( &tv ); } /* The main loop terminated. */ shut_down(); return 0; }
int main( int argc, char** argv ) { char* cp; struct passwd* pwd; uid_t uid; gid_t gid; char cwd[MAXPATHLEN]; FILE* logfp; int num_ready; int cnum, ridx; connecttab* c; httpd_conn* hc; httpd_sockaddr sa4; httpd_sockaddr sa6; int gotv4, gotv6; struct timeval tv; argv0 = argv[0]; cp = strrchr( argv0, '/' ); if ( cp != (char*) 0 ) ++cp; else cp = argv0; openlog( cp, LOG_NDELAY|LOG_PID, LOG_FACILITY ); /* Handle command-line arguments. */ parse_args( argc, argv ); /* Check port number. */ if ( port <= 0 ) { syslog( LOG_CRIT, "illegal port number" ); (void) fprintf( stderr, "%s: illegal port number\n", argv0 ); exit( 1 ); } /* Read zone info now, in case we chroot(). */ tzset(); /* Look up hostname now, in case we chroot(). */ lookup_hostname( &sa4, sizeof(sa4), &gotv4, &sa6, sizeof(sa6), &gotv6 ); if ( ! ( gotv4 || gotv6 ) ) { syslog( LOG_ERR, "can't find any valid address" ); (void) fprintf( stderr, "%s: can't find any valid address\n", argv0 ); exit( 1 ); } /* Throttle file. */ numthrottles = 0; maxthrottles = 0; throttles = (throttletab*) 0; if ( throttlefile != (char*) 0 ) read_throttlefile( throttlefile ); /* Log file. */ if ( logfile != (char*) 0 ) { if ( strcmp( logfile, "/dev/null" ) == 0 ) { no_log = 1; logfp = (FILE*) 0; } else { logfp = fopen( logfile, "a" ); if ( logfp == (FILE*) 0 ) { syslog( LOG_CRIT, "%.80s - %m", logfile ); perror( logfile ); exit( 1 ); } (void) fcntl( fileno( logfp ), F_SETFD, 1 ); } } else logfp = (FILE*) 0; /* Figure out uid/gid from user. */ pwd = getpwnam( user ); if ( pwd == (struct passwd*) 0 ) { syslog( LOG_CRIT, "unknown user - '%.80s'", user ); (void) fprintf( stderr, "%s: unknown user - '%s'\n", argv0, user ); exit( 1 ); } uid = pwd->pw_uid; gid = pwd->pw_gid; /* Switch directories if requested. */ if ( dir != (char*) 0 ) { if ( chdir( dir ) < 0 ) { syslog( LOG_CRIT, "chdir - %m" ); perror( "chdir" ); exit( 1 ); } } #ifdef USE_USER_DIR else if ( getuid() == 0 ) { /* No explicit directory was specified, we're root, and the ** USE_USER_DIR option is set - switch to the specified user's ** home dir. */ if ( chdir( pwd->pw_dir ) < 0 ) { syslog( LOG_CRIT, "chdir - %m" ); perror( "chdir" ); exit( 1 ); } } #endif /* USE_USER_DIR */ /* Get current directory. */ (void) getcwd( cwd, sizeof(cwd) - 1 ); if ( cwd[strlen( cwd ) - 1] != '/' ) (void) strcat( cwd, "/" ); if ( ! debug ) { /* We're not going to use stdin stdout or stderr from here on, so close ** them to save file descriptors. */ (void) fclose( stdin ); (void) fclose( stdout ); (void) fclose( stderr ); /* Daemonize - make ourselves a subprocess. */ #ifdef HAVE_DAEMON if ( daemon( 1, 1 ) < 0 ) { syslog( LOG_CRIT, "daemon - %m" ); exit( 1 ); } #else /* HAVE_DAEMON */ switch ( fork() ) { case 0: break; case -1: syslog( LOG_CRIT, "fork - %m" ); exit( 1 ); default: exit( 0 ); } #ifdef HAVE_SETSID (void) setsid(); #endif /* HAVE_SETSID */ #endif /* HAVE_DAEMON */ } else { /* Even if we don't daemonize, we still want to disown our parent ** process. */ #ifdef HAVE_SETSID (void) setsid(); #endif /* HAVE_SETSID */ } if ( pidfile != (char*) 0 ) { /* Write the PID file. */ FILE* pidfp = fopen( pidfile, "w" ); if ( pidfp == (FILE*) 0 ) { syslog( LOG_CRIT, "%.80s - %m", pidfile ); exit( 1 ); } (void) fprintf( pidfp, "%d\n", (int) getpid() ); (void) fclose( pidfp ); } /* Chroot if requested. */ if ( do_chroot ) { if ( chroot( cwd ) < 0 ) { syslog( LOG_CRIT, "chroot - %m" ); perror( "chroot" ); exit( 1 ); } (void) strcpy( cwd, "/" ); /* Always chdir to / after a chroot. */ if ( chdir( cwd ) < 0 ) { syslog( LOG_CRIT, "chroot chdir - %m" ); perror( "chroot chdir" ); exit( 1 ); } } /* Set up to catch signals. */ (void) signal( SIGTERM, handle_term ); (void) signal( SIGINT, handle_term ); (void) signal( SIGPIPE, SIG_IGN ); /* get EPIPE instead */ (void) signal( SIGHUP, handle_hup ); got_usr1 = 0; (void) signal( SIGUSR1, handle_usr1 ); (void) signal( SIGUSR2, handle_usr2 ); /* Initialize the timer package. */ tmr_init(); /* Initialize the HTTP layer. Got to do this before giving up root, ** so that we can bind to a privileged port. */ hs = httpd_initialize( hostname, gotv4 ? &sa4 : (httpd_sockaddr*) 0, gotv6 ? &sa6 : (httpd_sockaddr*) 0, port, cgi_pattern, charset, cwd, no_log, logfp, no_symlink, do_vhost, do_global_passwd, url_pattern, local_pattern, no_empty_referers ); if ( hs == (httpd_server*) 0 ) exit( 1 ); /* Set up the occasional timer. */ if ( tmr_create( (struct timeval*) 0, occasional, JunkClientData, OCCASIONAL_TIME * 1000L, 1 ) == (Timer*) 0 ) { syslog( LOG_CRIT, "tmr_create(occasional) failed" ); exit( 1 ); } if ( numthrottles > 0 ) { /* Set up the throttles timer. */ if ( tmr_create( (struct timeval*) 0, update_throttles, JunkClientData, THROTTLE_TIME * 1000L, 1 ) == (Timer*) 0 ) { syslog( LOG_CRIT, "tmr_create(update_throttles) failed" ); exit( 1 ); } } #ifdef STATS_TIME /* Set up the stats timer. */ if ( tmr_create( (struct timeval*) 0, show_stats, JunkClientData, STATS_TIME * 1000L, 1 ) == (Timer*) 0 ) { syslog( LOG_CRIT, "tmr_create(show_stats) failed" ); exit( 1 ); } #endif /* STATS_TIME */ start_time = stats_time = time( (time_t*) 0 ); stats_connections = stats_bytes = 0L; stats_simultaneous = 0; /* If we're root, try to become someone else. */ if ( getuid() == 0 ) { /* Set aux groups to null. */ if ( setgroups( 0, (const gid_t*) 0 ) < 0 ) { syslog( LOG_CRIT, "setgroups - %m" ); exit( 1 ); } /* Set primary group. */ if ( setgid( gid ) < 0 ) { syslog( LOG_CRIT, "setgid - %m" ); exit( 1 ); } /* Try setting aux groups correctly - not critical if this fails. */ if ( initgroups( user, gid ) < 0 ) syslog( LOG_WARNING, "initgroups - %m" ); #ifdef HAVE_SETLOGIN /* Set login name. */ (void) setlogin( user ); #endif /* HAVE_SETLOGIN */ /* Set uid. */ if ( setuid( uid ) < 0 ) { syslog( LOG_CRIT, "setuid - %m" ); exit( 1 ); } /* Check for unnecessary security exposure. */ if ( ! do_chroot ) syslog( LOG_CRIT, "started as root without requesting chroot(), warning only" ); } /* Initialize our connections table. */ maxconnects = fdwatch_get_nfiles(); if ( maxconnects < 0 ) { syslog( LOG_CRIT, "fdwatch initialization failure" ); exit( 1 ); } maxconnects -= SPARE_FDS; connects = NEW( connecttab, maxconnects ); if ( connects == (connecttab*) 0 ) { syslog( LOG_CRIT, "out of memory allocating a connecttab" ); exit( 1 ); } for ( cnum = 0; cnum < maxconnects; ++cnum ) { connects[cnum].conn_state = CNST_FREE; connects[cnum].hc = (httpd_conn*) 0; } numconnects = 0; httpd_conn_count = 0; if ( hs != (httpd_server*) 0 ) { if ( hs->listen4_fd != -1 ) fdwatch_add_fd( hs->listen4_fd, (void*) 0, FDW_READ ); if ( hs->listen6_fd != -1 ) fdwatch_add_fd( hs->listen6_fd, (void*) 0, FDW_READ ); } /* Main loop. */ (void) gettimeofday( &tv, (struct timezone*) 0 ); while ( ( ! terminate ) || numconnects > 0 ) { /* Do the fd watch. */ num_ready = fdwatch( tmr_mstimeout( &tv ) ); if ( num_ready < 0 ) { if ( errno == EINTR ) continue; /* try again */ syslog( LOG_ERR, "fdwatch - %m" ); exit( 1 ); } (void) gettimeofday( &tv, (struct timezone*) 0 ); if ( num_ready == 0 ) { /* No fd's are ready - run the timers. */ tmr_run( &tv ); continue; } /* Is it a new connection? */ if ( hs != (httpd_server*) 0 && hs->listen6_fd != -1 && fdwatch_check_fd( hs->listen6_fd ) ) { if ( handle_newconnect( &tv, hs->listen6_fd ) ) /* Go around the loop and do another fdwatch, rather than ** dropping through and processing existing connections. ** New connections always get priority. */ continue; } if ( hs != (httpd_server*) 0 && hs->listen4_fd != -1 && fdwatch_check_fd( hs->listen4_fd ) ) { if ( handle_newconnect( &tv, hs->listen4_fd ) ) /* Go around the loop and do another fdwatch, rather than ** dropping through and processing existing connections. ** New connections always get priority. */ continue; } /* Find the connections that need servicing. */ for ( ridx = 0; ridx < num_ready; ++ridx ) { c = (connecttab*) fdwatch_get_client_data( ridx ); if ( c == (connecttab*) 0 ) continue; hc = c->hc; if ( c->conn_state == CNST_READING && fdwatch_check_fd( hc->conn_fd ) ) handle_read( c, &tv ); else if ( c->conn_state == CNST_SENDING && fdwatch_check_fd( hc->conn_fd ) ) handle_send( c, &tv ); else if ( c->conn_state == CNST_LINGERING && fdwatch_check_fd( hc->conn_fd ) ) handle_linger( c, &tv ); } tmr_run( &tv ); if ( got_usr1 && ! terminate ) { terminate = 1; if ( hs != (httpd_server*) 0 ) { httpd_terminate( hs ); hs = (httpd_server*) 0; } } } /* The main loop terminated. */ shut_down(); syslog( LOG_NOTICE, "exiting" ); closelog(); exit( 0 ); }
static void handle_send( connecttab* c, struct timeval* tvP ) { int sz, coast; ClientData client_data; time_t elapsed; httpd_conn* hc = c->hc; /* Do we need to write the headers first? */ if ( hc->responselen == 0 ) { /* No, just write the file. */ sz = write( hc->conn_fd, &(hc->file_address[c->bytes_sent]), MIN( c->bytes_to_send - c->bytes_sent, c->limit ) ); } else { /* Yes. We'll combine headers and file into a single writev(), ** hoping that this generates a single packet. */ struct iovec iv[2]; iv[0].iov_base = hc->response; iv[0].iov_len = hc->responselen; iv[1].iov_base = &(hc->file_address[c->bytes_sent]); iv[1].iov_len = MIN( c->bytes_to_send - c->bytes_sent, c->limit ); sz = writev( hc->conn_fd, iv, 2 ); } if ( sz == 0 || ( sz < 0 && ( errno == EWOULDBLOCK || errno == EAGAIN ) ) ) { /* This shouldn't happen, but some kernels, e.g. ** SunOS 4.1.x, are broken and select() says that ** O_NDELAY sockets are always writable even when ** they're actually not. ** ** Current workaround is to block sending on this ** socket for a brief adaptively-tuned period. ** Fortunately we already have all the necessary ** blocking code, for use with throttling. */ c->wouldblock_delay += MIN_WOULDBLOCK_DELAY; c->conn_state = CNST_PAUSING; fdwatch_del_fd( hc->conn_fd ); client_data.p = c; c->wakeup_timer = tmr_create( tvP, wakeup_connection, client_data, c->wouldblock_delay, 0 ); if ( c->wakeup_timer == (Timer*) 0 ) { syslog( LOG_CRIT, "tmr_create(wakeup_connection) failed" ); exit( 1 ); } return; } if ( sz < 0 ) { /* Something went wrong, close this connection. ** ** If it's just an EPIPE, don't bother logging, that ** just means the client hung up on us. ** ** On some systems, write() occasionally gives an EINVAL. ** Dunno why, something to do with the socket going ** bad. Anyway, we don't log those either. ** ** And ECONNRESET isn't interesting either. */ if ( errno != EPIPE && errno != EINVAL && errno != ECONNRESET ) syslog( LOG_ERR, "write - %m sending %.80s", hc->encodedurl ); clear_connection( c, tvP ); return; } /* Ok, we wrote something. */ tmr_reset( tvP, c->idle_send_timer ); /* Was this a headers + file writev()? */ if ( hc->responselen > 0 ) { /* Yes; did we write only part of the headers? */ if ( sz < hc->responselen ) { /* Yes; move the unwritten part to the front of the buffer. */ int newlen = hc->responselen - sz; (void) memcpy( hc->response, &(hc->response[sz]), newlen ); hc->responselen = newlen; sz = 0; } else { /* Nope, we wrote the full headers, so adjust accordingly. */ sz -= hc->responselen; hc->responselen = 0; } } /* And update how much of the file we wrote. */ c->bytes_sent += sz; c->hc->bytes_sent += sz; /* Are we done? */ if ( c->bytes_sent >= c->bytes_to_send ) { /* This conection is finished! */ clear_connection( c, tvP ); return; } /* Tune the (blockheaded) wouldblock delay. */ if ( c->wouldblock_delay > MIN_WOULDBLOCK_DELAY ) c->wouldblock_delay -= MIN_WOULDBLOCK_DELAY; /* If we're throttling, check if we're sending too fast. */ if ( c->limit != THROTTLE_NOLIMIT ) { elapsed = tvP->tv_sec - c->started_at; if ( elapsed == 0 || c->hc->bytes_sent / elapsed > c->limit ) { c->conn_state = CNST_PAUSING; fdwatch_del_fd( hc->conn_fd ); /* When should we send the next c->limit bytes ** to get back on schedule? If less than a second ** (integer math rounding), use 1/8 second. */ coast = ( c->hc->bytes_sent + c->limit ) / c->limit - elapsed; client_data.p = c; c->wakeup_timer = tmr_create( tvP, wakeup_connection, client_data, coast ? ( coast * 1000L ) : 125L, 0 ); if ( c->wakeup_timer == (Timer*) 0 ) { syslog( LOG_CRIT, "tmr_create(wakeup_connection) failed" ); exit( 1 ); } } } }