8c9c54d833
If an Isolate touches the 'stdio' getter, a _NativeSocket with attached finalizer is created for it. Previously, when such an Isolate exited, the finalizer would close the underlying file descriptor. This CL changes the finalizer for stdin such that the native objects will be cleaned up, but the underlying file descriptor will not be closed. The underlying file descriptor will now only be closed if the stdin stream subscription is explicitly canceled. Accessing the stdin getter after the stream is explicitly canceled will result in a FileSystemException. See also: https://github.com/dart-lang/test/issues/583 fixes #29229 R=rmacnak@google.com Review-Url: https://codereview.chromium.org/2791423002 .
616 lines
15 KiB
C++
616 lines
15 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_LINUX)
|
|
|
|
#include "bin/file.h"
|
|
|
|
#include <errno.h> // NOLINT
|
|
#include <fcntl.h> // NOLINT
|
|
#include <libgen.h> // NOLINT
|
|
#include <sys/mman.h> // NOLINT
|
|
#include <sys/sendfile.h> // NOLINT
|
|
#include <sys/stat.h> // NOLINT
|
|
#include <sys/types.h> // NOLINT
|
|
#include <unistd.h> // NOLINT
|
|
#include <utime.h> // NOLINT
|
|
|
|
#include "bin/builtin.h"
|
|
#include "bin/fdutils.h"
|
|
#include "bin/log.h"
|
|
#include "platform/signal_blocker.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() != STDOUT_FILENO &&
|
|
handle_->fd() != STDERR_FILENO) {
|
|
Close();
|
|
}
|
|
delete handle_;
|
|
}
|
|
|
|
|
|
void File::Close() {
|
|
ASSERT(handle_->fd() >= 0);
|
|
if (handle_->fd() == STDOUT_FILENO) {
|
|
// If stdout, redirect fd to /dev/null.
|
|
int null_fd = TEMP_FAILURE_RETRY(open("/dev/null", O_WRONLY));
|
|
ASSERT(null_fd >= 0);
|
|
VOID_TEMP_FAILURE_RETRY(dup2(null_fd, handle_->fd()));
|
|
VOID_TEMP_FAILURE_RETRY(close(null_fd));
|
|
} else {
|
|
int err = TEMP_FAILURE_RETRY(close(handle_->fd()));
|
|
if (err != 0) {
|
|
const int kBufferSize = 1024;
|
|
char error_buf[kBufferSize];
|
|
Log::PrintErr("%s\n", Utils::StrError(errno, error_buf, kBufferSize));
|
|
}
|
|
}
|
|
handle_->set_fd(kClosedFd);
|
|
}
|
|
|
|
|
|
intptr_t File::GetFD() {
|
|
return handle_->fd();
|
|
}
|
|
|
|
|
|
bool File::IsClosed() {
|
|
return handle_->fd() == kClosedFd;
|
|
}
|
|
|
|
|
|
MappedMemory* File::Map(MapType type, int64_t position, int64_t length) {
|
|
ASSERT(handle_->fd() >= 0);
|
|
int prot = PROT_NONE;
|
|
switch (type) {
|
|
case kReadOnly:
|
|
prot = PROT_READ;
|
|
break;
|
|
case kReadExecute:
|
|
prot = PROT_READ | PROT_EXEC;
|
|
break;
|
|
default:
|
|
return NULL;
|
|
}
|
|
void* addr = mmap(NULL, length, prot, MAP_PRIVATE, handle_->fd(), position);
|
|
if (addr == MAP_FAILED) {
|
|
return NULL;
|
|
}
|
|
return new MappedMemory(addr, length);
|
|
}
|
|
|
|
|
|
void MappedMemory::Unmap() {
|
|
int result = munmap(address_, size_);
|
|
ASSERT(result == 0);
|
|
address_ = 0;
|
|
size_ = 0;
|
|
}
|
|
|
|
|
|
int64_t File::Read(void* buffer, int64_t num_bytes) {
|
|
ASSERT(handle_->fd() >= 0);
|
|
return TEMP_FAILURE_RETRY(read(handle_->fd(), buffer, num_bytes));
|
|
}
|
|
|
|
|
|
int64_t File::Write(const void* buffer, int64_t num_bytes) {
|
|
ASSERT(handle_->fd() >= 0);
|
|
return TEMP_FAILURE_RETRY(write(handle_->fd(), buffer, num_bytes));
|
|
}
|
|
|
|
|
|
bool File::VPrint(const char* format, va_list args) {
|
|
// Measure.
|
|
va_list measure_args;
|
|
va_copy(measure_args, args);
|
|
intptr_t len = vsnprintf(NULL, 0, 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 NO_RETRY_EXPECTED(lseek64(handle_->fd(), 0, SEEK_CUR));
|
|
}
|
|
|
|
|
|
bool File::SetPosition(int64_t position) {
|
|
ASSERT(handle_->fd() >= 0);
|
|
return NO_RETRY_EXPECTED(lseek64(handle_->fd(), position, SEEK_SET)) >= 0;
|
|
}
|
|
|
|
|
|
bool File::Truncate(int64_t length) {
|
|
ASSERT(handle_->fd() >= 0);
|
|
return TEMP_FAILURE_RETRY(ftruncate64(handle_->fd(), length) != -1);
|
|
}
|
|
|
|
|
|
bool File::Flush() {
|
|
ASSERT(handle_->fd() >= 0);
|
|
return NO_RETRY_EXPECTED(fsync(handle_->fd())) != -1;
|
|
}
|
|
|
|
|
|
bool File::Lock(File::LockType lock, int64_t start, int64_t end) {
|
|
ASSERT(handle_->fd() >= 0);
|
|
ASSERT((end == -1) || (end > start));
|
|
struct flock fl;
|
|
switch (lock) {
|
|
case File::kLockUnlock:
|
|
fl.l_type = F_UNLCK;
|
|
break;
|
|
case File::kLockShared:
|
|
case File::kLockBlockingShared:
|
|
fl.l_type = F_RDLCK;
|
|
break;
|
|
case File::kLockExclusive:
|
|
case File::kLockBlockingExclusive:
|
|
fl.l_type = F_WRLCK;
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
fl.l_whence = SEEK_SET;
|
|
fl.l_start = start;
|
|
fl.l_len = end == -1 ? 0 : end - start;
|
|
int cmd = F_SETLK;
|
|
if ((lock == File::kLockBlockingShared) ||
|
|
(lock == File::kLockBlockingExclusive)) {
|
|
cmd = F_SETLKW;
|
|
}
|
|
return TEMP_FAILURE_RETRY(fcntl(handle_->fd(), cmd, &fl)) != -1;
|
|
}
|
|
|
|
|
|
int64_t File::Length() {
|
|
ASSERT(handle_->fd() >= 0);
|
|
struct stat64 st;
|
|
if (TEMP_FAILURE_RETRY(fstat64(handle_->fd(), &st)) == 0) {
|
|
return st.st_size;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
|
|
File* File::FileOpenW(const wchar_t* system_name, FileOpenMode mode) {
|
|
UNREACHABLE();
|
|
return NULL;
|
|
}
|
|
|
|
|
|
File* File::Open(const char* name, FileOpenMode mode) {
|
|
// Report errors for non-regular files.
|
|
struct stat64 st;
|
|
if (TEMP_FAILURE_RETRY(stat64(name, &st)) == 0) {
|
|
// Only accept regular files, character devices, and pipes.
|
|
if (!S_ISREG(st.st_mode) && !S_ISCHR(st.st_mode) && !S_ISFIFO(st.st_mode)) {
|
|
errno = (S_ISDIR(st.st_mode)) ? EISDIR : ENOENT;
|
|
return NULL;
|
|
}
|
|
}
|
|
int flags = O_RDONLY;
|
|
if ((mode & kWrite) != 0) {
|
|
ASSERT((mode & kWriteOnly) == 0);
|
|
flags = (O_RDWR | O_CREAT);
|
|
}
|
|
if ((mode & kWriteOnly) != 0) {
|
|
ASSERT((mode & kWrite) == 0);
|
|
flags = (O_WRONLY | O_CREAT);
|
|
}
|
|
if ((mode & kTruncate) != 0) {
|
|
flags = flags | O_TRUNC;
|
|
}
|
|
flags |= O_CLOEXEC;
|
|
int fd = TEMP_FAILURE_RETRY(open64(name, flags, 0666));
|
|
if (fd < 0) {
|
|
return NULL;
|
|
}
|
|
if ((((mode & kWrite) != 0) && ((mode & kTruncate) == 0)) ||
|
|
(((mode & kWriteOnly) != 0) && ((mode & kTruncate) == 0))) {
|
|
int64_t position = NO_RETRY_EXPECTED(lseek64(fd, 0, SEEK_END));
|
|
if (position < 0) {
|
|
return NULL;
|
|
}
|
|
}
|
|
return new File(new FileHandle(fd));
|
|
}
|
|
|
|
|
|
File* File::OpenStdio(int fd) {
|
|
return ((fd < 0) || (2 < fd)) ? NULL : new File(new FileHandle(fd));
|
|
}
|
|
|
|
|
|
bool File::Exists(const char* name) {
|
|
struct stat64 st;
|
|
if (TEMP_FAILURE_RETRY(stat64(name, &st)) == 0) {
|
|
// Everything but a directory and a link is a file to Dart.
|
|
return !S_ISDIR(st.st_mode) && !S_ISLNK(st.st_mode);
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
bool File::Create(const char* name) {
|
|
int fd =
|
|
TEMP_FAILURE_RETRY(open64(name, O_RDONLY | O_CREAT | O_CLOEXEC, 0666));
|
|
if (fd < 0) {
|
|
return false;
|
|
}
|
|
// File.create returns a File, so we shouldn't be giving the illusion that the
|
|
// call has created a file or that a file already exists if there is already
|
|
// an entity at the same path that is a directory or a link.
|
|
bool is_file = true;
|
|
struct stat64 st;
|
|
if (TEMP_FAILURE_RETRY(fstat64(fd, &st)) == 0) {
|
|
if (S_ISDIR(st.st_mode)) {
|
|
errno = EISDIR;
|
|
is_file = false;
|
|
} else if (S_ISLNK(st.st_mode)) {
|
|
errno = ENOENT;
|
|
is_file = false;
|
|
}
|
|
}
|
|
FDUtils::SaveErrorAndClose(fd);
|
|
return is_file;
|
|
}
|
|
|
|
|
|
bool File::CreateLink(const char* name, const char* target) {
|
|
return NO_RETRY_EXPECTED(symlink(target, name)) == 0;
|
|
}
|
|
|
|
|
|
File::Type File::GetType(const char* pathname, bool follow_links) {
|
|
struct stat64 entry_info;
|
|
int stat_success;
|
|
if (follow_links) {
|
|
stat_success = TEMP_FAILURE_RETRY(stat64(pathname, &entry_info));
|
|
} else {
|
|
stat_success = TEMP_FAILURE_RETRY(lstat64(pathname, &entry_info));
|
|
}
|
|
if (stat_success == -1) {
|
|
return File::kDoesNotExist;
|
|
}
|
|
if (S_ISDIR(entry_info.st_mode)) {
|
|
return File::kIsDirectory;
|
|
}
|
|
if (S_ISREG(entry_info.st_mode)) {
|
|
return File::kIsFile;
|
|
}
|
|
if (S_ISLNK(entry_info.st_mode)) {
|
|
return File::kIsLink;
|
|
}
|
|
return File::kDoesNotExist;
|
|
}
|
|
|
|
|
|
static bool CheckTypeAndSetErrno(const char* name,
|
|
File::Type expected,
|
|
bool follow_links) {
|
|
File::Type actual = File::GetType(name, follow_links);
|
|
if (actual == expected) {
|
|
return true;
|
|
}
|
|
switch (actual) {
|
|
case File::kIsDirectory:
|
|
errno = EISDIR;
|
|
break;
|
|
case File::kDoesNotExist:
|
|
errno = ENOENT;
|
|
break;
|
|
default:
|
|
errno = EINVAL;
|
|
break;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
bool File::Delete(const char* name) {
|
|
return CheckTypeAndSetErrno(name, kIsFile, true) &&
|
|
(NO_RETRY_EXPECTED(unlink(name)) == 0);
|
|
}
|
|
|
|
|
|
bool File::DeleteLink(const char* name) {
|
|
return CheckTypeAndSetErrno(name, kIsLink, false) &&
|
|
(NO_RETRY_EXPECTED(unlink(name)) == 0);
|
|
}
|
|
|
|
|
|
bool File::Rename(const char* old_path, const char* new_path) {
|
|
return CheckTypeAndSetErrno(old_path, kIsFile, true) &&
|
|
(NO_RETRY_EXPECTED(rename(old_path, new_path)) == 0);
|
|
}
|
|
|
|
|
|
bool File::RenameLink(const char* old_path, const char* new_path) {
|
|
return CheckTypeAndSetErrno(old_path, kIsLink, false) &&
|
|
(NO_RETRY_EXPECTED(rename(old_path, new_path)) == 0);
|
|
}
|
|
|
|
|
|
bool File::Copy(const char* old_path, const char* new_path) {
|
|
if (!CheckTypeAndSetErrno(old_path, kIsFile, true)) {
|
|
return false;
|
|
}
|
|
struct stat64 st;
|
|
if (TEMP_FAILURE_RETRY(stat64(old_path, &st)) != 0) {
|
|
return false;
|
|
}
|
|
int old_fd = TEMP_FAILURE_RETRY(open64(old_path, O_RDONLY | O_CLOEXEC));
|
|
if (old_fd < 0) {
|
|
return false;
|
|
}
|
|
int new_fd = TEMP_FAILURE_RETRY(
|
|
open64(new_path, O_WRONLY | O_TRUNC | O_CREAT | O_CLOEXEC, st.st_mode));
|
|
if (new_fd < 0) {
|
|
VOID_TEMP_FAILURE_RETRY(close(old_fd));
|
|
return false;
|
|
}
|
|
int64_t offset = 0;
|
|
intptr_t result = 1;
|
|
while (result > 0) {
|
|
// Loop to ensure we copy everything, and not only up to 2GB.
|
|
result = NO_RETRY_EXPECTED(sendfile64(new_fd, old_fd, &offset, kMaxUint32));
|
|
}
|
|
// From sendfile man pages:
|
|
// Applications may wish to fall back to read(2)/write(2) in the case
|
|
// where sendfile() fails with EINVAL or ENOSYS.
|
|
if ((result < 0) && ((errno == EINVAL) || (errno == ENOSYS))) {
|
|
const intptr_t kBufferSize = 8 * KB;
|
|
uint8_t buffer[kBufferSize];
|
|
while ((result = TEMP_FAILURE_RETRY(read(old_fd, buffer, kBufferSize))) >
|
|
0) {
|
|
int wrote = TEMP_FAILURE_RETRY(write(new_fd, buffer, result));
|
|
if (wrote != result) {
|
|
result = -1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
int e = errno;
|
|
VOID_TEMP_FAILURE_RETRY(close(old_fd));
|
|
VOID_TEMP_FAILURE_RETRY(close(new_fd));
|
|
if (result < 0) {
|
|
VOID_NO_RETRY_EXPECTED(unlink(new_path));
|
|
errno = e;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
static bool StatHelper(const char* name, struct stat64* st) {
|
|
if (TEMP_FAILURE_RETRY(stat64(name, st)) != 0) {
|
|
return false;
|
|
}
|
|
// Signal an error if it's a directory.
|
|
if (S_ISDIR(st->st_mode)) {
|
|
errno = EISDIR;
|
|
return false;
|
|
}
|
|
// Otherwise assume the caller knows what it's doing.
|
|
return true;
|
|
}
|
|
|
|
|
|
int64_t File::LengthFromPath(const char* name) {
|
|
struct stat64 st;
|
|
if (!StatHelper(name, &st)) {
|
|
return -1;
|
|
}
|
|
return st.st_size;
|
|
}
|
|
|
|
|
|
static int64_t TimespecToMilliseconds(const struct timespec& t) {
|
|
return static_cast<int64_t>(t.tv_sec) * 1000L +
|
|
static_cast<int64_t>(t.tv_nsec) / 1000000L;
|
|
}
|
|
|
|
|
|
void File::Stat(const char* name, int64_t* data) {
|
|
struct stat64 st;
|
|
if (TEMP_FAILURE_RETRY(stat64(name, &st)) == 0) {
|
|
if (S_ISREG(st.st_mode)) {
|
|
data[kType] = kIsFile;
|
|
} else if (S_ISDIR(st.st_mode)) {
|
|
data[kType] = kIsDirectory;
|
|
} else if (S_ISLNK(st.st_mode)) {
|
|
data[kType] = kIsLink;
|
|
} else {
|
|
data[kType] = kDoesNotExist;
|
|
}
|
|
data[kCreatedTime] = TimespecToMilliseconds(st.st_ctim);
|
|
data[kModifiedTime] = TimespecToMilliseconds(st.st_mtim);
|
|
data[kAccessedTime] = TimespecToMilliseconds(st.st_atim);
|
|
data[kMode] = st.st_mode;
|
|
data[kSize] = st.st_size;
|
|
} else {
|
|
data[kType] = kDoesNotExist;
|
|
}
|
|
}
|
|
|
|
|
|
time_t File::LastModified(const char* name) {
|
|
struct stat64 st;
|
|
if (!StatHelper(name, &st)) {
|
|
return -1;
|
|
}
|
|
return st.st_mtime;
|
|
}
|
|
|
|
|
|
time_t File::LastAccessed(const char* name) {
|
|
struct stat64 st;
|
|
if (!StatHelper(name, &st)) {
|
|
return -1;
|
|
}
|
|
return st.st_atime;
|
|
}
|
|
|
|
|
|
bool File::SetLastAccessed(const char* name, int64_t millis) {
|
|
// First get the current times.
|
|
struct stat64 st;
|
|
if (!StatHelper(name, &st)) {
|
|
return false;
|
|
}
|
|
|
|
// Set the new time:
|
|
struct utimbuf times;
|
|
times.actime = millis / kMillisecondsPerSecond;
|
|
times.modtime = st.st_mtime;
|
|
return utime(name, ×) == 0;
|
|
}
|
|
|
|
|
|
bool File::SetLastModified(const char* name, int64_t millis) {
|
|
// First get the current times.
|
|
struct stat64 st;
|
|
if (!StatHelper(name, &st)) {
|
|
return false;
|
|
}
|
|
|
|
// Set the new time:
|
|
struct utimbuf times;
|
|
times.actime = st.st_atime;
|
|
times.modtime = millis / kMillisecondsPerSecond;
|
|
return utime(name, ×) == 0;
|
|
}
|
|
|
|
|
|
const char* File::LinkTarget(const char* pathname) {
|
|
struct stat64 link_stats;
|
|
if (TEMP_FAILURE_RETRY(lstat64(pathname, &link_stats)) != 0) {
|
|
return NULL;
|
|
}
|
|
if (!S_ISLNK(link_stats.st_mode)) {
|
|
errno = ENOENT;
|
|
return NULL;
|
|
}
|
|
// Don't rely on the link_stats.st_size for the size of the link
|
|
// target. For some filesystems, e.g. procfs, this value is always
|
|
// 0. Also the link might have changed before the readlink call.
|
|
const int kBufferSize = PATH_MAX + 1;
|
|
char target[kBufferSize];
|
|
size_t target_size =
|
|
TEMP_FAILURE_RETRY(readlink(pathname, target, kBufferSize));
|
|
if (target_size <= 0) {
|
|
return NULL;
|
|
}
|
|
char* target_name = DartUtils::ScopedCString(target_size + 1);
|
|
ASSERT(target_name != NULL);
|
|
memmove(target_name, target, target_size);
|
|
target_name[target_size] = '\0';
|
|
return target_name;
|
|
}
|
|
|
|
|
|
bool File::IsAbsolutePath(const char* pathname) {
|
|
return (pathname != NULL && pathname[0] == '/');
|
|
}
|
|
|
|
|
|
const char* File::GetCanonicalPath(const char* pathname) {
|
|
char* abs_path = NULL;
|
|
if (pathname != NULL) {
|
|
char* resolved_path = DartUtils::ScopedCString(PATH_MAX + 1);
|
|
ASSERT(resolved_path != NULL);
|
|
do {
|
|
abs_path = realpath(pathname, resolved_path);
|
|
} while (abs_path == NULL && errno == EINTR);
|
|
ASSERT(abs_path == NULL || IsAbsolutePath(abs_path));
|
|
ASSERT(abs_path == NULL || (abs_path == resolved_path));
|
|
}
|
|
return abs_path;
|
|
}
|
|
|
|
|
|
const char* File::PathSeparator() {
|
|
return "/";
|
|
}
|
|
|
|
|
|
const char* File::StringEscapedPathSeparator() {
|
|
return "/";
|
|
}
|
|
|
|
|
|
File::StdioHandleType File::GetStdioHandleType(int fd) {
|
|
ASSERT((0 <= fd) && (fd <= 2));
|
|
struct stat64 buf;
|
|
int result = TEMP_FAILURE_RETRY(fstat64(fd, &buf));
|
|
if (result == -1) {
|
|
return kOther;
|
|
}
|
|
if (S_ISCHR(buf.st_mode)) {
|
|
return kTerminal;
|
|
}
|
|
if (S_ISFIFO(buf.st_mode)) {
|
|
return kPipe;
|
|
}
|
|
if (S_ISSOCK(buf.st_mode)) {
|
|
return kSocket;
|
|
}
|
|
if (S_ISREG(buf.st_mode)) {
|
|
return kFile;
|
|
}
|
|
return kOther;
|
|
}
|
|
|
|
|
|
File::Identical File::AreIdentical(const char* file_1, const char* file_2) {
|
|
struct stat64 file_1_info;
|
|
struct stat64 file_2_info;
|
|
if ((TEMP_FAILURE_RETRY(lstat64(file_1, &file_1_info)) == -1) ||
|
|
(TEMP_FAILURE_RETRY(lstat64(file_2, &file_2_info)) == -1)) {
|
|
return File::kError;
|
|
}
|
|
return ((file_1_info.st_ino == file_2_info.st_ino) &&
|
|
(file_1_info.st_dev == file_2_info.st_dev))
|
|
? File::kIdentical
|
|
: File::kDifferent;
|
|
}
|
|
|
|
} // namespace bin
|
|
} // namespace dart
|
|
|
|
#endif // defined(HOST_OS_LINUX)
|