Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 14 additions & 16 deletions src/libstore/windows/pathlocks.cc
Original file line number Diff line number Diff line change
Expand Up @@ -56,44 +56,42 @@ 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;
}
case ltWrite: {
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;
}
Expand Down
14 changes: 7 additions & 7 deletions src/libutil-tests/unix/file-system-at.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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. */
}

Expand Down
232 changes: 121 additions & 111 deletions src/libutil/include/nix/util/error.hh
Original file line number Diff line number Diff line change
Expand Up @@ -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<typename... Args>
SystemError(Disambig, std::error_code errorCode, std::string_view errorDetails, Args &&... args)
Expand All @@ -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<typename... Args>
static SystemError fromPosixExplicit(int errNo, Args &&... args)
{
return errorCode;
return SystemError(
Disambig{},
std::make_error_code(static_cast<std::errc>(errNo)),
strerror(errNo),
std::forward<Args>(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<typename... Args>
static SystemError fromPosix(Args &&... args)
{
return errorCode == e;
return fromPosixExplicit(errno, std::forward<Args>(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<typename... Args>
SysError(int errNo, Args &&... args)
: SystemError(
Disambig{},
std::make_error_code(static_cast<std::errc>(errNo)),
strerror(errNo),
std::forward<Args>(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>(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<typename... Args>
static SystemError fromWindows(Args &&... args)
{
return fromWindowsExplicit(GetLastError(), std::forward<Args>(args)...);
}

private:
static std::string renderWindowsError(DWORD lastError);

public:
#endif

/**
* Construct using the native error (errno on POSIX, GetLastError() on Windows).
*/
template<typename... Args>
SysError(Args &&... args)
: SysError(errno, std::forward<Args>(args)...)
static SystemError fromNative(Args &&... args)
{
#ifdef _WIN32
return fromWindows(std::forward<Args>(args)...);
#else
return fromPosix(std::forward<Args>(args)...);
#endif
}

const std::error_code ec() const &
{
return errorCode;
}

bool is(std::errc e) const
{
return errorCode == e;
}
};

/**
* Wrapper to avoid churn
*/
template<typename... Args>
SystemError SysError(int errNo, Args &&... args)
{
return SystemError::fromPosixExplicit(errNo, std::forward<Args>(args)...);
}

/**
* Wrapper to avoid churn
*/
template<typename... Args>
SystemError SysError(Args &&... args)
{
return SystemError::fromPosix(std::forward<Args>(args)...);
}

#ifdef _WIN32

namespace windows {

/**
* Wrapper to avoid churn
*/
template<typename... Args>
SystemError WinError(DWORD lastError, Args &&... args)
{
return SystemError::fromWindowsExplicit(lastError, std::forward<Args>(args)...);
}

/**
* Wrapper to avoid churn
*/
template<typename... Args>
SystemError WinError(Args &&... args)
{
return SystemError::fromWindows(std::forward<Args>(args)...);
}

} // namespace windows

#endif

/**
* Convenience wrapper for when we use `errno`-based error handling
* on Unix, and `GetLastError()`-based error handling on Windows.
*/
template<typename... Args>
SystemError NativeSysError(Args &&... args)
{
return SystemError::fromNative(std::forward<Args>(args)...);
}

/**
* Throw an exception for the purpose of checking that exception
* handling works; see 'initLibUtil()'.
Expand Down Expand Up @@ -382,69 +457,4 @@ int handleExceptions(const std::string & programName, std::function<void()> 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<typename... Args>
WinError(DWORD lastError, Args &&... args)
: SystemError(
Disambig{},
std::error_code(lastError, std::system_category()),
renderError(lastError),
std::forward<Args>(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<typename... Args>
WinError(Args &&... args)
: WinError(GetLastError(), std::forward<Args>(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
2 changes: 1 addition & 1 deletion src/libutil/include/nix/util/file-system-at.hh
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
2 changes: 1 addition & 1 deletion src/libutil/include/nix/util/file-system.hh
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
Loading
Loading