| //===----------------------------------------------------------------------===// | 
 | // | 
 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | 
 | // See https://llvm.org/LICENSE.txt for license information. | 
 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | 
 | // | 
 | //===----------------------------------------------------------------------===// | 
 |  | 
 | // | 
 | // POSIX-like portability helper functions. | 
 | // | 
 | // These generally behave like the proper posix functions, with these | 
 | // exceptions: | 
 | // On Windows, they take paths in wchar_t* form, instead of char* form. | 
 | // The symlink() function is split into two frontends, symlink_file() | 
 | // and symlink_dir(). | 
 | // | 
 | // These are provided within an anonymous namespace within the detail | 
 | // namespace - callers need to include this header and call them as | 
 | // detail::function(), regardless of platform. | 
 | // | 
 |  | 
 | #ifndef POSIX_COMPAT_H | 
 | #define POSIX_COMPAT_H | 
 |  | 
 | #include <__assert> | 
 | #include <filesystem> | 
 |  | 
 | #include "filesystem_common.h" | 
 |  | 
 | #if defined(_LIBCPP_WIN32API) | 
 | # define WIN32_LEAN_AND_MEAN | 
 | # define NOMINMAX | 
 | # include <windows.h> | 
 | # include <io.h> | 
 | # include <winioctl.h> | 
 | #else | 
 | # include <unistd.h> | 
 | # include <sys/stat.h> | 
 | # include <sys/statvfs.h> | 
 | #endif | 
 | #include <time.h> | 
 |  | 
 | #if defined(_LIBCPP_WIN32API) | 
 | // This struct isn't defined in the normal Windows SDK, but only in the | 
 | // Windows Driver Kit. | 
 | struct LIBCPP_REPARSE_DATA_BUFFER { | 
 |   unsigned long  ReparseTag; | 
 |   unsigned short ReparseDataLength; | 
 |   unsigned short Reserved; | 
 |   union { | 
 |     struct { | 
 |       unsigned short SubstituteNameOffset; | 
 |       unsigned short SubstituteNameLength; | 
 |       unsigned short PrintNameOffset; | 
 |       unsigned short PrintNameLength; | 
 |       unsigned long  Flags; | 
 |       wchar_t        PathBuffer[1]; | 
 |     } SymbolicLinkReparseBuffer; | 
 |     struct { | 
 |       unsigned short SubstituteNameOffset; | 
 |       unsigned short SubstituteNameLength; | 
 |       unsigned short PrintNameOffset; | 
 |       unsigned short PrintNameLength; | 
 |       wchar_t        PathBuffer[1]; | 
 |     } MountPointReparseBuffer; | 
 |     struct { | 
 |       unsigned char DataBuffer[1]; | 
 |     } GenericReparseBuffer; | 
 |   }; | 
 | }; | 
 | #endif | 
 |  | 
 | _LIBCPP_BEGIN_NAMESPACE_FILESYSTEM | 
 |  | 
 | namespace detail { | 
 | namespace { | 
 |  | 
 | #if defined(_LIBCPP_WIN32API) | 
 |  | 
 | // Various C runtime header sets provide more or less of these. As we | 
 | // provide our own implementation, undef all potential defines from the | 
 | // C runtime headers and provide a complete set of macros of our own. | 
 |  | 
 | #undef _S_IFMT | 
 | #undef _S_IFDIR | 
 | #undef _S_IFCHR | 
 | #undef _S_IFIFO | 
 | #undef _S_IFREG | 
 | #undef _S_IFBLK | 
 | #undef _S_IFLNK | 
 | #undef _S_IFSOCK | 
 |  | 
 | #define _S_IFMT   0xF000 | 
 | #define _S_IFDIR  0x4000 | 
 | #define _S_IFCHR  0x2000 | 
 | #define _S_IFIFO  0x1000 | 
 | #define _S_IFREG  0x8000 | 
 | #define _S_IFBLK  0x6000 | 
 | #define _S_IFLNK  0xA000 | 
 | #define _S_IFSOCK 0xC000 | 
 |  | 
 | #undef S_ISDIR | 
 | #undef S_ISFIFO | 
 | #undef S_ISCHR | 
 | #undef S_ISREG | 
 | #undef S_ISLNK | 
 | #undef S_ISBLK | 
 | #undef S_ISSOCK | 
 |  | 
 | #define S_ISDIR(m)      (((m) & _S_IFMT) == _S_IFDIR) | 
 | #define S_ISCHR(m)      (((m) & _S_IFMT) == _S_IFCHR) | 
 | #define S_ISFIFO(m)     (((m) & _S_IFMT) == _S_IFIFO) | 
 | #define S_ISREG(m)      (((m) & _S_IFMT) == _S_IFREG) | 
 | #define S_ISBLK(m)      (((m) & _S_IFMT) == _S_IFBLK) | 
 | #define S_ISLNK(m)      (((m) & _S_IFMT) == _S_IFLNK) | 
 | #define S_ISSOCK(m)     (((m) & _S_IFMT) == _S_IFSOCK) | 
 |  | 
 | #define O_NONBLOCK 0 | 
 |  | 
 |  | 
 | // There were 369 years and 89 leap days from the Windows epoch | 
 | // (1601) to the Unix epoch (1970). | 
 | #define FILE_TIME_OFFSET_SECS (uint64_t(369 * 365 + 89) * (24 * 60 * 60)) | 
 |  | 
 | TimeSpec filetime_to_timespec(LARGE_INTEGER li) { | 
 |   TimeSpec ret; | 
 |   ret.tv_sec = li.QuadPart / 10000000 - FILE_TIME_OFFSET_SECS; | 
 |   ret.tv_nsec = (li.QuadPart % 10000000) * 100; | 
 |   return ret; | 
 | } | 
 |  | 
 | TimeSpec filetime_to_timespec(FILETIME ft) { | 
 |   LARGE_INTEGER li; | 
 |   li.LowPart = ft.dwLowDateTime; | 
 |   li.HighPart = ft.dwHighDateTime; | 
 |   return filetime_to_timespec(li); | 
 | } | 
 |  | 
 | FILETIME timespec_to_filetime(TimeSpec ts) { | 
 |   LARGE_INTEGER li; | 
 |   li.QuadPart = | 
 |       ts.tv_nsec / 100 + (ts.tv_sec + FILE_TIME_OFFSET_SECS) * 10000000; | 
 |   FILETIME ft; | 
 |   ft.dwLowDateTime = li.LowPart; | 
 |   ft.dwHighDateTime = li.HighPart; | 
 |   return ft; | 
 | } | 
 |  | 
 | int set_errno(int e = GetLastError()) { | 
 |   errno = static_cast<int>(__win_err_to_errc(e)); | 
 |   return -1; | 
 | } | 
 |  | 
 | class WinHandle { | 
 | public: | 
 |   WinHandle(const wchar_t *p, DWORD access, DWORD flags) { | 
 |     h = CreateFileW( | 
 |         p, access, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, | 
 |         nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | flags, nullptr); | 
 |   } | 
 |   ~WinHandle() { | 
 |     if (h != INVALID_HANDLE_VALUE) | 
 |       CloseHandle(h); | 
 |   } | 
 |   operator HANDLE() const { return h; } | 
 |   operator bool() const { return h != INVALID_HANDLE_VALUE; } | 
 |  | 
 | private: | 
 |   HANDLE h; | 
 | }; | 
 |  | 
 | int stat_handle(HANDLE h, StatT *buf) { | 
 |   FILE_BASIC_INFO basic; | 
 |   if (!GetFileInformationByHandleEx(h, FileBasicInfo, &basic, sizeof(basic))) | 
 |     return set_errno(); | 
 |   memset(buf, 0, sizeof(*buf)); | 
 |   buf->st_mtim = filetime_to_timespec(basic.LastWriteTime); | 
 |   buf->st_atim = filetime_to_timespec(basic.LastAccessTime); | 
 |   buf->st_mode = 0555; // Read-only | 
 |   if (!(basic.FileAttributes & FILE_ATTRIBUTE_READONLY)) | 
 |     buf->st_mode |= 0222; // Write | 
 |   if (basic.FileAttributes & FILE_ATTRIBUTE_DIRECTORY) { | 
 |     buf->st_mode |= _S_IFDIR; | 
 |   } else { | 
 |     buf->st_mode |= _S_IFREG; | 
 |   } | 
 |   if (basic.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) { | 
 |     FILE_ATTRIBUTE_TAG_INFO tag; | 
 |     if (!GetFileInformationByHandleEx(h, FileAttributeTagInfo, &tag, | 
 |                                       sizeof(tag))) | 
 |       return set_errno(); | 
 |     if (tag.ReparseTag == IO_REPARSE_TAG_SYMLINK) | 
 |       buf->st_mode = (buf->st_mode & ~_S_IFMT) | _S_IFLNK; | 
 |   } | 
 |   FILE_STANDARD_INFO standard; | 
 |   if (!GetFileInformationByHandleEx(h, FileStandardInfo, &standard, | 
 |                                     sizeof(standard))) | 
 |     return set_errno(); | 
 |   buf->st_nlink = standard.NumberOfLinks; | 
 |   buf->st_size = standard.EndOfFile.QuadPart; | 
 |   BY_HANDLE_FILE_INFORMATION info; | 
 |   if (!GetFileInformationByHandle(h, &info)) | 
 |     return set_errno(); | 
 |   buf->st_dev = info.dwVolumeSerialNumber; | 
 |   memcpy(&buf->st_ino.id[0], &info.nFileIndexHigh, 4); | 
 |   memcpy(&buf->st_ino.id[4], &info.nFileIndexLow, 4); | 
 |   return 0; | 
 | } | 
 |  | 
 | int stat_file(const wchar_t *path, StatT *buf, DWORD flags) { | 
 |   WinHandle h(path, FILE_READ_ATTRIBUTES, flags); | 
 |   if (!h) | 
 |     return set_errno(); | 
 |   int ret = stat_handle(h, buf); | 
 |   return ret; | 
 | } | 
 |  | 
 | int stat(const wchar_t *path, StatT *buf) { return stat_file(path, buf, 0); } | 
 |  | 
 | int lstat(const wchar_t *path, StatT *buf) { | 
 |   return stat_file(path, buf, FILE_FLAG_OPEN_REPARSE_POINT); | 
 | } | 
 |  | 
 | int fstat(int fd, StatT *buf) { | 
 |   HANDLE h = reinterpret_cast<HANDLE>(_get_osfhandle(fd)); | 
 |   return stat_handle(h, buf); | 
 | } | 
 |  | 
 | int mkdir(const wchar_t *path, int permissions) { | 
 |   (void)permissions; | 
 |   return _wmkdir(path); | 
 | } | 
 |  | 
 | int symlink_file_dir(const wchar_t *oldname, const wchar_t *newname, | 
 |                      bool is_dir) { | 
 |   path dest(oldname); | 
 |   dest.make_preferred(); | 
 |   oldname = dest.c_str(); | 
 |   DWORD flags = is_dir ? SYMBOLIC_LINK_FLAG_DIRECTORY : 0; | 
 |   if (CreateSymbolicLinkW(newname, oldname, | 
 |                           flags | SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE)) | 
 |     return 0; | 
 |   int e = GetLastError(); | 
 |   if (e != ERROR_INVALID_PARAMETER) | 
 |     return set_errno(e); | 
 |   if (CreateSymbolicLinkW(newname, oldname, flags)) | 
 |     return 0; | 
 |   return set_errno(); | 
 | } | 
 |  | 
 | int symlink_file(const wchar_t *oldname, const wchar_t *newname) { | 
 |   return symlink_file_dir(oldname, newname, false); | 
 | } | 
 |  | 
 | int symlink_dir(const wchar_t *oldname, const wchar_t *newname) { | 
 |   return symlink_file_dir(oldname, newname, true); | 
 | } | 
 |  | 
 | int link(const wchar_t *oldname, const wchar_t *newname) { | 
 |   if (CreateHardLinkW(newname, oldname, nullptr)) | 
 |     return 0; | 
 |   return set_errno(); | 
 | } | 
 |  | 
 | int remove(const wchar_t *path) { | 
 |   detail::WinHandle h(path, DELETE, FILE_FLAG_OPEN_REPARSE_POINT); | 
 |   if (!h) | 
 |     return set_errno(); | 
 |   FILE_DISPOSITION_INFO info; | 
 |   info.DeleteFile = TRUE; | 
 |   if (!SetFileInformationByHandle(h, FileDispositionInfo, &info, sizeof(info))) | 
 |     return set_errno(); | 
 |   return 0; | 
 | } | 
 |  | 
 | int truncate_handle(HANDLE h, off_t length) { | 
 |   LARGE_INTEGER size_param; | 
 |   size_param.QuadPart = length; | 
 |   if (!SetFilePointerEx(h, size_param, 0, FILE_BEGIN)) | 
 |     return set_errno(); | 
 |   if (!SetEndOfFile(h)) | 
 |     return set_errno(); | 
 |   return 0; | 
 | } | 
 |  | 
 | int ftruncate(int fd, off_t length) { | 
 |   HANDLE h = reinterpret_cast<HANDLE>(_get_osfhandle(fd)); | 
 |   return truncate_handle(h, length); | 
 | } | 
 |  | 
 | int truncate(const wchar_t *path, off_t length) { | 
 |   detail::WinHandle h(path, GENERIC_WRITE, 0); | 
 |   if (!h) | 
 |     return set_errno(); | 
 |   return truncate_handle(h, length); | 
 | } | 
 |  | 
 | int rename(const wchar_t *from, const wchar_t *to) { | 
 |   if (!(MoveFileExW(from, to, | 
 |                     MOVEFILE_COPY_ALLOWED | MOVEFILE_REPLACE_EXISTING | | 
 |                         MOVEFILE_WRITE_THROUGH))) | 
 |     return set_errno(); | 
 |   return 0; | 
 | } | 
 |  | 
 | template <class... Args> int open(const wchar_t *filename, Args... args) { | 
 |   return _wopen(filename, args...); | 
 | } | 
 | int close(int fd) { return _close(fd); } | 
 | int chdir(const wchar_t *path) { return _wchdir(path); } | 
 |  | 
 | struct StatVFS { | 
 |   uint64_t f_frsize; | 
 |   uint64_t f_blocks; | 
 |   uint64_t f_bfree; | 
 |   uint64_t f_bavail; | 
 | }; | 
 |  | 
 | int statvfs(const wchar_t *p, StatVFS *buf) { | 
 |   path dir = p; | 
 |   while (true) { | 
 |     error_code local_ec; | 
 |     const file_status st = status(dir, local_ec); | 
 |     if (!exists(st) || is_directory(st)) | 
 |       break; | 
 |     path parent = dir.parent_path(); | 
 |     if (parent == dir) { | 
 |       errno = ENOENT; | 
 |       return -1; | 
 |     } | 
 |     dir = parent; | 
 |   } | 
 |   ULARGE_INTEGER free_bytes_available_to_caller, total_number_of_bytes, | 
 |       total_number_of_free_bytes; | 
 |   if (!GetDiskFreeSpaceExW(dir.c_str(), &free_bytes_available_to_caller, | 
 |                            &total_number_of_bytes, &total_number_of_free_bytes)) | 
 |     return set_errno(); | 
 |   buf->f_frsize = 1; | 
 |   buf->f_blocks = total_number_of_bytes.QuadPart; | 
 |   buf->f_bfree = total_number_of_free_bytes.QuadPart; | 
 |   buf->f_bavail = free_bytes_available_to_caller.QuadPart; | 
 |   return 0; | 
 | } | 
 |  | 
 | wchar_t *getcwd(wchar_t *buff, size_t size) { return _wgetcwd(buff, size); } | 
 |  | 
 | wchar_t *realpath(const wchar_t *path, wchar_t *resolved_name) { | 
 |   // Only expected to be used with us allocating the buffer. | 
 |   _LIBCPP_ASSERT(resolved_name == nullptr, | 
 |                  "Windows realpath() assumes a null resolved_name"); | 
 |  | 
 |   WinHandle h(path, FILE_READ_ATTRIBUTES, 0); | 
 |   if (!h) { | 
 |     set_errno(); | 
 |     return nullptr; | 
 |   } | 
 |   size_t buff_size = MAX_PATH + 10; | 
 |   std::unique_ptr<wchar_t, decltype(&::free)> buff( | 
 |       static_cast<wchar_t *>(malloc(buff_size * sizeof(wchar_t))), &::free); | 
 |   DWORD retval = GetFinalPathNameByHandleW( | 
 |       h, buff.get(), buff_size, FILE_NAME_NORMALIZED | VOLUME_NAME_DOS); | 
 |   if (retval > buff_size) { | 
 |     buff_size = retval; | 
 |     buff.reset(static_cast<wchar_t *>(malloc(buff_size * sizeof(wchar_t)))); | 
 |     retval = GetFinalPathNameByHandleW(h, buff.get(), buff_size, | 
 |                                        FILE_NAME_NORMALIZED | VOLUME_NAME_DOS); | 
 |   } | 
 |   if (!retval) { | 
 |     set_errno(); | 
 |     return nullptr; | 
 |   } | 
 |   wchar_t *ptr = buff.get(); | 
 |   if (!wcsncmp(ptr, L"\\\\?\\", 4)) { | 
 |     if (ptr[5] == ':') { // \\?\X: -> X: | 
 |       memmove(&ptr[0], &ptr[4], (wcslen(&ptr[4]) + 1) * sizeof(wchar_t)); | 
 |     } else if (!wcsncmp(&ptr[4], L"UNC\\", 4)) { // \\?\UNC\server -> \\server | 
 |       wcscpy(&ptr[0], L"\\\\"); | 
 |       memmove(&ptr[2], &ptr[8], (wcslen(&ptr[8]) + 1) * sizeof(wchar_t)); | 
 |     } | 
 |   } | 
 |   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<HANDLE>(_get_osfhandle(fd)); | 
 |   return fchmod_handle(h, perms); | 
 | } | 
 |  | 
 | #define MAX_SYMLINK_SIZE MAXIMUM_REPARSE_DATA_BUFFER_SIZE | 
 | using SSizeT = ::int64_t; | 
 |  | 
 | SSizeT readlink(const wchar_t *path, wchar_t *ret_buf, size_t bufsize) { | 
 |   uint8_t buf[MAXIMUM_REPARSE_DATA_BUFFER_SIZE]; | 
 |   detail::WinHandle h(path, FILE_READ_ATTRIBUTES, FILE_FLAG_OPEN_REPARSE_POINT); | 
 |   if (!h) | 
 |     return set_errno(); | 
 |   DWORD out; | 
 |   if (!DeviceIoControl(h, FSCTL_GET_REPARSE_POINT, nullptr, 0, buf, sizeof(buf), | 
 |                        &out, 0)) | 
 |     return set_errno(); | 
 |   const auto *reparse = reinterpret_cast<LIBCPP_REPARSE_DATA_BUFFER *>(buf); | 
 |   size_t path_buf_offset = offsetof(LIBCPP_REPARSE_DATA_BUFFER, | 
 |                                     SymbolicLinkReparseBuffer.PathBuffer[0]); | 
 |   if (out < path_buf_offset) { | 
 |     errno = EINVAL; | 
 |     return -1; | 
 |   } | 
 |   if (reparse->ReparseTag != IO_REPARSE_TAG_SYMLINK) { | 
 |     errno = EINVAL; | 
 |     return -1; | 
 |   } | 
 |   const auto &symlink = reparse->SymbolicLinkReparseBuffer; | 
 |   unsigned short name_offset, name_length; | 
 |   if (symlink.PrintNameLength == 0) { | 
 |     name_offset = symlink.SubstituteNameOffset; | 
 |     name_length = symlink.SubstituteNameLength; | 
 |   } else { | 
 |     name_offset = symlink.PrintNameOffset; | 
 |     name_length = symlink.PrintNameLength; | 
 |   } | 
 |   // name_offset/length are expressed in bytes, not in wchar_t | 
 |   if (path_buf_offset + name_offset + name_length > out) { | 
 |     errno = EINVAL; | 
 |     return -1; | 
 |   } | 
 |   if (name_length / sizeof(wchar_t) > bufsize) { | 
 |     errno = ENOMEM; | 
 |     return -1; | 
 |   } | 
 |   memcpy(ret_buf, &symlink.PathBuffer[name_offset / sizeof(wchar_t)], | 
 |          name_length); | 
 |   return name_length / sizeof(wchar_t); | 
 | } | 
 |  | 
 | #else | 
 | int symlink_file(const char *oldname, const char *newname) { | 
 |   return ::symlink(oldname, newname); | 
 | } | 
 | int symlink_dir(const char *oldname, const char *newname) { | 
 |   return ::symlink(oldname, newname); | 
 | } | 
 | using ::chdir; | 
 | using ::close; | 
 | using ::fchmod; | 
 | #if defined(AT_SYMLINK_NOFOLLOW) && defined(AT_FDCWD) | 
 | using ::fchmodat; | 
 | #endif | 
 | using ::fstat; | 
 | using ::ftruncate; | 
 | using ::getcwd; | 
 | using ::link; | 
 | using ::lstat; | 
 | using ::mkdir; | 
 | using ::open; | 
 | using ::readlink; | 
 | using ::realpath; | 
 | using ::remove; | 
 | using ::rename; | 
 | using ::stat; | 
 | using ::statvfs; | 
 | using ::truncate; | 
 |  | 
 | #define O_BINARY 0 | 
 |  | 
 | using StatVFS = struct statvfs; | 
 | using ModeT = ::mode_t; | 
 | using SSizeT = ::ssize_t; | 
 |  | 
 | #endif | 
 |  | 
 | } // namespace | 
 | } // end namespace detail | 
 |  | 
 | _LIBCPP_END_NAMESPACE_FILESYSTEM | 
 |  | 
 | #endif // POSIX_COMPAT_H |