/** * 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 */ }
/** * 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); } }
/** * 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; }
/** * 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); } } }
/** * 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); }
/** * @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; } }
/** * 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); }