dart-sdk/runtime/bin/directory_macos.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

450 lines
13 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_MACOS)
#include "bin/directory.h"
#include <dirent.h> // NOLINT
#include <errno.h> // NOLINT
#include <string.h> // NOLINT
#include <sys/param.h> // NOLINT
#include <sys/stat.h> // NOLINT
#include <unistd.h> // NOLINT
#include "bin/dartutils.h"
#include "bin/file.h"
#include "bin/namespace.h"
#include "bin/platform.h"
#include "platform/signal_blocker.h"
namespace dart {
namespace bin {
PathBuffer::PathBuffer() : length_(0) {
data_ = calloc(PATH_MAX + 1, sizeof(char)); // NOLINT
}
PathBuffer::~PathBuffer() {
free(data_);
}
bool PathBuffer::AddW(const wchar_t* name) {
UNREACHABLE();
return false;
}
char* PathBuffer::AsString() const {
return reinterpret_cast<char*>(data_);
}
wchar_t* PathBuffer::AsStringW() const {
UNREACHABLE();
return NULL;
}
const char* PathBuffer::AsScopedString() const {
return DartUtils::ScopedCopyCString(AsString());
}
bool PathBuffer::Add(const char* name) {
char* data = AsString();
int written = snprintf(data + length_, PATH_MAX - length_, "%s", name);
data[PATH_MAX] = '\0';
if ((written <= PATH_MAX - length_) && (written >= 0) &&
(static_cast<size_t>(written) == strlen(name))) {
length_ += written;
return true;
} else {
errno = ENAMETOOLONG;
return false;
}
}
void PathBuffer::Reset(intptr_t new_length) {
length_ = new_length;
AsString()[length_] = '\0';
}
// A linked list of symbolic links, with their unique file system identifiers.
// These are scanned to detect loops while doing a recursive directory listing.
struct LinkList {
dev_t dev;
ino_t ino;
LinkList* next;
};
ListType DirectoryListingEntry::Next(DirectoryListing* listing) {
if (done_) {
return kListDone;
}
if (lister_ == 0) {
do {
lister_ = reinterpret_cast<intptr_t>(
opendir(listing->path_buffer().AsString()));
} while ((lister_ == 0) && (errno == EINTR));
if (lister_ == 0) {
done_ = true;
return kListError;
}
if (parent_ != NULL) {
if (!listing->path_buffer().Add(File::PathSeparator())) {
return kListError;
}
}
path_length_ = listing->path_buffer().length();
}
// Reset.
listing->path_buffer().Reset(path_length_);
ResetLink();
// Iterate the directory and post the directories and files to the
// ports.
int status = 0;
dirent entry;
dirent* result;
status = NO_RETRY_EXPECTED(
readdir_r(reinterpret_cast<DIR*>(lister_), &entry, &result));
if ((status == 0) && (result != NULL)) {
if (!listing->path_buffer().Add(entry.d_name)) {
done_ = true;
return kListError;
}
switch (entry.d_type) {
case DT_DIR:
if ((strcmp(entry.d_name, ".") == 0) ||
(strcmp(entry.d_name, "..") == 0)) {
return Next(listing);
}
return kListDirectory;
case DT_BLK:
case DT_CHR:
case DT_FIFO:
case DT_SOCK:
case DT_REG:
return kListFile;
case DT_LNK:
if (!listing->follow_links()) {
return kListLink;
}
// Else fall through to next case.
// Fall through.
case DT_UNKNOWN: {
// On some file systems the entry type is not determined by
// readdir_r. For those and for links we use stat to determine
// the actual entry type. Notice that stat returns the type of
// the file pointed to.
struct stat entry_info;
int stat_success;
stat_success = NO_RETRY_EXPECTED(
lstat(listing->path_buffer().AsString(), &entry_info));
if (stat_success == -1) {
return kListError;
}
if (listing->follow_links() && S_ISLNK(entry_info.st_mode)) {
// Check to see if we are in a loop created by a symbolic link.
LinkList current_link = {entry_info.st_dev, entry_info.st_ino, link_};
LinkList* previous = link_;
while (previous != NULL) {
if ((previous->dev == current_link.dev) &&
(previous->ino == current_link.ino)) {
// Report the looping link as a link, rather than following it.
return kListLink;
}
previous = previous->next;
}
stat_success = NO_RETRY_EXPECTED(
stat(listing->path_buffer().AsString(), &entry_info));
if (stat_success == -1) {
// Report a broken link as a link, even if follow_links is true.
return kListLink;
}
if (S_ISDIR(entry_info.st_mode)) {
// Recurse into the subdirectory with current_link added to the
// linked list of seen file system links.
link_ = new LinkList(current_link);
if ((strcmp(entry.d_name, ".") == 0) ||
(strcmp(entry.d_name, "..") == 0)) {
return Next(listing);
}
return kListDirectory;
}
}
if (S_ISDIR(entry_info.st_mode)) {
if ((strcmp(entry.d_name, ".") == 0) ||
(strcmp(entry.d_name, "..") == 0)) {
return Next(listing);
}
return kListDirectory;
} else if (S_ISREG(entry_info.st_mode) || S_ISCHR(entry_info.st_mode) ||
S_ISBLK(entry_info.st_mode) ||
S_ISFIFO(entry_info.st_mode) ||
S_ISSOCK(entry_info.st_mode)) {
return kListFile;
} else if (S_ISLNK(entry_info.st_mode)) {
return kListLink;
} else {
FATAL1("Unexpected st_mode: %d\n", entry_info.st_mode);
return kListError;
}
}
default:
// We should have covered all the bases. If not, let's get an error.
FATAL1("Unexpected d_type: %d\n", entry.d_type);
return kListError;
}
}
done_ = true;
if (status != 0) {
errno = status;
return kListError;
}
return kListDone;
}
DirectoryListingEntry::~DirectoryListingEntry() {
ResetLink();
if (lister_ != 0) {
closedir(reinterpret_cast<DIR*>(lister_));
}
}
void DirectoryListingEntry::ResetLink() {
if ((link_ != NULL) && ((parent_ == NULL) || (parent_->link_ != link_))) {
delete link_;
link_ = NULL;
}
if (parent_ != NULL) {
link_ = parent_->link_;
}
}
static bool DeleteRecursively(PathBuffer* path);
static bool DeleteFile(char* file_name, PathBuffer* path) {
return path->Add(file_name) && (unlink(path->AsString()) == 0);
}
static bool DeleteDir(char* dir_name, PathBuffer* path) {
if ((strcmp(dir_name, ".") == 0) || (strcmp(dir_name, "..") == 0)) {
return true;
}
return path->Add(dir_name) && DeleteRecursively(path);
}
static bool DeleteRecursively(PathBuffer* path) {
// Do not recurse into links for deletion. Instead delete the link.
// If it's a file, delete it.
struct stat st;
if (NO_RETRY_EXPECTED(lstat(path->AsString(), &st)) == -1) {
return false;
} else if (!S_ISDIR(st.st_mode)) {
return (unlink(path->AsString()) == 0);
}
if (!path->Add(File::PathSeparator())) {
return false;
}
// Not a link. Attempt to open as a directory and recurse into the
// directory.
DIR* dir_pointer;
do {
dir_pointer = opendir(path->AsString());
} while ((dir_pointer == NULL) && (errno == EINTR));
if (dir_pointer == NULL) {
return false;
}
// Iterate the directory and delete all files and directories.
int path_length = path->length();
dirent entry;
dirent* result;
while (NO_RETRY_EXPECTED(readdir_r(dir_pointer, &entry, &result)) == 0) {
if (result == NULL) {
// End of directory.
return (NO_RETRY_EXPECTED(closedir(dir_pointer)) == 0) &&
(NO_RETRY_EXPECTED(remove(path->AsString())) == 0);
}
bool ok = false;
switch (entry.d_type) {
case DT_DIR:
ok = DeleteDir(entry.d_name, path);
break;
case DT_BLK:
case DT_CHR:
case DT_FIFO:
case DT_SOCK:
case DT_REG:
case DT_LNK:
// Treat all links as files. This will delete the link which
// is what we want no matter if the link target is a file or a
// directory.
ok = DeleteFile(entry.d_name, path);
break;
case DT_UNKNOWN: {
if (!path->Add(entry.d_name)) {
break;
}
// On some file systems the entry type is not determined by
// readdir_r. For those we use lstat to determine the entry
// type.
struct stat entry_info;
if (NO_RETRY_EXPECTED(lstat(path->AsString(), &entry_info)) == -1) {
break;
}
path->Reset(path_length);
if (S_ISDIR(entry_info.st_mode)) {
ok = DeleteDir(entry.d_name, path);
} else {
// Treat links as files. This will delete the link which is
// what we want no matter if the link target is a file or a
// directory.
ok = DeleteFile(entry.d_name, path);
}
break;
}
default:
// We should have covered all the bases. If not, let's get an error.
FATAL1("Unexpected d_type: %d\n", entry.d_type);
break;
}
if (!ok) {
break;
}
path->Reset(path_length);
}
// Only happens if an error.
ASSERT(errno != 0);
int err = errno;
VOID_NO_RETRY_EXPECTED(closedir(dir_pointer));
errno = err;
return false;
}
Directory::ExistsResult Directory::Exists(Namespace* namespc,
const char* dir_name) {
struct stat entry_info;
int success = NO_RETRY_EXPECTED(stat(dir_name, &entry_info));
if (success == 0) {
if (S_ISDIR(entry_info.st_mode)) {
return EXISTS;
} else {
// An OSError may be constructed based on the return value of this
// function, so set errno to something that makes sense.
errno = ENOTDIR;
return DOES_NOT_EXIST;
}
} else {
if ((errno == EACCES) || (errno == EBADF) || (errno == EFAULT) ||
(errno == ENOMEM) || (errno == EOVERFLOW)) {
// Search permissions denied for one of the directories in the
// path or a low level error occured. We do not know if the
// directory exists.
return UNKNOWN;
}
ASSERT((errno == ELOOP) || (errno == ENAMETOOLONG) || (errno == ENOENT) ||
(errno == ENOTDIR));
return DOES_NOT_EXIST;
}
}
char* Directory::CurrentNoScope() {
return getcwd(NULL, 0);
}
bool Directory::Create(Namespace* namespc, const char* dir_name) {
// Create the directory with the permissions specified by the
// process umask.
int result = NO_RETRY_EXPECTED(mkdir(dir_name, 0777));
// If the directory already exists, treat it as a success.
if ((result == -1) && (errno == EEXIST)) {
return (Exists(namespc, dir_name) == EXISTS);
}
return (result == 0);
}
const char* Directory::SystemTemp(Namespace* namespc) {
PathBuffer path;
const char* temp_dir = getenv("TMPDIR");
if (temp_dir == NULL) {
temp_dir = getenv("TMP");
}
if (temp_dir == NULL) {
temp_dir = "/tmp";
}
if (!path.Add(temp_dir)) {
return NULL;
}
// Remove any trailing slash.
char* result = path.AsString();
int length = strlen(result);
if ((length > 1) && (result[length - 1] == '/')) {
result[length - 1] = '\0';
}
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 the directory with the permissions specified
// by the process umask.
// The return value is Dart_ScopeAllocated.
PathBuffer path;
if (!path.Add(prefix)) {
return NULL;
}
if (!path.Add("XXXXXX")) {
// Pattern has overflowed.
return NULL;
}
char* result;
do {
result = mkdtemp(path.AsString());
} while ((result == NULL) && (errno == EINTR));
if (result == NULL) {
return NULL;
}
return path.AsScopedString();
}
bool Directory::Delete(Namespace* namespc,
const char* dir_name,
bool recursive) {
if (!recursive) {
if ((File::GetType(namespc, dir_name, false) == File::kIsLink) &&
(File::GetType(namespc, dir_name, true) == File::kIsDirectory)) {
return (NO_RETRY_EXPECTED(unlink(dir_name)) == 0);
}
return (NO_RETRY_EXPECTED(rmdir(dir_name)) == 0);
} else {
PathBuffer path;
if (!path.Add(dir_name)) {
return false;
}
return DeleteRecursively(&path);
}
}
bool Directory::Rename(Namespace* namespc,
const char* path,
const char* new_path) {
ExistsResult exists = Exists(namespc, path);
if (exists != EXISTS) {
return false;
}
return (NO_RETRY_EXPECTED(rename(path, new_path)) == 0);
}
} // namespace bin
} // namespace dart
#endif // defined(HOST_OS_MACOS)