aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorTom Hubrecht <github@mail.hubrecht.ovh>2024-05-28 13:14:13 +0200
committerTom Hubrecht <github@mail.hubrecht.ovh>2024-05-29 11:01:34 +0200
commit9a52e4688ca265155817817f373938428f023966 (patch)
treeb90975deb9def6b8cb7c3ffe2510b7318df37d93 /src
parent8cd9aa24a8d48caf22225ce32dff3c5aaa111687 (diff)
util.{hh,cc}: Split out processes.{hh,cc}
Change-Id: I39280dc40ca3f7f9007bc6c898ffcf760e2238b7
Diffstat (limited to 'src')
-rw-r--r--src/libexpr/primops.cc2
-rw-r--r--src/libfetchers/git.cc1
-rw-r--r--src/libfetchers/mercurial.cc3
-rw-r--r--src/libmain/shared.hh1
-rw-r--r--src/libstore/build/hook-instance.hh1
-rw-r--r--src/libstore/build/local-derivation-goal.hh1
-rw-r--r--src/libstore/gc.cc1
-rw-r--r--src/libstore/globals.cc1
-rw-r--r--src/libstore/ssh.cc1
-rw-r--r--src/libstore/ssh.hh2
-rw-r--r--src/libutil/meson.build2
-rw-r--r--src/libutil/namespaces.cc5
-rw-r--r--src/libutil/processes.cc404
-rw-r--r--src/libutil/processes.hh115
-rw-r--r--src/libutil/util.cc376
-rw-r--r--src/libutil/util.hh91
-rw-r--r--src/nix/upgrade-nix.cc1
17 files changed, 537 insertions, 471 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"