xref: /aosp_15_r20/external/cronet/base/test/file_path_reparse_point_win.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
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