/** * Invoke a compiler locally. This is, obviously, the alternative to * dcc_compile_remote(). * * The server does basically the same thing, but it doesn't call this * routine because it wants to overlap execution of the compiler with * copying the input from the network. * * This routine used to exec() the compiler in place of distcc. That * is slightly more efficient, because it avoids the need to create, * schedule, etc another process. The problem is that in that case we * can't clean up our temporary files, and (not so important) we can't * log our resource usage. * **/ int build_local(CompileJob &job, MsgChannel *local_daemon, struct rusage *used) { list<string> arguments; string compiler_name = find_compiler(job); trace() << "invoking: " << compiler_name << endl; if (compiler_name.empty()) { log_error() << "could not find " << job.compilerName() << " in PATH." << endl; return EXIT_NO_SUCH_FILE; } arguments.push_back(compiler_name); appendList(arguments, job.allFlags()); if (!job.inputFile().empty()) { arguments.push_back(job.inputFile()); } if (!job.outputFile().empty()) { arguments.push_back("-o"); arguments.push_back(job.outputFile()); } char **argv = new char*[arguments.size() + 1]; int argc = 0; for (list<string>::const_iterator it = arguments.begin(); it != arguments.end(); ++it) { argv[argc++] = strdup(it->c_str()); } argv[argc] = 0; #if CLIENT_DEBUG trace() << "execing "; for (int i = 0; argv[i]; i++) { trace() << argv[i] << " "; } trace() << endl; #endif if (!local_daemon) { int fd; if (!dcc_lock_host(fd)) { log_error() << "can't lock for local job" << endl; return EXIT_DISTCC_FAILED; } lock_fd = fd; } bool color_output = job.language() != CompileJob::Lang_Custom && colorify_wanted(job); int pf[2]; if (color_output && pipe(pf)) { color_output = false; } if (used || color_output) { flush_debug(); child_pid = fork(); } if (!child_pid) { dcc_increment_safeguard(); if (color_output) { close(pf[0]); close(2); dup2(pf[1], 2); } int ret = execv(argv[0], argv); if (lock_fd) { dcc_unlock(lock_fd); } if (ret) { char buf[256]; snprintf(buf, sizeof(buf), "ICECC[%d]: %s:", getpid(), argv[0]); log_perror(buf); } _exit(ret); } if (color_output) { close(pf[1]); } // setup interrupt signals, so that the JobLocalBeginMsg will // have a matching JobLocalDoneMsg void (*old_sigint)(int) = signal(SIGINT, handle_user_break); void (*old_sigterm)(int) = signal(SIGTERM, handle_user_break); void (*old_sigquit)(int) = signal(SIGQUIT, handle_user_break); void (*old_sighup)(int) = signal(SIGHUP, handle_user_break); if (color_output) { string s_ccout; char buf[250]; int r; for (;;) { while ((r = read(pf[0], buf, sizeof(buf) - 1)) > 0) { buf[r] = '\0'; s_ccout.append(buf); } if (r == 0) { break; } if (r < 0 && errno != EINTR) { break; } } colorify_output(s_ccout); } int status = 1; while (wait4(child_pid, &status, 0, used) < 0 && errno == EINTR) {} status = shell_exit_status(status); signal(SIGINT, old_sigint); signal(SIGTERM, old_sigterm); signal(SIGQUIT, old_sigquit); signal(SIGHUP, old_sighup); if (user_break_signal) { raise(user_break_signal); } if (lock_fd) { dcc_unlock(lock_fd); } return status; }
int build_remote(CompileJob &job, MsgChannel *local_daemon, const Environments &_envs, int permill ) { srand( time( 0 ) + getpid() ); int torepeat = 1; // older compilers do not support the options we need to make it reproducible #if defined(__GNUC__) && ( ( (__GNUC__ == 3) && (__GNUC_MINOR__ >= 3) ) || (__GNUC__ >=4) ) if (!compiler_is_clang(job)) { if ( rand() % 1000 < permill) torepeat = 3; } #endif trace() << job.inputFile() << " compiled " << torepeat << " times on " << job.targetPlatform() << "\n"; map<string, string> versionfile_map, version_map; Environments envs = rip_out_paths( _envs, version_map, versionfile_map ); if (!envs.size()) { log_error() << "$ICECC_VERSION needs to point to .tar files\n"; throw(22); } const char *preferred_host = getenv("ICECC_PREFERRED_HOST"); if ( torepeat == 1 ) { string fake_filename; list<string> args = job.remoteFlags(); for ( list<string>::const_iterator it = args.begin(); it != args.end(); ++it ) fake_filename += "/" + *it; args = job.restFlags(); for ( list<string>::const_iterator it = args.begin(); it != args.end(); ++it ) fake_filename += "/" + *it; fake_filename += get_absfilename( job.inputFile() ); GetCSMsg getcs (envs, fake_filename, job.language(), torepeat, job.targetPlatform(), job.argumentFlags(), preferred_host ? preferred_host : string(), ignore_unverified()); if (!local_daemon->send_msg (getcs)) { log_warning() << "asked for CS\n"; throw( 24 ); } UseCSMsg *usecs = get_server( local_daemon ); int ret; if (!maybe_build_local (local_daemon, usecs, job, ret)) ret = build_remote_int( job, usecs, local_daemon, version_map[usecs->host_platform], versionfile_map[usecs->host_platform], 0, true ); delete usecs; return ret; } else { char *preproc = 0; dcc_make_tmpnam( "icecc", ".ix", &preproc, 0 ); const CharBufferDeleter preproc_holder(preproc); int cpp_fd = open(preproc, O_WRONLY ); /* When call_cpp returns normally (for the parent) it will have closed the write fd, i.e. cpp_fd. */ pid_t cpp_pid = call_cpp(job, cpp_fd ); if ( cpp_pid == -1 ) { ::unlink( preproc ); throw( 10 ); } int status = 255; waitpid( cpp_pid, &status, 0); if ( shell_exit_status(status) ) { // failure ::unlink( preproc ); return shell_exit_status( status ); } char rand_seed[400]; // "designed to be oversized" (Levi's) sprintf( rand_seed, "-frandom-seed=%d", rand() ); job.appendFlag( rand_seed, Arg_Remote ); GetCSMsg getcs (envs, get_absfilename( job.inputFile() ), job.language(), torepeat, job.targetPlatform(), job.argumentFlags(), preferred_host ? preferred_host : string(), ignore_unverified()); if (!local_daemon->send_msg (getcs)) { log_warning() << "asked for CS\n"; throw( 0 ); } map<pid_t, int> jobmap; CompileJob *jobs = new CompileJob[torepeat]; UseCSMsg **umsgs = new UseCSMsg*[torepeat]; bool misc_error = false; int *exit_codes = new int[torepeat]; for ( int i = 0; i < torepeat; i++ ) // init exit_codes[i] = 42; for ( int i = 0; i < torepeat; i++ ) { jobs[i] = job; char *buffer = 0; if ( i ) { dcc_make_tmpnam( "icecc", ".o", &buffer, 0 ); jobs[i].setOutputFile( buffer ); } else buffer = strdup( job.outputFile().c_str() ); const CharBufferDeleter buffer_holder( buffer ); umsgs[i] = get_server( local_daemon ); remote_daemon = umsgs[i]->hostname; trace() << "got_server_for_job " << umsgs[i]->hostname << endl; flush_debug(); pid_t pid = fork(); if ( !pid ) { int ret = 42; try { if (!maybe_build_local (local_daemon, umsgs[i], jobs[i], ret)) ret = build_remote_int( jobs[i], umsgs[i], local_daemon, version_map[umsgs[i]->host_platform], versionfile_map[umsgs[i]->host_platform], preproc, i == 0 ); } catch ( int error ) { log_info() << "build_remote_int failed and has thrown " << error << endl; kill( getpid(), SIGTERM ); return 0; // shouldn't matter } _exit( ret ); return 0; // doesn't matter } else { jobmap[pid] = i; } } for ( int i = 0; i < torepeat; i++ ) { pid_t pid = wait( &status ); if ( pid < 0 ) { log_perror( "wait failed" ); status = -1; } else { if ( WIFSIGNALED( status ) ) { // there was some misc error in processing misc_error = true; break; } exit_codes[jobmap[pid]] = shell_exit_status( status ); } } if (! misc_error ) { string first_md5 = md5_for_file( jobs[0].outputFile() ); for ( int i = 1; i < torepeat; i++ ) { if ( !exit_codes[0] ) { // if the first failed, we fail anyway if ( exit_codes[i] == 42 ) // they are free to fail for misc reasons continue; if ( exit_codes[i] ) { log_error() << umsgs[i]->hostname << " compiled with exit code " << exit_codes[i] << " and " << umsgs[0]->hostname << " compiled with exit code " << exit_codes[0] << " - aborting!\n"; ::unlink( jobs[0].outputFile().c_str()); exit_codes[0] = -1; // overwrite break; } string other_md5 = md5_for_file( jobs[i].outputFile() ); if ( other_md5 != first_md5 ) { log_error() << umsgs[i]->hostname << " compiled " << jobs[0].outputFile() << " with md5 sum " << other_md5 << "(" << jobs[i].outputFile() << ")" << " and " << umsgs[0]->hostname << " compiled with md5 sum " << first_md5 << " - aborting!\n"; rename( jobs[0].outputFile().c_str(), ( jobs[0].outputFile() + ".caught" ).c_str() ); rename( preproc, ( string( preproc ) + ".caught" ).c_str() ); exit_codes[0] = -1; // overwrite break; } } ::unlink( jobs[i].outputFile().c_str() ); delete umsgs[i]; } } else { ::unlink( jobs[0].outputFile().c_str() ); for ( int i = 1; i < torepeat; i++ ) { ::unlink( jobs[i].outputFile().c_str()); delete umsgs[i]; } } delete umsgs[0]; ::unlink( preproc ); int ret = exit_codes[0]; delete [] umsgs; delete [] jobs; delete [] exit_codes; if ( misc_error ) throw ( 27 ); return ret; } return 0; }
/** * Invoke a compiler locally. This is, obviously, the alternative to * dcc_compile_remote(). * * The server does basically the same thing, but it doesn't call this * routine because it wants to overlap execution of the compiler with * copying the input from the network. * * This routine used to exec() the compiler in place of distcc. That * is slightly more efficient, because it avoids the need to create, * schedule, etc another process. The problem is that in that case we * can't clean up our temporary files, and (not so important) we can't * log our resource usage. * **/ int build_local(CompileJob &job, MsgChannel *local_daemon, struct rusage *used) { list<string> arguments; string compiler_name = find_compiler(job); if (compiler_name.empty()) { log_error() << "could not find " << job.compilerName() << " in PATH." << endl; return EXIT_NO_SUCH_FILE; } arguments.push_back(compiler_name); appendList(arguments, job.allFlags()); if (job.dwarfFissionEnabled()) { arguments.push_back("-gsplit-dwarf"); } if (!job.inputFile().empty()) { arguments.push_back(job.inputFile()); } if (!job.outputFile().empty()) { arguments.push_back("-o"); arguments.push_back(job.outputFile()); } vector<char*> argv; string argstxt; for (list<string>::const_iterator it = arguments.begin(); it != arguments.end(); ++it) { argv.push_back(strdup(it->c_str())); argstxt += ' '; argstxt += *it; } argv.push_back(0); trace() << "invoking:" << argstxt << endl; if (!local_daemon) { int fd; if (!dcc_lock_host(fd)) { log_error() << "can't lock for local job" << endl; return EXIT_DISTCC_FAILED; } lock_fd = fd; } bool color_output = job.language() != CompileJob::Lang_Custom && colorify_wanted(job); int pf[2]; if (color_output && pipe(pf)) { color_output = false; } if (used || color_output) { flush_debug(); child_pid = fork(); } if (child_pid == -1){ log_perror("fork failed"); } if (!child_pid) { dcc_increment_safeguard(job.language() == CompileJob::Lang_Custom ? SafeguardStepCustom : SafeguardStepCompiler); if (color_output) { if ((-1 == close(pf[0])) && (errno != EBADF)){ log_perror("close failed"); } if ((-1 == close(2)) && (errno != EBADF)){ log_perror("close failed"); } if (-1 == dup2(pf[1], 2)){ log_perror("dup2 failed"); } } execv(argv[0], &argv[0]); int exitcode = ( errno == ENOENT ? 127 : 126 ); log_perror("execv failed"); if (lock_fd) { dcc_unlock(lock_fd); } { char buf[256]; snprintf(buf, sizeof(buf), "ICECC[%d]: %s:", getpid(), argv[0]); log_perror(buf); } _exit(exitcode); } for(vector<char*>::const_iterator i = argv.begin(); i != argv.end(); ++i){ free(*i); } argv.clear(); if (color_output) { if ((-1 == close(pf[1])) && (errno != EBADF)){ log_perror("close failed"); } } // setup interrupt signals, so that the JobLocalBeginMsg will // have a matching JobLocalDoneMsg void (*old_sigint)(int) = signal(SIGINT, handle_user_break); void (*old_sigterm)(int) = signal(SIGTERM, handle_user_break); void (*old_sigquit)(int) = signal(SIGQUIT, handle_user_break); void (*old_sighup)(int) = signal(SIGHUP, handle_user_break); if (color_output) { string s_ccout; char buf[250]; for (;;) { int r; while ((r = read(pf[0], buf, sizeof(buf) - 1)) > 0) { buf[r] = '\0'; s_ccout.append(buf); } if (r == 0) { break; } if (r < 0 && errno != EINTR) { break; } } colorify_output(s_ccout); } int status = 1; while (wait4(child_pid, &status, 0, used) < 0 && errno == EINTR) {} status = shell_exit_status(status); signal(SIGINT, old_sigint); signal(SIGTERM, old_sigterm); signal(SIGQUIT, old_sigquit); signal(SIGHUP, old_sighup); if (user_break_signal) { raise(user_break_signal); } if (lock_fd) { dcc_unlock(lock_fd); } return status; }