Пример #1
0
/**
 * Replace this program with another in the same process.
 *
 * Does not return, either execs the compiler in place, or exits with
 * a message.
 **/
static void dcc_execvp(char **argv)
{
    char *slash;

    execvp(argv[0], argv);

    /* If we're still running, the program was not found on the path.  One
     * thing that might have happened here is that the client sent an absolute
     * compiler path, but the compiler's located somewhere else on the server.
     * In the absence of anything better to do, we search the path for its
     * basename.
     *
     * Actually this code is called on both the client and server, which might
     * cause unintnded behaviour in contrived cases, like giving a full path
     * to a file that doesn't exist.  I don't think that's a problem. */

    slash = strrchr(argv[0], '/');
    if (slash)
        execvp(slash + 1, argv);

    /* shouldn't be reached */
    rs_log_error("failed to exec %s: %s", argv[0], strerror(errno));

    dcc_exit(EXIT_COMPILER_MISSING); /* a generalization, i know */
}
Пример #2
0
/**
 * Main loop for the parent process with the new preforked implementation.
 * The parent is just responsible for keeping a pool of children and they
 * accept connections themselves.
 **/
int dcc_preforking_parent(int listen_fd)
{
    while (1) {
        pid_t kid;

        while (dcc_nkids < dcc_max_kids) {
            if ((kid = fork()) == -1) {
                rs_log_error("fork failed: %s", strerror(errno));
                return EXIT_OUT_OF_MEMORY; /* probably */
            } else if (kid == 0) {
                dcc_exit(dcc_preforked_child(listen_fd));
            } else {
                /* in parent */
                ++dcc_nkids;
                rs_trace("up to %d children", dcc_nkids);
            }

            /* Don't start them too quickly, or we might overwhelm a machine
             * that's having trouble. */
            sleep(1);

            dcc_reap_kids(FALSE);
        }

        /* wait for any children to exit, and then start some more */
        dcc_reap_kids(TRUE);

        /* Another little safety brake here: since children should not exit
         * too quickly, pausing before starting them should be harmless. */
        sleep(1);
    }
}
Пример #3
0
/**
 * Fork a child to repeatedly accept and handle incoming connections.
 *
 * To protect against leaks, we quit after 50 requests and let the parent
 * recreate us.
 **/
static int dcc_preforked_child(int listen_fd)
{
    int ireq;
    const int child_lifetime = 50;

    for (ireq = 0; ireq < child_lifetime; ireq++) {
        int acc_fd;
        struct dcc_sockaddr_storage cli_addr;
        socklen_t cli_len;

        cli_len = sizeof cli_addr;

        do {
            acc_fd = accept(listen_fd, (struct sockaddr *) &cli_addr,
                            &cli_len);
        } while (acc_fd == -1 && errno == EINTR);

        if (acc_fd == -1) {
            rs_log_error("accept failed: %s", strerror(errno));
            dcc_exit(EXIT_CONNECT_FAILED);
        }

        dcc_service_job(acc_fd, acc_fd,
                        (struct sockaddr *) &cli_addr, cli_len);

        dcc_close(acc_fd);
    }

    rs_log_info("worn out");

    return 0;
}
Пример #4
0
/**
 * Main loop for no-fork mode.
 *
 * Much slower and may leak.  Should only be used when you want to run gdb on
 * distccd.
 **/
static void dcc_nofork_parent(int listen_fd)
{
    while (1) {
        int acc_fd;
        struct dcc_sockaddr_storage cli_addr;
        socklen_t cli_len;

        rs_log_info("waiting to accept connection");

        cli_len = sizeof cli_addr;
        acc_fd = accept(listen_fd,
                        (struct sockaddr *) &cli_addr, &cli_len);
        if (acc_fd == -1 && errno == EINTR) {
            ;
        }  else if (acc_fd == -1) {
            rs_log_error("accept failed: %s", strerror(errno));

#ifdef HAVE_GSSAPI
            if (dcc_auth_enabled) {
                dcc_gssapi_release_credentials();

                if (opt_blacklist_enabled || opt_whitelist_enabled) {
                    dcc_gssapi_free_list();
	            }
            }
#endif
            dcc_exit(EXIT_CONNECT_FAILED);
        } else {
            dcc_service_job(acc_fd, acc_fd, (struct sockaddr *) &cli_addr, cli_len);
            dcc_close(acc_fd);
        }
    }
}
Пример #5
0
/**
 * Called inside the newly-spawned child process to execute a command.
 * Either executes it, or returns an appropriate error.
 *
 * This routine also takes a lock on localhost so that it's counted
 * against the process load.  That lock will go away when the process
 * exits.
 *
 * In this current version locks are taken without regard to load limitation
 * on the current machine.  The main impact of this is that cpp running on
 * localhost will cause jobs to be preferentially distributed away from
 * localhost, but it should never cause the machine to deadlock waiting for
 * localhost slots.
 *
 * @param what Type of process to be run here (cpp, cc, ...)
 **/
