dart-sdk/runtime/bin/file_fuchsia.cc
asiva e8290f7f5a [VM/io] - Reland : Set correct file type for files backing unix domain sockets
The file type of file backing unix domain sockets was being incorrectly
set as kDoesNotExist resulting in errors when operations like delete
on the file was done. File::Exists on the other hand returned true.
File rename and copyfile functionality have been fixed too.

TEST=new tests added

Please see https://github.com/dart-lang/sdk/issues/48569 for the original issue.

Change-Id: I28505236fbad2ea86ad65065e753b13fb7ff655a
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/240300
Reviewed-by: Ryan Macnak <rmacnak@google.com>
Commit-Queue: Siva Annamalai <asiva@google.com>
2022-05-15 00:40:07 +00:00

732 lines
20 KiB
C++

// Copyright (c) 2016, 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(DART_HOST_OS_FUCHSIA)
#include "bin/file.h"
#include <errno.h> // NOLINT
#include <fcntl.h> // NOLINT
#include <lib/fdio/fdio.h> // NOLINT
#include <lib/fdio/namespace.h> // NOLINT
#include <libgen.h> // NOLINT
#include <sys/mman.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/namespace.h"
#include "platform/signal_blocker.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() != 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 Fuchsia's equivalent of /dev/null.
auto* null_fdio = fdio_null_create();
ASSERT(null_fdio != nullptr);
int null_fd = NO_RETRY_EXPECTED(fdio_bind_to_fd(null_fdio, -1, 0));
ASSERT(null_fd >= 0);
VOID_NO_RETRY_EXPECTED(dup2(null_fd, handle_->fd()));
VOID_NO_RETRY_EXPECTED(close(null_fd));
} else {
int err = NO_RETRY_EXPECTED(close(handle_->fd()));
if (err != 0) {
const int kBufferSize = 1024;
char error_buf[kBufferSize];
Syslog::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,
void* start) {
ASSERT(handle_->fd() >= 0);
ASSERT(length > 0);
void* hint = nullptr;
int prot = PROT_NONE;
int flags = MAP_PRIVATE;
switch (type) {
case kReadOnly:
prot = PROT_READ;
break;
case kReadExecute:
// Try to allocate near the VM's binary.
hint = reinterpret_cast<void*>(&Dart_Initialize);
prot = PROT_READ | PROT_EXEC;
break;
case kReadWrite:
prot = PROT_READ | PROT_WRITE;
break;
}
if (start != nullptr) {
hint = start;
flags |= MAP_FIXED;
}
void* addr = mmap(hint, length, prot, flags, handle_->fd(), position);
if (addr == MAP_FAILED) {
return NULL;
}
return new MappedMemory(addr, length, /*should_unmap=*/start == nullptr);
}
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 NO_RETRY_EXPECTED(read(handle_->fd(), buffer, num_bytes));
}
int64_t File::Write(const void* buffer, int64_t num_bytes) {
ASSERT(handle_->fd() >= 0);
return NO_RETRY_EXPECTED(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(lseek(handle_->fd(), 0, SEEK_CUR));
}
bool File::SetPosition(int64_t position) {
ASSERT(handle_->fd() >= 0);
return NO_RETRY_EXPECTED(lseek(handle_->fd(), position, SEEK_SET)) >= 0;
}
bool File::Truncate(int64_t length) {
ASSERT(handle_->fd() >= 0);
return NO_RETRY_EXPECTED(ftruncate(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 NO_RETRY_EXPECTED(fcntl(handle_->fd(), cmd, &fl)) != -1;
}
int64_t File::Length() {
ASSERT(handle_->fd() >= 0);
struct stat st;
if (NO_RETRY_EXPECTED(fstat(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::OpenFD(int fd) {
return new File(new FileHandle(fd));
}
File* File::Open(Namespace* namespc, const char* name, FileOpenMode mode) {
NamespaceScope ns(namespc, name);
// Report errors for non-regular files.
struct stat st;
if (NO_RETRY_EXPECTED(fstatat(ns.fd(), ns.path(), &st, 0)) == 0) {
if (S_ISDIR(st.st_mode)) {
errno = EISDIR;
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 = NO_RETRY_EXPECTED(openat(ns.fd(), ns.path(), flags, 0666));
if (fd < 0) {
return NULL;
}
if ((((mode & kWrite) != 0) && ((mode & kTruncate) == 0)) ||
(((mode & kWriteOnly) != 0) && ((mode & kTruncate) == 0))) {
int64_t position = lseek(fd, 0, SEEK_END);
if (position < 0) {
return NULL;
}
}
return OpenFD(fd);
}
Utils::CStringUniquePtr File::UriToPath(const char* uri) {
const char* path = (strlen(uri) >= 8 && strncmp(uri, "file:///", 8) == 0)
? uri + 7 : uri;
UriDecoder uri_decoder(path);
if (uri_decoder.decoded() == nullptr) {
errno = EINVAL;
return Utils::CreateCStringUniquePtr(nullptr);
}
return Utils::CreateCStringUniquePtr(strdup(uri_decoder.decoded()));
}
File* File::OpenUri(Namespace* namespc, const char* uri, FileOpenMode mode) {
auto path = UriToPath(uri);
if (path == nullptr) {
return nullptr;
}
return File::Open(namespc, path.get(), mode);
}
File* File::OpenStdio(int fd) {
return new File(new FileHandle(fd));
}
bool File::Exists(Namespace* namespc, const char* name) {
NamespaceScope ns(namespc, name);
struct stat st;
if (NO_RETRY_EXPECTED(fstatat(ns.fd(), ns.path(), &st, 0)) == 0) {
// Everything but a directory and a link is a file to Dart.
return !S_ISDIR(st.st_mode) && !S_ISLNK(st.st_mode);
}
return false;
}
bool File::ExistsUri(Namespace* namespc, const char* uri) {
auto path = UriToPath(uri);
if (path == nullptr) {
return false;
}
return File::Exists(namespc, path.get());
}
bool File::Create(Namespace* namespc, const char* name) {
NamespaceScope ns(namespc, name);
const int fd = NO_RETRY_EXPECTED(
openat(ns.fd(), ns.path(), O_RDONLY | O_CREAT | O_CLOEXEC, 0666));
if (fd < 0) {
Syslog::PrintErr("File::Create() openat(%ld, %s) failed: %s\n", ns.fd(),
ns.path(), strerror(errno));
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 = false;
struct stat st;
if (NO_RETRY_EXPECTED(fstat(fd, &st)) == 0) {
is_file = true;
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(Namespace* namespc,
const char* name,
const char* target) {
NamespaceScope ns(namespc, name);
return NO_RETRY_EXPECTED(symlinkat(target, ns.fd(), ns.path())) == 0;
}
File::Type File::GetType(Namespace* namespc,
const char* name,
bool follow_links) {
NamespaceScope ns(namespc, name);
struct stat entry_info;
int stat_success;
if (follow_links) {
stat_success =
TEMP_FAILURE_RETRY(fstatat(ns.fd(), ns.path(), &entry_info, 0));
} else {
stat_success = TEMP_FAILURE_RETRY(
fstatat(ns.fd(), ns.path(), &entry_info, AT_SYMLINK_NOFOLLOW));
}
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;
}
if (S_ISSOCK(entry_info.st_mode)) {
return File::kIsSock;
}
if (S_ISFIFO(entry_info.st_mode)) {
return File::kIsPipe;
}
return File::kDoesNotExist;
}
static void SetErrno(File::Type type) {
switch (type) {
case File::kIsDirectory:
errno = EISDIR;
break;
case File::kDoesNotExist:
errno = ENOENT;
break;
default:
errno = EINVAL;
break;
}
}
static bool CheckTypeAndSetErrno(Namespace* namespc,
const char* name,
File::Type expected,
bool follow_links) {
File::Type actual = File::GetType(namespc, name, follow_links);
if (actual == expected) {
return true;
}
SetErrno(actual);
return false;
}
bool File::Delete(Namespace* namespc, const char* name) {
File::Type type = File::GetType(namespc, name, true);
if (type == kIsFile || type == kIsSock || type == kIsPipe) {
NamespaceScope ns(namespc, name);
return (NO_RETRY_EXPECTED(unlinkat(ns.fd(), ns.path(), 0)) == 0);
}
SetErrno(type);
return false;
}
bool File::DeleteLink(Namespace* namespc, const char* name) {
NamespaceScope ns(namespc, name);
return CheckTypeAndSetErrno(namespc, name, kIsLink, false) &&
(NO_RETRY_EXPECTED(unlinkat(ns.fd(), ns.path(), 0)) == 0);
}
bool File::Rename(Namespace* namespc,
const char* old_path,
const char* new_path) {
File::Type type = File::GetType(namespc, old_path, true);
if (type == kIsFile || type == kIsSock || type == kIsPipe) {
NamespaceScope oldns(namespc, old_path);
NamespaceScope newns(namespc, new_path);
return (NO_RETRY_EXPECTED(renameat(oldns.fd(), oldns.path(), newns.fd(),
newns.path())) == 0);
}
SetErrno(type);
return false;
}
bool File::RenameLink(Namespace* namespc,
const char* old_path,
const char* new_path) {
NamespaceScope oldns(namespc, old_path);
NamespaceScope newns(namespc, new_path);
return CheckTypeAndSetErrno(namespc, old_path, kIsLink, false) &&
(NO_RETRY_EXPECTED(renameat(oldns.fd(), oldns.path(), newns.fd(),
newns.path())) == 0);
}
bool File::Copy(Namespace* namespc,
const char* old_path,
const char* new_path) {
File::Type type = File::GetType(namespc, old_path, true);
if (type != kIsFile && type != kIsSock && type != kIsPipe) {
SetErrno(type);
return false;
}
NamespaceScope oldns(namespc, old_path);
struct stat st;
if (NO_RETRY_EXPECTED(fstatat(oldns.fd(), oldns.path(), &st, 0)) != 0) {
return false;
}
const int old_fd = NO_RETRY_EXPECTED(
openat(oldns.fd(), oldns.path(), O_RDONLY | O_CLOEXEC));
if (old_fd < 0) {
return false;
}
NamespaceScope newns(namespc, new_path);
const int new_fd = NO_RETRY_EXPECTED(
openat(newns.fd(), newns.path(),
O_WRONLY | O_TRUNC | O_CREAT | O_CLOEXEC, st.st_mode));
if (new_fd < 0) {
close(old_fd);
return false;
}
// TODO(ZX-429): Use sendfile/copyfile or equivalent when there is one.
intptr_t result;
const intptr_t kBufferSize = 8 * KB;
uint8_t* buffer = reinterpret_cast<uint8_t*>(malloc(kBufferSize));
while ((result = NO_RETRY_EXPECTED(read(old_fd, buffer, kBufferSize))) > 0) {
int wrote = NO_RETRY_EXPECTED(write(new_fd, buffer, result));
if (wrote != result) {
result = -1;
break;
}
}
free(buffer);
FDUtils::SaveErrorAndClose(old_fd);
FDUtils::SaveErrorAndClose(new_fd);
if (result < 0) {
int e = errno;
VOID_NO_RETRY_EXPECTED(unlinkat(newns.fd(), newns.path(), 0));
errno = e;
return false;
}
return true;
}
static bool StatHelper(Namespace* namespc,
const char* name,
struct stat* st) {
NamespaceScope ns(namespc, name);
if (NO_RETRY_EXPECTED(fstatat(ns.fd(), ns.path(), st, 0)) != 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(Namespace* namespc, const char* name) {
struct stat st;
if (!StatHelper(namespc, 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;
}
static void MillisecondsToTimespec(int64_t millis, struct timespec* t) {
ASSERT(t != NULL);
t->tv_sec = millis / kMillisecondsPerSecond;
t->tv_nsec = (millis % kMillisecondsPerSecond) * 1000L;
}
void File::Stat(Namespace* namespc, const char* name, int64_t* data) {
NamespaceScope ns(namespc, name);
struct stat st;
if (TEMP_FAILURE_RETRY(fstatat(ns.fd(), ns.path(), &st, 0)) == 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 if (S_ISSOCK(st.st_mode)) {
data[kType] = kIsSock;
} else if (S_ISFIFO(st.st_mode)) {
data[kType] = kIsPipe;
} 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(Namespace* namespc, const char* name) {
struct stat st;
if (!StatHelper(namespc, name, &st)) {
return -1;
}
return st.st_mtime;
}
time_t File::LastAccessed(Namespace* namespc, const char* name) {
struct stat st;
if (!StatHelper(namespc, name, &st)) {
return -1;
}
return st.st_atime;
}
bool File::SetLastAccessed(Namespace* namespc,
const char* name,
int64_t millis) {
// First get the current times.
struct stat st;
if (!StatHelper(namespc, name, &st)) {
return false;
}
// Set the new time:
NamespaceScope ns(namespc, name);
struct timespec times[2];
MillisecondsToTimespec(millis, &times[0]);
times[1] = st.st_mtim;
return utimensat(ns.fd(), ns.path(), times, 0) == 0;
}
bool File::SetLastModified(Namespace* namespc,
const char* name,
int64_t millis) {
// First get the current times.
struct stat st;
if (!StatHelper(namespc, name, &st)) {
return false;
}
// Set the new time:
NamespaceScope ns(namespc, name);
struct timespec times[2];
times[0] = st.st_atim;
MillisecondsToTimespec(millis, &times[1]);
return utimensat(ns.fd(), ns.path(), times, 0) == 0;
}
const char* File::LinkTarget(Namespace* namespc,
const char* name,
char* dest,
int dest_size) {
NamespaceScope ns(namespc, name);
struct stat link_stats;
const int status = TEMP_FAILURE_RETRY(
fstatat(ns.fd(), ns.path(), &link_stats, AT_SYMLINK_NOFOLLOW));
if (status != 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];
const int target_size =
TEMP_FAILURE_RETRY(readlinkat(ns.fd(), ns.path(), target, kBufferSize));
if (target_size <= 0) {
return NULL;
}
if (dest == NULL) {
dest = DartUtils::ScopedCString(target_size + 1);
} else {
ASSERT(dest_size > 0);
if (dest_size <= target_size) {
return NULL;
}
}
memmove(dest, target, target_size);
dest[target_size] = '\0';
return dest;
}
bool File::IsAbsolutePath(const char* pathname) {
return ((pathname != NULL) && (pathname[0] == '/'));
}
const char* File::GetCanonicalPath(Namespace* namespc,
const char* name,
char* dest,
int dest_size) {
if (name == NULL) {
return NULL;
}
if (!Namespace::IsDefault(namespc)) {
// TODO(zra): There is no realpathat(). Also chasing a symlink might result
// in a path to something outside of the namespace, so canonicalizing paths
// would have to be done carefully. For now, don't do anything.
return name;
}
char* abs_path;
if (dest == NULL) {
dest = DartUtils::ScopedCString(PATH_MAX + 1);
} else {
ASSERT(dest_size >= PATH_MAX);
}
ASSERT(dest != NULL);
do {
abs_path = realpath(name, dest);
} while ((abs_path == NULL) && (errno == EINTR));
ASSERT(abs_path == NULL || IsAbsolutePath(abs_path));
ASSERT(abs_path == NULL || (abs_path == dest));
return abs_path;
}
const char* File::PathSeparator() {
return "/";
}
const char* File::StringEscapedPathSeparator() {
return "/";
}
static int fd_is_valid(int fd) {
return NO_RETRY_EXPECTED(fcntl(fd, F_GETFD)) != -1 || errno != EBADF;
}
File::StdioHandleType File::GetStdioHandleType(int fd) {
struct stat buf;
int result = TEMP_FAILURE_RETRY(fstat(fd, &buf));
if (result == -1) {
// fstat() on fds 0, 1, 2 on Fuchsia return -1 with errno ENOTSUP,
// but if they are opened, then we can read/write them, so pretend they
// are kPipe.
return ((errno == ENOTSUP) && fd_is_valid(fd)) ? kPipe : kTypeError;
}
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(Namespace* namespc_1,
const char* file_1,
Namespace* namespc_2,
const char* file_2) {
struct stat file_1_info;
struct stat file_2_info;
int status;
{
NamespaceScope ns1(namespc_1, file_1);
status = TEMP_FAILURE_RETRY(
fstatat(ns1.fd(), ns1.path(), &file_1_info, AT_SYMLINK_NOFOLLOW));
if (status == -1) {
return File::kError;
}
}
{
NamespaceScope ns2(namespc_2, file_2);
status = TEMP_FAILURE_RETRY(
fstatat(ns2.fd(), ns2.path(), &file_2_info, AT_SYMLINK_NOFOLLOW));
if (status == -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(DART_HOST_OS_FUCHSIA)