#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <setjmp.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <signal.h>
#include <string.h>
#include <time.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#ifdef HAVE_SYS_SIGNAL_H
# include <sys/signal.h>
#endif
#include <sys/param.h>
#include <sys/socket.h>
#include <sys/time.h>
#include "distcc.h"
#include "trace.h"
#include "util.h"
#include "stats.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 "stringmap.h"
#include "dotd.h"
#include "fix_debug_info.h"
#ifdef XCODE_INTEGRATION
# include "xci.h"
#endif
static int dcc_compile_log_fd = -1;
static int dcc_run_job(int in_fd, int out_fd);
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;
}
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;
}
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;
}
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;
}
int dcc_service_job(int in_fd,
int out_fd,
struct sockaddr *cli_addr,
int cli_len)
{
int ret;
dcc_job_summary_clear();
if ((ret = dcc_check_client(cli_addr, cli_len, opt_allowed)) != 0)
goto out;
ret = dcc_run_job(in_fd, out_fd);
dcc_job_summary();
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)
input_exten = ".tmp";
return dcc_make_tmpnam("distccd", input_exten, tmpnam_ret);
}
static int dcc_remap_compiler(char **compiler_name)
{
static int cmdlist_checked=0;
static stringmap_t *map=0;
const char *newname;
if (!cmdlist_checked) {
char *filename;
cmdlist_checked = 1;
filename = getenv("DISTCC_CMDLIST");
if (filename) {
const char *nw = getenv("DISTCC_CMDLIST_NUMWORDS");
int numFinalWordsToMatch=1;
if (nw)
numFinalWordsToMatch = atoi(nw);
map = stringmap_load(filename, numFinalWordsToMatch);
if (map) {
rs_trace("stringmap_load(%s, %d) found %d commands", filename, numFinalWordsToMatch, map->n);
} else {
rs_log_error("stringmap_load(%s, %d) failed: %s", filename, numFinalWordsToMatch, strerror(errno));
return EXIT_IO_ERROR;
}
}
}
if (!map)
return 1;
newname = stringmap_lookup(map, *compiler_name);
if (!newname) {
rs_log_warning("lookup of %s in DISTCC_CMDLIST failed", *compiler_name);
return 0;
}
if (strcmp(newname, *compiler_name)) {
rs_trace("changed compiler from %s to %s", *compiler_name, newname);
free(*compiler_name);
*compiler_name = strdup(newname);
}
return 1;
}
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("asprintf failed");
return EXIT_DISTCC_FAILED;
}
if (lstat(buf, &sb) == -1)
continue;
if (!S_ISLNK(sb.st_mode)) {
rs_trace("%s is not a symlink", buf);
break;
}
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;
} else {
rs_trace("%s is a safe symlink to %s", buf, linkbuf);
break;
}
}
free(buf);
return 0;
}
static const char *include_options[] = {
"-I",
"-F",
"-include",
"-imacros",
"-idirafter",
"-iframework",
"-iprefix",
"-iwithprefix",
"-iwithprefixbefore",
"-isystem",
"-iquote",
NULL
};
static int tweak_input_argument_for_server(char **argv,
const char *root_dir)
{
unsigned i;
for (i=0; argv[i]; i++)
if (dcc_is_source(argv[i]) && argv[i][0]=='/') {
unsigned j = 0;
char *prefixed_name;
while (argv[i][j] == '/') j++;
if (asprintf(&prefixed_name, "%s/%s",
root_dir,
argv[i] + j) == -1) {
rs_log_crit("asprintf failed");
return EXIT_OUT_OF_MEMORY;
}
rs_trace("changed input from \"%s\" to \"%s\"", argv[i],
prefixed_name);
free(argv[i]);
argv[i] = prefixed_name;
dcc_trace_argv("command after", argv);
return 0;
}
return 0;
}
static int tweak_include_arguments_for_server(char **argv,
const char *root_dir)
{
int index_of_first_filename_char = 0;
const char *include_option;
unsigned int i, j;
for (i = 0; argv[i]; ++i) {
for (j = 0; include_options[j]; ++j) {
if (str_startswith(include_options[j], argv[i])) {
if (strcmp(argv[i], include_options[j]) == 0) {
++i;
include_option = "";
index_of_first_filename_char = 0;
} else {
include_option = include_options[j];
index_of_first_filename_char = strlen(include_option);
}
if (argv[i] != NULL) {
if (argv[i][index_of_first_filename_char] == '/') {
char *buf;
asprintf(&buf, "%s%s%s",
include_option,
root_dir,
argv[i] + index_of_first_filename_char);
if (buf == NULL) {
return EXIT_OUT_OF_MEMORY;
}
free(argv[i]);
argv[i] = buf;
}
}
break;
}
}
}
return 0;
}
static int dcc_convert_mt_to_dotd_target(char **argv, char **dotd_target)
{
int i;
*dotd_target = NULL;
for (i = 0; argv[i]; ++i) {
if (strcmp(argv[i], "-MT") == 0) {
break;
}
}
if (argv[i] == NULL)
return 0;
if (argv[i+1] == NULL) {
rs_trace("found -MT at the end of the command line");
return 1;
}
*dotd_target = argv[i+1];
for (; argv[i+2]; ++i) {
argv[i] = argv[i+2];
}
argv[i] = argv[i+2];
return 0;
}
static int tweak_arguments_for_server(char **argv,
const char *root_dir,
const char *deps_fname,
char **dotd_target,
char ***tweaked_argv,
int *send_back_dotd)
{
int ret, parsed_needs_dotd = 0, parsed_sets_dotd_target = 0;
char *parsed_deps_fname = NULL, *parsed_dotd_target = NULL;
*dotd_target = 0;
if ((ret = dcc_copy_argv(argv, tweaked_argv, 3)))
return 1;
dcc_get_dotd_info(argv, &parsed_deps_fname, &parsed_needs_dotd,
&parsed_sets_dotd_target, &parsed_dotd_target);
if (parsed_deps_fname)
free(parsed_deps_fname);
if (parsed_needs_dotd) {
if ((ret = dcc_convert_mt_to_dotd_target(*tweaked_argv, dotd_target)))
return 1;
dcc_argv_append(*tweaked_argv, strdup("-MMD"));
dcc_argv_append(*tweaked_argv, strdup("-MF"));
dcc_argv_append(*tweaked_argv, strdup(deps_fname));
}
*send_back_dotd = parsed_needs_dotd;
tweak_include_arguments_for_server(*tweaked_argv, root_dir);
tweak_input_argument_for_server(*tweaked_argv, root_dir);
return 0;
}
static int make_temp_dir_and_chdir_for_cpp(int in_fd,
char **temp_dir, char **client_side_cwd, char **server_side_cwd)
{
int ret = 0;
if ((ret = dcc_get_new_tmpdir(temp_dir)))
return ret;
if ((ret = dcc_r_cwd(in_fd, client_side_cwd)))
return ret;
asprintf(server_side_cwd, "%s%s", *temp_dir, *client_side_cwd);
if (*server_side_cwd == NULL) {
ret = EXIT_OUT_OF_MEMORY;
} else if ((ret = dcc_mk_tmp_ancestor_dirs(*server_side_cwd))) {
;
} else if ((ret = dcc_mk_tmpdir(*server_side_cwd))) {
;
} else if (chdir(*server_side_cwd) == -1) {
ret = EXIT_IO_ERROR;
}
return ret;
}
static int dcc_run_job(int in_fd,
int out_fd)
{
char **argv = NULL;
char **tweaked_argv = NULL;
int status = 0;
char *temp_i = NULL, *temp_o = NULL;
char *err_fname = NULL, *out_fname = NULL, *deps_fname = NULL;
char *temp_dir = NULL;
int ret = 0, compile_ret = 0;
char *orig_input = NULL, *orig_output = NULL;
char *orig_input_tmp, *orig_output_tmp;
char *dotd_target = NULL;
pid_t cc_pid;
enum dcc_protover protover;
enum dcc_compress compr;
struct timeval start, end;
int time_ms;
char *time_str;
int job_result = -1;
enum dcc_cpp_where cpp_where;
char *server_cwd = NULL;
char *client_cwd = NULL;
int send_back_dotd = 0;
#ifdef XCODE_INTEGRATION
const char *host_info;
#endif
gettimeofday(&start, NULL);
if ((ret = dcc_make_tmpnam("distcc", ".deps", &deps_fname)))
goto out_cleanup;
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(deps_fname);
dcc_remove_if_exists(err_fname);
dcc_remove_if_exists(out_fname);
dcc_add_log_to_file(err_fname);
dcc_ignore_sigpipe(1);
tcp_cork_sock(out_fd, 1);
if ((ret = dcc_r_request_header(in_fd, &protover)))
goto out_cleanup;
dcc_get_features_from_protover(protover, &compr, &cpp_where);
if (cpp_where == DCC_CPP_ON_SERVER)
if ((ret = make_temp_dir_and_chdir_for_cpp(in_fd,
&temp_dir, &client_cwd, &server_cwd)))
goto out_cleanup;
if ((ret = dcc_r_argv(in_fd, &argv))
|| (ret = dcc_xci_unmask_developer_dir_in_argv(argv))
|| (ret = dcc_scan_args(argv, &orig_input_tmp, &orig_output_tmp,
&tweaked_argv, &dcc_optx_ext)))
goto out_cleanup;
#ifdef XCODE_INTEGRATION
if (!strcmp(argv[0], "--host-info") && !argv[1]) {
host_info = dcc_xci_host_info_string();
ret = !host_info ||
dcc_x_result_header(out_fd, protover) ||
dcc_x_token_string(out_fd, "HINF", host_info);
tcp_cork_sock(out_fd, 0);
goto out_cleanup;
}
#endif
orig_input = strdup(orig_input_tmp);
orig_output = strdup(orig_output_tmp);
if (orig_input == NULL || orig_output == NULL) {
ret = EXIT_OUT_OF_MEMORY;
goto out_cleanup;
}
dcc_free_argv(argv);
argv = tweaked_argv;
tweaked_argv = NULL;
rs_trace("output file %s", orig_output);
if ((ret = dcc_make_tmpnam("distccd", ".o", &temp_o)))
goto out_cleanup;
if (cpp_where == DCC_CPP_ON_SERVER) {
if (dcc_r_many_files(in_fd, temp_dir, compr)
|| dcc_set_output(argv, temp_o)
|| tweak_arguments_for_server(argv, temp_dir, deps_fname,
&dotd_target, &tweaked_argv,
&send_back_dotd))
goto out_cleanup;
dcc_free_argv(argv);
argv = tweaked_argv;
tweaked_argv = NULL;
} else {
if ((ret = dcc_input_tmpnam(orig_input, &temp_i)))
goto out_cleanup;
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 (!dcc_remap_compiler(&argv[0]))
goto out_cleanup;
if ((ret = dcc_check_compiler_masq(argv[0])))
goto out_cleanup;
if ((compile_ret = dcc_spawn_child(argv, &cc_pid,
"/dev/null", out_fname, err_fname))
|| (compile_ret = dcc_collect_child("cc", cc_pid, &status, in_fd))) {
status = W_EXITCODE(compile_ret, 0);
}
if ((ret = dcc_x_result_header(out_fd, protover))
|| (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)) {
dcc_x_token_int(out_fd, "DOTO", 0);
if (job_result == -1)
job_result = STATS_COMPILE_ERROR;
} else {
if (cpp_where == DCC_CPP_ON_SERVER) {
rs_trace("fixing up debug info");
if ((ret = dcc_fix_debug_info(temp_o, "/", temp_dir)))
goto out_cleanup;
}
if ((ret = dcc_x_file(out_fd, temp_o, "DOTO", compr, NULL)))
goto out_cleanup;
if (cpp_where == DCC_CPP_ON_SERVER) {
if (send_back_dotd) {
char *cleaned_dotd;
ret = dcc_cleanup_dotd(deps_fname,
&cleaned_dotd,
temp_dir,
dotd_target ? dotd_target : orig_output,
temp_o);
if (ret) goto out_cleanup;
ret = dcc_x_file(out_fd, cleaned_dotd, "DOTD", compr, NULL);
free(cleaned_dotd);
} else {
ret = dcc_x_token_int(out_fd, "DOTD", 0);
}
}
job_result = STATS_COMPILE_OK;
}
if (compile_ret == EXIT_IO_ERROR) {
job_result = STATS_CLI_DISCONN;
} else if (compile_ret == EXIT_TIMEOUT) {
job_result = STATS_COMPILE_TIMEOUT;
}
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:
switch (ret) {
case EXIT_BUSY:
job_result = STATS_REJ_OVERLOAD;
break;
case EXIT_IO_ERROR:
job_result = STATS_CLI_DISCONN;
break;
case EXIT_PROTOCOL_ERROR:
job_result = STATS_REJ_BAD_REQ;
break;
default:
if (job_result != STATS_COMPILE_ERROR
&& job_result != STATS_COMPILE_OK
&& job_result != STATS_CLI_DISCONN
&& job_result != STATS_COMPILE_TIMEOUT) {
job_result = STATS_OTHER;
}
}
gettimeofday(&end, NULL);
time_ms = (end.tv_sec - start.tv_sec) * 1000 + (end.tv_usec - start.tv_usec) / 1000;
dcc_job_summary_append(" ");
dcc_job_summary_append(stats_text[job_result]);
if (job_result == STATS_COMPILE_OK) {
dcc_stats_compile_ok(argv[0], orig_input, time_ms);
} else {
dcc_stats_event(job_result);
}
asprintf(&time_str, " exit:%d sig:%d core:%d ret:%d time:%dms ", WEXITSTATUS(status), WTERMSIG(status), WCOREDUMP(status), ret, time_ms);
dcc_job_summary_append(time_str);
free(time_str);
if (job_result == STATS_COMPILE_ERROR
|| job_result == STATS_COMPILE_OK) {
dcc_job_summary_append(argv[0]);
dcc_job_summary_append(" ");
dcc_job_summary_append(orig_input);
}
dcc_remove_log_to_file();
dcc_cleanup_tempfiles();
free(orig_input);
free(orig_output);
if (argv)
dcc_free_argv(argv);
if (tweaked_argv)
dcc_free_argv(tweaked_argv);
dcc_optx_ext = NULL;
free(temp_dir);
free(temp_i);
free(temp_o);
free(deps_fname);
free(err_fname);
free(out_fname);
free(client_cwd);
free(server_cwd);
return ret;
}