diff options
author | Craig Silverstein <csilvers@khanacademy.org> | 2008-04-11 22:36:40 +0000 |
---|---|---|
committer | Craig Silverstein <csilvers@khanacademy.org> | 2008-04-11 22:36:40 +0000 |
commit | 596cf4e2f019a766965ad0566495c56b7d295fe4 (patch) | |
tree | 1726969de95d86fc5647af1eeb99ea0489ba91c4 /src/dparent.c | |
parent | 0a38eace37b20a663b235fb20107829e22fbeb43 (diff) | |
download | distcc-git-596cf4e2f019a766965ad0566495c56b7d295fe4.tar.gz |
The first step of moving everything in the distcc directory to the top
level. I'm doing this in two stages, because I don't understand svn
enough to be confident to do it in one. This first stage just copies
all the files from distcc/FOO to FOO. Now there are two copies of
each file under distcc; the Makefile/etc uses the one in distcc and
ignores the one at the top level.
The next commit will delete everything under distcc, and rewrite the
Makefile/etc to use the top-level versions instead.
Diffstat (limited to 'src/dparent.c')
-rw-r--r-- | src/dparent.c | 376 |
1 files changed, 376 insertions, 0 deletions
diff --git a/src/dparent.c b/src/dparent.c new file mode 100644 index 0000000..12dd802 --- /dev/null +++ b/src/dparent.c @@ -0,0 +1,376 @@ +/* -*- c-file-style: "java"; indent-tabs-mode: nil; fill-column: 78; -*- + * + * distcc -- A simple distributed compiler system + * + * Copyright (C) 2002, 2003, 2004 by Martin Pool <mbp@samba.org> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + */ + + /* Near is thy forgetfulness of all things; and near the + * forgetfulness of thee by all. + * -- Marcus Aurelius + */ + + +/** + * @file + * + * Daemon parent. Accepts connections, forks, etc. + * + * @todo Quite soon we need load management. Basically when we think + * we're "too busy" we should stop accepting connections. This could + * be because of the load average, or because too many jobs are + * running, or perhaps just because of a signal from the administrator + * of this machine. In that case we want to do a blocking wait() to + * find out when the current jobs are done, or perhaps a sleep() if + * we're waiting for the load average to go back down. However, we + * probably ought to always keep at least one job running so that we + * can make progress through the queue. If you don't want any work + * done, you should kill the daemon altogether. + **/ + +#include "config.h" + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include <syslog.h> +#include <signal.h> +#include <fcntl.h> + +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <sys/socket.h> +#include <sys/ioctl.h> + +#include "exitcode.h" +#include "distcc.h" +#include "trace.h" +#include "util.h" +#include "dopt.h" +#include "exec.h" +#include "srvnet.h" +#include "types.h" +#include "daemon.h" +#include "netutil.h" +#include "zeroconf.h" + +static void dcc_nofork_parent(int listen_fd) NORETURN; +static void dcc_detach(void); +static void dcc_save_pid(pid_t); +int dcc_nkids = 0; + + +/** + * In forking or prefork mode, the maximum number of connections we want to + * allow at any time. + **/ +int dcc_max_kids = 0; + + +/** + * Be a standalone server, with responsibility for sockets and forking + * children. Puts the daemon in the background and detaches from the + * controlling tty. + **/ +int dcc_standalone_server(void) +{ + int listen_fd; + int n_cpus; + int ret; +#ifdef HAVE_AVAHI + void *avahi = NULL; +#endif + + if ((ret = dcc_socket_listen(arg_port, &listen_fd, opt_listen_addr)) != 0) + return ret; + + dcc_defer_accept(listen_fd); + + set_cloexec_flag(listen_fd, 1); + + if (dcc_ncpus(&n_cpus) == 0) + rs_log_info("%d CPU%s online on this server", n_cpus, n_cpus == 1 ? "" : "s"); + + /* By default, allow one job per CPU, plus two for the pot. The extra + * ones are started to allow for a bit of extra concurrency so that the + * machine is not idle waiting for disk or network IO. */ + if (arg_max_jobs) + dcc_max_kids = arg_max_jobs; + else + dcc_max_kids = 2 + n_cpus; + + rs_log_info("allowing up to %d active jobs", dcc_max_kids); + + if (!opt_no_detach) { + /* Don't go into the background until we're listening and + * ready. This is useful for testing -- when the daemon + * detaches, we know we can go ahead and try to connect. */ + dcc_detach(); + } else { + /* Still create a new process group, even if not detached */ + rs_trace("not detaching"); + if ((ret = dcc_new_pgrp()) != 0) + return ret; + dcc_save_pid(getpid()); + } + + /* Don't catch signals until we've detached or created a process group. */ + dcc_daemon_catch_signals(); + +#ifdef HAVE_AVAHI + /* Zeroconf registration */ + if (opt_zeroconf) { + if (!(avahi = dcc_zeroconf_register((uint16_t) arg_port, n_cpus))) + return EXIT_CONNECT_FAILED; + } +#endif + + /* This is called in the master daemon, whether that is detached or + * not. */ + dcc_master_pid = getpid(); + + if (opt_no_fork) { + dcc_log_daemon_started("non-forking daemon"); + dcc_nofork_parent(listen_fd); + ret = 0; + } else { + dcc_log_daemon_started("preforking daemon"); + ret = dcc_preforking_parent(listen_fd); + } + +#ifdef HAVE_AVAHI + /* Remove zeroconf registration */ + if (opt_zeroconf) { + if (dcc_zeroconf_unregister(avahi) != 0) + return EXIT_CONNECT_FAILED; + } +#endif + + return ret; +} + + + +static void dcc_log_child_exited(pid_t kid, + int status) +{ + if (WIFSIGNALED(status)) { + int sig = WTERMSIG(status); + int severity = sig == SIGTERM ? RS_LOG_INFO : RS_LOG_ERR; + + rs_log(severity, "child %d: signal %d (%s)", (int) kid, sig, + WCOREDUMP(status) ? "core dumped" : "no core"); + } else if (WIFEXITED(status)) { + rs_log_info("child %d exited: exit status %d", + (int) kid, WEXITSTATUS(status)); + } +} + + + +/** + * @sa dcc_wait_child(), which is used by a process that wants to do a blocking + * wait for some task like cpp or gcc. + * + * @param must_reap If True, don't return until at least one child has been + * collected. Used when e.g. all our process slots are full. In either case + * we keep going until all outstanding zombies are collected. + * + * FIXME: Are blocking waits meant to collect all of them, or just one? At + * the moment it waits until all children have exited. + **/ +void dcc_reap_kids(int must_reap) +{ + while (1) { + int status; + pid_t kid; + + kid = waitpid(WAIT_ANY, &status, must_reap ? 0 : WNOHANG); + if (kid == 0) { + /* nobody has exited */ + break; + } else if (kid != -1) { + /* child exited */ + --dcc_nkids; + rs_trace("down to %d children", dcc_nkids); + + dcc_log_child_exited(kid, status); + } else if (errno == ECHILD) { + /* No children left? That's ok, we'll go back to waiting + * for new connections. */ + break; + } else if (errno == EINTR) { + /* If we got a SIGTERM or something, then on the next pass + * through the loop we'll find no children done, and we'll + * return to the top loop at which point we'll exit. So + * no special action is required here. */ + continue; /* loop again */ + } else { + rs_log_error("wait failed: %s", strerror(errno)); + /* e.g. too many open files; nothing we can do */ + dcc_exit(EXIT_DISTCC_FAILED); + } + + /* If there are more children keep looking, but don't block once we've + * collected at least one. */ + must_reap = FALSE; + } +} + + +/** + * Main loop for no-fork mode. + * + * Much slower and may leak. Should only be used when you want to run gdb on + * distccd. + **/ +static void dcc_nofork_parent(int listen_fd) +{ + while (1) { + int acc_fd; + struct dcc_sockaddr_storage cli_addr; + socklen_t cli_len; + + rs_log_info("waiting to accept connection"); + + cli_len = sizeof cli_addr; + acc_fd = accept(listen_fd, + (struct sockaddr *) &cli_addr, &cli_len); + if (acc_fd == -1 && errno == EINTR) { + ; + } else if (acc_fd == -1) { + rs_log_error("accept failed: %s", strerror(errno)); + dcc_exit(EXIT_CONNECT_FAILED); + } else { + dcc_service_job(acc_fd, acc_fd, (struct sockaddr *) &cli_addr, cli_len); + dcc_close(acc_fd); + } + } +} + + +/** + * Save the pid of the child process into the pid file, if any. + * + * This is called from the parent so that we have the invariant that + * the pid file exists before the parent exits, hich is useful for + * test harnesses. Otherwise, there is a race where the parent has + * exited and they try to go ahead and read the child's pid, but it's + * not there yet. + **/ +static void dcc_save_pid(pid_t pid) +{ + FILE *fp; + + if (!arg_pid_file) + return; + + if (!(fp = fopen(arg_pid_file, "wt"))) { + rs_log_error("failed to open pid file: %s: %s", arg_pid_file, + strerror(errno)); + return; + } + + fprintf(fp, "%ld\n", (long) pid); + + if (fclose(fp) == -1) { + rs_log_error("failed to close pid file: %s: %s", arg_pid_file, + strerror(errno)); + return; + } + + atexit(dcc_remove_pid); +} + + +/** + * Remove our pid file on exit. + * + * Must be reentrant -- called from signal handler. + **/ +void dcc_remove_pid(void) +{ + if (!arg_pid_file) + return; + + if (unlink(arg_pid_file)) { + rs_log_warning("failed to remove pid file %s: %s", + arg_pid_file, strerror(errno)); + } +} + + +/** + * Become a daemon, discarding the controlling terminal. + * + * Borrowed from rsync. + * + * This function returns in the child, but not in the parent. + **/ +static void dcc_detach(void) +{ + int i; + pid_t pid; + pid_t sid; + + dcc_ignore_sighup(); + + if ((pid = fork()) == -1) { + rs_log_error("fork failed: %s", strerror(errno)); + exit(EXIT_DISTCC_FAILED); + } else if (pid != 0) { + /* In the parent. This guy is about to go away so as to + * detach from the controlling process, but first save the + * child's pid. */ + dcc_save_pid(pid); + _exit(0); + } + + /* This is called in the detached child */ + + /* detach from the terminal */ +#ifdef HAVE_SETSID + if ((sid = setsid()) == -1) { + rs_log_error("setsid failed: %s", strerror(errno)); + } else { + rs_trace("setsid to session %d", (int) sid); + } +#else /* no HAVE_SETSID */ +#ifdef TIOCNOTTY + i = open("/dev/tty", O_RDWR); + if (i >= 0) { + ioctl(i, (int) TIOCNOTTY, (char *)0); + close(i); + } +#endif /* TIOCNOTTY */ +#endif /* not HAVE_SETSID */ + + /* make sure that stdin, stdout an stderr don't stuff things + up (library functions, for example) */ + for (i=0;i<3;i++) { + close(i); + open("/dev/null", O_RDWR); + } + + /* If there's a lifetime limit on this server (for testing) then it needs + * to apply after detaching as well. */ + dcc_set_lifetime(); +} |