#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/stat.h>
#include <ctype.h>
#ifdef HAVE_FNMATCH_H
#include <fnmatch.h>
#endif
#include <sys/time.h>
#include <time.h>
#include <limits.h>
#include <assert.h>
#include "distcc.h"
#include "trace.h"
#include "exitcode.h"
#include "util.h"
#include "hosts.h"
#include "bulk.h"
#include "implicit.h"
#include "exec.h"
#include "where.h"
#include "lock.h"
#include "timeval.h"
#include "compile.h"
#include "include_server_if.h"
#include "emaillog.h"
#include "dotd.h"
int dcc_scan_includes = 0;
static const int max_discrepancies_before_demotion = 1;
static const char *const include_server_port_suffix = "/socket";
static const char *const discrepancy_suffix = "/discrepancy_counter";
int dcc_discrepancy_filename(char **filename)
{
const char *include_server_port = getenv("INCLUDE_SERVER_PORT");
*filename = NULL;
if (include_server_port == NULL) {
return 0;
} else if (str_endswith(include_server_port_suffix,
include_server_port)) {
int delta = strlen(discrepancy_suffix) -
strlen(include_server_port_suffix);
assert (delta > 0);
*filename = malloc(strlen(include_server_port) + 1 + delta);
if (!*filename) {
rs_log_error("failed to allocate space for filename");
return EXIT_OUT_OF_MEMORY;
}
strcpy(*filename, include_server_port);
int slash_pos = strlen(include_server_port)
- strlen(include_server_port_suffix);
assert((*filename)[slash_pos] == '/');
(void) strcpy(*filename + slash_pos, discrepancy_suffix);
return 0;
} else
return 0;
}
static int dcc_read_number_discrepancies(const char *discrepancy_filename)
{
if (!discrepancy_filename) return 0;
struct stat stat_record;
if (stat(discrepancy_filename, &stat_record) == 0) {
size_t size = stat_record.st_size;
if ((((size_t) (int) size) == size) &&
((int) size) > 0)
return ((int) size);
else
return INT_MAX;
} else
return 0;
}
static int dcc_note_discrepancy(const char *discrepancy_filename)
{
FILE *discrepancy_file;
if (!discrepancy_filename) return 0;
if (!(discrepancy_file = fopen(discrepancy_filename, "a"))) {
rs_log_error("failed to open discrepancy_filename file: %s: %s",
discrepancy_filename,
strerror(errno));
return EXIT_IO_ERROR;
}
if (fputc('@', discrepancy_file) == EOF) {
rs_log_error("failed to write to discrepancy_filename file: %s",
discrepancy_filename);
fclose(discrepancy_file);
return EXIT_IO_ERROR;
}
if (ftell(discrepancy_file) ==
(long int)max_discrepancies_before_demotion) {
rs_log_warning("now using plain distcc, possibly due to "
"inconsistent file system changes during build");
}
fclose(discrepancy_file);
return 0;
}
static void dcc_perhaps_adjust_cpp_where_and_protover(
char *input_fname,
struct dcc_hostdef *host,
char *discrepancy_filename)
{
if (dcc_read_number_discrepancies(discrepancy_filename) >=
max_discrepancies_before_demotion) {
host->cpp_where = DCC_CPP_ON_CLIENT;
dcc_get_protover_from_features(host->compr,
host->cpp_where,
&host->protover);
}
if (dcc_is_preprocessed(input_fname)) {
rs_log_warning("cannot use distcc_pump on already preprocessed file"
" (such as emitted by ccache)");
host->cpp_where = DCC_CPP_ON_CLIENT;
dcc_get_protover_from_features(host->compr,
host->cpp_where,
&host->protover);
}
if (getenv("CPATH") || getenv("C_INCLUDE_PATH")
|| getenv("CPLUS_INCLUDE_PATH")) {
rs_log_warning("cannot use distcc_pump with any of environment"
" variables CPATH, C_INCLUDE_PATH or CPLUS_INCLUDE_PATH"
" set, preprocessing locally");
host->cpp_where = DCC_CPP_ON_CLIENT;
dcc_get_protover_from_features(host->compr,
host->cpp_where,
&host->protover);
}
}
int dcc_fresh_dependency_exists(const char *dotd_fname,
const char *exclude_pattern,
time_t reference_time,
char **result)
{
struct stat stat_dotd;
off_t dotd_fname_size = 0;
FILE *fp;
int c;
int res;
char *dep_name;
*result = NULL;
res = stat(dotd_fname, &stat_dotd);
if (res) {
rs_trace("could not stat \"%s\": %s", dotd_fname, strerror(errno));
return 0;
}
if (stat_dotd.st_mtime < reference_time) {
rs_trace("old dotd file \"%s\"", dotd_fname);
return 0;
}
dotd_fname_size = stat_dotd.st_size;
if ((off_t) (size_t) dotd_fname_size == dotd_fname_size) {
dep_name = malloc((size_t) dotd_fname_size);
if (!dep_name) {
rs_log_error("failed to allocate space for dotd file");
return EXIT_OUT_OF_MEMORY;
}
} else {
rs_trace("file \"%s\" is too big", dotd_fname);
return 0;
}
if ((fp = fopen(dotd_fname, "r")) == NULL) {
rs_trace("could not open \"%s\": %s", dotd_fname, strerror(errno));
free(dep_name);
return 0;
}
while ((c = getc(fp)) != EOF && c != ':');
if (c != ':') goto return_0;
while (c != EOF) {
struct stat stat_dep;
int i = 0;
while ((c = getc(fp)) != EOF && (isspace(c) || c == '\\'));
ungetc(c, fp);
while ((c = getc(fp)) != EOF &&
(!isspace(c) || c == '\\')) {
if (i >= dotd_fname_size) {
rs_log_error("not enough room for dependency name");
goto return_0;
}
if (c == '\\') {
if ((c = getc(fp)) != EOF)
if (c != '\n') ungetc(c, fp);
}
else dep_name[i++] = c;
}
if (i != 0) {
dep_name[i] = '\0';
#ifdef HAVE_FNMATCH_H
if (exclude_pattern == NULL ||
fnmatch(exclude_pattern, dep_name, 0) == FNM_NOMATCH) {
#else
if (exclude_pattern == exclude_pattern) {
#endif
rs_log_info("Checking dependency: %s", dep_name);
res = stat(dep_name, &stat_dep);
if (res) goto return_0;
if (stat_dep.st_ctime >= reference_time) {
fclose(fp);
*result = realloc(dep_name, strlen(dep_name) + 1);
if (*result == NULL) {
rs_log_error("realloc failed");
return EXIT_OUT_OF_MEMORY;
}
return 0;
}
}
}
}
return_0:
fclose(fp);
free(dep_name);
return 0;
}
static int dcc_compile_local(char *argv[],
char *input_name)
{
pid_t pid;
int ret;
int status;
dcc_note_execution(dcc_hostdef_local, argv);
dcc_note_state(DCC_PHASE_COMPILE, input_name, "localhost");
if ((ret = dcc_spawn_child(argv, &pid, NULL, NULL, NULL)) != 0)
return ret;
if ((ret = dcc_collect_child("cc", pid, &status, timeout_null_fd)))
return ret;
return dcc_critique_status(status, "compile", input_name,
dcc_hostdef_local, 1);
}
static int dcc_please_send_email_after_investigation(
const char *input_fname,
const char *deps_fname,
const char *discrepancy_filename) {
int ret;
char *fresh_dependency;
const char *include_server_port = getenv("INCLUDE_SERVER_PORT");
struct stat stat_port;
rs_log_warning("remote compilation of '%s' failed, retried locally "
"and got a different result.", input_fname);
if ((include_server_port != NULL) &&
(stat(include_server_port, &stat_port)) == 0) {
time_t build_start = stat_port.st_ctime;
if (deps_fname) {
const char *exclude_pattern =
getenv("DISTCC_EXCLUDE_FRESH_FILES");
if ((ret = dcc_fresh_dependency_exists(deps_fname,
exclude_pattern,
build_start,
&fresh_dependency))) {
return ret;
}
if (fresh_dependency) {
rs_log_warning("file '%s', a dependency of %s, "
"changed during the build", fresh_dependency,
input_fname);
free(fresh_dependency);
return dcc_note_discrepancy(discrepancy_filename);
}
}
}
dcc_please_send_email();
return dcc_note_discrepancy(discrepancy_filename);
}
static int
dcc_build_somewhere(char *argv[],
int sg_level,
int *status)
{
char *input_fname = NULL, *output_fname, *cpp_fname, *deps_fname = NULL;
char **files;
char **server_side_argv = NULL;
int server_side_argv_deep_copied = 0;
char *server_stderr_fname = NULL;
int needs_dotd = 0;
int sets_dotd_target = 0;
pid_t cpp_pid = 0;
int cpu_lock_fd = -1, local_cpu_lock_fd = -1;
int ret;
int remote_ret = 0;
struct dcc_hostdef *host = NULL;
char *discrepancy_filename = NULL;
char **new_argv;
if ((ret = dcc_expand_preprocessor_options(&argv)) != 0)
goto clean_up;
if ((ret = dcc_discrepancy_filename(&discrepancy_filename)))
goto clean_up;
if (sg_level)
goto run_local;
ret = dcc_scan_args(argv, &input_fname, &output_fname, &new_argv,
&dcc_optx_ext);
dcc_free_argv(argv);
argv = new_argv;
if (ret != 0) {
goto lock_local;
}
#if 0
dcc_note_state(DCC_PHASE_STARTUP, input_fname, NULL);
#endif
if ((ret = dcc_make_tmpnam("distcc_server_stderr", ".txt",
&server_stderr_fname))) {
goto fallback;
}
if ((ret = dcc_pick_host_from_list_and_lock_it(&host, &cpu_lock_fd)) != 0) {
goto fallback;
}
if (host->mode == DCC_MODE_LOCAL) {
goto run_local;
}
if ((ret = dcc_lock_local_cpp(&local_cpu_lock_fd)) != 0) {
goto fallback;
}
if (host->cpp_where == DCC_CPP_ON_SERVER) {
dcc_perhaps_adjust_cpp_where_and_protover(input_fname, host,
discrepancy_filename);
}
if (dcc_scan_includes) {
ret = dcc_approximate_includes(host, argv);
goto unlock_and_clean_up;
}
if (host->cpp_where == DCC_CPP_ON_SERVER) {
if ((ret = dcc_talk_to_include_server(argv, &files))) {
rs_log_warning("failed to get includes from include server, "
"preprocessing locally");
if (dcc_getenv_bool("DISTCC_TESTING_INCLUDE_SERVER", 0))
dcc_exit(ret);
host->cpp_where = DCC_CPP_ON_CLIENT;
dcc_get_protover_from_features(host->compr,
host->cpp_where,
&host->protover);
} else {
dcc_unlock(local_cpu_lock_fd);
local_cpu_lock_fd = -1;
}
}
if (host->cpp_where == DCC_CPP_ON_CLIENT) {
files = NULL;
if ((ret = dcc_cpp_maybe(argv, input_fname, &cpp_fname, &cpp_pid) != 0))
goto fallback;
if ((ret = dcc_strip_local_args(argv, &server_side_argv)))
goto fallback;
} else {
char *dotd_target = NULL;
cpp_fname = NULL;
cpp_pid = 0;
dcc_get_dotd_info(argv, &deps_fname, &needs_dotd,
&sets_dotd_target, &dotd_target);
server_side_argv_deep_copied = 1;
if ((ret = dcc_copy_argv(argv, &server_side_argv, 2)))
goto fallback;
if (needs_dotd && !sets_dotd_target) {
dcc_argv_append(server_side_argv, strdup("-MT"));
if (dotd_target == NULL)
dcc_argv_append(server_side_argv, strdup(output_fname));
else
dcc_argv_append(server_side_argv, strdup(dotd_target));
}
}
if ((ret = dcc_compile_remote(server_side_argv,
input_fname,
cpp_fname,
files,
output_fname,
needs_dotd ? deps_fname : NULL,
server_stderr_fname,
cpp_pid, local_cpu_lock_fd,
host, status)) != 0) {
local_cpu_lock_fd = -1;
goto fallback;
}
local_cpu_lock_fd = -1;
dcc_enjoyed_host(host);
dcc_unlock(cpu_lock_fd);
cpu_lock_fd = -1;
ret = dcc_critique_status(*status, "compile", input_fname, host, 1);
if (ret == 0) {
if ((dcc_copy_file_to_fd(server_stderr_fname, STDERR_FILENO))) {
rs_log_warning("Could not show server-side errors");
goto fallback;
}
goto clean_up;
}
if (ret < 128) {
rs_log_warning("remote compilation of '%s' failed, retrying locally",
input_fname);
remote_ret = ret;
goto fallback;
}
fallback:
if (host)
dcc_disliked_host(host);
if (cpu_lock_fd != -1) {
dcc_unlock(cpu_lock_fd);
cpu_lock_fd = -1;
}
if (local_cpu_lock_fd != -1) {
dcc_unlock(local_cpu_lock_fd);
local_cpu_lock_fd = -1;
}
if (!dcc_getenv_bool("DISTCC_FALLBACK", 1)) {
rs_log_warning("failed to distribute and fallbacks are disabled");
if ((dcc_copy_file_to_fd(server_stderr_fname, STDERR_FILENO))) {
rs_log_error("Could not print error messages from '%s'",
server_stderr_fname);
}
goto clean_up;
}
if (host) {
rs_log(RS_LOG_WARNING|RS_LOG_NONAME,
"failed to distribute %s to %s, running locally instead",
input_fname ? input_fname : "(unknown)",
host->hostdef_string);
} else {
rs_log_warning("failed to distribute, running locally instead");
}
lock_local:
dcc_lock_local(&cpu_lock_fd);
run_local:
ret = dcc_compile_local(argv, input_fname);
if (remote_ret != 0 && remote_ret != ret) {
(void) dcc_please_send_email_after_investigation(
input_fname,
deps_fname,
discrepancy_filename);
}
unlock_and_clean_up:
if (cpu_lock_fd != -1) {
dcc_unlock(cpu_lock_fd);
cpu_lock_fd = -1;
}
if (local_cpu_lock_fd != -1) {
dcc_unlock(local_cpu_lock_fd);
local_cpu_lock_fd = -1;
}
clean_up:
dcc_free_argv(argv);
if (server_side_argv_deep_copied) {
if (server_side_argv != NULL) {
dcc_free_argv(server_side_argv);
}
} else {
free(server_side_argv);
}
free(discrepancy_filename);
dcc_optx_ext = NULL;
return ret;
}
int dcc_build_somewhere_timed(char *argv[],
int sg_level,
int *status)
{
struct timeval before, after, delta;
int ret;
if (gettimeofday(&before, NULL))
rs_log_warning("gettimeofday failed");
ret = dcc_build_somewhere(argv, sg_level, status);
if (gettimeofday(&after, NULL)) {
rs_log_warning("gettimeofday failed");
} else {
timeval_subtract(&delta, &after, &before);
rs_log(RS_LOG_INFO|RS_LOG_NONAME,
"elapsed compilation time %ld.%06lds",
delta.tv_sec, (long) delta.tv_usec);
}
return ret;
}