1
0
Fork 0
mirror of https://github.com/NixOS/nix synced 2025-06-29 06:21:14 +02:00

Start factoring out Unix assumptions

This splits files and adds new identifiers in preperation for supporting
windows, but no Windows-specific code is actually added yet.

Co-authored-by: Robert Hensing <roberth@users.noreply.github.com>
This commit is contained in:
John Ericson 2024-03-29 15:59:14 -04:00
parent 852391765d
commit 02fa20622f
34 changed files with 352 additions and 296 deletions

View file

@ -0,0 +1,21 @@
#include "util.hh"
#include "environment-variables.hh"
extern char * * environ __attribute__((weak));
namespace nix {
void clearEnv()
{
for (auto & name : getEnv())
unsetenv(name.first.c_str());
}
void replaceEnv(const std::map<std::string, std::string> & newEnv)
{
clearEnv();
for (auto & newEnvVar : newEnv)
setenv(newEnvVar.first.c_str(), newEnvVar.second.c_str(), 1);
}
}

View file

@ -0,0 +1,155 @@
#include "file-system.hh"
#include "signals.hh"
#include "finally.hh"
#include "serialise.hh"
#include <fcntl.h>
#include <unistd.h>
namespace nix {
std::string readFile(int fd)
{
struct stat st;
if (fstat(fd, &st) == -1)
throw SysError("statting file");
return drainFD(fd, true, st.st_size);
}
void readFull(int fd, char * buf, size_t count)
{
while (count) {
checkInterrupt();
ssize_t res = read(fd, buf, count);
if (res == -1) {
if (errno == EINTR) continue;
throw SysError("reading from file");
}
if (res == 0) throw EndOfFile("unexpected end-of-file");
count -= res;
buf += res;
}
}
void writeFull(int fd, std::string_view s, bool allowInterrupts)
{
while (!s.empty()) {
if (allowInterrupts) checkInterrupt();
ssize_t res = write(fd, s.data(), s.size());
if (res == -1 && errno != EINTR)
throw SysError("writing to file");
if (res > 0)
s.remove_prefix(res);
}
}
std::string readLine(int fd)
{
std::string s;
while (1) {
checkInterrupt();
char ch;
// FIXME: inefficient
ssize_t rd = read(fd, &ch, 1);
if (rd == -1) {
if (errno != EINTR)
throw SysError("reading a line");
} else if (rd == 0)
throw EndOfFile("unexpected EOF reading a line");
else {
if (ch == '\n') return s;
s += ch;
}
}
}
void drainFD(int fd, Sink & sink, bool block)
{
// silence GCC maybe-uninitialized warning in finally
int saved = 0;
if (!block) {
saved = fcntl(fd, F_GETFL);
if (fcntl(fd, F_SETFL, saved | O_NONBLOCK) == -1)
throw SysError("making file descriptor non-blocking");
}
Finally finally([&]() {
if (!block) {
if (fcntl(fd, F_SETFL, saved) == -1)
throw SysError("making file descriptor blocking");
}
});
std::vector<unsigned char> buf(64 * 1024);
while (1) {
checkInterrupt();
ssize_t rd = read(fd, buf.data(), buf.size());
if (rd == -1) {
if (!block && (errno == EAGAIN || errno == EWOULDBLOCK))
break;
if (errno != EINTR)
throw SysError("reading from file");
}
else if (rd == 0) break;
else sink({reinterpret_cast<char *>(buf.data()), (size_t) rd});
}
}
//////////////////////////////////////////////////////////////////////
void Pipe::create()
{
int fds[2];
#if HAVE_PIPE2
if (pipe2(fds, O_CLOEXEC) != 0) throw SysError("creating pipe");
#else
if (pipe(fds) != 0) throw SysError("creating pipe");
closeOnExec(fds[0]);
closeOnExec(fds[1]);
#endif
readSide = fds[0];
writeSide = fds[1];
}
//////////////////////////////////////////////////////////////////////
void closeMostFDs(const std::set<int> & exceptions)
{
#if __linux__
try {
for (auto & s : readDirectory("/proc/self/fd")) {
auto fd = std::stoi(s.name);
if (!exceptions.count(fd)) {
debug("closing leaked FD %d", fd);
close(fd);
}
}
return;
} catch (SysError &) {
}
#endif
int maxFD = 0;
maxFD = sysconf(_SC_OPEN_MAX);
for (int fd = 0; fd < maxFD; ++fd)
if (!exceptions.count(fd))
close(fd); /* ignore result */
}
void closeOnExec(int fd)
{
int prev;
if ((prev = fcntl(fd, F_GETFD, 0)) == -1 ||
fcntl(fd, F_SETFD, prev | FD_CLOEXEC) == -1)
throw SysError("setting close-on-exec flag");
}
}

