diff options
author | Tom Hubrecht <github@mail.hubrecht.ovh> | 2024-05-28 13:14:13 +0200 |
---|---|---|
committer | Tom Hubrecht <github@mail.hubrecht.ovh> | 2024-05-29 11:01:34 +0200 |
commit | 9a52e4688ca265155817817f373938428f023966 (patch) | |
tree | b90975deb9def6b8cb7c3ffe2510b7318df37d93 | |
parent | 8cd9aa24a8d48caf22225ce32dff3c5aaa111687 (diff) |
util.{hh,cc}: Split out processes.{hh,cc}
Change-Id: I39280dc40ca3f7f9007bc6c898ffcf760e2238b7
-rw-r--r-- | src/libexpr/primops.cc | 2 | ||||
-rw-r--r-- | src/libfetchers/git.cc | 1 | ||||
-rw-r--r-- | src/libfetchers/mercurial.cc | 3 | ||||
-rw-r--r-- | src/libmain/shared.hh | 1 | ||||
-rw-r--r-- | src/libstore/build/hook-instance.hh | 1 | ||||
-rw-r--r-- | src/libstore/build/local-derivation-goal.hh | 1 | ||||
-rw-r--r-- | src/libstore/gc.cc | 1 | ||||
-rw-r--r-- | src/libstore/globals.cc | 1 | ||||
-rw-r--r-- | src/libstore/ssh.cc | 1 | ||||
-rw-r--r-- | src/libstore/ssh.hh | 2 | ||||
-rw-r--r-- | src/libutil/meson.build | 2 | ||||
-rw-r--r-- | src/libutil/namespaces.cc | 5 | ||||
-rw-r--r-- | src/libutil/processes.cc | 404 | ||||
-rw-r--r-- | src/libutil/processes.hh | 115 | ||||
-rw-r--r-- | src/libutil/util.cc | 376 | ||||
-rw-r--r-- | src/libutil/util.hh | 91 | ||||
-rw-r--r-- | src/nix/upgrade-nix.cc | 1 | ||||
-rw-r--r-- | tests/functional/repl_characterization/test-session.cc | 1 | ||||
-rw-r--r-- | tests/unit/libutil/tests.cc | 2 |
19 files changed, 539 insertions, 472 deletions
diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index 64a52dfd6..f8ce90ac1 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -9,8 +9,8 @@ #include "json-to-value.hh" #include "names.hh" #include "path-references.hh" +#include "processes.hh" #include "store-api.hh" -#include "util.hh" #include "value-to-json.hh" #include "value-to-xml.hh" #include "primops.hh" diff --git a/src/libfetchers/git.cc b/src/libfetchers/git.cc index 4d4bf6cd6..86d9d437f 100644 --- a/src/libfetchers/git.cc +++ b/src/libfetchers/git.cc @@ -1,6 +1,7 @@ #include "fetchers.hh" #include "cache.hh" #include "globals.hh" +#include "processes.hh" #include "tarfile.hh" #include "store-api.hh" #include "url-parts.hh" diff --git a/src/libfetchers/mercurial.cc b/src/libfetchers/mercurial.cc index 87fecfca3..086db498f 100644 --- a/src/libfetchers/mercurial.cc +++ b/src/libfetchers/mercurial.cc @@ -1,7 +1,6 @@ #include "fetchers.hh" #include "cache.hh" -#include "globals.hh" -#include "tarfile.hh" +#include "processes.hh" #include "store-api.hh" #include "url-parts.hh" diff --git a/src/libmain/shared.hh b/src/libmain/shared.hh index a7810f77c..907f336f1 100644 --- a/src/libmain/shared.hh +++ b/src/libmain/shared.hh @@ -7,6 +7,7 @@ #include "common-args.hh" #include "path.hh" #include "derived-path.hh" +#include "processes.hh" #include "exit.hh" #include <signal.h> diff --git a/src/libstore/build/hook-instance.hh b/src/libstore/build/hook-instance.hh index d84f62877..481158296 100644 --- a/src/libstore/build/hook-instance.hh +++ b/src/libstore/build/hook-instance.hh @@ -2,6 +2,7 @@ ///@file #include "logging.hh" +#include "processes.hh" #include "serialise.hh" namespace nix { diff --git a/src/libstore/build/local-derivation-goal.hh b/src/libstore/build/local-derivation-goal.hh index b7f317fb6..f3a83d42f 100644 --- a/src/libstore/build/local-derivation-goal.hh +++ b/src/libstore/build/local-derivation-goal.hh @@ -3,6 +3,7 @@ #include "derivation-goal.hh" #include "local-store.hh" +#include "processes.hh" namespace nix { diff --git a/src/libstore/gc.cc b/src/libstore/gc.cc index 6b37f0af3..60d88f3b2 100644 --- a/src/libstore/gc.cc +++ b/src/libstore/gc.cc @@ -1,6 +1,7 @@ #include "derivations.hh" #include "globals.hh" #include "local-store.hh" +#include "processes.hh" #include "signals.hh" #include "finally.hh" diff --git a/src/libstore/globals.cc b/src/libstore/globals.cc index 897d3e8ef..f14e6c91f 100644 --- a/src/libstore/globals.cc +++ b/src/libstore/globals.cc @@ -26,6 +26,7 @@ #include "config-impl.hh" #ifdef __APPLE__ +#include "processes.hh" #include <curl/curl.h> #include <sys/sysctl.h> #endif diff --git a/src/libstore/ssh.cc b/src/libstore/ssh.cc index 2f921e2d0..1d8266b9d 100644 --- a/src/libstore/ssh.cc +++ b/src/libstore/ssh.cc @@ -1,6 +1,7 @@ #include "environment-variables.hh" #include "ssh.hh" #include "finally.hh" +#include "util.hh" namespace nix { diff --git a/src/libstore/ssh.hh b/src/libstore/ssh.hh index 0802c6cc0..f9c532caa 100644 --- a/src/libstore/ssh.hh +++ b/src/libstore/ssh.hh @@ -2,7 +2,7 @@ ///@file #include "file-system.hh" -#include "util.hh" +#include "processes.hh" #include "sync.hh" namespace nix { diff --git a/src/libutil/meson.build b/src/libutil/meson.build index a4813d9a3..34715b25e 100644 --- a/src/libutil/meson.build +++ b/src/libutil/meson.build @@ -23,6 +23,7 @@ libutil_sources = files( 'namespaces.cc', 'position.cc', 'print-elided.cc', + 'processes.cc', 'references.cc', 'regex.cc', 'serialise.cc', @@ -81,6 +82,7 @@ libutil_headers = files( 'pool.hh', 'position.hh', 'print-elided.hh', + 'processes.hh', 'ref.hh', 'references.hh', 'regex-combinators.hh', diff --git a/src/libutil/namespaces.cc b/src/libutil/namespaces.cc index 4b539f342..cde442671 100644 --- a/src/libutil/namespaces.cc +++ b/src/libutil/namespaces.cc @@ -1,9 +1,10 @@ #if __linux__ #include "file-system.hh" -#include "namespaces.hh" +#include "logging.hh" #include "util.hh" -#include "finally.hh" +#include "namespaces.hh" +#include "processes.hh" #include <sys/mount.h> diff --git a/src/libutil/processes.cc b/src/libutil/processes.cc new file mode 100644 index 000000000..8d1b119b0 --- /dev/null +++ b/src/libutil/processes.cc @@ -0,0 +1,404 @@ +#include "environment-variables.hh" +#include "finally.hh" +#include "logging.hh" +#include "processes.hh" +#include "serialise.hh" +#include "signals.hh" + +#include <cerrno> +#include <cstdlib> +#include <cstring> +#include <future> +#include <iostream> +#include <thread> + +#include <grp.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> + +#ifdef __APPLE__ +# include <sys/syscall.h> +#endif + +#ifdef __linux__ +# include <sys/prctl.h> +# include <sys/mman.h> +#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 +} + + +Pid::operator pid_t() +{ + return pid; +} + + +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<void()> fun) +{ + pid_t pid = fork(); + if (pid != 0) return pid; + fun(); + abort(); +} + +#if __linux__ +static int childEntry(void * arg) +{ + auto main = (std::function<void()> *) arg; + (*main)(); + return 1; +} +#endif + + +pid_t startProcess(std::function<void()> fun, const ProcessOptions & options) +{ + std::function<void()> 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<std::string> & 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<int, std::string> 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 * source = options.standardIn; + + if (options.input) { + source_ = std::make_unique<StringSource>(*options.input); + source = source_.get(); + } + + /* Create a pipe. */ + Pipe out, in; + if (options.standardOut) out.create(); + if (source) in.create(); + + ProcessOptions processOptions; + + std::optional<Finally<std::function<void()>>> 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<void> promise; + + Finally doJoin([&]() { + if (writerThread.joinable()) + writerThread.join(); + }); + + + if (source) { + in.readSide.close(); + writerThread = std::thread([&]() { + try { + std::vector<char> 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; +} + +} diff --git a/src/libutil/processes.hh b/src/libutil/processes.hh new file mode 100644 index 000000000..91a4edfd2 --- /dev/null +++ b/src/libutil/processes.hh @@ -0,0 +1,115 @@ +#pragma once +///@file + +#include "types.hh" +#include "error.hh" + +#include <sys/types.h> +#include <sys/stat.h> +#include <dirent.h> +#include <unistd.h> +#include <signal.h> + +#include <boost/lexical_cast.hpp> + +#include <functional> +#include <map> +#include <optional> + +namespace nix { + +struct Sink; +struct Source; + +class Pid +{ + pid_t pid = -1; + bool separatePG = false; + int killSignal = SIGKILL; +public: + Pid(); + Pid(pid_t pid); + ~Pid() noexcept(false); + void operator =(pid_t pid); + operator pid_t(); + int kill(); + int wait(); + + void setSeparatePG(bool separatePG); + void setKillSignal(int signal); + pid_t release(); +}; + +/** + * Kill all processes running under the specified uid by sending them + * a SIGKILL. + */ +void killUser(uid_t uid); + + +/** + * Fork a process that runs the given function, and return the child + * pid to the caller. + */ +struct ProcessOptions +{ + std::string errorPrefix = ""; + bool dieWithParent = true; + bool runExitHandlers = false; + /** + * use clone() with the specified flags (Linux only) + */ + int cloneFlags = 0; +}; + +pid_t startProcess(std::function<void()> fun, const ProcessOptions & options = ProcessOptions()); + + +/** + * Run a program and return its stdout in a string (i.e., like the + * shell backtick operator). + */ +std::string runProgram(Path program, bool searchPath = false, + const Strings & args = Strings(), + const std::optional<std::string> & input = {}, bool isInteractive = false); + +struct RunOptions +{ + Path program; + bool searchPath = true; + Strings args; + std::optional<uid_t> uid; + std::optional<uid_t> gid; + std::optional<Path> chdir; + std::optional<std::map<std::string, std::string>> environment; + std::optional<std::string> input; + Source * standardIn = nullptr; + Sink * standardOut = nullptr; + bool mergeStderrToStdout = false; + bool isInteractive = false; +}; + +std::pair<int, std::string> runProgram(RunOptions && options); + +void runProgram2(const RunOptions & options); + +class ExecError : public Error +{ +public: + int status; + + template<typename... Args> + ExecError(int status, const Args & ... args) + : Error(args...), status(status) + { } +}; + +/** + * Convert the exit status of a child as returned by wait() into an + * error string. + */ +std::string statusToString(int status); + +bool statusOk(int status); + +} diff --git a/src/libutil/util.cc b/src/libutil/util.cc index 55f3154c9..702db2afb 100644 --- a/src/libutil/util.cc +++ b/src/libutil/util.cc @@ -1,4 +1,6 @@ #include "util.hh" +#include "processes.hh" + #include "sync.hh" #include "finally.hh" #include "serialise.hh" @@ -208,211 +210,6 @@ unsigned int getMaxCPU() ////////////////////////////////////////////////////////////////////// -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 -} - - -Pid::operator pid_t() -{ - return pid; -} - - -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<void()> fun) -{ - pid_t pid = fork(); - if (pid != 0) return pid; - fun(); - abort(); -} - - -#if __linux__ -static int childEntry(void * arg) -{ - auto main = (std::function<void()> *) arg; - (*main)(); - return 1; -} -#endif - - -pid_t startProcess(std::function<void()> fun, const ProcessOptions & options) -{ - std::function<void()> 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::vector<char *> stringsToCharPtrs(const Strings & ss) { @@ -422,149 +219,6 @@ std::vector<char *> stringsToCharPtrs(const Strings & ss) return res; } -std::string runProgram(Path program, bool searchPath, const Strings & args, - const std::optional<std::string> & 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<int, std::string> 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 * source = options.standardIn; - - if (options.input) { - source_ = std::make_unique<StringSource>(*options.input); - source = source_.get(); - } - - /* Create a pipe. */ - Pipe out, in; - if (options.standardOut) out.create(); - if (source) in.create(); - - ProcessOptions processOptions; - - std::optional<Finally<std::function<void()>>> 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<void> promise; - - Finally doJoin([&]() { - if (writerThread.joinable()) - writerThread.join(); - }); - - - if (source) { - in.readSide.close(); - writerThread = std::thread([&]() { - try { - std::vector<char> 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)); -} - ////////////////////////////////////////////////////////////////////// @@ -648,32 +302,6 @@ std::string Rewriter::operator()(std::string s) } -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; -} - - std::string toLower(const std::string & s) { std::string r(s); diff --git a/src/libutil/util.hh b/src/libutil/util.hh index d0e207146..8d1900131 100644 --- a/src/libutil/util.hh +++ b/src/libutil/util.hh @@ -97,78 +97,6 @@ Path createNixStateDir(); */ unsigned int getMaxCPU(); -class Pid -{ - pid_t pid = -1; - bool separatePG = false; - int killSignal = SIGKILL; -public: - Pid(); - Pid(pid_t pid); - ~Pid() noexcept(false); - void operator =(pid_t pid); - operator pid_t(); - int kill(); - int wait(); - - void setSeparatePG(bool separatePG); - void setKillSignal(int signal); - pid_t release(); -}; - -/** - * Kill all processes running under the specified uid by sending them - * a SIGKILL. - */ -void killUser(uid_t uid); - - -/** - * Fork a process that runs the given function, and return the child - * pid to the caller. - */ -struct ProcessOptions -{ - std::string errorPrefix = ""; - bool dieWithParent = true; - bool runExitHandlers = false; - /** - * use clone() with the specified flags (Linux only) - */ - int cloneFlags = 0; -}; - -pid_t startProcess(std::function<void()> fun, const ProcessOptions & options = ProcessOptions()); - - -/** - * Run a program and return its stdout in a string (i.e., like the - * shell backtick operator). - */ -std::string runProgram(Path program, bool searchPath = false, - const Strings & args = Strings(), - const std::optional<std::string> & input = {}, bool isInteractive = false); - -struct RunOptions -{ - Path program; - bool searchPath = true; - Strings args; - std::optional<uid_t> uid; - std::optional<uid_t> gid; - std::optional<Path> chdir; - std::optional<std::map<std::string, std::string>> environment; - std::optional<std::string> input; - Source * standardIn = nullptr; - Sink * standardOut = nullptr; - bool mergeStderrToStdout = false; - bool isInteractive = false; -}; - -std::pair<int, std::string> runProgram(RunOptions && options); - -void runProgram2(const RunOptions & options); - /** * Change the stack size. @@ -204,17 +132,6 @@ void restoreMountNamespace(); void unshareFilesystem(); -class ExecError : public Error -{ -public: - int status; - - template<typename... Args> - ExecError(int status, const Args & ... args) - : Error(args...), status(status) - { } -}; - /** * Convert a list of strings to a null-terminated vector of `char * *`s. The result must not be accessed beyond the lifetime of the @@ -323,14 +240,6 @@ inline std::string rewriteStrings(std::string s, const StringMap & rewrites) } -/** - * Convert the exit status of a child as returned by wait() into an - * error string. - */ -std::string statusToString(int status); - -bool statusOk(int status); - /** * Parse a string into an integer. diff --git a/src/nix/upgrade-nix.cc b/src/nix/upgrade-nix.cc index 3e47fc8f0..cbc28fdd7 100644 --- a/src/nix/upgrade-nix.cc +++ b/src/nix/upgrade-nix.cc @@ -5,6 +5,7 @@ #include "common-args.hh" #include "local-fs-store.hh" #include "logging.hh" +#include "processes.hh" #include "profiles.hh" #include "store-api.hh" #include "filetransfer.hh" diff --git a/tests/functional/repl_characterization/test-session.cc b/tests/functional/repl_characterization/test-session.cc index c3eb45b85..ab0710b24 100644 --- a/tests/functional/repl_characterization/test-session.cc +++ b/tests/functional/repl_characterization/test-session.cc @@ -5,6 +5,7 @@ #include "test-session.hh" #include "util.hh" #include "escape-char.hh" +#include "processes.hh" namespace nix { diff --git a/tests/unit/libutil/tests.cc b/tests/unit/libutil/tests.cc index c8dc82cb0..a42afd997 100644 --- a/tests/unit/libutil/tests.cc +++ b/tests/unit/libutil/tests.cc @@ -1,9 +1,9 @@ #include "file-system.hh" #include "util.hh" +#include "processes.hh" #include "types.hh" #include "terminal.hh" -#include <limits.h> #include <gtest/gtest.h> #include <numeric> |