mirror of
https://github.com/dart-lang/sdk
synced 2024-09-30 04:48:37 +00:00
[vm/io] Cleanup long path handling on Windows
* Switch to `std::unique_ptr<wchar_t[]>` to represent a dynamically strings instead of wrappers like `StringRAII` and `Utf8ToWideScope`; * Avoid back and forth conversion between UTF8 and UTF16: convert to UTF16 first then work on that. This also simplifies code - previously it tried to work with strings allocated in different ways uniformly, which is actually unnecessary if resulting string needs to be converted to UTF16 (and allocated with `malloc`) anyway; * Fix a bug in `File::CreateLink`: it was handling relative links with long paths incorrectly. Change file_long_path_test to cover this case and make it run on non-Windows systems as well to ensure that all behavior that can match actually matches. TEST=ci Change-Id: I1279aff1d2cdace5e2ce8633c2f7ea69a34fe41a Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/356680 Commit-Queue: Slava Egorov <vegorov@google.com> Reviewed-by: Alexander Aprelev <aam@google.com>
This commit is contained in:
parent
e7daade291
commit
c2e9ceeb6b
|
@ -637,6 +637,11 @@ has been specified on the command line.''')
|
|||
var progress = Progress.find(data["progress"] as String);
|
||||
var nnbdMode = NnbdMode.find(data["nnbd"] as String);
|
||||
|
||||
// TODO: deflaking should report logs.
|
||||
if (data['repeat'] == 5) {
|
||||
progress = Progress.verbose;
|
||||
}
|
||||
|
||||
void addConfiguration(Configuration innerConfiguration,
|
||||
[String? namedConfiguration]) {
|
||||
var configuration = TestConfiguration(
|
||||
|
|
|
@ -242,6 +242,11 @@ class Directory {
|
|||
enum ExistsResult { UNKNOWN, EXISTS, DOES_NOT_EXIST };
|
||||
|
||||
static void List(DirectoryListing* listing);
|
||||
|
||||
#if defined(DART_HOST_OS_WINDOWS)
|
||||
static ExistsResult Exists(const wchar_t* path);
|
||||
#endif
|
||||
|
||||
static ExistsResult Exists(Namespace* namespc, const char* path);
|
||||
|
||||
// Returns the current working directory. The caller must call
|
||||
|
|
|
@ -47,8 +47,8 @@ const char* PathBuffer::AsScopedString() const {
|
|||
}
|
||||
|
||||
bool PathBuffer::Add(const char* name) {
|
||||
Utf8ToWideScope wide_name(name);
|
||||
return AddW(wide_name.wide());
|
||||
const auto wide_name = Utf8ToWideChar(name);
|
||||
return AddW(wide_name.get());
|
||||
}
|
||||
|
||||
bool PathBuffer::AddW(const wchar_t* name) {
|
||||
|
@ -228,114 +228,137 @@ void DirectoryListingEntry::ResetLink() {
|
|||
}
|
||||
}
|
||||
|
||||
static bool DeleteFile(const wchar_t* file_name, PathBuffer* path) {
|
||||
if (!path->AddW(file_name)) {
|
||||
return false;
|
||||
namespace {
|
||||
class RecursiveDeleter {
|
||||
public:
|
||||
RecursiveDeleter() : path_() {}
|
||||
|
||||
// Delete the given directory recursively. Expects an absolute long prefixed
|
||||
// path - which allows deletion to proceed without checking if path needs to
|
||||
// be prefixed while recursing.
|
||||
bool DeleteRecursively(const std::unique_ptr<wchar_t[]>& path) {
|
||||
ASSERT(wcsncmp(path.get(), L"\\\\?\\", 4) == 0);
|
||||
path_.Reset(0);
|
||||
if (path == nullptr || !path_.AddW(path.get()) || path_.length() == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (path_.AsStringW()[path_.length() - 1] == '\\') {
|
||||
// Strip trailing slash otherwise FindFirstFileW will fail.
|
||||
path_.Reset(path_.length() - 1);
|
||||
}
|
||||
|
||||
return DeleteDirectory();
|
||||
}
|
||||
|
||||
if (DeleteFileW(path->AsStringW()) != 0) {
|
||||
return true;
|
||||
}
|
||||
private:
|
||||
const wchar_t* path() const { return path_.AsStringW(); }
|
||||
|
||||
// If we failed because the file is read-only, make it writeable and try
|
||||
// again. This mirrors Linux/Mac where a directory containing read-only files
|
||||
// can still be recursively deleted.
|
||||
if (GetLastError() == ERROR_ACCESS_DENIED) {
|
||||
DWORD attributes = GetFileAttributesW(path->AsStringW());
|
||||
bool DeleteDirectory() {
|
||||
DWORD attributes = GetFileAttributesW(path());
|
||||
if (attributes == INVALID_FILE_ATTRIBUTES) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((attributes & FILE_ATTRIBUTE_READONLY) == FILE_ATTRIBUTE_READONLY) {
|
||||
attributes &= ~FILE_ATTRIBUTE_READONLY;
|
||||
// If the directory is a junction, it's pointing to some other place in the
|
||||
// filesystem that we do not want to recurse into.
|
||||
if ((attributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0) {
|
||||
// Just delete the junction itself.
|
||||
return RemoveDirectoryW(path()) != 0;
|
||||
}
|
||||
|
||||
if (SetFileAttributesW(path->AsStringW(), attributes) == 0) {
|
||||
// If it's a file, remove it directly.
|
||||
if ((attributes & FILE_ATTRIBUTE_DIRECTORY) == 0) {
|
||||
return DeleteFile();
|
||||
}
|
||||
|
||||
if (!path_.AddW(L"\\*")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
WIN32_FIND_DATAW find_file_data;
|
||||
HANDLE find_handle = FindFirstFileW(path(), &find_file_data);
|
||||
|
||||
if (find_handle == INVALID_HANDLE_VALUE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Adjust the path by removing the '*' used for the search.
|
||||
const int path_length = path_.length() - 1;
|
||||
path_.Reset(path_length);
|
||||
|
||||
do {
|
||||
if (!DeleteEntry(&find_file_data)) {
|
||||
break;
|
||||
}
|
||||
path_.Reset(path_length); // DeleteEntry adds to the path.
|
||||
} while (FindNextFileW(find_handle, &find_file_data) != 0);
|
||||
|
||||
DWORD last_error = GetLastError();
|
||||
// Always close handle.
|
||||
FindClose(find_handle);
|
||||
if (last_error != ERROR_NO_MORE_FILES) {
|
||||
// Unexpected error, set and return.
|
||||
SetLastError(last_error);
|
||||
return false;
|
||||
}
|
||||
// All content deleted successfully, try to delete directory.
|
||||
// Drop the "\" from the end of the path.
|
||||
path_.Reset(path_length - 1);
|
||||
return RemoveDirectoryW(path()) != 0;
|
||||
}
|
||||
|
||||
bool DeleteEntry(LPWIN32_FIND_DATAW find_file_data) {
|
||||
wchar_t* entry_name = find_file_data->cFileName;
|
||||
if ((wcscmp(entry_name, L".") == 0) || (wcscmp(entry_name, L"..") == 0)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!path_.AddW(entry_name)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
DWORD attributes = find_file_data->dwFileAttributes;
|
||||
if ((attributes & FILE_ATTRIBUTE_DIRECTORY) != 0) {
|
||||
return DeleteDirectory();
|
||||
} else {
|
||||
return DeleteFile();
|
||||
}
|
||||
}
|
||||
|
||||
bool DeleteFile() {
|
||||
if (DeleteFileW(path()) != 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If we failed because the file is read-only, make it writeable and try
|
||||
// again. This mirrors Linux/Mac where a directory containing read-only
|
||||
// files can still be recursively deleted.
|
||||
if (GetLastError() == ERROR_ACCESS_DENIED) {
|
||||
DWORD attributes = GetFileAttributesW(path());
|
||||
if (attributes == INVALID_FILE_ATTRIBUTES) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return DeleteFileW(path->AsStringW()) != 0;
|
||||
if ((attributes & FILE_ATTRIBUTE_READONLY) == FILE_ATTRIBUTE_READONLY) {
|
||||
attributes &= ~FILE_ATTRIBUTE_READONLY;
|
||||
|
||||
if (SetFileAttributesW(path(), attributes) == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return DeleteFileW(path()) != 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool DeleteDir(const wchar_t* dir_name, PathBuffer* path) {
|
||||
if ((wcscmp(dir_name, L".") == 0) || (wcscmp(dir_name, L"..") == 0)) {
|
||||
return true;
|
||||
}
|
||||
return path->AddW(dir_name) && DeleteRecursively(path);
|
||||
}
|
||||
|
||||
static bool DeleteEntry(LPWIN32_FIND_DATAW find_file_data, PathBuffer* path) {
|
||||
DWORD attributes = find_file_data->dwFileAttributes;
|
||||
|
||||
if ((attributes & FILE_ATTRIBUTE_DIRECTORY) != 0) {
|
||||
return DeleteDir(find_file_data->cFileName, path);
|
||||
} else {
|
||||
return DeleteFile(find_file_data->cFileName, path);
|
||||
}
|
||||
}
|
||||
|
||||
static bool DeleteRecursively(PathBuffer* path) {
|
||||
PathBuffer prefixed_path;
|
||||
if (!prefixed_path.Add(PrefixLongDirectoryPath(path->AsScopedString()))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
DWORD attributes = GetFileAttributesW(prefixed_path.AsStringW());
|
||||
if (attributes == INVALID_FILE_ATTRIBUTES) {
|
||||
return false;
|
||||
}
|
||||
// If the directory is a junction, it's pointing to some other place in the
|
||||
// filesystem that we do not want to recurse into.
|
||||
if ((attributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0) {
|
||||
// Just delete the junction itself.
|
||||
return RemoveDirectoryW(prefixed_path.AsStringW()) != 0;
|
||||
}
|
||||
// If it's a file, remove it directly.
|
||||
if ((attributes & FILE_ATTRIBUTE_DIRECTORY) == 0) {
|
||||
return DeleteFile(L"", &prefixed_path);
|
||||
}
|
||||
PathBuffer path_;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
if (!prefixed_path.AddW(L"\\*")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
WIN32_FIND_DATAW find_file_data;
|
||||
HANDLE find_handle =
|
||||
FindFirstFileW(prefixed_path.AsStringW(), &find_file_data);
|
||||
|
||||
if (find_handle == INVALID_HANDLE_VALUE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Adjust the path by removing the '*' used for the search.
|
||||
int path_length = prefixed_path.length() - 1;
|
||||
prefixed_path.Reset(path_length);
|
||||
|
||||
do {
|
||||
if (!DeleteEntry(&find_file_data, &prefixed_path)) {
|
||||
break;
|
||||
}
|
||||
prefixed_path.Reset(path_length); // DeleteEntry adds to the path.
|
||||
} while (FindNextFileW(find_handle, &find_file_data) != 0);
|
||||
|
||||
DWORD last_error = GetLastError();
|
||||
// Always close handle.
|
||||
FindClose(find_handle);
|
||||
if (last_error != ERROR_NO_MORE_FILES) {
|
||||
// Unexpected error, set and return.
|
||||
SetLastError(last_error);
|
||||
return false;
|
||||
}
|
||||
// All content deleted successfully, try to delete directory.
|
||||
prefixed_path.Reset(path_length -
|
||||
1); // Drop the "\" from the end of the path.
|
||||
return RemoveDirectoryW(prefixed_path.AsStringW()) != 0;
|
||||
}
|
||||
|
||||
static Directory::ExistsResult ExistsHelper(const wchar_t* dir_name) {
|
||||
Directory::ExistsResult Directory::Exists(const wchar_t* dir_name) {
|
||||
DWORD attributes = GetFileAttributesW(dir_name);
|
||||
if (attributes == INVALID_FILE_ATTRIBUTES) {
|
||||
DWORD last_error = GetLastError();
|
||||
|
@ -356,9 +379,8 @@ static Directory::ExistsResult ExistsHelper(const wchar_t* dir_name) {
|
|||
|
||||
Directory::ExistsResult Directory::Exists(Namespace* namespc,
|
||||
const char* dir_name) {
|
||||
const char* prefixed_dir_name = PrefixLongDirectoryPath(dir_name);
|
||||
Utf8ToWideScope system_name(prefixed_dir_name);
|
||||
return ExistsHelper(system_name.wide());
|
||||
const auto path = ToWinAPIDirectoryPath(dir_name);
|
||||
return Exists(path.get());
|
||||
}
|
||||
|
||||
char* Directory::CurrentNoScope() {
|
||||
|
@ -378,12 +400,11 @@ char* Directory::CurrentNoScope() {
|
|||
}
|
||||
|
||||
bool Directory::Create(Namespace* namespc, const char* dir_name) {
|
||||
const char* prefixed_dir_name = PrefixLongDirectoryPath(dir_name);
|
||||
Utf8ToWideScope system_name(prefixed_dir_name);
|
||||
int create_status = CreateDirectoryW(system_name.wide(), nullptr);
|
||||
const auto path = ToWinAPIDirectoryPath(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) &&
|
||||
(ExistsHelper(system_name.wide()) == EXISTS)) {
|
||||
(Exists(path.get()) == EXISTS)) {
|
||||
return true;
|
||||
}
|
||||
return (create_status != 0);
|
||||
|
@ -399,8 +420,8 @@ const char* Directory::SystemTemp(Namespace* namespc) {
|
|||
// Creates a new temporary directory with a UUID as suffix.
|
||||
static const char* CreateTempFromUUID(const char* prefix) {
|
||||
PathBuffer path;
|
||||
Utf8ToWideScope system_prefix(prefix);
|
||||
if (!path.AddW(system_prefix.wide())) {
|
||||
const auto system_prefix = Utf8ToWideChar(prefix);
|
||||
if (!path.AddW(system_prefix.get())) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
@ -414,14 +435,14 @@ static const char* CreateTempFromUUID(const char* prefix) {
|
|||
if ((status != RPC_S_OK) && (status != RPC_S_UUID_LOCAL_ONLY)) {
|
||||
return nullptr;
|
||||
}
|
||||
RPC_WSTR uuid_string;
|
||||
wchar_t* uuid_string;
|
||||
status = UuidToStringW(&uuid, &uuid_string);
|
||||
if (status != RPC_S_OK) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// RPC_WSTR is an unsigned short*, so we cast to wchar_t*.
|
||||
if (!path.AddW(reinterpret_cast<wchar_t*>(uuid_string))) {
|
||||
if (!path.AddW(uuid_string)) {
|
||||
return nullptr;
|
||||
}
|
||||
RpcStringFreeW(&uuid_string);
|
||||
|
@ -445,8 +466,8 @@ static const char* CreateTempFromUUID(const char* prefix) {
|
|||
// to have a small bound on the number of calls to CreateDirectoryW().
|
||||
const char* Directory::CreateTemp(Namespace* namespc, const char* prefix) {
|
||||
PathBuffer path;
|
||||
Utf8ToWideScope system_prefix(prefix);
|
||||
if (!path.AddW(system_prefix.wide())) {
|
||||
const auto system_prefix = Utf8ToWideChar(prefix);
|
||||
if (!path.AddW(system_prefix.get())) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
@ -485,39 +506,35 @@ const char* Directory::CreateTemp(Namespace* namespc, const char* prefix) {
|
|||
bool Directory::Delete(Namespace* namespc,
|
||||
const char* dir_name,
|
||||
bool recursive) {
|
||||
const char* prefixed_dir_name = PrefixLongDirectoryPath(dir_name);
|
||||
const auto path =
|
||||
ToWinAPIDirectoryPath(dir_name, /*force_long_prefix=*/recursive);
|
||||
bool result = false;
|
||||
Utf8ToWideScope system_dir_name(prefixed_dir_name);
|
||||
if (!recursive) {
|
||||
if (File::GetType(namespc, prefixed_dir_name, true) == File::kIsDirectory) {
|
||||
result = (RemoveDirectoryW(system_dir_name.wide()) != 0);
|
||||
if (File::GetType(path.get(), /*follow_links=*/true) ==
|
||||
File::kIsDirectory) {
|
||||
result = (RemoveDirectoryW(path.get()) != 0);
|
||||
} else {
|
||||
SetLastError(ERROR_FILE_NOT_FOUND);
|
||||
}
|
||||
} else {
|
||||
PathBuffer path;
|
||||
if (path.AddW(system_dir_name.wide())) {
|
||||
result = DeleteRecursively(&path);
|
||||
}
|
||||
RecursiveDeleter deleter;
|
||||
result = deleter.DeleteRecursively(path);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool Directory::Rename(Namespace* namespc,
|
||||
const char* path,
|
||||
const char* new_path) {
|
||||
const char* prefixed_dir = PrefixLongDirectoryPath(path);
|
||||
Utf8ToWideScope system_path(prefixed_dir);
|
||||
ExistsResult exists = ExistsHelper(system_path.wide());
|
||||
const char* old_name,
|
||||
const char* new_name) {
|
||||
const auto old_path = ToWinAPIDirectoryPath(old_name);
|
||||
ExistsResult exists = Exists(old_path.get());
|
||||
if (exists != EXISTS) {
|
||||
SetLastError(ERROR_FILE_NOT_FOUND);
|
||||
return false;
|
||||
}
|
||||
const char* prefixed_new_dir = PrefixLongDirectoryPath(new_path);
|
||||
Utf8ToWideScope system_new_path(prefixed_new_dir);
|
||||
const auto new_path = ToWinAPIDirectoryPath(new_name);
|
||||
DWORD flags = MOVEFILE_WRITE_THROUGH;
|
||||
int move_status =
|
||||
MoveFileExW(system_path.wide(), system_new_path.wide(), flags);
|
||||
int move_status = MoveFileExW(old_path.get(), new_path.get(), flags);
|
||||
return (move_status != 0);
|
||||
}
|
||||
|
||||
|
|
|
@ -269,6 +269,9 @@ class File : public ReferenceCounted<File> {
|
|||
static bool IsAbsolutePath(const char* path);
|
||||
static const char* PathSeparator();
|
||||
static const char* StringEscapedPathSeparator();
|
||||
#if defined(DART_HOST_OS_WINDOWS)
|
||||
static Type GetType(const wchar_t* path, bool follow_links);
|
||||
#endif
|
||||
static Type GetType(Namespace* namespc, const char* path, bool follow_links);
|
||||
static Identical AreIdentical(Namespace* namespc_1,
|
||||
const char* file_1,
|
||||
|
|
|
@ -36,9 +36,9 @@ intptr_t FileSystemWatcher::WatchPath(intptr_t id,
|
|||
int events,
|
||||
bool recursive) {
|
||||
USE(id);
|
||||
Utf8ToWideScope name(path);
|
||||
const auto name = Utf8ToWideChar(path);
|
||||
HANDLE dir =
|
||||
CreateFileW(name.wide(), FILE_LIST_DIRECTORY,
|
||||
CreateFileW(name.get(), FILE_LIST_DIRECTORY,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
|
||||
nullptr, OPEN_EXISTING,
|
||||
FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, nullptr);
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include <Shlwapi.h> // NOLINT
|
||||
#include <fcntl.h> // NOLINT
|
||||
|
@ -322,153 +322,126 @@ File* File::OpenFD(int fd) {
|
|||
return new File(new FileHandle(fd));
|
||||
}
|
||||
|
||||
class StringRAII {
|
||||
public:
|
||||
StringRAII(StringRAII& origin) {
|
||||
own_ = origin.own_;
|
||||
s_ = origin.release();
|
||||
}
|
||||
|
||||
explicit StringRAII(const char* s) : own_(false), s_(s) {}
|
||||
explicit StringRAII(char* s) : own_(true), s_(s) {}
|
||||
~StringRAII() {
|
||||
if (own_) {
|
||||
free(const_cast<char*>(s_));
|
||||
static std::unique_ptr<wchar_t[]> ConvertToAbsolutePath(
|
||||
const std::unique_ptr<wchar_t[]>& path) {
|
||||
// Initial buffer size is selected to avoid overallocating too much
|
||||
// memory.
|
||||
int buffer_size = 1024;
|
||||
do {
|
||||
auto buffer = std::make_unique<wchar_t[]>(buffer_size);
|
||||
int full_path_length =
|
||||
GetFullPathNameW(path.get(), buffer_size, buffer.get(),
|
||||
/*lpFilePart=*/nullptr);
|
||||
if (full_path_length == 0) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
const char* str() const { return s_; }
|
||||
const char* release() {
|
||||
own_ = false;
|
||||
return s_;
|
||||
}
|
||||
|
||||
private:
|
||||
bool own_;
|
||||
const char* s_;
|
||||
};
|
||||
// Note: when sucessful full_path_length does *not* include terminating
|
||||
// NUL character, but on failure it *does* include it when returning
|
||||
// the size of buffer which we need. Hence comparison here is `<`, rather
|
||||
// than `<=`.
|
||||
if (full_path_length < buffer_size) {
|
||||
return buffer;
|
||||
}
|
||||
|
||||
class Wchart {
|
||||
public:
|
||||
explicit Wchart(int size) {
|
||||
buf_ = reinterpret_cast<wchar_t*>(malloc(size * sizeof(wchar_t)));
|
||||
}
|
||||
~Wchart() { free(buf_); }
|
||||
wchar_t* buf() const { return buf_; }
|
||||
|
||||
private:
|
||||
wchar_t* buf_;
|
||||
};
|
||||
|
||||
static StringRAII ConvertToAbsolutePath(const char* path,
|
||||
bool* p_has_converted_successfully) {
|
||||
const int kPathLength = 16384;
|
||||
Wchart buffer(kPathLength); // use some reasonably large initial buffer
|
||||
Utf8ToWideScope path_utf8_to_wide(path);
|
||||
*p_has_converted_successfully = true;
|
||||
int full_path_length =
|
||||
GetFullPathNameW(path_utf8_to_wide.wide(), kPathLength, buffer.buf(),
|
||||
/*lpFilePart=*/nullptr);
|
||||
if (full_path_length == 0) {
|
||||
*p_has_converted_successfully = false;
|
||||
// GetFullPathNameW failed
|
||||
return StringRAII(path);
|
||||
}
|
||||
if (full_path_length < kPathLength) {
|
||||
WideToUtf8Scope scope(buffer.buf());
|
||||
return StringRAII(Utils::StrDup(scope.utf8()));
|
||||
}
|
||||
|
||||
// Try again with bigger buffer.
|
||||
Wchart bigger_buffer(full_path_length);
|
||||
if (GetFullPathNameW(path_utf8_to_wide.wide(), full_path_length,
|
||||
bigger_buffer.buf(),
|
||||
/*lpFilePart=*/nullptr) == 0) {
|
||||
*p_has_converted_successfully = false;
|
||||
// GetFullPathNameW failed
|
||||
return StringRAII(path);
|
||||
}
|
||||
WideToUtf8Scope scope(bigger_buffer.buf());
|
||||
return StringRAII(Utils::StrDup(scope.utf8()));
|
||||
buffer_size = full_path_length;
|
||||
} while (true);
|
||||
}
|
||||
|
||||
static StringRAII PrefixLongPathIfExceedLimit(
|
||||
const char* path,
|
||||
bool is_file,
|
||||
std::function<char*(int)> allocate) {
|
||||
static bool IsAbsolutePath(const wchar_t* pathname) {
|
||||
if (pathname == nullptr) return false;
|
||||
char first = pathname[0];
|
||||
char second = pathname[1];
|
||||
if (first == L'\\' && second == L'\\') return true;
|
||||
if (second != L':') return false;
|
||||
first |= 0x20;
|
||||
char third = pathname[2];
|
||||
return (first >= L'a') && (first <= L'z') &&
|
||||
(third == L'\\' || third == L'/');
|
||||
}
|
||||
|
||||
// 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.
|
||||
//
|
||||
// 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) {
|
||||
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;
|
||||
|
||||
const char* kLongPathPrefix = "\\\\?\\";
|
||||
const wchar_t* kLongPathPrefix = L"\\\\?\\";
|
||||
const int kLongPathPrefixLength = 4;
|
||||
|
||||
// if absolute path is short or already prefixed, just return it.
|
||||
if ((File::IsAbsolutePath(path) && strlen(path) < path_short_limit) ||
|
||||
strncmp(path, kLongPathPrefix, kLongPathPrefixLength) == 0) {
|
||||
return StringRAII(path);
|
||||
std::unique_ptr<wchar_t[]> absolute_path;
|
||||
if (!IsAbsolutePath(path.get())) {
|
||||
absolute_path = ConvertToAbsolutePath(path);
|
||||
if (absolute_path == nullptr) {
|
||||
return path;
|
||||
}
|
||||
} else {
|
||||
absolute_path = std::move(path);
|
||||
}
|
||||
|
||||
// Long relative path have to be converted to absolute path before prefixing.
|
||||
bool is_ok = true;
|
||||
StringRAII absolute_path_raii(
|
||||
File::IsAbsolutePath(path)
|
||||
? StringRAII(path)
|
||||
: StringRAII(ConvertToAbsolutePath(path, &is_ok)));
|
||||
if (!is_ok) {
|
||||
return StringRAII(path);
|
||||
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;
|
||||
}
|
||||
}
|
||||
const char* absolute_path = absolute_path_raii.str();
|
||||
int length = strlen(absolute_path);
|
||||
if (length < path_short_limit) {
|
||||
// No need for a prefix if absolute path is short
|
||||
return StringRAII(path);
|
||||
}
|
||||
if (strncmp(absolute_path, kLongPathPrefix, kLongPathPrefixLength) == 0) {
|
||||
// Relative path converted to absolute could get a prefix.
|
||||
return StringRAII(absolute_path);
|
||||
|
||||
if (wcsncmp(absolute_path.get(), kLongPathPrefix, kLongPathPrefixLength) ==
|
||||
0) {
|
||||
return absolute_path;
|
||||
}
|
||||
|
||||
// Add prefix and replace forward slashes with backward slashes.
|
||||
char* result = allocate((kLongPathPrefixLength + length + 1) * sizeof(char));
|
||||
strncpy(result, kLongPathPrefix, kLongPathPrefixLength);
|
||||
for (int i = 0; i < length; i++) {
|
||||
result[kLongPathPrefixLength + i] =
|
||||
absolute_path[i] == '/' ? '\\' : absolute_path[i];
|
||||
auto result =
|
||||
std::make_unique<wchar_t[]>(kLongPathPrefixLength + path_length + 1);
|
||||
wcsncpy(result.get(), kLongPathPrefix, kLongPathPrefixLength);
|
||||
for (int i = 0; i < path_length; i++) {
|
||||
result.get()[kLongPathPrefixLength + i] =
|
||||
absolute_path[i] == L'/' ? L'\\' : absolute_path[i];
|
||||
}
|
||||
result[length + kLongPathPrefixLength] = '\0';
|
||||
return StringRAII(result);
|
||||
result.get()[path_length + kLongPathPrefixLength] = L'\0';
|
||||
return result;
|
||||
}
|
||||
|
||||
static const char* PrefixLongFilePath(const char* path) {
|
||||
return PrefixLongPathIfExceedLimit(
|
||||
path, /*is_file=*/true,
|
||||
[](int size) {
|
||||
return reinterpret_cast<char*>(Dart_ScopeAllocate(size));
|
||||
})
|
||||
.release();
|
||||
// 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);
|
||||
}
|
||||
|
||||
static StringRAII PrefixLongFilePathNoScope(const char* path) {
|
||||
return PrefixLongPathIfExceedLimit(path, /*is_file=*/true, [](int size) {
|
||||
return reinterpret_cast<char*>(malloc(size));
|
||||
});
|
||||
std::unique_ptr<wchar_t[]> ToWinAPIDirectoryPath(
|
||||
const char* path,
|
||||
bool force_long_prefix /* = false */) {
|
||||
return ToWinAPIPath(path, /*is_file=*/false, force_long_prefix);
|
||||
}
|
||||
|
||||
const char* PrefixLongDirectoryPath(const char* path) {
|
||||
return PrefixLongPathIfExceedLimit(
|
||||
path, /*is_file=*/false,
|
||||
[](int size) {
|
||||
return reinterpret_cast<char*>(Dart_ScopeAllocate(size));
|
||||
})
|
||||
.release();
|
||||
}
|
||||
|
||||
File* File::Open(Namespace* namespc, const char* path, FileOpenMode mode) {
|
||||
// File::Open can be called without scope(when launching isolate),
|
||||
// so it mallocs prefixed path
|
||||
StringRAII string_raii = PrefixLongFilePathNoScope(path);
|
||||
Utf8ToWideScope system_name(string_raii.str());
|
||||
File* file = FileOpenW(system_name.wide(), mode);
|
||||
File* File::Open(Namespace* namespc, const char* name, FileOpenMode mode) {
|
||||
const auto path = ToWinAPIFilePath(name);
|
||||
File* file = FileOpenW(path.get(), mode);
|
||||
return file;
|
||||
}
|
||||
|
||||
|
@ -479,13 +452,13 @@ Utils::CStringUniquePtr File::UriToPath(const char* uri) {
|
|||
return Utils::CreateCStringUniquePtr(nullptr);
|
||||
}
|
||||
|
||||
Utf8ToWideScope uri_w(uri_decoder.decoded());
|
||||
if (!UrlIsFileUrlW(uri_w.wide())) {
|
||||
const auto uri_w = Utf8ToWideChar(uri_decoder.decoded());
|
||||
if (!UrlIsFileUrlW(uri_w.get())) {
|
||||
return Utils::CreateCStringUniquePtr(Utils::StrDup(uri_decoder.decoded()));
|
||||
}
|
||||
wchar_t filename_w[MAX_PATH];
|
||||
DWORD filename_len = MAX_PATH;
|
||||
HRESULT result = PathCreateFromUrlW(uri_w.wide(), filename_w, &filename_len,
|
||||
HRESULT result = PathCreateFromUrlW(uri_w.get(), filename_w, &filename_len,
|
||||
/* dwFlags= */ 0);
|
||||
if (result != S_OK) {
|
||||
return Utils::CreateCStringUniquePtr(nullptr);
|
||||
|
@ -519,7 +492,7 @@ File* File::OpenStdio(int fd) {
|
|||
return new File(new FileHandle(stdio_fd));
|
||||
}
|
||||
|
||||
static bool StatHelper(wchar_t* path, struct __stat64* st) {
|
||||
static bool StatHelper(const wchar_t* path, struct __stat64* st) {
|
||||
int stat_status = _wstat64(path, st);
|
||||
if (stat_status != 0) {
|
||||
return false;
|
||||
|
@ -531,11 +504,14 @@ static bool StatHelper(wchar_t* path, struct __stat64* st) {
|
|||
return true;
|
||||
}
|
||||
|
||||
bool File::Exists(Namespace* namespc, const char* name) {
|
||||
StringRAII string_raii = PrefixLongFilePathNoScope(name);
|
||||
Utf8ToWideScope system_name(string_raii.str());
|
||||
static bool FileExists(const wchar_t* path) {
|
||||
struct __stat64 st;
|
||||
return StatHelper(system_name.wide(), &st);
|
||||
return StatHelper(path, &st);
|
||||
}
|
||||
|
||||
bool File::Exists(Namespace* namespc, const char* name) {
|
||||
const auto path = ToWinAPIFilePath(name);
|
||||
return FileExists(path.get());
|
||||
}
|
||||
|
||||
bool File::ExistsUri(Namespace* namespc, const char* uri) {
|
||||
|
@ -548,12 +524,12 @@ bool File::ExistsUri(Namespace* namespc, const char* uri) {
|
|||
}
|
||||
|
||||
bool File::Create(Namespace* namespc, const char* name, bool exclusive) {
|
||||
Utf8ToWideScope system_name(PrefixLongFilePath(name));
|
||||
const auto path = ToWinAPIFilePath(name);
|
||||
int flags = O_RDONLY | O_CREAT;
|
||||
if (exclusive) {
|
||||
flags |= O_EXCL;
|
||||
}
|
||||
int fd = _wopen(system_name.wide(), flags, 0666);
|
||||
int fd = _wopen(path.get(), flags, 0666);
|
||||
if (fd < 0) {
|
||||
return false;
|
||||
}
|
||||
|
@ -593,13 +569,14 @@ typedef struct _REPARSE_DATA_BUFFER {
|
|||
bool File::CreateLink(Namespace* namespc,
|
||||
const char* utf8_name,
|
||||
const char* utf8_target) {
|
||||
Utf8ToWideScope name(PrefixLongFilePath(utf8_name));
|
||||
Utf8ToWideScope target(PrefixLongFilePath(utf8_target));
|
||||
DWORD flags = SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE;
|
||||
const auto name = ToWinAPIFilePath(utf8_name);
|
||||
|
||||
File::Type type;
|
||||
std::unique_ptr<wchar_t[]> target;
|
||||
bool target_is_directory;
|
||||
if (File::IsAbsolutePath(utf8_target)) {
|
||||
type = File::GetType(namespc, utf8_target, true);
|
||||
target = ToWinAPIFilePath(utf8_target);
|
||||
target_is_directory =
|
||||
File::GetType(target.get(), /*follow_links=*/true) == kIsDirectory;
|
||||
} else {
|
||||
// The path of `target` is relative to `name`.
|
||||
//
|
||||
|
@ -618,15 +595,17 @@ bool File::CreateLink(Namespace* namespc,
|
|||
// 1. target_path := name ..\..\Link
|
||||
// 2. target_path := remove_file(target_path) ..\..\
|
||||
// 3. target_path := combine(target_path, target) ..\..\..\Dir\MyFile
|
||||
target = Utf8ToWideChar(utf8_target);
|
||||
|
||||
// 1. target_path := name
|
||||
intptr_t target_path_max_length = name.length() + target.length();
|
||||
Wchart target_path(target_path_max_length);
|
||||
wcscpy_s(target_path.buf(), target_path_max_length, name.wide());
|
||||
intptr_t target_path_max_length =
|
||||
wcslen(name.get()) + wcslen(target.get()) + 2;
|
||||
auto target_path = std::make_unique<wchar_t[]>(target_path_max_length);
|
||||
wcscpy_s(target_path.get(), target_path_max_length, name.get());
|
||||
|
||||
// 2. target_path := remove_file(target_path)
|
||||
HRESULT remove_result =
|
||||
PathCchRemoveFileSpec(target_path.buf(), target_path_max_length);
|
||||
PathCchRemoveFileSpec(target_path.get(), target_path_max_length);
|
||||
if (remove_result == S_FALSE) {
|
||||
// If the file component could not be removed, then `name` is
|
||||
// top-level, like "C:\" or "/". Attempts to create files at those paths
|
||||
|
@ -640,27 +619,29 @@ bool File::CreateLink(Namespace* namespc,
|
|||
|
||||
// 3. target_path := combine(target_path, target)
|
||||
HRESULT combine_result = PathCchCombineEx(
|
||||
target_path.buf(), target_path_max_length, target_path.buf(),
|
||||
target.wide(), PATHCCH_ALLOW_LONG_PATHS);
|
||||
target_path.get(), target_path_max_length, target_path.get(),
|
||||
target.get(), PATHCCH_ALLOW_LONG_PATHS);
|
||||
if (combine_result != S_OK) {
|
||||
SetLastError(combine_result);
|
||||
return false;
|
||||
}
|
||||
|
||||
type = PathIsDirectoryW(target_path.buf()) ? kIsDirectory : kIsFile;
|
||||
target_is_directory =
|
||||
File::GetType(target_path.get(), /*follow_links=*/true) == kIsDirectory;
|
||||
}
|
||||
|
||||
if (type == kIsDirectory) {
|
||||
DWORD flags = SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE;
|
||||
if (target_is_directory) {
|
||||
flags |= SYMBOLIC_LINK_FLAG_DIRECTORY;
|
||||
}
|
||||
int create_status = CreateSymbolicLinkW(name.wide(), target.wide(), flags);
|
||||
int create_status = CreateSymbolicLinkW(name.get(), target.get(), flags);
|
||||
|
||||
// If running on a Windows 10 build older than 14972, an invalid parameter
|
||||
// error will be returned when trying to use the
|
||||
// SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE flag. Retry without the flag.
|
||||
if ((create_status == 0) && (GetLastError() == ERROR_INVALID_PARAMETER)) {
|
||||
flags &= ~SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE;
|
||||
create_status = CreateSymbolicLinkW(name.wide(), target.wide(), flags);
|
||||
create_status = CreateSymbolicLinkW(name.get(), target.get(), flags);
|
||||
}
|
||||
|
||||
return (create_status != 0);
|
||||
|
@ -678,15 +659,14 @@ bool File::CreatePipe(Namespace* namespc, File** readPipe, File** writePipe) {
|
|||
}
|
||||
|
||||
bool File::Delete(Namespace* namespc, const char* name) {
|
||||
Utf8ToWideScope system_name(PrefixLongFilePath(name));
|
||||
int status = _wremove(system_name.wide());
|
||||
const auto path = ToWinAPIFilePath(name);
|
||||
int status = _wremove(path.get());
|
||||
return status != -1;
|
||||
}
|
||||
|
||||
bool File::DeleteLink(Namespace* namespc, const char* name) {
|
||||
Utf8ToWideScope system_name(PrefixLongFilePath(name));
|
||||
static bool DeleteLinkHelper(const wchar_t* path) {
|
||||
bool result = false;
|
||||
DWORD attributes = GetFileAttributesW(system_name.wide());
|
||||
DWORD attributes = GetFileAttributesW(path);
|
||||
if ((attributes == INVALID_FILE_ATTRIBUTES) ||
|
||||
((attributes & FILE_ATTRIBUTE_REPARSE_POINT) == 0)) {
|
||||
SetLastError(ERROR_NOT_A_REPARSE_POINT);
|
||||
|
@ -695,207 +675,176 @@ bool File::DeleteLink(Namespace* namespc, const char* name) {
|
|||
if ((attributes & FILE_ATTRIBUTE_DIRECTORY) != 0) {
|
||||
// It's a junction, which is a special type of directory, or a symbolic
|
||||
// link to a directory. Remove the directory.
|
||||
result = (RemoveDirectoryW(system_name.wide()) != 0);
|
||||
result = (RemoveDirectoryW(path) != 0);
|
||||
} else {
|
||||
// Symbolic link to a file. Remove the file.
|
||||
result = (DeleteFileW(system_name.wide()) != 0);
|
||||
result = (DeleteFileW(path) != 0);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool File::Rename(Namespace* namespc,
|
||||
const char* old_path,
|
||||
const char* new_path) {
|
||||
const char* prefixed_old_path = PrefixLongFilePath(old_path);
|
||||
File::Type type = GetType(namespc, prefixed_old_path, false);
|
||||
if (type != kIsFile) {
|
||||
bool File::DeleteLink(Namespace* namespc, const char* name) {
|
||||
const auto path = ToWinAPIFilePath(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);
|
||||
File::Type type = File::GetType(old_path.get(), /*follow_links=*/false);
|
||||
if (type != expected) {
|
||||
SetLastError(ERROR_FILE_NOT_FOUND);
|
||||
return false;
|
||||
}
|
||||
const char* prefixed_new_path = PrefixLongFilePath(new_path);
|
||||
Utf8ToWideScope system_old_path(prefixed_old_path);
|
||||
Utf8ToWideScope system_new_path(prefixed_new_path);
|
||||
const auto new_path = ToWinAPIFilePath(new_name);
|
||||
DWORD flags = MOVEFILE_WRITE_THROUGH | MOVEFILE_REPLACE_EXISTING;
|
||||
|
||||
// Symbolic links (e.g. produced by Link.create) to directories on Windows
|
||||
// appear as special directories. MoveFileExW's MOVEFILE_REPLACE_EXISTING
|
||||
// does not allow for replacement of directories, so we need to remove it
|
||||
// before renaming.
|
||||
if ((Directory::Exists(namespc, prefixed_new_path) == Directory::EXISTS) &&
|
||||
(GetType(namespc, prefixed_new_path, false) == kIsLink)) {
|
||||
if ((Directory::Exists(new_path.get()) == Directory::EXISTS) &&
|
||||
(File::GetType(new_path.get(), /*follow_links=*/false) ==
|
||||
File::kIsLink)) {
|
||||
// Bail out if the DeleteLink call fails.
|
||||
if (!DeleteLink(namespc, prefixed_new_path)) {
|
||||
if (!DeleteLinkHelper(new_path.get())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
int move_status =
|
||||
MoveFileExW(system_old_path.wide(), system_new_path.wide(), flags);
|
||||
int move_status = MoveFileExW(old_path.get(), new_path.get(), flags);
|
||||
return (move_status != 0);
|
||||
}
|
||||
|
||||
bool File::Rename(Namespace* namespc,
|
||||
const char* old_name,
|
||||
const char* new_name) {
|
||||
return RenameHelper(File::kIsFile, old_name, new_name);
|
||||
}
|
||||
|
||||
bool File::RenameLink(Namespace* namespc,
|
||||
const char* old_path,
|
||||
const char* new_path) {
|
||||
const char* prefixed_old_path = PrefixLongFilePath(old_path);
|
||||
File::Type type = GetType(namespc, prefixed_old_path, false);
|
||||
if (type != kIsLink) {
|
||||
SetLastError(ERROR_FILE_NOT_FOUND);
|
||||
return false;
|
||||
}
|
||||
Utf8ToWideScope system_old_path(prefixed_old_path);
|
||||
const char* prefixed_new_path = PrefixLongFilePath(new_path);
|
||||
Utf8ToWideScope system_new_path(prefixed_new_path);
|
||||
DWORD flags = MOVEFILE_WRITE_THROUGH | MOVEFILE_REPLACE_EXISTING;
|
||||
|
||||
// Symbolic links (e.g. produced by Link.create) to directories on Windows
|
||||
// appear as special directories. MoveFileExW's MOVEFILE_REPLACE_EXISTING
|
||||
// does not allow for replacement of directories, so we need to remove it
|
||||
// before renaming.
|
||||
if ((Directory::Exists(namespc, prefixed_new_path) == Directory::EXISTS) &&
|
||||
(GetType(namespc, prefixed_new_path, false) == kIsLink)) {
|
||||
// Bail out if the DeleteLink call fails.
|
||||
if (!DeleteLink(namespc, prefixed_new_path)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
int move_status =
|
||||
MoveFileExW(system_old_path.wide(), system_new_path.wide(), flags);
|
||||
return (move_status != 0);
|
||||
const char* old_name,
|
||||
const char* new_name) {
|
||||
return RenameHelper(File::kIsLink, old_name, new_name);
|
||||
}
|
||||
|
||||
static wchar_t* CopyToDartScopeString(wchar_t* string) {
|
||||
wchar_t* wide_path = reinterpret_cast<wchar_t*>(
|
||||
Dart_ScopeAllocate(MAX_PATH * sizeof(wchar_t) + 1));
|
||||
wcscpy(wide_path, string);
|
||||
return wide_path;
|
||||
}
|
||||
|
||||
static wchar_t* CopyIntoTempFile(const char* src, const char* dest) {
|
||||
// This function will copy the file to a temp file in the destination
|
||||
// directory and return the path of temp file.
|
||||
// Creating temp file name has the same logic as Directory::CreateTemp(),
|
||||
// which tries with the rng and falls back to a uuid if it failed.
|
||||
const char* last_back_slash = strrchr(dest, '\\');
|
||||
// It is possible the path uses forwardslash as path separator.
|
||||
const char* last_forward_slash = strrchr(dest, '/');
|
||||
const char* last_path_separator = nullptr;
|
||||
if (last_back_slash == nullptr && last_forward_slash == nullptr) {
|
||||
return nullptr;
|
||||
} else if (last_forward_slash != nullptr && last_forward_slash != nullptr) {
|
||||
// If both types occur in the path, use the one closer to the end.
|
||||
if (last_back_slash - dest > last_forward_slash - dest) {
|
||||
last_path_separator = last_back_slash;
|
||||
} else {
|
||||
last_path_separator = last_forward_slash;
|
||||
static std::unique_ptr<wchar_t[]> GetDirectoryPath(
|
||||
const std::unique_ptr<wchar_t[]>& path) {
|
||||
for (intptr_t i = wcslen(path.get()) - 1; i >= 0; --i) {
|
||||
if (path.get()[i] == '\\' || path.get()[i] == '/') {
|
||||
auto result = std::make_unique<wchar_t[]>(i + 1);
|
||||
wcsncpy(result.get(), path.get(), i);
|
||||
return result;
|
||||
}
|
||||
} else {
|
||||
last_path_separator =
|
||||
(last_forward_slash == nullptr) ? last_back_slash : last_forward_slash;
|
||||
}
|
||||
int length_of_parent_dir = last_path_separator - dest + 1;
|
||||
if (length_of_parent_dir + 8 > MAX_PATH) {
|
||||
return nullptr;
|
||||
}
|
||||
uint32_t suffix_bytes = 0;
|
||||
const int kSuffixSize = sizeof(suffix_bytes);
|
||||
if (Crypto::GetRandomBytes(kSuffixSize,
|
||||
reinterpret_cast<uint8_t*>(&suffix_bytes))) {
|
||||
PathBuffer buffer;
|
||||
char* dir = reinterpret_cast<char*>(
|
||||
Dart_ScopeAllocate(1 + sizeof(char) * length_of_parent_dir));
|
||||
memmove(dir, dest, length_of_parent_dir);
|
||||
dir[length_of_parent_dir] = '\0';
|
||||
if (!buffer.Add(dir)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
char suffix[8 + 1];
|
||||
Utils::SNPrint(suffix, sizeof(suffix), "%x", suffix_bytes);
|
||||
Utf8ToWideScope source_path(src);
|
||||
if (!buffer.Add(suffix)) {
|
||||
return nullptr;
|
||||
}
|
||||
if (CopyFileExW(source_path.wide(), buffer.AsStringW(), nullptr, nullptr,
|
||||
nullptr, 0) != 0) {
|
||||
return CopyToDartScopeString(buffer.AsStringW());
|
||||
}
|
||||
// If CopyFileExW() fails to copy to a temp file with random hex, fall
|
||||
// back to copy to a uuid temp file.
|
||||
}
|
||||
// UUID has a total of 36 characters in the form of
|
||||
// xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx.
|
||||
if (length_of_parent_dir + 36 > MAX_PATH) {
|
||||
return nullptr;
|
||||
}
|
||||
UUID uuid;
|
||||
RPC_STATUS status = UuidCreateSequential(&uuid);
|
||||
if ((status != RPC_S_OK) && (status != RPC_S_UUID_LOCAL_ONLY)) {
|
||||
return nullptr;
|
||||
}
|
||||
RPC_WSTR uuid_string;
|
||||
status = UuidToStringW(&uuid, &uuid_string);
|
||||
if (status != RPC_S_OK) {
|
||||
return nullptr;
|
||||
}
|
||||
PathBuffer buffer;
|
||||
char* dir = reinterpret_cast<char*>(
|
||||
Dart_ScopeAllocate(1 + sizeof(char) * length_of_parent_dir));
|
||||
memmove(dir, dest, length_of_parent_dir);
|
||||
dir[length_of_parent_dir] = '\0';
|
||||
Utf8ToWideScope dest_path(dir);
|
||||
if (!buffer.AddW(dest_path.wide()) ||
|
||||
!buffer.AddW(reinterpret_cast<wchar_t*>(uuid_string))) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
RpcStringFreeW(&uuid_string);
|
||||
Utf8ToWideScope source_path(src);
|
||||
if (CopyFileExW(source_path.wide(), buffer.AsStringW(), nullptr, nullptr,
|
||||
nullptr, 0) != 0) {
|
||||
return CopyToDartScopeString(buffer.AsStringW());
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static void FreeUUID(wchar_t* ptr) {
|
||||
RpcStringFreeW(&ptr);
|
||||
}
|
||||
|
||||
static std::unique_ptr<wchar_t, decltype(FreeUUID) *> GenerateUUIDString() {
|
||||
UUID uuid;
|
||||
RPC_STATUS status = UuidCreateSequential(&uuid);
|
||||
if ((status != RPC_S_OK) && (status != RPC_S_UUID_LOCAL_ONLY)) {
|
||||
return {nullptr, nullptr};
|
||||
}
|
||||
wchar_t* uuid_string;
|
||||
status = UuidToStringW(&uuid, &uuid_string);
|
||||
if (status != RPC_S_OK) {
|
||||
return {nullptr, nullptr};
|
||||
}
|
||||
|
||||
return {uuid_string, &FreeUUID};
|
||||
}
|
||||
|
||||
// This function will copy the |src| file to a temporary file in the
|
||||
// directory where |dest| resides and returns the path of temp file.
|
||||
static std::unique_ptr<wchar_t[]> CopyIntoTempFile(
|
||||
const std::unique_ptr<wchar_t[]>& src,
|
||||
const std::unique_ptr<wchar_t[]>& dest) {
|
||||
const auto dir = GetDirectoryPath(dest);
|
||||
if (dir == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
uint32_t suffix_bytes = 0;
|
||||
const int kSuffixSize = sizeof(suffix_bytes);
|
||||
if (Crypto::GetRandomBytes(kSuffixSize,
|
||||
reinterpret_cast<uint8_t*>(&suffix_bytes))) {
|
||||
const size_t file_path_buf_size = wcslen(dir.get()) + 8 + 1;
|
||||
auto file_path = std::make_unique<wchar_t[]>(file_path_buf_size);
|
||||
swprintf(file_path.get(), file_path_buf_size, L"%s%x", dir.get(),
|
||||
suffix_bytes);
|
||||
|
||||
if (CopyFileExW(src.get(), file_path.get(), nullptr, nullptr, nullptr, 0) !=
|
||||
0) {
|
||||
return file_path;
|
||||
}
|
||||
|
||||
// If CopyFileExW() fails to copy to a temp file with random hex, fall
|
||||
// back to copy to a uuid temp file.
|
||||
}
|
||||
|
||||
const auto uuid_str = GenerateUUIDString();
|
||||
if (uuid_str == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const size_t file_path_buf_size =
|
||||
wcslen(dir.get()) + wcslen(uuid_str.get()) + 1;
|
||||
auto file_path = std::make_unique<wchar_t[]>(file_path_buf_size);
|
||||
swprintf(file_path.get(), file_path_buf_size, L"%s%s", dir.get(),
|
||||
uuid_str.get());
|
||||
|
||||
if (CopyFileExW(src.get(), file_path.get(), nullptr, nullptr, nullptr, 0) !=
|
||||
0) {
|
||||
return file_path;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool File::Copy(Namespace* namespc,
|
||||
const char* old_path,
|
||||
const char* new_path) {
|
||||
const char* prefixed_old_path = PrefixLongFilePath(old_path);
|
||||
const char* prefixed_new_path = PrefixLongFilePath(new_path);
|
||||
File::Type type = GetType(namespc, prefixed_old_path, false);
|
||||
const char* old_name,
|
||||
const char* new_name) {
|
||||
// We are going to concatenate old path with temporary file names in
|
||||
// CopyIntoTempFile so we force long prefix no matter what.
|
||||
const auto old_path = ToWinAPIFilePath(old_name, /*force_long_prefix=*/true);
|
||||
const auto new_path = ToWinAPIFilePath(new_name);
|
||||
File::Type type = GetType(old_path.get(), /*follow_links=*/false);
|
||||
if (type != kIsFile) {
|
||||
SetLastError(ERROR_FILE_NOT_FOUND);
|
||||
return false;
|
||||
}
|
||||
|
||||
wchar_t* temp_file = CopyIntoTempFile(prefixed_old_path, prefixed_new_path);
|
||||
const auto temp_file = CopyIntoTempFile(old_path, new_path);
|
||||
if (temp_file == nullptr) {
|
||||
// If temp file creation fails, fall back on doing a direct copy.
|
||||
Utf8ToWideScope system_old_path(prefixed_old_path);
|
||||
Utf8ToWideScope system_new_path(prefixed_new_path);
|
||||
return CopyFileExW(system_old_path.wide(), system_new_path.wide(), nullptr,
|
||||
nullptr, nullptr, 0) != 0;
|
||||
return CopyFileExW(old_path.get(), new_path.get(), nullptr, nullptr,
|
||||
nullptr, 0) != 0;
|
||||
}
|
||||
Utf8ToWideScope system_new_dest(prefixed_new_path);
|
||||
|
||||
// Remove the existing file. Otherwise, renaming will fail.
|
||||
if (Exists(namespc, prefixed_new_path)) {
|
||||
DeleteFileW(system_new_dest.wide());
|
||||
if (FileExists(new_path.get())) {
|
||||
DeleteFileW(new_path.get());
|
||||
}
|
||||
|
||||
if (!MoveFileW(temp_file, system_new_dest.wide())) {
|
||||
if (!MoveFileW(temp_file.get(), new_path.get())) {
|
||||
DWORD error = GetLastError();
|
||||
DeleteFileW(temp_file);
|
||||
DeleteFileW(temp_file.get());
|
||||
SetLastError(error);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int64_t File::LengthFromPath(Namespace* namespc, const char* name) {
|
||||
struct __stat64 st;
|
||||
Utf8ToWideScope system_name(PrefixLongFilePath(name));
|
||||
if (!StatHelper(system_name.wide(), &st)) {
|
||||
const auto path = ToWinAPIFilePath(name);
|
||||
if (!StatHelper(path.get(), &st)) {
|
||||
return -1;
|
||||
}
|
||||
return st.st_size;
|
||||
|
@ -905,10 +854,9 @@ const char* File::LinkTarget(Namespace* namespc,
|
|||
const char* pathname,
|
||||
char* dest,
|
||||
int dest_size) {
|
||||
const wchar_t* name =
|
||||
StringUtilsWin::Utf8ToWide(PrefixLongFilePath(pathname));
|
||||
const auto path = ToWinAPIFilePath(pathname);
|
||||
HANDLE dir_handle = CreateFileW(
|
||||
name, GENERIC_READ,
|
||||
path.get(), GENERIC_READ,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr,
|
||||
OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT,
|
||||
nullptr);
|
||||
|
@ -993,13 +941,12 @@ const char* File::LinkTarget(Namespace* namespc,
|
|||
}
|
||||
|
||||
void File::Stat(Namespace* namespc, const char* name, int64_t* data) {
|
||||
const char* prefixed_name = PrefixLongFilePath(name);
|
||||
File::Type type = GetType(namespc, prefixed_name, true);
|
||||
const auto path = ToWinAPIFilePath(name);
|
||||
File::Type type = GetType(path.get(), /*follow_links=*/true);
|
||||
data[kType] = type;
|
||||
if (type != kDoesNotExist) {
|
||||
struct _stat64 st;
|
||||
Utf8ToWideScope system_name(prefixed_name);
|
||||
int stat_status = _wstat64(system_name.wide(), &st);
|
||||
int stat_status = _wstat64(path.get(), &st);
|
||||
if (stat_status == 0) {
|
||||
data[kCreatedTime] = st.st_ctime * 1000;
|
||||
data[kModifiedTime] = st.st_mtime * 1000;
|
||||
|
@ -1014,8 +961,8 @@ void File::Stat(Namespace* namespc, const char* name, int64_t* data) {
|
|||
|
||||
time_t File::LastAccessed(Namespace* namespc, const char* name) {
|
||||
struct __stat64 st;
|
||||
Utf8ToWideScope system_name(PrefixLongFilePath(name));
|
||||
if (!StatHelper(system_name.wide(), &st)) {
|
||||
const auto path = ToWinAPIFilePath(name);
|
||||
if (!StatHelper(path.get(), &st)) {
|
||||
return -1;
|
||||
}
|
||||
return st.st_atime;
|
||||
|
@ -1023,8 +970,8 @@ time_t File::LastAccessed(Namespace* namespc, const char* name) {
|
|||
|
||||
time_t File::LastModified(Namespace* namespc, const char* name) {
|
||||
struct __stat64 st;
|
||||
Utf8ToWideScope system_name(PrefixLongFilePath(name));
|
||||
if (!StatHelper(system_name.wide(), &st)) {
|
||||
const auto path = ToWinAPIFilePath(name);
|
||||
if (!StatHelper(path.get(), &st)) {
|
||||
return -1;
|
||||
}
|
||||
return st.st_mtime;
|
||||
|
@ -1034,8 +981,8 @@ bool File::SetLastAccessed(Namespace* namespc,
|
|||
const char* name,
|
||||
int64_t millis) {
|
||||
struct __stat64 st;
|
||||
Utf8ToWideScope system_name(PrefixLongFilePath(name));
|
||||
if (!StatHelper(system_name.wide(), &st)) { // Checks that it is a file.
|
||||
const auto path = ToWinAPIFilePath(name);
|
||||
if (!StatHelper(path.get(), &st)) { // Checks that it is a file.
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -1047,7 +994,7 @@ bool File::SetLastAccessed(Namespace* namespc,
|
|||
// So set the file access time directly using SetFileTime.
|
||||
FILETIME at = GetFiletimeFromMillis(millis);
|
||||
HANDLE file_handle =
|
||||
CreateFileW(system_name.wide(), FILE_WRITE_ATTRIBUTES,
|
||||
CreateFileW(path.get(), FILE_WRITE_ATTRIBUTES,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
|
||||
nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);
|
||||
if (file_handle == INVALID_HANDLE_VALUE) {
|
||||
|
@ -1063,8 +1010,8 @@ bool File::SetLastModified(Namespace* namespc,
|
|||
int64_t millis) {
|
||||
// First get the current times.
|
||||
struct __stat64 st;
|
||||
Utf8ToWideScope system_name(PrefixLongFilePath(name));
|
||||
if (!StatHelper(system_name.wide(), &st)) {
|
||||
const auto path = ToWinAPIFilePath(name);
|
||||
if (!StatHelper(path.get(), &st)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -1072,7 +1019,7 @@ bool File::SetLastModified(Namespace* namespc,
|
|||
struct __utimbuf64 times;
|
||||
times.actime = st.st_atime;
|
||||
times.modtime = millis / kMillisecondsPerSecond;
|
||||
return _wutime64(system_name.wide(), ×) == 0;
|
||||
return _wutime64(path.get(), ×) == 0;
|
||||
}
|
||||
|
||||
// Keep this function synchronized with the behavior
|
||||
|
@ -1092,10 +1039,10 @@ const char* File::GetCanonicalPath(Namespace* namespc,
|
|||
const char* pathname,
|
||||
char* dest,
|
||||
int dest_size) {
|
||||
Utf8ToWideScope system_name(PrefixLongFilePath(pathname));
|
||||
const auto path = ToWinAPIFilePath(pathname);
|
||||
HANDLE file_handle =
|
||||
CreateFileW(system_name.wide(), 0, FILE_SHARE_READ, nullptr,
|
||||
OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);
|
||||
CreateFileW(path.get(), 0, FILE_SHARE_READ, nullptr, OPEN_EXISTING,
|
||||
FILE_FLAG_BACKUP_SEMANTICS, nullptr);
|
||||
if (file_handle == INVALID_HANDLE_VALUE) {
|
||||
return nullptr;
|
||||
}
|
||||
|
@ -1108,8 +1055,9 @@ const char* File::GetCanonicalPath(Namespace* namespc,
|
|||
SetLastError(error);
|
||||
return nullptr;
|
||||
}
|
||||
auto path = std::unique_ptr<wchar_t[]>(new wchar_t[required_size]);
|
||||
int result_size = GetFinalPathNameByHandle(file_handle, path.get(),
|
||||
|
||||
const auto canonical_path = std::make_unique<wchar_t[]>(required_size);
|
||||
int result_size = GetFinalPathNameByHandle(file_handle, canonical_path.get(),
|
||||
required_size, VOLUME_NAME_DOS);
|
||||
ASSERT(result_size <= required_size - 1);
|
||||
CloseHandle(file_handle);
|
||||
|
@ -1117,16 +1065,18 @@ const char* File::GetCanonicalPath(Namespace* namespc,
|
|||
// Remove leading \\?\ since it is only to overcome MAX_PATH limitation.
|
||||
// Leave it if input used it though.
|
||||
int offset = 0;
|
||||
if ((result_size > 4) && (wcsncmp(path.get(), L"\\\\?\\", 4) == 0) &&
|
||||
if ((result_size > 4) &&
|
||||
(wcsncmp(canonical_path.get(), L"\\\\?\\", 4) == 0) &&
|
||||
(strncmp(pathname, "\\\\?\\", 4) != 0)) {
|
||||
if ((result_size > 8) && (wcsncmp(path.get(), L"\\\\?\\UNC\\", 8) == 0)) {
|
||||
if ((result_size > 8) &&
|
||||
(wcsncmp(canonical_path.get(), L"\\\\?\\UNC\\", 8) == 0)) {
|
||||
// Leave '\\?\UNC\' prefix intact - stripping it makes invalid UNC name.
|
||||
} else {
|
||||
offset = 4;
|
||||
}
|
||||
}
|
||||
int utf8_size = WideCharToMultiByte(CP_UTF8, 0, path.get() + offset, -1,
|
||||
nullptr, 0, nullptr, nullptr);
|
||||
int utf8_size = WideCharToMultiByte(CP_UTF8, 0, canonical_path.get() + offset,
|
||||
-1, nullptr, 0, nullptr, nullptr);
|
||||
if (dest == nullptr) {
|
||||
dest = DartUtils::ScopedCString(utf8_size);
|
||||
dest_size = utf8_size;
|
||||
|
@ -1134,8 +1084,8 @@ const char* File::GetCanonicalPath(Namespace* namespc,
|
|||
if (dest_size != 0) {
|
||||
ASSERT(utf8_size <= dest_size);
|
||||
}
|
||||
if (0 == WideCharToMultiByte(CP_UTF8, 0, path.get() + offset, -1, dest,
|
||||
dest_size, nullptr, nullptr)) {
|
||||
if (0 == WideCharToMultiByte(CP_UTF8, 0, canonical_path.get() + offset, -1,
|
||||
dest, dest_size, nullptr, nullptr)) {
|
||||
return nullptr;
|
||||
}
|
||||
return dest;
|
||||
|
@ -1157,27 +1107,17 @@ File::StdioHandleType File::GetStdioHandleType(int fd) {
|
|||
return kPipe;
|
||||
}
|
||||
|
||||
File::Type File::GetType(Namespace* namespc,
|
||||
const char* pathname,
|
||||
bool follow_links) {
|
||||
// File::GetType can be called without scope(when launching isolate),
|
||||
// so it mallocs prefixed path.
|
||||
StringRAII string_raii = PrefixLongFilePathNoScope(pathname);
|
||||
const char* prefixed_path = string_raii.str();
|
||||
|
||||
// Convert to wchar_t string.
|
||||
Utf8ToWideScope name(prefixed_path);
|
||||
DWORD attributes = GetFileAttributesW(name.wide());
|
||||
File::Type File::GetType(const wchar_t* path, bool follow_links) {
|
||||
DWORD attributes = GetFileAttributesW(path);
|
||||
if (attributes == INVALID_FILE_ATTRIBUTES) {
|
||||
return kDoesNotExist;
|
||||
return File::kDoesNotExist;
|
||||
} else if ((attributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0) {
|
||||
if (follow_links) {
|
||||
HANDLE target_handle = CreateFileW(
|
||||
name.wide(), 0,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr,
|
||||
OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);
|
||||
path, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
|
||||
nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);
|
||||
if (target_handle == INVALID_HANDLE_VALUE) {
|
||||
return kDoesNotExist;
|
||||
return File::kDoesNotExist;
|
||||
} else {
|
||||
BY_HANDLE_FILE_INFORMATION info;
|
||||
if (!GetFileInformationByHandle(target_handle, &info)) {
|
||||
|
@ -1186,16 +1126,23 @@ File::Type File::GetType(Namespace* namespc,
|
|||
}
|
||||
CloseHandle(target_handle);
|
||||
return ((info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0)
|
||||
? kIsDirectory
|
||||
: kIsFile;
|
||||
? File::kIsDirectory
|
||||
: File::kIsFile;
|
||||
}
|
||||
} else {
|
||||
return kIsLink;
|
||||
return File::kIsLink;
|
||||
}
|
||||
} else if ((attributes & FILE_ATTRIBUTE_DIRECTORY) != 0) {
|
||||
return kIsDirectory;
|
||||
return File::kIsDirectory;
|
||||
}
|
||||
return kIsFile;
|
||||
return File::kIsFile;
|
||||
}
|
||||
|
||||
File::Type File::GetType(Namespace* namespc,
|
||||
const char* name,
|
||||
bool follow_links) {
|
||||
const auto path = ToWinAPIFilePath(name);
|
||||
return GetType(path.get(), follow_links);
|
||||
}
|
||||
|
||||
File::Identical File::AreIdentical(Namespace* namespc_1,
|
||||
|
@ -1205,12 +1152,11 @@ File::Identical File::AreIdentical(Namespace* namespc_1,
|
|||
USE(namespc_1);
|
||||
USE(namespc_2);
|
||||
BY_HANDLE_FILE_INFORMATION file_info[2];
|
||||
const char* file_names[2] = {PrefixLongFilePath(file_1),
|
||||
PrefixLongFilePath(file_2)};
|
||||
const std::unique_ptr<wchar_t[]> file_names[2] = {ToWinAPIFilePath(file_1),
|
||||
ToWinAPIFilePath(file_2)};
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
Utf8ToWideScope wide_name(file_names[i]);
|
||||
HANDLE file_handle = CreateFileW(
|
||||
wide_name.wide(), 0,
|
||||
file_names[i].get(), 0,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr,
|
||||
OPEN_EXISTING,
|
||||
FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, nullptr);
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
#ifndef RUNTIME_BIN_FILE_WIN_H_
|
||||
#define RUNTIME_BIN_FILE_WIN_H_
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "bin/file.h"
|
||||
|
||||
// The limit for a regular directory is 248.
|
||||
|
@ -14,7 +16,18 @@
|
|||
namespace dart {
|
||||
namespace bin {
|
||||
|
||||
const char* PrefixLongDirectoryPath(const char* path);
|
||||
// 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.
|
||||
std::unique_ptr<wchar_t[]> ToWinAPIDirectoryPath(
|
||||
const char* path,
|
||||
bool force_long_prefix = false);
|
||||
|
||||
} // namespace bin
|
||||
} // namespace dart
|
||||
|
|
|
@ -48,11 +48,14 @@ const char* Namespace::GetCurrent(Namespace* namespc) {
|
|||
}
|
||||
|
||||
bool Namespace::SetCurrent(Namespace* namespc, const char* path) {
|
||||
// TODO(zichangguo): "\\?\" prepended long path doesn't work.
|
||||
// https://github.com/dart-lang/sdk/issues/42416
|
||||
path = PrefixLongDirectoryPath(path);
|
||||
Utf8ToWideScope system_path(path);
|
||||
bool result = SetCurrentDirectoryW(system_path.wide()) != 0;
|
||||
// SetCurrentDirectory does not actually support paths larger than MAX_PATH,
|
||||
// this limitation is due to the size of the internal buffer used for storing
|
||||
// current directory. In Windows 10, version 1607, changes have been made
|
||||
// to the OS to lift MAX_PATH limitations from file and directory management
|
||||
// APIs, but both application and OS need to opt-in into new behavior.
|
||||
// See https://learn.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation?tabs=registry#enable-long-paths-in-windows-10-version-1607-and-later
|
||||
const auto system_path = Utf8ToWideChar(path);
|
||||
bool result = SetCurrentDirectoryW(system_path.get()) != 0;
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
|
@ -307,12 +307,12 @@ bool SocketBase::ReverseLookup(const RawAddr& addr,
|
|||
|
||||
bool SocketBase::ParseAddress(int type, const char* address, RawAddr* addr) {
|
||||
int result;
|
||||
Utf8ToWideScope system_address(address);
|
||||
const auto system_address = Utf8ToWideChar(address);
|
||||
if (type == SocketAddress::TYPE_IPV4) {
|
||||
result = InetPton(AF_INET, system_address.wide(), &addr->in.sin_addr);
|
||||
result = InetPton(AF_INET, system_address.get(), &addr->in.sin_addr);
|
||||
} else {
|
||||
ASSERT(type == SocketAddress::TYPE_IPV6);
|
||||
result = InetPton(AF_INET6, system_address.wide(), &addr->in6.sin6_addr);
|
||||
result = InetPton(AF_INET6, system_address.get(), &addr->in6.sin6_addr);
|
||||
}
|
||||
return result == 1;
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
#include <errno.h> // NOLINT
|
||||
#include <time.h> // NOLINT
|
||||
#include <memory>
|
||||
|
||||
#include "bin/utils.h"
|
||||
#include "bin/utils_win.h"
|
||||
|
@ -237,6 +238,13 @@ void TimerUtils::Sleep(int64_t millis) {
|
|||
::Sleep(millis);
|
||||
}
|
||||
|
||||
std::unique_ptr<wchar_t[]> Utf8ToWideChar(const char* path) {
|
||||
int wide_len = MultiByteToWideChar(CP_UTF8, 0, path, -1, nullptr, 0);
|
||||
auto result = std::make_unique<wchar_t[]>(wide_len);
|
||||
MultiByteToWideChar(CP_UTF8, 0, path, -1, result.get(), wide_len);
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace bin
|
||||
} // namespace dart
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#ifndef RUNTIME_BIN_UTILS_WIN_H_
|
||||
#define RUNTIME_BIN_UTILS_WIN_H_
|
||||
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
#include "platform/utils.h"
|
||||
|
@ -75,30 +76,7 @@ class WideToUtf8Scope {
|
|||
DISALLOW_IMPLICIT_CONSTRUCTORS(WideToUtf8Scope);
|
||||
};
|
||||
|
||||
class Utf8ToWideScope {
|
||||
public:
|
||||
explicit Utf8ToWideScope(const char* utf8, intptr_t length = -1) {
|
||||
int wide_len = MultiByteToWideChar(CP_UTF8, 0, utf8, length, nullptr, 0);
|
||||
wchar_t* wide =
|
||||
reinterpret_cast<wchar_t*>(malloc(sizeof(wchar_t) * wide_len));
|
||||
MultiByteToWideChar(CP_UTF8, 0, utf8, length, wide, wide_len);
|
||||
length_ = wide_len;
|
||||
wide_ = wide;
|
||||
}
|
||||
|
||||
~Utf8ToWideScope() { free(wide_); }
|
||||
|
||||
wchar_t* wide() const { return wide_; }
|
||||
intptr_t length() const { return length_; }
|
||||
intptr_t size_in_bytes() const { return length_ * sizeof(*wide_); }
|
||||
|
||||
private:
|
||||
intptr_t length_;
|
||||
wchar_t* wide_;
|
||||
|
||||
DISALLOW_ALLOCATION();
|
||||
DISALLOW_IMPLICIT_CONSTRUCTORS(Utf8ToWideScope);
|
||||
};
|
||||
std::unique_ptr<wchar_t[]> Utf8ToWideChar(const char* path);
|
||||
|
||||
} // namespace bin
|
||||
} // namespace dart
|
||||
|
|
|
@ -65,6 +65,7 @@
|
|||
#endif
|
||||
|
||||
#include <intrin.h>
|
||||
#define RPC_USE_NATIVE_WCHAR
|
||||
#include <rpc.h>
|
||||
#include <shellapi.h>
|
||||
#include <versionhelpers.h>
|
||||
|
|
|
@ -251,6 +251,38 @@ class DirectoryTest {
|
|||
});
|
||||
}
|
||||
|
||||
static void testExistsCreateDeleteWithTrailingBackslashSync() {
|
||||
// Check that adding trailing backslash does not break anything on
|
||||
// Windows.
|
||||
if (!Platform.isWindows) {
|
||||
return;
|
||||
}
|
||||
Directory d = Directory.systemTemp.createTempSync('dart_directory_test');
|
||||
try {
|
||||
Directory d2 = new Directory('${d.path}\\');
|
||||
Expect.isTrue(d.existsSync());
|
||||
Expect.isTrue(d2.existsSync());
|
||||
Directory created1 = new Directory("${d.path}\\subdir\\");
|
||||
Directory created2 = new Directory("${d.path}\\subdir\\subdir\\");
|
||||
created1.createSync();
|
||||
created2.createSync();
|
||||
Expect.isTrue(created1.existsSync());
|
||||
Expect.isTrue(created2.existsSync());
|
||||
created2.deleteSync();
|
||||
created1.deleteSync();
|
||||
Expect.isFalse(created1.existsSync());
|
||||
Expect.isFalse(created2.existsSync());
|
||||
created1.createSync();
|
||||
created2.createSync();
|
||||
created1.deleteSync(recursive: true);
|
||||
Expect.isFalse(created1.existsSync());
|
||||
Expect.isFalse(created2.existsSync());
|
||||
} finally {
|
||||
d.deleteSync(recursive: true);
|
||||
Expect.isFalse(d.existsSync());
|
||||
}
|
||||
}
|
||||
|
||||
static void testExistsCreateDeleteSync() {
|
||||
Directory d = Directory.systemTemp.createTempSync('dart_directory_test');
|
||||
Directory d2 = new Directory('${d.path}/');
|
||||
|
@ -437,6 +469,7 @@ class DirectoryTest {
|
|||
testDeleteTooLongNameSync();
|
||||
testExistsCreateDelete();
|
||||
testExistsCreateDeleteSync();
|
||||
testExistsCreateDeleteWithTrailingBackslashSync();
|
||||
testDeleteLinkSync();
|
||||
testDeleteLinkAsFileSync();
|
||||
testDeleteBrokenLinkAsFileSync();
|
||||
|
|
|
@ -6,30 +6,28 @@
|
|||
|
||||
import 'dart:io';
|
||||
|
||||
import "package:expect/expect.dart";
|
||||
import 'package:expect/expect.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
const maxPath = 260;
|
||||
const maxDirectoryPath = maxPath - 12;
|
||||
String longName = '${'x' * 248}';
|
||||
|
||||
Directory createLongPathDir(Directory tmp) {
|
||||
if (tmp.path.length <= maxDirectoryPath) {
|
||||
var path = tmp.path;
|
||||
path += '\\${'t' * 248}';
|
||||
var dir = Directory(path);
|
||||
dir.createSync(recursive: true);
|
||||
// Test the rename() of directory
|
||||
dir = dir.renameSync(tmp.path + '\\$longName');
|
||||
Expect.isTrue(dir.existsSync());
|
||||
Expect.isFalse(Directory(path).existsSync());
|
||||
return dir;
|
||||
} else {
|
||||
return tmp;
|
||||
}
|
||||
Directory createLongPathDir(Directory tmp, [String? suffix]) {
|
||||
var path = tmp.path;
|
||||
path = p.join(path, 't' * 248);
|
||||
var dir = Directory(path);
|
||||
dir.createSync(recursive: true);
|
||||
Expect.isTrue(dir.existsSync());
|
||||
// Test the rename() of directory
|
||||
dir = dir.renameSync(p.join(tmp.path, '$longName$suffix'));
|
||||
Expect.isTrue(dir.existsSync());
|
||||
Expect.isFalse(Directory(path).existsSync());
|
||||
return dir;
|
||||
}
|
||||
|
||||
void testCreate(String dir) {
|
||||
final path = '${dir}\\a_long_path_filename';
|
||||
final path = p.join(dir, 'a_long_path_filename');
|
||||
Expect.isTrue(path.length > maxPath);
|
||||
final file = File(path);
|
||||
file.createSync();
|
||||
|
@ -38,8 +36,8 @@ void testCreate(String dir) {
|
|||
}
|
||||
|
||||
void testCopy(String dir) {
|
||||
final src = '${dir}\\a_long_path_filename_1';
|
||||
final dest = '${dir}\\a_long_path_filename_2';
|
||||
final src = p.join(dir, 'a_long_path_filename_1');
|
||||
final dest = p.join(dir, 'a_long_path_filename_2');
|
||||
Expect.isTrue(src.length > maxPath);
|
||||
final file1 = File(src);
|
||||
file1.createSync();
|
||||
|
@ -51,7 +49,7 @@ void testCopy(String dir) {
|
|||
}
|
||||
|
||||
void testRename(String dir) {
|
||||
final path = '${dir}\\a_long_path_filename';
|
||||
final path = p.join(dir, 'a_long_path_filename');
|
||||
Expect.isTrue(path.length > maxPath);
|
||||
final file = File(path);
|
||||
file.createSync();
|
||||
|
@ -65,7 +63,7 @@ void testRename(String dir) {
|
|||
}
|
||||
|
||||
void testReadWrite(String dir) {
|
||||
final path = '${dir}\\a_long_path_filename';
|
||||
final path = p.join(dir, 'a_long_path_filename');
|
||||
Expect.isTrue(path.length > maxPath);
|
||||
final file = File(path);
|
||||
|
||||
|
@ -82,7 +80,7 @@ void testReadWrite(String dir) {
|
|||
}
|
||||
|
||||
void testOpen(String dir) {
|
||||
final path = '${dir}\\a_long_path_filename';
|
||||
final path = p.join(dir, 'a_long_path_filename');
|
||||
Expect.isTrue(path.length > maxPath);
|
||||
final file = File(path);
|
||||
file.createSync();
|
||||
|
@ -91,7 +89,7 @@ void testOpen(String dir) {
|
|||
}
|
||||
|
||||
void testFileStat(String dir) {
|
||||
final path = '${dir}\\a_long_path_filename';
|
||||
final path = p.join(dir, 'a_long_path_filename');
|
||||
Expect.isTrue(path.length > maxPath);
|
||||
final file = File(path);
|
||||
file.createSync();
|
||||
|
@ -108,21 +106,37 @@ void testFileStat(String dir) {
|
|||
stat.accessed.toString(), file.lastAccessedSync().toString());
|
||||
}
|
||||
|
||||
void testCreateLinkToDir(String dir) {
|
||||
final linkPath = '$dir\\a_long_path_linkname';
|
||||
final renamedPath = '$dir\\a_long_renamed_path_linkname';
|
||||
String _createDirectoryHelper(String currentDir, String targetDir) {
|
||||
if (p.isRelative(targetDir)) {
|
||||
targetDir = p.normalize(p.absolute(currentDir, targetDir));
|
||||
}
|
||||
final dir = Directory(targetDir)..createSync();
|
||||
Expect.isTrue(dir.existsSync());
|
||||
return dir.path;
|
||||
}
|
||||
|
||||
void testCreateLinkToDir(String dir, String dir2) {
|
||||
final linkPath = p.join(dir, 'a_long_path_linkname');
|
||||
final renamedPath = p.join(dir, 'a_long_renamed_path_linkname');
|
||||
|
||||
Expect.isTrue(linkPath.length > maxPath);
|
||||
Expect.isTrue(renamedPath.length > maxPath);
|
||||
|
||||
var targetDirectory1 = '$dir\\a_long_directory_target1';
|
||||
var targetDirectory2 = '$dir\\a_long_directory_target2';
|
||||
final targetDirectory1 =
|
||||
_createDirectoryHelper(dir, p.join(dir2, 'a_long_directory_target1'));
|
||||
final targetDirectory2 =
|
||||
_createDirectoryHelper(dir, p.join(dir2, 'a_long_directory_target2'));
|
||||
|
||||
Directory(targetDirectory1).createSync();
|
||||
Directory(targetDirectory2).createSync();
|
||||
final linkTarget1 = p.isRelative(dir2)
|
||||
? p.relative(targetDirectory1, from: p.dirname(p.absolute(linkPath)))
|
||||
: targetDirectory1;
|
||||
|
||||
final linkTarget2 = p.isRelative(dir2)
|
||||
? p.relative(targetDirectory2, from: p.dirname(p.absolute(linkPath)))
|
||||
: targetDirectory2;
|
||||
|
||||
// Create link
|
||||
final link = Link(linkPath)..createSync(targetDirectory1);
|
||||
final link = Link(linkPath)..createSync(linkTarget1);
|
||||
Expect.isTrue(link.existsSync());
|
||||
final resolvedCreatePath = link.resolveSymbolicLinksSync();
|
||||
Expect.isTrue(
|
||||
|
@ -139,7 +153,7 @@ void testCreateLinkToDir(String dir) {
|
|||
'${link.path} should resolve to $targetDirectory1 but resolved to $resolvedRenamePath');
|
||||
|
||||
// Update link target
|
||||
renamedLink.updateSync(targetDirectory2);
|
||||
renamedLink.updateSync(linkTarget2);
|
||||
final resolvedUpdatedPath = renamedLink.resolveSymbolicLinksSync();
|
||||
Expect.isTrue(
|
||||
FileSystemEntity.identicalSync(targetDirectory2, resolvedUpdatedPath),
|
||||
|
@ -150,11 +164,30 @@ void testCreateLinkToDir(String dir) {
|
|||
renamedLink.deleteSync();
|
||||
}
|
||||
|
||||
void testCreateLinkToFile(String dir) {
|
||||
final path = '${dir}\\a_long_path_linkname';
|
||||
void testCreateLinkToFile(String dir, String dir2) {
|
||||
final path = p.join(dir, 'a_long_path_linkname');
|
||||
Expect.isTrue(path.length > maxPath);
|
||||
var target = '$dir\\a_long_path_target';
|
||||
final link = Link(path)..createSync(target);
|
||||
|
||||
String _pathHelper(String currentDir, String targetPath) {
|
||||
if (p.isRelative(targetPath)) {
|
||||
return p.normalize(p.absolute(currentDir, targetPath));
|
||||
} else {
|
||||
return targetPath;
|
||||
}
|
||||
}
|
||||
|
||||
var target = _pathHelper(dir, p.join(dir2, 'a_long_path_target'));
|
||||
var target2 = _pathHelper(dir, p.join(dir2, 'an_updated_target'));
|
||||
|
||||
var linkTarget = p.isRelative(dir2)
|
||||
? p.relative(target, from: p.dirname(p.absolute(path)))
|
||||
: target;
|
||||
|
||||
var linkTarget2 = p.isRelative(dir2)
|
||||
? p.relative(target2, from: p.dirname(p.absolute(path)))
|
||||
: target2;
|
||||
|
||||
final link = Link(path)..createSync(linkTarget);
|
||||
|
||||
final dest = File(target)..createSync();
|
||||
Expect.isTrue(dest.existsSync());
|
||||
|
@ -165,15 +198,14 @@ void testCreateLinkToFile(String dir) {
|
|||
'${link.path} should resolve to $target but resolved to $resolvedPath');
|
||||
|
||||
// Rename link
|
||||
var renamedLink = link.renameSync('${dir}\\a_renamed_long_path_link');
|
||||
var renamedLink = link.renameSync(p.join(dir, 'a_renamed_long_path_link'));
|
||||
Expect.isTrue(renamedLink.existsSync());
|
||||
Expect.isFalse(link.existsSync());
|
||||
Expect.isTrue(renamedLink.targetSync().contains('a_long_path_target'));
|
||||
|
||||
// Update link target
|
||||
target = '$dir\\an_updated_target';
|
||||
final renamedDest = File(target)..createSync();
|
||||
renamedLink.updateSync(target);
|
||||
final renamedDest = File(target2)..createSync();
|
||||
renamedLink.updateSync(linkTarget2);
|
||||
Expect.isTrue(renamedLink.targetSync().contains('an_updated_target'));
|
||||
|
||||
dest.deleteSync();
|
||||
|
@ -182,8 +214,8 @@ void testCreateLinkToFile(String dir) {
|
|||
}
|
||||
|
||||
testNormalLinkToLongPath(String short, String long) {
|
||||
var target = File('$long\\file_target')..createSync();
|
||||
final link = Link('$short\\link')..createSync(target.path);
|
||||
var target = File(p.join(long, 'file_target'))..createSync();
|
||||
final link = Link(p.join(short, 'link'))..createSync(target.path);
|
||||
Expect.isTrue(target.path.length > maxPath);
|
||||
Expect.isTrue(link.resolveSymbolicLinksSync().length > maxPath);
|
||||
Expect.isTrue(link.path.length < maxPath);
|
||||
|
@ -192,7 +224,7 @@ testNormalLinkToLongPath(String short, String long) {
|
|||
Expect.isTrue(link.existsSync());
|
||||
Expect.equals(target.path, link.targetSync());
|
||||
|
||||
var targetDir = Directory('$long\\dir_target')..createSync();
|
||||
var targetDir = Directory(p.join(long, 'dir_target'))..createSync();
|
||||
link.updateSync(targetDir.path);
|
||||
Expect.equals(targetDir.path, link.targetSync());
|
||||
|
||||
|
@ -202,8 +234,8 @@ testNormalLinkToLongPath(String short, String long) {
|
|||
}
|
||||
|
||||
testLongPathLinkToNormal(String short, String long) {
|
||||
var target = File('$short\\file_target')..createSync();
|
||||
final link = Link('$long\\link')..createSync(target.path);
|
||||
var target = File(p.join(short, 'file_target'))..createSync();
|
||||
final link = Link(p.join(long, 'link'))..createSync(target.path);
|
||||
|
||||
Expect.isTrue(target.path.length < maxPath);
|
||||
Expect.isTrue(link.path.length > maxPath);
|
||||
|
@ -212,7 +244,7 @@ testLongPathLinkToNormal(String short, String long) {
|
|||
Expect.isTrue(link.existsSync());
|
||||
Expect.equals(target.path, link.targetSync());
|
||||
|
||||
var targetDir = Directory('$short\\dir_target')..createSync();
|
||||
var targetDir = Directory(p.join(short, 'dir_target'))..createSync();
|
||||
link.updateSync(targetDir.path);
|
||||
Expect.equals(targetDir.path, link.targetSync());
|
||||
|
||||
|
@ -221,38 +253,49 @@ testLongPathLinkToNormal(String short, String long) {
|
|||
targetDir.deleteSync();
|
||||
}
|
||||
|
||||
testDirectorySetCurrent(String dir) {
|
||||
testDirectorySetCurrentFails(String dir) {
|
||||
// This tests setting a long path directory to current directory.
|
||||
// This will fail.
|
||||
Expect.isTrue(dir.length > maxPath);
|
||||
Expect.throws<FileSystemException>(() {
|
||||
Directory.current = dir;
|
||||
}, (e) => e.toString().contains('extension is too long'));
|
||||
if (Platform.isWindows) {
|
||||
// On Windows current directory path is limited to MAX_PATH characters.
|
||||
// Windows 10, Version 1607 introduced a way to lift this limitation but
|
||||
// it requires opt-in from both application and the OS configuration.
|
||||
//
|
||||
// See https://learn.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation?tabs=registry#enable-long-paths-in-windows-10-version-1607-and-later
|
||||
Expect.throws<FileSystemException>(() {
|
||||
Directory.current = dir;
|
||||
}, (e) => e.toString().contains('extension is too long'));
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
if (!Platform.isWindows) {
|
||||
return;
|
||||
}
|
||||
final tmp = Directory.systemTemp.createTempSync('dart-file-long-path');
|
||||
final tmp = Directory.systemTemp.createTempSync('flpt');
|
||||
final oldCurrent = Directory.current;
|
||||
Directory.current = tmp;
|
||||
try {
|
||||
String dir = createLongPathDir(tmp).path;
|
||||
testDirectorySetCurrent(dir);
|
||||
for (final path in [dir, ".\\$longName", ".\\$longName\\..\\$longName"]) {
|
||||
String dir1 = createLongPathDir(tmp, 'dir1').path;
|
||||
String dir2 = createLongPathDir(tmp, 'dir2').path;
|
||||
testDirectorySetCurrentFails(dir1);
|
||||
for (final path in [
|
||||
dir1,
|
||||
p.join('.', '${longName}dir1'),
|
||||
p.join('.', '${longName}dir1', '..', '${longName}dir1'),
|
||||
]) {
|
||||
testCreate(path);
|
||||
testCopy(path);
|
||||
testRename(path);
|
||||
testReadWrite(path);
|
||||
testOpen(path);
|
||||
testFileStat(path);
|
||||
testCreateLinkToDir(path);
|
||||
testCreateLinkToFile(path);
|
||||
for (var relative in [true, false]) {
|
||||
final targetDir = relative ? p.relative(dir2, from: dir1) : dir2;
|
||||
testCreateLinkToDir(path, targetDir);
|
||||
testCreateLinkToFile(path, targetDir);
|
||||
}
|
||||
}
|
||||
|
||||
testNormalLinkToLongPath(tmp.path, dir);
|
||||
testLongPathLinkToNormal(tmp.path, dir);
|
||||
testNormalLinkToLongPath(tmp.path, dir1);
|
||||
testLongPathLinkToNormal(tmp.path, dir1);
|
||||
} finally {
|
||||
// Reset the current Directory.
|
||||
Directory.current = oldCurrent;
|
||||
|
|
Loading…
Reference in a new issue