Skip to content

Commit

Permalink
[io/win] Always use absolute, canonicalized paths.
Browse files Browse the repository at this point in the history
This ensures that on Windows we take advantage of 32k-character file name limit consistently for all files and directories operations.

TEST=ci
BUG=#56125
BUG=#55972
CoreLibraryReviewExempt: adds windows-specific error code for invalid file name
Change-Id: I83bd3ceac579e589469e47b2cf5216db457cae8b
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/375421
Reviewed-by: Slava Egorov <[email protected]>
Commit-Queue: Alexander Aprelev <[email protected]>
  • Loading branch information
aam authored and Commit Queue committed Aug 15, 2024
1 parent ca77bea commit f9c603e
Show file tree
Hide file tree
Showing 9 changed files with 111 additions and 150 deletions.
11 changes: 5 additions & 6 deletions runtime/bin/directory_win.cc
Original file line number Diff line number Diff line change
Expand Up @@ -379,7 +379,7 @@ Directory::ExistsResult Directory::Exists(const wchar_t* dir_name) {

Directory::ExistsResult Directory::Exists(Namespace* namespc,
const char* dir_name) {
const auto path = ToWinAPIDirectoryPath(dir_name);
const auto path = ToWinAPIPath(dir_name);
return Exists(path.get());
}

Expand All @@ -400,7 +400,7 @@ char* Directory::CurrentNoScope() {
}

bool Directory::Create(Namespace* namespc, const char* dir_name) {
const auto path = ToWinAPIDirectoryPath(dir_name);
const auto path = ToWinAPIPath(dir_name);
int create_status = CreateDirectoryW(path.get(), nullptr);
// If the directory already existed, treat it as a success.
if ((create_status == 0) && (GetLastError() == ERROR_ALREADY_EXISTS) &&
Expand Down Expand Up @@ -506,8 +506,7 @@ const char* Directory::CreateTemp(Namespace* namespc, const char* prefix) {
bool Directory::Delete(Namespace* namespc,
const char* dir_name,
bool recursive) {
const auto path =
ToWinAPIDirectoryPath(dir_name, /*force_long_prefix=*/recursive);
const auto path = ToWinAPIPath(dir_name);
bool result = false;
if (!recursive) {
if (File::GetType(path.get(), /*follow_links=*/true) ==
Expand All @@ -526,13 +525,13 @@ bool Directory::Delete(Namespace* namespc,
bool Directory::Rename(Namespace* namespc,
const char* old_name,
const char* new_name) {
const auto old_path = ToWinAPIDirectoryPath(old_name);
const auto old_path = ToWinAPIPath(old_name);
ExistsResult exists = Exists(old_path.get());
if (exists != EXISTS) {
SetLastError(ERROR_FILE_NOT_FOUND);
return false;
}
const auto new_path = ToWinAPIDirectoryPath(new_name);
const auto new_path = ToWinAPIPath(new_name);
DWORD flags = MOVEFILE_WRITE_THROUGH;
int move_status = MoveFileExW(old_path.get(), new_path.get(), flags);
return (move_status != 0);
Expand Down
160 changes: 39 additions & 121 deletions runtime/bin/file_win.cc
Original file line number Diff line number Diff line change
Expand Up @@ -379,115 +379,33 @@ static bool IsDeviceNamespacePrefixed(const std::unique_ptr<wchar_t[]>& path) {
kDeviceNamespacePrefixLength) == 0;
}

// Converts the given UTF8 path to wide char. If resulting path does not
// fit into MAX_PATH / MAX_DIRECTORY_PATH (or if |force_long_prefix| is true)
// then converts the path to the absolute `\\?\`-prefixed form.
// Converts the given UTF8 path to wide char '\\?\'-prefix absolute path.
//
// This function does not change paths which are already prefixed with `\\.\`
// prefix.
//
// UNC paths (`\\server\share\...`) are converted to `\\?\UNC\server\share\...`
// if necessary.
//
// Note:
// 1. Some WinAPI functions (like SetCurrentDirectoryW) are always limited
// to MAX_PATH long paths and converting to `\\?\`-prefixed form does not
// remove this limitation. Always check Win API documentation.
// 2. This function might change relative path to an absolute path.
static std::unique_ptr<wchar_t[]> ToWinAPIPath(const char* utf8_path,
bool is_file,
bool force_long_prefix) {
// Note that some WinAPI functions (like SetCurrentDirectoryW) are always
// limited to MAX_PATH long paths and converting to `\\?\`-prefixed form does
// not remove this limitation. Always check Win API documentation.
std::unique_ptr<wchar_t[]> ToWinAPIPath(const char* utf8_path) {
auto path = Utf8ToWideChar(utf8_path);

// File name and Directory name have different size limit.
// Reference: https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file#maximum-path-length-limitation
const int path_short_limit = is_file ? MAX_PATH : MAX_DIRECTORY_PATH;

std::unique_ptr<wchar_t[]> absolute_path;
// Need to convert to absolute path if we want to use long prefix
// to normalize the path - this is to get rid of .., . that can't be
// interpreted in the long-prefixed mode).
if (force_long_prefix || !IsAbsolutePath(path.get())) {
absolute_path = ConvertToAbsolutePath(path);
if (absolute_path == nullptr) {
return path;
}
} else {
absolute_path = std::move(path);
// Among other things ConvertToAbsolutePath replaces '/' with '\',
// which PathAllocCanonicalize won't do.
auto abs_path = ConvertToAbsolutePath(path);
if (abs_path.get() == nullptr) {
return std::unique_ptr<wchar_t[]>(nullptr);
}

int path_length = wcslen(absolute_path.get());

if (!force_long_prefix && path_length < path_short_limit) {
if (path == nullptr) {
return absolute_path;
} else {
return path;
}
PWSTR canonical_path;
if (PathAllocCanonicalize(abs_path.get(),
PATHCCH_ENSURE_IS_EXTENDED_LENGTH_PATH,
&canonical_path) != S_OK) {
return std::unique_ptr<wchar_t[]>(nullptr);
}

if (IsLongPathPrefixed(absolute_path) ||
IsDeviceNamespacePrefixed(absolute_path)) {
return absolute_path;
}

// If the path already starts with `\\` but not with `\\?\` or `\\.\`
// then we need to replace `\\` with `\\?\UNC\`.
const bool is_unc = (wcsncmp(absolute_path.get(), L"\\\\", 2) == 0);
const wchar_t* kUNCLongPathPrefix = L"\\\\?\\UNC\\";
const int kUNCLongPathPrefixLength = 8;

// Add prefix and replace forward slashes with backward slashes.
//
// If the path is UNC we skip the first two characters of the path `\\`
// hence -2.
const intptr_t result_length =
(is_unc ? kUNCLongPathPrefixLength : kLongPathPrefixLength) +
path_length + (is_unc ? -2 : 0) + 1;
auto result = std::make_unique<wchar_t[]>(result_length);
intptr_t result_pos;
intptr_t path_pos;
if (is_unc) {
wcsncpy(result.get(), kUNCLongPathPrefix, kUNCLongPathPrefixLength);
result_pos = kUNCLongPathPrefixLength;
path_pos = 2;
} else {
wcsncpy(result.get(), kLongPathPrefix, kLongPathPrefixLength);
result_pos = kLongPathPrefixLength;
path_pos = 0;
}
while (path_pos < path_length) {
wchar_t ch = absolute_path[path_pos++];
result.get()[result_pos++] = ch == L'/' ? L'\\' : ch;
}
result.get()[result_pos++] = L'\0';
ASSERT(result_pos == result_length);
auto result = std::unique_ptr<wchar_t[]>(wcsdup(canonical_path));
LocalFree(canonical_path);
return result;
}

// Converts the given UTF8 path to wide char. If resulting path does not
// fit into MAX_DIRECTORY_PATH (or if |force_long_prefix| is true) then
// converts the path to the absolute `\\?\`-prefixed form.
//
// Note:
// 1. Some WinAPI functions (like SetCurrentDirectoryW) are always limited
// to MAX_PATH long paths and converting to `\\?\`-prefixed form does not
// remove this limitation. Always check Win API documentation.
// 2. This function might change relative path to an absolute path.
static std::unique_ptr<wchar_t[]> ToWinAPIFilePath(
const char* path,
bool force_long_prefix = false) {
return ToWinAPIPath(path, /*is_file=*/true, force_long_prefix);
}

std::unique_ptr<wchar_t[]> ToWinAPIDirectoryPath(
const char* path,
bool force_long_prefix /* = false */) {
return ToWinAPIPath(path, /*is_file=*/false, force_long_prefix);
}

File* File::Open(Namespace* namespc, const char* name, FileOpenMode mode) {
const auto path = ToWinAPIFilePath(name);
const auto path = ToWinAPIPath(name);
File* file = FileOpenW(path.get(), mode);
return file;
}
Expand Down Expand Up @@ -557,7 +475,7 @@ static bool FileExists(const wchar_t* path) {
}

bool File::Exists(Namespace* namespc, const char* name) {
const auto path = ToWinAPIFilePath(name);
const auto path = ToWinAPIPath(name);
return FileExists(path.get());
}

Expand All @@ -571,7 +489,7 @@ bool File::ExistsUri(Namespace* namespc, const char* uri) {
}

bool File::Create(Namespace* namespc, const char* name, bool exclusive) {
const auto path = ToWinAPIFilePath(name);
const auto path = ToWinAPIPath(name);
int flags = O_RDONLY | O_CREAT;
if (exclusive) {
flags |= O_EXCL;
Expand Down Expand Up @@ -616,12 +534,12 @@ typedef struct _REPARSE_DATA_BUFFER {
bool File::CreateLink(Namespace* namespc,
const char* utf8_name,
const char* utf8_target) {
const auto name = ToWinAPIFilePath(utf8_name);
const auto name = ToWinAPIPath(utf8_name);

std::unique_ptr<wchar_t[]> target;
bool target_is_directory;
if (File::IsAbsolutePath(utf8_target)) {
target = ToWinAPIFilePath(utf8_target);
target = ToWinAPIPath(utf8_target);
target_is_directory =
File::GetType(target.get(), /*follow_links=*/true) == kIsDirectory;
} else {
Expand Down Expand Up @@ -706,7 +624,7 @@ bool File::CreatePipe(Namespace* namespc, File** readPipe, File** writePipe) {
}

bool File::Delete(Namespace* namespc, const char* name) {
const auto path = ToWinAPIFilePath(name);
const auto path = ToWinAPIPath(name);
int status = _wremove(path.get());
return status != -1;
}
Expand All @@ -731,20 +649,20 @@ static bool DeleteLinkHelper(const wchar_t* path) {
}

bool File::DeleteLink(Namespace* namespc, const char* name) {
const auto path = ToWinAPIFilePath(name);
const auto path = ToWinAPIPath(name);
return DeleteLinkHelper(path.get());
}

static bool RenameHelper(File::Type expected,
const char* old_name,
const char* new_name) {
const auto old_path = ToWinAPIFilePath(old_name);
const auto old_path = ToWinAPIPath(old_name);
File::Type type = File::GetType(old_path.get(), /*follow_links=*/false);
if (type != expected) {
SetLastError(ERROR_FILE_NOT_FOUND);
return false;
}
const auto new_path = ToWinAPIFilePath(new_name);
const auto new_path = ToWinAPIPath(new_name);
DWORD flags = MOVEFILE_WRITE_THROUGH | MOVEFILE_REPLACE_EXISTING;

// Symbolic links (e.g. produced by Link.create) to directories on Windows
Expand Down Expand Up @@ -860,8 +778,8 @@ bool File::Copy(Namespace* namespc,
const char* new_name) {
// We are going to concatenate new path with temporary file names in
// CopyIntoTempFile so we force long prefix no matter what.
const auto old_path = ToWinAPIFilePath(old_name);
const auto new_path = ToWinAPIFilePath(new_name, /*force_long_prefix=*/true);
const auto old_path = ToWinAPIPath(old_name);
const auto new_path = ToWinAPIPath(new_name);

File::Type type = GetType(old_path.get(), /*follow_links=*/false);
if (type != kIsFile) {
Expand Down Expand Up @@ -893,7 +811,7 @@ bool File::Copy(Namespace* namespc,

int64_t File::LengthFromPath(Namespace* namespc, const char* name) {
struct __stat64 st;
const auto path = ToWinAPIFilePath(name);
const auto path = ToWinAPIPath(name);
if (!StatHelper(path.get(), &st)) {
return -1;
}
Expand All @@ -904,7 +822,7 @@ const char* File::LinkTarget(Namespace* namespc,
const char* pathname,
char* dest,
int dest_size) {
const auto path = ToWinAPIFilePath(pathname);
const auto path = ToWinAPIPath(pathname);
HANDLE dir_handle = CreateFileW(
path.get(), GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr,
Expand Down Expand Up @@ -991,7 +909,7 @@ const char* File::LinkTarget(Namespace* namespc,
}

void File::Stat(Namespace* namespc, const char* name, int64_t* data) {
const auto path = ToWinAPIFilePath(name);
const auto path = ToWinAPIPath(name);
File::Type type = GetType(path.get(), /*follow_links=*/true);
data[kType] = type;
if (type != kDoesNotExist) {
Expand All @@ -1011,7 +929,7 @@ void File::Stat(Namespace* namespc, const char* name, int64_t* data) {

time_t File::LastAccessed(Namespace* namespc, const char* name) {
struct __stat64 st;
const auto path = ToWinAPIFilePath(name);
const auto path = ToWinAPIPath(name);
if (!StatHelper(path.get(), &st)) {
return -1;
}
Expand All @@ -1020,7 +938,7 @@ time_t File::LastAccessed(Namespace* namespc, const char* name) {

time_t File::LastModified(Namespace* namespc, const char* name) {
struct __stat64 st;
const auto path = ToWinAPIFilePath(name);
const auto path = ToWinAPIPath(name);
if (!StatHelper(path.get(), &st)) {
return -1;
}
Expand All @@ -1031,7 +949,7 @@ bool File::SetLastAccessed(Namespace* namespc,
const char* name,
int64_t millis) {
struct __stat64 st;
const auto path = ToWinAPIFilePath(name);
const auto path = ToWinAPIPath(name);
if (!StatHelper(path.get(), &st)) { // Checks that it is a file.
return false;
}
Expand Down Expand Up @@ -1060,7 +978,7 @@ bool File::SetLastModified(Namespace* namespc,
int64_t millis) {
// First get the current times.
struct __stat64 st;
const auto path = ToWinAPIFilePath(name);
const auto path = ToWinAPIPath(name);
if (!StatHelper(path.get(), &st)) {
return false;
}
Expand Down Expand Up @@ -1089,7 +1007,7 @@ const char* File::GetCanonicalPath(Namespace* namespc,
const char* pathname,
char* dest,
int dest_size) {
const auto path = ToWinAPIFilePath(pathname);
const auto path = ToWinAPIPath(pathname);
HANDLE file_handle =
CreateFileW(path.get(), 0, FILE_SHARE_READ, nullptr, OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS, nullptr);
Expand Down Expand Up @@ -1191,7 +1109,7 @@ File::Type File::GetType(const wchar_t* path, bool follow_links) {
File::Type File::GetType(Namespace* namespc,
const char* name,
bool follow_links) {
const auto path = ToWinAPIFilePath(name);
const auto path = ToWinAPIPath(name);
return GetType(path.get(), follow_links);
}

Expand All @@ -1202,8 +1120,8 @@ File::Identical File::AreIdentical(Namespace* namespc_1,
USE(namespc_1);
USE(namespc_2);
BY_HANDLE_FILE_INFORMATION file_info[2];
const std::unique_ptr<wchar_t[]> file_names[2] = {ToWinAPIFilePath(file_1),
ToWinAPIFilePath(file_2)};
const std::unique_ptr<wchar_t[]> file_names[2] = {ToWinAPIPath(file_1),
ToWinAPIPath(file_2)};
for (int i = 0; i < 2; ++i) {
HANDLE file_handle = CreateFileW(
file_names[i].get(), 0,
Expand Down
16 changes: 5 additions & 11 deletions runtime/bin/file_win.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,12 @@
namespace dart {
namespace bin {

// Converts the given UTF8 path to wide char. If resulting path does not
// fit into MAX_DIRECTORY_PATH (or if |force_long_prefix| is true) then
// converts the path to the absolute `\\?\`-prefixed form.
// Converts the given UTF8 path to wide char '\\?\'-prefix absolute path.
//
// Note:
// 1. Some WinAPI functions (like SetCurrentDirectoryW) are always limited
// to MAX_PATH long paths and converting to `\\?\`-prefixed form does not
// remove this limitation. Always check Win API documentation.
// 2. This function might change relative path to an absolute path.
std::unique_ptr<wchar_t[]> ToWinAPIDirectoryPath(
const char* path,
bool force_long_prefix = false);
// Note that some WinAPI functions (like SetCurrentDirectoryW) are always
// limited to MAX_PATH long paths and converting to `\\?\`-prefixed form does
// not remove this limitation. Always check Win API documentation.
std::unique_ptr<wchar_t[]> ToWinAPIPath(const char* path);

} // namespace bin
} // namespace dart
Expand Down
1 change: 1 addition & 0 deletions sdk/lib/io/common.dart
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ const _errorNetworkAccessDenied = 65;
const _errorBadNetName = 67;
const _errorFileExists = 80;
const _errorDriveLocked = 108;
const _errorInvalidName = 123;
const _errorBadPathName = 161;
const _errorAlreadyExists = 183;
const _errorFilenameExedRange = 206;
Expand Down
Loading

0 comments on commit f9c603e

Please sign in to comment.