Skip to content

Commit

Permalink
refs #124, mapped folder/volumes where wrongly handled as symlinks, f…
Browse files Browse the repository at this point in the history
…ailing fs::canonical
  • Loading branch information
gulrak committed May 23, 2021
1 parent e65a6ba commit e51e6ee
Show file tree
Hide file tree
Showing 2 changed files with 101 additions and 53 deletions.
134 changes: 84 additions & 50 deletions include/ghc/filesystem.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2000,56 +2000,70 @@ GHC_INLINE file_status file_status_from_st_mode(T mode)
#endif
}

GHC_INLINE path resolveSymlink(const path& p, std::error_code& ec)
{
#ifdef GHC_OS_WINDOWS
#ifndef REPARSE_DATA_BUFFER_HEADER_SIZE
typedef struct _REPARSE_DATA_BUFFER
typedef struct _REPARSE_DATA_BUFFER
{
ULONG ReparseTag;
USHORT ReparseDataLength;
USHORT Reserved;
union
{
ULONG ReparseTag;
USHORT ReparseDataLength;
USHORT Reserved;
union
struct
{
USHORT SubstituteNameOffset;
USHORT SubstituteNameLength;
USHORT PrintNameOffset;
USHORT PrintNameLength;
ULONG Flags;
WCHAR PathBuffer[1];
} SymbolicLinkReparseBuffer;
struct
{
USHORT SubstituteNameOffset;
USHORT SubstituteNameLength;
USHORT PrintNameOffset;
USHORT PrintNameLength;
WCHAR PathBuffer[1];
} MountPointReparseBuffer;
struct
{
struct
{
USHORT SubstituteNameOffset;
USHORT SubstituteNameLength;
USHORT PrintNameOffset;
USHORT PrintNameLength;
ULONG Flags;
WCHAR PathBuffer[1];
} SymbolicLinkReparseBuffer;
struct
{
USHORT SubstituteNameOffset;
USHORT SubstituteNameLength;
USHORT PrintNameOffset;
USHORT PrintNameLength;
WCHAR PathBuffer[1];
} MountPointReparseBuffer;
struct
{
UCHAR DataBuffer[1];
} GenericReparseBuffer;
} DUMMYUNIONNAME;
} REPARSE_DATA_BUFFER;
UCHAR DataBuffer[1];
} GenericReparseBuffer;
} DUMMYUNIONNAME;
} REPARSE_DATA_BUFFER;
#ifndef MAXIMUM_REPARSE_DATA_BUFFER_SIZE
#define MAXIMUM_REPARSE_DATA_BUFFER_SIZE (16 * 1024)
#endif
#endif

