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;
}