View file

@ -0,0 +1,72 @@
#pragma once
///@file
#include <thread>
#include <atomic>
#include <cstdlib>
#include <poll.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#include "signals.hh"
namespace nix {
class MonitorFdHup
{
private:
std::thread thread;
public:
MonitorFdHup(int fd)
{
thread = std::thread([fd]() {
while (true) {
/* Wait indefinitely until a POLLHUP occurs. */
struct pollfd fds[1];
fds[0].fd = fd;
/* Polling for no specific events (i.e. just waiting
for an error/hangup) doesn't work on macOS
anymore. So wait for read events and ignore
them. */
fds[0].events =
#ifdef __APPLE__
POLLRDNORM
#else
0
#endif
;
auto count = poll(fds, 1, -1);
if (count == -1) abort(); // can't happen
/* This shouldn't happen, but can on macOS due to a bug.
See rdar://37550628.
This may eventually need a delay or further
coordination with the main thread if spinning proves
too harmful.
*/
if (count == 0) continue;
if (fds[0].revents & POLLHUP) {
triggerInterrupt();
break;
}
/* This will only happen on macOS. We sleep a bit to
avoid waking up too often if the client is sending
input. */
sleep(1);
}
});
};
~MonitorFdHup()
{
pthread_cancel(thread.native_handle());
thread.join();
}
};
}

View file

@ -0,0 +1,422 @@
#include "current-process.hh"
#include "environment-variables.hh"
#include "signals.hh"
#include "processes.hh"
#include "finally.hh"
#include "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
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();
abort();
}
#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)
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 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;
// 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;
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;
}
}

View file

@ -0,0 +1,123 @@
#pragma once
///@file
#include "types.hh"
#include "error.hh"
#include "logging.hh"
#include "ansicolor.hh"
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include <unistd.h>
#include <signal.h>
#include <boost/lexical_cast.hpp>
#include <atomic>
#include <functional>
#include <map>
#include <sstream>
#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();
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;
bool allowVfork = 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);
}

188
src/libutil/unix/signals.cc Normal file
View file

