dart-sdk/runtime/bin/directory_win.cc
Zachary Anderson d0295c873c [dart:io] Namespaces for file IO
Fuchsia requires the ability to sandbox Isolates w.r.t. file IO.
When a new Isolate starts, Fuchsia will pass the Isolate an object
called a namespace. We can translate the namespace object into a
file descriptor suitable for passing to the *at() family of
POSIX file system calls. The file system calls will then
have visibility only into the specified namespace.

We also plumb Namespaces through on all the other platforms as well to
make the change easier to test and so that in the future we can
implement e.g. per-isolate cwds.

This change adds a new internal class to dart:io called _Namespace,
which is implemented in a patch file. See:

sdk/lib/io/namespace_impl.dart
runtime/bin/namespace_patch.dart

The embedder can set up a non-default namespace by calling
_Namespace._setupNamespace during Isolate setup.

Instances of _Namespace have a native field that holds a pointer
to a native Namespace object. See:

runtime/bin/namespace.h

Calls from e.g. file_impl.dart are now also passed a
_Namespace object. The implementations in e.g. file.cc and
file_linux.cc then extract the namespace, and use it to compute a
file descriptor and path suitable for passing to e.g. openat().

related US-313

R=asiva@google.com, rmacnak@google.com

Review-Url: https://codereview.chromium.org/3007703002 .
2017-08-30 09:34:36 -07:00

475 lines
14 KiB
C++

