diff --git a/src/libstore/windows/pathlocks.cc b/src/libstore/windows/pathlocks.cc index 45abdc165f0..df68ac66e9b 100644 --- a/src/libstore/windows/pathlocks.cc +++ b/src/libstore/windows/pathlocks.cc @@ -56,26 +56,24 @@ bool lockFile(Descriptor desc, LockType lockType, bool wait) switch (lockType) { case ltNone: { OVERLAPPED ov = {0}; - if (!UnlockFileEx(desc, 0, 2, 0, &ov)) { - WinError winError("Failed to unlock file desc %s", desc); - throw winError; - } + if (!UnlockFileEx(desc, 0, 2, 0, &ov)) + throw WinError("Failed to unlock file desc %s", desc); return true; } case ltRead: { OVERLAPPED ov = {0}; if (!LockFileEx(desc, wait ? 0 : LOCKFILE_FAIL_IMMEDIATELY, 0, 1, 0, &ov)) { - WinError winError("Failed to lock file desc %s", desc); - if (winError.lastError == ERROR_LOCK_VIOLATION) + auto lastError = GetLastError(); + if (lastError == ERROR_LOCK_VIOLATION) return false; - throw winError; + throw WinError(lastError, "Failed to lock file desc %s", desc); } ov.Offset = 1; if (!UnlockFileEx(desc, 0, 1, 0, &ov)) { - WinError winError("Failed to unlock file desc %s", desc); - if (winError.lastError != ERROR_NOT_LOCKED) - throw winError; + auto lastError = GetLastError(); + if (lastError != ERROR_NOT_LOCKED) + throw WinError(lastError, "Failed to unlock file desc %s", desc); } return true; } @@ -83,17 +81,17 @@ bool lockFile(Descriptor desc, LockType lockType, bool wait) OVERLAPPED ov = {0}; ov.Offset = 1; if (!LockFileEx(desc, LOCKFILE_EXCLUSIVE_LOCK | (wait ? 0 : LOCKFILE_FAIL_IMMEDIATELY), 0, 1, 0, &ov)) { - WinError winError("Failed to lock file desc %s", desc); - if (winError.lastError == ERROR_LOCK_VIOLATION) + auto lastError = GetLastError(); + if (lastError == ERROR_LOCK_VIOLATION) return false; - throw winError; + throw WinError(lastError, "Failed to lock file desc %s", desc); } ov.Offset = 0; if (!UnlockFileEx(desc, 0, 1, 0, &ov)) { - WinError winError("Failed to unlock file desc %s", desc); - if (winError.lastError != ERROR_NOT_LOCKED) - throw winError; + auto lastError = GetLastError(); + if (lastError != ERROR_NOT_LOCKED) + throw WinError(lastError, "Failed to unlock file desc %s", desc); } return true; } diff --git a/src/libutil-tests/unix/file-system-at.cc b/src/libutil-tests/unix/file-system-at.cc index c91235ba6e8..18540160bfb 100644 --- a/src/libutil-tests/unix/file-system-at.cc +++ b/src/libutil-tests/unix/file-system-at.cc @@ -47,16 +47,16 @@ TEST(fchmodatTryNoFollow, works) /* Check that symlinks are not followed and targets are not changed. */ EXPECT_NO_THROW( - try { fchmodatTryNoFollow(dirFd.get(), CanonPath("filelink"), 0777); } catch (SysError & e) { - if (e.errNo != EOPNOTSUPP) + try { fchmodatTryNoFollow(dirFd.get(), CanonPath("filelink"), 0777); } catch (SystemError & e) { + if (!e.is(std::errc::operation_not_supported)) throw; }); ASSERT_EQ(stat((tmpDir / "file").c_str(), &st), 0); EXPECT_EQ(st.st_mode & 0777, 0644); EXPECT_NO_THROW( - try { fchmodatTryNoFollow(dirFd.get(), CanonPath("dirlink"), 0777); } catch (SysError & e) { - if (e.errNo != EOPNOTSUPP) + try { fchmodatTryNoFollow(dirFd.get(), CanonPath("dirlink"), 0777); } catch (SystemError & e) { + if (!e.is(std::errc::operation_not_supported)) throw; }); ASSERT_EQ(stat((tmpDir / "dir").c_str(), &st), 0); @@ -110,14 +110,14 @@ TEST(fchmodatTryNoFollow, fallbackWithoutProc) try { fchmodatTryNoFollow(dirFd.get(), CanonPath("file"), 0600); - } catch (SysError & e) { + } catch (SystemError & e) { _exit(1); } try { fchmodatTryNoFollow(dirFd.get(), CanonPath("link"), 0777); - } catch (SysError & e) { - if (e.errNo == EOPNOTSUPP) + } catch (SystemError & e) { + if (e.is(std::errc::operation_not_supported)) _exit(0); /* Success. */ } diff --git a/src/libutil/include/nix/util/error.hh b/src/libutil/include/nix/util/error.hh index d9babf34d1c..74fc708052b 100644 --- a/src/libutil/include/nix/util/error.hh +++ b/src/libutil/include/nix/util/error.hh @@ -250,18 +250,15 @@ class SystemError : public Error std::error_code errorCode; std::string errorDetails; -protected: - /** - * Just here to allow derived classes to use the right constructor - * (the protected one). + * Just here to allow the static methods to use the right constructor + * (the private one). */ struct Disambig {}; /** - * Protected constructor for subclasses that provide their own error message. - * The error message is appended to the formatted hint. + * Private constructor with explicit error message string. */ template SystemError(Disambig, std::error_code errorCode, std::string_view errorDetails, Args &&... args) @@ -284,66 +281,144 @@ public: { } - const std::error_code ec() const & + /** + * Construct a POSIX error using the explicitly-provided error number. + * `strerror` will be used to try to add additional information to the message. + */ + template + static SystemError fromPosixExplicit(int errNo, Args &&... args) { - return errorCode; + return SystemError( + Disambig{}, + std::make_error_code(static_cast(errNo)), + strerror(errNo), + std::forward(args)...); } - bool is(std::errc e) const + /** + * Construct a POSIX error using the ambient `errno`. + * + * Be sure to not perform another `errno`-modifying operation before + * calling this! + */ + template + static SystemError fromPosix(Args &&... args) { - return errorCode == e; + return fromPosixExplicit(errno, std::forward(args)...); } -}; - -/** - * POSIX system error, created using `errno`, `strerror` friends. - * - * Throw this, but prefer not to catch this, and catch `SystemError` - * instead. This allows implementations to freely switch between this - * and `windows::WinError` without breaking catch blocks. - * - * However, it is permissible to catch this and rethrow so long as - * certain conditions are not met (e.g. to catch only if `errNo = - * EFooBar`). In that case, try to also catch the equivalent `windows::WinError` - * code. - * - * @todo Rename this to `PosixError` or similar. At this point Windows - * support is too WIP to justify the code churn, but if it is finished - * then a better identifier becomes moe worth it. - */ -class SysError : public SystemError -{ -public: - int errNo; +#ifdef _WIN32 /** - * Construct using the explicitly-provided error number. `strerror` - * will be used to try to add additional information to the message. + * Construct a Windows error using the explicitly-provided error number. + * `FormatMessageA` will be used to try to add additional information + * to the message. */ template - SysError(int errNo, Args &&... args) - : SystemError( - Disambig{}, - std::make_error_code(static_cast(errNo)), - strerror(errNo), - std::forward(args)...) - , errNo(errNo) + static SystemError fromWindowsExplicit(DWORD lastError, Args &&... args) { + return SystemError( + Disambig{}, + std::error_code(lastError, std::system_category()), + renderWindowsError(lastError), + std::forward(args)...); } /** - * Construct using the ambient `errno`. + * Construct a Windows error using `GetLastError()`. * - * Be sure to not perform another `errno`-modifying operation before - * calling this constructor! + * Be sure to not perform another last-error-modifying operation + * before calling this! + */ + template + static SystemError fromWindows(Args &&... args) + { + return fromWindowsExplicit(GetLastError(), std::forward(args)...); + } + +private: + static std::string renderWindowsError(DWORD lastError); + +public: +#endif + + /** + * Construct using the native error (errno on POSIX, GetLastError() on Windows). */ template - SysError(Args &&... args) - : SysError(errno, std::forward(args)...) + static SystemError fromNative(Args &&... args) { +#ifdef _WIN32 + return fromWindows(std::forward(args)...); +#else + return fromPosix(std::forward(args)...); +#endif + } + + const std::error_code ec() const & + { + return errorCode; + } + + bool is(std::errc e) const + { + return errorCode == e; } }; +/** + * Wrapper to avoid churn + */ +template +SystemError SysError(int errNo, Args &&... args) +{ + return SystemError::fromPosixExplicit(errNo, std::forward(args)...); +} + +/** + * Wrapper to avoid churn + */ +template +SystemError SysError(Args &&... args) +{ + return SystemError::fromPosix(std::forward(args)...); +} + +#ifdef _WIN32 + +namespace windows { + +/** + * Wrapper to avoid churn + */ +template +SystemError WinError(DWORD lastError, Args &&... args) +{ + return SystemError::fromWindowsExplicit(lastError, std::forward(args)...); +} + +/** + * Wrapper to avoid churn + */ +template +SystemError WinError(Args &&... args) +{ + return SystemError::fromWindows(std::forward(args)...); +} + +} // namespace windows + +#endif + +/** + * Convenience wrapper for when we use `errno`-based error handling + * on Unix, and `GetLastError()`-based error handling on Windows. + */ +template +SystemError NativeSysError(Args &&... args) +{ + return SystemError::fromNative(std::forward(args)...); +} + /** * Throw an exception for the purpose of checking that exception * handling works; see 'initLibUtil()'. @@ -382,69 +457,4 @@ int handleExceptions(const std::string & programName, std::function fun) # define nixUnreachableWhenHardened std::unreachable #endif -#ifdef _WIN32 - -namespace windows { - -/** - * Windows Error type. - * - * Unless you need to catch a specific error number, don't catch this in - * portable code. Catch `SystemError` instead. - */ -class WinError : public SystemError -{ -public: - DWORD lastError; - - /** - * Construct using the explicitly-provided error number. - * `FormatMessageA` will be used to try to add additional - * information to the message. - */ - template - WinError(DWORD lastError, Args &&... args) - : SystemError( - Disambig{}, - std::error_code(lastError, std::system_category()), - renderError(lastError), - std::forward(args)...) - , lastError(lastError) - { - } - - /** - * Construct using `GetLastError()` and the ambient "last error". - * - * Be sure to not perform another last-error-modifying operation - * before calling this constructor! - */ - template - WinError(Args &&... args) - : WinError(GetLastError(), std::forward(args)...) - { - } - -private: - - static std::string renderError(DWORD lastError); -}; - -} // namespace windows - -#endif - -/** - * Convenience alias for when we use a `errno`-based error handling - * function on Unix, and `GetLastError()`-based error handling on on - * Windows. - */ -using NativeSysError = -#ifdef _WIN32 - windows::WinError -#else - SysError -#endif - ; - } // namespace nix diff --git a/src/libutil/include/nix/util/file-system-at.hh b/src/libutil/include/nix/util/file-system-at.hh index 4026e68e5d8..d4f160adafc 100644 --- a/src/libutil/include/nix/util/file-system-at.hh +++ b/src/libutil/include/nix/util/file-system-at.hh @@ -97,7 +97,7 @@ namespace unix { * AT_SYMLINK_NOFOLLOW, since it's the best we can do without failing. * * @pre path.isRoot() is false - * @throws SysError if any operation fails + * @throws SystemError if any operation fails */ void fchmodatTryNoFollow(Descriptor dirFd, const CanonPath & path, mode_t mode); diff --git a/src/libutil/include/nix/util/file-system.hh b/src/libutil/include/nix/util/file-system.hh index 310daa01efe..7f9a0e872ea 100644 --- a/src/libutil/include/nix/util/file-system.hh +++ b/src/libutil/include/nix/util/file-system.hh @@ -199,7 +199,7 @@ std::filesystem::path readLink(const std::filesystem::path & path); * * @note this function will clobber `errno` (Unix) / "last error" * (Windows), so care must be used to get those error codes, then call - * this, then build a `SysError` / `WinError` with the saved error code. + * this, then build a `SystemError` with the saved error code. */ std::filesystem::path descriptorToPath(Descriptor fd); diff --git a/src/libutil/linux/linux-namespaces.cc b/src/libutil/linux/linux-namespaces.cc index b7787cb6fc8..657aafe8f76 100644 --- a/src/libutil/linux/linux-namespaces.cc +++ b/src/libutil/linux/linux-namespaces.cc @@ -40,7 +40,7 @@ bool userNamespacesSupported() auto r = pid.wait(); assert(!r); - } catch (SysError & e) { + } catch (SystemError & e) { debug("user namespaces do not work on this system: %s", e.msg()); return false; } @@ -77,7 +77,7 @@ bool mountAndPidNamespacesSupported() return false; } - } catch (SysError & e) { + } catch (SystemError & e) { debug("mount namespaces do not work on this system: %s", e.msg()); return false; } diff --git a/src/libutil/unix-domain-socket.cc b/src/libutil/unix-domain-socket.cc index bc9033e3b8f..a8296197034 100644 --- a/src/libutil/unix-domain-socket.cc +++ b/src/libutil/unix-domain-socket.cc @@ -78,8 +78,8 @@ bindConnectProcHelper(std::string_view operationName, auto && operation, Socket if (operation(fd, psaddr, sizeof(addr)) == -1) throw SysError("cannot %s to socket at '%s'", operationName, path); writeFull(pipe.writeSide.get(), "0\n"); - } catch (SysError & e) { - writeFull(pipe.writeSide.get(), fmt("%d\n", e.errNo)); + } catch (SystemError & e) { + writeFull(pipe.writeSide.get(), fmt("%d\n", e.ec().value())); } catch (...) { writeFull(pipe.writeSide.get(), "-1\n"); } diff --git a/src/libutil/unix/file-descriptor.cc b/src/libutil/unix/file-descriptor.cc index 86177d01bef..26cae8e232e 100644 --- a/src/libutil/unix/file-descriptor.cc +++ b/src/libutil/unix/file-descriptor.cc @@ -188,7 +188,7 @@ void unix::closeExtraFDs() } } return; - } catch (SysError &) { + } catch (SystemError &) { } #endif diff --git a/src/libutil/unix/file-system.cc b/src/libutil/unix/file-system.cc index 3598bc8dee7..10ef34d76e2 100644 --- a/src/libutil/unix/file-system.cc +++ b/src/libutil/unix/file-system.cc @@ -186,9 +186,9 @@ static void _deletePath( if ((st.st_mode & PERM_MASK) != PERM_MASK) try { unix::fchmodatTryNoFollow(parentfd, CanonPath(name), st.st_mode | PERM_MASK); - } catch (SysError & e) { + } catch (SystemError & e) { e.addTrace({}, "while making directory %1% accessible for deletion", PathFmt(path)); - if (e.errNo == EOPNOTSUPP) + if (e.is(std::errc::operation_not_supported)) e.addTrace({}, "%1% is now a symlink, expected directory", PathFmt(path)); throw; } diff --git a/src/libutil/unix/include/nix/util/monitor-fd.hh b/src/libutil/unix/include/nix/util/monitor-fd.hh index de2338ef09a..27feb575b8d 100644 --- a/src/libutil/unix/include/nix/util/monitor-fd.hh +++ b/src/libutil/unix/include/nix/util/monitor-fd.hh @@ -60,7 +60,7 @@ inline void MonitorFdHup::runThread(int watchFd, int notifyFd) { int kqResult = kqueue(); if (kqResult < 0) { - throw SysError("MonitorFdHup kqueue"); + throw SystemError::fromPosix("MonitorFdHup kqueue"); } AutoCloseFD kq{kqResult}; @@ -78,14 +78,14 @@ inline void MonitorFdHup::runThread(int watchFd, int notifyFd) int result = kevent(kq.get(), kevs.data(), kevs.size(), nullptr, 0, nullptr); if (result < 0) { - throw SysError("MonitorFdHup kevent add"); + throw SystemError::fromPosix("MonitorFdHup kevent add"); } while (true) { struct kevent event; int numEvents = kevent(kq.get(), nullptr, 0, &event, 1, nullptr); if (numEvents < 0) { - throw SysError("MonitorFdHup kevent watch"); + throw SystemError::fromPosix("MonitorFdHup kevent watch"); } if (numEvents > 0 && (event.flags & EV_EOF)) { @@ -112,7 +112,7 @@ inline void MonitorFdHup::runThread(int watchFd, int notifyFd) if (errno == EINTR || errno == EAGAIN) { continue; } else { - throw SysError("in MonitorFdHup poll()"); + throw SystemError::fromPosix("in MonitorFdHup poll()"); } } diff --git a/src/libutil/unix/users.cc b/src/libutil/unix/users.cc index 5d05dc79f36..dd0cfcf7431 100644 --- a/src/libutil/unix/users.cc +++ b/src/libutil/unix/users.cc @@ -39,7 +39,7 @@ std::filesystem::path getHome() auto st = maybeStat(homeDir->c_str()); if (st && st->st_uid != geteuid()) unownedUserHomeDir.swap(homeDir); - } catch (SysError & e) { + } catch (SystemError & e) { warn( "couldn't stat $HOME ('%s') for reason other than not existing, falling back to the one defined in the 'passwd' file: %s", *homeDir, diff --git a/src/libutil/windows/current-process.cc b/src/libutil/windows/current-process.cc index d4392e2279d..f5dca06d09c 100644 --- a/src/libutil/windows/current-process.cc +++ b/src/libutil/windows/current-process.cc @@ -8,6 +8,8 @@ namespace nix { +using namespace nix::windows; + std::chrono::microseconds getCpuUserTime() { FILETIME creationTime; @@ -17,7 +19,7 @@ std::chrono::microseconds getCpuUserTime() if (!GetProcessTimes(GetCurrentProcess(), &creationTime, &exitTime, &kernelTime, &userTime)) { auto lastError = GetLastError(); - throw windows::WinError(lastError, "failed to get CPU time"); + throw WinError(lastError, "failed to get CPU time"); } ULARGE_INTEGER uLargeInt; diff --git a/src/libutil/windows/file-system-at.cc b/src/libutil/windows/file-system-at.cc index db4a3b0d663..3ba241611ff 100644 --- a/src/libutil/windows/file-system-at.cc +++ b/src/libutil/windows/file-system-at.cc @@ -13,8 +13,6 @@ namespace nix { -using namespace nix::windows; - namespace windows { namespace { @@ -210,6 +208,8 @@ bool isReparsePoint(HANDLE handle) } // namespace windows +using namespace nix::windows; + Descriptor openFileEnsureBeneathNoSymlinks( Descriptor dirFd, const CanonPath & path, ACCESS_MASK desiredAccess, ULONG createOptions, ULONG createDisposition) { @@ -259,9 +259,10 @@ Descriptor openFileEnsureBeneathNoSymlinks( FILE_TRAVERSE | SYNCHRONIZE, // Just need traversal rights FILE_DIRECTORY_FILE | FILE_OPEN_REPARSE_POINT // Open directory, don't follow symlinks ); - } catch (WinError & e) { + } catch (SystemError & e) { /* Check if this is because it's a symlink */ - if (e.lastError == ERROR_CANT_ACCESS_FILE || e.lastError == ERROR_ACCESS_DENIED) { + auto err = e.ec().value(); + if (err == ERROR_CANT_ACCESS_FILE || err == ERROR_ACCESS_DENIED) { throwIfSymlink(wcomponent, pathUpTo(std::next(it))); } throw; @@ -286,9 +287,9 @@ Descriptor openFileEnsureBeneathNoSymlinks( desiredAccess, createOptions | FILE_OPEN_REPARSE_POINT, // Don't follow symlinks on final component either createDisposition); - } catch (WinError & e) { + } catch (SystemError & e) { /* Check if final component is a symlink when we requested to not follow it */ - if (e.lastError == ERROR_CANT_ACCESS_FILE) { + if (e.ec().value() == ERROR_CANT_ACCESS_FILE) { throwIfSymlink(finalComponent, path); } throw; diff --git a/src/libutil/windows/known-folders.cc b/src/libutil/windows/known-folders.cc index 1d286742845..5e0966c8491 100644 --- a/src/libutil/windows/known-folders.cc +++ b/src/libutil/windows/known-folders.cc @@ -8,8 +8,6 @@ namespace nix::windows::known_folders { -using namespace nix::windows; - static std::filesystem::path getKnownFolder(REFKNOWNFOLDERID rfid) { PWSTR str = nullptr; diff --git a/src/libutil/windows/muxable-pipe.cc b/src/libutil/windows/muxable-pipe.cc index f258f0f414b..c005118007b 100644 --- a/src/libutil/windows/muxable-pipe.cc +++ b/src/libutil/windows/muxable-pipe.cc @@ -7,15 +7,17 @@ namespace nix { +using namespace nix::windows; + void MuxablePipePollState::poll(HANDLE ioport, std::optional timeout) { /* We are on at least Windows Vista / Server 2008 and can get many (countof(oentries)) statuses in one API call. */ if (!GetQueuedCompletionStatusEx( ioport, oentries, sizeof(oentries) / sizeof(*oentries), &removed, timeout ? *timeout : INFINITE, false)) { - windows::WinError winError("GetQueuedCompletionStatusEx"); - if (winError.lastError != WAIT_TIMEOUT) - throw winError; + auto lastError = GetLastError(); + if (lastError != WAIT_TIMEOUT) + throw WinError(lastError, "GetQueuedCompletionStatusEx"); assert(removed == 0); } else { assert(0 < removed && removed <= sizeof(oentries) / sizeof(*oentries)); @@ -52,12 +54,12 @@ void MuxablePipePollState::iterate( // here is possible (but not obligatory) to call // `handleRead` and repeat ReadFile immediately } else { - windows::WinError winError("ReadFile(%s, ..)", (*p)->readSide.get()); - if (winError.lastError == ERROR_BROKEN_PIPE) { + auto lastError = GetLastError(); + if (lastError == ERROR_BROKEN_PIPE) { handleEOF((*p)->readSide.get()); nextp = channels.erase(p); // no need to maintain `channels` ? - } else if (winError.lastError != ERROR_IO_PENDING) - throw winError; + } else if (lastError != ERROR_IO_PENDING) + throw WinError(lastError, "ReadFile(%s, ..)", (*p)->readSide.get()); } } break; diff --git a/src/libutil/windows/windows-error.cc b/src/libutil/windows/windows-error.cc index f04b70a2161..2e2c8d8d694 100644 --- a/src/libutil/windows/windows-error.cc +++ b/src/libutil/windows/windows-error.cc @@ -6,9 +6,9 @@ # define WIN32_LEAN_AND_MEAN # include -namespace nix::windows { +namespace nix { -std::string WinError::renderError(DWORD lastError) +std::string SystemError::renderWindowsError(DWORD lastError) { LPSTR errorText = NULL; @@ -32,5 +32,5 @@ std::string WinError::renderError(DWORD lastError) return fmt("CODE=%d", lastError); } -} // namespace nix::windows +} // namespace nix #endif diff --git a/src/nix/man-pages.cc b/src/nix/man-pages.cc index 9b01307a13c..0aeea97247d 100644 --- a/src/nix/man-pages.cc +++ b/src/nix/man-pages.cc @@ -17,7 +17,7 @@ void showManPage(const std::string & name) setEnv("MANPATH", (getNixManDir().string() + ":").c_str()); execlp("man", "man", name.c_str(), nullptr); if (errno == ENOENT) { - // Not SysError because we don't want to suffix the errno, aka No such file or directory. + // Not SystemError because we don't want to suffix the errno, aka No such file or directory. throw Error( "The '%1%' command was not found, but it is needed for '%2%' and some other '%3%' commands' help text. Perhaps you could install the '%1%' command?", "man",