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 @@ -1227,21 +1227,25 @@ path __read_symlink(const path& p, error_code* ec) { ErrorHandler err("read_symlink", ec, &p); -#ifdef PATH_MAX +#if defined(PATH_MAX) || defined(MAX_SYMLINK_SIZE) struct NullDeleter { void operator()(void*) const {} }; +#ifdef MAX_SYMLINK_SIZE + const size_t size = MAX_SYMLINK_SIZE + 1; +#else const size_t size = PATH_MAX + 1; - char stack_buff[size]; - auto buff = std::unique_ptr(stack_buff); +#endif + path::value_type stack_buff[size]; + auto buff = std::unique_ptr(stack_buff); #else StatT sb; if (detail::lstat(p.c_str(), &sb) == -1) { return err.report(capture_errno()); } const size_t size = sb.st_size + 1; - auto buff = unique_ptr(new char[size]); + auto buff = unique_ptr(new path::value_type[size]); #endif - ::ssize_t ret; - if ((ret = ::readlink(p.c_str(), buff.get(), size)) == -1) + detail::SSizeT ret; + if ((ret = detail::readlink(p.c_str(), buff.get(), size)) == -1) return err.report(capture_errno()); _LIBCPP_ASSERT(ret > 0, "TODO"); if (static_cast(ret) >= size) 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 @@ -32,6 +32,7 @@ # define NOMINMAX # include # include +# include #else # include # include @@ -39,6 +40,36 @@ #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 { @@ -400,6 +431,52 @@ 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); @@ -418,6 +495,7 @@ using ::lstat; using ::mkdir; using ::open; +using ::readlink; using ::realpath; using ::remove; using ::rename; @@ -429,6 +507,7 @@ using StatVFS = struct statvfs; using ModeT = ::mode_t; +using SSizeT = ::ssize_t; #endif