@ -0,0 +1,188 @@
#include "signals.hh"
#include "util.hh"
#include "error.hh"
#include "sync.hh"
#include "terminal.hh"
#include <thread>
namespace nix {
std::atomic<bool> _isInterrupted = false;
static thread_local bool interruptThrown = false;
thread_local std::function<bool()> interruptCheck;
void setInterruptThrown()
{
interruptThrown = true;
}
void _interrupted()
{
/* Block user interrupts while an exception is being handled.
Throwing an exception while another exception is being handled
kills the program! */
if (!interruptThrown && !std::uncaught_exceptions()) {
interruptThrown = true;
throw Interrupted("interrupted by the user");
}
}
//////////////////////////////////////////////////////////////////////
/* We keep track of interrupt callbacks using integer tokens, so we can iterate
safely without having to lock the data structure while executing arbitrary
functions.
*/
struct InterruptCallbacks {
typedef int64_t Token;
/* We use unique tokens so that we can't accidentally delete the wrong
handler because of an erroneous double delete. */
Token nextToken = 0;
/* Used as a list, see InterruptCallbacks comment. */
std::map<Token, std::function<void()>> callbacks;
};
static Sync<InterruptCallbacks> _interruptCallbacks;
static void signalHandlerThread(sigset_t set)
{
while (true) {
int signal = 0;
sigwait(&set, &signal);
if (signal == SIGINT || signal == SIGTERM || signal == SIGHUP)
triggerInterrupt();
else if (signal == SIGWINCH) {
updateWindowSize();
}
}
}
void triggerInterrupt()
{
_isInterrupted = true;
{
InterruptCallbacks::Token i = 0;
while (true) {
std::function<void()> callback;
{
auto interruptCallbacks(_interruptCallbacks.lock());
auto lb = interruptCallbacks->callbacks.lower_bound(i);
if (lb == interruptCallbacks->callbacks.end())
break;
callback = lb->second;
i = lb->first + 1;
}
try {
callback();
} catch (...) {
ignoreException();
}
}
}
}
static sigset_t savedSignalMask;
static bool savedSignalMaskIsSet = false;
void setChildSignalMask(sigset_t * sigs)
{
assert(sigs); // C style function, but think of sigs as a reference
#if _POSIX_C_SOURCE >= 1 || _XOPEN_SOURCE || _POSIX_SOURCE
sigemptyset(&savedSignalMask);
// There's no "assign" or "copy" function, so we rely on (math) idempotence
// of the or operator: a or a = a.
sigorset(&savedSignalMask, sigs, sigs);
#else
// Without sigorset, our best bet is to assume that sigset_t is a type that
// can be assigned directly, such as is the case for a sigset_t defined as
// an integer type.
savedSignalMask = *sigs;
#endif
savedSignalMaskIsSet = true;
}
void saveSignalMask() {
if (sigprocmask(SIG_BLOCK, nullptr, &savedSignalMask))
throw SysError("querying signal mask");
savedSignalMaskIsSet = true;
}
void startSignalHandlerThread()
{
updateWindowSize();
saveSignalMask();
sigset_t set;
sigemptyset(&set);
sigaddset(&set, SIGINT);
sigaddset(&set, SIGTERM);
sigaddset(&set, SIGHUP);
sigaddset(&set, SIGPIPE);
sigaddset(&set, SIGWINCH);
if (pthread_sigmask(SIG_BLOCK, &set, nullptr))
throw SysError("blocking signals");
std::thread(signalHandlerThread, set).detach();
}
void restoreSignals()
{
// If startSignalHandlerThread wasn't called, that means we're not running
// in a proper libmain process, but a process that presumably manages its
// own signal handlers. Such a process should call either
// - initNix(), to be a proper libmain process
// - startSignalHandlerThread(), to resemble libmain regarding signal
// handling only
// - saveSignalMask(), for processes that define their own signal handling
// thread
// TODO: Warn about this? Have a default signal mask? The latter depends on
// whether we should generally inherit signal masks from the caller.
// I don't know what the larger unix ecosystem expects from us here.
if (!savedSignalMaskIsSet)
return;
if (sigprocmask(SIG_SETMASK, &savedSignalMask, nullptr))
throw SysError("restoring signals");
}
/* RAII helper to automatically deregister a callback. */
struct InterruptCallbackImpl : InterruptCallback
{
InterruptCallbacks::Token token;
~InterruptCallbackImpl() override
{
auto interruptCallbacks(_interruptCallbacks.lock());
interruptCallbacks->callbacks.erase(token);
}
};
std::unique_ptr<InterruptCallback> createInterruptCallback(std::function<void()> callback)
{
auto interruptCallbacks(_interruptCallbacks.lock());
auto token = interruptCallbacks->nextToken++;
interruptCallbacks->callbacks.emplace(token, callback);
std::unique_ptr<InterruptCallbackImpl> res {new InterruptCallbackImpl{}};
res->token = token;
return std::unique_ptr<InterruptCallback>(res.release());
}
}

104
src/libutil/unix/signals.hh Normal file
View file

