1 // Copyright 2024 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "base/test/file_path_reparse_point_win.h"
6
7 #include <windows.h>
8
9 #include <winioctl.h>
10
11 #include <utility>
12
13 namespace base::test {
14
15 namespace {
16 // https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/ntifs/ns-ntifs-_reparse_data_buffer
17 // This struct should be available in the windows sdk in ntifs.h, but the
18 // chromium builders do not find this file.
19 typedef struct _REPARSE_DATA_BUFFER {
20 ULONG ReparseTag;
21 USHORT ReparseDataLength;
22 USHORT Reserved;
23 union {
24 struct {
25 USHORT SubstituteNameOffset;
26 USHORT SubstituteNameLength;
27 USHORT PrintNameOffset;
28 USHORT PrintNameLength;
29 ULONG Flags;
30 WCHAR PathBuffer[1];
31 } SymbolicLinkReparseBuffer;
32 struct {
33 USHORT SubstituteNameOffset;
34 USHORT SubstituteNameLength;
35 USHORT PrintNameOffset;
36 USHORT PrintNameLength;
37 WCHAR PathBuffer[1];
38 } MountPointReparseBuffer;
39 struct {
40 UCHAR DataBuffer[1];
41 } GenericReparseBuffer;
42 };
43 } REPARSE_DATA_BUFFER, *PREPARSE_DATA_BUFFER;
44 } // namespace
45
Create(const FilePath & source,const FilePath & target)46 std::optional<base::test::FilePathReparsePoint> FilePathReparsePoint::Create(
47 const FilePath& source,
48 const FilePath& target) {
49 auto reparse_point = base::test::FilePathReparsePoint(source, target);
50 if (!reparse_point.IsValid()) {
51 return std::nullopt;
52 }
53 return std::move(reparse_point);
54 }
55
FilePathReparsePoint(FilePathReparsePoint && other)56 FilePathReparsePoint::FilePathReparsePoint(FilePathReparsePoint&& other)
57 : dir_(std::move(other.dir_)),
58 created_(std::exchange(other.created_, false)) {}
59
operator =(FilePathReparsePoint && other)60 FilePathReparsePoint& FilePathReparsePoint::operator=(
61 FilePathReparsePoint&& other) {
62 dir_ = std::move(other.dir_);
63 created_ = std::exchange(other.created_, false);
64 return *this;
65 }
66
~FilePathReparsePoint()67 FilePathReparsePoint::~FilePathReparsePoint() {
68 if (created_) {
69 DeleteReparsePoint(dir_.get());
70 }
71 }
72
73 // Creates a reparse point from |source| (an empty directory) to |target|.
FilePathReparsePoint(const FilePath & source,const FilePath & target)74 FilePathReparsePoint::FilePathReparsePoint(const FilePath& source,
75 const FilePath& target) {
76 dir_.Set(
77 ::CreateFile(source.value().c_str(), GENERIC_READ | GENERIC_WRITE,
78 FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL,
79 OPEN_EXISTING,
80 FILE_FLAG_BACKUP_SEMANTICS, // Needed to open a directory.
81 NULL));
82 created_ = dir_.is_valid() && SetReparsePoint(dir_.get(), target);
83 }
84
85 // Sets a reparse point. |source| will now point to |target|. Returns true if
86 // the call succeeds, false otherwise.
SetReparsePoint(HANDLE source,const FilePath & target_path)87 bool FilePathReparsePoint::SetReparsePoint(HANDLE source,
88 const FilePath& target_path) {
89 std::wstring kPathPrefix = FILE_PATH_LITERAL("\\??\\");
90 std::wstring target_str;
91 // The juction will not work if the target path does not start with \??\ .
92 if (kPathPrefix != target_path.value().substr(0, kPathPrefix.size())) {
93 target_str += kPathPrefix;
94 }
95 target_str += target_path.value();
96 const wchar_t* target = target_str.c_str();
97 USHORT size_target = static_cast<USHORT>(wcslen(target)) * sizeof(target[0]);
98 char buffer[2000] = {0};
99 DWORD returned;
100
101 REPARSE_DATA_BUFFER* data = reinterpret_cast<REPARSE_DATA_BUFFER*>(buffer);
102
103 data->ReparseTag = 0xa0000003;
104 memcpy(data->MountPointReparseBuffer.PathBuffer, target, size_target + 2);
105
106 data->MountPointReparseBuffer.SubstituteNameLength = size_target;
107 data->MountPointReparseBuffer.PrintNameOffset = size_target + 2;
108 data->ReparseDataLength = size_target + 4 + 8;
109
110 int data_size = data->ReparseDataLength + 8;
111
112 if (!DeviceIoControl(source, FSCTL_SET_REPARSE_POINT, &buffer, data_size,
113 NULL, 0, &returned, NULL)) {
114 return false;
115 }
116 return true;
117 }
118
119 // Delete the reparse point referenced by |source|. Returns true if the call
120 // succeeds, false otherwise.
DeleteReparsePoint(HANDLE source)121 bool FilePathReparsePoint::DeleteReparsePoint(HANDLE source) {
122 DWORD returned;
123 REPARSE_DATA_BUFFER data = {0};
124 data.ReparseTag = 0xa0000003;
125 if (!DeviceIoControl(source, FSCTL_DELETE_REPARSE_POINT, &data, 8, NULL, 0,
126 &returned, NULL)) {
127 return false;
128 }
129 return true;
130 }
131
132 } // namespace base::test
133