Merge branch 'sb/daemon'
* sb/daemon: daemon.c: minor style fixup git-daemon: rewrite kindergarden, new option --max-connections git-daemon: Simplify dead-children reaping logic git-daemon: use LOG_PID, simplify logging code git-daemon: call logerror() instead of error()
This commit is contained in:
commit
ff1e8bfcd6
@ -9,8 +9,9 @@ SYNOPSIS
|
||||
--------
|
||||
[verse]
|
||||
'git daemon' [--verbose] [--syslog] [--export-all]
|
||||
[--timeout=n] [--init-timeout=n] [--strict-paths]
|
||||
[--base-path=path] [--user-path | --user-path=path]
|
||||
[--timeout=n] [--init-timeout=n] [--max-connections=n]
|
||||
[--strict-paths] [--base-path=path] [--base-path-relaxed]
|
||||
[--user-path | --user-path=path]
|
||||
[--interpolated-path=pathtemplate]
|
||||
[--reuseaddr] [--detach] [--pid-file=file]
|
||||
[--enable=service] [--disable=service]
|
||||
@ -99,6 +100,10 @@ OPTIONS
|
||||
it takes for the server to process the sub-request and time spent
|
||||
waiting for next client's request.
|
||||
|
||||
--max-connections::
|
||||
Maximum number of concurrent clients, defaults to 32. Set it to
|
||||
zero for no limit.
|
||||
|
||||
--syslog::
|
||||
Log to syslog instead of stderr. Note that this option does not imply
|
||||
--verbose, thus by default only error conditions will be logged.
|
||||
|
295
daemon.c
295
daemon.c
@ -16,12 +16,11 @@
|
||||
static int log_syslog;
|
||||
static int verbose;
|
||||
static int reuseaddr;
|
||||
static int child_handler_pipe[2];
|
||||
|
||||
static const char daemon_usage[] =
|
||||
"git daemon [--verbose] [--syslog] [--export-all]\n"
|
||||
" [--timeout=n] [--init-timeout=n] [--strict-paths]\n"
|
||||
" [--base-path=path] [--base-path-relaxed]\n"
|
||||
" [--timeout=n] [--init-timeout=n] [--max-connections=n]\n"
|
||||
" [--strict-paths] [--base-path=path] [--base-path-relaxed]\n"
|
||||
" [--user-path | --user-path=path]\n"
|
||||
" [--interpolated-path=path]\n"
|
||||
" [--reuseaddr] [--detach] [--pid-file=file]\n"
|
||||
@ -78,38 +77,19 @@ static struct interp interp_table[] = {
|
||||
|
||||
static void logreport(int priority, const char *err, va_list params)
|
||||
{
|
||||
/* We should do a single write so that it is atomic and output
|
||||
* of several processes do not get intermingled. */
|
||||
char buf[1024];
|
||||
int buflen;
|
||||
int maxlen, msglen;
|
||||
|
||||
/* sizeof(buf) should be big enough for "[pid] \n" */
|
||||
buflen = snprintf(buf, sizeof(buf), "[%ld] ", (long) getpid());
|
||||
|
||||
maxlen = sizeof(buf) - buflen - 1; /* -1 for our own LF */
|
||||
msglen = vsnprintf(buf + buflen, maxlen, err, params);
|
||||
|
||||
if (log_syslog) {
|
||||
char buf[1024];
|
||||
vsnprintf(buf, sizeof(buf), err, params);
|
||||
syslog(priority, "%s", buf);
|
||||
return;
|
||||
}
|
||||
|
||||
/* maxlen counted our own LF but also counts space given to
|
||||
* vsnprintf for the terminating NUL. We want to make sure that
|
||||
* we have space for our own LF and NUL after the "meat" of the
|
||||
* message, so truncate it at maxlen - 1.
|
||||
} else {
|
||||
/*
|
||||
* Since stderr is set to linebuffered mode, the
|
||||
* logging of different processes will not overlap
|
||||
*/
|
||||
if (msglen > maxlen - 1)
|
||||
msglen = maxlen - 1;
|
||||
else if (msglen < 0)
|
||||
msglen = 0; /* Protect against weird return values. */
|
||||
buflen += msglen;
|
||||
|
||||
buf[buflen++] = '\n';
|
||||
buf[buflen] = '\0';
|
||||
|
||||
write_in_full(2, buf, buflen);
|
||||
fprintf(stderr, "[%d] ", (int)getpid());
|
||||
vfprintf(stderr, err, params);
|
||||
fputc('\n', stderr);
|
||||
}
|
||||
}
|
||||
|
||||
static void logerror(const char *err, ...)
|
||||
@ -604,69 +584,46 @@ static int execute(struct sockaddr *addr)
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int max_connections = 32;
|
||||
|
||||
/*
|
||||
* 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;
|
||||
static pid_t dead_child[MAX_CHILDREN];
|
||||
|
||||
/* These are updated by the main loop */
|
||||
static unsigned int children_spawned;
|
||||
static unsigned int children_deleted;
|
||||
static unsigned int live_children;
|
||||
|
||||
static struct child {
|
||||
struct child *next;
|
||||
pid_t pid;
|
||||
int addrlen;
|
||||
struct sockaddr_storage address;
|
||||
} live_child[MAX_CHILDREN];
|
||||
} *firstborn;
|
||||
|
||||
static void add_child(int idx, pid_t pid, struct sockaddr *addr, int addrlen)
|
||||
static void add_child(pid_t pid, struct sockaddr *addr, int addrlen)
|
||||
{
|
||||
live_child[idx].pid = pid;
|
||||
live_child[idx].addrlen = addrlen;
|
||||
memcpy(&live_child[idx].address, addr, addrlen);
|
||||
}
|
||||
struct child *newborn, **cradle;
|
||||
|
||||
/*
|
||||
* Walk from "deleted" to "spawned", and remove child "pid".
|
||||
*
|
||||
* We move everything up by one, since the new "deleted" will
|
||||
* be one higher.
|
||||
* This must be xcalloc() -- we'll compare the whole sockaddr_storage
|
||||
* but individual address may be shorter.
|
||||
*/
|
||||
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;
|
||||
newborn = xcalloc(1, sizeof(*newborn));
|
||||
live_children++;
|
||||
newborn->pid = pid;
|
||||
memcpy(&newborn->address, addr, addrlen);
|
||||
for (cradle = &firstborn; *cradle; cradle = &(*cradle)->next)
|
||||
if (!memcmp(&(*cradle)->address, &newborn->address,
|
||||
sizeof(newborn->address)))
|
||||
break;
|
||||
newborn->next = *cradle;
|
||||
*cradle = newborn;
|
||||
}
|
||||
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;
|
||||
|
||||
static void remove_child(pid_t pid)
|
||||
{
|
||||
struct child **cradle, *blanket;
|
||||
|
||||
for (cradle = &firstborn; (blanket = *cradle); cradle = &blanket->next)
|
||||
if (blanket->pid == pid) {
|
||||
*cradle = blanket->next;
|
||||
live_children--;
|
||||
free(blanket);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@ -674,99 +631,60 @@ static void remove_child(pid_t pid, unsigned deleted, unsigned spawned)
|
||||
* 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.
|
||||
* We kill the newest connection from a duplicate IP.
|
||||
*/
|
||||
static void kill_some_children(int signo, unsigned start, unsigned stop)
|
||||
static void kill_some_child(void)
|
||||
{
|
||||
start %= MAX_CHILDREN;
|
||||
stop %= MAX_CHILDREN;
|
||||
while (start != stop) {
|
||||
if (!(start & 3))
|
||||
kill(live_child[start].pid, signo);
|
||||
start = (start + 1) % MAX_CHILDREN;
|
||||
const struct child *blanket, *next;
|
||||
|
||||
if (!(blanket = firstborn))
|
||||
return;
|
||||
|
||||
for (; (next = blanket->next); blanket = next)
|
||||
if (!memcmp(&blanket->address, &next->address,
|
||||
sizeof(next->address))) {
|
||||
kill(blanket->pid, SIGTERM);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void check_dead_children(void)
|
||||
{
|
||||
unsigned spawned, reaped, deleted;
|
||||
int status;
|
||||
pid_t pid;
|
||||
|
||||
spawned = children_spawned;
|
||||
reaped = children_reaped;
|
||||
deleted = children_deleted;
|
||||
|
||||
while (deleted < reaped) {
|
||||
pid_t pid = dead_child[deleted % MAX_CHILDREN];
|
||||
const char *dead = pid < 0 ? " (with error)" : "";
|
||||
|
||||
if (pid < 0)
|
||||
pid = -pid;
|
||||
|
||||
/* XXX: Custom logging, since we don't wanna getpid() */
|
||||
if (verbose) {
|
||||
if (log_syslog)
|
||||
syslog(LOG_INFO, "[%d] Disconnected%s",
|
||||
pid, dead);
|
||||
else
|
||||
fprintf(stderr, "[%d] Disconnected%s\n",
|
||||
pid, dead);
|
||||
}
|
||||
remove_child(pid, deleted, spawned);
|
||||
deleted++;
|
||||
}
|
||||
children_deleted = deleted;
|
||||
}
|
||||
|
||||
static void check_max_connections(void)
|
||||
{
|
||||
for (;;) {
|
||||
int active;
|
||||
unsigned spawned, deleted;
|
||||
|
||||
check_dead_children();
|
||||
|
||||
spawned = children_spawned;
|
||||
deleted = children_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);
|
||||
while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
|
||||
const char *dead = "";
|
||||
remove_child(pid);
|
||||
if (!WIFEXITED(status) || (WEXITSTATUS(status) > 0))
|
||||
dead = " (with error)";
|
||||
loginfo("[%d] Disconnected%s", (int)pid, dead);
|
||||
}
|
||||
}
|
||||
|
||||
static void handle(int incoming, struct sockaddr *addr, int addrlen)
|
||||
{
|
||||
pid_t pid = fork();
|
||||
|
||||
if (pid) {
|
||||
unsigned idx;
|
||||
pid_t pid;
|
||||
|
||||
if (max_connections && live_children >= max_connections) {
|
||||
kill_some_child();
|
||||
sleep(1); /* give it some time to die */
|
||||
check_dead_children();
|
||||
if (live_children >= max_connections) {
|
||||
close(incoming);
|
||||
if (pid < 0)
|
||||
logerror("Too many children, dropping connection");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
idx = children_spawned % MAX_CHILDREN;
|
||||
children_spawned++;
|
||||
add_child(idx, pid, addr, addrlen);
|
||||
if ((pid = fork())) {
|
||||
close(incoming);
|
||||
if (pid < 0) {
|
||||
logerror("Couldn't fork %s", strerror(errno));
|
||||
return;
|
||||
}
|
||||
|
||||
check_max_connections();
|
||||
add_child(pid, addr, addrlen);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -779,21 +697,11 @@ static void handle(int incoming, struct sockaddr *addr, int addrlen)
|
||||
|
||||
static void child_handler(int signo)
|
||||
{
|
||||
for (;;) {
|
||||
int status;
|
||||
pid_t pid = waitpid(-1, &status, WNOHANG);
|
||||
|
||||
if (pid > 0) {
|
||||
unsigned reaped = children_reaped;
|
||||
if (!WIFEXITED(status) || WEXITSTATUS(status) > 0)
|
||||
pid = -pid;
|
||||
dead_child[reaped % MAX_CHILDREN] = pid;
|
||||
children_reaped = reaped + 1;
|
||||
write(child_handler_pipe[1], &status, 1);
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
/*
|
||||
* Otherwise empty handler because systemcalls will get interrupted
|
||||
* upon signal receipt
|
||||
* SysV needs the handler to be rearmed
|
||||
*/
|
||||
signal(SIGCHLD, child_handler);
|
||||
}
|
||||
|
||||
@ -836,7 +744,7 @@ static int socksetup(char *listen_addr, int listen_port, int **socklist_p)
|
||||
if (sockfd < 0)
|
||||
continue;
|
||||
if (sockfd >= FD_SETSIZE) {
|
||||
error("too large socket descriptor.");
|
||||
logerror("Socket descriptor too large");
|
||||
close(sockfd);
|
||||
continue;
|
||||
}
|
||||
@ -936,35 +844,28 @@ static int service_loop(int socknum, int *socklist)
|
||||
struct pollfd *pfd;
|
||||
int i;
|
||||
|
||||
if (pipe(child_handler_pipe) < 0)
|
||||
die ("Could not set up pipe for child handler");
|
||||
|
||||
pfd = xcalloc(socknum + 1, sizeof(struct pollfd));
|
||||
pfd = xcalloc(socknum, sizeof(struct pollfd));
|
||||
|
||||
for (i = 0; i < socknum; i++) {
|
||||
pfd[i].fd = socklist[i];
|
||||
pfd[i].events = POLLIN;
|
||||
}
|
||||
pfd[socknum].fd = child_handler_pipe[0];
|
||||
pfd[socknum].events = POLLIN;
|
||||
|
||||
signal(SIGCHLD, child_handler);
|
||||
|
||||
for (;;) {
|
||||
int i;
|
||||
|
||||
if (poll(pfd, socknum + 1, -1) < 0) {
|
||||
check_dead_children();
|
||||
|
||||
if (poll(pfd, socknum, -1) < 0) {
|
||||
if (errno != EINTR) {
|
||||
error("poll failed, resuming: %s",
|
||||
logerror("Poll failed, resuming: %s",
|
||||
strerror(errno));
|
||||
sleep(1);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (pfd[socknum].revents & POLLIN) {
|
||||
read(child_handler_pipe[0], &i, 1);
|
||||
check_dead_children();
|
||||
}
|
||||
|
||||
for (i = 0; i < socknum; i++) {
|
||||
if (pfd[i].revents & POLLIN) {
|
||||
@ -1055,11 +956,6 @@ int main(int argc, char **argv)
|
||||
gid_t gid = 0;
|
||||
int i;
|
||||
|
||||
/* Without this we cannot rely on waitpid() to tell
|
||||
* what happened to our children.
|
||||
*/
|
||||
signal(SIGCHLD, SIG_DFL);
|
||||
|
||||
for (i = 1; i < argc; i++) {
|
||||
char *arg = argv[i];
|
||||
|
||||
@ -1105,6 +1001,12 @@ int main(int argc, char **argv)
|
||||
init_timeout = atoi(arg+15);
|
||||
continue;
|
||||
}
|
||||
if (!prefixcmp(arg, "--max-connections=")) {
|
||||
max_connections = atoi(arg+18);
|
||||
if (max_connections < 0)
|
||||
max_connections = 0; /* unlimited */
|
||||
continue;
|
||||
}
|
||||
if (!strcmp(arg, "--strict-paths")) {
|
||||
strict_paths = 1;
|
||||
continue;
|
||||
@ -1178,9 +1080,10 @@ int main(int argc, char **argv)
|
||||
}
|
||||
|
||||
if (log_syslog) {
|
||||
openlog("git-daemon", 0, LOG_DAEMON);
|
||||
openlog("git-daemon", LOG_PID, LOG_DAEMON);
|
||||
set_die_routine(daemon_die);
|
||||
}
|
||||
} else
|
||||
setlinebuf(stderr); /* avoid splitting a message in the middle */
|
||||
|
||||
if (inetd_mode && (group_name || user_name))
|
||||
die("--user and --group are incompatible with --inetd");
|
||||
@ -1233,8 +1136,10 @@ int main(int argc, char **argv)
|
||||
return execute(peer);
|
||||
}
|
||||
|
||||
if (detach)
|
||||
if (detach) {
|
||||
daemonize();
|
||||
loginfo("Ready to rumble");
|
||||
}
|
||||
else
|
||||
sanitize_stdfds();
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user