//===----------------------------------------------------------------------===// // // 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 #include "filesystem_common.h" #if defined(_LIBCPP_WIN32API) # define WIN32_LEAN_AND_MEAN # define NOMINMAX # include # include # include #else # include # include # include #endif #include #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(__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(_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(_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 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 buff( static_cast(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(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(_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(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