From 034e93884b897abe6277a2b7aff05dd38a5373c6 Mon Sep 17 00:00:00 2001 From: John Ericson Date: Sat, 2 Sep 2023 17:35:16 -0400 Subject: [PATCH] Build libnixutil with MinGW Co-Authored-By volth --- Makefile | 15 +- mk/lib.mk | 33 +- mk/platform.mk | 32 ++ src/libutil/ambient-authority.cc | 10 +- src/libutil/archive.cc | 8 + src/libutil/args.cc | 6 +- src/libutil/environment-variables.cc | 14 - src/libutil/error.cc | 2 +- src/libutil/error.hh | 51 ++- src/libutil/file-descriptor.cc | 208 +++------- src/libutil/file-descriptor.hh | 66 ++- src/libutil/file-path.cc | 17 + src/libutil/file-path.hh | 16 + src/libutil/file-system.cc | 158 ++++++-- src/libutil/file-system.hh | 3 + src/libutil/fs-sink.cc | 28 +- src/libutil/fs-sink.hh | 4 + src/libutil/{ => linux}/namespaces.cc | 20 +- src/libutil/{ => linux}/namespaces.hh | 4 - src/libutil/local.mk | 17 +- src/libutil/logging.cc | 36 +- src/libutil/processes.cc | 380 +---------------- src/libutil/processes.hh | 26 +- src/libutil/serialise.cc | 14 + src/libutil/serialise.hh | 16 +- src/libutil/terminal.cc | 9 +- src/libutil/terminal.hh | 4 + src/libutil/thread-pool.cc | 7 +- src/libutil/unix/environment-variables.cc | 21 + src/libutil/unix/file-descriptor.cc | 155 +++++++ src/libutil/{ => unix}/monitor-fd.hh | 0 src/libutil/unix/processes.cc | 388 ++++++++++++++++++ src/libutil/{ => unix}/signals.cc | 0 src/libutil/{ => unix}/signals.hh | 0 src/libutil/{ => unix}/unix-domain-socket.cc | 0 src/libutil/{ => unix}/unix-domain-socket.hh | 0 src/libutil/unix/users.cc | 62 +++ src/libutil/users.cc | 55 --- src/libutil/users.hh | 6 +- src/libutil/util.cc | 1 - src/libutil/windows/file-descriptor.cc | 113 ++++++ src/libutil/windows/processes.cc | 404 +++++++++++++++++++ src/libutil/windows/signals.hh | 15 + src/libutil/windows/users.cc | 46 +++ src/libutil/windows/windows-error.cc | 31 ++ src/libutil/windows/windows-error.hh | 36 ++ src/libutil/windows/windows-file-path.cc | 45 +++ src/libutil/windows/windows-file-path.hh | 20 + src/nix/main.cc | 4 +- 49 files changed, 1850 insertions(+), 756 deletions(-) create mode 100644 mk/platform.mk create mode 100644 src/libutil/file-path.cc create mode 100644 src/libutil/file-path.hh rename src/libutil/{ => linux}/namespaces.cc (95%) rename src/libutil/{ => linux}/namespaces.hh (96%) create mode 100644 src/libutil/unix/environment-variables.cc create mode 100644 src/libutil/unix/file-descriptor.cc rename src/libutil/{ => unix}/monitor-fd.hh (100%) create mode 100644 src/libutil/unix/processes.cc rename src/libutil/{ => unix}/signals.cc (100%) rename src/libutil/{ => unix}/signals.hh (100%) rename src/libutil/{ => unix}/unix-domain-socket.cc (100%) rename src/libutil/{ => unix}/unix-domain-socket.hh (100%) create mode 100644 src/libutil/unix/users.cc create mode 100644 src/libutil/windows/file-descriptor.cc create mode 100644 src/libutil/windows/processes.cc create mode 100644 src/libutil/windows/signals.hh create mode 100644 src/libutil/windows/users.cc create mode 100644 src/libutil/windows/windows-error.cc create mode 100644 src/libutil/windows/windows-error.hh create mode 100644 src/libutil/windows/windows-file-path.cc create mode 100644 src/libutil/windows/windows-file-path.hh diff --git a/Makefile b/Makefile index 4f4ac0c6e295..349f1b56ef27 100644 --- a/Makefile +++ b/Makefile @@ -1,11 +1,16 @@ -include Makefile.config clean-files += Makefile.config +include mk/platform.mk + ifeq ($(ENABLE_BUILD), yes) makefiles = \ mk/precompiled-headers.mk \ local.mk \ - src/libutil/local.mk \ + src/libutil/local.mk + +ifdef HOST_UNIX +makefiles += \ src/libstore/local.mk \ src/libfetchers/local.mk \ src/libmain/local.mk \ @@ -23,22 +28,28 @@ makefiles = \ doc/manual/local.mk \ doc/internal-api/local.mk endif +endif ifeq ($(ENABLE_BUILD)_$(ENABLE_TESTS), yes_yes) UNIT_TEST_ENV = _NIX_TEST_UNIT_DATA=unit-test-data makefiles += \ - src/libutil/tests/local.mk \ + src/libutil/tests/local.mk +ifdef HOST_UNIX +makefiles += \ src/libstore/tests/local.mk \ src/libexpr/tests/local.mk endif +endif ifeq ($(ENABLE_TESTS), yes) +ifdef HOST_UNIX makefiles += \ tests/functional/local.mk \ tests/functional/ca/local.mk \ tests/functional/dyn-drv/local.mk \ tests/functional/test-libstoreconsumer/local.mk \ tests/functional/plugins/local.mk +endif else makefiles += \ mk/disable-tests.mk diff --git a/mk/lib.mk b/mk/lib.mk index 65361db39f1a..5e501357795e 100644 --- a/mk/lib.mk +++ b/mk/lib.mk @@ -12,38 +12,7 @@ man-pages := install-tests := install-tests-groups := -ifdef HOST_OS - HOST_KERNEL = $(firstword $(subst -, ,$(HOST_OS))) - ifeq ($(patsubst mingw%,,$(HOST_KERNEL)),) - HOST_MINGW = 1 - HOST_WINDOWS = 1 - endif - ifeq ($(HOST_KERNEL), cygwin) - HOST_CYGWIN = 1 - HOST_WINDOWS = 1 - HOST_UNIX = 1 - endif - ifeq ($(patsubst darwin%,,$(HOST_KERNEL)),) - HOST_DARWIN = 1 - HOST_UNIX = 1 - endif - ifeq ($(patsubst freebsd%,,$(HOST_KERNEL)),) - HOST_FREEBSD = 1 - HOST_UNIX = 1 - endif - ifeq ($(patsubst netbsd%,,$(HOST_KERNEL)),) - HOST_NETBSD = 1 - HOST_UNIX = 1 - endif - ifeq ($(HOST_KERNEL), linux) - HOST_LINUX = 1 - HOST_UNIX = 1 - endif - ifeq ($(patsubst solaris%,,$(HOST_KERNEL)),) - HOST_SOLARIS = 1 - HOST_UNIX = 1 - endif -endif +include mk/platform.mk # Hack to define a literal space. space := diff --git a/mk/platform.mk b/mk/platform.mk new file mode 100644 index 000000000000..fe960dedf73b --- /dev/null +++ b/mk/platform.mk @@ -0,0 +1,32 @@ +ifdef HOST_OS + HOST_KERNEL = $(firstword $(subst -, ,$(HOST_OS))) + ifeq ($(patsubst mingw%,,$(HOST_KERNEL)),) + HOST_MINGW = 1 + HOST_WINDOWS = 1 + endif + ifeq ($(HOST_KERNEL), cygwin) + HOST_CYGWIN = 1 + HOST_WINDOWS = 1 + HOST_UNIX = 1 + endif + ifeq ($(patsubst darwin%,,$(HOST_KERNEL)),) + HOST_DARWIN = 1 + HOST_UNIX = 1 + endif + ifeq ($(patsubst freebsd%,,$(HOST_KERNEL)),) + HOST_FREEBSD = 1 + HOST_UNIX = 1 + endif + ifeq ($(patsubst netbsd%,,$(HOST_KERNEL)),) + HOST_NETBSD = 1 + HOST_UNIX = 1 + endif + ifeq ($(HOST_KERNEL), linux) + HOST_LINUX = 1 + HOST_UNIX = 1 + endif + ifeq ($(patsubst solaris%,,$(HOST_KERNEL)),) + HOST_SOLARIS = 1 + HOST_UNIX = 1 + endif +endif diff --git a/src/libutil/ambient-authority.cc b/src/libutil/ambient-authority.cc index 1f54189a8b9e..cc092ffa662d 100644 --- a/src/libutil/ambient-authority.cc +++ b/src/libutil/ambient-authority.cc @@ -1,5 +1,4 @@ #include "ambient-authority.hh" -#include "namespaces.hh" #include "util.hh" #include "finally.hh" #include "file-system.hh" @@ -14,9 +13,12 @@ # include # include # include "cgroup.hh" +# include "namespaces.hh" #endif -#include +#ifndef _WIN32 +# include +#endif namespace nix { @@ -70,9 +72,13 @@ void setStackSize(size_t stackSize) void restoreProcessContext(bool restoreMounts) { + #ifndef _WIN32 restoreSignals(); + #endif if (restoreMounts) { + #if __linux__ restoreMountNamespace(); + #endif } #if __linux__ diff --git a/src/libutil/archive.cc b/src/libutil/archive.cc index 50e02a346405..122a648d179c 100644 --- a/src/libutil/archive.cc +++ b/src/libutil/archive.cc @@ -17,6 +17,12 @@ #include "file-system.hh" #include "signals.hh" +#if _WIN32 +# include +# include "windows-file-path.hh" +# include "windows-error.hh" +#endif + namespace nix { struct ArchiveSettings : Config @@ -218,11 +224,13 @@ static void parse(ParseSink & sink, Source & source, const Path & path) sink.closeRegularFile(); } +#ifndef _WIN32 else if (s == "executable" && type == tpRegular) { auto s = readString(source); if (s != "") throw badArchive("executable marker has non-empty value"); sink.isExecutable(); } +#endif else if (s == "entry" && type == tpDirectory) { std::string name, prevName; diff --git a/src/libutil/args.cc b/src/libutil/args.cc index b113c34d066b..deb1d7795bd5 100644 --- a/src/libutil/args.cc +++ b/src/libutil/args.cc @@ -6,7 +6,9 @@ #include "users.hh" #include "json-utils.hh" -#include +#ifndef _WIN32 +# include +#endif namespace nix { @@ -345,6 +347,7 @@ Args::Flag Args::Flag::mkHashTypeOptFlag(std::string && longName, std::optional< static void _completePath(AddCompletions & completions, std::string_view prefix, bool onlyDirs) { completions.setType(Completions::Type::Filenames); + #ifndef _WIN32 glob_t globbuf; int flags = GLOB_NOESCAPE; #ifdef GLOB_ONLYDIR @@ -362,6 +365,7 @@ static void _completePath(AddCompletions & completions, std::string_view prefix, } } globfree(&globbuf); + #endif } void Args::completePath(AddCompletions & completions, size_t, std::string_view prefix) diff --git a/src/libutil/environment-variables.cc b/src/libutil/environment-variables.cc index 6618d787271c..7f4bb2d00759 100644 --- a/src/libutil/environment-variables.cc +++ b/src/libutil/environment-variables.cc @@ -32,18 +32,4 @@ std::map getEnv() return env; } - -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/error.cc b/src/libutil/error.cc index 8488e7e219e1..759b45e07f92 100644 --- a/src/libutil/error.cc +++ b/src/libutil/error.cc @@ -17,7 +17,7 @@ void BaseError::addTrace(std::shared_ptr && e, hintformat hint, boo void throwExceptionSelfCheck(){ // This is meant to be caught in initLibUtil() - throw SysError("C++ exception handling is broken. This would appear to be a problem with the way Nix was compiled and/or linked and/or loaded."); + throw Error("C++ exception handling is broken. This would appear to be a problem with the way Nix was compiled and/or linked and/or loaded."); } // c++ std::exception descendants must have a 'const char* what()' function. diff --git a/src/libutil/error.hh b/src/libutil/error.hh index c04dcbd77b2f..35ecc2db69e7 100644 --- a/src/libutil/error.hh +++ b/src/libutil/error.hh @@ -200,27 +200,66 @@ MakeError(Error, BaseError); MakeError(UsageError, Error); MakeError(UnimplementedError, Error); +/** + * To use in catch-blocks. + */ class SysError : public Error { public: - int errNo; + /** + * Has to be big enough for all platforms: + * + * - Unix: `int` (perhaps `int32_t`) + * - Windows: `DWORD` (which is `uint32_t`) + * + * The smallest type which contains all valeus of both is `int64_t`. + */ + int64_t errNo; template - SysError(int errNo_, const Args & ... args) - : Error("") + SysError(int64_t errNo, const Args & ... args) + : Error(args...), errNo(errNo) + { } +}; + +/** + * To throw. Don't catch this in portable code! Catch `SysError` + * instead. + */ +class PosixError : public SysError +{ +public: + template + PosixError(int errNo, const Args & ... args) + : SysError(errNo, "") { - errNo = errNo_; auto hf = hintfmt(args...); err.msg = hintfmt("%1%: %2%", normaltxt(hf.str()), strerror(errNo)); } template - SysError(const Args & ... args) - : SysError(errno, args ...) + PosixError(const Args & ... args) + : PosixError(errno, args ...) { } }; +#ifdef _WIN32 +class WinError; +#endif + +/** + * Convenience alias for when we use a `errno`-based error handling + * function on Unix, and a Win32 on on Windows. + */ +typedef +#ifdef _WIN32 + WinError +#else + PosixError +#endif + NativeSysError; + /** Throw an exception for the purpose of checking that exception handling works; see 'initLibUtil()'. */ void throwExceptionSelfCheck(); diff --git a/src/libutil/file-descriptor.cc b/src/libutil/file-descriptor.cc index 38dd70c8e4c3..51e5042ca7e6 100644 --- a/src/libutil/file-descriptor.cc +++ b/src/libutil/file-descriptor.cc @@ -5,130 +5,48 @@ #include #include +#ifdef _WIN32 +# include +# include +# include "windows-error.hh" +#endif 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 writeLine(int fd, std::string s) +void writeLine(HANDLE fd, std::string s) { s += '\n'; writeFull(fd, s); } -std::string drainFD(int fd, bool block, const size_t reserveSize) +std::string drainFD(HANDLE fd, bool block, const size_t reserveSize) { // the parser needs two extra bytes to append terminating characters, other users will // not care very much about the extra memory. StringSink sink(reserveSize + 2); +#ifdef _WIN32 + assert(block); + drainFD(fd, sink); +#else drainFD(fd, sink, block); +#endif return std::move(sink.s); } -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 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({(char *) buf.data(), (size_t) rd}); - } -} - ////////////////////////////////////////////////////////////////////// -AutoCloseFD::AutoCloseFD() : fd{-1} {} +AutoCloseFD::AutoCloseFD() : fd{INVALID_DESCRIPTOR} {} -AutoCloseFD::AutoCloseFD(int fd) : fd{fd} {} + +AutoCloseFD::AutoCloseFD(Descriptor fd) : fd{fd} {} AutoCloseFD::AutoCloseFD(AutoCloseFD && that) : fd{that.fd} { - that.fd = -1; + that.fd = INVALID_DESCRIPTOR; } @@ -136,7 +54,7 @@ AutoCloseFD & AutoCloseFD::operator =(AutoCloseFD && that) { close(); fd = that.fd; - that.fd = -1; + that.fd = INVALID_DESCRIPTOR; return *this; } @@ -151,7 +69,7 @@ AutoCloseFD::~AutoCloseFD() } -int AutoCloseFD::get() const +Descriptor AutoCloseFD::get() const { return fd; } @@ -159,56 +77,54 @@ int AutoCloseFD::get() const void AutoCloseFD::close() { - if (fd != -1) { - if (::close(fd) == -1) + if (fd != INVALID_DESCRIPTOR) { + if( +#ifdef _WIN32 + ::CloseHandle(fd) +#else + ::close(fd) +#endif + == -1) /* This should never happen. */ - throw SysError("closing file descriptor %1%", fd); - fd = -1; + throw NativeSysError("closing file descriptor %1%", fd); + fd = INVALID_DESCRIPTOR; } } void AutoCloseFD::fsync() { - if (fd != -1) { - int result; -#if __APPLE__ - result = ::fcntl(fd, F_FULLFSYNC); + if (fd != INVALID_DESCRIPTOR) { + int result; + result = +#ifdef _WIN32 + ::FlushFileBuffers(fd) +#elif __APPLE__ + ::fcntl(fd, F_FULLFSYNC) #else - result = ::fsync(fd); + ::fsync(fd) #endif - if (result == -1) - throw SysError("fsync file descriptor %1%", fd); - } + ; + if (result == -1) + throw NativeSysError("fsync file descriptor %1%", fd); + } } AutoCloseFD::operator bool() const { - return fd != -1; + return fd != INVALID_DESCRIPTOR; } -int AutoCloseFD::release() +Descriptor AutoCloseFD::release() { - int oldFD = fd; - fd = -1; + Descriptor oldFD = fd; + fd = INVALID_DESCRIPTOR; return oldFD; } -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 Pipe::close() @@ -217,38 +133,4 @@ void Pipe::close() writeSide.close(); } -////////////////////////////////////////////////////////////////////// - -void closeMostFDs(const std::set & 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"); -} - } diff --git a/src/libutil/file-descriptor.hh b/src/libutil/file-descriptor.hh index 80ec86135920..17a977292b15 100644 --- a/src/libutil/file-descriptor.hh +++ b/src/libutil/file-descriptor.hh @@ -4,58 +4,87 @@ #include "types.hh" #include "error.hh" +#ifdef _WIN32 +# include +#endif + namespace nix { struct Sink; struct Source; +/** + * Operating System capability + */ +typedef +#if _WIN32 + HANDLE +#else + int +#endif + Descriptor; + +const Descriptor INVALID_DESCRIPTOR = +#if _WIN32 + INVALID_HANDLE_VALUE +#else + -1 +#endif + ; + /** * Read the contents of a resource into a string. */ -std::string readFile(int fd); +std::string readFile(Descriptor fd); /** * Wrappers arount read()/write() that read/write exactly the * requested number of bytes. */ -void readFull(int fd, char * buf, size_t count); +void readFull(Descriptor fd, char * buf, size_t count); -void writeFull(int fd, std::string_view s, bool allowInterrupts = true); +void writeFull(Descriptor fd, std::string_view s, bool allowInterrupts = true); /** * Read a line from a file descriptor. */ -std::string readLine(int fd); +std::string readLine(Descriptor fd); /** * Write a line to a file descriptor. */ -void writeLine(int fd, std::string s); +void writeLine(Descriptor fd, std::string s); /** * Read a file descriptor until EOF occurs. */ -std::string drainFD(int fd, bool block = true, const size_t reserveSize=0); +std::string drainFD(Descriptor fd, bool block = true, const size_t reserveSize=0); -void drainFD(int fd, Sink & sink, bool block = true); +void drainFD( + Descriptor fd + , Sink & sink +#ifndef _WIN32 + , bool block = true +#endif + ); /** * Automatic cleanup of resources. */ class AutoCloseFD { - int fd; + Descriptor fd; public: AutoCloseFD(); - AutoCloseFD(int fd); + AutoCloseFD(Descriptor fd); AutoCloseFD(const AutoCloseFD & fd) = delete; AutoCloseFD(AutoCloseFD&& fd); ~AutoCloseFD(); AutoCloseFD& operator =(const AutoCloseFD & fd) = delete; AutoCloseFD& operator =(AutoCloseFD&& fd); - int get() const; + Descriptor get() const; explicit operator bool() const; - int release(); + Descriptor release(); void close(); void fsync(); }; @@ -68,16 +97,27 @@ public: void close(); }; +#ifndef _WIN32 + /** * Close all file descriptors except those listed in the given set. * Good practice in child processes. */ -void closeMostFDs(const std::set & exceptions); +void closeMostFDs(const std::set & exceptions); /** * Set the close-on-exec flag for the given file descriptor. */ -void closeOnExec(int fd); +void closeOnExec(Descriptor fd); + +#endif + +#ifdef _WIN32 +# if _WIN32_WINNT >= 0x0600 +Path handleToPath(Descriptor handle); +std::wstring handleToFileName(Descriptor handle); +# endif +#endif MakeError(EndOfFile, Error); diff --git a/src/libutil/file-path.cc b/src/libutil/file-path.cc new file mode 100644 index 000000000000..bc9b1ce698e7 --- /dev/null +++ b/src/libutil/file-path.cc @@ -0,0 +1,17 @@ +#include "file-path.hh" + +namespace nix { + +Path::size_type rfindPathSep(PathView path, Path::size_type from) { +#ifdef _WIN32 + Path::size_type p1 = path.rfind('/', from); + Path::size_type p2 = path.rfind('\\', from); + return p1 == Path::npos ? p2 : + p2 == Path::npos ? p1 : + std::max(p1, p2); +#else + return path.rfind('/', from); +#endif +} + +} diff --git a/src/libutil/file-path.hh b/src/libutil/file-path.hh new file mode 100644 index 000000000000..bd540c36469c --- /dev/null +++ b/src/libutil/file-path.hh @@ -0,0 +1,16 @@ +#pragma once +///@file + +#include "types.hh" + +namespace nix { + +#ifdef _WIN32 +inline bool isPathSep(char c) { return c == '/' || c == '\\'; } +#else +inline bool isPathSep(char c) { return c == '/'; } +#endif + +Path::size_type rfindPathSep(PathView path, Path::size_type from = Path::npos); + +} diff --git a/src/libutil/file-system.cc b/src/libutil/file-system.cc index c96effff9199..35138993533f 100644 --- a/src/libutil/file-system.cc +++ b/src/libutil/file-system.cc @@ -1,5 +1,6 @@ #include "environment-variables.hh" #include "file-system.hh" +#include "file-path.hh" #include "signals.hh" #include "finally.hh" #include "serialise.hh" @@ -17,6 +18,10 @@ #include #include +#ifdef _WIN32 +# include +#endif + namespace fs = std::filesystem; namespace nix { @@ -34,7 +39,7 @@ Path absPath(Path path, std::optional dir, bool resolveSymlinks) char buf[PATH_MAX]; if (!getcwd(buf, sizeof(buf))) #endif - throw SysError("cannot get cwd"); + throw PosixError("cannot get cwd"); path = concatStrings(buf, "/", path); #ifdef __GNU__ free(buf); @@ -127,10 +132,10 @@ std::string_view baseNameOf(std::string_view path) return ""; auto last = path.size() - 1; - if (path[last] == '/' && last > 0) + if (isPathSep(last) && last > 0) last -= 1; - auto pos = path.rfind('/', last); + auto pos = rfindPathSep(path, last); if (pos == std::string::npos) pos = 0; else @@ -159,29 +164,40 @@ struct stat stat(const Path & path) { struct stat st; if (stat(path.c_str(), &st)) - throw SysError("getting status of '%1%'", path); + throw PosixError("getting status of '%1%'", path); return st; } struct stat lstat(const Path & path) { +#ifndef _WIN32 struct stat st; if (lstat(path.c_str(), &st)) - throw SysError("getting status of '%1%'", path); + throw PosixError("getting status of '%1%'", path); return st; +#else + // TODO + throw UnimplementedError("getting status of '%1%'", path); +#endif } bool pathExists(const Path & path) { +#ifndef _WIN32 int res; struct stat st; 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); + throw PosixError("getting status of %1%", path); return false; +#else + // TODO + throw UnimplementedError("getting status of '%1%'", path); +#endif } bool pathAccessible(const Path & path) @@ -198,6 +214,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) { @@ -207,17 +224,20 @@ Path readLink(const Path & path) if (errno == EINVAL) throw Error("'%1%' is not a symlink", path); else - throw SysError("reading symbolic link '%1%'", path); + throw PosixError("reading symbolic link '%1%'", path); else if (rlSize < bufSize) return std::string(buf.data(), rlSize); } +#else + // TODO + throw UnimplementedError("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; } @@ -239,7 +259,7 @@ DirEntries readDirectory(DIR *dir, const Path & path) #endif ); } - if (errno) throw SysError("reading directory '%1%'", path); + if (errno) throw PosixError("reading directory '%1%'", path); return entries; } @@ -247,7 +267,7 @@ DirEntries readDirectory(DIR *dir, const Path & path) DirEntries readDirectory(const Path & path) { AutoCloseDir dir(opendir(path.c_str())); - if (!dir) throw SysError("opening directory '%1%'", path); + if (!dir) throw PosixError("opening directory '%1%'", path); return readDirectory(dir.get(), path); } @@ -255,37 +275,66 @@ 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 UnimplementedError("get file type '%1%'", path); +#endif } +static inline Descriptor toDesc(int fd) +{ +#ifdef _WIN32 + return (HANDLE) _get_osfhandle(fd); +#else + return fd; +#endif +} + std::string readFile(const Path & path) { - AutoCloseFD fd = open(path.c_str(), O_RDONLY | O_CLOEXEC); + AutoCloseFD fd = toDesc(open(path.c_str(), O_RDONLY +// TODO +#ifndef _WIN32 + | O_CLOEXEC +#endif + )); if (!fd) - throw SysError("opening file '%1%'", path); + throw PosixError("opening file '%1%'", path); return readFile(fd.get()); } void readFile(const Path & path, Sink & sink) { - AutoCloseFD fd = open(path.c_str(), O_RDONLY | O_CLOEXEC); + AutoCloseFD fd = toDesc(open(path.c_str(), O_RDONLY +// TODO +#ifndef _WIN32 + | O_CLOEXEC +#endif + )); if (!fd) - throw SysError("opening file '%s'", path); + throw PosixError("opening file '%s'", path); drainFD(fd.get(), 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 = toDesc(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); + throw PosixError("opening file '%1%'", path); try { writeFull(fd.get(), s); } catch (Error & e) { @@ -303,9 +352,14 @@ 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 = toDesc(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); + throw PosixError("opening file '%1%'", path); std::vector buf(64 * 1024); @@ -330,15 +384,16 @@ void writeFile(const Path & path, Source & source, mode_t mode, bool sync) void syncParent(const Path & path) { - AutoCloseFD fd = open(dirOf(path).c_str(), O_RDONLY, 0); + AutoCloseFD fd = toDesc(open(dirOf(path).c_str(), O_RDONLY, 0)); if (!fd) - throw SysError("opening file '%1%'", path); + throw PosixError("opening file '%1%'", path); fd.fsync(); } -static void _deletePath(int parentfd, const Path & path, uint64_t & bytesFreed) +static void _deletePath(Descriptor parentfd, const Path & path, uint64_t & bytesFreed) { +#ifndef _WIN32 checkInterrupt(); std::string name(baseNameOf(path)); @@ -346,7 +401,7 @@ static void _deletePath(int parentfd, const Path & path, uint64_t & bytesFreed) struct stat st; if (fstatat(parentfd, name.c_str(), &st, AT_SYMLINK_NOFOLLOW) == -1) { if (errno == ENOENT) return; - throw SysError("getting status of '%1%'", path); + throw PosixError("getting status of '%1%'", path); } if (!S_ISDIR(st.st_mode)) { @@ -378,15 +433,15 @@ static void _deletePath(int parentfd, const Path & path, uint64_t & bytesFreed) const auto PERM_MASK = S_IRUSR | S_IWUSR | S_IXUSR; if ((st.st_mode & PERM_MASK) != PERM_MASK) { if (fchmodat(parentfd, name.c_str(), st.st_mode | PERM_MASK, 0) == -1) - throw SysError("chmod '%1%'", path); + throw PosixError("chmod '%1%'", path); } int fd = openat(parentfd, path.c_str(), O_RDONLY); if (fd == -1) - throw SysError("opening directory '%1%'", path); + throw PosixError("opening directory '%1%'", path); AutoCloseDir dir(fdopendir(fd)); if (!dir) - throw SysError("opening directory '%1%'", path); + throw PosixError("opening directory '%1%'", path); for (auto & i : readDirectory(dir.get(), path)) _deletePath(dirfd(dir.get()), path + "/" + i.name, bytesFreed); } @@ -394,8 +449,11 @@ static void _deletePath(int parentfd, const Path & path, uint64_t & bytesFreed) int flags = S_ISDIR(st.st_mode) ? AT_REMOVEDIR : 0; if (unlinkat(parentfd, name.c_str(), flags) == -1) { if (errno == ENOENT) return; - throw SysError("cannot unlink '%1%'", path); + throw PosixError("cannot unlink '%1%'", path); } +#else + throw UnimplementedError("_deletePath"); +#endif } static void _deletePath(const Path & path, uint64_t & bytesFreed) @@ -404,10 +462,10 @@ static void _deletePath(const Path & path, uint64_t & bytesFreed) if (dir == "") dir = "/"; - AutoCloseFD dirfd{open(dir.c_str(), O_RDONLY)}; + AutoCloseFD dirfd = toDesc(open(dir.c_str(), O_RDONLY)); if (!dirfd) { if (errno == ENOENT) return; - throw SysError("opening directory '%1%'", path); + throw PosixError("opening directory '%1%'", path); } _deletePath(dirfd.get(), path, bytesFreed); @@ -423,6 +481,7 @@ void deletePath(const Path & path) Paths createDirs(const Path & path) { +#ifndef _WIN32 Paths created; if (path == "/") return created; @@ -430,17 +489,20 @@ Paths createDirs(const Path & path) if (lstat(path.c_str(), &st) == -1) { created = createDirs(dirOf(path)); if (mkdir(path.c_str(), 0777) == -1 && errno != EEXIST) - throw SysError("creating directory '%1%'", path); + throw PosixError("creating directory '%1%'", path); st = lstat(path); created.push_back(path); } if (S_ISLNK(st.st_mode) && stat(path.c_str(), &st) == -1) - throw SysError("statting symlink '%1%'", path); + throw PosixError("statting symlink '%1%'", path); if (!S_ISDIR(st.st_mode)) throw Error("'%1%' is not a directory", path); return created; +#else + throw UnimplementedError("createDirs"); +#endif } @@ -470,7 +532,7 @@ AutoDelete::~AutoDelete() deletePath(path); else { if (remove(path.c_str()) == -1) - throw SysError("cannot unlink '%1%'", path); + throw PosixError("cannot unlink '%1%'", path); } } } catch (...) { @@ -513,7 +575,11 @@ Path createTempDir(const Path & tmpRoot, const Path & prefix, while (1) { checkInterrupt(); Path tmpDir = tempName(tmpRoot, prefix, includePid, counter); - if (mkdir(tmpDir.c_str(), mode) == 0) { + if (mkdir(tmpDir.c_str() +#ifndef _WIN32 + , mode +#endif + ) == 0) { #if __FreeBSD__ /* Explicitly set the group of the directory. This is to work around around problems caused by BSD's group @@ -524,12 +590,12 @@ Path createTempDir(const Path & tmpRoot, const Path & prefix, "wheel", then "tar" will fail to unpack archives that have the setgid bit set on directories. */ if (chown(tmpDir.c_str(), (uid_t) -1, getegid()) != 0) - throw SysError("setting group of directory '%1%'", tmpDir); + throw PosixError("setting group of directory '%1%'", tmpDir); #endif return tmpDir; } if (errno != EEXIST) - throw SysError("creating directory '%1%'", tmpDir); + throw PosixError("creating directory '%1%'", tmpDir); } } @@ -539,17 +605,23 @@ std::pair createTempFile(const Path & prefix) Path tmpl(getEnv("TMPDIR").value_or("/tmp") + "/" + prefix + ".XXXXXX"); // Strictly speaking, this is UB, but who cares... // FIXME: use O_TMPFILE. - AutoCloseFD fd(mkstemp((char *) tmpl.c_str())); + AutoCloseFD fd = toDesc(mkstemp((char *) tmpl.c_str())); if (!fd) - throw SysError("creating temporary file '%s'", tmpl); + throw PosixError("creating temporary file '%s'", tmpl); +#ifndef _WIN32 closeOnExec(fd.get()); +#endif return {std::move(fd), tmpl}; } void createSymlink(const Path & target, const Path & link) { +#ifndef _WIN32 if (symlink(target.c_str(), link.c_str())) - throw SysError("creating symlink from '%1%' to '%2%'", link, target); + throw PosixError("creating symlink from '%1%' to '%2%'", link, target); +#else + throw UnimplementedError("createSymlink"); +#endif } void replaceSymlink(const Path & target, const Path & link) @@ -570,7 +642,8 @@ void replaceSymlink(const Path & target, const Path & link) } } -void setWriteTime(const fs::path & p, const struct stat & st) +#ifndef _WIN32 +static void setWriteTime(const fs::path & p, const struct stat & st) { struct timeval times[2]; times[0] = { @@ -582,13 +655,16 @@ void setWriteTime(const fs::path & p, const struct stat & st) .tv_usec = 0, }; if (lutimes(p.c_str(), times) != 0) - throw SysError("changing modification time of '%s'", p); + throw PosixError("changing modification time of '%s'", p); } +#endif void copy(const fs::directory_entry & from, const fs::path & to, bool andDelete) { // TODO: Rewrite the `is_*` to use `symlink_status()` +#ifndef _WIN32 auto statOfFrom = lstat(from.path().c_str()); +#endif auto fromStatus = from.symlink_status(); // Mark the directory as writable so that we can delete its children @@ -608,7 +684,9 @@ void copy(const fs::directory_entry & from, const fs::path & to, bool andDelete) throw Error("file '%s' has an unsupported type", from.path()); } +#ifndef _WIN32 setWriteTime(to, statOfFrom); +#endif if (andDelete) { if (!fs::is_symlink(fromStatus)) fs::permissions(from.path(), fs::perms::owner_write, fs::perm_options::add | fs::perm_options::nofollow); diff --git a/src/libutil/file-system.hh b/src/libutil/file-system.hh index 4637507b35b8..3930f1c3d91d 100644 --- a/src/libutil/file-system.hh +++ b/src/libutil/file-system.hh @@ -14,6 +14,9 @@ #include #include #include +#ifdef _WIN32 +# include +#endif #include #include diff --git a/src/libutil/fs-sink.cc b/src/libutil/fs-sink.cc index a08a723a4fce..3b24ae562bea 100644 --- a/src/libutil/fs-sink.cc +++ b/src/libutil/fs-sink.cc @@ -3,6 +3,12 @@ #include "config.hh" #include "fs-sink.hh" +#if _WIN32 +# include +# include "windows-file-path.hh" +# include "windows-error.hh" +#endif + namespace nix { struct RestoreSinkSettings : Config @@ -19,15 +25,27 @@ static GlobalConfig::Register r1(&restoreSinkSettings); void RestoreSink::createDirectory(const Path & path) { Path p = dstPath + path; - if (mkdir(p.c_str(), 0777) == -1) - throw SysError("creating directory '%1%'", p); + if ( +#ifndef _WIN32 + mkdir(p.c_str(), 0777) == -1 +#else + !CreateDirectoryW(pathW(p).c_str(), NULL) +#endif + ) + throw NativeSysError("creating directory '%1%'", p); }; void RestoreSink::createRegularFile(const Path & path) { Path p = dstPath + path; - fd = open(p.c_str(), O_CREAT | O_EXCL | O_WRONLY | O_CLOEXEC, 0666); - if (!fd) throw SysError("creating file '%1%'", p); + fd = +#ifdef _WIN32 + CreateFileW(pathW(path).c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL) +#else + open(p.c_str(), O_CREAT | O_EXCL | O_WRONLY | O_CLOEXEC, 0666) +#endif + ; + if (!fd) throw NativeSysError("creating file '%1%'", p); } void RestoreSink::closeRegularFile() @@ -36,6 +54,7 @@ void RestoreSink::closeRegularFile() fd.close(); } +#ifndef _WIN32 void RestoreSink::isExecutable() { struct stat st; @@ -44,6 +63,7 @@ void RestoreSink::isExecutable() if (fchmod(fd.get(), st.st_mode | (S_IXUSR | S_IXGRP | S_IXOTH)) == -1) throw SysError("fchmod"); } +#endif void RestoreSink::preallocateContents(uint64_t len) { diff --git a/src/libutil/fs-sink.hh b/src/libutil/fs-sink.hh index 4af557b79601..2cb8edd4b038 100644 --- a/src/libutil/fs-sink.hh +++ b/src/libutil/fs-sink.hh @@ -16,7 +16,9 @@ struct ParseSink virtual void createRegularFile(const Path & path) { }; virtual void closeRegularFile() { }; +#ifndef _WIN32 virtual void isExecutable() { }; +#endif virtual void preallocateContents(uint64_t size) { }; virtual void receiveContents(std::string_view data) { }; @@ -33,7 +35,9 @@ struct RestoreSink : ParseSink void createRegularFile(const Path & path) override; void closeRegularFile() override; +#ifndef _WIN32 void isExecutable() override; +#endif void preallocateContents(uint64_t size) override; void receiveContents(std::string_view data) override; diff --git a/src/libutil/namespaces.cc b/src/libutil/linux/namespaces.cc similarity index 95% rename from src/libutil/namespaces.cc rename to src/libutil/linux/namespaces.cc index ded1c6fedc56..ddcd63bf4643 100644 --- a/src/libutil/namespaces.cc +++ b/src/libutil/linux/namespaces.cc @@ -5,18 +5,14 @@ #include "processes.hh" #include "signals.hh" -#if __linux__ -# include -# include -# include "cgroup.hh" -#endif +#include +#include +#include "cgroup.hh" #include namespace nix { -#if __linux__ - bool userNamespacesSupported() { static auto res = [&]() -> bool @@ -101,19 +97,14 @@ bool mountAndPidNamespacesSupported() return res; } -#endif - ////////////////////////////////////////////////////////////////////// -#if __linux__ static AutoCloseFD fdSavedMountNamespace; static AutoCloseFD fdSavedRoot; -#endif void saveMountNamespace() { -#if __linux__ static std::once_flag done; std::call_once(done, []() { fdSavedMountNamespace = open("/proc/self/ns/mnt", O_RDONLY); @@ -122,12 +113,10 @@ void saveMountNamespace() fdSavedRoot = open("/proc/self/root", O_RDONLY); }); -#endif } void restoreMountNamespace() { -#if __linux__ try { auto savedCwd = absPath("."); @@ -146,15 +135,12 @@ void restoreMountNamespace() } catch (Error & e) { debug(e.msg()); } -#endif } void unshareFilesystem() { -#ifdef __linux__ if (unshare(CLONE_FS) != 0 && errno != EPERM) throw SysError("unsharing filesystem state in download thread"); -#endif } } diff --git a/src/libutil/namespaces.hh b/src/libutil/linux/namespaces.hh similarity index 96% rename from src/libutil/namespaces.hh rename to src/libutil/linux/namespaces.hh index 7e4e921a80af..ef3c9123fbe7 100644 --- a/src/libutil/namespaces.hh +++ b/src/libutil/linux/namespaces.hh @@ -26,12 +26,8 @@ void restoreMountNamespace(); */ void unshareFilesystem(); -#if __linux__ - bool userNamespacesSupported(); bool mountAndPidNamespacesSupported(); -#endif - } diff --git a/src/libutil/local.mk b/src/libutil/local.mk index ee1fd3aed450..51ea64ca98e1 100644 --- a/src/libutil/local.mk +++ b/src/libutil/local.mk @@ -6,7 +6,13 @@ libutil_DIR := $(d) libutil_SOURCES := $(wildcard $(d)/*.cc) ifdef HOST_UNIX - libutil_SOURCES := $(wildcard $(d)/unix/*.cc) + libutil_SOURCES += $(wildcard $(d)/unix/*.cc) +endif +ifdef HOST_LINUX + libutil_SOURCES += $(wildcard $(d)/linux/*.cc) +endif +ifdef HOST_WINDOWS + libutil_SOURCES += $(wildcard $(d)/windows/*.cc) endif libutil_CXXFLAGS += -I src/libutil @@ -24,5 +30,12 @@ endif INCLUDE_libutil := -I $(d) ifdef HOST_UNIX - INCLUDE_libutil := -I $(d)/unix + INCLUDE_libutil += -I $(d)/unix +endif +ifdef HOST_LINUX + INCLUDE_libutil += -I $(d)/linux +endif +ifdef HOST_WINDOWS + INCLUDE_libutil += -I $(d)/windows endif +libutil_CXXFLAGS += $(INCLUDE_libutil) diff --git a/src/libutil/logging.cc b/src/libutil/logging.cc index 60b0865bf2c1..7dc2bbcf26e7 100644 --- a/src/libutil/logging.cc +++ b/src/libutil/logging.cc @@ -9,6 +9,12 @@ #include #include +#ifdef _WIN32 +# define WIN32_LEAN_AND_MEAN +# include +# include +#endif + namespace nix { LoggerSettings loggerSettings; @@ -35,8 +41,15 @@ void Logger::warn(const std::string & msg) void Logger::writeToStdout(std::string_view s) { - writeFull(STDOUT_FILENO, s); - writeFull(STDOUT_FILENO, "\n"); + Descriptor standard_out = +#ifdef _WIN32 + GetStdHandle(STD_OUTPUT_HANDLE) +#else + STDOUT_FILENO +#endif + ; + writeFull(standard_out, s); + writeFull(standard_out, "\n"); } class SimpleLogger : public Logger @@ -113,7 +126,13 @@ Verbosity verbosity = lvlInfo; void writeToStderr(std::string_view s) { try { - writeFull(STDERR_FILENO, s, false); + writeFull( +#ifdef _WIN32 + GetStdHandle(STD_ERROR_HANDLE), +#else + STDERR_FILENO, +#endif + s, false); } catch (SysError & e) { /* Ignore failing writes to stderr. We need to ignore write errors to ensure that cleanup code that logs to stderr runs @@ -129,9 +148,18 @@ Logger * makeSimpleLogger(bool printBuildLogs) std::atomic nextId{0}; +static uint64_t getPid() +{ +#ifndef _WIN32 + return getpid(); +#else + return GetCurrentProcessId(); +#endif +} + Activity::Activity(Logger & logger, Verbosity lvl, ActivityType type, const std::string & s, const Logger::Fields & fields, ActivityId parent) - : logger(logger), id(nextId++ + (((uint64_t) getpid()) << 32)) + : logger(logger), id(nextId++ + (((uint64_t) getPid()) << 32)) { logger.startActivity(id, lvl, type, s, fields, parent); } diff --git a/src/libutil/processes.cc b/src/libutil/processes.cc index 2c640dad7c5a..3616254fa05f 100644 --- a/src/libutil/processes.cc +++ b/src/libutil/processes.cc @@ -1,33 +1,6 @@ -#include "ambient-authority.hh" -#include "environment-variables.hh" -#include "signals.hh" #include "processes.hh" -#include "finally.hh" #include "serialise.hh" -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#ifdef __APPLE__ -# include -#endif - -#ifdef __linux__ -# include -# include -#endif - - namespace nix { Pid::Pid() @@ -35,215 +8,9 @@ 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'. */ -} - - ////////////////////////////////////////////////////////////////////// -/* Wrapper around vfork to prevent the child process from clobbering - the caller's stack frame in the parent. */ -static pid_t doFork(bool allowVfork, std::function fun) __attribute__((noinline)); -static pid_t doFork(bool allowVfork, std::function 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 main = (std::function *) arg; - (*main)(); - return 1; -} -#endif - - -pid_t startProcess(std::function fun, const ProcessOptions & options) -{ - std::function 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 = (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 & input, bool isInteractive) { @@ -255,6 +22,7 @@ std::string runProgram(Path program, bool searchPath, const Strings & args, return res.second; } + // Output = error code + "standard out" output stream std::pair runProgram(RunOptions && options) { @@ -272,150 +40,4 @@ std::pair runProgram(RunOptions && options) return {status, std::move(sink.s)}; } -void runProgram2(const RunOptions & options) -{ - checkInterrupt(); - - assert(!(options.standardIn && options.input)); - - std::unique_ptr source_; - Source * source = options.standardIn; - - if (options.input) { - source_ = std::make_unique(*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>> 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 promise; - - Finally doJoin([&]() { - if (writerThread.joinable()) - writerThread.join(); - }); - - - if (source) { - in.readSide.close(); - writerThread = std::thread([&]() { - try { - std::vector buf(8 * 1024); - while (true) { - size_t n; - try { - n = source->read(buf.data(), buf.size()); - } catch (EndOfFile &) { - break; - } - writeFull(in.writeSide.get(), {buf.data(), n}); - } - promise.set_value(); - } catch (...) { - promise.set_exception(std::current_exception()); - } - in.writeSide.close(); - }); - } - - if (options.standardOut) - drainFD(out.readSide.get(), *options.standardOut); - - /* Wait for the child to finish. */ - int status = pid.wait(); - - /* Wait for the writer thread to finish. */ - if (source) promise.get_future().get(); - - if (status) - throw ExecError(status, "program '%1%' %2%", options.program, statusToString(status)); -} - -////////////////////////////////////////////////////////////////////// - -std::string statusToString(int status) -{ - if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { - if (WIFEXITED(status)) - return fmt("failed with exit code %1%", WEXITSTATUS(status)); - else if (WIFSIGNALED(status)) { - int sig = WTERMSIG(status); -#if HAVE_STRSIGNAL - const char * description = strsignal(sig); - return fmt("failed due to signal %1% (%2%)", sig, description); -#else - return fmt("failed due to signal %1%", sig); -#endif - } - else - return "died abnormally"; - } else return "succeeded"; -} - - -bool statusOk(int status) -{ - return WIFEXITED(status) && WEXITSTATUS(status) == 0; -} - } diff --git a/src/libutil/processes.hh b/src/libutil/processes.hh index 978c37105c67..af45dc8a9bcf 100644 --- a/src/libutil/processes.hh +++ b/src/libutil/processes.hh @@ -10,6 +10,10 @@ #include #include #include +#ifdef _WIN32 +# include +# include +#endif #include #include @@ -27,29 +31,41 @@ struct Source; class Pid { +#ifdef _WIN32 + HANDLE hProcess = INVALID_HANDLE_VALUE; + DWORD dwProcessId = -1; +#else pid_t pid = -1; bool separatePG = false; int killSignal = SIGKILL; +#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 }; +#ifndef _WIN32 /** * Kill all processes running under the specified uid by sending them * a SIGKILL. */ void killUser(uid_t uid); +#endif /** @@ -83,10 +99,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; diff --git a/src/libutil/serialise.cc b/src/libutil/serialise.cc index 725ddbb8d6b8..d456fea03e3e 100644 --- a/src/libutil/serialise.cc +++ b/src/libutil/serialise.cc @@ -7,6 +7,11 @@ #include +#ifdef _WIN32 +# include +# include "windows-error.hh" +#endif + namespace nix { @@ -122,6 +127,14 @@ bool BufferedSource::hasData() size_t FdSource::readUnbuffered(char * data, size_t len) { +#ifdef _WIN32 + DWORD n; + checkInterrupt(); + if (!::ReadFile(fd, data, len, &n, NULL)) { + _good = false; + throw WinError("ReadFile when FdSource::readUnbuffered"); + } +#else ssize_t n; do { checkInterrupt(); @@ -129,6 +142,7 @@ size_t FdSource::readUnbuffered(char * data, size_t len) } while (n == -1 && errno == EINTR); if (n == -1) { _good = false; throw SysError("reading from file"); } if (n == 0) { _good = false; throw EndOfFile("unexpected end-of-file"); } +#endif read += n; return n; } diff --git a/src/libutil/serialise.hh b/src/libutil/serialise.hh index 9e07226bf81f..e8fc1f6c9ef3 100644 --- a/src/libutil/serialise.hh +++ b/src/libutil/serialise.hh @@ -118,18 +118,18 @@ protected: */ struct FdSink : BufferedSink { - int fd; + Descriptor fd; size_t written = 0; - FdSink() : fd(-1) { } - FdSink(int fd) : fd(fd) { } + FdSink() : fd(INVALID_DESCRIPTOR) { } + FdSink(Descriptor fd) : fd(fd) { } FdSink(FdSink&&) = default; FdSink & operator=(FdSink && s) { flush(); fd = s.fd; - s.fd = -1; + s.fd = INVALID_DESCRIPTOR; written = s.written; return *this; } @@ -150,17 +150,17 @@ private: */ struct FdSource : BufferedSource { - int fd; + Descriptor fd; size_t read = 0; - FdSource() : fd(-1) { } - FdSource(int fd) : fd(fd) { } + FdSource() : fd(INVALID_DESCRIPTOR) { } + FdSource(Descriptor fd) : fd(fd) { } FdSource(FdSource&&) = default; FdSource& operator=(FdSource && s) { fd = s.fd; - s.fd = -1; + s.fd = INVALID_DESCRIPTOR; read = s.read; return *this; } diff --git a/src/libutil/terminal.cc b/src/libutil/terminal.cc index 8febc8771e36..99fbce43373c 100644 --- a/src/libutil/terminal.cc +++ b/src/libutil/terminal.cc @@ -2,7 +2,12 @@ #include "environment-variables.hh" #include "sync.hh" -#include +#if _WIN32 +# include +# define isatty _isatty +#else +# include +#endif #include namespace nix { @@ -86,6 +91,7 @@ std::string filterANSIEscapes(std::string_view s, bool filterAll, unsigned int w ////////////////////////////////////////////////////////////////////// +#ifndef _WIN32 static Sync> windowSize{{0, 0}}; @@ -104,5 +110,6 @@ std::pair getWindowSize() { return *windowSize.lock(); } +#endif } diff --git a/src/libutil/terminal.hh b/src/libutil/terminal.hh index 9cb191308da8..2e80bb56d342 100644 --- a/src/libutil/terminal.hh +++ b/src/libutil/terminal.hh @@ -21,6 +21,8 @@ std::string filterANSIEscapes(std::string_view s, bool filterAll = false, unsigned int width = std::numeric_limits::max()); +#ifndef _WIN32 + /** * Recalculate the window size, updating a global variable. Used in the * `SIGWINCH` signal handler. @@ -35,4 +37,6 @@ void updateWindowSize(); */ std::pair getWindowSize(); +#endif + } diff --git a/src/libutil/thread-pool.cc b/src/libutil/thread-pool.cc index c5e735617391..93605a56217f 100644 --- a/src/libutil/thread-pool.cc +++ b/src/libutil/thread-pool.cc @@ -79,8 +79,10 @@ void ThreadPool::process() void ThreadPool::doWork(bool mainThread) { +#ifndef _WIN32 if (!mainThread) interruptCheck = [&]() { return (bool) quit; }; +#endif bool didWork = false; std::exception_ptr exc; @@ -107,7 +109,10 @@ void ThreadPool::doWork(bool mainThread) try { std::rethrow_exception(exc); } catch (std::exception & e) { - if (!dynamic_cast(&e) && + if (true && +#ifndef _WIN32 + !dynamic_cast(&e) && +#endif !dynamic_cast(&e)) ignoreException(); } catch (...) { diff --git a/src/libutil/unix/environment-variables.cc b/src/libutil/unix/environment-variables.cc new file mode 100644 index 000000000000..c7288089629a --- /dev/null +++ b/src/libutil/unix/environment-variables.cc @@ -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 & newEnv) +{ + clearEnv(); + for (auto & newEnvVar : newEnv) + setenv(newEnvVar.first.c_str(), newEnvVar.second.c_str(), 1); +} + +} diff --git a/src/libutil/unix/file-descriptor.cc b/src/libutil/unix/file-descriptor.cc new file mode 100644 index 000000000000..8f364c1b95cf --- /dev/null +++ b/src/libutil/unix/file-descriptor.cc @@ -0,0 +1,155 @@ +#include "file-system.hh" +#include "signals.hh" +#include "finally.hh" +#include "serialise.hh" + +#include +#include + +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 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({(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 & 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"); +} + +} 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/processes.cc b/src/libutil/unix/processes.cc new file mode 100644 index 000000000000..e01e708a0c92 --- /dev/null +++ b/src/libutil/unix/processes.cc @@ -0,0 +1,388 @@ +#include "ambient-authority.hh" +#include "environment-variables.hh" +#include "signals.hh" +#include "processes.hh" +#include "finally.hh" +#include "serialise.hh" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#ifdef __APPLE__ +# include +#endif + +#ifdef __linux__ +# include +# include +#endif + + +namespace nix { + +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'. */ +} + + +////////////////////////////////////////////////////////////////////// + + +/* Wrapper around vfork to prevent the child process from clobbering + the caller's stack frame in the parent. */ +static pid_t doFork(bool allowVfork, std::function fun) __attribute__((noinline)); +static pid_t doFork(bool allowVfork, std::function 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 main = (std::function *) arg; + (*main)(); + return 1; +} +#endif + + +pid_t startProcess(std::function fun, const ProcessOptions & options) +{ + std::function 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 = (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; +} + + +void runProgram2(const RunOptions & options) +{ + checkInterrupt(); + + assert(!(options.standardIn && options.input)); + + std::unique_ptr source_; + Source * source = options.standardIn; + + if (options.input) { + source_ = std::make_unique(*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>> 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 promise; + + Finally doJoin([&]() { + if (writerThread.joinable()) + writerThread.join(); + }); + + + if (source) { + in.readSide.close(); + writerThread = std::thread([&]() { + try { + std::vector buf(8 * 1024); + while (true) { + size_t n; + try { + n = source->read(buf.data(), buf.size()); + } catch (EndOfFile &) { + break; + } + writeFull(in.writeSide.get(), {buf.data(), n}); + } + promise.set_value(); + } catch (...) { + promise.set_exception(std::current_exception()); + } + in.writeSide.close(); + }); + } + + if (options.standardOut) + drainFD(out.readSide.get(), *options.standardOut); + + /* Wait for the child to finish. */ + int status = pid.wait(); + + /* Wait for the writer thread to finish. */ + if (source) promise.get_future().get(); + + if (status) + throw ExecError(status, "program '%1%' %2%", options.program, statusToString(status)); +} + +////////////////////////////////////////////////////////////////////// + +std::string statusToString(int status) +{ + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { + if (WIFEXITED(status)) + return fmt("failed with exit code %1%", WEXITSTATUS(status)); + else if (WIFSIGNALED(status)) { + int sig = WTERMSIG(status); +#if HAVE_STRSIGNAL + const char * description = strsignal(sig); + return fmt("failed due to signal %1% (%2%)", sig, description); +#else + return fmt("failed due to signal %1%", sig); +#endif + } + else + return "died abnormally"; + } else return "succeeded"; +} + + +bool statusOk(int status) +{ + return WIFEXITED(status) && WEXITSTATUS(status) == 0; +} + +} diff --git a/src/libutil/signals.cc b/src/libutil/unix/signals.cc similarity index 100% rename from src/libutil/signals.cc rename to src/libutil/unix/signals.cc diff --git a/src/libutil/signals.hh b/src/libutil/unix/signals.hh similarity index 100% rename from src/libutil/signals.hh rename to src/libutil/unix/signals.hh diff --git a/src/libutil/unix-domain-socket.cc b/src/libutil/unix/unix-domain-socket.cc similarity index 100% rename from src/libutil/unix-domain-socket.cc rename to src/libutil/unix/unix-domain-socket.cc diff --git a/src/libutil/unix-domain-socket.hh b/src/libutil/unix/unix-domain-socket.hh similarity index 100% rename from src/libutil/unix-domain-socket.hh rename to src/libutil/unix/unix-domain-socket.hh diff --git a/src/libutil/unix/users.cc b/src/libutil/unix/users.cc new file mode 100644 index 000000000000..1d3d2380e884 --- /dev/null +++ b/src/libutil/unix/users.cc @@ -0,0 +1,62 @@ +#include "util.hh" +#include "users.hh" +#include "environment-variables.hh" +#include "file-system.hh" + +#include +#include +#include + +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 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 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; +} + +} diff --git a/src/libutil/users.cc b/src/libutil/users.cc index 95a641322a49..d546e364f508 100644 --- a/src/libutil/users.cc +++ b/src/libutil/users.cc @@ -3,63 +3,8 @@ #include "environment-variables.hh" #include "file-system.hh" -#include -#include -#include - 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 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 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; -} - - Path getCacheDir() { auto cacheDir = getEnv("XDG_CACHE_HOME"); diff --git a/src/libutil/users.hh b/src/libutil/users.hh index cecbb8bfb9ed..10ef331d0ee8 100644 --- a/src/libutil/users.hh +++ b/src/libutil/users.hh @@ -3,16 +3,20 @@ #include "types.hh" -#include +#ifndef _WIN32 +# include +#endif namespace nix { std::string getUserName(); +#ifndef _WIN32 /** * @return the given user's home directory from /etc/passwd. */ Path getHomeOf(uid_t userId); +#endif /** * @return $HOME or the user's home directory from /etc/passwd. diff --git a/src/libutil/util.cc b/src/libutil/util.cc index ee7a22849860..4b9ca3c45ce4 100644 --- a/src/libutil/util.cc +++ b/src/libutil/util.cc @@ -4,7 +4,6 @@ #include #include #include -#include namespace nix { diff --git a/src/libutil/windows/file-descriptor.cc b/src/libutil/windows/file-descriptor.cc new file mode 100644 index 000000000000..ff4f3447e55a --- /dev/null +++ b/src/libutil/windows/file-descriptor.cc @@ -0,0 +1,113 @@ +#include "file-system.hh" +#include "signals.hh" +#include "finally.hh" +#include "serialise.hh" +#include "windows-error.hh" + +#include +#include +#include + +namespace nix { + +std::string readFile(HANDLE handle) +{ + LARGE_INTEGER li; + if (!GetFileSizeEx(handle, &li)) + throw WinError("%s:%d statting file", __FILE__, __LINE__); + + return drainFD(handle, true, li.QuadPart); +} + + +void readFull(HANDLE handle, char * buf, size_t count) +{ + while (count) { + checkInterrupt(); + DWORD res; + if (!ReadFile(handle, (char *) buf, count, &res, NULL)) + throw WinError("%s:%d reading from file", __FILE__, __LINE__); + if (res == 0) throw EndOfFile("unexpected end-of-file"); + count -= res; + buf += res; + } +} + + +void writeFull(HANDLE handle, std::string_view s, bool allowInterrupts) +{ + while (!s.empty()) { + if (allowInterrupts) checkInterrupt(); + DWORD res; +#if _WIN32_WINNT >= 0x0600 + auto path = handleToPath(handle); // debug; do it before becuase handleToPath changes lasterror + if (!WriteFile(handle, s.data(), s.size(), &res, NULL)) { + throw WinError("writing to file %1%:%2%", handle, path); + } +#else + if (!WriteFile(handle, s.data(), s.size(), &res, NULL)) { + throw WinError("writing to file %1%", handle); + } +#endif + if (res > 0) + s.remove_prefix(res); + } +} + + +std::string readLine(HANDLE handle) +{ + std::string s; + while (1) { + checkInterrupt(); + char ch; + // FIXME: inefficient + DWORD rd; + if (!ReadFile(handle, &ch, 1, &rd, NULL)) { + throw WinError("reading a line"); + } else if (rd == 0) + throw EndOfFile("unexpected EOF reading a line"); + else { + if (ch == '\n') return s; + s += ch; + } + } +} + + +void drainFD(HANDLE handle, Sink & sink/*, bool block*/) +{ + std::vector buf(64 * 1024); + while (1) { + checkInterrupt(); + DWORD rd; + if (!ReadFile(handle, buf.data(), buf.size(), &rd, NULL)) { + WinError winError("%s:%d reading from handle %p", __FILE__, __LINE__, handle); + if (winError.errNo == ERROR_BROKEN_PIPE) + break; + throw winError; + } + else if (rd == 0) break; + sink({(char *) buf.data(), (size_t) rd}); + } +} + + +////////////////////////////////////////////////////////////////////// + +void Pipe::create() +{ + SECURITY_ATTRIBUTES saAttr = {0}; + saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); + saAttr.lpSecurityDescriptor = NULL; + saAttr.bInheritHandle = TRUE; + + HANDLE hReadPipe, hWritePipe; + if (!CreatePipe(&hReadPipe, &hWritePipe, &saAttr, 0)) + throw WinError("CreatePipe"); + + readSide = hReadPipe; + writeSide = hWritePipe; +} + +} diff --git a/src/libutil/windows/processes.cc b/src/libutil/windows/processes.cc new file mode 100644 index 000000000000..7ae880c4fde9 --- /dev/null +++ b/src/libutil/windows/processes.cc @@ -0,0 +1,404 @@ +#include "ambient-authority.hh" +#include "environment-variables.hh" +#include "signals.hh" +#include "processes.hh" +#include "finally.hh" +#include "serialise.hh" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + + +namespace nix { + +Pid::Pid(pid_t pid) + : pid(pid) +{ +} + + +Pid::~Pid() +{ + if (hProcess != INVALID_HANDLE_VALUE) kill(); +} + + +int Pid::kill() +{ + assert(hProcess != INVALID_HANDLE_VALUE); + +std::cerr << (format("killing process %1%") % dwProcessId) << std::endl; + debug(format("killing process %1%") % dwProcessId); + + switch (WaitForSingleObject(hProcess, 1000)) { // wait 1s for good finish to get a valid exit code (how small the timeout could be?; build.cc calls Pid::kill as soon as stdout pipe ended) + case WAIT_TIMEOUT: + std::cerr << (format("Pid::kill(): WaitForSingleObject(%1% [%2%]) -> TIMEOUT") % hProcess % dwProcessId) << std::endl; + if (!TerminateProcess(hProcess, 177)) { + std::cerr << ((WinError("Pid::kill(): TerminateProcess(%1% [%2%])", hProcess, dwProcessId).msg())) << std::endl; + } + return wait(); + + case WAIT_OBJECT_0: { + std::cerr << (format("Pid::kill(): WaitForSingleObject(%1% [%2%]) -> WAIT_OBJECT_0") % hProcess % dwProcessId) << std::endl; + DWORD dwExitCode = 176; + if (!GetExitCodeProcess(hProcess, &dwExitCode)) { + std::cerr << ((WinError("Pid::kill(): GetExitCodeProcess(%1% [%2%])", hProcess, dwProcessId).msg())) << std::endl; + } + std::cerr << (format("Pid::kill(): GetExitCodeProcess(%1% [%2%]) -> %3%") % hProcess % dwProcessId % dwExitCode) << std::endl; + CloseHandle(hProcess); + hProcess = INVALID_HANDLE_VALUE; + dwProcessId = DWORD(-1); + return dwExitCode; + } + + default: + throw WinError("WaitForSingleObject(%1% [%2%]) - WTF", hProcess, dwProcessId); + } +} + + +int Pid::wait() +{ + switch (WaitForSingleObject(hProcess, INFINITE)) { + case WAIT_OBJECT_0: + break; + default: + std::cerr << ((WinError("Pid::wait(): WaitForSingleObject(%1% [%2%])", hProcess, dwProcessId).msg())) << std::endl; + } + + DWORD dwExitCode = 176; + if (!GetExitCodeProcess(hProcess, &dwExitCode)) { + std::cerr << ((WinError("Pid::wait(): GetExitCodeProcess(%1% [%2%])", hProcess, dwProcessId).msg())) << std::endl; + } else { + std::cerr << (format("Pid::wait(): GetExitCodeProcess(%1% [%2%]) -> %3%") % hProcess % dwProcessId % dwExitCode).str() << std::endl; + } + + CloseHandle(hProcess); + hProcess = INVALID_HANDLE_VALUE; + dwProcessId = DWORD(-1); + return dwExitCode; +} + + +////////////////////////////////////////////////////////////////////// + + +void runProgram2(const RunOptions & options) +{ + checkInterrupt(); + + assert(!(options.standardIn && options.input)); + + std::unique_ptr source_; + Source * source = options.standardIn; + + if (options.input) { + source_ = std::make_unique(*options.input); + source = source_.get(); + } + +{ +std::string args; +for (auto & s : options.args) { + if (!args.empty()) args += " "; + args += windowsEscape(s); +} +std::cerr << __FILE__ << ":" << __LINE__ + << " standardIn='" << options.standardIn << "'" + << " standardOut='" << options.standardOut << "'" + << " program='" << options.program << "'" + << " args='" << args << "'" + << " searchPath='" << options.searchPath << "'" + << std::endl; +if (options.input) + std::cerr << "input='" << *options.input << "'" << std::endl; +} + + Pid pid; + Pipe in, out; + + std::map uenv; +// uenv[L"HOME"] = getEnvW(L"HOME", L""); + uenv[L"USERPROFILE"] = getEnvW(L"USERPROFILE", L""); + uenv[L"TEMP"] = getEnvW(L"TEMP", L""); + uenv[L"PATH"] = getEnvW(L"PATH", L""); +// std::cerr << "HOME=" << to_bytes(uenv[L"HOME"]) << std::endl; + std::cerr << "USERPROFILE=" << to_bytes(uenv[L"USERPROFILE"]) << std::endl; + std::cerr << "TEMP=" << to_bytes(uenv[L"TEMP"]) << std::endl; + std::cerr << "PATH=" << to_bytes(uenv[L"PATH"]) << std::endl; + + std::wstring uenvline; + for (auto & i : uenv) + uenvline += i.first + L'=' + i.second + L'\0'; + uenvline += L'\0'; + + STARTUPINFOW si = {0}; + si.cb = sizeof(STARTUPINFOW); + si.dwFlags = STARTF_USESTDHANDLES; + + AutoCloseWindowsHandle nul; + if (!source || !options.standardOut) { + SECURITY_ATTRIBUTES sa = {0}; + sa.nLength = sizeof(SECURITY_ATTRIBUTES); + sa.bInheritHandle = TRUE; + nul = CreateFileA("NUL", GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, &sa, OPEN_EXISTING, 0, NULL); + if (!nul.get()) + throw WinError("CreateFileA(NUL)"); + } + + if (source) { + in.createPipe(); + if (!SetHandleInformation(in.hWrite.get(), HANDLE_FLAG_INHERIT, 0)) // do not inherit the write end + throw WinError("SetHandleInformation"); + si.hStdInput = in.hRead.get(); + } else { + si.dwFlags = STARTF_USESTDHANDLES; + + AutoCloseWindowsHandle nul; + if (!source || !options.standardOut) { + SECURITY_ATTRIBUTES sa = {0}; + sa.nLength = sizeof(SECURITY_ATTRIBUTES); + sa.bInheritHandle = TRUE; + nul = CreateFileA("NUL", GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, &sa, OPEN_EXISTING, 0, NULL); + if (!nul.get()) + throw WinError("CreateFileA(NUL)"); + } + + if (source) { + in.createPipe(); + if (!SetHandleInformation(in.hWrite.get(), HANDLE_FLAG_INHERIT, 0)) // do not inherit the write end + throw WinError("SetHandleInformation"); + si.hStdInput = in.hRead.get(); + } else { + si.hStdInput = nul.get(); + } + if (options.standardOut) { + out.createPipe(); + if (!SetHandleInformation(out.hRead.get(), HANDLE_FLAG_INHERIT, 0)) // do not inherit the read end + throw WinError("SetHandleInformation"); + si.hStdOutput = out.hWrite.get(); + si.hStdError = out.hWrite.get(); + } else { + si.hStdOutput = nul.get(); + si.hStdError = nul.get(); + } + + + + /* + * try to read executable and find shebangs (needed for hg) + */ + Path executable; + if (!options.searchPath) { + executable = options.program; + if (!pathExists(executable)) + throw Error("executable '%1%' not found", executable); + } else if ( (options.program.length() >= 7 && options.program[0] == '\\') + || (options.program.length() >= 4 && options.program[1] == ':' && isslash(options.program[1]))) { + executable = options.program; + if (!pathExists(executable)) + throw Error("executable '%1%' not found", executable); + } else { + assert(options.program.find('/') == string::npos); + assert(options.program.find('\\') == string::npos); + + bool found = false; + for (const std::string & subpath : tokenizeString(getEnv("PATH", ""), ";")) { + Path candidate = canonPath(subpath) + '/' + options.program; + if (pathExists(candidate )) { executable = candidate ; found = true; break; } + if (pathExists(candidate+".exe")) { executable = candidate+".exe"; found = true; break; } + if (pathExists(candidate+".cmd")) { executable = candidate+".cmd"; found = true; break; } + if (pathExists(candidate+".bat")) { executable = candidate+".bat"; found = true; break; } + } + if (!found) + throw Error("executable '%1%' not found on PATH", executable); + } + + string shebang; + if ( !boost::algorithm::iends_with(executable, ".exe") + && !boost::algorithm::iends_with(executable, ".cmd") + && !boost::algorithm::iends_with(executable, ".bat") + ) { + std::vector buf(512); + { + AutoCloseWindowsHandle fd = CreateFileW(pathW(executable).c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (fd.get() == INVALID_HANDLE_VALUE) + throw WinError("CreateFileW '%1%'", executable); + + DWORD filled = 0; + while (filled < buf.size()) { + DWORD n; + if (!ReadFile(fd.get(), buf.data() + filled, buf.size() - filled, &n, NULL)) + throw WinError("ReadFile '%1%'", executable); + if (n == 0) + break; + filled += n; + } + buf.resize(filled); + } + if (buf.size() < 3) + throw Error("executable '%1%' is too small", executable); + if (buf[0] == '#' && buf[1] == '!') { + std::vector::const_iterator lf = std::find(buf.begin(), buf.end(), '\n'); + if (lf == buf.end()) + throw Error("executable '%1%' shebang is too long", executable); + + shebang = trim(std::string(buf.data()+2, lf-buf.begin()-2)); +// std::cerr << "_________________shebang1='" << shebang << "'" << std::endl; + + if (shebang.empty()) + throw Error("executable '%1%' shebang is empty", executable); + + // BUGBUG: msys hack, remove later + if (shebang[0] == '/') { + shebang = trim(runProgramGetStdout("cygpath", true, {"-m", shebang})); +// std::cerr << "_________________shebang2='" << shebang << "'" << std::endl; + } + assert(!shebang.empty() && shebang[0] != '/'); + + if (!pathExists(shebang)) + throw Error("executable '%1%' shebang '%2%' does not exist", executable, shebang); + } + } + + std::wstring ucmdline; + if (!shebang.empty()) + ucmdline = windowsEscapeW(from_bytes(shebang)) + L' '; + ucmdline += windowsEscapeW(from_bytes(executable)); + for (const auto & v : options.args) { + ucmdline += L' '; + ucmdline += windowsEscapeW(from_bytes(v)); + } + + std::cerr << "_________________executable='" << to_bytes(pathW(executable)) << "'" << std::endl; + std::cerr << "_________________shebang='" << (shebang) << "'" << std::endl; + std::cerr << "_________________ucmdline='" << to_bytes(ucmdline) << "'" << std::endl; + + PROCESS_INFORMATION pi = {0}; + if (!CreateProcessW( + pathW(shebang.empty() ? executable : shebang).c_str(), // LPCWSTR lpApplicationName, + const_cast((ucmdline).c_str()), // LPWSTR lpCommandLine, + NULL, // LPSECURITY_ATTRIBUTES lpProcessAttributes, + NULL, // LPSECURITY_ATTRIBUTES lpThreadAttributes, + TRUE, // BOOL bInheritHandles, + CREATE_UNICODE_ENVIRONMENT | CREATE_SUSPENDED, // DWORD dwCreationFlags, + const_cast(uenvline.c_str()), + /*from_bytes(rewriteStrings(env["PWD"], inputRewrites)).c_str()*/NULL, // LPCWSTR lpCurrentDirectory, + &si, // LPSTARTUPINFOW lpStartupInfo, + &pi // LPPROCESS_INFORMATION lpProcessInformation + )) { + throw WinError("CreateProcessW(%1%)", to_bytes(ucmdline)); + } +// std::cerr << to_bytes(ucmdline) << " pi.hProcess=" << pi.hProcess << "\n"; + + // to kill the child process on parent death (hJob can be reused?) + HANDLE hJob = CreateJobObjectA(NULL, NULL); + if (hJob == NULL) { + TerminateProcess(pi.hProcess, 0); + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + throw WinError("CreateJobObjectA()"); + } + if (!AssignProcessToJobObject(hJob, pi.hProcess)) { + TerminateProcess(pi.hProcess, 0); + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + throw WinError("AssignProcessToJobObject()"); + } + if (!ResumeThread(pi.hThread)) { + TerminateProcess(pi.hProcess, 0); + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + throw WinError("ResumeThread()"); + } + + out.hWrite = INVALID_HANDLE_VALUE; + CloseHandle(pi.hThread); + pid.set(pi.hProcess, pi.dwProcessId); + + std::thread writerThread; + + std::promise promise; + + Finally doJoin([&]() { + if (writerThread.joinable()) + writerThread.join(); + }); + + + if (source) { + in.readSide = INVALID_DESCRIPTOR; + writerThread = std::thread([&]() { + try { + std::vector 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 = INVALID_DESCRIPTOR; + }); + } + if (options.standardOut) + drainFD(out.readSide.get(), *options.standardOut); + + /* Wait for the child to finish. */ + int status = pid.wait(); + +// std::cerr << "_________________executable='" << to_bytes(pathW(executable)) << "' status=" << status << std::endl; + + /* Wait for the writer thread to finish. */ + if (source) promise.get_future().get(); + +// std::cerr << "_________________executable='" << to_bytes(pathW(executable)) << "' got future" << std::endl; + + if (status) + throw ExecError(status, fmt("program '%1%' %2%", options.program, statusToString(status))); +} + + +////////////////////////////////////////////////////////////////////// + +std::string statusToString(int status) +{ + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { + if (WIFEXITED(status)) + return (format("failed with exit code %1%") % WEXITSTATUS(st +atus)).str(); + else if (WIFSIGNALED(status)) { + int sig = WTERMSIG(status); +#if HAVE_STRSIGNAL + const char * description = strsignal(sig); + return (format("failed due to signal %1% (%2%)") % sig % des +cription).str(); +#else + return (format("failed due to signal %1%") % sig).str(); +#endif + } + else + return "died abnormally"; + } else return "succeeded";} + + +bool statusOk(int status) +{ + return status == 0; +} + +} diff --git a/src/libutil/windows/signals.hh b/src/libutil/windows/signals.hh new file mode 100644 index 000000000000..0d684170c6ff --- /dev/null +++ b/src/libutil/windows/signals.hh @@ -0,0 +1,15 @@ +#pragma once +///@file + +#include "types.hh" + +namespace nix { + +/* User interruption. */ + +void inline checkInterrupt() +{ + /* Do nothing for now */ +} + +} diff --git a/src/libutil/windows/users.cc b/src/libutil/windows/users.cc new file mode 100644 index 000000000000..d1f682b97b42 --- /dev/null +++ b/src/libutil/windows/users.cc @@ -0,0 +1,46 @@ +#include "util.hh" +#include "users.hh" +#include "environment-variables.hh" +#include "file-system.hh" +#include "windows-error.hh" + +#define WIN32_LEAN_AND_MEAN +#include + +namespace nix { + +std::string getUserName() +{ + // Get the required buffer size + DWORD size = 0; + if (!GetUserNameA(nullptr, &size)) { + auto lastError = GetLastError(); + if (lastError != ERROR_INSUFFICIENT_BUFFER) + throw WinError(lastError, "cannot figure out size of user name"); + } + + std::string name; + // Allocate a buffer of sufficient size + // + // - 1 because no need for null byte + name.resize(size - 1); + + // Retrieve the username + if (!GetUserNameA(&name[0], &size)) + throw WinError("cannot figure out user name"); + + return name; +} + +Path getHome() +{ + static Path homeDir = []() + { + Path homeDir = getEnv("USERPROFILE").value_or("C:\\Users\\Default"); + assert(!homeDir.empty()); + return canonPath(homeDir); + }(); + return homeDir; +} + +} diff --git a/src/libutil/windows/windows-error.cc b/src/libutil/windows/windows-error.cc new file mode 100644 index 000000000000..26faaae6d21e --- /dev/null +++ b/src/libutil/windows/windows-error.cc @@ -0,0 +1,31 @@ +#include "windows-error.hh" + +#include +#define WIN32_LEAN_AND_MEAN +#include + +namespace nix { + +std::string WinError::renderError(DWORD lastError) +{ + LPSTR errorText = NULL; + + FormatMessageA( FORMAT_MESSAGE_FROM_SYSTEM // use system message tables to retrieve error text + |FORMAT_MESSAGE_ALLOCATE_BUFFER // allocate buffer on local heap for error text + |FORMAT_MESSAGE_IGNORE_INSERTS, // Important! will fail otherwise, since we're not (and CANNOT) pass insertion parameters + NULL, // unused with FORMAT_MESSAGE_FROM_SYSTEM + lastError, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPTSTR)&errorText, // output + 0, // minimum size for output buffer + NULL); // arguments - see note + + if (NULL != errorText ) { + std::string s2 { errorText }; + LocalFree(errorText); + return s2; + } + return fmt("CODE=%d", lastError); +} + +} diff --git a/src/libutil/windows/windows-error.hh b/src/libutil/windows/windows-error.hh new file mode 100644 index 000000000000..73dc1637ccf8 --- /dev/null +++ b/src/libutil/windows/windows-error.hh @@ -0,0 +1,36 @@ +#pragma once +///@file + +#include + +#include "error.hh" + +namespace nix { + +/** + * To throw. Don't catch this in portable code! Catch `SysError` + * instead. + */ +class WinError : public SysError +{ +public: + template + WinError(DWORD lastError, const Args & ... args) + : SysError(lastError, "") + { + auto hf = hintfmt(args...); + err.msg = hintfmt("%1%: %2%", normaltxt(hf.str()), renderError(lastError)); + } + + template + WinError(const Args & ... args) + : WinError(GetLastError(), args ...) + { + } + +private: + + std::string renderError(DWORD lastError); +}; + +} diff --git a/src/libutil/windows/windows-file-path.cc b/src/libutil/windows/windows-file-path.cc new file mode 100644 index 000000000000..a8208e27d73e --- /dev/null +++ b/src/libutil/windows/windows-file-path.cc @@ -0,0 +1,45 @@ +#include +#include +#include +#include + +#include "file-path.hh" +#include "windows-file-path.hh" + +namespace nix { + +std::string to_bytes(const std::wstring & path) { + std::wstring_convert> converter; + return converter.to_bytes(path); +} +std::wstring from_bytes(const std::string & s) { + std::wstring_convert> converter; + return converter.from_bytes(s); +} + +std::optional maybePathW(const Path & path) { + if (path.length() >= 3 && (('A' <= path[0] && path[0] <= 'Z') || ('a' <= path[0] && path[0] <= 'z')) && path[1] == ':' && isPathSep(path[2])) { + WPath sw = from_bytes("\\\\?\\" + path); + std::replace(sw.begin(), sw.end(), '/', '\\'); + return sw; + } + if (path.length() >= 7 && path[0] == '\\' && path[1] == '\\' && (path[2] == '.' || path[2] == '?') && path[3] == '\\' && + ('A' <= path[4] && path[4] <= 'Z') && path[5] == ':' && isPathSep(path[6])) { + WPath sw = from_bytes(path); + std::replace(sw.begin(), sw.end(), '/', '\\'); + return sw; + } + return std::optional(); +} + +WPath pathW(const Path & path) { + std::optional sw = maybePathW(path); + if (!sw) { + // FIXME why are we not using the regular error handling? + std::cerr << "invalid path for WinAPI call ["< + +#include "types.hh" + +namespace nix { + +typedef std::wstring WPath; + +std::string to_bytes(const std::wstring & path); + +std::wstring from_bytes(const std::string & s); + +std::optional maybePathW(const Path & path); + +WPath pathW(const Path & path); + +} diff --git a/src/nix/main.cc b/src/nix/main.cc index 0c2c11d59403..f471095529a9 100644 --- a/src/nix/main.cc +++ b/src/nix/main.cc @@ -2,7 +2,9 @@ #include "args/root.hh" #include "ambient-authority.hh" -#include "namespaces.hh" +#if __linux__ +# include "namespaces.hh" +#endif #include "command.hh" #include "common-args.hh" #include "eval.hh"