예제 #1
0
파일: remote.cpp 프로젝트: bozaro/icecream
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;
}
예제 #2
0
파일: local.cpp 프로젝트: abael/icecream
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;
}
예제 #3
0
파일: local.cpp 프로젝트: CMon/icecream
/*
 * 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());
}
예제 #4
0
/*
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;
}
예제 #5
0
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;
}
예제 #6
0
파일: remote.cpp 프로젝트: liangqi/icecream
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;
}
예제 #7
0
파일: remote.cpp 프로젝트: liangqi/icecream
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;
            }
        }
    }
}
예제 #9
0
파일: main.cpp 프로젝트: bozaro/icecream
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);
}
예제 #10
0
파일: local.cpp 프로젝트: abael/icecream
/**
 * 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;
}
예제 #11
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;
}
예제 #12
0
파일: local.cpp 프로젝트: CMon/icecream
bool compiler_is_clang( const CompileJob& job )
{
    return job.compilerName().find("clang") != string::npos;
}
예제 #13
0
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;
}
예제 #14
0
파일: remote.cpp 프로젝트: bozaro/icecream
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;
}