Example #1
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 #2
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 #3
0
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);
}
Example #4
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 #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);

    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 #6
0
bool compiler_is_clang( const CompileJob& job )
{
    return job.compilerName().find("clang") != string::npos;
}
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;
}
Example #8
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 );
#ifndef HAVE_LIBCAP_NG
        // Safety check
        if (getuid() == 0 || getgid() == 0) {
            error_client( client, "UID is 0 - aborting." );
            _exit(142);
        }
#endif


#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
        argc += 1; // -no-canonical-prefixes
        char **argv = new char*[argc + 1];
	int i = 0;
        bool clang = false;
        if (IS_PROTOCOL_30( client )) {
            assert(!j.compilerName().empty());
            clang = (j.compilerName().find( "clang" ) != string::npos);
            argv[i++] = strdup(( "usr/bin/" + j.compilerName()).c_str());
	} else {
            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);
        }

        argv[i++] = strdup("-x");
        argv[i++] = strdup((j.language() == CompileJob::Lang_CXX) ? "c++" : "c");

        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() );
        }
        if (!clang)
            argv[i++] = strdup("-fpreprocessed");
        if(!hasPipe)
           argv[i++] = strdup("-pipe");
        argv[i++] = strdup( "-" );
        argv[i++] = strdup( "-o" );
        argv[i++] = strdup(outfilename.c_str());
        if (!clang) {
            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 );
        }
        if (clang)
            argv[i++] = strdup( "-no-canonical-prefixes" ); // otherwise clang tries to access /proc/self/exe
        // 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;
                    client_fd = -1;
                    input_complete = true;
                    delete fcmsg;
                    fcmsg = 0;
                    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 ( shell_exit_status(status) != 0 ) {
                    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 = shell_exit_status(status);
                    job_stat[JobStatistics::exit_code] = shell_exit_status(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;
            }
        }
    }
}