mirror of
https://github.com/NixOS/nix.git
synced 2025-11-08 19:46:02 +01:00
There are two big changes: 1. Public and private config is now separated. Configuration variables that are only used internally do not go in a header which is installed. (Additionally, libutil has a unix-specific private config header, which should only be used in unix-specific code. This keeps things a bit more organized, in a purely private implementation-internal way.) 2. Secondly, there is no more `-include`. There are very few config items that need to be publically exposed, so now it is feasible to just make the headers that need them just including the (public) configuration header. And there are also a few more small cleanups on top of those: - The configuration files have better names. - The few CPP variables that remain exposed in the public headers are now also renamed to always start with `NIX_`. This ensures they should not conflict with variables defined elsewhere. - We now always use `#if` and not `#ifdef`/`#ifndef` for our configuration variables, which helps avoid bugs by requiring that variables must be defined in all cases.
433 lines
12 KiB
C++
433 lines
12 KiB
C++
#include "nix/current-process.hh"
|
|
#include "nix/environment-variables.hh"
|
|
#include "nix/executable-path.hh"
|
|
#include "nix/signals.hh"
|
|
#include "nix/processes.hh"
|
|
#include "nix/finally.hh"
|
|
#include "nix/serialise.hh"
|
|
|
|
#include <cerrno>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <future>
|
|
#include <iostream>
|
|
#include <sstream>
|
|
#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
|
|
|
|
#include "util-config-private.hh"
|
|
#include "util-unix-config-private.hh"
|
|
|
|
|
|
namespace nix {
|
|
|
|
Pid::Pid()
|
|
{
|
|
}
|
|
|
|
|
|
Pid::Pid(pid_t pid)
|
|
: pid(pid)
|
|
{
|
|
}
|
|
|
|
|
|
Pid::~Pid()
|
|
{
|
|
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'. */
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
using ChildWrapperFunction = std::function<void()>;
|
|
|
|
/* Wrapper around vfork to prevent the child process from clobbering
|
|
the caller's stack frame in the parent. */
|
|
static pid_t doFork(bool allowVfork, ChildWrapperFunction & fun) __attribute__((noinline));
|
|
static pid_t doFork(bool allowVfork, ChildWrapperFunction & fun)
|
|
{
|
|
#ifdef __linux__
|
|
pid_t pid = allowVfork ? vfork() : fork();
|
|
#else
|
|
pid_t pid = fork();
|
|
#endif
|
|
if (pid != 0) return pid;
|
|
fun();
|
|
unreachable();
|
|
}
|
|
|
|
|
|
#if __linux__
|
|
static int childEntry(void * arg)
|
|
{
|
|
auto & fun = *reinterpret_cast<ChildWrapperFunction*>(arg);
|
|
fun();
|
|
return 1;
|
|
}
|
|
#endif
|
|
|
|
|
|
pid_t startProcess(std::function<void()> fun, const ProcessOptions & options)
|
|
{
|
|
ChildWrapperFunction wrapper = [&] {
|
|
if (!options.allowVfork) {
|
|
/* Set a simple logger, while releasing (not destroying)
|
|
the parent logger. We don't want to run the parent
|
|
logger's destructor since that will crash (e.g. when
|
|
~ProgressBar() tries to join a thread that doesn't
|
|
exist. */
|
|
logger.release();
|
|
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 = static_cast<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(options.allowVfork, wrapper);
|
|
|
|
if (pid == -1) throw SysError("unable to fork");
|
|
|
|
return pid;
|
|
}
|
|
|
|
|
|
std::string runProgram(Path program, bool lookupPath, const Strings & args,
|
|
const std::optional<std::string> & input, bool isInteractive)
|
|
{
|
|
auto res = runProgram(RunOptions {.program = program, .lookupPath = lookupPath, .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;
|
|
// vfork implies that the environment of the main process and the fork will
|
|
// be shared (technically this is undefined, but in practice that's the
|
|
// case), so we can't use it if we alter the environment
|
|
processOptions.allowVfork = !options.environment;
|
|
|
|
auto suspension = logger->suspendIf(options.isInteractive);
|
|
|
|
/* 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.lookupPath)
|
|
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;
|
|
}
|
|
|
|
int execvpe(const char * file0, const char * const argv[], const char * const envp[])
|
|
{
|
|
auto file = ExecutablePath::load().findPath(file0);
|
|
// `const_cast` is safe. See the note in
|
|
// https://pubs.opengroup.org/onlinepubs/9799919799/functions/exec.html
|
|
return execve(file.c_str(), const_cast<char *const *>(argv), const_cast<char *const *>(envp));
|
|
}
|
|
|
|
}
|