#include "current-process.hh" #include "environment-variables.hh" #include "finally.hh" #include "logging.hh" #include "processes.hh" #include "serialise.hh" #include "signals.hh" #include #include #include #include #include #include #include #include #include #include #ifdef __APPLE__ # include #endif #ifdef __linux__ # include # include #endif namespace nix { Pid::Pid() { } Pid::Pid(pid_t pid) : pid(pid) { } Pid::~Pid() noexcept(false) { if (pid != -1) kill(); } void Pid::operator =(pid_t pid) { if (this->pid != -1 && this->pid != pid) kill(); this->pid = pid; killSignal = SIGKILL; // reset signal to default } int Pid::kill() { assert(pid != -1); debug("killing process %1%", pid); /* Send the requested signal to the child. If it has its own process group, send the signal to every process in the child process group (which hopefully includes *all* its children). */ if (::kill(separatePG ? -pid : pid, killSignal) != 0) { /* On BSDs, killing a process group will return EPERM if all processes in the group are zombies (or something like that). So try to detect and ignore that situation. */ #if __FreeBSD__ || __APPLE__ if (errno != EPERM || ::kill(pid, 0) != 0) #endif logError(SysError("killing process %d", pid).info()); } return wait(); } int Pid::wait() { assert(pid != -1); while (1) { int status; int res = waitpid(pid, &status, 0); if (res == pid) { pid = -1; return status; } if (errno != EINTR) throw SysError("cannot get exit status of PID %d", pid); checkInterrupt(); } } void Pid::setSeparatePG(bool separatePG) { this->separatePG = separatePG; } void Pid::setKillSignal(int signal) { this->killSignal = signal; } pid_t Pid::release() { pid_t p = pid; pid = -1; return p; } void killUser(uid_t uid) { debug("killing all processes running under uid '%1%'", uid); assert(uid != 0); /* just to be safe... */ /* The system call kill(-1, sig) sends the signal `sig' to all users to which the current process can send signals. So we fork a process, switch to uid, and send a mass kill. */ Pid pid = startProcess([&]() { if (setuid(uid) == -1) throw SysError("setting uid"); while (true) { #ifdef __APPLE__ /* OSX's kill syscall takes a third parameter that, among other things, determines if kill(-1, signo) affects the calling process. In the OSX libc, it's set to true, which means "follow POSIX", which we don't want here */ if (syscall(SYS_kill, -1, SIGKILL, false) == 0) break; #else if (kill(-1, SIGKILL) == 0) break; #endif if (errno == ESRCH || errno == EPERM) break; /* no more processes */ if (errno != EINTR) throw SysError("cannot kill processes for uid '%1%'", uid); } _exit(0); }); int status = pid.wait(); if (status != 0) throw Error("cannot kill processes for uid '%1%': %2%", uid, statusToString(status)); /* !!! We should really do some check to make sure that there are no processes left running under `uid', but there is no portable way to do so (I think). The most reliable way may be `ps -eo uid | grep -q $uid'. */ } ////////////////////////////////////////////////////////////////////// static pid_t doFork(std::function fun) { pid_t pid = fork(); if (pid != 0) return pid; fun(); abort(); } #if __linux__ static int childEntry(void * arg) { auto main = (std::function *) arg; (*main)(); return 1; } #endif pid_t startProcess(std::function fun, const ProcessOptions & options) { std::function wrapper = [&]() { logger = makeSimpleLogger(); try { #if __linux__ if (options.dieWithParent && prctl(PR_SET_PDEATHSIG, SIGKILL) == -1) throw SysError("setting death signal"); #endif fun(); } catch (std::exception & e) { try { std::cerr << options.errorPrefix << e.what() << "\n"; } catch (...) { } } catch (...) { } if (options.runExitHandlers) exit(1); else _exit(1); }; pid_t pid = -1; if (options.cloneFlags) { #ifdef __linux__ // Not supported, since then we don't know when to free the stack. assert(!(options.cloneFlags & CLONE_VM)); size_t stackSize = 1 * 1024 * 1024; auto stack = (char *) mmap(0, stackSize, PROT_WRITE | PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0); if (stack == MAP_FAILED) throw SysError("allocating stack"); Finally freeStack([&]() { munmap(stack, stackSize); }); pid = clone(childEntry, stack + stackSize, options.cloneFlags | SIGCHLD, &wrapper); #else throw Error("clone flags are only supported on Linux"); #endif } else pid = doFork(wrapper); if (pid == -1) throw SysError("unable to fork"); return pid; } std::string runProgram(Path program, bool searchPath, const Strings & args, const std::optional & input, bool isInteractive) { auto res = runProgram(RunOptions {.program = program, .searchPath = searchPath, .args = args, .input = input, .isInteractive = isInteractive}); if (!statusOk(res.first)) throw ExecError(res.first, "program '%1%' %2%", program, statusToString(res.first)); return res.second; } // Output = error code + "standard out" output stream std::pair runProgram(RunOptions && options) { StringSink sink; options.standardOut = &sink; int status = 0; try { runProgram2(options); } catch (ExecError & e) { status = e.status; } return {status, std::move(sink.s)}; } void runProgram2(const RunOptions & options) { checkInterrupt(); assert(!(options.standardIn && options.input)); std::unique_ptr source_; Source * source = options.standardIn; if (options.input) { source_ = std::make_unique(*options.input); source = source_.get(); } /* Create a pipe. */ Pipe out, in; if (options.standardOut) out.create(); if (source) in.create(); ProcessOptions processOptions; std::optional>> resumeLoggerDefer; if (options.isInteractive) { logger->pause(); resumeLoggerDefer.emplace( []() { logger->resume(); } ); } /* Fork. */ Pid pid = startProcess([&]() { if (options.environment) replaceEnv(*options.environment); if (options.standardOut && dup2(out.writeSide.get(), STDOUT_FILENO) == -1) throw SysError("dupping stdout"); if (options.mergeStderrToStdout) if (dup2(STDOUT_FILENO, STDERR_FILENO) == -1) throw SysError("cannot dup stdout into stderr"); if (source && dup2(in.readSide.get(), STDIN_FILENO) == -1) throw SysError("dupping stdin"); if (options.chdir && chdir((*options.chdir).c_str()) == -1) throw SysError("chdir failed"); if (options.gid && setgid(*options.gid) == -1) throw SysError("setgid failed"); /* Drop all other groups if we're setgid. */ if (options.gid && setgroups(0, 0) == -1) throw SysError("setgroups failed"); if (options.uid && setuid(*options.uid) == -1) throw SysError("setuid failed"); Strings args_(options.args); args_.push_front(options.program); restoreProcessContext(); if (options.searchPath) execvp(options.program.c_str(), stringsToCharPtrs(args_).data()); // This allows you to refer to a program with a pathname relative // to the PATH variable. else execv(options.program.c_str(), stringsToCharPtrs(args_).data()); throw SysError("executing '%1%'", options.program); }, processOptions); out.writeSide.close(); std::thread writerThread; std::promise promise; Finally doJoin([&]() { if (writerThread.joinable()) writerThread.join(); }); if (source) { in.readSide.close(); writerThread = std::thread([&]() { try { std::vector buf(8 * 1024); while (true) { size_t n; try { n = source->read(buf.data(), buf.size()); } catch (EndOfFile &) { break; } writeFull(in.writeSide.get(), {buf.data(), n}); } promise.set_value(); } catch (...) { promise.set_exception(std::current_exception()); } in.writeSide.close(); }); } if (options.standardOut) drainFD(out.readSide.get(), *options.standardOut); /* Wait for the child to finish. */ int status = pid.wait(); /* Wait for the writer thread to finish. */ if (source) promise.get_future().get(); if (status) throw ExecError(status, "program '%1%' %2%", options.program, statusToString(status)); } std::string statusToString(int status) { if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { if (WIFEXITED(status)) return fmt("failed with exit code %1%", WEXITSTATUS(status)); else if (WIFSIGNALED(status)) { int sig = WTERMSIG(status); #if HAVE_STRSIGNAL const char * description = strsignal(sig); return fmt("failed due to signal %1% (%2%)", sig, description); #else return fmt("failed due to signal %1%", sig); #endif } else return "died abnormally"; } else return "succeeded"; } bool statusOk(int status) { return WIFEXITED(status) && WEXITSTATUS(status) == 0; } }