blob: 6e11076beece2d8d4967fc39b385af41b0c3d30e [file] [log] [blame]
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
#include "platform/globals.h"
#if defined(HOST_OS_WINDOWS)
#include <functional>
#include <memory>
#include <string>
#include <Shlwapi.h> // NOLINT
#include <WinIoCtl.h> // NOLINT
#include <fcntl.h> // NOLINT
#include <io.h> // NOLINT
#undef StrDup // defined in Shlwapi.h as StrDupW
#include <stdio.h> // NOLINT
#include <string.h> // NOLINT
#include <sys/stat.h> // NOLINT
#include <sys/utime.h> // NOLINT
#include "bin/builtin.h"
#include "bin/crypto.h"
#include "bin/directory.h"
#include "bin/file.h"
#include "bin/file_win.h"
#include "bin/namespace.h"
#include "bin/utils.h"
#include "bin/utils_win.h"
#include "platform/syslog.h"
#include "platform/utils.h"
namespace dart {
namespace bin {
class FileHandle {
public:
explicit FileHandle(int fd) : fd_(fd) {}
~FileHandle() {}
int fd() const { return fd_; }
void set_fd(int fd) { fd_ = fd; }
private:
int fd_;
DISALLOW_COPY_AND_ASSIGN(FileHandle);
};
File::~File() {
if (!IsClosed() && handle_->fd() != _fileno(stdout) &&
handle_->fd() != _fileno(stderr)) {
Close();
}
delete handle_;
}
void File::Close() {
ASSERT(handle_->fd() >= 0);
int closing_fd = handle_->fd();
if ((closing_fd == _fileno(stdout)) || (closing_fd == _fileno(stderr))) {
int fd = _open("NUL", _O_WRONLY);
ASSERT(fd >= 0);
_dup2(fd, closing_fd);
Utils::Close(fd);
} else {
int err = Utils::Close(closing_fd);
if (err != 0) {
Syslog::PrintErr("%s\n", strerror(errno));
}
}
handle_->set_fd(kClosedFd);
}
intptr_t File::GetFD() {
return handle_->fd();
}
bool File::IsClosed() {
return handle_->fd() == kClosedFd;
}
MappedMemory* File::Map(File::MapType type,
int64_t position,
int64_t length,
void* start) {
DWORD prot_alloc;
DWORD prot_final;
switch (type) {
case File::kReadOnly:
prot_alloc = PAGE_READWRITE;
prot_final = PAGE_READONLY;
break;
case File::kReadExecute:
prot_alloc = PAGE_EXECUTE_READWRITE;
prot_final = PAGE_EXECUTE_READ;
break;
case File::kReadWrite:
prot_alloc = PAGE_READWRITE;
prot_final = PAGE_READWRITE;
break;
}
void* addr = start;
if (addr == nullptr) {
addr = VirtualAlloc(nullptr, length, MEM_COMMIT | MEM_RESERVE, prot_alloc);
if (addr == nullptr) {
Syslog::PrintErr("VirtualAlloc failed %d\n", GetLastError());
return nullptr;
}
}
const int64_t remaining_length = Length() - position;
SetPosition(position);
if (!ReadFully(addr, Utils::Minimum(length, remaining_length))) {
Syslog::PrintErr("ReadFully failed %d\n", GetLastError());
if (start == nullptr) {
VirtualFree(addr, 0, MEM_RELEASE);
}
return nullptr;
}
// If the requested mapping is larger than the file size, we should fill the
// extra memory with zeros.
if (length > remaining_length) {
memset(reinterpret_cast<uint8_t*>(addr) + remaining_length, 0,
length - remaining_length);
}
DWORD old_prot;
bool result = VirtualProtect(addr, length, prot_final, &old_prot);
if (!result) {
Syslog::PrintErr("VirtualProtect failed %d\n", GetLastError());
if (start == nullptr) {
VirtualFree(addr, 0, MEM_RELEASE);
}
return nullptr;
}
return new MappedMemory(addr, length, /*should_unmap=*/start == nullptr);
}
void MappedMemory::Unmap() {
BOOL result = VirtualFree(address_, 0, MEM_RELEASE);
ASSERT(result);
address_ = 0;
size_ = 0;
}
int64_t File::Read(void* buffer, int64_t num_bytes) {
ASSERT(handle_->fd() >= 0);
return Utils::Read(handle_->fd(), buffer, num_bytes);
}
int64_t File::Write(const void* buffer, int64_t num_bytes) {
int fd = handle_->fd();
// Avoid narrowing conversion
ASSERT(fd >= 0 && num_bytes <= MAXDWORD && num_bytes >= 0);
HANDLE handle = reinterpret_cast<HANDLE>(_get_osfhandle(fd));
DWORD written = 0;
BOOL result = WriteFile(handle, buffer, num_bytes, &written, NULL);
if (!result) {
return -1;
}
DWORD mode;
int64_t bytes_written = written;
if (GetConsoleMode(handle, &mode)) {
// If `handle` is for a console, then `written` may refer to the number of
// characters printed to the screen rather than the number of bytes of the
// buffer that were actually consumed. To compute the number of bytes that
// were actually consumed, we convert the buffer to a wchar_t using the
// console's current code page, filling as many characters as were
// printed, and then convert that many characters back to the encoding for
// the code page, which gives the number of bytes of `buffer` used to
// generate the characters that were printed.
wchar_t* wide = new wchar_t[written];
int cp = GetConsoleOutputCP();
MultiByteToWideChar(cp, 0, reinterpret_cast<const char*>(buffer), -1, wide,
written);
int buffer_len =
WideCharToMultiByte(cp, 0, wide, written, NULL, 0, NULL, NULL);
delete[] wide;
bytes_written = buffer_len;
}
return bytes_written;
}
bool File::VPrint(const char* format, va_list args) {
// Measure.
va_list measure_args;
va_copy(measure_args, args);
intptr_t len = _vscprintf(format, measure_args);
va_end(measure_args);
char* buffer = reinterpret_cast<char*>(malloc(len + 1));
// Print.
va_list print_args;
va_copy(print_args, args);
_vsnprintf(buffer, len + 1, format, print_args);
va_end(print_args);
bool result = WriteFully(buffer, len);
free(buffer);
return result;
}
int64_t File::Position() {
ASSERT(handle_->fd() >= 0);
return _lseeki64(handle_->fd(), 0, SEEK_CUR);
}
bool File::SetPosition(int64_t position) {
ASSERT(handle_->fd() >= 0);
return _lseeki64(handle_->fd(), position, SEEK_SET) >= 0;
}
bool File::Truncate(int64_t length) {
ASSERT(handle_->fd() >= 0);
return _chsize_s(handle_->fd(), length) == 0;
}
bool File::Flush() {
ASSERT(handle_->fd());
return _commit(handle_->fd()) != -1;
}
bool File::Lock(File::LockType lock, int64_t start, int64_t end) {
ASSERT(handle_->fd() >= 0);
ASSERT((end == -1) || (end > start));
HANDLE handle = reinterpret_cast<HANDLE>(_get_osfhandle(handle_->fd()));
OVERLAPPED overlapped;
ZeroMemory(&overlapped, sizeof(OVERLAPPED));
overlapped.Offset = Utils::Low32Bits(start);
overlapped.OffsetHigh = Utils::High32Bits(start);
int64_t length = end == -1 ? 0 : end - start;
if (length == 0) {
length = kMaxInt64;
}
int32_t length_low = Utils::Low32Bits(length);
int32_t length_high = Utils::High32Bits(length);
BOOL rc;
switch (lock) {
case File::kLockUnlock:
rc = UnlockFileEx(handle, 0, length_low, length_high, &overlapped);
break;
case File::kLockShared:
case File::kLockExclusive:
case File::kLockBlockingShared:
case File::kLockBlockingExclusive: {
DWORD flags = 0;
if ((lock == File::kLockShared) || (lock == File::kLockExclusive)) {
flags |= LOCKFILE_FAIL_IMMEDIATELY;
}
if ((lock == File::kLockExclusive) ||
(lock == File::kLockBlockingExclusive)) {
flags |= LOCKFILE_EXCLUSIVE_LOCK;
}
rc = LockFileEx(handle, flags, 0, length_low, length_high, &overlapped);
break;
}
default:
UNREACHABLE();
}
return rc;
}
int64_t File::Length() {
ASSERT(handle_->fd() >= 0);
struct __stat64 st;
if (_fstat64(handle_->fd(), &st) == 0) {
return st.st_size;
}
return -1;
}
File* File::FileOpenW(const wchar_t* system_name, FileOpenMode mode) {
int flags = O_RDONLY | O_BINARY | O_NOINHERIT;
if ((mode & kWrite) != 0) {
ASSERT((mode & kWriteOnly) == 0);
flags = (O_RDWR | O_CREAT | O_BINARY | O_NOINHERIT);
}
if ((mode & kWriteOnly) != 0) {
ASSERT((mode & kWrite) == 0);
flags = (O_WRONLY | O_CREAT | O_BINARY | O_NOINHERIT);
}
if ((mode & kTruncate) != 0) {
flags = flags | O_TRUNC;
}
int fd = _wopen(system_name, flags, 0666);
if (fd < 0) {
return NULL;
}
if ((((mode & kWrite) != 0) && ((mode & kTruncate) == 0)) ||
(((mode & kWriteOnly) != 0) && ((mode & kTruncate) == 0))) {
int64_t position = _lseeki64(fd, 0, SEEK_END);
if (position < 0) {
return NULL;
}
}
return new File(new FileHandle(fd));
}
class StringRAII {
public:
explicit StringRAII(const char* s) : s_(s), own_(false) {}
explicit StringRAII(char* s) : s_(s), own_(true) {}
~StringRAII() {
if (own_) {
free(const_cast<char*>(s_));
}
}
const char* str() const { return s_; }
const char* release() {
own_ = false;
return s_;
}
private:
bool own_;
const char* s_;
};
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()));
}
static StringRAII PrefixLongPathIfExceedLimit(
const char* path,
bool is_file,
std::function<char*(int)> allocate) {
// 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 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);
}
// 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)
: ConvertToAbsolutePath(path, &is_ok);
if (!is_ok) {
return StringRAII(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);
}
// 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];
}
result[length + kLongPathPrefixLength] = '\0';
return StringRAII(result);
}
static const char* PrefixLongFilePath(const char* path) {
return PrefixLongPathIfExceedLimit(
path, /*is_file=*/true,
[](int size) {
return reinterpret_cast<char*>(Dart_ScopeAllocate(size));
})
.release();
}
static StringRAII PrefixLongFilePathNoScope(const char* path) {
return PrefixLongPathIfExceedLimit(path, /*is_file=*/true, [](int size) {
return reinterpret_cast<char*>(malloc(size));
});
}
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);
return file;
}
Utils::CStringUniquePtr File::UriToPath(const char* uri) {
UriDecoder uri_decoder(uri);
if (uri_decoder.decoded() == nullptr) {
SetLastError(ERROR_INVALID_NAME);
return Utils::CreateCStringUniquePtr(nullptr);
}
Utf8ToWideScope uri_w(uri_decoder.decoded());
if (!UrlIsFileUrlW(uri_w.wide())) {
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,
/* dwFlags= */ 0);
if (result != S_OK) {
return Utils::CreateCStringUniquePtr(nullptr);
}
WideToUtf8Scope utf8_path(filename_w);
return utf8_path.release();
}
File* File::OpenUri(Namespace* namespc, const char* uri, FileOpenMode mode) {
auto path = UriToPath(uri);
if (path == nullptr) {
return nullptr;
}
return Open(namespc, path.get(), mode);
}
File* File::OpenStdio(int fd) {
int stdio_fd = -1;
switch (fd) {
case 1:
stdio_fd = _fileno(stdout);
break;
case 2:
stdio_fd = _fileno(stderr);
break;
default:
UNREACHABLE();
}
_setmode(stdio_fd, _O_BINARY);
return new File(new FileHandle(stdio_fd));
}
static bool StatHelper(wchar_t* path, struct __stat64* st) {
int stat_status = _wstat64(path, st);
if (stat_status != 0) {
return false;
}
if ((st->st_mode & S_IFMT) != S_IFREG) {
SetLastError(ERROR_NOT_SUPPORTED);
return false;
}
return true;
}
bool File::Exists(Namespace* namespc, const char* name) {
StringRAII string_raii = PrefixLongFilePathNoScope(name);
Utf8ToWideScope system_name(string_raii.str());
struct __stat64 st;
return StatHelper(system_name.wide(), &st);
}
bool File::ExistsUri(Namespace* namespc, const char* uri) {
UriDecoder uri_decoder(uri);
if (uri_decoder.decoded() == nullptr) {
SetLastError(ERROR_INVALID_NAME);
return false;
}
return File::Exists(namespc, uri_decoder.decoded());
}
bool File::Create(Namespace* namespc, const char* name) {
Utf8ToWideScope system_name(PrefixLongFilePath(name));
int fd = _wopen(system_name.wide(), O_RDONLY | O_CREAT, 0666);
if (fd < 0) {
return false;
}
return (Utils::Close(fd) == 0);
}
// This structure is needed for creating and reading Junctions.
typedef struct _REPARSE_DATA_BUFFER {
ULONG ReparseTag;
USHORT ReparseDataLength;
USHORT Reserved;
union {
struct {
USHORT SubstituteNameOffset;
USHORT SubstituteNameLength;
USHORT PrintNameOffset;
USHORT PrintNameLength;
ULONG Flags;
WCHAR PathBuffer[1];
} SymbolicLinkReparseBuffer;
struct {
USHORT SubstituteNameOffset;
USHORT SubstituteNameLength;
USHORT PrintNameOffset;
USHORT PrintNameLength;
WCHAR PathBuffer[1];
} MountPointReparseBuffer;
struct {
UCHAR DataBuffer[1];
} GenericReparseBuffer;
};
} REPARSE_DATA_BUFFER, *PREPARSE_DATA_BUFFER;
static const int kReparseDataHeaderSize = sizeof(ULONG) + 2 * sizeof(USHORT);
static const int kMountPointHeaderSize = 4 * sizeof(USHORT);
// Note: CreateLink used to create junctions on Windows instead of true
// symbolic links. All File::*Link methods now support handling links created
// as junctions and symbolic links.
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;
File::Type type = File::GetType(namespc, utf8_target, true);
if (type == kIsDirectory) {
flags |= SYMBOLIC_LINK_FLAG_DIRECTORY;
}
int create_status = CreateSymbolicLinkW(name.wide(), target.wide(), 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);
}
return (create_status != 0);
}
bool File::Delete(Namespace* namespc, const char* name) {
Utf8ToWideScope system_name(PrefixLongFilePath(name));
int status = _wremove(system_name.wide());
return status != -1;
}
bool File::DeleteLink(Namespace* namespc, const char* name) {
Utf8ToWideScope system_name(PrefixLongFilePath(name));
bool result = false;
DWORD attributes = GetFileAttributesW(system_name.wide());
if ((attributes == INVALID_FILE_ATTRIBUTES) ||
((attributes & FILE_ATTRIBUTE_REPARSE_POINT) == 0)) {
SetLastError(ERROR_NOT_A_REPARSE_POINT);
return false;
}
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);
} else {
// Symbolic link to a file. Remove the file.
result = (DeleteFileW(system_name.wide()) != 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) {
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);
DWORD flags = MOVEFILE_WRITE_THROUGH | MOVEFILE_REPLACE_EXISTING;
int move_status =
MoveFileExW(system_old_path.wide(), system_new_path.wide(), flags);
return (move_status != 0);
}
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;
// Junction links 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 a link. This step is only
// necessary for junctions created by the old Link.create implementation.
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);
}
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 = NULL;
if (last_back_slash == NULL && last_forward_slash == NULL) {
return NULL;
} else if (last_forward_slash != NULL && last_forward_slash != NULL) {
// 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;
}
} else {
last_path_separator =
(last_forward_slash == NULL) ? 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 NULL;
}
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 NULL;
}
char suffix[8 + 1];
Utils::SNPrint(suffix, sizeof(suffix), "%x", suffix_bytes);
Utf8ToWideScope source_path(src);
if (!buffer.Add(suffix)) {
return NULL;
}
if (CopyFileExW(source_path.wide(), buffer.AsStringW(), NULL, NULL, NULL,
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 NULL;
}
UUID uuid;
RPC_STATUS status = UuidCreateSequential(&uuid);
if ((status != RPC_S_OK) && (status != RPC_S_UUID_LOCAL_ONLY)) {
return NULL;
}
RPC_WSTR uuid_string;
status = UuidToStringW(&uuid, &uuid_string);
if (status != RPC_S_OK) {
return NULL;
}
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 NULL;
}
RpcStringFreeW(&uuid_string);
Utf8ToWideScope source_path(src);
if (CopyFileExW(source_path.wide(), buffer.AsStringW(), NULL, NULL, NULL,
0) != 0) {
return CopyToDartScopeString(buffer.AsStringW());
}
return NULL;
}
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);
if (type != kIsFile) {
SetLastError(ERROR_FILE_NOT_FOUND);
return false;
}
wchar_t* temp_file = CopyIntoTempFile(prefixed_old_path, prefixed_new_path);
if (temp_file == NULL) {
// 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(), NULL,
NULL, NULL, 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 (!MoveFileW(temp_file, system_new_dest.wide())) {
DWORD error = GetLastError();
DeleteFileW(temp_file);
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)) {
return -1;
}
return st.st_size;
}
const char* File::LinkTarget(Namespace* namespc,
const char* pathname,
char* dest,
int dest_size) {
const wchar_t* name =
StringUtilsWin::Utf8ToWide(PrefixLongFilePath(pathname));
HANDLE dir_handle = CreateFileW(
name, GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL,
OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT,
NULL);
if (dir_handle == INVALID_HANDLE_VALUE) {
return NULL;
}
// Allocate a buffer for regular paths (smaller than MAX_PATH). If buffer is
// too small for a long path, allocate a bigger buffer and try again.
int buffer_size =
sizeof(REPARSE_DATA_BUFFER) + (MAX_PATH + 1) * sizeof(WCHAR);
REPARSE_DATA_BUFFER* buffer =
reinterpret_cast<REPARSE_DATA_BUFFER*>(Dart_ScopeAllocate(buffer_size));
DWORD received_bytes; // Value is not used.
int result = DeviceIoControl(dir_handle, FSCTL_GET_REPARSE_POINT, NULL, 0,
buffer, buffer_size, &received_bytes, NULL);
if (result == 0) {
DWORD error = GetLastError();
// If ERROR_MORE_DATA is thrown, the target path exceeds the size limit. A
// bigger buffer will be required.
if (error == ERROR_MORE_DATA) {
// Allocate a bigger buffer with MAX_LONG_PATH
buffer_size =
sizeof(REPARSE_DATA_BUFFER) + (MAX_LONG_PATH + 1) * sizeof(WCHAR);
buffer = reinterpret_cast<REPARSE_DATA_BUFFER*>(
Dart_ScopeAllocate(buffer_size));
result = DeviceIoControl(dir_handle, FSCTL_GET_REPARSE_POINT, NULL, 0,
buffer, buffer_size, &received_bytes, NULL);
if (result == 0) {
// Overwrite the ERROR_MORE_DATA.
error = GetLastError();
}
}
if (result == 0) {
CloseHandle(dir_handle);
SetLastError(error);
return NULL;
}
}
if (CloseHandle(dir_handle) == 0) {
return NULL;
}
wchar_t* target;
size_t target_offset;
size_t target_length;
if (buffer->ReparseTag == IO_REPARSE_TAG_MOUNT_POINT) {
target = buffer->MountPointReparseBuffer.PathBuffer;
target_offset = buffer->MountPointReparseBuffer.SubstituteNameOffset;
target_length = buffer->MountPointReparseBuffer.SubstituteNameLength;
} else if (buffer->ReparseTag == IO_REPARSE_TAG_SYMLINK) {
target = buffer->SymbolicLinkReparseBuffer.PathBuffer;
target_offset = buffer->SymbolicLinkReparseBuffer.SubstituteNameOffset;
target_length = buffer->SymbolicLinkReparseBuffer.SubstituteNameLength;
} else { // Not a junction or a symbolic link.
SetLastError(ERROR_NOT_A_REPARSE_POINT);
return NULL;
}
target_offset /= sizeof(wchar_t); // Offset and length are in bytes.
target_length /= sizeof(wchar_t);
target += target_offset;
// Remove "\??\" from beginning of target.
if ((target_length > 4) && (wcsncmp(L"\\??\\", target, 4) == 0)) {
target += 4;
target_length -= 4;
}
int utf8_length = WideCharToMultiByte(CP_UTF8, 0, target, target_length, NULL,
0, NULL, NULL);
if (dest_size > 0 && dest_size <= utf8_length) {
return NULL;
}
if (dest == NULL) {
dest = DartUtils::ScopedCString(utf8_length + 1);
}
if (0 == WideCharToMultiByte(CP_UTF8, 0, target, target_length, dest,
utf8_length, NULL, NULL)) {
return NULL;
}
dest[utf8_length] = '\0';
return dest;
}
void File::Stat(Namespace* namespc, const char* name, int64_t* data) {
const char* prefixed_name = PrefixLongFilePath(name);
File::Type type = GetType(namespc, prefixed_name, false);
data[kType] = type;
if (type != kDoesNotExist) {
struct _stat64 st;
Utf8ToWideScope system_name(prefixed_name);
int stat_status = _wstat64(system_name.wide(), &st);
if (stat_status == 0) {
data[kCreatedTime] = st.st_ctime * 1000;
data[kModifiedTime] = st.st_mtime * 1000;
data[kAccessedTime] = st.st_atime * 1000;
data[kMode] = st.st_mode;
data[kSize] = st.st_size;
} else {
data[kType] = File::kDoesNotExist;
}
}
}
time_t File::LastAccessed(Namespace* namespc, const char* name) {
struct __stat64 st;
Utf8ToWideScope system_name(PrefixLongFilePath(name));
if (!StatHelper(system_name.wide(), &st)) {
return -1;
}
return st.st_atime;
}
time_t File::LastModified(Namespace* namespc, const char* name) {
struct __stat64 st;
Utf8ToWideScope system_name(PrefixLongFilePath(name));
if (!StatHelper(system_name.wide(), &st)) {
return -1;
}
return st.st_mtime;
}
bool File::SetLastAccessed(Namespace* namespc,
const char* name,
int64_t millis) {
// First get the current times.
struct __stat64 st;
Utf8ToWideScope system_name(PrefixLongFilePath(name));
if (!StatHelper(system_name.wide(), &st)) {
return false;
}
// Set the new time:
struct __utimbuf64 times;
times.actime = millis / kMillisecondsPerSecond;
times.modtime = st.st_mtime;
return _wutime64(system_name.wide(), &times) == 0;
}
bool File::SetLastModified(Namespace* namespc,
const char* name,
int64_t millis) {
// First get the current times.
struct __stat64 st;
Utf8ToWideScope system_name(PrefixLongFilePath(name));
if (!StatHelper(system_name.wide(), &st)) {
return false;
}
// Set the new time:
struct __utimbuf64 times;
times.actime = st.st_atime;
times.modtime = millis / kMillisecondsPerSecond;
return _wutime64(system_name.wide(), &times) == 0;
}
// Keep this function synchronized with the behavior
// of `FileSystemEntity.isAbsolute` in file_system_entity.dart.
bool File::IsAbsolutePath(const char* pathname) {
if (pathname == NULL) return false;
char first = pathname[0];
char second = pathname[1];
if (first == '\\' && second == '\\') return true;
if (second != ':') return false;
first |= 0x20;
char third = pathname[2];
return (first >= 'a') && (first <= 'z') && (third == '\\' || third == '/');
}
const char* File::GetCanonicalPath(Namespace* namespc,
const char* pathname,
char* dest,
int dest_size) {
Utf8ToWideScope system_name(PrefixLongFilePath(pathname));
HANDLE file_handle =
CreateFileW(system_name.wide(), 0, FILE_SHARE_READ, NULL, OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS, NULL);
if (file_handle == INVALID_HANDLE_VALUE) {
return NULL;
}
wchar_t dummy_buffer[1];
int required_size =
GetFinalPathNameByHandle(file_handle, dummy_buffer, 0, VOLUME_NAME_DOS);
if (required_size == 0) {
DWORD error = GetLastError();
CloseHandle(file_handle);
SetLastError(error);
return NULL;
}
auto path = std::unique_ptr<wchar_t[]>(new wchar_t[required_size]);
int result_size = GetFinalPathNameByHandle(file_handle, path.get(),
required_size, VOLUME_NAME_DOS);
ASSERT(result_size <= required_size - 1);
CloseHandle(file_handle);
// Remove leading \\?\ if possible, unless input used it.
int offset = 0;
if ((result_size > 4) && (wcsncmp(path.get(), L"\\\\?\\", 4) == 0) &&
(strncmp(pathname, "\\\\?\\", 4) != 0)) {
offset = 4;
}
int utf8_size = WideCharToMultiByte(CP_UTF8, 0, path.get() + offset, -1,
nullptr, 0, nullptr, nullptr);
if (dest == NULL) {
dest = DartUtils::ScopedCString(utf8_size);
dest_size = utf8_size;
}
if (dest_size != 0) {
ASSERT(utf8_size <= dest_size);
}
if (0 == WideCharToMultiByte(CP_UTF8, 0, path.get() + offset, -1, dest,
dest_size, NULL, NULL)) {
return NULL;
}
return dest;
}
const char* File::PathSeparator() {
// This is already UTF-8 encoded.
return "\\";
}
const char* File::StringEscapedPathSeparator() {
// This is already UTF-8 encoded.
return "\\\\";
}
File::StdioHandleType File::GetStdioHandleType(int fd) {
// Treat all stdio handles as pipes. The Windows event handler and
// socket code will handle the different handle types.
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 result = kIsFile;
if (attributes == INVALID_FILE_ATTRIBUTES) {
result = 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,
NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
if (target_handle == INVALID_HANDLE_VALUE) {
result = File::kIsLink;
} else {
BY_HANDLE_FILE_INFORMATION info;
if (!GetFileInformationByHandle(target_handle, &info)) {
CloseHandle(target_handle);
return File::kIsLink;
}
CloseHandle(target_handle);
return ((info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0)
? File::kIsDirectory
: File::kIsFile;
}
} else {
result = kIsLink;
}
} else if ((attributes & FILE_ATTRIBUTE_DIRECTORY) != 0) {
result = kIsDirectory;
}
return result;
}
File::Identical File::AreIdentical(Namespace* namespc_1,
const char* file_1,
Namespace* namespc_2,
const char* file_2) {
USE(namespc_1);
USE(namespc_2);
BY_HANDLE_FILE_INFORMATION file_info[2];
const char* file_names[2] = {PrefixLongFilePath(file_1),
PrefixLongFilePath(file_2)};
for (int i = 0; i < 2; ++i) {
Utf8ToWideScope wide_name(file_names[i]);
HANDLE file_handle = CreateFileW(
wide_name.wide(), 0,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL,
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, NULL);
if (file_handle == INVALID_HANDLE_VALUE) {
return File::kError;
}
int result = GetFileInformationByHandle(file_handle, &file_info[i]);
if (result == 0) {
DWORD error = GetLastError();
CloseHandle(file_handle);
SetLastError(error);
return File::kError;
}
if (CloseHandle(file_handle) == 0) {
return File::kError;
}
}
if ((file_info[0].dwVolumeSerialNumber ==
file_info[1].dwVolumeSerialNumber) &&
(file_info[0].nFileIndexHigh == file_info[1].nFileIndexHigh) &&
(file_info[0].nFileIndexLow == file_info[1].nFileIndexLow)) {
return kIdentical;
} else {
return kDifferent;
}
}
} // namespace bin
} // namespace dart
#endif // defined(HOST_OS_WINDOWS)