/* -*- c-file-style: "java"; indent-tabs-mode: nil; tab-width: 4; fill-column: 78 -*- * * distcc -- A simple distributed compiler system * * Copyright (C) 2002, 2003 by Martin Pool * Copyright 2007 Google Inc. * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, * USA. */ /* "Just like distributed.net, only useful!" */ /** * @file * * distcc volunteer server. Accepts and serves requests to compile * files. * * May be run from inetd (default if stdin is a socket), or as a * daemon by itself. * * distcc has an adequate but perhaps not optimal system for deciding * where to send files. The general principle is that the server * should say how many jobs it is willing to accept, rather than the * client having to know. This is probably good in two ways: it * allows for people in the future to impose limits on how much work * their contributed machine will do, and secondly it seems better to * put this information in one place rather than on every client. **/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_ARPA_NAMESER_H # include #endif #include #include "exitcode.h" #include "distcc.h" #include "trace.h" #include "util.h" #include "dopt.h" #include "srvnet.h" #include "daemon.h" #include "types.h" #ifdef HAVE_GSSAPI #include "auth.h" #endif /* for trace.c */ char const *rs_program_name = "distccd"; /* for serve.c */ char const *dcc_daemon_wd; /* The working directory for the server. */ static int dcc_inetd_server(void); static void dcc_setup_real_log(void); /** * Errors during startup (e.g. bad options) need to be reported somewhere, * although we have not yet parsed the options to work out where the user * wants them. * * In inetd mode, we can't write to stderr because that will corrupt the * stream, so if it looks like stderr is a socket we go to syslog instead. **/ static int dcc_setup_startup_log(void) { rs_trace_set_level(RS_LOG_INFO); if (!is_a_socket(STDERR_FILENO)) { rs_add_logger(rs_logger_file, RS_LOG_DEBUG, 0, STDERR_FILENO); } else { openlog("distccd", LOG_PID, LOG_DAEMON); rs_add_logger(rs_logger_syslog, RS_LOG_DEBUG, NULL, 0); } return 0; } static int dcc_should_be_inetd(void) { /* Work out if we ought to serve stdin or be a standalone daemon */ if (opt_inetd_mode) return 1; else if (opt_daemon_mode) return 0; else if (is_a_socket(STDIN_FILENO)) { rs_log_info("stdin is socket; assuming --inetd mode"); return 1; } else if (isatty(STDIN_FILENO)) { rs_log_info("stdin is a tty; assuming --daemon mode"); return 0; } else { rs_log_info("stdin is neither a tty nor a socket; assuming --daemon mode"); return 0; } } static int dcc_setup_daemon_path(void) { int ret; const char *path; if ((path = getenv("DISTCCD_PATH")) != NULL) { if ((ret = dcc_set_path(path))) return ret; return 0; } else { path = getenv("PATH"); rs_log_info("daemon's PATH is %s", path ? path : "(NULL)"); return 0; } } /** * distcc daemon. May run from inetd, or standalone. Accepts * requests from clients to compile files. **/ int main(int argc, char *argv[]) { int ret; dcc_setup_startup_log(); if (distccd_parse_options(argc, (const char **) argv)) dcc_exit(EXIT_DISTCC_FAILED); /* check this before redirecting the logs, so that it's really obvious */ if (!dcc_should_be_inetd()) if (opt_allowed == NULL) { rs_log_error("--allow option is now mandatory; " "you must specify which clients are allowed to connect"); ret = EXIT_BAD_ARGUMENTS; goto out; } if ((ret = dcc_set_lifetime()) != 0) dcc_exit(ret); /* do this before giving away root */ if (nice(opt_niceness) == -1) { rs_log_warning("nice %d failed: %s", opt_niceness, strerror(errno)); /* continue anyhow */ } if ((ret = dcc_discard_root()) != 0) dcc_exit(ret); /* Discard privileges before opening log so that if it's created, it has * the right ownership. */ dcc_setup_real_log(); /* Do everything from $TMPDIR directory. Allows start directory to be * unmounted. */ if ((ret = dcc_get_tmp_top(&dcc_daemon_wd))) goto out; if (chdir(dcc_daemon_wd) == -1) { rs_log_error("failed to chdir to %s: %s", dcc_daemon_wd, strerror(errno)); ret = EXIT_IO_ERROR; goto out; } else { rs_trace("chdir to %s", dcc_daemon_wd); } if ((ret = dcc_setup_daemon_path())) goto out; #ifdef HAVE_GSSAPI /* Obtain credentials if authentication is requested. */ if (dcc_auth_enabled) { if ((ret = dcc_gssapi_acquire_credentials()) != 0) { goto out; } /* Read contents of list file into an array and apply qsort. */ if (opt_blacklist_enabled || opt_whitelist_enabled) { if ((ret = dcc_gssapi_obtain_list((opt_blacklist_enabled) ? 1 : 0)) != 0) { goto out; } } } #endif /* Initialize the distcc io timeout value */ dcc_get_io_timeout(); if (dcc_should_be_inetd()) ret = dcc_inetd_server(); else ret = dcc_standalone_server(); out: dcc_exit(ret); } /** * If a --lifetime options was specified, set up a timer that will kill the * daemon when it expires. **/ int dcc_set_lifetime(void) { if (opt_lifetime) { alarm(opt_lifetime); /* rs_trace("set alarm for %+d seconds", opt_lifetime); */ } return 0; } /** * Set log to the final destination after options have been read. **/ static void dcc_setup_real_log(void) { int fd; /* Even in inetd mode, we might want to log to stderr, because that will * work OK for ssh connections. */ if (opt_log_stderr) { rs_remove_all_loggers(); rs_add_logger(rs_logger_file, opt_log_level_num, 0, STDERR_FILENO); return; } if (arg_log_file) { /* Don't remove loggers yet, in case this fails and needs to go to the * default. */ if ((fd = open(arg_log_file, O_CREAT|O_APPEND|O_WRONLY, 0666)) == -1) { rs_log_error("failed to open %s: %s", arg_log_file, strerror(errno)); /* continue and use syslog */ } else { rs_remove_all_loggers(); rs_add_logger(rs_logger_file, opt_log_level_num, NULL, fd); return; } } rs_remove_all_loggers(); openlog("distccd", LOG_PID, LOG_DAEMON); rs_add_logger(rs_logger_syslog, opt_log_level_num, NULL, 0); } int dcc_log_daemon_started(const char *role) { rs_log_info("%s started (%s %s, built %s %s)", role, PACKAGE_VERSION, GNU_HOST, __DATE__, __TIME__); return 0; } /** * Serve a single file on stdin, and then exit. **/ static int dcc_inetd_server(void) { int ret, close_ret; struct dcc_sockaddr_storage ss; struct sockaddr *psa = (struct sockaddr *) &ss; socklen_t len = sizeof ss; dcc_log_daemon_started("inetd server"); if ((getpeername(STDIN_FILENO, psa, &len) == -1)) { /* This can fail with ENOTSOCK if e.g. sshd has started us on a pipe, * not on a socket. I think it's harmless. */ rs_log_notice("failed to get peer name: %s", strerror(errno)); psa = NULL; /* make sure we don't refer to uninitialized mem */ len = 0; } ret = dcc_service_job(STDIN_FILENO, STDOUT_FILENO, psa, len); close_ret = dcc_close(STDIN_FILENO); #ifdef HAVE_GSSAPI if (dcc_auth_enabled) { dcc_gssapi_release_credentials(); if (opt_blacklist_enabled || opt_whitelist_enabled) { dcc_gssapi_free_list(); } } #endif if (ret) return ret; else return close_ret; }