X-Git-Url: https://git.verplant.org/?a=blobdiff_plain;f=daemon.c;h=0ae9af0ad4d6c58d8c7cc0b615d8f9a7bc75572c;hb=1cadb5a2719eef7dc4e707ee2f796c94661f4622;hp=315a74bf108dc648fd9b86b40a4257fe27df907e;hpb=a87e8be2aece466e3f41dfde81c80709e1d56cd3;p=git.git diff --git a/daemon.c b/daemon.c index 315a74bf..0ae9af0a 100644 --- a/daemon.c +++ b/daemon.c @@ -1,9 +1,12 @@ #include "cache.h" #include "pkt-line.h" +#include +#include #include #include +#include -static const char daemon_usage[] = "git-daemon [--port=n]"; +static const char daemon_usage[] = "git-daemon [--inetd | --port=n]"; static int upload(char *dir, int dirlen) { @@ -23,13 +26,27 @@ static int upload(char *dir, int dirlen) access("HEAD", R_OK)) return -1; + /* + * We'll ignore SIGTERM from now on, we have a + * good client. + */ + signal(SIGTERM, SIG_IGN); + /* git-upload-pack only ever reads stuff, so this is safe */ execlp("git-upload-pack", "git-upload-pack", ".", NULL); return -1; } -static int execute(char *line, int len) +static int execute(void) { + static char line[1000]; + int len; + + len = packet_read_line(0, line, sizeof(line)); + + if (len && line[len-1] == '\n') + line[--len] = 0; + if (!strncmp("git-upload-pack /", line, 17)) return upload(line + 16, len - 16); @@ -37,25 +54,167 @@ static int execute(char *line, int len) return -1; } + +/* + * We count spawned/reaped separately, just to avoid any + * races when updating them from signals. The SIGCHLD handler + * will only update children_reaped, and the fork logic will + * only update children_spawned. + * + * MAX_CHILDREN should be a power-of-two to make the modulus + * operation cheap. It should also be at least twice + * the maximum number of connections we will ever allow. + */ +#define MAX_CHILDREN 128 + +static int max_connections = 25; + +/* These are updated by the signal handler */ +static volatile unsigned int children_reaped = 0; +pid_t dead_child[MAX_CHILDREN]; + +/* These are updated by the main loop */ +static unsigned int children_spawned = 0; +static unsigned int children_deleted = 0; + +struct child { + pid_t pid; + int addrlen; + struct sockaddr_in address; +} live_child[MAX_CHILDREN]; + +static void add_child(int idx, pid_t pid, struct sockaddr_in *addr, int addrlen) +{ + live_child[idx].pid = pid; + live_child[idx].addrlen = addrlen; + live_child[idx].address = *addr; +} + +/* + * Walk from "deleted" to "spawned", and remove child "pid". + * + * We move everything up by one, since the new "deleted" will + * be one higher. + */ +static void remove_child(pid_t pid, unsigned deleted, unsigned spawned) +{ + struct child n; + + deleted %= MAX_CHILDREN; + spawned %= MAX_CHILDREN; + if (live_child[deleted].pid == pid) { + live_child[deleted].pid = -1; + return; + } + n = live_child[deleted]; + for (;;) { + struct child m; + deleted = (deleted + 1) % MAX_CHILDREN; + if (deleted == spawned) + die("could not find dead child %d\n", pid); + m = live_child[deleted]; + live_child[deleted] = n; + if (m.pid == pid) + return; + n = m; + } +} + +/* + * This gets called if the number of connections grows + * past "max_connections". + * + * We _should_ start off by searching for connections + * from the same IP, and if there is some address wth + * multiple connections, we should kill that first. + * + * As it is, we just "randomly" kill 25% of the connections, + * and our pseudo-random generator sucks too. I have no + * shame. + * + * Really, this is just a place-holder for a _real_ algorithm. + */ +static void kill_some_children(int signo, unsigned start, unsigned stop) +{ + start %= MAX_CHILDREN; + stop %= MAX_CHILDREN; + while (start != stop) { + if (!(start & 3)) + kill(live_child[start].pid, signo); + start = (start + 1) % MAX_CHILDREN; + } +} + +static void check_max_connections(void) +{ + for (;;) { + int active; + unsigned spawned, reaped, deleted; + + spawned = children_spawned; + reaped = children_reaped; + deleted = children_deleted; + + while (deleted < reaped) { + pid_t pid = dead_child[deleted % MAX_CHILDREN]; + remove_child(pid, deleted, spawned); + deleted++; + } + children_deleted = deleted; + + active = spawned - deleted; + if (active <= max_connections) + break; + + /* Kill some unstarted connections with SIGTERM */ + kill_some_children(SIGTERM, deleted, spawned); + if (active <= max_connections << 1) + break; + + /* If the SIGTERM thing isn't helping use SIGKILL */ + kill_some_children(SIGKILL, deleted, spawned); + sleep(1); + } +} + static void handle(int incoming, struct sockaddr_in *addr, int addrlen) { - static char line[1000]; - int len; + pid_t pid = fork(); + + if (pid) { + unsigned idx; - if (fork()) { close(incoming); + if (pid < 0) + return; + + idx = children_spawned % MAX_CHILDREN; + children_spawned++; + add_child(idx, pid, addr, addrlen); + + check_max_connections(); return; } dup2(incoming, 0); dup2(incoming, 1); close(incoming); - len = packet_read_line(0, line, sizeof(line)); + exit(execute()); +} - if (len && line[len-1] == '\n') - line[--len] = 0; +static void child_handler(int signo) +{ + for (;;) { + pid_t pid = waitpid(-1, NULL, WNOHANG); - exit(execute(line, len)); + if (pid > 0) { + unsigned reaped = children_reaped; + dead_child[reaped % MAX_CHILDREN] = pid; + children_reaped = reaped + 1; + continue; + } + break; + } } static int serve(int port) @@ -63,6 +222,7 @@ static int serve(int port) int sockfd; struct sockaddr_in addr; + signal(SIGCHLD, child_handler); sockfd = socket(PF_INET, SOCK_STREAM, IPPROTO_IP); if (sockfd < 0) die("unable to open socket (%s)", strerror(errno)); @@ -96,6 +256,7 @@ static int serve(int port) int main(int argc, char **argv) { int port = DEFAULT_GIT_PORT; + int inetd_mode = 0; int i; for (i = 1; i < argc; i++) { @@ -110,8 +271,17 @@ int main(int argc, char **argv) continue; } } + + if (!strcmp(arg, "--inetd")) { + inetd_mode = 1; + continue; + } + usage(daemon_usage); } + if (inetd_mode) + return execute(); + return serve(port); }