GHC_INLINE std::shared_ptr<REPARSE_DATA_BUFFER> getReparseData(const path& p, std::error_code& ec)
{
std::shared_ptr<void> file(CreateFileW(GHC_NATIVEWP(p), 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0, OPEN_EXISTING, FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, 0), CloseHandle);
if (file.get() == INVALID_HANDLE_VALUE) {
ec = detail::make_system_error();
return path();
return nullptr;
}

std::shared_ptr<REPARSE_DATA_BUFFER> reparseData((REPARSE_DATA_BUFFER*)std::calloc(1, MAXIMUM_REPARSE_DATA_BUFFER_SIZE), std::free);
ULONG bufferUsed;
path result;
if (DeviceIoControl(file.get(), FSCTL_GET_REPARSE_POINT, 0, 0, reparseData.get(), MAXIMUM_REPARSE_DATA_BUFFER_SIZE, &bufferUsed, 0)) {
if (IsReparseTagMicrosoft(reparseData->ReparseTag)) {
return reparseData;
}
else {
ec = detail::make_system_error();
}
return nullptr;
}
#endif

GHC_INLINE path resolveSymlink(const path& p, std::error_code& ec)
{
#ifdef GHC_OS_WINDOWS
path result;
auto reparseData = detail::getReparseData(p, ec);
if (!ec) {
if (reparseData && IsReparseTagMicrosoft(reparseData->ReparseTag)) {
switch (reparseData->ReparseTag) {
case IO_REPARSE_TAG_SYMLINK: {
auto printName = std::wstring(&reparseData->SymbolicLinkReparseBuffer.PathBuffer[reparseData->SymbolicLinkReparseBuffer.PrintNameOffset / sizeof(WCHAR)], reparseData->SymbolicLinkReparseBuffer.PrintNameLength / sizeof(WCHAR));
Expand All @@ -2067,16 +2081,14 @@ GHC_INLINE path resolveSymlink(const path& p, std::error_code& ec)
break;
}
case IO_REPARSE_TAG_MOUNT_POINT:
result = std::wstring(&reparseData->MountPointReparseBuffer.PathBuffer[reparseData->MountPointReparseBuffer.SubstituteNameOffset / sizeof(WCHAR)], reparseData->MountPointReparseBuffer.SubstituteNameLength / sizeof(WCHAR));
result = detail::getFullPathName(GHC_NATIVEWP(p), ec);
//result = std::wstring(&reparseData->MountPointReparseBuffer.PathBuffer[reparseData->MountPointReparseBuffer.SubstituteNameOffset / sizeof(WCHAR)], reparseData->MountPointReparseBuffer.SubstituteNameLength / sizeof(WCHAR));
break;
default:
break;
}
}
}
else {
ec = detail::make_system_error();
}
return result;
#else
size_t bufferSize = 256;
Expand Down Expand Up @@ -2126,13 +2138,35 @@ GHC_INLINE uintmax_t hard_links_from_INFO<BY_HANDLE_FILE_INFORMATION>(const BY_H
}

template <typename INFO>
GHC_INLINE file_status status_from_INFO(const path& p, const INFO* info, std::error_code&, uintmax_t* sz = nullptr, time_t* lwt = nullptr)
GHC_INLINE DWORD reparse_tag_from_INFO(const INFO* info)
{
return 0;
}

template <>
GHC_INLINE DWORD reparse_tag_from_INFO(const WIN32_FIND_DATAW* info)
{
return info->dwReserved0;
}

template <typename INFO>
GHC_INLINE file_status status_from_INFO(const path& p, const INFO* info, std::error_code& ec, uintmax_t* sz = nullptr, time_t* lwt = nullptr)
{
file_type ft = file_type::unknown;
if ((info->dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)) {
ft = file_type::symlink;
if (sizeof(INFO) == sizeof(WIN32_FIND_DATAW)) {
if (detail::reparse_tag_from_INFO(info) == IO_REPARSE_TAG_SYMLINK) {
ft = file_type::symlink;
}
}
else {
if ((info->dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)) {
auto reparseData = detail::getReparseData(p, ec);
if (!ec && reparseData && IsReparseTagMicrosoft(reparseData->ReparseTag) && reparseData->ReparseTag == IO_REPARSE_TAG_SYMLINK) {
ft = file_type::symlink;
}
}
}
if (ft == file_type::unknown) {
if ((info->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
ft = file_type::directory;
}
Expand Down Expand Up @@ -2181,9 +2215,6 @@ GHC_INLINE file_status symlink_status_ex(const path& p, std::error_code& ec, uin
if (nhl) {
*nhl = 0;
}
if (attr.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) {
fs.type(file_type::symlink);
}
}
if (detail::is_not_found_error(ec)) {
return file_status(file_type::not_found);
Expand Down Expand Up @@ -2221,15 +2252,18 @@ GHC_INLINE file_status status_ex(const path& p, std::error_code& ec, file_status
ec = detail::make_system_error();
}
else if (attr.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) {
path target = resolveSymlink(p, ec);
file_status result;
if (!ec && !target.empty()) {
if (sls) {
*sls = status_from_INFO(p, &attr, ec);
auto reparseData = detail::getReparseData(p, ec);
if (!ec && reparseData && IsReparseTagMicrosoft(reparseData->ReparseTag) && reparseData->ReparseTag == IO_REPARSE_TAG_SYMLINK) {
path target = resolveSymlink(p, ec);
file_status result;
if (!ec && !target.empty()) {
if (sls) {
*sls = status_from_INFO(p, &attr, ec);
}
return detail::status_ex(target, ec, nullptr, sz, nhl, lwt, recurse_count + 1);
}
return detail::status_ex(target, ec, nullptr, sz, nhl, lwt, recurse_count + 1);
return file_status(file_type::unknown);
}
return file_status(file_type::unknown);
}
if (ec) {
if (detail::is_not_found_error(ec)) {
Expand Down
20 changes: 17 additions & 3 deletions test/filesystem_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2017,7 +2017,8 @@ TEST_CASE("fs.op.exists - exists", "[filesystem][operations][fs.op.exists]")
CHECK(!ec);
#if defined(GHC_OS_WINDOWS) && !defined(GHC_FILESYSTEM_FWD)
if (::GetFileAttributesW(L"C:\\fs-test") != INVALID_FILE_ATTRIBUTES) {
CHECK(fs::exists("C:\\fs-test"));
CHECK(fs::exists("C:\\fs-test"));
CHECK(!fs::is_symlink("C:\\fs-test"));
}
#endif
}
Expand Down Expand Up @@ -2810,7 +2811,7 @@ TEST_CASE("std::string_view support", "[filesystem][fs.string_view]")
#endif
}

TEST_CASE("Windows: Long filename support", "[filesystem][path][fs.path.win.long]")
TEST_CASE("Windows: Long filename support", "[filesystem][path][fs.win][fs.path.win.long]")
{
#ifdef GHC_OS_WINDOWS
TemporaryDirectory t(TempOpt::change_path);
Expand All @@ -2835,7 +2836,7 @@ TEST_CASE("Windows: Long filename support", "[filesystem][path][fs.path.win.long
#endif
}

TEST_CASE("Windows: path namespace handling", "[filesystem][path][fs.path.win.namespaces]")
TEST_CASE("Windows: path namespace handling", "[filesystem][path][fs.win][fs.path.win.namespaces]")
{
#ifdef GHC_OS_WINDOWS
{
Expand Down Expand Up @@ -2889,3 +2890,16 @@ TEST_CASE("Windows: path namespace handling", "[filesystem][path][fs.path.win.na
WARN("Windows specific tests are empty on non-Windows systems.");
#endif
}

TEST_CASE("Windows: Mapped folders handling ", "[filesystem][fs.win][fs.win.mapped]")
{
#ifdef GHC_OS_WINDOWS
// this test expects a mapped volume on C:\\fs-test as is the case on the development test system
// does nothing on other systems
if (fs::exists("C:\\fs-test")) {
CHECK(fs::canonical("C:\\fs-test\\Test.txt").string() == "C:\\fs-test\\Test.txt");
}
#else
WARN("Windows specific tests are empty on non-Windows systems.");
#endif
}

0 comments on commit e51e6ee

Please sign in to comment.