You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mesos.apache.org by jo...@apache.org on 2016/01/29 02:39:42 UTC

mesos git commit: Windows: Added code for creating a reparse point.

Repository: mesos
Updated Branches:
  refs/heads/master 39eeea74e -> ad058db1b


Windows: Added code for creating a reparse point.

Review: https://reviews.apache.org/r/40676/


Project: http://git-wip-us.apache.org/repos/asf/mesos/repo
Commit: http://git-wip-us.apache.org/repos/asf/mesos/commit/ad058db1
Tree: http://git-wip-us.apache.org/repos/asf/mesos/tree/ad058db1
Diff: http://git-wip-us.apache.org/repos/asf/mesos/diff/ad058db1

Branch: refs/heads/master
Commit: ad058db1b64c90892081159b95e47a19520a5e3b
Parents: 39eeea7
Author: Alex Naparu <al...@outlook.com>
Authored: Thu Jan 28 14:30:06 2016 -0800
Committer: Joris Van Remoortere <jo...@gmail.com>
Committed: Thu Jan 28 17:39:27 2016 -0800

----------------------------------------------------------------------
 .../stout/internal/windows/reparsepoint.hpp     | 208 ++++++++++++++-----
 1 file changed, 152 insertions(+), 56 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mesos/blob/ad058db1/3rdparty/libprocess/3rdparty/stout/include/stout/internal/windows/reparsepoint.hpp
----------------------------------------------------------------------
diff --git a/3rdparty/libprocess/3rdparty/stout/include/stout/internal/windows/reparsepoint.hpp b/3rdparty/libprocess/3rdparty/stout/include/stout/internal/windows/reparsepoint.hpp
index 36b0068..85eb0be 100644
--- a/3rdparty/libprocess/3rdparty/stout/include/stout/internal/windows/reparsepoint.hpp
+++ b/3rdparty/libprocess/3rdparty/stout/include/stout/internal/windows/reparsepoint.hpp
@@ -13,24 +13,31 @@
 #ifndef __STOUT_INTERNAL_WINDOWS_REPARSEPOINT_HPP__
 #define __STOUT_INTERNAL_WINDOWS_REPARSEPOINT_HPP__
 
+#include <mutex>
 #include <string>
 
+#include <stout/nothing.hpp>
+#include <stout/synchronized.hpp>
 #include <stout/try.hpp>
 #include <stout/windows.hpp>
 
