/* -*- 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 * * 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 */ /* He who waits until circumstances completely favour * * his undertaking will never accomplish anything. * * -- Martin Luther */ /** * @file * * Actually serve remote requests. Called from daemon.c. * * @todo Make sure wait statuses are packed in a consistent format * (exit<<8 | signal). Is there any platform that doesn't do this? * * @todo The server should catch signals, and terminate the compiler process * group before handling them. * * @todo It might be nice to detect that the client has dropped the * connection, and then kill the compiler immediately. However, we probably * won't notice that until we try to do IO. SIGPIPE won't help because it's * not triggered until we try to do IO. I don't think it matters a lot, * though, because the client's not very likely to do that. The main case is * probably somebody getting bored and interrupting compilation. * * What might help is to select() on the network socket while we're waiting * for the child to complete, allowing SIGCHLD to interrupt the select() when * the child completes. However I'm not sure if it's really worth the trouble * of doing that just to handle a fairly marginal case. **/ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_SYS_SIGNAL_H # include #endif /* HAVE_SYS_SIGNAL_H */ #include #include #include "distcc.h" #include "trace.h" #include "util.h" #include "rpc.h" #include "exitcode.h" #include "snprintf.h" #include "dopt.h" #include "bulk.h" #include "exec.h" #include "srvnet.h" #include "hosts.h" #include "daemon.h" #include "versinfo.h" /* From indirect_server.c */ extern int dcc_ensure_pch_cache_size_mb(int min_size_in_mb); /** * We copy all serious distccd messages to this file, as well as sending the * compiler errors there, so they're visible to the client. **/ static int dcc_compile_log_fd = -1; static int dcc_run_job(int in_fd, int out_fd); /** * Copy all server messages to the error file, so that they can be * echoed back to the client if necessary. **/ static int dcc_add_log_to_file(const char *err_fname) { if (dcc_compile_log_fd != -1) { rs_log_crit("compile log already open?"); return 0; /* continue? */ } dcc_compile_log_fd = open(err_fname, O_WRONLY|O_CREAT|O_TRUNC, 0600); if (dcc_compile_log_fd == -1) { rs_log_error("failed to open %s: %s", err_fname, strerror(errno)); return EXIT_IO_ERROR; } /* Only send fairly serious errors back */ rs_add_logger(rs_logger_file, RS_LOG_WARNING, NULL, dcc_compile_log_fd); return 0; } static int dcc_remove_log_to_file(void) { if (dcc_compile_log_fd == -1) { rs_log_warning("compile log not open?"); return 0; /* continue? */ } /* must exactly match call in dcc_add_log_to_file */ rs_remove_logger(rs_logger_file, RS_LOG_WARNING, NULL, dcc_compile_log_fd); dcc_close(dcc_compile_log_fd); dcc_compile_log_fd = -1; return 0; } /* Read and execute a job to/from socket. This is the common entry point no * matter what mode the daemon is running in: preforked, nonforked, or * ssh/inetd. */ int dcc_service_job(int in_fd, int out_fd, struct sockaddr *cli_addr, int cli_len) { int ret; /* Log client name and check access if appropriate. For ssh connections * the client comes from a unix-domain socket and that's always * allowed. */ if ((ret = dcc_check_client(cli_addr, cli_len, opt_allowed)) != 0) goto out; /* Ensure that we have at least pullfile_min_free_space MB (--min-disk-free) * on the volume holding the PCH cache, and fail if pruning doesn't alleviate * the pressure. */ if ((ret = dcc_ensure_free_space()) != 0) goto out; ret = dcc_run_job(in_fd, out_fd); out: return ret; } static int dcc_input_tmpnam(char * orig_input, char **tmpnam_ret) { const char *input_exten; rs_trace("input file %s", orig_input); input_exten = dcc_find_extension(orig_input); if (input_exten) input_exten = dcc_preproc_exten(input_exten); if (!input_exten) /* previous line might return NULL */ input_exten = ".tmp"; return dcc_make_tmpnam("distccd", input_exten, tmpnam_ret); } /** * Find the absolute path for the first occurrence of @p compiler_name on the * PATH. Print a warning if it looks like a symlink to distcc. * * We want to guard against somebody accidentally running the server with a * masqueraded compiler on its $PATH. The worst that's likely to happen here * is wasting some time running a distcc or ccache client that does nothing, * so it's not a big deal. (This could be easy to do if it's on the default * PATH and they start the daemon from the command line.) * * At the moment we don't look for the compiler too. **/ static int dcc_check_compiler_masq(char *compiler_name) { const char *envpath, *p, *n; char *buf = NULL; struct stat sb; int len; char linkbuf[MAXPATHLEN]; if (compiler_name[0] == '/') return 0; if (!(envpath = getenv("PATH"))) { rs_trace("PATH seems not to be defined"); return 0; } for (n = p = envpath; *n; p = n) { n = strchr(p, ':'); if (n) len = n++ - p; else { len = strlen(p); n = p + len; } if (asprintf(&buf, "%.*s/%s", len, p, compiler_name) == -1) { rs_log_crit("asnprintf failed"); return EXIT_DISTCC_FAILED; } if (lstat(buf, &sb) == -1) continue; /* ENOENT, EACCESS, etc */ if (!S_ISLNK(sb.st_mode)) { rs_trace("%s is not a symlink", buf); break; /* found it */ } if ((len = readlink(buf, linkbuf, sizeof linkbuf)) <= 0) continue; linkbuf[len] = '\0'; if (strstr(linkbuf, "distcc")) { rs_log_warning("%s on distccd's path is %s and really a link to %s", compiler_name, buf, linkbuf); break; /* but use it anyhow */ } else { rs_trace("%s is a safe symlink to %s", buf, linkbuf); break; /* found it */ } } free(buf); return 0; } static int dcc_send_system_info(int out_fd) { char *info = dcc_get_system_version(); if (!info) info = ""; return dcc_x_token_string(out_fd, "SINF", info); } static int dcc_send_compiler_version(int out_fd, char *compiler) { char *info = dcc_get_compiler_version(compiler); if (!info) info = ""; return dcc_x_token_string(out_fd, "CVER", info); } /** * Send down the host info. This includes the OS version, hardware info, and compiler versions. */ int dcc_send_host_info(int out_fd) { int ret; char *sysKey = "SYSTEM="; char *compilerKey = "COMPILER="; char *ncpusKey = "CPUS="; char *cpuSpeedKey = "CPUSPEED="; char *maxJobsKey = "JOBS="; char *priorityKey = "PRIORITY="; char *distcc = "DISTCC=" PACKAGE_VERSION "\n"; char *sysInfo = dcc_get_system_version(); char **compilers = dcc_get_all_compiler_versions(); int ncpus; unsigned long long cpuSpeed; if (dcc_ncpus(&ncpus)) ncpus = 0; if (dcc_cpuspeed(&cpuSpeed)) cpuSpeed = 0; int i, len = 0; char *msg; if (sysInfo) { len += strlen(sysInfo) + strlen(sysKey) + 1; } if (distcc) len += strlen(distcc); if (compilers) { for (i=0; compilers && compilers[i] != NULL; i++) { len += strlen(compilers[i]) + strlen(compilerKey) + 1; } } if (ncpus > 0) { len += strlen(ncpusKey) + 8; // 7 digits for cpu count should be plenty } if (cpuSpeed > 0) { len += strlen(cpuSpeedKey) + 32; // 31 digits for cpu speed } if (dcc_max_kids > 0) { len += strlen(maxJobsKey) + 8; // 7 digits for job count should be plenty } if (build_machine_priority > 0) { len += strlen(maxJobsKey) + 32; // 31 digits for priority } msg = malloc(len+1); msg[0] = 0; if (sysInfo) { strcat(msg, sysKey); strcat(msg, sysInfo); strcat(msg, "\n"); } if (distcc) strcat(msg, distcc); if (compilers) { for (i=0; compilers && compilers[i]!=NULL; i++) { strcat(msg, compilerKey); strcat(msg, compilers[i]); strcat(msg, "\n"); } free(compilers); } if (ncpus > 0) { strcat(msg, ncpusKey); sprintf(&msg[strlen(msg)], "%d\n", ncpus); } if (cpuSpeed > 0) { strcat(msg, cpuSpeedKey); sprintf(&msg[strlen(msg)], "%llu\n", cpuSpeed); } if (dcc_max_kids > 0) { strcat(msg, maxJobsKey); sprintf(&msg[strlen(msg)], "%d\n", dcc_max_kids); } if (build_machine_priority > 0) { strcat(msg, priorityKey); sprintf(&msg[strlen(msg)], "%d\n", build_machine_priority); } // a bit of a hack - if we are writing to stdout then just print the string if (out_fd == 1) ret = write(out_fd, msg, len)==len; else ret = dcc_x_token_string(out_fd, "HINF", msg); free(msg); return ret; } /** * Read a request, run the compiler, and send a response. **/ static int dcc_run_job(int in_fd, int out_fd) { char **argv; int status; char *temp_i, *temp_o, *err_fname, *out_fname; int ret, compile_ret; char *orig_input, *orig_output; pid_t cc_pid; enum dcc_protover protover; enum dcc_compress compr; dcc_indirection indirect; if ((ret = dcc_make_tmpnam("distcc", ".stderr", &err_fname))) goto out_cleanup; if ((ret = dcc_make_tmpnam("distcc", ".stdout", &out_fname))) goto out_cleanup; dcc_remove_if_exists(err_fname); dcc_remove_if_exists(out_fname); /* Capture any messages relating to this compilation to the same file as * compiler errors so that they can all be sent back to the client. */ dcc_add_log_to_file(err_fname); /* Ignore SIGPIPE; we consistently check error codes and will see the * EPIPE. Note that it is set back to the default behaviour when spawning * a child, to handle cases like the assembler dying while its being fed * from the compiler */ dcc_ignore_sigpipe(1); /* Allow output to accumulate into big packets. */ tcp_cork_sock(out_fd, 1); if ((ret = dcc_r_request_header(in_fd, &protover)) || (ret = dcc_r_argv(in_fd, &argv)) || (ret = dcc_scan_args(argv, &orig_input, &orig_output, &argv))) goto out_cleanup; if (strcmp(argv[0],"--host-info") == 0) { if ((ret = dcc_x_result_header(out_fd, protover)) || (ret = dcc_send_host_info(out_fd))) dcc_x_token_int(out_fd, "DOTO", 0); } else { rs_trace("output file %s", orig_output); if ((ret = dcc_input_tmpnam(orig_input, &temp_i))) goto out_cleanup; if ((ret = dcc_make_tmpnam("distccd", ".o", &temp_o))) goto out_cleanup; compr = (protover == 2) ? DCC_COMPRESS_LZO1X : DCC_COMPRESS_NONE; if ((ret = dcc_r_token_file(in_fd, "DOTI", temp_i, compr)) || (ret = dcc_set_input(argv, temp_i)) || (ret = dcc_set_output(argv, temp_o))) goto out_cleanup; if ((ret = dcc_check_compiler_masq(argv[0]))) goto out_cleanup; indirect.in_fd = in_fd; indirect.out_fd = out_fd; if ((compile_ret = dcc_spawn_child(argv, &cc_pid, "/dev/null", out_fname, err_fname, &indirect)) || (compile_ret = dcc_collect_child("cc", cc_pid, &status))) { /* We didn't get around to finding a wait status from the actual compiler */ status = W_EXITCODE(compile_ret, 0); } if ((ret = dcc_x_result_header(out_fd, protover)) || (ret = dcc_send_system_info(out_fd)) || (ret = dcc_send_compiler_version(out_fd, argv[0])) || (ret = dcc_x_cc_status(out_fd, status)) || (ret = dcc_x_file(out_fd, err_fname, "SERR", compr, NULL)) || (ret = dcc_x_file(out_fd, out_fname, "SOUT", compr, NULL)) || WIFSIGNALED(status) || WEXITSTATUS(status)) { /* Something went wrong, so send DOTO 0 */ dcc_x_token_int(out_fd, "DOTO", 0); } else { ret = dcc_x_file(out_fd, temp_o, "DOTO", compr, NULL); } dcc_critique_status(status, argv[0], orig_input, dcc_hostdef_local, 0); } tcp_cork_sock(out_fd, 0); rs_log(RS_LOG_INFO|RS_LOG_NONAME, "job complete"); out_cleanup: dcc_remove_log_to_file(); dcc_cleanup_tempfiles(); return ret; }