From f553c4c27c83e5c0fcccf9146a48e93f484b07e5 Mon Sep 17 00:00:00 2001 From: John Ericson Date: Sat, 2 Sep 2023 17:35:16 -0400 Subject: [PATCH] WIP Co-Authored-By volth --- src/libutil/{ => unix}/monitor-fd.hh | 0 src/libutil/unix/util-unix.hh | 73 ++++++++++ src/libutil/unix/util.cc | 63 +++++++++ src/libutil/util.cc | 200 ++++++--------------------- src/libutil/util.hh | 90 +++--------- 5 files changed, 201 insertions(+), 225 deletions(-) rename src/libutil/{ => unix}/monitor-fd.hh (100%) create mode 100644 src/libutil/unix/util-unix.hh create mode 100644 src/libutil/unix/util.cc diff --git a/src/libutil/monitor-fd.hh b/src/libutil/unix/monitor-fd.hh similarity index 100% rename from src/libutil/monitor-fd.hh rename to src/libutil/unix/monitor-fd.hh diff --git a/src/libutil/unix/util-unix.hh b/src/libutil/unix/util-unix.hh new file mode 100644 index 000000000000..1876234a44b2 --- /dev/null +++ b/src/libutil/unix/util-unix.hh @@ -0,0 +1,73 @@ +#pragma once +///@file + +#include "util.hh" + +namespace nix { + +/** + * @return the given user's home directory from /etc/passwd. + */ +Path getHomeOf(uid_t userId); + +/** + * Kill all processes running under the specified uid by sending them + * a SIGKILL. + */ +void killUser(uid_t uid); + + +/** + * 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(); + +/** + * 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 createInterruptCallback( + std::function 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 callback; + + ReceiveInterrupts() + : target(pthread_self()) + , callback(createInterruptCallback([&]() { pthread_kill(target, SIGUSR1); })) + { } +}; + +} diff --git a/src/libutil/unix/util.cc b/src/libutil/unix/util.cc new file mode 100644 index 000000000000..bf292117020e --- /dev/null +++ b/src/libutil/unix/util.cc @@ -0,0 +1,63 @@ +#include "util.hh" +#include "sync.hh" +#include "finally.hh" +#include "serialise.hh" +#include "cgroup.hh" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __APPLE__ +#include +#include +#endif + +#ifdef __linux__ +#include +#include +#include + +#include +#endif + + +extern char * * environ __attribute__((weak)); + + +namespace nix { + +void clearEnv() +{ + for (auto & name : getEnv()) + unsetenv(name.first.c_str()); +} + +void replaceEnv(const std::map & newEnv) +{ + clearEnv(); + for (auto & newEnvVar : newEnv) + setenv(newEnvVar.first.c_str(), newEnvVar.second.c_str(), 1); +} + +} diff --git a/src/libutil/util.cc b/src/libutil/util.cc index 5a10c69e2dfa..a9b46ae68744 100644 --- a/src/libutil/util.cc +++ b/src/libutil/util.cc @@ -18,14 +18,8 @@ #include #include -#include -#include -#include #include -#include -#include #include -#include #include #ifdef __APPLE__ @@ -95,20 +89,6 @@ std::map getEnv() } -void clearEnv() -{ - for (auto & name : getEnv()) - unsetenv(name.first.c_str()); -} - -void replaceEnv(const std::map & newEnv) -{ - clearEnv(); - for (auto & newEnvVar : newEnv) - setenv(newEnvVar.first.c_str(), newEnvVar.second.c_str(), 1); -} - - Path absPath(Path path, std::optional dir, bool resolveSymlinks) { if (path[0] != '/') { @@ -266,9 +246,14 @@ struct stat stat(const Path & path) struct stat lstat(const Path & path) { struct stat st; +#ifndef _WIN32 if (lstat(path.c_str(), &st)) throw SysError("getting status of '%1%'", path); return st; +#else + // TODO + throw SysError("Not yet implemented: getting status of '%1%'", path); +#endif } @@ -276,11 +261,17 @@ bool pathExists(const Path & path) { int res; struct stat st; +#ifndef _WIN32 + res = lstat(path.c_str(), &st); res = lstat(path.c_str(), &st); if (!res) return true; if (errno != ENOENT && errno != ENOTDIR) throw SysError("getting status of %1%", path); return false; +#else + // TODO + throw SysError("Not yet implemented: getting status of '%1%'", path); +#endif } bool pathAccessible(const Path & path) @@ -297,6 +288,7 @@ bool pathAccessible(const Path & path) Path readLink(const Path & path) { +#ifndef _WIN32 checkInterrupt(); std::vector buf; for (ssize_t bufSize = PATH_MAX/4; true; bufSize += bufSize/2) { @@ -310,13 +302,16 @@ Path readLink(const Path & path) else if (rlSize < bufSize) return std::string(buf.data(), rlSize); } +#else + // TODO + throw SysError("Not yet implemented: reading symbolic link '%1%'", path); +#endif } bool isLink(const Path & path) { - struct stat st = lstat(path); - return S_ISLNK(st.st_mode); + return getFileType(path) == DT_DIR; } @@ -354,11 +349,16 @@ DirEntries readDirectory(const Path & path) unsigned char getFileType(const Path & path) { +#ifndef _WIN32 struct stat st = lstat(path); if (S_ISDIR(st.st_mode)) return DT_DIR; if (S_ISLNK(st.st_mode)) return DT_LNK; if (S_ISREG(st.st_mode)) return DT_REG; return DT_UNKNOWN; +#else + // TODO + throw SysError("Not yet implemented: get file type '%1%'", path); +#endif } @@ -374,7 +374,12 @@ std::string readFile(int fd) std::string readFile(const Path & path) { - AutoCloseFD fd = open(path.c_str(), O_RDONLY | O_CLOEXEC); + AutoCloseFD fd = open(path.c_str(), O_RDONLY +// TODO +#ifndef _WIN32 + | O_CLOEXEC +#endif + ); if (!fd) throw SysError("opening file '%1%'", path); return readFile(fd.get()); @@ -383,7 +388,12 @@ std::string readFile(const Path & path) void readFile(const Path & path, Sink & sink) { - AutoCloseFD fd = open(path.c_str(), O_RDONLY | O_CLOEXEC); + AutoCloseFD fd = open(path.c_str(), O_RDONLY +// TODO +#ifndef _WIN32 + | O_CLOEXEC +#endif + ); if (!fd) throw SysError("opening file '%s'", path); drainFD(fd.get(), sink); @@ -392,7 +402,12 @@ void readFile(const Path & path, Sink & sink) void writeFile(const Path & path, std::string_view s, mode_t mode, bool sync) { - AutoCloseFD fd = open(path.c_str(), O_WRONLY | O_TRUNC | O_CREAT | O_CLOEXEC, mode); + AutoCloseFD fd = open(path.c_str(), O_WRONLY | O_TRUNC | O_CREAT +// TODO +#ifndef _WIN32 + | O_CLOEXEC +#endif + , mode); if (!fd) throw SysError("opening file '%1%'", path); try { @@ -412,7 +427,12 @@ void writeFile(const Path & path, std::string_view s, mode_t mode, bool sync) void writeFile(const Path & path, Source & source, mode_t mode, bool sync) { - AutoCloseFD fd = open(path.c_str(), O_WRONLY | O_TRUNC | O_CREAT | O_CLOEXEC, mode); + AutoCloseFD fd = open(path.c_str(), O_WRONLY | O_TRUNC | O_CREAT +// TODO +#ifndef _WIN32 + | O_CLOEXEC +#endif + , mode); if (!fd) throw SysError("opening file '%1%'", path); @@ -1724,134 +1744,6 @@ std::pair getWindowSize() return *windowSize.lock(); } - -/* 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> callbacks; -}; - -static Sync _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 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(); -} - -static 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"); -} - #if __linux__ rlim_t savedStackSize = 0; #endif diff --git a/src/libutil/util.hh b/src/libutil/util.hh index b302d6f45446..00b07be6a7f1 100644 --- a/src/libutil/util.hh +++ b/src/libutil/util.hh @@ -10,6 +10,9 @@ #include #include #include +#ifdef _WIN32 +# include +#endif #include #include @@ -198,11 +201,6 @@ void deletePath(const Path & path, uint64_t & bytesFreed); std::string getUserName(); -/** - * @return the given user's home directory from /etc/passwd. - */ -Path getHomeOf(uid_t userId); - /** * @return $HOME or the user's home directory from /etc/passwd. */ @@ -368,34 +366,36 @@ struct DIRDeleter typedef std::unique_ptr AutoCloseDir; - class Pid { +#ifndef _WIN32 pid_t pid = -1; bool separatePG = false; int killSignal = SIGKILL; +#else + HANDLE hProcess; + DWORD dwProcessId; +#endif public: Pid(); - Pid(pid_t pid); ~Pid(); - void operator =(pid_t pid); - operator pid_t(); + int kill(); int wait(); +#ifndef _WIN32 + Pid(pid_t pid); + + void operator =(pid_t pid); + operator pid_t(); + void setSeparatePG(bool separatePG); void setKillSignal(int signal); pid_t release(); +#endif }; -/** - * 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. @@ -427,10 +427,12 @@ struct RunOptions { Path program; bool searchPath = true; - Strings args; + Strings args; // TODO: unicode on Windows? +#ifndef _WIN32 std::optional uid; std::optional gid; std::optional chdir; +#endif std::optional> environment; std::optional input; Source * standardIn = nullptr; @@ -821,60 +823,6 @@ template class Callback; -/** - * 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(); - -/** - * 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 createInterruptCallback( - std::function 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 callback; - - ReceiveInterrupts() - : target(pthread_self()) - , callback(createInterruptCallback([&]() { pthread_kill(target, SIGUSR1); })) - { } -}; - - /** * A RAII helper that increments a counter on construction and