blob: b9b1f350e0e8ff620bd99873b07aefe2e44f4fcc [file] [log] [blame]
// Copyright 2016 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// for S_IF*
#define _XOPEN_SOURCE
#include "src/storage/minfs/host.h"
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <lib/syslog/cpp/macros.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include <zircon/assert.h>
#include <limits>
#include <memory>
#include <string_view>
#include <type_traits>
#include <utility>
#include <fbl/ref_ptr.h>
#include "src/lib/storage/vfs/cpp/vfs.h"
#include "src/lib/storage/vfs/cpp/vfs_types.h"
#include "src/storage/minfs/format.h"
#include "src/storage/minfs/minfs.h"
#include "src/storage/minfs/runner.h"
namespace {
zx_status_t do_stat(fbl::RefPtr<fs::Vnode> vn, struct stat* s) {
fs::VnodeAttributes a;
zx_status_t status = vn->GetAttributes(&a);
if (status == ZX_OK) {
memset(s, 0, sizeof(struct stat));
s->st_mode = static_cast<mode_t>(a.mode);
s->st_size = static_cast<off_t>(a.content_size);
s->st_ino = a.inode;
s->st_ctime = static_cast<time_t>(a.creation_time);
s->st_mtime = static_cast<time_t>(a.modification_time);
}
return status;
}
struct HostFile {
fbl::RefPtr<fs::Vnode> vn;
uint64_t off = 0;
fs::VdirCookie dircookie;
};
constexpr int kMaxFd = 64;
static HostFile fdtab[kMaxFd];
constexpr int kFdMagic = 0x45AB0000;
HostFile* file_get(int fd) {
if ((fd & 0xFFFF0000) != kFdMagic) {
return nullptr;
}
fd &= 0x0000FFFF;
if ((fd < 0) || (fd >= kMaxFd)) {
return nullptr;
}
if (fdtab[fd].vn == nullptr) {
return nullptr;
}
return fdtab + fd;
}
int status_to_errno(zx_status_t status) {
switch (status) {
case ZX_OK:
return 0;
case ZX_ERR_FILE_BIG:
return EFBIG;
case ZX_ERR_NO_SPACE:
return ENOSPC;
case ZX_ERR_ALREADY_EXISTS:
return EEXIST;
default:
return EIO;
}
}
#define FAIL(err) \
do { \
errno = (err); \
return errno ? -1 : 0; \
} while (0)
#define STATUS(status) FAIL(status_to_errno(status))
// Ensure the order of these global destructors are ordered.
// TODO(planders): Host-side tools should avoid using globals.
struct FakeFs {
std::unique_ptr<fs::Vfs> fake_vfs = nullptr;
fbl::RefPtr<minfs::VnodeMinfs> fake_root = nullptr; // Must be destroyed before fake_vfs.
} fake_fs;
} // namespace
int emu_mkfs(const char* path) {
fbl::unique_fd fd(open(path, O_RDWR));
if (!fd) {
FX_LOGS(ERROR) << "error: could not open path " << path;
return -1;
}
struct stat s;
if (fstat(fd.get(), &s) < 0) {
FX_LOGS(ERROR) << "error: minfs could not find end of file/device";
return -1;
}
off_t size = s.st_size / minfs::kMinfsBlockSize;
auto bc_or = minfs::Bcache::Create(std::move(fd), static_cast<uint32_t>(size));
if (bc_or.is_error()) {
FX_LOGS(ERROR) << "error: cannot create block cache: " << bc_or.error_value();
return -1;
}
return Mkfs(bc_or.value().get()).status_value();
}
int emu_mount_bcache(std::unique_ptr<minfs::Bcache> bc) {
auto fs = minfs::Runner::Create(nullptr, std::move(bc), minfs::MountOptions());
if (fs.is_error()) {
return -1;
}
auto root = fs->minfs().OpenRootNode();
if (root.is_error()) {
return -1;
}
fake_fs.fake_root = std::move(root).value();
fake_fs.fake_vfs = std::move(fs).value();
return 0;
}
int emu_create_bcache(const char* path, std::unique_ptr<minfs::Bcache>* out_bc) {
fbl::unique_fd fd(open(path, O_RDWR));
if (!fd) {
FX_LOGS(ERROR) << "error: could not open path " << path;
return -1;
}
struct stat s;
if (fstat(fd.get(), &s) < 0) {
FX_LOGS(ERROR) << "error: minfs could not find end of file/device";
return 0;
}
off_t size = s.st_size / minfs::kMinfsBlockSize;
auto bc_or = minfs::Bcache::Create(std::move(fd), static_cast<uint32_t>(size));
if (bc_or.is_error()) {
FX_LOGS(ERROR) << "error: cannot create block cache: " << bc_or.status_value();
return -1;
}
*out_bc = std::move(bc_or.value());
return 0;
}
int emu_mount(const char* path) {
std::unique_ptr<minfs::Bcache> bc;
if (emu_create_bcache(path, &bc) != 0) {
return -1;
}
return emu_mount_bcache(std::move(bc));
}
int emu_get_used_resources(const char* path, uint64_t* out_data_size, uint64_t* out_inodes,
uint64_t* out_used_size) {
std::unique_ptr<minfs::Bcache> bc;
if (emu_create_bcache(path, &bc) != 0) {
return -1;
}
auto data_size_or = minfs::UsedDataSize(bc);
if (data_size_or.is_error()) {
return -1;
}
auto inodes_or = minfs::UsedInodes(bc);
if (inodes_or.is_error()) {
return -1;
}
auto used_size_or = minfs::UsedSize(bc);
if (used_size_or.is_error()) {
return -1;
}
*out_data_size = data_size_or.value();
*out_inodes = inodes_or.value();
*out_used_size = used_size_or.value();
return 0;
}
bool emu_is_mounted() { return fake_fs.fake_root != nullptr; }
// Converts POSIX open() flags to |VnodeConnectionOptions|.
fs::VnodeConnectionOptions fdio_flags_to_connection_options(uint32_t flags) {
fs::VnodeConnectionOptions options;
switch (flags & O_ACCMODE) {
case O_RDONLY:
options.rights.read = true;
break;
case O_WRONLY:
options.rights.write = true;
break;
case O_RDWR:
options.rights.read = true;
options.rights.write = true;
break;
}
#ifdef O_PATH
if (flags & O_PATH) {
options.flags.node_reference = true;
}
#endif
#ifdef O_DIRECTORY
if (flags & O_DIRECTORY) {
options.flags.directory = true;
}
#endif
if (flags & O_CREAT) {
options.flags.create = true;
}
if (flags & O_EXCL) {
options.flags.fail_if_exists = true;
}
if (flags & O_TRUNC) {
options.flags.truncate = true;
}
if (flags & O_APPEND) {
options.flags.append = true;
}
return options;
}
int emu_open(const char* path, int flags, mode_t mode) {
// TODO: fdtab lock
ZX_DEBUG_ASSERT_MSG(!host_path(path), "'emu_' functions can only operate on target paths");
if (flags & O_APPEND) {
errno = ENOTSUP;
return -1;
}
for (int fd = 0; fd < kMaxFd; fd++) {
if (fdtab[fd].vn == nullptr) {
std::string_view str(path + PREFIX_SIZE);
fs::VnodeConnectionOptions options = fdio_flags_to_connection_options(flags);
auto result =
fake_fs.fake_vfs->Open(fake_fs.fake_root, str, options, fs::Rights::ReadWrite(), mode);
if (result.is_error()) {
STATUS(result.error());
}
fdtab[fd].vn = fbl::RefPtr<fs::Vnode>::Downcast(result.ok().vnode);
return fd | kFdMagic;
}
}
FAIL(EMFILE);
}
int emu_close(int fd) {
// TODO: fdtab lock
HostFile* f = file_get(fd);
if (f == nullptr) {
return -1;
}
f->vn->Close();
f->vn.reset();
f->off = 0;
f->dircookie = fs::VdirCookie();
return 0;
}
ssize_t emu_write(int fd, const void* buf, size_t count) {
HostFile* f = file_get(fd);
if (f == nullptr) {
return -1;
}
size_t actual = 0;
zx_status_t status = f->vn->Write(buf, count, f->off, &actual);
if (status == ZX_OK) {
f->off += actual;
ZX_DEBUG_ASSERT(actual <= std::numeric_limits<ssize_t>::max());
return static_cast<ssize_t>(actual);
}
ZX_DEBUG_ASSERT(status < 0);
STATUS(status);
}
ssize_t emu_pwrite(int fd, const void* buf, size_t count, off_t off) {
HostFile* f = file_get(fd);
if (f == nullptr) {
return -1;
}
size_t actual;
zx_status_t status = f->vn->Write(buf, count, off, &actual);
if (status == ZX_OK) {
ZX_DEBUG_ASSERT(actual <= std::numeric_limits<ssize_t>::max());
return static_cast<ssize_t>(actual);
}
ZX_DEBUG_ASSERT(status < 0);
STATUS(status);
}
ssize_t emu_read(int fd, void* buf, size_t count) {
HostFile* f = file_get(fd);
if (f == nullptr) {
return -1;
}
size_t actual = 0;
zx_status_t status = f->vn->Read(buf, count, f->off, &actual);
if (status == ZX_OK) {
f->off += actual;
ZX_DEBUG_ASSERT(actual <= std::numeric_limits<ssize_t>::max());
return static_cast<ssize_t>(actual);
}
ZX_DEBUG_ASSERT(status < 0);
STATUS(status);
}
ssize_t emu_pread(int fd, void* buf, size_t count, off_t off) {
HostFile* f = file_get(fd);
if (f == nullptr) {
return -1;
}
size_t actual;
zx_status_t status = f->vn->Read(buf, count, off, &actual);
if (status == ZX_OK) {
ZX_DEBUG_ASSERT(actual <= std::numeric_limits<ssize_t>::max());
return static_cast<ssize_t>(actual);
}
ZX_DEBUG_ASSERT(status < 0);
STATUS(status);
}
int emu_ftruncate(int fd, off_t len) {
HostFile* f = file_get(fd);
if (f == nullptr) {
return -1;
}
int r = f->vn->Truncate(len);
return r < 0 ? -1 : r;
}
off_t emu_lseek(int fd, off_t offset, int whence) {
HostFile* f = file_get(fd);
if (f == nullptr) {
return -1;
}
uint64_t old = f->off;
switch (whence) {
case SEEK_SET: {
if (offset < 0) {
FAIL(EINVAL);
}
f->off = offset;
break;
}
case SEEK_END: {
fs::VnodeAttributes a;
if (f->vn->GetAttributes(&a)) {
FAIL(EINVAL);
}
old = a.content_size;
__FALLTHROUGH;
}
case SEEK_CUR: {
uint64_t n = old + offset;
if (offset < 0) {
if (n >= old) {
FAIL(EINVAL);
}
} else {
if (n < old) {
FAIL(EINVAL);
}
}
f->off = n;
break;
}
default: {
FAIL(EINVAL);
}
}
return static_cast<off_t>(f->off);
}
int emu_fstat(int fd, struct stat* s) {
HostFile* f = file_get(fd);
if (f == nullptr) {
return -1;
}
STATUS(do_stat(f->vn, s));
}
int emu_stat(const char* fn, struct stat* s) {
ZX_DEBUG_ASSERT_MSG(!host_path(fn), "'emu_' functions can only operate on target paths");
fbl::RefPtr<fs::Vnode> vn = fake_fs.fake_root;
fbl::RefPtr<fs::Vnode> cur = fake_fs.fake_root;
const char* nextpath = nullptr;
fn += PREFIX_SIZE;
do {
while (fn[0] == '/') {
fn++;
}
if (fn[0] == 0) {
break;
}
size_t len = strlen(fn);
nextpath = strchr(fn, '/');
if (nextpath != nullptr) {
len = nextpath - fn;
nextpath++;
}
fbl::RefPtr<fs::Vnode> vn_fs;
zx_status_t status = cur->Lookup(std::string_view(fn, len), &vn_fs);
if (status != ZX_OK) {
return -ENOENT;
}
vn = fbl::RefPtr<fs::Vnode>::Downcast(vn_fs);
cur = vn;
fn = nextpath;
} while (nextpath != nullptr);
zx_status_t status = do_stat(vn, s);
STATUS(status);
}
constexpr size_t kDirBufSize = 2048;
struct MinDir {
~MinDir() {
if (vn)
vn->Close();
}
uint64_t magic = minfs::kMinfsMagic0;
fbl::RefPtr<fs::Vnode> vn;
fs::VdirCookie cookie;
uint8_t* ptr = nullptr;
uint8_t data[kDirBufSize] = {0};
size_t size = 0;
dirent de = {};
};
int emu_mkdir(const char* path, mode_t mode) {
ZX_DEBUG_ASSERT_MSG(!host_path(path), "'emu_' functions can only operate on target paths");
mode = S_IFDIR;
int fd = emu_open(path, O_CREAT | O_EXCL, S_IFDIR | (mode & 0777));
if (fd >= 0) {
emu_close(fd);
return 0;
} else {
return fd;
}
}
DIR* emu_opendir(const char* name) {
ZX_DEBUG_ASSERT_MSG(!host_path(name), "'emu_' functions can only operate on target paths");
std::string_view path(name + PREFIX_SIZE);
fs::VnodeConnectionOptions options;
options.rights.read = true;
options.flags.posix_write = true;
auto result =
fake_fs.fake_vfs->Open(fake_fs.fake_root, path, options, fs::Rights::ReadWrite(), 0);
if (result.is_error()) {
return nullptr;
}
MinDir* dir = new MinDir();
dir->vn = fbl::RefPtr<fs::Vnode>::Downcast(result.ok().vnode);
return reinterpret_cast<DIR*>(dir);
}
dirent* emu_readdir(DIR* dirp) {
MinDir* dir = reinterpret_cast<MinDir*>(dirp);
for (;;) {
if (dir->size >= sizeof(vdirent_t)) {
vdirent_t* vde = reinterpret_cast<vdirent_t*>(dir->ptr);
dirent* ent = &dir->de;
size_t name_len = vde->size;
size_t entry_len = vde->size + sizeof(vdirent_t);
ZX_DEBUG_ASSERT(dir->size >= entry_len);
memcpy(ent->d_name, vde->name, name_len);
ent->d_name[name_len] = '\0';
ent->d_type = vde->type;
dir->ptr += entry_len;
dir->size -= entry_len;
return ent;
}
size_t actual = 0;
zx_status_t status = dir->vn->Readdir(&dir->cookie, &dir->data, kDirBufSize, &actual);
if (status != ZX_OK || actual == 0) {
break;
}
dir->ptr = dir->data;
dir->size = actual;
}
return nullptr;
}
void emu_rewinddir(DIR* dirp) {
MinDir* dir = reinterpret_cast<MinDir*>(dirp);
dir->size = 0;
dir->ptr = NULL;
dir->cookie.n = 0;
}
int emu_closedir(DIR* dirp) {
if (reinterpret_cast<uint64_t*>(dirp)[0] != minfs::kMinfsMagic0) {
return closedir(dirp);
}
MinDir* dir = reinterpret_cast<MinDir*>(dirp);
delete dir;
return 0;
}