1
0
mirror of https://github.com/fumiama/simple-http-server.git synced 2026-06-10 13:00:28 +08:00

fix: possible concurrent errors

This commit is contained in:
源文雨
2026-01-29 01:36:03 +08:00
parent 5d157c0f32
commit 80c4c982dd
2 changed files with 163 additions and 21 deletions

182
server.c
View File

@@ -24,6 +24,7 @@
#include <sys/un.h> #include <sys/un.h>
#include <sys/wait.h> #include <sys/wait.h>
#include <unistd.h> #include <unistd.h>
#include <errno.h>
#if !__APPLE__ #if !__APPLE__
#include <sys/sendfile.h> #include <sys/sendfile.h>
@@ -76,7 +77,7 @@ static void execute_cgi(tcpool_thread_timer_t *, int, const http_request_t *);
static void forbidden(int); static void forbidden(int);
static int get_line(int, char *, int); static int get_line(int, char *, int);
static int headers(int, const char *, uint32_t); static int headers(int, const char *, uint32_t);
static void internal_error(int); static void internal_error(int, const char *, int);
static void not_found(int); static void not_found(int);
static void serve_file(int, const char *); static void serve_file(int, const char *);
static int startup(uint16_t *, int); static int startup(uint16_t *, int);
@@ -244,6 +245,19 @@ static void error_die(const char *sc) {
exit(1); exit(1);
} }
static void cgi_timeout_handler(int sig) {
puts("CGI kill alarm triggered.");
pthread_kill(pthread_self(), SIGQUIT);
}
static void reset_sigaction_push(void* old_sa_timeout) {
sigaction(SIGALRM, (const struct sigaction *)old_sa_timeout, NULL);
}
static void alarm_push(void* sec) {
alarm((unsigned int)(uintptr_t)sec);
}
static void clear_in_cgi_push(void* index) {
is_in_cgi[(uintptr_t)index] = 0;
}
/**********************************************************************/ /**********************************************************************/
/* Execute a CGI script. Will need to set environment variables as /* Execute a CGI script. Will need to set environment variables as
* appropriate. * appropriate.
@@ -255,19 +269,31 @@ static void execute_cgi(tcpool_thread_timer_t *timer, int content_length, const
pid_t pid; pid_t pid;
int client = timer->accept_fd; int client = timer->accept_fd;
// Set up timeout handler
struct sigaction sa_timeout, old_sa_timeout;
sa_timeout.sa_handler = cgi_timeout_handler;
sigemptyset(&sa_timeout.sa_mask);
sa_timeout.sa_flags = 0;
sigaction(SIGALRM, &sa_timeout, &old_sa_timeout);
pthread_cleanup_push(reset_sigaction_push, (void*)&old_sa_timeout);
alarm(30); // 30 seconds timeout for CGI execution
pthread_cleanup_push(alarm_push, 0);
if(pipe(cgi_output) < 0) { if(pipe(cgi_output) < 0) {
internal_error(client); internal_error(client, "pipe(cgi_output) failed", errno);
return; return;
} }
if(pipe(cgi_input) < 0) { if(pipe(cgi_input) < 0) {
int err = errno;
close(cgi_output[0]); close(cgi_output[1]); close(cgi_output[0]); close(cgi_output[1]);
internal_error(client); internal_error(client, "pipe(cgi_input) failed", err);
return; return;
} }
if((pid = fork()) < 0) { if((pid = fork()) < 0) {
int err = errno;
close(cgi_output[0]); close(cgi_output[1]); close(cgi_output[0]); close(cgi_output[1]);
close(cgi_input[0]); close(cgi_input[1]); close(cgi_input[0]); close(cgi_input[1]);
internal_error(client); internal_error(client, "fork failed", err);
return; return;
} }
/* child: CGI script */ /* child: CGI script */
@@ -277,16 +303,23 @@ static void execute_cgi(tcpool_thread_timer_t *timer, int content_length, const
close(cgi_output[0]); close(cgi_output[0]);
close(cgi_input[1]); close(cgi_input[1]);
write(STDOUT_FILENO, "DONEPREP", 8);
execl(request->path, request->path, request->method, request->query_string, NULL); execl(request->path, request->path, request->method, request->query_string, NULL);
exit(EXIT_FAILURE); // a success execl will never return // execl failed, write error marker to pipe
uint32_t error_marker = 0xFFFFFFFF;
write(STDOUT_FILENO, &error_marker, sizeof(error_marker));
_exit(EXIT_FAILURE); // use _exit instead of exit to avoid flushing parent's buffers
} }
pthread_cleanup_push((void (*)(void*))&close, (void*)(uintptr_t)cgi_output[0]); pthread_cleanup_push((void (*)(void*))&close, (void*)(uintptr_t)cgi_output[0]);
pthread_cleanup_push((void (*)(void*))&close, (void*)(uintptr_t)cgi_input[1]); pthread_cleanup_push((void (*)(void*))&close, (void*)(uintptr_t)cgi_input[1]);
pthread_cleanup_push((void (*)(void*))&wait_pid_push, (void*)(uintptr_t)pid); pthread_cleanup_push((void (*)(void*))&wait_pid_push, (void*)(uintptr_t)pid);
pthread_cleanup_push(clear_in_cgi_push, (void*)(uintptr_t)timer->index);
/* parent */ /* parent */
is_in_cgi[timer->index] = 1; is_in_cgi[timer->index] = 1;
close(cgi_output[1]); close(cgi_output[1]);
close(cgi_input[0]); close(cgi_input[0]);
if(request->method_type == POST) { if(request->method_type == POST) {
#if __APPLE__ #if __APPLE__
for(int i = 0; i < content_length;) { for(int i = 0; i < content_length;) {
@@ -295,7 +328,7 @@ static void execute_cgi(tcpool_thread_timer_t *timer, int content_length, const
write(cgi_input[1], timer->data, cnt); write(cgi_input[1], timer->data, cnt);
i += cnt; i += cnt;
} else { } else {
internal_error(client); internal_error(client, "recv POST data failed", errno);
goto CGI_CLOSE; goto CGI_CLOSE;
} }
} }
@@ -304,7 +337,7 @@ static void execute_cgi(tcpool_thread_timer_t *timer, int content_length, const
while(len < content_length) { while(len < content_length) {
int delta = splice(client, NULL, cgi_input[1], NULL, content_length - len, SPLICE_F_GIFT); int delta = splice(client, NULL, cgi_input[1], NULL, content_length - len, SPLICE_F_GIFT);
if(delta <= 0) { if(delta <= 0) {
internal_error(client); internal_error(client, "splice POST data failed", errno);
goto CGI_CLOSE; goto CGI_CLOSE;
} }
len += delta; len += delta;
@@ -312,15 +345,68 @@ static void execute_cgi(tcpool_thread_timer_t *timer, int content_length, const
#endif #endif
} }
char buf[8];
read(cgi_output[0], buf, 8); // wait child finish prep
uint32_t cnt = 0; uint32_t cnt = 0;
char* p = (char*)&cnt; char* p = (char*)&cnt;
while(p - (char*)&cnt < sizeof(uint32_t)) { while(p - (char*)&cnt < sizeof(uint32_t)) {
int offset = read(cgi_output[0], p, sizeof(uint32_t)); // Check if thread is being cleaned up
if(offset > 0) p += offset; if(!is_in_cgi[timer->index]) {
else { puts("CGI thread cleanup detected, abort read.");
internal_error(client);
goto CGI_CLOSE; goto CGI_CLOSE;
} }
int offset = read(cgi_output[0], p, sizeof(uint32_t) - (p - (char*)&cnt));
if(offset > 0) {
p += offset;
}
else if(offset == 0) {
// EOF: CGI process closed the pipe without writing header
// Check if child process is still running
int status;
pid_t result = waitpid(pid, &status, WNOHANG);
if(result > 0) {
// Child has exited
if(WIFEXITED(status)) {
char msg[128];
snprintf(msg, sizeof(msg), "CGI process exited with code %d before writing data", WEXITSTATUS(status));
internal_error(client, msg, 0);
} else if(WIFSIGNALED(status)) {
char msg[128];
snprintf(msg, sizeof(msg), "CGI process killed by signal %d", WTERMSIG(status));
internal_error(client, msg, 0);
} else {
internal_error(client, "CGI process terminated abnormally", 0);
}
} else if(result < 0) {
// waitpid error
internal_error(client, "waitpid failed", errno);
} else {
// result == 0: child may have become zombie
internal_error(client, "CGI closed pipe, child status unavailable", 0);
}
goto CGI_CLOSE;
}
else {
// offset < 0: error occurred
if(errno == EINTR) {
// Interrupted by signal, retry
continue;
}
// Check if fd was closed due to thread cleanup
if(errno == EBADF && !is_in_cgi[timer->index]) {
puts("CGI thread cleanup in progress, fd closed.");
goto CGI_CLOSE;
}
internal_error(client, "read CGI header failed", errno);
goto CGI_CLOSE;
}
}
// Check for error marker from child
if(cnt == 0xFFFFFFFF) {
internal_error(client, "CGI script execution failed (execl error)", 0);
goto CGI_CLOSE;
} }
printf("read %u bytes from pipe, ", cnt); printf("read %u bytes from pipe, ", cnt);
if(cnt > 0) { if(cnt > 0) {
@@ -328,9 +414,19 @@ static void execute_cgi(tcpool_thread_timer_t *timer, int content_length, const
#if __APPLE__ #if __APPLE__
int cap = (cnt>sizeof(timer->data))?sizeof(timer->data):cnt; int cap = (cnt>sizeof(timer->data))?sizeof(timer->data):cnt;
while(len < cnt) { while(len < cnt) {
// Check if thread is being cleaned up
if(!is_in_cgi[timer->index]) {
puts("CGI thread cleanup detected during output read.");
goto CGI_CLOSE;
}
int n = read(cgi_output[0], timer->data, cap); int n = read(cgi_output[0], timer->data, cap);
if(n <= 0) { if(n <= 0) {
internal_error(client); if(n < 0 && errno == EBADF && !is_in_cgi[timer->index]) {
puts("CGI thread cleanup in progress.");
goto CGI_CLOSE;
}
internal_error(client, "read CGI output failed", errno);
goto CGI_CLOSE; goto CGI_CLOSE;
} }
len += n; len += n;
@@ -338,9 +434,19 @@ static void execute_cgi(tcpool_thread_timer_t *timer, int content_length, const
} }
#else #else
while(len < cnt) { while(len < cnt) {
// Check if thread is being cleaned up
if(!is_in_cgi[timer->index]) {
puts("CGI thread cleanup detected during splice.");
goto CGI_CLOSE;
}
int delta = splice(cgi_output[0], NULL, client, NULL, cnt - len, SPLICE_F_GIFT); int delta = splice(cgi_output[0], NULL, client, NULL, cnt - len, SPLICE_F_GIFT);
if(delta <= 0) { if(delta <= 0) {
internal_error(client); if(delta < 0 && errno == EBADF && !is_in_cgi[timer->index]) {
puts("CGI thread cleanup in progress.");
goto CGI_CLOSE;
}
internal_error(client, "splice CGI output failed", errno);
goto CGI_CLOSE; goto CGI_CLOSE;
} }
len += delta; len += delta;
@@ -352,7 +458,9 @@ CGI_CLOSE:
pthread_cleanup_pop(1); pthread_cleanup_pop(1);
pthread_cleanup_pop(1); pthread_cleanup_pop(1);
pthread_cleanup_pop(1); pthread_cleanup_pop(1);
is_in_cgi[timer->index] = 0; pthread_cleanup_pop(1);
pthread_cleanup_pop(1);
pthread_cleanup_pop(1);
} }
/**********************************************************************/ /**********************************************************************/
@@ -431,10 +539,27 @@ static int headers(int client, const char *filepath, uint32_t file_size) {
/* Inform the client that a CGI script could not be executed. /* Inform the client that a CGI script could not be executed.
* Parameter: the client socket descriptor. */ * Parameter: the client socket descriptor. */
/**********************************************************************/ /**********************************************************************/
#define HTTP500 "HTTP/1.0 500 Internal Server Error\r\nContent-Type: text/html\r\n\r\n<P>Internal Server Error.\r\n" static void internal_error(int client, const char *reason, int err) {
static void internal_error(int client) { char buf[512];
send(client, HTTP500, sizeof(HTTP500)-1, 0); int len;
puts("500 Internal Server Error."); if(err == 0) {
len = snprintf(buf, sizeof(buf),
"HTTP/1.0 500 Internal Server Error\r\n"
"Content-Type: text/html\r\n\r\n"
"<HTML><TITLE>Internal Server Error</TITLE>\r\n"
"<BODY><P>Internal Server Error: %s\r\n</BODY></HTML>\r\n",
reason ? reason : "Unknown error");
printf("500 Internal Server Error: %s\n", reason ? reason : "Unknown error");
} else {
len = snprintf(buf, sizeof(buf),
"HTTP/1.0 500 Internal Server Error\r\n"
"Content-Type: text/html\r\n\r\n"
"<HTML><TITLE>Internal Server Error</TITLE>\r\n"
"<BODY><P>Internal Server Error: %s: %s\r\n</BODY></HTML>\r\n",
reason ? reason : "Unknown error", strerror(err));
printf("500 Internal Server Error: %s: %s\n", reason ? reason : "Unknown error", strerror(err));
}
send(client, buf, len, 0);
} }
/**********************************************************************/ /**********************************************************************/
@@ -539,9 +664,26 @@ static void wait_pid_push(void* pid) {
printf("Wait pid %d start.\n", p); printf("Wait pid %d start.\n", p);
alarm(5); alarm(5);
waitpid(p, NULL, 0); int status;
pid_t result = waitpid(p, &status, 0);
alarm(0); alarm(0);
printf("Wait pid %d finish.\n", p);
if(result > 0) {
if(WIFEXITED(status)) {
int exit_code = WEXITSTATUS(status);
printf("Wait pid %d finish: exited with code %d.\n", p, exit_code);
} else if(WIFSIGNALED(status)) {
int sig = WTERMSIG(status);
printf("Wait pid %d finish: killed by signal %d (%s)%s.\n",
p, sig, strsignal(sig), WCOREDUMP(status) ? " (core dumped)" : "");
} else {
printf("Wait pid %d finish: terminated abnormally.\n", p);
}
} else if(result < 0) {
printf("Wait pid %d failed: %s\n", p, strerror(errno));
} else {
printf("Wait pid %d finish: no status change.\n", p);
}
timeout_pid = 0; timeout_pid = 0;
sigaction(SIGALRM, &old_sa, NULL); sigaction(SIGALRM, &old_sa, NULL);

View File

@@ -259,7 +259,7 @@ static void accept_timer(void *p) {
uint32_t index = timer->index; uint32_t index = timer->index;
pthread_t thread = timer->thread; pthread_t thread = timer->thread;
uint8_t isbusy; uint8_t isbusy;
const time_t check_interval = TCPOOL_MAXWAITSEC / 4; const time_t check_interval = (TCPOOL_MAXWAITSEC / 4) ? (TCPOOL_MAXWAITSEC / 4) : 1;
sleep(check_interval); sleep(check_interval);
while(thread && !pthread_kill(thread, 0)) { while(thread && !pthread_kill(thread, 0)) {