diff --git a/libcxx/src/filesystem/operations.cpp b/libcxx/src/filesystem/operations.cpp --- a/libcxx/src/filesystem/operations.cpp +++ b/libcxx/src/filesystem/operations.cpp @@ -455,10 +455,6 @@ return static_cast(st.st_mode) & perms::mask; } -::mode_t posix_convert_perms(perms prms) { - return static_cast< ::mode_t>(prms & perms::mask); -} - file_status create_file_status(error_code& m_ec, path const& p, const StatT& path_stat, error_code* ec) { if (ec) @@ -530,7 +526,7 @@ } bool posix_fchmod(const FileDescriptor& fd, const StatT& st, error_code& ec) { - if (::fchmod(fd.fd, st.st_mode) == -1) { + if (detail::fchmod(fd.fd, st.st_mode) == -1) { ec = capture_errno(); return true; } @@ -1212,11 +1208,11 @@ else if (remove_perms) prms = st.permissions() & ~prms; } - const auto real_perms = detail::posix_convert_perms(prms); + const auto real_perms = static_cast(prms & perms::mask); #if defined(AT_SYMLINK_NOFOLLOW) && defined(AT_FDCWD) const int flags = set_sym_perms ? AT_SYMLINK_NOFOLLOW : 0; - if (::fchmodat(AT_FDCWD, p.c_str(), real_perms, flags) == -1) { + if (detail::fchmodat(AT_FDCWD, p.c_str(), real_perms, flags) == -1) { return err.report(capture_errno()); } #else diff --git a/libcxx/src/filesystem/posix_compat.h b/libcxx/src/filesystem/posix_compat.h --- a/libcxx/src/filesystem/posix_compat.h +++ b/libcxx/src/filesystem/posix_compat.h @@ -349,6 +349,57 @@ } return buff.release(); } + +#define AT_FDCWD -1 +#define AT_SYMLINK_NOFOLLOW 1 +using ModeT = int; + +int fchmod_handle(HANDLE h, int perms) { + FILE_BASIC_INFO basic; + if (!GetFileInformationByHandleEx(h, FileBasicInfo, &basic, sizeof(basic))) + return set_errno(); + DWORD orig_attributes = basic.FileAttributes; + basic.FileAttributes &= ~FILE_ATTRIBUTE_READONLY; + if ((perms & 0222) == 0) + basic.FileAttributes |= FILE_ATTRIBUTE_READONLY; + if (basic.FileAttributes != orig_attributes && + !SetFileInformationByHandle(h, FileBasicInfo, &basic, sizeof(basic))) + return set_errno(); + return 0; +} + +int fchmodat(int fd, const wchar_t *path, int perms, int flag) { + DWORD attributes = GetFileAttributesW(path); + if (attributes == INVALID_FILE_ATTRIBUTES) + return set_errno(); + if (attributes & FILE_ATTRIBUTE_REPARSE_POINT && + !(flag & AT_SYMLINK_NOFOLLOW)) { + // If the file is a symlink, and we are supposed to operate on the target + // of the symlink, we need to open a handle to it, without the + // FILE_FLAG_OPEN_REPARSE_POINT flag, to open the destination of the + // symlink, and operate on it via the handle. + detail::WinHandle h(path, FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES, 0); + if (!h) + return set_errno(); + return fchmod_handle(h, perms); + } else { + // For a non-symlink, or if operating on the symlink itself instead of + // its target, we can use SetFileAttributesW, saving a few calls. + DWORD orig_attributes = attributes; + attributes &= ~FILE_ATTRIBUTE_READONLY; + if ((perms & 0222) == 0) + attributes |= FILE_ATTRIBUTE_READONLY; + if (attributes != orig_attributes && !SetFileAttributesW(path, attributes)) + return set_errno(); + } + return 0; +} + +int fchmod(int fd, int perms) { + HANDLE h = reinterpret_cast(_get_osfhandle(fd)); + return fchmod_handle(h, perms); +} + #else int symlink_file(const char *oldname, const char *newname) { return ::symlink(oldname, newname); @@ -358,6 +409,8 @@ } using ::chdir; using ::close; +using ::fchmod; +using ::fchmodat; using ::fstat; using ::ftruncate; using ::getcwd; @@ -375,6 +428,7 @@ #define O_BINARY 0 using StatVFS = struct statvfs; +using ModeT = ::mode_t; #endif