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];
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 `FILE_SHARE_READ` allows other
183  // processes to read the file at the same time. MSDN[1] provides a more
184  // detailed explanation of these flags.
185  //
186  // [1] https://msdn.microsoft.com/en-us/library/windows/desktop/aa363858(v=vs.85).aspx // NOLINT(whitespace/line_length)
187  // [2] https://msdn.microsoft.com/en-us/library/windows/desktop/aa364399(v=vs.85).aspx // NOLINT(whitespace/line_length)
188  const DWORD access_flags = resolved_path_is_directory
189  ? FILE_FLAG_BACKUP_SEMANTICS
190  : FILE_ATTRIBUTE_NORMAL;
191 
192  const HANDLE handle = ::CreateFileW(
193  longpath(absolute_path).data(),
194  GENERIC_READ, // Open the file for reading only.
195  FILE_SHARE_READ, // Just reading this file, allow others to do the same.
196  nullptr, // Ignored.
197  OPEN_EXISTING, // Open existing file.
198  access_flags, // Open file, not the symlink itself.
199  nullptr); // Ignored.
200 
201  if (handle == INVALID_HANDLE_VALUE) {
202  return WindowsError();
203  }
204 
205  return SharedHandle(handle, ::CloseHandle);
206 }
207 
208 
209 // Attempts to get a file or folder handle for an absolute path, and does not
210 // follow symlinks. That is, if the path points at a symlink, the handle will
211 // refer to the symlink rather than the file or folder the symlink points at.
212 inline Try<SharedHandle> get_handle_no_follow(const std::string& absolute_path)
213 {
214  const Try<DWORD> attributes = get_file_attributes(longpath(absolute_path));
215 
216  if (attributes.isError()) {
217  return Error(attributes.error());
218  }
219 
220  bool resolved_path_is_directory = attributes.get() & FILE_ATTRIBUTE_DIRECTORY;
221 
222  // NOTE: According to the `CreateFile` documentation[1], the `OPEN_EXISTING`
223  // and `FILE_FLAG_OPEN_REPARSE_POINT` flags need to be used when getting a
224  // handle for the symlink.
225  //
226  // Note also that `CreateFile` will appropriately generate a handle for
227  // either a folder or a file, as long as the appropriate flag is being set:
228  // `FILE_FLAG_BACKUP_SEMANTICS` or `FILE_FLAG_OPEN_REPARSE_POINT`.
229  //
230  // The `FILE_FLAG_BACKUP_SEMANTICS` flag is being set whenever the target is
231  // a directory. According to MSDN[1]: "You must set this flag to obtain a
232  // handle to a directory. A directory handle can be passed to some functions
233  // instead of a file handle". More `FILE_FLAG_BACKUP_SEMANTICS` documentation
234  // can be found in MSDN[2].
235  //
236  // The `GENERIC_READ` flag is being used because it's the most common way of
237  // opening a file for reading only. The `FILE_SHARE_READ` allows other
238  // processes to read the file at the same time. MSDN[1] provides a more
239  // detailed explanation of these flags.
240  //
241  // [1] https://msdn.microsoft.com/en-us/library/windows/desktop/aa363858(v=vs.85).aspx // NOLINT(whitespace/line_length)
242  // [2] https://msdn.microsoft.com/en-us/library/windows/desktop/aa364399(v=vs.85).aspx // NOLINT(whitespace/line_length)
243  const DWORD access_flags = resolved_path_is_directory
244  ? (FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS)
245  : FILE_FLAG_OPEN_REPARSE_POINT;
246 
247  const HANDLE handle = ::CreateFileW(
248  longpath(absolute_path).data(),
249  GENERIC_READ, // Open the file for reading only.
250  FILE_SHARE_READ, // Just reading this file, allow others to do the same.
251  nullptr, // Ignored.
252  OPEN_EXISTING, // Open existing symlink.
253  access_flags, // Open symlink, not the file it points to.
254  nullptr); // Ignored.
255 
256  if (handle == INVALID_HANDLE_VALUE) {
257  return WindowsError();
258  }
259 
260  return SharedHandle(handle, ::CloseHandle);
261 }
262 
263 
264 // Attempts to get the symlink data for a file or folder handle.
266 {
267  // To get the symlink data, we call `DeviceIoControl`. This function is part
268  // of the Device Driver Kit (DDK)[1] and, along with `FSCTL_GET_REPARSE_POINT`
269  // is used to emit information about reparse points (and, thus, symlinks,
270  // since symlinks are implemented with reparse points). This technique is
271  // being used in Boost FS code as well[2].
272  //
273  // Summarized, the documentation tells us that we need to pass in
274  // `FSCTL_GET_REPARSE_POINT` to get the function to populate a
275  // `REPARSE_DATA_BUFFER` struct with data about a reparse point.
276  // The `REPARSE_DATA_BUFFER` struct is defined in a DDK header file,
277  // so to avoid bringing in a multitude of DDK headers we take a cue from
278  // Boost FS, and copy the struct into this header (see above).
279  //
280  // Finally, for context, it may be worth looking at the MSDN
281  // documentation[3] for `DeviceIoControl` itself.
282  //
283  // [1] https://msdn.microsoft.com/en-us/library/windows/desktop/aa364571(v=vs.85).aspx // NOLINT(whitespace/line_length)
284  // [2] https://svn.boost.org/trac/boost/ticket/4663
285  // [3] https://msdn.microsoft.com/en-us/library/windows/desktop/aa363216(v=vs.85).aspx // NOLINT(whitespace/line_length)
286  REPARSE_DATA_BUFFER buffer;
287  DWORD ignored = 0;
288 
289  // The semantics of this function are: get the reparse data associated with
290  // the `handle` of some open directory or file, and that data in
291  // `reparse_point_data`.
292  const BOOL reparse_data_obtained = ::DeviceIoControl(
293  handle, // Handle to file or directory.
294  FSCTL_GET_REPARSE_POINT, // Gets reparse point data for file/folder.
295  nullptr, // Ignored.
296  0, // Ignored.
297  &buffer,
298  sizeof(buffer),
299  &ignored, // Ignored.
300  nullptr); // Ignored.
301 
302  if (!reparse_data_obtained) {
303  return WindowsError(
304  "'internal::windows::get_symbolic_link_data': 'DeviceIoControl' call "
305  "failed");
306  }
307 
308  return build_symbolic_link(buffer);
309 }
310 
311 
312 // Creates a reparse point with the specified target. The target can be either
313 // a file (in which case a junction is created), or a folder (in which case a
314 // mount point is created).
316  const std::string& target,
317  const std::string& reparse_point)
318 {
319  // Determine if target is a folder or a file. This makes a difference
320  // in the way we call `create_symbolic_link`.
321  const Try<DWORD> attributes = get_file_attributes(longpath(target));
322 
323  bool target_is_folder = false;
324  if (attributes.isSome()) {
325  target_is_folder = attributes.get() & FILE_ATTRIBUTE_DIRECTORY;
326  }
327 
328  // Bail out if target is already a reparse point.
329  Try<bool> attribute_set = reparse_point_attribute_set(longpath(target));
330  if (attribute_set.isSome() && attribute_set.get()) {
331  return Error("Path '" + target + "' is already a reparse point");
332  }
333 
334  DWORD flags = target_is_folder ? SYMBOLIC_LINK_FLAG_DIRECTORY : 0;
335 
336  // Lambda to create symlink with given flags.
337  auto link = [&reparse_point, &target](const DWORD flags) {
338  return ::CreateSymbolicLinkW(
339  // Path to link.
340  longpath(reparse_point).data(),
341  // Path to target.
342  longpath(target).data(),
343  flags);
344  };
345 
346  // `CreateSymbolicLink` normally adjusts the process token's privileges to
347  // allow for symlink creation; however, we explicitly avoid this with the
348  // following flag to not require administrative privileges.
349  if (link(flags | SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE)) {
350  return Nothing();
351  }
352 
353  // If this failed because the non-symbolic link feature was not supported,
354  // try again without the feature. This is for legacy support.
355  if (::GetLastError() == ERROR_INVALID_PARAMETER) {
356  if (link(flags)) {
357  return Nothing();
358  }
359  }
360 
361  return WindowsError();
362 }
363 
364 } // namespace windows {
365 } // namespace internal {
366 
367 #endif // __STOUT_INTERNAL_WINDOWS_REPARSEPOINT_HPP__
Try< SharedHandle > get_handle_no_follow(const std::string &absolute_path)
Definition: reparsepoint.hpp:212
Definition: nothing.hpp:16
Definition: errorbase.hpp:35
Definition: windows.hpp:78
Definition: try.hpp:34
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:106
USHORT SubstituteNameLength
Definition: reparsepoint.hpp:74
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:265
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:70
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:42
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
bool isError() const
Definition: try.hpp:71
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:315
const T & get() const
Definition: try.hpp:73