static void dcc_inside_child(char **argv,
                             const char *stdin_file,
                             const char *stdout_file,
                             const char *stderr_file)
{
    int ret;

    if ((ret = dcc_ignore_sigpipe(0)))
        goto fail;              /* set handler back to default */

    /* Ignore failure */
    dcc_increment_safeguard();

#ifdef __CYGWIN__
    /* This will execute compiler and CORRECTLY redirect output if compiler is
     * a native Windows application.  If this never returns, it means the
     * compiler-execute succeeded.  We use a hack to decide if it's a windows
     * application: if argv[0] starts with "<letter>:" or with "\\", then it's
     * a windows path and we try dcc_execvp_cyg.  If not, we assume it's a
     * cygwin app and fall through to the unix-style forking, below.  If we
     * guess wrong, dcc_execvp_cyg will probably fail with error 3
     * (windows-exe for "path not found"), so again we'll fall through to the
     * unix-fork case.  Otherwise we just fail in a generic way.
     * TODO(csilvers): Figure out the right way to deal with this.  Running
     *                 cygwin apps via dcc_execvp_cyg segfaults (and takes a
     *                 long time to do it too), so I want to avoid that if
     *                 possible.  I don't know enough about cygwin or
     *                 cygwin/windows interactions to know the right thing to
     *                 do here.  Until distcc has cl.exe support, this may
     *                 all be a moot point anyway.
     */
    if (argv[0] && ((argv[0][0] != '\0' && argv[0][1] == ':') ||
                    (argv[0][0] == '\\' && argv[0][1] == '\\'))) {
        DWORD status;
        status = dcc_execvp_cyg(argv, stdin_file, stdout_file, stderr_file);
        if (status != 3) {
            ret = EXIT_DISTCC_FAILED;
            goto fail;
        }
    }
#endif

    /* do this last, so that any errors from previous operations are
     * visible */
    if ((ret = dcc_redirect_fds(stdin_file, stdout_file, stderr_file)))
        goto fail;

    dcc_execvp(argv);

    ret = EXIT_DISTCC_FAILED;

    fail:
    dcc_exit(ret);
}
Пример #6
0
/**
 * @sa dcc_wait_child(), which is used by a process that wants to do a blocking
 * wait for some task like cpp or gcc.
 *
 * @param must_reap If True, don't return until at least one child has been
 * collected.  Used when e.g. all our process slots are full.  In either case
 * we keep going until all outstanding zombies are collected.
 *
 * FIXME: Are blocking waits meant to collect all of them, or just one?  At
 * the moment it waits until all children have exited.
 **/
void dcc_reap_kids(int must_reap)
{
    while (1) {
        int status;
        pid_t kid;

        kid = waitpid(WAIT_ANY, &status, must_reap ? 0 : WNOHANG);
        if (kid == 0) {
            /* nobody has exited */
            break;
        } else if (kid != -1) {
            /* child exited */
            --dcc_nkids;
            rs_trace("down to %d children", dcc_nkids);

            dcc_log_child_exited(kid, status);
        } else if (errno == ECHILD) {
            /* No children left?  That's ok, we'll go back to waiting
             * for new connections. */
            break;
        } else if (errno == EINTR) {
            /* If we got a SIGTERM or something, then on the next pass
             * through the loop we'll find no children done, and we'll
             * return to the top loop at which point we'll exit.  So
             * no special action is required here. */
            continue;       /* loop again */
        } else {
            rs_log_error("wait failed: %s", strerror(errno));
            /* e.g. too many open files; nothing we can do */
            dcc_exit(EXIT_DISTCC_FAILED);
        }

        /* If there are more children keep looking, but don't block once we've
         * collected at least one. */
        must_reap = FALSE;
    }
}
Пример #7
0
/**
 * distcc daemon.  May run from inetd, or standalone.  Accepts
 * requests from clients to compile files.
 **/
int main(int argc, char *argv[])
{
    int ret;
    const char *tmp;

    dcc_setup_startup_log();

    if (distccd_parse_options(argc, (const char **) argv))
        dcc_exit(EXIT_DISTCC_FAILED);

    /* check this before redirecting the logs, so that it's really obvious */
    if (!dcc_should_be_inetd())
        if (opt_allowed == NULL) {
            rs_log_error("--allow option is now mandatory; "
                         "you must specify which clients are allowed to connect");
            ret = EXIT_BAD_ARGUMENTS;
            goto out;
        }

    if ((ret = dcc_set_lifetime()) != 0)
        dcc_exit(ret);

    /* do this before giving away root */
    if (nice(opt_niceness) == -1) {
        rs_log_warning("nice %d failed: %s", opt_niceness,
                       strerror(errno));
        /* continue anyhow */
    }

    if ((ret = dcc_discard_root()) != 0)
        dcc_exit(ret);

    /* Discard privileges before opening log so that if it's created, it has
     * the right ownership. */
    dcc_setup_real_log();

    /* Do everything from root directory.  Allows start directory to be
     * unmounted, should make accidental writing of local files cause a
     * failure... */
    if ((ret = dcc_get_tmp_top(&tmp)))
        goto out;

    if (chdir(tmp) == -1) {
        rs_log_error("failed to chdir to %s: %s", tmp, strerror(errno));
        ret = EXIT_IO_ERROR;
        goto out;
    } else {
        rs_trace("chdir to %s", tmp);
    }

    if ((ret = dcc_setup_daemon_path()))
        goto out;

    if (dcc_should_be_inetd())
        ret = dcc_inetd_server();
    else
        ret = dcc_standalone_server();

    out:
    dcc_exit(ret);
}