| // Copyright 2024 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #ifdef UNSAFE_BUFFERS_BUILD |
| // TODO(crbug.com/390223051): Remove C-library calls to fix the errors. |
| #pragma allow_unsafe_libc_calls |
| #endif |
| |
| #include "base/test/file_path_reparse_point_win.h" |
| |
| #include <windows.h> |
| |
| #include <winioctl.h> |
| |
| #include <utility> |
| |
| namespace base::test { |
| |
| namespace { |
| // https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/ntifs/ns-ntifs-_reparse_data_buffer |
| // This struct should be available in the windows sdk in ntifs.h, but the |
| // chromium builders do not find this file. |
| 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; |
| } // namespace |
| |
| std::optional<base::test::FilePathReparsePoint> FilePathReparsePoint::Create( |
| const FilePath& source, |
| const FilePath& target) { |
| auto reparse_point = base::test::FilePathReparsePoint(source, target); |
| if (!reparse_point.IsValid()) { |
| return std::nullopt; |
| } |
| return std::move(reparse_point); |
| } |
| |
| FilePathReparsePoint::FilePathReparsePoint(FilePathReparsePoint&& other) |
| : dir_(std::move(other.dir_)), |
| created_(std::exchange(other.created_, false)) {} |
| |
| FilePathReparsePoint& FilePathReparsePoint::operator=( |
| FilePathReparsePoint&& other) { |
| dir_ = std::move(other.dir_); |
| created_ = std::exchange(other.created_, false); |
| return *this; |
| } |
| |
| FilePathReparsePoint::~FilePathReparsePoint() { |
| if (created_) { |
| DeleteReparsePoint(dir_.get()); |
| } |
| } |
| |
| // Creates a reparse point from |source| (an empty directory) to |target|. |
| FilePathReparsePoint::FilePathReparsePoint(const FilePath& source, |
| const FilePath& target) { |
| dir_.Set( |
| ::CreateFile(source.value().c_str(), GENERIC_READ | GENERIC_WRITE, |
| FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, |
| OPEN_EXISTING, |
| FILE_FLAG_BACKUP_SEMANTICS, // Needed to open a directory. |
| NULL)); |
| created_ = dir_.is_valid() && SetReparsePoint(dir_.get(), target); |
| } |
| |
| // Sets a reparse point. |source| will now point to |target|. Returns true if |
| // the call succeeds, false otherwise. |
| bool FilePathReparsePoint::SetReparsePoint(HANDLE source, |
| const FilePath& target_path) { |
| std::wstring kPathPrefix = FILE_PATH_LITERAL("\\??\\"); |
| std::wstring target_str; |
| // The juction will not work if the target path does not start with \??\ . |
| if (kPathPrefix != target_path.value().substr(0, kPathPrefix.size())) { |
| target_str += kPathPrefix; |
| } |
| target_str += target_path.value(); |
| const wchar_t* target = target_str.c_str(); |
| USHORT size_target = static_cast<USHORT>(wcslen(target)) * sizeof(target[0]); |
| char buffer[2000] = {}; |
| DWORD returned; |
| |
| REPARSE_DATA_BUFFER* data = reinterpret_cast<REPARSE_DATA_BUFFER*>(buffer); |
| |
| data->ReparseTag = 0xa0000003; |
| memcpy(data->MountPointReparseBuffer.PathBuffer, target, size_target + 2); |
| |
| data->MountPointReparseBuffer.SubstituteNameLength = size_target; |
| data->MountPointReparseBuffer.PrintNameOffset = size_target + 2; |
| data->ReparseDataLength = size_target + 4 + 8; |
| |
| int data_size = data->ReparseDataLength + 8; |
| |
| if (!DeviceIoControl(source, FSCTL_SET_REPARSE_POINT, &buffer, data_size, |
| NULL, 0, &returned, NULL)) { |
| return false; |
| } |
| return true; |
| } |
| |
| // Delete the reparse point referenced by |source|. Returns true if the call |
| // succeeds, false otherwise. |
| bool FilePathReparsePoint::DeleteReparsePoint(HANDLE source) { |
| DWORD returned; |
| REPARSE_DATA_BUFFER data = {0}; |
| data.ReparseTag = 0xa0000003; |
| if (!DeviceIoControl(source, FSCTL_DELETE_REPARSE_POINT, &data, 8, NULL, 0, |
| &returned, NULL)) { |
| return false; |
| } |
| return true; |
| } |
| |
| } // namespace base::test |