// 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 "bin/directory.h"
#include <errno.h> // NOLINT
#include <sys/stat.h> // NOLINT
#include "bin/dartutils.h"
#include "bin/file.h"
#include "bin/log.h"
#include "bin/namespace.h"
#include "bin/utils.h"
#include "bin/utils_win.h"
#undef DeleteFile
#define MAX_LONG_PATH 32767
namespace dart {
namespace bin {
PathBuffer::PathBuffer() : length_(0) {
data_ = calloc(MAX_LONG_PATH + 1, sizeof(wchar_t)); // NOLINT
}
PathBuffer::~PathBuffer() {
free(data_);
}
char* PathBuffer::AsString() const {
UNREACHABLE();
return NULL;
}
wchar_t* PathBuffer::AsStringW() const {
return reinterpret_cast<wchar_t*>(data_);
}
const char* PathBuffer::AsScopedString() const {
return StringUtilsWin::WideToUtf8(AsStringW());
}
bool PathBuffer::Add(const char* name) {
Utf8ToWideScope wide_name(name);
return AddW(wide_name.wide());
}
bool PathBuffer::AddW(const wchar_t* name) {
wchar_t* data = AsStringW();
int written =
_snwprintf(data + length_, MAX_LONG_PATH - length_, L"%s", name);
data[MAX_LONG_PATH] = L'\0';
if ((written <= MAX_LONG_PATH - length_) && (written >= 0) &&
(static_cast<size_t>(written) == wcsnlen(name, MAX_LONG_PATH + 1))) {
length_ += written;
return true;
} else {
SetLastError(ERROR_BUFFER_OVERFLOW);
return false;
}
}
void PathBuffer::Reset(intptr_t new_length) {
length_ = new_length;
AsStringW()[length_] = L'\0';
}
// If link_name points to a link, IsBrokenLink will return true if link_name
// points to an invalid target.
static bool IsBrokenLink(const wchar_t* link_name) {
HANDLE handle = CreateFileW(
link_name, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
if (handle == INVALID_HANDLE_VALUE) {
return true;
} else {
CloseHandle(handle);
return false;
}
}
// A linked list structure holding a link target's unique file system ID.
// Used to detect loops in the file system when listing recursively.
struct LinkList {
DWORD volume;
DWORD id_low;
DWORD id_high;
LinkList* next;
};
// Forward declarations.
static bool DeleteRecursively(PathBuffer* path);
static ListType HandleFindFile(DirectoryListing* listing,
DirectoryListingEntry* entry,
const WIN32_FIND_DATAW& find_file_data) {
if (!listing->path_buffer().AddW(find_file_data.cFileName)) {
return kListError;
}
DWORD attributes = find_file_data.dwFileAttributes;
if ((attributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0) {
if (!listing->follow_links()) {
return kListLink;
}
HANDLE handle =
CreateFileW(listing->path_buffer().AsStringW(), 0,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
if (handle == INVALID_HANDLE_VALUE) {
// Report as (broken) link.
return kListLink;
}
if ((attributes & FILE_ATTRIBUTE_DIRECTORY) != 0) {
// Check the seen link targets to see if we are in a file system loop.
LinkList current_link;
BY_HANDLE_FILE_INFORMATION info;
// Get info
if (!GetFileInformationByHandle(handle, &info)) {
DWORD error = GetLastError();
CloseHandle(handle);
SetLastError(error);
return kListError;
}
CloseHandle(handle);
current_link.volume = info.dwVolumeSerialNumber;
current_link.id_low = info.nFileIndexLow;
current_link.id_high = info.nFileIndexHigh;
current_link.next = entry->link();
LinkList* previous = entry->link();
while (previous != NULL) {
if ((previous->volume == current_link.volume) &&
(previous->id_low == current_link.id_low) &&
(previous->id_high == current_link.id_high)) {
// Report the looping link as a link, rather than following it.
return kListLink;
}
previous = previous->next;
}
// Recurse into the directory, adding current link to the seen links list.
if ((wcscmp(find_file_data.cFileName, L".") == 0) ||
(wcscmp(find_file_data.cFileName, L"..") == 0)) {
return entry->Next(listing);
}
entry->set_link(new LinkList(current_link));
return kListDirectory;
}
}
if ((attributes & FILE_ATTRIBUTE_DIRECTORY) != 0) {
if ((wcscmp(find_file_data.cFileName, L".") == 0) ||
(wcscmp(find_file_data.cFileName, L"..") == 0)) {
return entry->Next(listing);
}
return kListDirectory;
} else {
return kListFile;
}
}
ListType DirectoryListingEntry::Next(DirectoryListing* listing) {
if (done_) {
return kListDone;
}
WIN32_FIND_DATAW find_file_data;
if (lister_ == 0) {
const wchar_t* tail = parent_ == NULL ? L"*" : L"\\*";
if (!listing->path_buffer().AddW(tail)) {
done_ = true;
return kListError;
}
path_length_ = listing->path_buffer().length() - 1;
HANDLE find_handle =
FindFirstFileW(listing->path_buffer().AsStringW(), &find_file_data);
if (find_handle == INVALID_HANDLE_VALUE) {
done_ = true;
return kListError;
}
lister_ = reinterpret_cast<intptr_t>(find_handle);
listing->path_buffer().Reset(path_length_);
return HandleFindFile(listing, this, find_file_data);
}
// Reset.
listing->path_buffer().Reset(path_length_);
ResetLink();
if (FindNextFileW(reinterpret_cast<HANDLE>(lister_), &find_file_data) != 0) {
return HandleFindFile(listing, this, find_file_data);
}
done_ = true;
if (GetLastError() != ERROR_NO_MORE_FILES) {
return kListError;
}
return kListDone;
}
DirectoryListingEntry::~DirectoryListingEntry() {
ResetLink();
if (lister_ != 0) {
FindClose(reinterpret_cast<HANDLE>(lister_));
}
}
void DirectoryListingEntry::ResetLink() {
if ((link_ != NULL) && ((parent_ == NULL) || (parent_->link_ != link_))) {
delete link_;
link_ = NULL;
}
if (parent_ != NULL) {
link_ = parent_->link_;
}
}
static bool DeleteFile(wchar_t* file_name, PathBuffer* path) {
if (!path->AddW(file_name)) {
return false;
}
if (DeleteFileW(path->AsStringW()) != 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->AsStringW());
if (attributes == INVALID_FILE_ATTRIBUTES) {
return false;
}
if ((attributes & FILE_ATTRIBUTE_READONLY) == FILE_ATTRIBUTE_READONLY) {
attributes &= ~FILE_ATTRIBUTE_READONLY;
if (SetFileAttributesW(path->AsStringW(), attributes) == 0) {
return false;
}
return DeleteFileW(path->AsStringW()) != 0;
}
}
return false;
}
static bool DeleteDir(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) {
DWORD attributes = GetFileAttributesW(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(path->AsStringW()) != 0;
}
// If it's a file, remove it directly.
if ((attributes & FILE_ATTRIBUTE_DIRECTORY) == 0) {
return DeleteFile(L"", path);
}
if (!path->AddW(L"\\*")) {
return false;
}
WIN32_FIND_DATAW find_file_data;
HANDLE find_handle = FindFirstFileW(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 = path->length() - 1;
path->Reset(path_length);
do {
if (!DeleteEntry(&find_file_data, path)) {
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 succesfully, try to delete directory.
path->Reset(path_length - 1); // Drop the "\" from the end of the path.
return RemoveDirectoryW(path->AsStringW()) != 0;
}
static Directory::ExistsResult ExistsHelper(const wchar_t* dir_name) {
DWORD attributes = GetFileAttributesW(dir_name);
if (attributes == INVALID_FILE_ATTRIBUTES) {
DWORD last_error = GetLastError();
if ((last_error == ERROR_FILE_NOT_FOUND) ||
(last_error == ERROR_PATH_NOT_FOUND)) {
return Directory::DOES_NOT_EXIST;
} else {
// We might not be able to get the file attributes for other
// reasons such as lack of permissions. In that case we do
// not know if the directory exists.
return Directory::UNKNOWN;
}
}
bool exists = (attributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
exists = exists && !IsBrokenLink(dir_name);
return exists ? Directory::EXISTS : Directory::DOES_NOT_EXIST;
}
Directory::ExistsResult Directory::Exists(Namespace* namespc,
const char* dir_name) {
Utf8ToWideScope system_name(dir_name);
return ExistsHelper(system_name.wide());
}
char* Directory::CurrentNoScope() {
int length = GetCurrentDirectoryW(0, NULL);
if (length == 0) {
return NULL;
}
wchar_t* current = new wchar_t[length + 1];
GetCurrentDirectoryW(length + 1, current);
int utf8_len =
WideCharToMultiByte(CP_UTF8, 0, current, -1, NULL, 0, NULL, NULL);
char* result = reinterpret_cast<char*>(malloc(utf8_len));
WideCharToMultiByte(CP_UTF8, 0, current, -1, result, utf8_len, NULL, NULL);
delete[] current;
return result;
}
bool Directory::Create(Namespace* namespc, const char* dir_name) {
Utf8ToWideScope system_name(dir_name);
int create_status = CreateDirectoryW(system_name.wide(), NULL);
// If the directory already existed, treat it as a success.
if ((create_status == 0) && (GetLastError() == ERROR_ALREADY_EXISTS) &&
(ExistsHelper(system_name.wide()) == EXISTS)) {
return true;
}
return (create_status != 0);
}
const char* Directory::SystemTemp(Namespace* namespc) {
PathBuffer path;
// Remove \ at end.
path.Reset(GetTempPathW(MAX_LONG_PATH, path.AsStringW()) - 1);
return path.AsScopedString();
}
const char* Directory::CreateTemp(Namespace* namespc, const char* prefix) {
// Returns a new, unused directory name, adding characters to the
// end of prefix.
// Creates this directory, with a default security
// descriptor inherited from its parent directory.
// The return value is Dart_ScopeAllocated.
PathBuffer path;
Utf8ToWideScope system_prefix(prefix);
if (!path.AddW(system_prefix.wide())) {
return NULL;
}
// Length of xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx is 36.
if (path.length() > MAX_LONG_PATH - 36) {
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;
}
// RPC_WSTR is an unsigned short*, so we cast to wchar_t*.
if (!path.AddW(reinterpret_cast<wchar_t*>(uuid_string))) {
return NULL;
}
RpcStringFreeW(&uuid_string);
if (!CreateDirectoryW(path.AsStringW(), NULL)) {
return NULL;
}
return path.AsScopedString();
}
bool Directory::Delete(Namespace* namespc,
const char* dir_name,
bool recursive) {
bool result = false;
Utf8ToWideScope system_dir_name(dir_name);
if (!recursive) {
if (File::GetType(namespc, dir_name, true) == File::kIsDirectory) {
result = (RemoveDirectoryW(system_dir_name.wide()) != 0);
} else {
SetLastError(ERROR_FILE_NOT_FOUND);
}
} else {
PathBuffer path;
if (path.AddW(system_dir_name.wide())) {
result = DeleteRecursively(&path);
}
}
return result;
}
bool Directory::Rename(Namespace* namespc,
const char* path,
const char* new_path) {
Utf8ToWideScope system_path(path);
Utf8ToWideScope system_new_path(new_path);
ExistsResult exists = ExistsHelper(system_path.wide());
if (exists != EXISTS) {
return false;
}
ExistsResult new_exists = ExistsHelper(system_new_path.wide());
// MoveFile does not allow replacing existing directories. Therefore,
// if the new_path is currently a directory we need to delete it
// first.
if (new_exists == EXISTS) {
bool success = Delete(namespc, new_path, true);
if (!success) {
return false;
}
}
DWORD flags = MOVEFILE_WRITE_THROUGH;
int move_status =
MoveFileExW(system_path.wide(), system_new_path.wide(), flags);
return (move_status != 0);
}
} // namespace bin
} // namespace dart
#endif // defined(HOST_OS_WINDOWS)