util.{hh,cc}: Split out processes.{hh,cc}
Change-Id: I39280dc40ca3f7f9007bc6c898ffcf760e2238b7
This commit is contained in:
parent
8cd9aa24a8
commit
9a52e4688c
|
@ -9,8 +9,8 @@
|
||||||
#include "json-to-value.hh"
|
#include "json-to-value.hh"
|
||||||
#include "names.hh"
|
#include "names.hh"
|
||||||
#include "path-references.hh"
|
#include "path-references.hh"
|
||||||
|
#include "processes.hh"
|
||||||
#include "store-api.hh"
|
#include "store-api.hh"
|
||||||
#include "util.hh"
|
|
||||||
#include "value-to-json.hh"
|
#include "value-to-json.hh"
|
||||||
#include "value-to-xml.hh"
|
#include "value-to-xml.hh"
|
||||||
#include "primops.hh"
|
#include "primops.hh"
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#include "fetchers.hh"
|
#include "fetchers.hh"
|
||||||
#include "cache.hh"
|
#include "cache.hh"
|
||||||
#include "globals.hh"
|
#include "globals.hh"
|
||||||
|
#include "processes.hh"
|
||||||
#include "tarfile.hh"
|
#include "tarfile.hh"
|
||||||
#include "store-api.hh"
|
#include "store-api.hh"
|
||||||
#include "url-parts.hh"
|
#include "url-parts.hh"
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
#include "fetchers.hh"
|
#include "fetchers.hh"
|
||||||
#include "cache.hh"
|
#include "cache.hh"
|
||||||
#include "globals.hh"
|
#include "processes.hh"
|
||||||
#include "tarfile.hh"
|
|
||||||
#include "store-api.hh"
|
#include "store-api.hh"
|
||||||
#include "url-parts.hh"
|
#include "url-parts.hh"
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
#include "common-args.hh"
|
#include "common-args.hh"
|
||||||
#include "path.hh"
|
#include "path.hh"
|
||||||
#include "derived-path.hh"
|
#include "derived-path.hh"
|
||||||
|
#include "processes.hh"
|
||||||
#include "exit.hh"
|
#include "exit.hh"
|
||||||
|
|
||||||
#include <signal.h>
|
#include <signal.h>
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
///@file
|
///@file
|
||||||
|
|
||||||
#include "logging.hh"
|
#include "logging.hh"
|
||||||
|
#include "processes.hh"
|
||||||
#include "serialise.hh"
|
#include "serialise.hh"
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
#include "derivation-goal.hh"
|
#include "derivation-goal.hh"
|
||||||
#include "local-store.hh"
|
#include "local-store.hh"
|
||||||
|
#include "processes.hh"
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#include "derivations.hh"
|
#include "derivations.hh"
|
||||||
#include "globals.hh"
|
#include "globals.hh"
|
||||||
#include "local-store.hh"
|
#include "local-store.hh"
|
||||||
|
#include "processes.hh"
|
||||||
#include "signals.hh"
|
#include "signals.hh"
|
||||||
#include "finally.hh"
|
#include "finally.hh"
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,7 @@
|
||||||
#include "config-impl.hh"
|
#include "config-impl.hh"
|
||||||
|
|
||||||
#ifdef __APPLE__
|
#ifdef __APPLE__
|
||||||
|
#include "processes.hh"
|
||||||
#include <curl/curl.h>
|
#include <curl/curl.h>
|
||||||
#include <sys/sysctl.h>
|
#include <sys/sysctl.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#include "environment-variables.hh"
|
#include "environment-variables.hh"
|
||||||
#include "ssh.hh"
|
#include "ssh.hh"
|
||||||
#include "finally.hh"
|
#include "finally.hh"
|
||||||
|
#include "util.hh"
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
///@file
|
///@file
|
||||||
|
|
||||||
#include "file-system.hh"
|
#include "file-system.hh"
|
||||||
#include "util.hh"
|
#include "processes.hh"
|
||||||
#include "sync.hh"
|
#include "sync.hh"
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
|
@ -23,6 +23,7 @@ libutil_sources = files(
|
||||||
'namespaces.cc',
|
'namespaces.cc',
|
||||||
'position.cc',
|
'position.cc',
|
||||||
'print-elided.cc',
|
'print-elided.cc',
|
||||||
|
'processes.cc',
|
||||||
'references.cc',
|
'references.cc',
|
||||||
'regex.cc',
|
'regex.cc',
|
||||||
'serialise.cc',
|
'serialise.cc',
|
||||||
|
@ -81,6 +82,7 @@ libutil_headers = files(
|
||||||
'pool.hh',
|
'pool.hh',
|
||||||
'position.hh',
|
'position.hh',
|
||||||
'print-elided.hh',
|
'print-elided.hh',
|
||||||
|
'processes.hh',
|
||||||
'ref.hh',
|
'ref.hh',
|
||||||
'references.hh',
|
'references.hh',
|
||||||
'regex-combinators.hh',
|
'regex-combinators.hh',
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
#if __linux__
|
#if __linux__
|
||||||
|
|
||||||
#include "file-system.hh"
|
#include "file-system.hh"
|
||||||
#include "namespaces.hh"
|
#include "logging.hh"
|
||||||
#include "util.hh"
|
#include "util.hh"
|
||||||
#include "finally.hh"
|
#include "namespaces.hh"
|
||||||
|
#include "processes.hh"
|
||||||
|
|
||||||
#include <sys/mount.h>
|
#include <sys/mount.h>
|
||||||
|
|
||||||
|
|
404
src/libutil/processes.cc
Normal file
404
src/libutil/processes.cc
Normal file
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
115
src/libutil/processes.hh
Normal file
115
src/libutil/processes.hh
Normal file
|
@ -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);
|
||||||
|
|
||||||
|
}
|
|
@ -1,4 +1,6 @@
|
||||||
#include "util.hh"
|
#include "util.hh"
|
||||||
|
#include "processes.hh"
|
||||||
|
|
||||||
#include "sync.hh"
|
#include "sync.hh"
|
||||||
#include "finally.hh"
|
#include "finally.hh"
|
||||||
#include "serialise.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)
|
std::vector<char *> stringsToCharPtrs(const Strings & ss)
|
||||||
{
|
{
|
||||||
|
@ -422,149 +219,6 @@ std::vector<char *> stringsToCharPtrs(const Strings & ss)
|
||||||
return res;
|
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 toLower(const std::string & s)
|
||||||
{
|
{
|
||||||
std::string r(s);
|
std::string r(s);
|
||||||
|
|
|
@ -97,78 +97,6 @@ Path createNixStateDir();
|
||||||
*/
|
*/
|
||||||
unsigned int getMaxCPU();
|
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.
|
* Change the stack size.
|
||||||
|
@ -204,17 +132,6 @@ void restoreMountNamespace();
|
||||||
void unshareFilesystem();
|
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
|
* Convert a list of strings to a null-terminated vector of `char
|
||||||
* *`s. The result must not be accessed beyond the lifetime of the
|
* *`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.
|
* Parse a string into an integer.
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
#include "common-args.hh"
|
#include "common-args.hh"
|
||||||
#include "local-fs-store.hh"
|
#include "local-fs-store.hh"
|
||||||
#include "logging.hh"
|
#include "logging.hh"
|
||||||
|
#include "processes.hh"
|
||||||
#include "profiles.hh"
|
#include "profiles.hh"
|
||||||
#include "store-api.hh"
|
#include "store-api.hh"
|
||||||
#include "filetransfer.hh"
|
#include "filetransfer.hh"
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
#include "test-session.hh"
|
#include "test-session.hh"
|
||||||
#include "util.hh"
|
#include "util.hh"
|
||||||
#include "escape-char.hh"
|
#include "escape-char.hh"
|
||||||
|
#include "processes.hh"
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
#include "file-system.hh"
|
#include "file-system.hh"
|
||||||
#include "util.hh"
|
#include "util.hh"
|
||||||
|
#include "processes.hh"
|
||||||
#include "types.hh"
|
#include "types.hh"
|
||||||
#include "terminal.hh"
|
#include "terminal.hh"
|
||||||
|
|
||||||
#include <limits.h>
|
|
||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
#include <numeric>
|
#include <numeric>
|
||||||
|
|
Loading…
Reference in a new issue