Apache Mesos
reparsepoint.hpp
Go to the documentation of this file.
1 // Licensed under the Apache License, Version 2.0 (the "License");
2 // you may not use this file except in compliance with the License.
3 // You may obtain a copy of the License at
4 //
5 // http://www.apache.org/licenses/LICENSE-2.0
6 //
7 // Unless required by applicable law or agreed to in writing, software
8 // distributed under the License is distributed on an "AS IS" BASIS,
9 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 // See the License for the specific language governing permissions and
11 // limitations under the License.
12 
13 #ifndef __STOUT_INTERNAL_WINDOWS_REPARSEPOINT_HPP__
14 #define __STOUT_INTERNAL_WINDOWS_REPARSEPOINT_HPP__
15 
16 #include <string>
17 
18 #include <stout/nothing.hpp>
19 #include <stout/try.hpp>
20 #include <stout/windows.hpp>
21 
22 #include <stout/os/mkdir.hpp>
23 
26 
27 
28 namespace os {
29 namespace stat {
30 
31 // Specify whether symlink path arguments should be followed or
32 // not. APIs in the os::stat family that take a FollowSymlink
33 // argument all provide FollowSymlink::FOLLOW_SYMLINK as the default value,
34 // so they will follow symlinks unless otherwise specified.
35 enum class FollowSymlink
36 {
39 };
40 
41 } // namespace stat {
42 } // namespace os {
43 
44 
45 namespace internal {
46 namespace windows {
47 
48 // We pass this struct to `DeviceIoControl` to get information about a reparse
49 // point (including things like whether it's a symlink). It is normally part of
50 // the Device Driver Kit (DDK), specifically `nitfs.h`, but rather than taking
51 // a dependency on the DDK, we choose to simply copy the struct here. This is a
52 // well-worn path used by Boost FS[1], among others. See documentation
53 // here[2][3].
54 //
55 // [1] http://www.boost.org/doc/libs/1_46_1/libs/filesystem/v3/src/operations.cpp // NOLINT(whitespace/line_length)
56 // [2] https://msdn.microsoft.com/en-us/library/cc232007.aspx
57 // [3] https://msdn.microsoft.com/en-us/library/cc232005.aspx
58 typedef struct _REPARSE_DATA_BUFFER
59 {
60  // Describes, among other things, which type of reparse point this is (e.g.,
61  // a symlink).
62  ULONG ReparseTag;
63  // Size in bytes of common portion of the `REPARSE_DATA_BUFFER`.
65  // Unused. Ignore.
66  USHORT Reserved;
67  // Holds symlink data.
68  struct
69  {
70  // Byte offset in `PathBuffer` where the substitute name begins.
71  // Calculated as an offset from 0.
73  // Length in bytes of the substitute name.
75  // Byte offset in `PathBuffer` where the print name begins. Calculated as
76  // an offset from 0.
78  // Length in bytes of the print name.
80  // Indicates whether symlink is absolute or relative. If flags containing
81  // `SYMLINK_FLAG_RELATIVE`, then the substitute name is a relative
82  // symlink.
83  ULONG Flags;
84  // The path string; the Windows declaration is the first byte, but we
85  // declare a suitably sized array so we can use this struct more easily. The
86  // "path string" itself is a Unicode `wchar` array containing both
87  // substitute name and print name. They can occur in any order. Use the
88  // offset and length of each in this struct to calculate where each starts
89  // and ends.
90  //
91  // https://msdn.microsoft.com/en-us/library/windows/hardware/ff552012(v=vs.85).aspx // NOLINT(whitespace/line_length)
92  WCHAR PathBuffer[MAXIMUM_REPARSE_DATA_BUFFER_SIZE];
93  } SymbolicLinkReparseBuffer;
95 
96 
97 // Convenience struct for holding symlink data, meant purely for internal use.
98 // We pass this around instead of the `REPARSE_DATA_BUFFER` struct, simply
99 // because this struct is easier to deal with and reason about.
101 {
102  std::wstring substitute_name;
103  std::wstring print_name;
104  ULONG flags;
105 };
106 
107 
108 // Checks file/folder attributes for a path to see if the reparse point
109 // attribute is set; this indicates whether the path points at a reparse point,
110 // rather than a "normal" file or folder.
111 inline Try<bool> reparse_point_attribute_set(const std::wstring& absolute_path)
112 {
113  const Try<DWORD> attributes = get_file_attributes(absolute_path.data());
114  if (attributes.isError()) {
115  return Error(attributes.error());
116  }
117 
118  return (attributes.get() & FILE_ATTRIBUTE_REPARSE_POINT) != 0;
119 }
120 
121 
122 // Attempts to extract symlink data out of a `REPARSE_DATA_BUFFER` (which could
123 // hold other things, e.g., mount point data).
125 {
126  const bool is_symLink = (data.ReparseTag & IO_REPARSE_TAG_SYMLINK) != 0;
127  if (!is_symLink) {
128  return Error("Data buffer is not a symlink");
129  }
130 
131  // NOTE: This buffer is not null terminated.
132  const WCHAR* substitute_name =
134  data.SymbolicLinkReparseBuffer.SubstituteNameOffset / sizeof(WCHAR);
135  const size_t substitute_name_length =
136  data.SymbolicLinkReparseBuffer.SubstituteNameLength / sizeof(WCHAR);
137 
138  // NOTE: This buffer is not null terminated.
139  const WCHAR* print_name =
141  data.SymbolicLinkReparseBuffer.PrintNameOffset / sizeof(WCHAR);
142  const size_t print_name_length =
143  data.SymbolicLinkReparseBuffer.PrintNameLength / sizeof(WCHAR);
144 
145  return SymbolicLink{
146  std::wstring(substitute_name, substitute_name_length),
147  std::wstring(print_name, print_name_length),
149 }
150 
151 
152 // Attempts to get a file or folder handle for an absolute path, and follows
153 // symlinks. That is, if the path points at a symlink, the handle will refer to
154 // the file or folder the symlink points at, rather than the symlink itself.
155 inline Try<SharedHandle> get_handle_follow(const std::string& absolute_path)
156 {
157  const Try<DWORD> attributes = get_file_attributes(longpath(absolute_path));
158 
159  if (attributes.isError()) {
160  return Error(attributes.error());
161  }
162 
163  bool resolved_path_is_directory = attributes.get() & FILE_ATTRIBUTE_DIRECTORY;
164 
165  // NOTE: The name of `CreateFile` is misleading: it is also used to retrieve
166  // handles to existing files or directories as if it were actually `OpenPath`
167  // (which does not exist). We use `OPEN_EXISTING` but not
168  // `FILE_FLAG_OPEN_REPARSE_POINT` to explicitily follow (resolve) symlinks in
169  // the path to the file or directory.
170  //
171  // Note also that `CreateFile` will appropriately generate a handle for
172  // either a folder or a file, as long as the appropriate flag is being set:
173  // `FILE_FLAG_BACKUP_SEMANTICS`.
174  //
175  // The `FILE_FLAG_BACKUP_SEMANTICS` flag is being set whenever the target is
176  // a directory. According to MSDN[1]: "You must set this flag to obtain a
177  // handle to a directory. A directory handle can be passed to some functions
178  // instead of a file handle". More `FILE_FLAG_BACKUP_SEMANTICS` documentation
179  // can be found in MSDN[2].
180  //
181  // The `GENERIC_READ` flag is being used because it's the most common way of
182  // opening a file for reading only. The `SHARE` flags allow other processes
183  // to read the file at the same time, as well as allow this process to read
184  // files that were also opened with these flags. MSDN[1] provides a more
185  // detailed explanation of these flags.
186  //
187  // [1] https://msdn.microsoft.com/en-us/library/windows/desktop/aa363858(v=vs.85).aspx // NOLINT(whitespace/line_length)
188  // [2] https://msdn.microsoft.com/en-us/library/windows/desktop/aa364399(v=vs.85).aspx // NOLINT(whitespace/line_length)
189  const DWORD access_flags = resolved_path_is_directory
190  ? FILE_FLAG_BACKUP_SEMANTICS
191  : FILE_ATTRIBUTE_NORMAL;
192 
193  const HANDLE handle = ::CreateFileW(
194  longpath(absolute_path).data(),
195  GENERIC_READ, // Open the file for reading only.
196  // Must pass in all SHARE flags below, in case file is already open.
197  // Otherwise, we may get an access denied error.
198  FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
199  nullptr, // Ignored.
200  OPEN_EXISTING, // Open existing file.
201  access_flags, // Open file, not the symlink itself.
202  nullptr); // Ignored.
203 
204  if (handle == INVALID_HANDLE_VALUE) {
205  return WindowsError();
206  }
207 
208  return SharedHandle(handle, ::CloseHandle);
209 }
210 
211 
212 // Attempts to get a file or folder handle for an absolute path, and does not
213 // follow symlinks. That is, if the path points at a symlink, the handle will
214 // refer to the symlink rather than the file or folder the symlink points at.
215 inline Try<SharedHandle> get_handle_no_follow(const std::string& absolute_path)
216 {
217  const Try<DWORD> attributes = get_file_attributes(longpath(absolute_path));
218 
219  if (attributes.isError()) {
220  return Error(attributes.error());
221  }
222 
223  bool resolved_path_is_directory = attributes.get() & FILE_ATTRIBUTE_DIRECTORY;
224 
225  // NOTE: According to the `CreateFile` documentation[1], the `OPEN_EXISTING`
226  // and `FILE_FLAG_OPEN_REPARSE_POINT` flags need to be used when getting a
227  // handle for the symlink.
228  //
229  // Note also that `CreateFile` will appropriately generate a handle for
230  // either a folder or a file, as long as the appropriate flag is being set:
231  // `FILE_FLAG_BACKUP_SEMANTICS` or `FILE_FLAG_OPEN_REPARSE_POINT`.
232  //
233  // The `FILE_FLAG_BACKUP_SEMANTICS` flag is being set whenever the target is
234  // a directory. According to MSDN[1]: "You must set this flag to obtain a
235  // handle to a directory. A directory handle can be passed to some functions
236  // instead of a file handle". More `FILE_FLAG_BACKUP_SEMANTICS` documentation
237  // can be found in MSDN[2].
238  //
239  // The `GENERIC_READ` flag is being used because it's the most common way of
240  // opening a file for reading only. The `SHARE` flags allow other processes
241  // to read the file at the same time, as well as allow this process to read
242  // files that were also opened with these flags. MSDN[1] provides a more
243  // detailed explanation of these flags.
244  //
245  // [1] https://msdn.microsoft.com/en-us/library/windows/desktop/aa363858(v=vs.85).aspx // NOLINT(whitespace/line_length)
246  // [2] https://msdn.microsoft.com/en-us/library/windows/desktop/aa364399(v=vs.85).aspx // NOLINT(whitespace/line_length)
247  const DWORD access_flags = resolved_path_is_directory
248  ? (FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS)
249  : FILE_FLAG_OPEN_REPARSE_POINT;
250 
251  const HANDLE handle = ::CreateFileW(
252  longpath(absolute_path).data(),
253  GENERIC_READ, // Open the file for reading only.
254  // Must pass in all SHARE flags below, in case file is already open.
255  // Otherwise, we may get an access denied error.
256  FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
257  nullptr, // Ignored.
258  OPEN_EXISTING, // Open existing symlink.
259  access_flags, // Open symlink, not the file it points to.
260  nullptr); // Ignored.
261 
262  if (handle == INVALID_HANDLE_VALUE) {
263  return WindowsError();
264  }
265 
266  return SharedHandle(handle, ::CloseHandle);
267 }
268 
269 
270 // Attempts to get the symlink data for a file or folder handle.
272 {
273  // To get the symlink data, we call `DeviceIoControl`. This function is part
274  // of the Device Driver Kit (DDK)[1] and, along with `FSCTL_GET_REPARSE_POINT`
275  // is used to emit information about reparse points (and, thus, symlinks,
276  // since symlinks are implemented with reparse points). This technique is
277  // being used in Boost FS code as well[2].
278  //
279  // Summarized, the documentation tells us that we need to pass in
280  // `FSCTL_GET_REPARSE_POINT` to get the function to populate a
281  // `REPARSE_DATA_BUFFER` struct with data about a reparse point.
282  // The `REPARSE_DATA_BUFFER` struct is defined in a DDK header file,
283  // so to avoid bringing in a multitude of DDK headers we take a cue from
284  // Boost FS, and copy the struct into this header (see above).
285  //
286  // Finally, for context, it may be worth looking at the MSDN
287  // documentation[3] for `DeviceIoControl` itself.
288  //
289  // [1] https://msdn.microsoft.com/en-us/library/windows/desktop/aa364571(v=vs.85).aspx // NOLINT(whitespace/line_length)
290  // [2] https://svn.boost.org/trac/boost/ticket/4663
291  // [3] https://msdn.microsoft.com/en-us/library/windows/desktop/aa363216(v=vs.85).aspx // NOLINT(whitespace/line_length)
292  REPARSE_DATA_BUFFER buffer;
293  DWORD ignored = 0;
294 
295  // The semantics of this function are: get the reparse data associated with
296  // the `handle` of some open directory or file, and that data in
297  // `reparse_point_data`.
298  const BOOL reparse_data_obtained = ::DeviceIoControl(
299  handle, // Handle to file or directory.
300  FSCTL_GET_REPARSE_POINT, // Gets reparse point data for file/folder.
301  nullptr, // Ignored.
302  0, // Ignored.
303  &buffer,
304  sizeof(buffer),
305  &ignored, // Ignored.
306  nullptr); // Ignored.
307 
308  if (!reparse_data_obtained) {
309  return WindowsError(
310  "'internal::windows::get_symbolic_link_data': 'DeviceIoControl' call "
311  "failed");
312  }
313 
314  return build_symbolic_link(buffer);
315 }
316 
317 
318 // Creates a reparse point with the specified target. The target can be either
319 // a file (in which case a junction is created), or a folder (in which case a
320 // mount point is created).
322  const std::string& target,
323  const std::string& reparse_point)
324 {
325  // Determine if target is a folder or a file. This makes a difference
326  // in the way we call `create_symbolic_link`.
327  const Try<DWORD> attributes = get_file_attributes(longpath(target));
328 
329  bool target_is_folder = false;
330  if (attributes.isSome()) {
331  target_is_folder = attributes.get() & FILE_ATTRIBUTE_DIRECTORY;
332  }
333 
334  // Bail out if target is already a reparse point.
335  Try<bool> attribute_set = reparse_point_attribute_set(longpath(target));
336  if (attribute_set.isSome() && attribute_set.get()) {
337  return Error("Path '" + target + "' is already a reparse point");
338  }
339 
340  DWORD flags = target_is_folder ? SYMBOLIC_LINK_FLAG_DIRECTORY : 0;
341 
342  // Lambda to create symlink with given flags.
343  auto link = [&reparse_point, &target](const DWORD flags) {
344  return ::CreateSymbolicLinkW(
345  // Path to link.
346  longpath(reparse_point).data(),
347  // Path to target.
348  longpath(target).data(),
349  flags);
350  };
351 
352  // `CreateSymbolicLink` normally adjusts the process token's privileges to
353  // allow for symlink creation; however, we explicitly avoid this with the
354  // following flag to not require administrative privileges.
355  if (link(flags | SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE)) {
356  return Nothing();
357  }
358 
359  // If this failed because the non-symbolic link feature was not supported,
360  // try again without the feature. This is for legacy support.
361  if (::GetLastError() == ERROR_INVALID_PARAMETER) {
362  if (link(flags)) {
363  return Nothing();
364  }
365  }
366 
367  return WindowsError();
368 }
369 
370 } // namespace windows {
371 } // namespace internal {
372 
373 #endif // __STOUT_INTERNAL_WINDOWS_REPARSEPOINT_HPP__
Try< SharedHandle > get_handle_no_follow(const std::string &absolute_path)
Definition: reparsepoint.hpp:215
Definition: nothing.hpp:16
Definition: errorbase.hpp:36
T & get()&
Definition: try.hpp:80
Definition: windows.hpp:72
Definition: check.hpp:33
ULONG ReparseTag
Definition: reparsepoint.hpp:62
Try< DWORD > get_file_attributes(const std::wstring &path)
Definition: attributes.hpp:27
ULONG Flags
Definition: reparsepoint.hpp:83
Definition: error.hpp:108
USHORT SubstituteNameLength
Definition: reparsepoint.hpp:74
Definition: posix_signalhandler.hpp:23
USHORT PrintNameOffset
Definition: reparsepoint.hpp:77
USHORT Reserved
Definition: reparsepoint.hpp:66
FollowSymlink
Definition: reparsepoint.hpp:35
Try< bool > reparse_point_attribute_set(const std::wstring &absolute_path)
Definition: reparsepoint.hpp:111
Try< SymbolicLink > build_symbolic_link(const REPARSE_DATA_BUFFER &data)
Definition: reparsepoint.hpp:124
struct internal::windows::_REPARSE_DATA_BUFFER::@13 SymbolicLinkReparseBuffer
Try< SymbolicLink > get_symbolic_link_data(const HANDLE handle)
Definition: reparsepoint.hpp:271
WCHAR PathBuffer[MAXIMUM_REPARSE_DATA_BUFFER_SIZE]
Definition: reparsepoint.hpp:92
constexpr Handle HANDLE
Definition: ingress.hpp:37
USHORT ReparseDataLength
Definition: reparsepoint.hpp:64
bool isSome() const
Definition: try.hpp:77
Try< hashmap< std::string, uint64_t > > stat(const std::string &hierarchy, const std::string &cgroup, const std::string &file)
static Try error(const E &e)
Definition: try.hpp:43
USHORT PrintNameLength
Definition: reparsepoint.hpp:79
USHORT SubstituteNameOffset
Definition: reparsepoint.hpp:72
Try< SharedHandle > get_handle_follow(const std::string &absolute_path)
Definition: reparsepoint.hpp:155
#define flags
Definition: decoder.hpp:18
Definition: attributes.hpp:24
bool isError() const
Definition: try.hpp:78
struct internal::windows::_REPARSE_DATA_BUFFER REPARSE_DATA_BUFFER
std::wstring longpath(const std::string &path)
Definition: longpath.hpp:38
Definition: reparsepoint.hpp:58
Try< Nothing > create_symbolic_link(const std::string &target, const std::string &reparse_point)
Definition: reparsepoint.hpp:321
Definition: parse.hpp:33