@ -0,0 +1,104 @@
#pragma once
///@file
#include "types.hh"
#include "error.hh"
#include "logging.hh"
#include "ansicolor.hh"
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include <unistd.h>
#include <signal.h>
#include <boost/lexical_cast.hpp>
#include <atomic>
#include <functional>
#include <map>
#include <sstream>
#include <optional>
namespace nix {
/* User interruption. */
extern std::atomic<bool> _isInterrupted;
extern thread_local std::function<bool()> interruptCheck;
void setInterruptThrown();
void _interrupted();
void inline checkInterrupt()
{
if (_isInterrupted || (interruptCheck && interruptCheck()))
_interrupted();
}
MakeError(Interrupted, BaseError);
/**
* Start a thread that handles various signals. Also block those signals
* on the current thread (and thus any threads created by it).
* Saves the signal mask before changing the mask to block those signals.
* See saveSignalMask().
*/
void startSignalHandlerThread();
/**
* Saves the signal mask, which is the signal mask that nix will restore
* before creating child processes.
* See setChildSignalMask() to set an arbitrary signal mask instead of the
* current mask.
*/
void saveSignalMask();
/**
* To use in a process that already called `startSignalHandlerThread()`
* or `saveSignalMask()` first.
*/
void restoreSignals();
/**
* Sets the signal mask. Like saveSignalMask() but for a signal set that doesn't
* necessarily match the current thread's mask.
* See saveSignalMask() to set the saved mask to the current mask.
*/
void setChildSignalMask(sigset_t *sigs);
struct InterruptCallback
{
virtual ~InterruptCallback() { };
};
/**
* Register a function that gets called on SIGINT (in a non-signal
* context).
*/
std::unique_ptr<InterruptCallback> createInterruptCallback(
std::function<void()> callback);
void triggerInterrupt();
/**
* A RAII class that causes the current thread to receive SIGUSR1 when
* the signal handler thread receives SIGINT. That is, this allows
* SIGINT to be multiplexed to multiple threads.
*/
struct ReceiveInterrupts
{
pthread_t target;
std::unique_ptr<InterruptCallback> callback;
ReceiveInterrupts()
: target(pthread_self())
, callback(createInterruptCallback([&]() { pthread_kill(target, SIGUSR1); }))
{ }
};
}

View file

@ -0,0 +1,107 @@
#include "file-system.hh"
#include "processes.hh"
#include "unix-domain-socket.hh"
#include "util.hh"
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
namespace nix {
AutoCloseFD createUnixDomainSocket()
{
AutoCloseFD fdSocket = socket(PF_UNIX, SOCK_STREAM
#ifdef SOCK_CLOEXEC
| SOCK_CLOEXEC
#endif
, 0);
if (!fdSocket)
throw SysError("cannot create Unix domain socket");
closeOnExec(fdSocket.get());
return fdSocket;
}
AutoCloseFD createUnixDomainSocket(const Path & path, mode_t mode)
{
auto fdSocket = nix::createUnixDomainSocket();
bind(fdSocket.get(), path);
if (chmod(path.c_str(), mode) == -1)
throw SysError("changing permissions on '%1%'", path);
if (listen(fdSocket.get(), 100) == -1)
throw SysError("cannot listen on socket '%1%'", path);
return fdSocket;
}
static void bindConnectProcHelper(
std::string_view operationName, auto && operation,
int fd, const std::string & path)
{
struct sockaddr_un addr;
addr.sun_family = AF_UNIX;
// Casting between types like these legacy C library interfaces
// require is forbidden in C++. To maintain backwards
// compatibility, the implementation of the bind/connect functions
// contains some hints to the compiler that allow for this
// special case.
auto * psaddr = reinterpret_cast<struct sockaddr *>(&addr);
if (path.size() + 1 >= sizeof(addr.sun_path)) {
Pipe pipe;
pipe.create();
Pid pid = startProcess([&] {
try {
pipe.readSide.close();
Path dir = dirOf(path);
if (chdir(dir.c_str()) == -1)
throw SysError("chdir to '%s' failed", dir);
std::string base(baseNameOf(path));
if (base.size() + 1 >= sizeof(addr.sun_path))
throw Error("socket path '%s' is too long", base);
memcpy(addr.sun_path, base.c_str(), base.size() + 1);
if (operation(fd, psaddr, sizeof(addr)) == -1)
throw SysError("cannot %s to socket at '%s'", operationName, path);
writeFull(pipe.writeSide.get(), "0\n");
} catch (SysError & e) {
writeFull(pipe.writeSide.get(), fmt("%d\n", e.errNo));
} catch (...) {
writeFull(pipe.writeSide.get(), "-1\n");
}
});
pipe.writeSide.close();
auto errNo = string2Int<int>(chomp(drainFD(pipe.readSide.get())));
if (!errNo || *errNo == -1)
throw Error("cannot %s to socket at '%s'", operationName, path);
else if (*errNo > 0) {
errno = *errNo;
throw SysError("cannot %s to socket at '%s'", operationName, path);
}
} else {
memcpy(addr.sun_path, path.c_str(), path.size() + 1);
if (operation(fd, psaddr, sizeof(addr)) == -1)
throw SysError("cannot %s to socket at '%s'", operationName, path);
}
}
void bind(int fd, const std::string & path)
{
unlink(path.c_str());
bindConnectProcHelper("bind", ::bind, fd, path);
}
void connect(int fd, const std::string & path)
{
bindConnectProcHelper("connect", ::connect, fd, path);
}
}

