summaryrefslogtreecommitdiff
path: root/src/dparent.c
diff options
context:
space:
mode:
authorCraig Silverstein <csilvers@khanacademy.org>2008-04-11 22:36:40 +0000
committerCraig Silverstein <csilvers@khanacademy.org>2008-04-11 22:36:40 +0000
commit596cf4e2f019a766965ad0566495c56b7d295fe4 (patch)
tree1726969de95d86fc5647af1eeb99ea0489ba91c4 /src/dparent.c
parent0a38eace37b20a663b235fb20107829e22fbeb43 (diff)
downloaddistcc-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.c376
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();
+}