int CreateJunction()

in src/main/native/windows/file.cc [169:434]


int CreateJunction(const wstring& junction_name, const wstring& junction_target,
                   wstring* error) {
  if (!IsAbsoluteNormalizedWindowsPath(junction_name)) {
    if (error) {
      *error = MakeErrorMessage(
          WSTR(__FILE__), __LINE__, L"CreateJunction", junction_name,
          L"expected an absolute Windows path for junction_name");
    }
    return CreateJunctionResult::kError;
  }
  if (!IsAbsoluteNormalizedWindowsPath(junction_target)) {
    if (error) {
      *error = MakeErrorMessage(
          WSTR(__FILE__), __LINE__, L"CreateJunction", junction_target,
          L"expected an absolute Windows path for junction_target");
    }
    return CreateJunctionResult::kError;
  }

  const WCHAR* target = HasUncPrefix(junction_target.c_str())
                            ? junction_target.c_str() + 4
                            : junction_target.c_str();
  const size_t target_size = HasUncPrefix(junction_target.c_str())
                                 ? junction_target.size() - 4
                                 : junction_target.size();
  // The entire REPARSE_DATA_BUFFER cannot be larger than
  // MAXIMUM_REPARSE_DATA_BUFFER_SIZE bytes.
  //
  // The structure's layout is:
  //   [8 bytes] : ReparseTag, ReparseDataLength, Reserved
  //   [8 bytes] : MountPointReparseBuffer members before PathBuffer
  //   ---- start of MountPointReparseBuffer.PathBuffer ----
  //   [4 WCHARs]             : "\??\" prefix
  //   [target.size() WCHARs] : junction target name
  //   [1 WCHAR]              : null-terminator
  //   [target.size() WCHARs] : junction target displayed name
  //   [1 WCHAR]              : null-terminator
  // The sum of these must not exceed MAXIMUM_REPARSE_DATA_BUFFER_SIZE.
  // We can rearrange this to get the limit for target.size().
  static const size_t kMaxJunctionTargetLen =
      ((MAXIMUM_REPARSE_DATA_BUFFER_SIZE -
        offsetof(REPARSE_DATA_BUFFER, MountPointReparseBuffer.PathBuffer)) /
           sizeof(WCHAR) -
       /* one "\??\" prefix */ 4 -
       /* two null terminators */ 2) /
      /* two copies of the string are stored */ 2;
  if (target_size > kMaxJunctionTargetLen) {
    if (error) {
      *error = MakeErrorMessage(WSTR(__FILE__), __LINE__, L"CreateJunction",
                                target, L"target path is too long");
    }
    return CreateJunctionResult::kTargetNameTooLong;
  }
  const wstring name = HasUncPrefix(junction_name.c_str())
                           ? junction_name
                           : (wstring(L"\\\\?\\") + junction_name);

  // Junctions are directories, so create a directory.
  // If CreateDirectoryW succeeds, we'll try to set the junction's target.
  // If CreateDirectoryW fails, we don't care about the exact reason -- could be
  // that the directory already exists, or we have no access to create a
  // directory, or the path was invalid to begin with. Either way set `create`
  // to false, meaning we'll just attempt to open the path for metadata-reading
  // and check if it's a junction pointing to the desired target.
  bool create = CreateDirectoryW(name.c_str(), nullptr) != 0;

  AutoHandle handle;
  if (create) {
    handle = CreateFileW(
        name.c_str(), GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, nullptr,
        OPEN_EXISTING,
        FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, nullptr);
  }

  if (!handle.IsValid()) {
    // We can't open the directory for writing: either we didn't even try to
    // (`create` was false), or the path disappeared, or it turned into a file,
    // or another process holds it open without write-sharing.
    // Either way, don't try to create the junction, just try opening it without
    // any read or write access (we can still read its metadata) and maximum
    // sharing, and check its target.
    create = false;
    handle = CreateFileW(
        name.c_str(), 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
        nullptr, OPEN_EXISTING,
        FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, nullptr);
    if (!handle.IsValid()) {
      // We can't open the directory at all: either it disappeared, or it turned
      // into a file, or the path is invalid, or another process holds it open
      // without any sharing. Give up.
      DWORD err = GetLastError();
      if (err == ERROR_SHARING_VIOLATION) {
        // The junction is held open by another process.
        return CreateJunctionResult::kAccessDenied;
      } else if (err == ERROR_FILE_NOT_FOUND || err == ERROR_PATH_NOT_FOUND) {
        // Meanwhile the directory disappeared or one of its parent directories
        // disappeared.
        return CreateJunctionResult::kDisappeared;
      }

      // The path seems to exist yet we cannot open it for metadata-reading.
      // Report as much information as we have, then give up.
      if (error) {
        *error = MakeErrorMessage(WSTR(__FILE__), __LINE__, L"CreateFileW",
                                  name, err);
      }
      return CreateJunctionResult::kError;
    }
  }

  // We have an open handle to the file! It may still be other than a junction,
  // so check its attributes.
  BY_HANDLE_FILE_INFORMATION info;
  if (!GetFileInformationByHandle(handle, &info)) {
    DWORD err = GetLastError();
    // Some unknown error occurred.
    if (error) {
      *error = MakeErrorMessage(WSTR(__FILE__), __LINE__,
                                L"GetFileInformationByHandle", name, err);
    }
    return CreateJunctionResult::kError;
  }

  if (info.dwFileAttributes == INVALID_FILE_ATTRIBUTES) {
    DWORD err = GetLastError();
    // Some unknown error occurred.
    if (error) {
      *error = MakeErrorMessage(WSTR(__FILE__), __LINE__,
                                L"GetFileInformationByHandle", name, err);
    }
    return CreateJunctionResult::kError;
  }

  if (info.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) {
    // The path already exists and it's a junction. Do not overwrite, just check
    // its target.
    create = false;
  }

  if (create) {
    if (!(info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
      // Even though we managed to create the directory and it didn't exist
      // before, another process changed it in the meantime so it's no longer a
      // directory.
      create = false;
      if (!(info.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)) {
        // The path is no longer a directory, and it's not a junction either.
        // Though this is a case for kAlreadyExistsButNotJunction, let's instead
        // print the attributes and return kError, to give more information to
        // the user.
        if (error) {
          *error = MakeErrorMessage(
              WSTR(__FILE__), __LINE__, L"GetFileInformationByHandle", name,
              wstring(L"attrs=0x") + uint32asHexString(info.dwFileAttributes));
        }
        return CreateJunctionResult::kError;
      }
    }
  }

  if (!create) {
    // The path already exists. Check if it's a junction.
    if (!(info.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)) {
      return CreateJunctionResult::kAlreadyExistsButNotJunction;
    }
  }

  uint8_t reparse_buffer_bytes[MAXIMUM_REPARSE_DATA_BUFFER_SIZE];
  PREPARSE_DATA_BUFFER reparse_buffer =
      reinterpret_cast<PREPARSE_DATA_BUFFER>(reparse_buffer_bytes);
  if (create) {
    // The junction doesn't exist yet, and we have an open handle to the
    // candidate directory with write access and no sharing. Proceed to turn the
    // directory into a junction.

    memset(reparse_buffer_bytes, 0, MAXIMUM_REPARSE_DATA_BUFFER_SIZE);

    reparse_buffer->MountPointReparseBuffer.SubstituteNameOffset = 0;
    reparse_buffer->MountPointReparseBuffer.SubstituteNameLength =
        (4 + target_size) * sizeof(WCHAR);
    reparse_buffer->MountPointReparseBuffer.PrintNameOffset =
        reparse_buffer->MountPointReparseBuffer.SubstituteNameLength +
        /* null-terminator */ sizeof(WCHAR);
    reparse_buffer->MountPointReparseBuffer.PrintNameLength =
        target_size * sizeof(WCHAR);

    reparse_buffer->ReparseTag = IO_REPARSE_TAG_MOUNT_POINT;
    reparse_buffer->ReparseDataLength =
        4 * sizeof(USHORT) +
        reparse_buffer->MountPointReparseBuffer.SubstituteNameLength +
        reparse_buffer->MountPointReparseBuffer.PrintNameLength +
        /* 2 null-terminators */ (2 * sizeof(WCHAR));
    reparse_buffer->Reserved = 0;

    // "\??\" is meaningful to the kernel, it's a synomym for the "\DosDevices\"
    // object path. (NOT to be confused with "\\?\" which is meaningful for the
    // Win32 API.) We need to use this prefix to tell the kernel where the
    // reparse point is pointing to.
    memcpy((uint8_t*)reparse_buffer->MountPointReparseBuffer.PathBuffer +
               reparse_buffer->MountPointReparseBuffer.SubstituteNameOffset,
           L"\\??\\", 4 * sizeof(WCHAR));
    memcpy((uint8_t*)reparse_buffer->MountPointReparseBuffer.PathBuffer +
               reparse_buffer->MountPointReparseBuffer.SubstituteNameOffset +
               4 * sizeof(WCHAR),
           target,
           reparse_buffer->MountPointReparseBuffer.SubstituteNameLength -
               4 * sizeof(WCHAR));

    // In addition to their target, junctions also have another string which is
    // a user-visible name of where the junction points, as listed by "dir".
    // This can be any string and won't affect the usability of the junction.
    // MKLINK uses the target path without the "\??\" prefix as the display
    // name, so let's do that here too. This is also in line with how UNIX
    // behaves. Using a dummy or fake display name would be misleading, it would
    // make the output of `dir` look like:
    //   2017-01-18  01:37 PM    <JUNCTION>     juncname [dummy string]
    memcpy((uint8_t*)reparse_buffer->MountPointReparseBuffer.PathBuffer +
               reparse_buffer->MountPointReparseBuffer.PrintNameOffset,
           target, reparse_buffer->MountPointReparseBuffer.PrintNameLength);

    DWORD bytes_returned;
    if (!::DeviceIoControl(
            handle, FSCTL_SET_REPARSE_POINT, reparse_buffer,
            reparse_buffer->ReparseDataLength +
                offsetof(REPARSE_DATA_BUFFER, GenericReparseBuffer.DataBuffer),
            nullptr, 0, &bytes_returned, nullptr)) {
      DWORD err = GetLastError();
      if (err == ERROR_DIR_NOT_EMPTY) {
        return CreateJunctionResult::kAlreadyExistsButNotJunction;
      }
      // Some unknown error occurred.
      if (error) {
        *error = MakeErrorMessage(WSTR(__FILE__), __LINE__, L"DeviceIoControl",
                                  name, err);
      }
      return CreateJunctionResult::kError;
    }
  } else {
    // The junction already exists. Check if it points to the right target.

    DWORD bytes_returned;
    if (!::DeviceIoControl(handle, FSCTL_GET_REPARSE_POINT, nullptr, 0,
                           reparse_buffer, MAXIMUM_REPARSE_DATA_BUFFER_SIZE,
                           &bytes_returned, nullptr)) {
      DWORD err = GetLastError();
      // Some unknown error occurred.
      if (error) {
        *error = MakeErrorMessage(WSTR(__FILE__), __LINE__, L"DeviceIoControl",
                                  name, err);
      }
      return CreateJunctionResult::kError;
    }

    WCHAR* actual_target =
        reparse_buffer->MountPointReparseBuffer.PathBuffer +
        reparse_buffer->MountPointReparseBuffer.SubstituteNameOffset +
        /* "\??\" prefix */ 4;
    if (reparse_buffer->MountPointReparseBuffer.SubstituteNameLength !=
            (/* "\??\" prefix */ 4 + target_size) * sizeof(WCHAR) ||
        _wcsnicmp(actual_target, target, target_size) != 0) {
      return CreateJunctionResult::kAlreadyExistsWithDifferentTarget;
    }
  }

  return CreateJunctionResult::kSuccess;
}