View file

@ -0,0 +1,31 @@
#pragma once
///@file
#include "types.hh"
#include "file-descriptor.hh"
#include <unistd.h>
namespace nix {
/**
* Create a Unix domain socket.
*/
AutoCloseFD createUnixDomainSocket();
/**
* Create a Unix domain socket in listen mode.
*/
AutoCloseFD createUnixDomainSocket(const Path & path, mode_t mode);
/**
* Bind a Unix domain socket to a path.
*/
void bind(int fd, const std::string & path);
/**
* Connect to a Unix domain socket.
*/
void connect(int fd, const std::string & path);
}

66
src/libutil/unix/users.cc Normal file
View file

@ -0,0 +1,66 @@
#include "util.hh"
#include "users.hh"
#include "environment-variables.hh"
#include "file-system.hh"
#include <pwd.h>
#include <sys/types.h>
#include <unistd.h>
namespace nix {
std::string getUserName()
{
auto pw = getpwuid(geteuid());
std::string name = pw ? pw->pw_name : getEnv("USER").value_or("");
if (name.empty())
throw Error("cannot figure out user name");
return name;
}
Path getHomeOf(uid_t userId)
{
std::vector<char> buf(16384);
struct passwd pwbuf;
struct passwd * pw;
if (getpwuid_r(userId, &pwbuf, buf.data(), buf.size(), &pw) != 0
|| !pw || !pw->pw_dir || !pw->pw_dir[0])
throw Error("cannot determine user's home directory");
return pw->pw_dir;
}
Path getHome()
{
static Path homeDir = []()
{
std::optional<std::string> unownedUserHomeDir = {};
auto homeDir = getEnv("HOME");
if (homeDir) {
// Only use $HOME if doesn't exist or is owned by the current user.
struct stat st;
int result = stat(homeDir->c_str(), &st);
if (result != 0) {
if (errno != ENOENT) {
warn("couldn't stat $HOME ('%s') for reason other than not existing ('%d'), falling back to the one defined in the 'passwd' file", *homeDir, errno);
homeDir.reset();
}
} else if (st.st_uid != geteuid()) {
unownedUserHomeDir.swap(homeDir);
}
}
if (!homeDir) {
homeDir = getHomeOf(geteuid());
if (unownedUserHomeDir.has_value() && unownedUserHomeDir != homeDir) {
warn("$HOME ('%s') is not owned by you, falling back to the one defined in the 'passwd' file ('%s')", *unownedUserHomeDir, *homeDir);
}
}
return *homeDir;
}();
return homeDir;
}
bool isRootUser() {
return getuid() == 0;
}
}