From 21abf886dfae3cbb1a1790d3f216fe35309d865e 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 | 20 +- 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 | 216 ++++--------------- src/libutil/file-descriptor.hh | 66 ++++-- src/libutil/file-system.cc | 52 ++++- 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.hh | 25 ++- 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 | 161 ++++++++++++++ src/libutil/{ => unix}/monitor-fd.hh | 0 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 | 96 +++++++++ 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 | 44 ++++ src/libutil/windows/windows-file-path.hh | 23 ++ src/nix/main.cc | 4 +- 44 files changed, 951 insertions(+), 354 deletions(-) create mode 100644 mk/platform.mk 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%) 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/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 5aee00d03df2..9dc27a3bb2c3 100644 --- a/src/libutil/archive.cc +++ b/src/libutil/archive.cc @@ -16,6 +16,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 @@ -42,8 +48,14 @@ static void dumpContents(const Path & path, off_t size, { sink << "contents" << size; - AutoCloseFD fd = open(path.c_str(), O_RDONLY | O_CLOEXEC); - if (!fd) throw SysError("opening file '%1%'", path); + AutoCloseFD fd = +#ifdef _WIN32 + CreateFileW(pathW(path).c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL) +#else + open(path.c_str(), O_RDONLY | O_CLOEXEC) +#endif + ; + if (!fd) throw NativeSysError("opening file '%1%'", path); std::vector buf(65536); size_t left = size; @@ -107,8 +119,10 @@ static time_t dump(const Path & path, Sink & sink, PathFilter & filter) } } +#ifndef _WIN32 else if (S_ISLNK(st.st_mode)) sink << "type" << "symlink" << "target" << readLink(path); +#endif else throw Error("file '%1%' has an unsupported type", path); @@ -232,11 +246,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 e92e16772459..c53d13424757 100644 --- a/src/libutil/args.cc +++ b/src/libutil/args.cc @@ -5,7 +5,9 @@ #include "users.hh" #include "json-utils.hh" -#include +#ifndef _WIN32 +# include +#endif namespace nix { @@ -309,6 +311,7 @@ Args::Flag Args::Flag::mkHashTypeOptFlag(std::string && longName, std::optional< static void _completePath(std::string_view prefix, bool onlyDirs) { + #ifndef _WIN32 completionType = ctFilenames; glob_t globbuf; int flags = GLOB_NOESCAPE; @@ -327,6 +330,7 @@ static void _completePath(std::string_view prefix, bool onlyDirs) } } globfree(&globbuf); + #endif } void completePath(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..ae13d3487959 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,96 +77,50 @@ 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() -{ - readSide.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-system.cc b/src/libutil/file-system.cc index c96effff9199..321abc040411 100644 --- a/src/libutil/file-system.cc +++ b/src/libutil/file-system.cc @@ -167,9 +167,14 @@ struct stat stat(const Path & path) struct stat lstat(const Path & path) { struct stat st; +#ifndef _WIN32 if (lstat(path.c_str(), &st)) throw SysError("getting status of '%1%'", path); return st; +#else + // TODO + throw SysError("Not yet implemented: getting status of '%1%'", path); +#endif } @@ -177,11 +182,17 @@ bool pathExists(const Path & path) { int res; struct stat st; +#ifndef _WIN32 + res = lstat(path.c_str(), &st); res = lstat(path.c_str(), &st); if (!res) return true; if (errno != ENOENT && errno != ENOTDIR) throw SysError("getting status of %1%", path); return false; +#else + // TODO + throw SysError("Not yet implemented: getting status of '%1%'", path); +#endif } bool pathAccessible(const Path & path) @@ -198,6 +209,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) { @@ -211,13 +223,16 @@ Path readLink(const Path & path) else if (rlSize < bufSize) return std::string(buf.data(), rlSize); } +#else + // TODO + throw SysError("Not yet implemented: reading symbolic link '%1%'", path); +#endif } bool isLink(const Path & path) { - struct stat st = lstat(path); - return S_ISLNK(st.st_mode); + return getFileType(path) == DT_DIR; } @@ -255,17 +270,27 @@ DirEntries readDirectory(const Path & path) unsigned char getFileType(const Path & path) { +#ifndef _WIN32 struct stat st = lstat(path); if (S_ISDIR(st.st_mode)) return DT_DIR; if (S_ISLNK(st.st_mode)) return DT_LNK; if (S_ISREG(st.st_mode)) return DT_REG; return DT_UNKNOWN; +#else + // TODO + throw SysError("Not yet implemented: get file type '%1%'", path); +#endif } std::string readFile(const Path & path) { - AutoCloseFD fd = open(path.c_str(), O_RDONLY | O_CLOEXEC); + AutoCloseFD fd = open(path.c_str(), O_RDONLY +// TODO +#ifndef _WIN32 + | O_CLOEXEC +#endif + ); if (!fd) throw SysError("opening file '%1%'", path); return readFile(fd.get()); @@ -274,7 +299,12 @@ std::string readFile(const Path & path) void readFile(const Path & path, Sink & sink) { - AutoCloseFD fd = open(path.c_str(), O_RDONLY | O_CLOEXEC); + AutoCloseFD fd = open(path.c_str(), O_RDONLY +// TODO +#ifndef _WIN32 + | O_CLOEXEC +#endif + ); if (!fd) throw SysError("opening file '%s'", path); drainFD(fd.get(), sink); @@ -283,7 +313,12 @@ void readFile(const Path & path, Sink & sink) void writeFile(const Path & path, std::string_view s, mode_t mode, bool sync) { - AutoCloseFD fd = open(path.c_str(), O_WRONLY | O_TRUNC | O_CREAT | O_CLOEXEC, mode); + AutoCloseFD fd = open(path.c_str(), O_WRONLY | O_TRUNC | O_CREAT +// TODO +#ifndef _WIN32 + | O_CLOEXEC +#endif + , mode); if (!fd) throw SysError("opening file '%1%'", path); try { @@ -303,7 +338,12 @@ void writeFile(const Path & path, std::string_view s, mode_t mode, bool sync) void writeFile(const Path & path, Source & source, mode_t mode, bool sync) { - AutoCloseFD fd = open(path.c_str(), O_WRONLY | O_TRUNC | O_CREAT | O_CLOEXEC, mode); + AutoCloseFD fd = open(path.c_str(), O_WRONLY | O_TRUNC | O_CREAT +// TODO +#ifndef _WIN32 + | O_CLOEXEC +#endif + , mode); if (!fd) throw SysError("opening file '%1%'", path); 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 c3b756db8146..872c1abba78f 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_LDFLAGS += -pthread $(OPENSSL_LIBS) $(LIBBROTLI_LIBS) $(LIBARCHIVE_LIBS) $(BOOST_LDFLAGS) -lboost_context @@ -18,5 +24,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.hh b/src/libutil/processes.hh index 978c37105c67..5c38b15d3be9 100644 --- a/src/libutil/processes.hh +++ b/src/libutil/processes.hh @@ -10,6 +10,9 @@ #include #include #include +#ifdef _WIN32 +# include +#endif #include #include @@ -27,29 +30,41 @@ struct Source; class Pid { +#ifndef _WIN32 pid_t pid = -1; bool separatePG = false; int killSignal = SIGKILL; +#else + HANDLE hProcess; + DWORD dwProcessId; +#endif public: Pid(); - Pid(pid_t pid); ~Pid(); - void operator =(pid_t pid); - operator pid_t(); + int kill(); int wait(); +#ifndef _WIN32 + Pid(pid_t pid); + + void operator =(pid_t pid); + operator pid_t(); + void setSeparatePG(bool separatePG); void setKillSignal(int signal); pid_t release(); +#endif }; +#ifndef _WIN32 /** * Kill all processes running under the specified uid by sending them * a SIGKILL. */ void killUser(uid_t uid); +#endif /** @@ -83,10 +98,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..4ebd80120879 --- /dev/null +++ b/src/libutil/unix/file-descriptor.cc @@ -0,0 +1,161 @@ +#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 Pipe::close() +{ + readSide.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/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/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..c7e3d2c8bd57 --- /dev/null +++ b/src/libutil/windows/file-descriptor.cc @@ -0,0 +1,96 @@ +#include "file-system.hh" +#include "signals.hh" +#include "finally.hh" +#include "serialise.hh" +#include "windows-error.hh" + +#include +#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}); + } +} + +} 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..21e7a5100e85 --- /dev/null +++ b/src/libutil/windows/windows-file-path.cc @@ -0,0 +1,44 @@ +#include "windows-file-path.hh" + +#include +#include +#include +#include + +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); + +#ifdef _WIN32 +inline bool isPathSep(char c) { return c == '/' || c == '\\'; } +#else +inline bool isPathSep(char c) { return c == '/'; } +#endif + +} diff --git a/src/nix/main.cc b/src/nix/main.cc index ff9708486f38..aec0271345e6 100644 --- a/src/nix/main.cc +++ b/src/nix/main.cc @@ -1,7 +1,9 @@ #include #include "ambient-authority.hh" -#include "namespaces.hh" +#if __linux__ +# include "namespaces.hh" +#endif #include "command.hh" #include "common-args.hh" #include "eval.hh"