From ed9e019b01559f9bf0f6edb6d2e9e706e3071fa2 Mon Sep 17 00:00:00 2001 From: Roman Savchenko Date: Wed, 6 Feb 2019 18:25:20 +0200 Subject: [PATCH 1/2] Add test for reparse point that is not symlink --- test/Jamfile.v2 | 1 + test/issues/reparce_tag_file_placeholder.cpp | 142 +++++++++++++++++++ 2 files changed, 143 insertions(+) create mode 100644 test/issues/reparce_tag_file_placeholder.cpp diff --git a/test/Jamfile.v2 b/test/Jamfile.v2 index 01d0158..c19111d 100644 --- a/test/Jamfile.v2 +++ b/test/Jamfile.v2 @@ -78,3 +78,4 @@ run quick.cpp ; run issues/70-71-copy.cpp ; run issues/99_canonical_with_junction_point.cpp : : : @check-mklink ; +run issues/reparce_tag_file_placeholder.cpp : : : @check-mklink ; diff --git a/test/issues/reparce_tag_file_placeholder.cpp b/test/issues/reparce_tag_file_placeholder.cpp new file mode 100644 index 0000000..445f8e3 --- /dev/null +++ b/test/issues/reparce_tag_file_placeholder.cpp @@ -0,0 +1,142 @@ +// Boost operations_test.cpp ---------------------------------------------------------// + +// Copyright Roman Savchenko 2020 + +// Distributed under the Boost Software License, Version 1.0. +// See http://www.boost.org/LICENSE_1_0.txt + +// Library home page: http://www.boost.org/libs/filesystem + +#include +#include + +#include +#include + +# include +# include + +#ifdef _MSC_VER +# pragma comment(lib, "Advapi32.lib") +#endif + +// Test correct boost::filesystem::status when reparse point ReparseTag set to IO_REPARSE_TAG_FILE_PLACEHOLDER +// https://docs.microsoft.com/en-us/windows/compatibility/placeholder-files?redirectedfrom=MSDN + +#if !defined(__MINGW32__) || defined(__MINGW64__) +typedef struct _REPARSE_DATA_BUFFER { + 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 { + UCHAR DataBuffer[1]; + } GenericReparseBuffer; + }; +} REPARSE_DATA_BUFFER, *PREPARSE_DATA_BUFFER; +#endif + +#ifndef IO_REPARSE_TAG_FILE_PLACEHOLDER +# define IO_REPARSE_TAG_FILE_PLACEHOLDER (0x80000015L) +#endif + +#ifndef FSCTL_SET_REPARSE_POINT +# define FSCTL_SET_REPARSE_POINT (0x000900a4) +#endif + +#ifndef REPARSE_DATA_BUFFER_HEADER_SIZE +# define REPARSE_DATA_BUFFER_HEADER_SIZE FIELD_OFFSET(REPARSE_DATA_BUFFER, GenericReparseBuffer) +#endif + +bool obtain_restore_privilege() +{ + HANDLE hToken; + if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken)) + { + std::cout << "OpenProcessToken() failed with: " << GetLastError() << std::endl; + return false; + } + + TOKEN_PRIVILEGES tp; + if (!LookupPrivilegeValue(NULL, SE_RESTORE_NAME, &tp.Privileges[0].Luid)) + { + CloseHandle(hToken); + std::cout << "LookupPrivilegeValue() failed with: " << GetLastError() << std::endl; + return false; + } + + tp.PrivilegeCount = 1; + tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; + + if (!AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), NULL, NULL)) + { + CloseHandle(hToken); + std::cout << "AdjustTokenPrivileges() failed with: " << GetLastError() << std::endl; + return false; + } + + CloseHandle(hToken); + return true; +} + +bool create_io_reparse_file_placeholder(const wchar_t* name) +{ + if (!obtain_restore_privilege()) + { + return false; + } + + HANDLE hHandle = CreateFileW(name, GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, + FILE_FLAG_OPEN_REPARSE_POINT, 0); + + if (hHandle == INVALID_HANDLE_VALUE) + { + std::cout << "CreateFile() failed with: " << GetLastError() << std::endl; + return false; + } + + PREPARSE_DATA_BUFFER pReparse = reinterpret_cast(GlobalAlloc(GPTR, MAXIMUM_REPARSE_DATA_BUFFER_SIZE)); + //note: IO_REPARSE_TAG_FILE_PLACEHOLDER - just to show that reparse point could be not only symlink or junction + pReparse->ReparseTag = IO_REPARSE_TAG_FILE_PLACEHOLDER; + + DWORD dwLen; + bool ret = DeviceIoControl(hHandle, FSCTL_SET_REPARSE_POINT, pReparse, + pReparse->ReparseDataLength + REPARSE_DATA_BUFFER_HEADER_SIZE, + NULL, 0, &dwLen, NULL) != 0; + + if (!ret) + { + std::cout << "DeviceIoControl() failed with: " << GetLastError() << std::endl; + } + + CloseHandle(hHandle); + GlobalFree(pReparse); + return ret; +} + +int main() +{ + boost::filesystem::path rpt = boost::filesystem::temp_directory_path() / "reparse_point_test.txt"; + + BOOST_TEST(create_io_reparse_file_placeholder(rpt.native().c_str())); + BOOST_TEST(boost::filesystem::status(rpt).type() == boost::filesystem::reparse_file); + BOOST_TEST(boost::filesystem::remove(rpt)); + + return boost::report_errors(); +} From d3e3f46ce6d8527e856a45f675fb85e224f1309c Mon Sep 17 00:00:00 2001 From: Roman Savchenko Date: Fri, 8 Jun 2018 19:23:54 +0300 Subject: [PATCH 2/2] Correct handling of status() for reparse point when it is not a symlink. --- src/operations.cpp | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/src/operations.cpp b/src/operations.cpp index a664fd9..791a0c1 100644 --- a/src/operations.cpp +++ b/src/operations.cpp @@ -1864,6 +1864,8 @@ file_status status(const path& p, error_code* ec) return process_status_failure(p, ec); } + if (ec != 0) ec->clear(); + perms permissions = make_permissions(p, attr); // reparse point handling; @@ -1871,6 +1873,12 @@ file_status status(const path& p, error_code* ec) // handle to discover if the file exists if (attr & FILE_ATTRIBUTE_REPARSE_POINT) { + if (!is_reparse_point_a_symlink(p)) + { + return file_status(reparse_file, permissions); + } + + // try to resolve symlink handle_wrapper h( create_file_handle( p.c_str(), @@ -1880,16 +1888,22 @@ file_status status(const path& p, error_code* ec) OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0)); // hTemplateFile + if (h.handle == INVALID_HANDLE_VALUE) { return process_status_failure(p, ec); } - if (!is_reparse_point_a_symlink(p)) - return file_status(reparse_file, permissions); + // take attributes of target + BY_HANDLE_FILE_INFORMATION info; + if (!::GetFileInformationByHandle(h.handle, &info)) + { + return process_status_failure(p, ec); + } + + attr = info.dwFileAttributes; } - if (ec != 0) ec->clear(); return (attr & FILE_ATTRIBUTE_DIRECTORY) ? file_status(directory_file, permissions) : file_status(regular_file, permissions);