-
-namespace internal {
-namespace windows {
+#include <stout/os/mkdir.hpp>
+#include <stout/os/realpath.hpp>
 
 // We pass this struct to `DeviceIoControl` to get information about a reparse
 // point (including things like whether it's a symlink). It is normally part of
 // the Device Driver Kit (DDK), specifically `nitfs.h`, but rather than taking
 // a dependency on the DDK, we choose to simply copy the struct here. This is a
 // well-worn path used by Boost FS[1], among others. See documentation
-// here[2].
+// here[2][3].
 //
 // [1] http://www.boost.org/doc/libs/1_46_1/libs/filesystem/v3/src/operations.cpp
 // [2] https://msdn.microsoft.com/en-us/library/cc232007.aspx
+// [3] https://msdn.microsoft.com/en-us/library/cc232005.aspx
+//
+// We are declaring this structure (and the REPARSE_DATA_BUFFER_HEADER_SIZE
+// macro right below it in the global namespace, to be consistent with the
+// original Windows DDK declarations.
 typedef struct _REPARSE_DATA_BUFFER
 {
   // Describes, among other things, which type of reparse point this is (e.g.,
@@ -85,12 +92,15 @@ typedef struct _REPARSE_DATA_BUFFER
   };
 } REPARSE_DATA_BUFFER;
 
-#define REPARSE_MOUNTPOINT_HEADER_SIZE 8
+#define REPARSE_DATA_BUFFER_HEADER_SIZE \
+  FIELD_OFFSET(REPARSE_DATA_BUFFER, GenericReparseBuffer)
 
+namespace internal {
+namespace windows {
 
 // Convenience struct for holding symlink data, meant purely for internal use.
-// We pass this around instead of the extremely inelegant `REPARSE_DATA_BUFFER`
-// struct, simply because this struct is easier to deal with and reason about.
+// We pass this around instead of the `REPARSE_DATA_BUFFER` struct, simply
+// because this struct is easier to deal with and reason about.
 struct SymbolicLink
 {
   std::wstring substitute_name;
@@ -104,7 +114,7 @@ struct SymbolicLink
 // rather than a "normal" file or folder.
 inline Try<bool> reparse_point_attribute_set(const std::string& absolute_path)
 {
-  const DWORD attributes = GetFileAttributes(absolute_path.c_str());
+  const DWORD attributes = ::GetFileAttributes(absolute_path.c_str());
   if (attributes == INVALID_FILE_ATTRIBUTES) {
     return WindowsError(
         "Failed to get attributes for file '" + absolute_path + "'");
@@ -149,41 +159,52 @@ inline Try<SymbolicLink> build_symbolic_link(const REPARSE_DATA_BUFFER& data)
 // refer to the symlink rather than the file or folder the symlink points at.
 inline Try<SharedHandle> get_handle_no_follow(const std::string& absolute_path)
 {
-  struct _stat s;
+  struct _stat absolute_path_stat;
   bool resolved_path_is_directory = false;
-  if (::_stat(absolute_path.c_str(), &s) >= 0) {
-    resolved_path_is_directory = S_ISDIR(s.st_mode);
+  if (::_stat(absolute_path.c_str(), &absolute_path_stat) == 0) {
+    resolved_path_is_directory = S_ISDIR(absolute_path_stat.st_mode);
+  } else {
+    return ErrnoError("'_stat' failed on path '" + absolute_path + "'");
   }
 
-  // NOTE: The `CreateFile` documentation[1] tells us which flags we need to
-  // invoke to open a handle that will point at the symlink instead of the
-  // folder or file it points at. The short answer is that you need to be sure
-  // to pass in `OPEN_EXISTING` and `FILE_FLAG_OPEN_REPARSE_POINT` to get a
-  // handle for the symlink and not the file the symlink points to.
+  // NOTE: According to the `CreateFile` documentation[1], the `OPEN_EXISTING`
+  // and `FILE_FLAG_OPEN_REPARSE_POINT` flags need to be used when getting a
+  // handle for the symlink.
   //
   // Note also that `CreateFile` will appropriately generate a handle for
-  // either a folder or a file (they are different!), as long as you
-  // appropriately set a magic flag, `FILE_FLAG_BACKUP_SEMANTICS`. It is not
-  // clear why, or what this flag means, the documentation[1] only says it's
-  // necessary.
+  // either a folder or a file, as long as the appropriate flag is being set:
+  // `FILE_FLAG_BACKUP_SEMANTICS` or `FILE_FLAG_OPEN_REPARSE_POINT`.
+  //
+  // The `FILE_FLAG_BACKUP_SEMANTICS` flag is being set whenever the target is
+  // a directory. According to MSDN[1]: "You must set this flag to obtain a
+  // handle to a directory. A directory handle can be passed to some functions
+  // instead of a file handle". More `FILE_FLAG_BACKUP_SEMANTICS` documentation
+  // can be found in MSDN[2].
+  //
+  // The `GENERIC_READ` flag is being used because it's the most common way of
+  // opening a file for reading only. The `FILE_SHARE_READ` allows other
+  // processes to read the file at the same time. MSDN[1] provides a more
+  // detailed explanation of these flags.
   //
   // [1] https://msdn.microsoft.com/en-us/library/windows/desktop/aa363858(v=vs.85).aspx
+  // [2] https://msdn.microsoft.com/en-us/library/windows/desktop/aa364399(v=vs.85).aspx
   const DWORD access_flags = resolved_path_is_directory
     ? (FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS)
     : FILE_FLAG_OPEN_REPARSE_POINT;
 
-  const HANDLE handle = CreateFile(
-    absolute_path.c_str(),
-    0,              // Ignored.
-    0,              // Ignored.
-    NULL,           // Ignored.
-    OPEN_EXISTING,  // Open existing symlink.
-    access_flags,   // Open symlink, not the file it points to.
-    NULL);          // Ignored.
+  const HANDLE handle = ::CreateFile(
+      absolute_path.c_str(),
+      GENERIC_READ,     // Open the file for reading only.
+      FILE_SHARE_READ,  // Just reading this file, allow others to do the same.
+      NULL,             // Ignored.
+      OPEN_EXISTING,    // Open existing symlink.
+      access_flags,      // Open symlink, not the file it points to.
+      NULL);            // Ignored.
 
   if (handle == INVALID_HANDLE_VALUE) {
     return WindowsError(
-        "Could not open handle for symlink at path '" + absolute_path + "'");
+        "'internal::windows::get_handle_no_follow': 'CreateFile' call failed "
+        "at path '" + absolute_path + "'");
   }
 
   return SharedHandle(handle, CloseHandle);
@@ -194,29 +215,24 @@ inline Try<SharedHandle> get_handle_no_follow(const std::string& absolute_path)
 inline Try<SymbolicLink> get_symbolic_link_data(const HANDLE handle)
 {
   // To get the symlink data, we call `DeviceIoControl`. This function is part
-  // of the Device Driver Kit (DDK), and buried away in a corner of the
-  // DDK documentation[1] is an incomplete explanation of how to twist API to
-  // get it to emit information about reparse points (and, thus, symlinks,
-  // since symlinks are implemented with reparse points). This technique is a
-  // hack, but it is used pretty much everywhere, including the Boost FS code,
-  // though it's worth noting that they seem to use it incorrectly[2].
+  // of the Device Driver Kit (DDK)[1] and, along with `FSCTL_GET_REPARSE_POINT`
+  // is used to emit information about reparse points (and, thus, symlinks,
+  // since symlinks are implemented with reparse points). This technique is
+  // being used in Boost FS code as well[2].
   //
-  // Summarized, the documentation tells us that we need to pass in the magic
-  // flag `FSCTL_GET_REPARSE_POINT` to get the function to populate a
-  // `REPARSE_DATA_BUFFER` struct with data about a reparse point. What it
-  // doesn't tell you is that this struct is in a header in the DDK, so in
-  // order to get information about the reparse point, you must either take a
-  // dependency on the DDK, or copy the struct from the header into your code.
-  // We take a cue from Boost FS, and copy the struct into this header (see
-  // above).
+  // Summarized, the documentation tells us that we need to pass in
+  // `FSCTL_GET_REPARSE_POINT` to get the function to populate a
+  // `REPARSE_DATA_BUFFER` struct with data about a reparse point.
+  // The `REPARSE_DATA_BUFFER` struct is defined in a DDK header file,
+  // so to avoid bringing in a multitude of DDK headers we take a cue from
+  // Boost FS, and copy the struct into this header (see above).
   //
-  // Finally, for context, it may be worth looking at the (sparse)
-  // documentation for `DeviceIoControl` itself.
+  // Finally, for context, it may be worth looking at the MSDN
+  // documentation[3] for `DeviceIoControl` itself.
   //
   // [1] https://msdn.microsoft.com/en-us/library/windows/desktop/aa364571(v=vs.85).aspx
   // [2] https://svn.boost.org/trac/boost/ticket/4663
   // [3] https://msdn.microsoft.com/en-us/library/windows/desktop/aa363216(v=vs.85).aspx
-
   const size_t reparse_point_data_size = MAXIMUM_REPARSE_DATA_BUFFER_SIZE;
   BYTE buffer[reparse_point_data_size];
   REPARSE_DATA_BUFFER* reparse_point_data =
@@ -227,23 +243,103 @@ inline Try<SymbolicLink> get_symbolic_link_data(const HANDLE handle)
   // The semantics of this function are: get the reparse data associated with
   // the `handle` of some open directory or file, and that data in
   // `reparse_point_data`.
-  const BOOL reparse_data_obtained = DeviceIoControl(
-    handle,                  // handle to file or directory
-    FSCTL_GET_REPARSE_POINT, // Gets reparse point data for file/folder handle.
-    NULL,                    // Ignored.
-    0,                       // Ignored.
-    reparse_point_data,
-    reparse_point_data_size,
-    &ignored,                // Ignored.
-    NULL);                   // Ignored.
+  const BOOL reparse_data_obtained = ::DeviceIoControl(
+      handle,                   // Handle to file or directory.
+      FSCTL_GET_REPARSE_POINT,  // Gets reparse point data for file/folder.
+      NULL,                     // Ignored.
+      0,                        // Ignored.
+      reparse_point_data,
+      reparse_point_data_size,
+      &ignored,                 // Ignored.
+      NULL);                    // Ignored.
 
   if (!reparse_data_obtained) {
-    return WindowsError("Failed to obtain reparse point data for handle");
+    return WindowsError(
+        "'internal::windows::get_symbolic_link_data': 'DeviceIoControl' call "
+        "failed");
   }
 
   return build_symbolic_link(*reparse_point_data);
 }
 
+
+// Creates a reparse point with the specified target. The target can be either
+// a file (in which case a junction is created), or a folder (in which case a
+// mount point is created).
+//
+// Calling this function results in a temporary elevation of the process token's
+// privileges (if those privileges are not already held), to allow for junction
+// or mount point creation. This operation is gated by a static mutex, which
+// makes it thread-safe.
+inline Try<Nothing> create_symbolic_link(
+    const std::string& target,
+    const std::string& reparse_point)
+{
+  // Normalize input paths.
+  const Result<std::string> real_reparse_point_path =
+    os::realpath(reparse_point);
+
+  const Result<std::string> real_target_path = os::realpath(target);
+
+  if (!real_reparse_point_path.isSome()) {
+    return Error(
+        "Failed to get realpath for '" +
+        reparse_point + "': " + (real_reparse_point_path.isError() ?
+        real_reparse_point_path.error() : "No such directory"));
+  }
+
+  if (!real_target_path.isSome()) {
+    return Error(
+        "Failed to get realpath for '" +
+        target + "': " + (real_target_path.isError() ?
+        real_target_path.error() : "No such directory"));
+  }
+
+  const std::string& absolute_target_path = real_target_path.get();
+
+  // Determine if target is a folder or a file. This makes a difference
+  // in the way we call `create_symbolic_link`.
+  struct _stat absolute_target_path_stat;
+  if (::_stat(absolute_target_path.c_str(), &absolute_target_path_stat) != 0) {
+    return ErrnoError("'_stat' failed on path '" + absolute_target_path + "'");
+  }
+
+  const bool target_is_folder = S_ISDIR(absolute_target_path_stat.st_mode);
+
+  // Bail out if target is already a reparse point.
+  Try<bool> attribute_set = reparse_point_attribute_set(absolute_target_path);
+  if (!attribute_set.isSome()) {
+    return Error(
+        "Could not get reparse point attribute for '" + absolute_target_path +
+        "'" + (attribute_set.isError() ? ": " + attribute_set.error() : ""));
+  }
+
+  if (attribute_set.get()) {
+    return Error(
+        "Path '" + absolute_target_path + "' is already a reparse point");
+  }
+
+  // `CreateSymbolicLink` adjusts the process token's privileges to allow for
+  // symlink creation. MSDN[1] makes no guarantee when it comes to the thread
+  // safety of this operation, so we are making use of a mutex to prevent
+  // multiple concurrent calls.
+  //
+  // [1] https://msdn.microsoft.com/en-us/library/windows/desktop/aa363866(v=vs.85).aspx
+  static std::mutex adjust_privileges_mutex;
+  synchronized(adjust_privileges_mutex) {
+    if (!::CreateSymbolicLink(
+        reparse_point.c_str(),  // path to the symbolic link
+        target.c_str(),        // symlink target
+        target_is_folder ? SYMBOLIC_LINK_FLAG_DIRECTORY : 0)) {
+      return WindowsError(
+          "'internal::windows::create_symbolic_link': 'CreateSymbolicLink' "
+          "call failed");
+    }
+  }
+
+  return Nothing();
+}
+
 } // namespace windows {
 } // namespace internal {