Example #1
0
/*
 * 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());
}
Example #2
0
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;
}
Example #3
0
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;
}
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;
            }
        }
    }
}
Example #5
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;
}
Example #6
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;
}
Example #7
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;
}