static bool maybe_build_local(MsgChannel *local_daemon, UseCSMsg *usecs, CompileJob &job, int &ret) { remote_daemon = usecs->hostname; if (usecs->hostname == "127.0.0.1") { // If this is a test build, do local builds on the local daemon // that has --no-remote, use remote building for the remaining ones. if (getenv("ICECC_TEST_REMOTEBUILD") && usecs->port != 0 ) return false; trace() << "building myself, but telling localhost\n"; int job_id = usecs->job_id; job.setJobID(job_id); job.setEnvironmentVersion("__client"); CompileFileMsg compile_file(&job); if (!local_daemon->send_msg(compile_file)) { log_info() << "write of job failed" << endl; throw(29); } struct timeval begintv, endtv; struct rusage ru; gettimeofday(&begintv, 0); ret = build_local(job, local_daemon, &ru); gettimeofday(&endtv, 0); // filling the stats, so the daemon can play proxy for us JobDoneMsg msg(job_id, ret, JobDoneMsg::FROM_SUBMITTER); msg.real_msec = (endtv.tv_sec - begintv.tv_sec) * 1000 + (endtv.tv_usec - begintv.tv_usec) / 1000; struct stat st; if (!stat(job.outputFile().c_str(), &st)) { msg.out_uncompressed = st.st_size; } msg.user_msec = ru.ru_utime.tv_sec * 1000 + ru.ru_utime.tv_usec / 1000; msg.sys_msec = ru.ru_stime.tv_sec * 1000 + ru.ru_stime.tv_usec / 1000; msg.pfaults = ru.ru_majflt + ru.ru_minflt + ru.ru_nswap; msg.exitcode = ret; if (msg.user_msec > 50 && msg.out_uncompressed > 1024) { trace() << "speed=" << float(msg.out_uncompressed / msg.user_msec) << endl; } return local_daemon->send_msg(msg); } return false; }
bool compiler_is_clang(const CompileJob &job) { if (job.language() == CompileJob::Lang_Custom) { return false; } assert(job.compilerName().find('/') == string::npos); return job.compilerName().find("clang") != string::npos; }
/* * Get the name of the compiler depedant on the * language of the job and the environment * variable set. This is useful for native cross-compilers. * (arm-linux-gcc for example) */ string find_compiler( const CompileJob& job ) { if (job.language() == CompileJob::Lang_C) { if (const char* env = getenv( "ICECC_CC" )) return env; } if (job.language() == CompileJob::Lang_CXX) { if (const char* env = getenv ("ICECC_CXX")) return env; } return compiler_path_lookup(job.compilerName()); }
/* Clang works suboptimally when handling an already preprocessed source file, for example error messages quote (already preprocessed) parts of the source. Therefore it is better to only locally merge all #include files into the source file and do the actual preprocessing remotely together with compiling. There exists a Clang patch to implement option -frewrite-includes that does such #include rewritting, and it's been only recently merged upstream. This is similar with newer gcc versions, and gcc has -fdirectives-only, which works similarly to -frewrite-includes (although it's not exactly the same). */ bool compiler_only_rewrite_includes(const CompileJob &job) { if( job.blockRewriteIncludes()) { return false; } if (const char *rewrite_includes = getenv("ICECC_REMOTE_CPP")) { return (*rewrite_includes != '\0') && (*rewrite_includes != '0'); } if (!compiler_is_clang(job)) { #ifdef HAVE_GCC_FDIRECTIVES_ONLY // gcc has had -fdirectives-only for a long time, but clang on macosx poses as gcc // and fails when given the option. Since we right now detect whether a compiler // is gcc merely by checking the binary name, enable usage only if the configure // check found the option working. return true; #endif } if (compiler_is_clang(job)) { if (const char *rewrite_includes = getenv("ICECC_CLANG_REMOTE_CPP")) { return (*rewrite_includes != '\0') && (*rewrite_includes != '0'); } #ifdef HAVE_CLANG_REWRITE_INCLUDES // Assume that we use the same clang (as least as far as capabilities go) // as was available when icecream was built. ICECC_CLANG_REMOTE_CPP above // allows override, and the only case when this should realistically break // is if somebody downgrades their clang. return true; #endif } return false; }
static bool analyze_program(const char* name, CompileJob& job) { string compiler_name = find_basename( name ); string::size_type pos = compiler_name.rfind('/'); if (pos != string::npos) compiler_name = compiler_name.substr(pos); job.setCompilerName( compiler_name ); string suffix = compiler_name; if ( compiler_name.size() > 2) suffix = compiler_name.substr(compiler_name.size()-2); if (suffix == "++" || suffix == "CC") job.setLanguage (CompileJob::Lang_CXX); else if (suffix == "cc") job.setLanguage (CompileJob::Lang_C); else if (compiler_name == "clang") job.setLanguage (CompileJob::Lang_C); else { job.setLanguage( CompileJob::Lang_Custom ); job.setCompilerName( name ); // keep path return true; } return false; }
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; }
static int build_remote_int(CompileJob &job, UseCSMsg *usecs, MsgChannel *local_daemon, const string &environment, const string &version_file, const char *preproc_file, bool output ) { string hostname = usecs->hostname; unsigned int port = usecs->port; int job_id = usecs->job_id; bool got_env = usecs->got_env; job.setJobID( job_id ); job.setEnvironmentVersion( environment ); // hoping on the scheduler's wisdom trace() << "Have to use host " << hostname << ":" << port << " - Job ID: " << job.jobID() << " - env: " << usecs->host_platform << " - has env: " << (got_env ? "true" : "false") << " - match j: " << usecs->matched_job_id << "\n"; int status = 255; MsgChannel *cserver = 0; try { cserver = Service::createChannel(hostname, port, 10); if ( !cserver ) { log_error() << "no server found behind given hostname " << hostname << ":" << port << endl; throw ( 2 ); } if ( !got_env ) { log_block b("Transfer Environment"); // transfer env struct stat buf; if ( stat( version_file.c_str(), &buf ) ) { log_perror( "error stat'ing version file" ); throw( 4 ); } EnvTransferMsg msg( job.targetPlatform(), job.environmentVersion() ); if ( !cserver->send_msg( msg ) ) throw( 6 ); int env_fd = open( version_file.c_str(), O_RDONLY ); if (env_fd < 0) throw ( 5 ); write_server_cpp( env_fd, cserver ); if ( !cserver->send_msg( EndMsg() ) ) { log_error() << "write of environment failed" << endl; throw( 8 ); } if ( IS_PROTOCOL_31( cserver )) { VerifyEnvMsg verifymsg( job.targetPlatform(), job.environmentVersion() ); if ( !cserver->send_msg( verifymsg ) ) throw( 22 ); Msg *msg = cserver->get_msg(60); if ( msg && msg->type == M_VERIFY_ENV_RESULT ) { if( !static_cast<VerifyEnvResultMsg*>( msg )->ok ) { // The remote can't handle the environment at all (e.g. kernel too old), // mark it as never to be used again for this environment. log_info() << "Host " << hostname << " did not successfully verify environment." << endl; BlacklistHostEnvMsg blacklist( job.targetPlatform(), job.environmentVersion(), hostname ); local_daemon->send_msg( blacklist ); throw( 24 ); } else trace() << "Verified host " << hostname << " for environment " << job.environmentVersion() << " (" << job.targetPlatform() << ")" << endl; } else throw( 25 ); } } if( !IS_PROTOCOL_31( cserver ) && ignore_unverified()) { log_warning() << "Host " << hostname << " cannot be verified." << endl; throw( 26 ); } CompileFileMsg compile_file( &job ); { log_block b("send compile_file"); if ( !cserver->send_msg( compile_file ) ) { log_info() << "write of job failed" << endl; throw( 9 ); } } if ( !preproc_file ) { int sockets[2]; if (pipe(sockets)) { /* for all possible cases, this is something severe */ exit(errno); } /* This will fork, and return the pid of the child. It will not return for the child itself. If it returns normally it will have closed the write fd, i.e. sockets[1]. */ pid_t cpp_pid = call_cpp(job, sockets[1], sockets[0] ); if ( cpp_pid == -1 ) throw( 18 ); try { log_block bl2("write_server_cpp from cpp"); write_server_cpp( sockets[0], cserver ); } catch ( int error ) { kill( cpp_pid, SIGTERM ); throw ( error ); } log_block wait_cpp("wait for cpp"); while(waitpid( cpp_pid, &status, 0) < 0 && errno == EINTR) ; if ( shell_exit_status(status) != 0 ) { // failure delete cserver; cserver = 0; return shell_exit_status( status ); } } else { int cpp_fd = open( preproc_file, O_RDONLY ); if ( cpp_fd < 0 ) throw ( 11 ); log_block cpp_block("write_server_cpp"); write_server_cpp( cpp_fd, cserver ); } { if ( !cserver->send_msg( EndMsg() ) ) { log_info() << "write of end failed" << endl; throw( 12 ); } } Msg *msg; { log_block wait_cs("wait for cs"); msg = cserver->get_msg( 12 * 60 ); if ( !msg ) throw( 14 ); } check_for_failure( msg, cserver ); if ( msg->type != M_COMPILE_RESULT ) { log_warning() << "waited for compile result, but got " << (char)msg->type << endl; delete msg; throw( 13 ); } CompileResultMsg *crmsg = dynamic_cast<CompileResultMsg*>( msg ); assert ( crmsg ); status = crmsg->status; if ( status && crmsg->was_out_of_memory ) { delete crmsg; log_info() << "the server ran out of memory, recompiling locally" << endl; throw( 17 ); // recompile locally - TODO: handle this as a normal local job not an error case } if ( output ) { write(STDOUT_FILENO, crmsg->out.c_str(), crmsg->out.size() ); if(colorify_wanted(job)) colorify_output(crmsg->err); else write(STDERR_FILENO, crmsg->err.c_str(), crmsg->err.size() ); if ( status && ( crmsg->err.length() || crmsg->out.length() ) ) { log_error() << "Compiled on " << hostname << endl; } } delete crmsg; assert( !job.outputFile().empty() ); if( status == 0 ) { string tmp_file = job.outputFile() + "_icetmp"; int obj_fd = open( tmp_file.c_str(), O_CREAT|O_TRUNC|O_WRONLY|O_LARGEFILE, 0666 ); if ( obj_fd == -1 ) { std::string errmsg("can't create "); errmsg += tmp_file + ":"; log_perror(errmsg.c_str()); return EXIT_DISTCC_FAILED; } msg = 0; size_t uncompressed = 0; size_t compressed = 0; while ( 1 ) { delete msg; msg = cserver->get_msg(40); if ( !msg ) { // the network went down? unlink( tmp_file.c_str()); throw ( 19 ); } check_for_failure( msg, cserver ); if ( msg->type == M_END ) break; if ( msg->type != M_FILE_CHUNK ) { unlink( tmp_file.c_str()); delete msg; throw ( 20 ); } FileChunkMsg *fcmsg = dynamic_cast<FileChunkMsg*>( msg ); compressed += fcmsg->compressed; uncompressed += fcmsg->len; if ( write( obj_fd, fcmsg->buffer, fcmsg->len ) != ( ssize_t )fcmsg->len ) { unlink( tmp_file.c_str()); delete msg; throw ( 21 ); } } if (uncompressed) trace() << "got " << compressed << " bytes (" << (compressed * 100 / uncompressed) << "%)" << endl; delete msg; if( close( obj_fd ) == 0 ) rename( tmp_file.c_str(), job.outputFile().c_str()); else unlink( tmp_file.c_str()); } } catch ( int x ) { delete cserver; cserver = 0; throw( x ); } delete cserver; return status; }
int work_it( CompileJob &j, unsigned int job_stat[], MsgChannel* client, CompileResultMsg& rmsg, const string &outfilename, unsigned long int mem_limit, int client_fd, int /*job_in_fd*/ ) { rmsg.out.erase(rmsg.out.begin(), rmsg.out.end()); rmsg.out.erase(rmsg.out.begin(), rmsg.out.end()); std::list<string> list = j.remoteFlags(); appendList( list, j.restFlags() ); int sock_err[2]; int sock_out[2]; int sock_in[2]; int main_sock[2]; char buffer[4096]; if ( pipe( sock_err ) ) return EXIT_DISTCC_FAILED; if ( pipe( sock_out ) ) return EXIT_DISTCC_FAILED; if ( pipe( main_sock ) ) return EXIT_DISTCC_FAILED; if ( pipe( death_pipe ) ) return EXIT_DISTCC_FAILED; // We use a socket pair instead of a pipe to get a "slightly" bigger // output buffer. This saves context switches and latencies. if (socketpair(AF_UNIX, SOCK_STREAM, 0, sock_in) < 0) return EXIT_DISTCC_FAILED; int maxsize = 2*1024*2024; #ifdef SO_SNDBUFFORCE if (setsockopt(sock_in[1], SOL_SOCKET, SO_SNDBUFFORCE, &maxsize, sizeof(maxsize)) < 0) #endif { setsockopt(sock_in[1], SOL_SOCKET, SO_SNDBUF, &maxsize, sizeof(maxsize)); } if ( fcntl( sock_in[1], F_SETFL, O_NONBLOCK ) ) return EXIT_DISTCC_FAILED; /* Testing */ struct sigaction act; sigemptyset( &act.sa_mask ); act.sa_handler = SIG_IGN; act.sa_flags = 0; sigaction( SIGPIPE, &act, 0L ); act.sa_handler = theSigCHLDHandler; act.sa_flags = SA_NOCLDSTOP; sigaction( SIGCHLD, &act, 0 ); sigaddset( &act.sa_mask, SIGCHLD ); // Make sure we don't block this signal. gdb tends to do that :-( sigprocmask( SIG_UNBLOCK, &act.sa_mask, 0 ); flush_debug(); pid_t pid = fork(); if ( pid == -1 ) { return EXIT_OUT_OF_MEMORY; } else if ( pid == 0 ) { setenv( "PATH", "usr/bin", 1 ); // Safety check if (getuid() == 0 || getgid() == 0) { error_client( client, "UID is 0 - aborting." ); _exit(142); } #ifdef RLIMIT_AS struct rlimit rlim; if ( getrlimit( RLIMIT_AS, &rlim ) ) { error_client( client, "getrlimit failed." ); log_perror( "getrlimit" ); } rlim.rlim_cur = mem_limit*1024*1024; rlim.rlim_max = mem_limit*1024*1024; if ( setrlimit( RLIMIT_AS, &rlim ) ) { error_client( client, "setrlimit failed." ); log_perror( "setrlimit" ); } #endif int argc = list.size(); argc++; // the program argc += 6; // -x c - -o file.o -fpreprocessed argc += 4; // gpc parameters argc += 1; // -pipe char **argv = new char*[argc + 1]; int i = 0; if (j.language() == CompileJob::Lang_C) argv[i++] = strdup( "usr/bin/gcc" ); else if (j.language() == CompileJob::Lang_CXX) argv[i++] = strdup( "usr/bin/g++" ); else assert(0); bool hasPipe = false; for ( std::list<string>::const_iterator it = list.begin(); it != list.end(); ++it) { if(*it == "-pipe") hasPipe = true; argv[i++] = strdup( it->c_str() ); } argv[i++] = strdup("-fpreprocessed"); if(!hasPipe) argv[i++] = strdup("-pipe"); argv[i++] = strdup("-x"); argv[i++] = strdup((j.language() == CompileJob::Lang_CXX) ? "c++" : "c"); argv[i++] = strdup( "-" ); argv[i++] = strdup( "-o" ); argv[i++] = strdup(outfilename.c_str()); argv[i++] = strdup( "--param" ); sprintf( buffer, "ggc-min-expand=%d", ggc_min_expand_heuristic( mem_limit ) ); argv[i++] = strdup( buffer ); argv[i++] = strdup( "--param" ); sprintf( buffer, "ggc-min-heapsize=%d", ggc_min_heapsize_heuristic( mem_limit ) ); argv[i++] = strdup( buffer ); // before you add new args, check above for argc argv[i] = 0; assert(i <= argc); close_debug(); close( sock_out[0] ); dup2 (sock_out[1], STDOUT_FILENO ); close(sock_out[1]); close(sock_err[0]); dup2( sock_err[1], STDERR_FILENO ); close(sock_err[1]); close( sock_in[1] ); dup2( sock_in[0], STDIN_FILENO); close (sock_in[0]); close( main_sock[0] ); fcntl(main_sock[1], F_SETFD, FD_CLOEXEC); close( death_pipe[0] ); close( death_pipe[1] ); #ifdef ICECC_DEBUG for(int f = STDERR_FILENO+1; f < 4096; ++f) { long flags; assert((flags = fcntl(f, F_GETFD, 0)) < 0 || (flags & FD_CLOEXEC)); } #endif execv( argv[0], const_cast<char *const*>( argv ) ); // no return perror( "ICECC: execv" ); char resultByte = 1; write(main_sock[1], &resultByte, 1); _exit(-1); } close( sock_in[0] ); close( sock_out[1] ); close( sock_err[1] ); // idea borrowed from kprocess. // check whether the compiler could be run at all. close( main_sock[1] ); for(;;) { char resultByte; ssize_t n = ::read(main_sock[0], &resultByte, 1); if (n == -1 && errno == EINTR) continue; // Ignore if (n == 1) { rmsg.status = resultByte; error_client( client, "compiler did not start" ); return EXIT_COMPILER_MISSING; } break; // != EINTR } close( main_sock[0] ); struct timeval starttv; gettimeofday(&starttv, 0 ); int return_value = 0; // Got EOF for preprocessed input. stdout send may be still pending. bool input_complete = false; // Pending data to send to stdin FileChunkMsg *fcmsg = 0; size_t off = 0; log_block parent_wait("parent, waiting"); for(;;) { if ( client_fd >= 0 && !fcmsg ) { if (Msg *msg = client->get_msg(0)) { if (input_complete) { rmsg.err.append( "client cancelled\n" ); return_value = EXIT_CLIENT_KILLED; client_fd = -1; kill(pid, SIGTERM); delete fcmsg; fcmsg = 0; delete msg; } else { if ( msg->type == M_END ) { input_complete = true; if (!fcmsg) { close( sock_in[1] ); sock_in[1] = -1; } delete msg; } else if ( msg->type == M_FILE_CHUNK ) { fcmsg = static_cast<FileChunkMsg*>( msg ); off = 0; job_stat[JobStatistics::in_uncompressed] += fcmsg->len; job_stat[JobStatistics::in_compressed] += fcmsg->compressed; } else { log_error() << "protocol error while reading preprocessed file\n"; return_value = EXIT_IO_ERROR; client_fd = -1; kill(pid, SIGTERM); delete fcmsg; fcmsg = 0; delete msg; } } } else if (client->at_eof()) { log_error() << "unexpected EOF while reading preprocessed file\n"; return_value = EXIT_IO_ERROR; client_fd = -1; kill(pid, SIGTERM); delete fcmsg; fcmsg = 0; } } fd_set rfds; FD_ZERO( &rfds ); if (sock_out[0] >= 0) FD_SET( sock_out[0], &rfds ); if (sock_err[0] >= 0) FD_SET( sock_err[0], &rfds ); int max_fd = std::max( sock_out[0], sock_err[0] ); if ( client_fd >= 0 && !fcmsg ) { FD_SET( client_fd, &rfds ); if ( client_fd > max_fd ) max_fd = client_fd; // Note that we don't actually query the status of this fd - // we poll it in every iteration. } FD_SET( death_pipe[0], &rfds ); if ( death_pipe[0] > max_fd ) max_fd = death_pipe[0]; fd_set wfds, *wfdsp = 0; FD_ZERO( &wfds ); if (fcmsg) { FD_SET( sock_in[1], &wfds ); wfdsp = &wfds; if ( sock_in[1] > max_fd ) max_fd = sock_in[1]; } struct timeval tv, *tvp = 0; if (!input_complete) { tv.tv_sec = 60; tv.tv_usec = 0; tvp = &tv; } switch( select( max_fd+1, &rfds, wfdsp, 0, tvp ) ) { case 0: if (!input_complete) { log_error() << "timeout while reading preprocessed file\n"; kill(pid, SIGTERM); // Won't need it any more ... return_value = EXIT_IO_ERROR; client_fd = -1; input_complete = true; delete fcmsg; fcmsg = 0; continue; } // this should never happen assert( false ); return EXIT_DISTCC_FAILED; case -1: if (errno == EINTR) continue; // this should never happen assert( false ); return EXIT_DISTCC_FAILED; default: if ( fcmsg && FD_ISSET(sock_in[1], &wfds) ) { ssize_t bytes = write( sock_in[1], fcmsg->buffer + off, fcmsg->len - off ); if ( bytes < 0 ) { if (errno == EINTR) continue; kill(pid, SIGTERM); // Most likely crashed anyway ... return_value = EXIT_COMPILER_CRASHED; continue; } // The fd is -1 anyway //write(job_in_fd, fcmsg->buffer + off, bytes); off += bytes; if (off == fcmsg->len) { delete fcmsg; fcmsg = 0; if (input_complete) { close( sock_in[1] ); sock_in[1] = -1; } } } if ( sock_out[0] >= 0 && FD_ISSET(sock_out[0], &rfds) ) { ssize_t bytes = read( sock_out[0], buffer, sizeof(buffer)-1 ); if ( bytes > 0 ) { buffer[bytes] = 0; rmsg.out.append( buffer ); } else if (bytes == 0) { close(sock_out[0]); sock_out[0] = -1; } } if ( sock_err[0] >= 0 && FD_ISSET(sock_err[0], &rfds) ) { ssize_t bytes = read( sock_err[0], buffer, sizeof(buffer)-1 ); if ( bytes > 0 ) { buffer[bytes] = 0; rmsg.err.append( buffer ); } else if (bytes == 0) { close(sock_err[0]); sock_err[0] = -1; } } if ( FD_ISSET(death_pipe[0], &rfds) ) { // Note that we have already read any remaining stdout/stderr: // the sigpipe is delivered after everything was written, // and the notification is multiplexed into the select above. struct rusage ru; int status; if (wait4(pid, &status, 0, &ru) != pid) { // this should never happen assert( false ); return EXIT_DISTCC_FAILED; } if ( !WIFEXITED(status) || WEXITSTATUS(status) ) { unsigned long int mem_used = ( ru.ru_minflt + ru.ru_majflt ) * getpagesize() / 1024; rmsg.status = EXIT_OUT_OF_MEMORY; if ( mem_used * 100 > 85 * mem_limit * 1024 || rmsg.err.find( "memory exhausted" ) != string::npos ) { // the relation between ulimit and memory used is pretty thin ;( return EXIT_OUT_OF_MEMORY; } } if ( WIFEXITED(status) ) { struct timeval endtv; gettimeofday(&endtv, 0 ); rmsg.status = WEXITSTATUS(status); job_stat[JobStatistics::exit_code] = WEXITSTATUS(status); job_stat[JobStatistics::real_msec] = (endtv.tv_sec - starttv.tv_sec) * 1000 + (long(endtv.tv_usec) - long(starttv.tv_usec)) / 1000; job_stat[JobStatistics::user_msec] = ru.ru_utime.tv_sec * 1000 + ru.ru_utime.tv_usec / 1000; job_stat[JobStatistics::sys_msec] = ru.ru_stime.tv_sec * 1000 + ru.ru_stime.tv_usec / 1000; job_stat[JobStatistics::sys_pfaults] = ru.ru_majflt + ru.ru_nswap + ru.ru_minflt; } return return_value; } } } }
int main(int argc, char **argv) { char *env = getenv("ICECC_DEBUG"); int debug_level = Error; if (env) { if (!strcasecmp(env, "info")) { debug_level |= Info | Warning; } else if (!strcasecmp(env, "warnings")) { debug_level |= Warning; // taking out warning } else { // any other value debug_level |= Info | Debug | Warning; } } std::string logfile; if (const char *logfileEnv = getenv("ICECC_LOGFILE")) { logfile = logfileEnv; } setup_debug(debug_level, logfile, "ICECC"); CompileJob job; bool icerun = false; string compiler_name = argv[0]; dcc_client_catch_signals(); char cwd[ PATH_MAX ]; if( getcwd( cwd, PATH_MAX ) != NULL ) job.setWorkingDirectory( cwd ); if (find_basename(compiler_name) == rs_program_name) { if (argc > 1) { string arg = argv[1]; if (arg == "--help") { dcc_show_usage(); return 0; } if (arg == "--version") { printf("ICECC " VERSION "\n"); return 0; } if (arg == "--build-native") { return create_native(argv + 2); } if (arg.size() > 0) { job.setCompilerName(arg); job.setCompilerPathname(arg); } } } else if (find_basename(compiler_name) == "icerun") { icerun = true; if (argc > 1) { string arg = argv[1]; if (arg == "--help") { icerun_show_usage(); return 0; } if (arg == "--version") { printf("ICERUN " VERSION "\n"); return 0; } if (arg.size() > 0) { job.setCompilerName(arg); job.setCompilerPathname(arg); } } } else { std::string resolved; // check if it's a symlink to icerun if (resolve_link(compiler_name, resolved) == 0 && find_basename(resolved) == "icerun") { icerun = true; } } int sg_level = dcc_recursion_safeguard(); if (sg_level > 0) { log_error() << "icecream seems to have invoked itself recursively!" << endl; return EXIT_RECURSION; } /* Ignore SIGPIPE; we consistently check error codes and will * see the EPIPE. */ dcc_ignore_sigpipe(1); list<string> extrafiles; local |= analyse_argv(argv, job, icerun, &extrafiles); /* If ICECC is set to disable, then run job locally, without contacting the daemon at all. Because of file-based locking that is used in this case, all calls will be serialized and run by one. If ICECC is set to no, the job is run locally as well, but it is serialized using the daemon, so several may be run at once. */ char *icecc = getenv("ICECC"); if (icecc && !strcasecmp(icecc, "disable")) { return build_local(job, 0); } if (icecc && !strcasecmp(icecc, "no")) { local = true; } if (const char *extrafilesenv = getenv("ICECC_EXTRAFILES")) { for (;;) { const char *colon = strchr(extrafilesenv, ':'); string file; if (colon == NULL) { file = extrafilesenv; } else { file = string(extrafilesenv, colon - extrafilesenv); } file = get_absfilename(file); struct stat st; if (stat(file.c_str(), &st) == 0) { extrafiles.push_back(file); } else { log_warning() << "File in ICECC_EXTRAFILES not found: " << file << endl; return build_local(job, 0); } if (colon == NULL) { break; } extrafilesenv = colon + 1; } } MsgChannel *local_daemon; if (getenv("ICECC_TEST_SOCKET") == NULL) { /* try several options to reach the local daemon - 3 sockets, one TCP */ local_daemon = Service::createChannel("/var/run/icecc/iceccd.socket"); if (!local_daemon) { local_daemon = Service::createChannel("/var/run/iceccd.socket"); } if (!local_daemon && getenv("HOME")) { string path = getenv("HOME"); path += "/.iceccd.socket"; local_daemon = Service::createChannel(path); } if (!local_daemon) { local_daemon = Service::createChannel("127.0.0.1", 10245, 0/*timeout*/); } } else { local_daemon = Service::createChannel(getenv("ICECC_TEST_SOCKET")); if (!local_daemon) { log_error() << "test socket error" << endl; return EXIT_TEST_SOCKET_ERROR; } } if (!local_daemon) { log_warning() << "no local daemon found" << endl; return build_local(job, 0); } Environments envs; if (!local) { if (getenv("ICECC_VERSION")) { // if set, use it, otherwise take default try { envs = parse_icecc_version(job.targetPlatform(), find_prefix(job.compilerName())); } catch (int x) { // we just build locally } } else if (!extrafiles.empty() && !IS_PROTOCOL_32(local_daemon)) { log_warning() << "Local daemon is too old to handle compiler plugins." << endl; local = true; } else { if (!local_daemon->send_msg(GetNativeEnvMsg(compiler_is_clang(job) ? "clang" : "gcc", extrafiles))) { log_warning() << "failed to write get native environment" << endl; goto do_local_error; } // the timeout is high because it creates the native version Msg *umsg = local_daemon->get_msg(4 * 60); string native; if (umsg && umsg->type == M_NATIVE_ENV) { native = static_cast<UseNativeEnvMsg*>(umsg)->nativeVersion; } if (native.empty() || ::access(native.c_str(), R_OK)) { log_warning() << "daemon can't determine native environment. " "Set $ICECC_VERSION to an icecc environment.\n"; } else { envs.push_back(make_pair(job.targetPlatform(), native)); log_info() << "native " << native << endl; } delete umsg; } // we set it to local so we tell the local daemon about it - avoiding file locking if (envs.size() == 0) { local = true; } for (Environments::const_iterator it = envs.begin(); it != envs.end(); ++it) { trace() << "env: " << it->first << " '" << it->second << "'" << endl; if (::access(it->second.c_str(), R_OK)) { log_error() << "can't read environment " << it->second << endl; local = true; } } } int ret; if (local) { log_block b("building_local"); struct rusage ru; Msg *startme = 0L; /* Inform the daemon that we like to start a job. */ if (local_daemon->send_msg(JobLocalBeginMsg(0, get_absfilename(job.outputFile())))) { /* Now wait until the daemon gives us the start signal. 40 minutes should be enough for all normal compile or link jobs. */ startme = local_daemon->get_msg(40 * 60); } /* If we can't talk to the daemon anymore we need to fall back to lock file locking. */ if (!startme || startme->type != M_JOB_LOCAL_BEGIN) { goto do_local_error; } ret = build_local(job, local_daemon, &ru); } else { try { // check if it should be compiled three times const char *s = getenv("ICECC_REPEAT_RATE"); int rate = s ? atoi(s) : 0; ret = build_remote(job, local_daemon, envs, rate); /* We have to tell the local daemon that everything is fine and that the remote daemon will send the scheduler our done msg. If we don't, the local daemon will have to assume the job failed and tell the scheduler - and that fail message may arrive earlier than the remote daemon's success msg. */ if (ret == 0) { local_daemon->send_msg(EndMsg()); } } catch (int error) { if (error >= 100) { log_info() << "local build forced by error " << error << endl; goto do_local_error; } if (remote_daemon.size()) { log_error() << "got exception " << error << " (" << remote_daemon.c_str() << ") " << endl; } else { log_error() << "got exception " << error << " (this should be an exception!)" << endl; } /* currently debugging a client? throw an error then */ if (debug_level != Error) { return error; } goto do_local_error; } } delete local_daemon; return ret; do_local_error: delete local_daemon; return build_local(job, 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); 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; }
/** * 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; }
bool compiler_is_clang( const CompileJob& job ) { return job.compilerName().find("clang") != string::npos; }
bool analyse_argv( const char * const *argv, CompileJob &job, bool icerun, list<string> *extrafiles ) { ArgumentsList args; string ofile; #if CLIENT_DEBUG > 1 trace() << "scanning arguments "; for ( int index = 0; argv[index]; index++ ) trace() << argv[index] << " "; trace() << endl; #endif bool had_cc = (job.compilerName().size() > 0); bool always_local = analyze_program(had_cc ? job.compilerName().c_str() : argv[0], job); bool seen_c = false; bool seen_s = false; bool seen_mf = false; bool seen_md = false; bool fno_color_diagnostics = false; // if rewriting includes and precompiling on remote machine, then cpp args are not local Argument_Type Arg_Cpp = compiler_only_rewrite_includes( job ) ? Arg_Rest : Arg_Local; if( icerun ) { always_local = true; job.setLanguage( CompileJob::Lang_Custom ); } for (int i = had_cc ? 2 : 1; argv[i]; i++) { const char *a = argv[i]; if (icerun) { args.append(a, Arg_Local); } else if (a[0] == '-') { if (!strcmp(a, "-E") || !strncmp(a, "-fdump", 6) || !strcmp(a, "-combine")) { always_local = true; args.append(a, Arg_Local); } else if (!strcmp(a, "-MD") || !strcmp(a, "-MMD")) { seen_md = true; args.append(a, Arg_Local); /* These two generate dependencies as a side effect. They * should work with the way we call cpp. */ } else if (!strcmp(a, "-MG") || !strcmp(a, "-MP")) { args.append(a, Arg_Local); /* These just modify the behaviour of other -M* options and do * nothing by themselves. */ } else if (!strcmp(a, "-MF")) { seen_mf = true; args.append(a, Arg_Local); args.append( argv[++i], Arg_Local ); /* as above but with extra argument */ } else if (!strcmp(a, "-MT") || !strcmp(a, "-MQ")) { args.append(a, Arg_Local); args.append( argv[++i], Arg_Local ); /* as above but with extra argument */ } else if (a[1] == 'M') { /* -M(anything else) causes the preprocessor to produce a list of make-style dependencies on header files, either to stdout or to a local file. It implies -E, so only the preprocessor is run, not the compiler. There would be no point trying to distribute it even if we could. */ always_local = true; args.append(a, Arg_Local); } else if ( str_equal( "--param", a ) ) { args.append( a, Arg_Remote ); /* skip next word, being option argument */ if (argv[i+1]) args.append( argv[++i], Arg_Remote ); } else if ( a[1] == 'B' ) { /* -B overwrites the path where the compiler finds the assembler. As we don't use that, better force local job. */ always_local = true; args.append( a, Arg_Local ); if ( str_equal( a, "-B" ) ) { /* skip next word, being option argument */ if (argv[i+1]) args.append( argv[++i], Arg_Local ); } } else if (str_startswith("-Wa,", a)) { /* Options passed through to the assembler. The only one we * need to handle so far is -al=output, which directs the * listing to the named file and cannot be remote. There are * some other options which also refer to local files, * but most of them make no sense when called via the compiler, * hence we only look for -a[a-z]*= and localize the job if we * find it. */ const char *pos = a; bool local = false; while ((pos = strstr(pos+1, "-a"))) { pos += 2; while (*pos >= 'a' && *pos <= 'z') pos++; if (*pos == '=') { local = true; break; } if (!*pos) break; } /* Some weird build systems pass directly additional assembler files. * Example: -Wa,src/code16gcc.s * Need to handle it locally then. Search if the first part after -Wa, does not start with - */ pos = a+3; while (*pos) { if (*pos == ',' || *pos == ' ') { pos++; continue; } if (*pos == '-') break; local = true; break; } if (local) { always_local = true; args.append(a, Arg_Local); } else args.append(a, Arg_Remote); } else if (!strcmp(a, "-S")) { seen_s = true; } else if (!strcmp(a, "-fprofile-arcs") || !strcmp(a, "-ftest-coverage") || !strcmp( a, "-frepo" ) || !strcmp( a, "-fprofile-generate" ) || !strcmp( a, "-fprofile-use" ) || !strcmp( a, "-save-temps") || !strcmp( a, "-fbranch-probabilities") ) { #if CLIENT_DEBUG log_info() << "compiler will emit profile info; must be local" << endl; #endif always_local = true; args.append(a, Arg_Local); } else if (!strcmp(a, "-x")) { #if CLIENT_DEBUG log_info() << "gcc's -x handling is complex; running locally" << endl; #endif always_local = true; args.append(a, Arg_Local); } else if (!strcmp(a, "-march=native") || !strcmp(a, "-mcpu=native") || !strcmp(a, "-mtune=native")) { #if CLIENT_DEBUG log_info() << "-{march,mpcu,mtune}=native optimizes for local machine; must be local" << endl; #endif always_local = true; args.append(a, Arg_Local); } else if (!strcmp(a, "-c")) { seen_c = true; } else if (str_startswith("-o", a)) { if (!strcmp(a, "-o")) { /* Whatever follows must be the output */ if ( argv[i+1] ) ofile = argv[++i]; } else { a += 2; ofile = a; } if (ofile == "-" ) { /* Different compilers may treat "-o -" as either "write to * stdout", or "write to a file called '-'". We can't know, * so we just always run it locally. Hopefully this is a * pretty rare case. */ #if CLIENT_DEBUG log_info() << "output to stdout? running locally" << endl; #endif always_local = true; } } else if (str_equal("-include", a)) { /* This has a duplicate meaning. it can either include a file for preprocessing or a precompiled header. decide which one. */ if (argv[i+1]) { ++i; std::string p = argv[i]; string::size_type dot_index = p.find_last_of('.'); if (dot_index != string::npos) { string ext = p.substr(dot_index + 1); if (ext[0] != 'h' && ext[0] != 'H' && access(p.c_str(), R_OK) && access((p + ".gch").c_str(), R_OK)) always_local = true; } else always_local = true; /* Included file is not header.suffix or header.suffix.gch! */ args.append(a, Arg_Local); args.append(argv[i], Arg_Local); } } else if (str_equal("-D", a) || str_equal("-U", a) ) { args.append(a, Arg_Cpp); /* skip next word, being option argument */ if (argv[i+1]) { ++i; args.append( argv[i], Arg_Cpp ); } } else if (str_equal("-I", a) || str_equal("-L", a) || str_equal("-l", a) || str_equal("-MF", a) || str_equal("-MT", a) || str_equal("-MQ", a) || str_equal("-imacros", a) || str_equal("-iprefix", a) || str_equal("-iwithprefix", a) || str_equal("-isystem", a) || str_equal("-iquote", a) || str_equal("-imultilib", a) || str_equal("-isysroot", a) || str_equal("-include", a) || str_equal("-iwithprefixbefore", a) || str_equal("-idirafter", a) ) { args.append(a, Arg_Local); /* skip next word, being option argument */ if (argv[i+1]) { ++i; if (str_startswith("-O", argv[i])) always_local = true; args.append( argv[i], Arg_Local ); } } else if (str_startswith("-Wp,", a) || str_startswith("-D", a) || str_startswith("-U", a)) { args.append(a, Arg_Cpp); } else if (str_startswith("-I", a) || str_startswith("-l", a) || str_startswith("-L", a)) { args.append(a, Arg_Local); } else if (str_equal("-undef", a)) { args.append(a, Arg_Cpp); } else if (str_equal("-nostdinc", a) || str_equal("-nostdinc++", a) || str_equal("-MD", a) || str_equal("-MMD", a) || str_equal("-MG", a) || str_equal("-MP", a)) { args.append(a, Arg_Local); } else if (str_equal("-fno-color-diagnostics", a)) { fno_color_diagnostics = true; args.append( a, Arg_Rest ); } else if (str_equal("-fcolor-diagnostics", a)) { fno_color_diagnostics = false; args.append( a, Arg_Rest ); } else if ( str_equal( "-flto", a ) ) { // pointless when preprocessing, and Clang would emit a warning args.append( a, Arg_Remote ); } else if (str_startswith("-fplugin=", a)) { string file = a+strlen("-fplugin="); if (access(file.c_str(), R_OK) == 0) { file = get_absfilename(file); extrafiles->push_back(file); } else { always_local = true; } args.append("-fplugin="+file, Arg_Rest); } else if (str_equal("-Xclang", a)) { string a = argv[i]; if (argv[i+1]) { ++i; const char *p = argv[i]; if (str_equal("-load", p)) { if (argv[i+1] && argv[i+2] && str_equal(argv[i+1], "-Xclang")) { args.append(a, Arg_Rest); args.append(p, Arg_Rest); string file = argv[i+2]; if (access(file.c_str(), R_OK) == 0) { file = get_absfilename(file); extrafiles->push_back(file); } else { always_local = true; } args.append(argv[i+1], Arg_Rest); args.append(file, Arg_Rest); i += 2; } } else { args.append(a, Arg_Rest); args.append(p, Arg_Rest); } } } else args.append( a, Arg_Rest ); } else if (a[0] == '@') { args.append(a, Arg_Local); } else { args.append( a, Arg_Rest ); } } if (!seen_c && !seen_s) always_local = true; else if ( seen_s ) { if ( seen_c ) log_info() << "can't have both -c and -S, ignoring -c" << endl; args.append( "-S", Arg_Remote ); } else { args.append( "-c", Arg_Remote ); } if ( !always_local ) { ArgumentsList backup = args; /* TODO: ccache has the heuristic of ignoring arguments that are not * extant files when looking for the input file; that's possibly * worthwile. Of course we can't do that on the server. */ string ifile; for ( ArgumentsList::iterator it = args.begin(); it != args.end(); ) { if ( it->first == "-") { always_local = true; break; } if ( it->first == "-Xclang" ) { ++it; ++it; } else if ( it->second != Arg_Rest || it->first.at( 0 ) == '-' || it->first.at( 0 ) == '@' ) ++it; else if ( ifile.empty() ) { #if CLIENT_DEBUG log_info() << "input file: " << it->first << endl; #endif job.setInputFile( it->first ); ifile = it->first; it = args.erase( it ); } else { log_info() << "found another non option on command line. Two input files? " << it->first << endl; always_local = true; args = backup; job.setInputFile( string() ); break; } } if ( ifile.find( '.' ) != string::npos ) { string::size_type dot_index = ifile.find_last_of( '.' ); string ext = ifile.substr( dot_index + 1 ); if (ext == "cc" || ext == "cpp" || ext == "cxx" || ext == "cp" || ext == "c++" || ext == "C" || ext == "ii") { #if CLIENT_DEBUG if ( job.language() != CompileJob::Lang_CXX ) log_info() << "switching to C++ for " << ifile << endl; #endif job.setLanguage( CompileJob::Lang_CXX ); } else if(ext == "mi" || ext == "m" || ext == "mii" || ext == "mm" || ext == "M" ) { job.setLanguage( CompileJob::Lang_OBJC ); } else if ( ext == "s" || ext == "S" || // assembler ext == "ads" || ext == "adb" || // ada ext == "f" || ext == "for" || // fortran ext == "FOR" || ext == "F" || ext == "fpp" || ext == "FPP" || ext == "r" ) { always_local = true; } else if ( ext != "c" && ext != "i" ) { // C is special, it depends on arg[0] name log_warning() << "unknown extension " << ext << endl; always_local = true; } if ( !always_local && ofile.empty() ) { ofile = ifile.substr( 0, dot_index ); if ( seen_s ) ofile += ".s"; else ofile += ".o"; string::size_type slash = ofile.find_last_of( '/' ); if ( slash != string::npos ) ofile = ofile.substr( slash + 1 ); } if ( !always_local && seen_md && !seen_mf) { string dfile = ofile.substr( 0, ofile.find_last_of( '.' ) ) + ".d"; #if CLIENT_DEBUG log_info() << "dep file: " << dfile << endl; #endif args.append("-MF", Arg_Local); args.append(dfile, Arg_Local); } } } else { job.setInputFile( string() ); } struct stat st; if ( ofile.empty() || (!stat( ofile.c_str(), &st ) && !S_ISREG( st.st_mode ))) always_local = true; // redirecting Clang's output will turn off its automatic coloring, so force it, unless disabled if (compiler_is_clang(job) && colorify_possible() && !fno_color_diagnostics) args.append("-fcolor-diagnostics", Arg_Rest); job.setFlags( args ); job.setOutputFile( ofile ); #if CLIENT_DEBUG trace() << "scanned result: local args=" << concat_args( job.localFlags() ) << ", remote args=" << concat_args( job.remoteFlags() ) << ", rest=" << concat_args( job.restFlags() ) << ", local=" << always_local << ", compiler=" << job.compilerName() << ", lang=" << (job.language() != CompileJob::Lang_Custom ? (job.language() == CompileJob::Lang_CXX ? "C++" : "C" ) : "<custom>") << endl; #endif return always_local; }
static int build_remote_int(CompileJob &job, UseCSMsg *usecs, MsgChannel *local_daemon, const string &environment, const string &version_file, const char *preproc_file, bool output) { string hostname = usecs->hostname; unsigned int port = usecs->port; int job_id = usecs->job_id; bool got_env = usecs->got_env; job.setJobID(job_id); job.setEnvironmentVersion(environment); // hoping on the scheduler's wisdom trace() << "Have to use host " << hostname << ":" << port << " - Job ID: " << job.jobID() << " - env: " << usecs->host_platform << " - has env: " << (got_env ? "true" : "false") << " - match j: " << usecs->matched_job_id << "\n"; int status = 255; MsgChannel *cserver = 0; try { cserver = Service::createChannel(hostname, port, 10); if (!cserver) { log_error() << "no server found behind given hostname " << hostname << ":" << port << endl; throw(2); } if (!got_env) { log_block b("Transfer Environment"); // transfer env struct stat buf; if (stat(version_file.c_str(), &buf)) { log_perror("error stat'ing version file"); throw(4); } EnvTransferMsg msg(job.targetPlatform(), job.environmentVersion()); if (!cserver->send_msg(msg)) { throw(6); } int env_fd = open(version_file.c_str(), O_RDONLY); if (env_fd < 0) { throw(5); } write_server_cpp(env_fd, cserver); if (!cserver->send_msg(EndMsg())) { log_error() << "write of environment failed" << endl; throw(8); } if (IS_PROTOCOL_31(cserver)) { VerifyEnvMsg verifymsg(job.targetPlatform(), job.environmentVersion()); if (!cserver->send_msg(verifymsg)) { throw(22); } Msg *verify_msg = cserver->get_msg(60); if (verify_msg && verify_msg->type == M_VERIFY_ENV_RESULT) { if (!static_cast<VerifyEnvResultMsg*>(verify_msg)->ok) { // The remote can't handle the environment at all (e.g. kernel too old), // mark it as never to be used again for this environment. log_info() << "Host " << hostname << " did not successfully verify environment." << endl; BlacklistHostEnvMsg blacklist(job.targetPlatform(), job.environmentVersion(), hostname); local_daemon->send_msg(blacklist); throw(24); } else trace() << "Verified host " << hostname << " for environment " << job.environmentVersion() << " (" << job.targetPlatform() << ")" << endl; } else { throw(25); } } } if (!IS_PROTOCOL_31(cserver) && ignore_unverified()) { log_warning() << "Host " << hostname << " cannot be verified." << endl; throw(26); } CompileFileMsg compile_file(&job); { log_block b("send compile_file"); if (!cserver->send_msg(compile_file)) { log_info() << "write of job failed" << endl; throw(9); } } if (!preproc_file) { int sockets[2]; if (pipe(sockets)) { /* for all possible cases, this is something severe */ exit(errno); } /* This will fork, and return the pid of the child. It will not return for the child itself. If it returns normally it will have closed the write fd, i.e. sockets[1]. */ pid_t cpp_pid = call_cpp(job, sockets[1], sockets[0]); if (cpp_pid == -1) { throw(18); } try { log_block bl2("write_server_cpp from cpp"); write_server_cpp(sockets[0], cserver); } catch (int error) { kill(cpp_pid, SIGTERM); throw(error); } log_block wait_cpp("wait for cpp"); while (waitpid(cpp_pid, &status, 0) < 0 && errno == EINTR) {} if (shell_exit_status(status) != 0) { // failure delete cserver; cserver = 0; return shell_exit_status(status); } } else { int cpp_fd = open(preproc_file, O_RDONLY); if (cpp_fd < 0) { throw(11); } log_block cpp_block("write_server_cpp"); write_server_cpp(cpp_fd, cserver); } if (!cserver->send_msg(EndMsg())) { log_info() << "write of end failed" << endl; throw(12); } Msg *msg; { log_block wait_cs("wait for cs"); msg = cserver->get_msg(12 * 60); if (!msg) { throw(14); } } check_for_failure(msg, cserver); if (msg->type != M_COMPILE_RESULT) { log_warning() << "waited for compile result, but got " << (char)msg->type << endl; delete msg; throw(13); } CompileResultMsg *crmsg = dynamic_cast<CompileResultMsg*>(msg); assert(crmsg); status = crmsg->status; if (status && crmsg->was_out_of_memory) { delete crmsg; log_info() << "the server ran out of memory, recompiling locally" << endl; throw(101); } if (output) { if ((!crmsg->out.empty() || !crmsg->err.empty()) && output_needs_workaround(job)) { delete crmsg; log_info() << "command needs stdout/stderr workaround, recompiling locally" << endl; throw(102); } write(STDOUT_FILENO, crmsg->out.c_str(), crmsg->out.size()); if (colorify_wanted(job)) { colorify_output(crmsg->err); } else { write(STDERR_FILENO, crmsg->err.c_str(), crmsg->err.size()); } if (status && (crmsg->err.length() || crmsg->out.length())) { log_error() << "Compiled on " << hostname << endl; } } delete crmsg; assert(!job.outputFile().empty()); if (status == 0) { receive_file(job.outputFile(), cserver); } } catch (int x) { // Handle pending status messages, if any. if(cserver) { while(Msg* msg = cserver->get_msg(0)) { if(msg->type == M_STATUS_TEXT) log_error() << "Remote status (compiled on " << cserver->name << "): " << static_cast<StatusTextMsg*>(msg)->text << endl; delete msg; } delete cserver; cserver = 0; } throw(x); } delete cserver; return status; }