blob: eeb705758b48f7a68b2aebc2d3adef73e68cbcc1 [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.
#include <inttypes.h>
#include <fcntl.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <async/loop.h>
#include <fbl/algorithm.h>
#include <fbl/alloc_checker.h>
#include <fbl/atomic.h>
#include <fbl/auto_lock.h>
#include <fbl/ref_ptr.h>
#include <fbl/unique_ptr.h>
#include <fs/vfs.h>
#include <zircon/device/vfs.h>
#include <zircon/thread_annotations.h>
#include <fdio/debug.h>
#include <fdio/vfs.h>
#include "devmgr.h"
#include "dnode.h"
#include "memfs-private.h"
#define MXDEBUG 0
namespace memfs {
namespace {
Vfs root_vfs;
Vfs system_vfs;
fbl::unique_ptr<async::Loop> global_loop;
} // namespace
// Artificially cap the maximum in-memory file size to 512MB.
constexpr size_t kMemfsMaxFileSize = 512 * 1024 * 1024;
static fbl::RefPtr<VnodeDir> global_root = nullptr;
static fbl::RefPtr<VnodeDir> memfs_root = nullptr;
static fbl::RefPtr<VnodeDir> devfs_root = nullptr;
static fbl::RefPtr<VnodeDir> bootfs_root = nullptr;
static fbl::RefPtr<VnodeDir> systemfs_root = nullptr;
static bool WindowMatchesVMO(zx_handle_t vmo, zx_off_t offset, zx_off_t length) {
if (offset != 0)
return false;
uint64_t size;
if (zx_vmo_get_size(vmo, &size) < 0)
return false;
return size == length;
}
zx_status_t Vfs::CreateFromVmo(VnodeDir* parent, bool vmofile, fbl::StringPiece name,
zx_handle_t vmo, zx_off_t off,
zx_off_t len) {
fbl::AutoLock lock(&vfs_lock_);
return parent->CreateFromVmo(vmofile, name, vmo, off, len);
}
void Vfs::MountSubtree(VnodeDir* parent, fbl::RefPtr<VnodeDir> subtree) {
fbl::AutoLock lock(&vfs_lock_);
parent->MountSubtree(fbl::move(subtree));
}
fbl::atomic<uint64_t> VnodeMemfs::ino_ctr_(0);
VnodeMemfs::VnodeMemfs(Vfs* vfs) : dnode_(nullptr), link_count_(0), vfs_(vfs),
ino_(ino_ctr_.fetch_add(1, fbl::memory_order_relaxed)) {
create_time_ = modify_time_ = zx_time_get(ZX_CLOCK_UTC);
}
VnodeMemfs::~VnodeMemfs() {}
VnodeFile::VnodeFile(Vfs* vfs)
: VnodeMemfs(vfs), vmo_(ZX_HANDLE_INVALID), length_(0) {}
VnodeFile::VnodeFile(Vfs* vfs, zx_handle_t vmo, zx_off_t length)
: VnodeMemfs(vfs), vmo_(vmo), length_(length) {}
VnodeFile::~VnodeFile() {
if (vmo_ != ZX_HANDLE_INVALID) {
zx_handle_close(vmo_);
}
}
VnodeDir::VnodeDir(Vfs* vfs) : VnodeMemfs(vfs) {
link_count_ = 1; // Implied '.'
}
VnodeDir::~VnodeDir() {}
VnodeVmo::VnodeVmo(Vfs* vfs, zx_handle_t vmo, zx_off_t offset, zx_off_t length)
: VnodeMemfs(vfs), vmo_(vmo), offset_(offset), length_(length), have_local_clone_(false) {}
VnodeVmo::~VnodeVmo() {
if (have_local_clone_) {
zx_handle_close(vmo_);
}
}
zx_status_t VnodeDir::ValidateFlags(uint32_t flags) {
if (flags & ZX_FS_RIGHT_WRITABLE) {
return ZX_ERR_NOT_FILE;
}
return ZX_OK;
}
zx_status_t VnodeFile::ValidateFlags(uint32_t flags) {
if (flags & ZX_FS_FLAG_DIRECTORY) {
return ZX_ERR_NOT_DIR;
}
return ZX_OK;
}
zx_status_t VnodeVmo::ValidateFlags(uint32_t flags) {
if (flags & ZX_FS_FLAG_DIRECTORY) {
return ZX_ERR_NOT_DIR;
}
if (flags & ZX_FS_RIGHT_WRITABLE) {
return ZX_ERR_ACCESS_DENIED;
}
return ZX_OK;
}
zx_status_t VnodeVmo::Serve(fs::Vfs* vfs, zx::channel channel, uint32_t flags) {
return ZX_OK;
}
zx_status_t VnodeVmo::GetHandles(uint32_t flags, zx_handle_t* hnds, size_t* hcount,
uint32_t* type, void* extra, uint32_t* esize) {
zx_off_t* off = static_cast<zx_off_t*>(extra);
zx_off_t* len = off + 1;
zx_handle_t vmo;
zx_status_t status;
if (!have_local_clone_ && !WindowMatchesVMO(vmo_, offset_, length_)) {
status = zx_vmo_clone(vmo_, ZX_VMO_CLONE_COPY_ON_WRITE, offset_, length_, &vmo_);
if (status < 0)
return status;
offset_ = 0;
have_local_clone_ = true;
}
status = zx_handle_duplicate(
vmo_,
ZX_RIGHT_READ | ZX_RIGHT_EXECUTE | ZX_RIGHT_MAP |
ZX_RIGHT_DUPLICATE | ZX_RIGHT_TRANSFER | ZX_RIGHT_GET_PROPERTY,
&vmo);
if (status < 0)
return status;
xprintf("vmofile: %x (%x) off=%" PRIu64 " len=%" PRIu64 "\n", vmo, vmo_, offset_, length_);
*off = offset_;
*len = length_;
hnds[0] = vmo;
*type = FDIO_PROTOCOL_VMOFILE;
*esize = sizeof(zx_off_t) * 2;
*hcount = 1;
return ZX_OK;
}
zx_status_t VnodeFile::Read(void* data, size_t len, size_t off, size_t* out_actual) {
if ((off >= length_) || (vmo_ == ZX_HANDLE_INVALID)) {
*out_actual = 0;
return ZX_OK;
} else if (len > length_ - off) {
len = length_ - off;
}
return zx_vmo_read(vmo_, data, off, len, out_actual);
}
zx_status_t VnodeVmo::Read(void* data, size_t len, size_t off, size_t* out_actual) {
if (off > length_) {
*out_actual = 0;
return ZX_OK;
}
size_t rlen = length_ - off;
if (len > rlen)
len = rlen;
return zx_vmo_read(vmo_, data, offset_ + off, len, out_actual);
}
zx_status_t VnodeFile::Write(const void* data, size_t len, size_t offset,
size_t* out_actual) {
zx_status_t status;
size_t newlen = offset + len;
newlen = newlen > kMemfsMaxFileSize ? kMemfsMaxFileSize : newlen;
size_t alignedLen = fbl::round_up(newlen, static_cast<size_t>(PAGE_SIZE));
if (vmo_ == ZX_HANDLE_INVALID) {
// First access to the file? Allocate it.
if ((status = zx_vmo_create(alignedLen, 0, &vmo_)) != ZX_OK) {
return status;
}
} else if (newlen > fbl::round_up(length_, static_cast<size_t>(PAGE_SIZE))) {
// Accessing beyond the end of the file? Extend it.
if ((status = zx_vmo_set_size(vmo_, alignedLen)) != ZX_OK) {
return status;
}
}
if ((status = zx_vmo_write(vmo_, data, offset, len, out_actual)) != ZX_OK) {
return status;
}
if (newlen > length_) {
length_ = newlen;
}
if (*out_actual == 0 && offset >= kMemfsMaxFileSize) {
// short write because we're beyond the end of the permissible length
return ZX_ERR_FILE_BIG;
}
UpdateModified();
return ZX_OK;
}
zx_status_t VnodeFile::Append(const void* data, size_t len, size_t* out_end,
size_t* out_actual) {
zx_status_t status = Write(data, len, length_, out_actual);
*out_end = length_;
return status;
}
zx_status_t VnodeFile::Mmap(int flags, size_t len, size_t* off, zx_handle_t* out) {
if (vmo_ == ZX_HANDLE_INVALID) {
// First access to the file? Allocate it.
zx_status_t status;
if ((status = zx_vmo_create(0, 0, &vmo_)) != ZX_OK) {
return status;
}
}
zx_rights_t rights = ZX_RIGHT_TRANSFER | ZX_RIGHT_MAP;
rights |= (flags & FDIO_MMAP_FLAG_READ) ? ZX_RIGHT_READ : 0;
rights |= (flags & FDIO_MMAP_FLAG_WRITE) ? ZX_RIGHT_WRITE : 0;
rights |= (flags & FDIO_MMAP_FLAG_EXEC) ? ZX_RIGHT_EXECUTE : 0;
if (flags & FDIO_MMAP_FLAG_PRIVATE) {
return zx_vmo_clone(vmo_, ZX_VMO_CLONE_COPY_ON_WRITE, 0, length_, out);
}
return zx_handle_duplicate(vmo_, rights, out);
}
zx_status_t VnodeDir::Mmap(int flags, size_t len, size_t* off, zx_handle_t* out) {
return ZX_ERR_ACCESS_DENIED;
}
bool VnodeDir::IsRemote() const { return remoter_.IsRemote(); }
zx::channel VnodeDir::DetachRemote() { return remoter_.DetachRemote(); }
zx_handle_t VnodeDir::GetRemote() const { return remoter_.GetRemote(); }
void VnodeDir::SetRemote(zx::channel remote) { return remoter_.SetRemote(fbl::move(remote)); }
zx_status_t VnodeDir::Lookup(fbl::RefPtr<fs::Vnode>* out, fbl::StringPiece name) {
if (!IsDirectory()) {
return ZX_ERR_NOT_FOUND;
}
fbl::RefPtr<Dnode> dn;
zx_status_t r = dnode_->Lookup(name, &dn);
ZX_DEBUG_ASSERT(r <= 0);
if (r == ZX_OK) {
if (dn == nullptr) {
// Looking up our own vnode
*out = fbl::RefPtr<VnodeDir>(this);
} else {
// Looking up a different vnode
*out = dn->AcquireVnode();
}
}
return r;
}
constexpr uint64_t kMemfsBlksize = PAGE_SIZE;
zx_status_t VnodeFile::Getattr(vnattr_t* attr) {
memset(attr, 0, sizeof(vnattr_t));
attr->inode = ino_;
attr->mode = V_TYPE_FILE | V_IRUSR | V_IWUSR | V_IRGRP | V_IROTH;
attr->size = length_;
attr->blksize = kMemfsBlksize;
attr->blkcount = fbl::round_up(attr->size, kMemfsBlksize) / VNATTR_BLKSIZE;
attr->nlink = link_count_;
attr->create_time = create_time_;
attr->modify_time = modify_time_;
return ZX_OK;
}
zx_status_t VnodeDir::Getattr(vnattr_t* attr) {
memset(attr, 0, sizeof(vnattr_t));
attr->inode = ino_;
attr->mode = V_TYPE_DIR | V_IRUSR;
attr->size = 0;
attr->blksize = kMemfsBlksize;
attr->blkcount = fbl::round_up(attr->size, kMemfsBlksize) / VNATTR_BLKSIZE;
attr->nlink = link_count_;
attr->create_time = create_time_;
attr->modify_time = modify_time_;
return ZX_OK;
}
zx_status_t VnodeVmo::Getattr(vnattr_t* attr) {
memset(attr, 0, sizeof(vnattr_t));
attr->inode = ino_;
attr->mode = V_TYPE_FILE | V_IRUSR;
attr->size = length_;
attr->blksize = kMemfsBlksize;
attr->blkcount = fbl::round_up(attr->size, kMemfsBlksize) / VNATTR_BLKSIZE;
attr->nlink = link_count_;
attr->create_time = create_time_;
attr->modify_time = modify_time_;
return ZX_OK;
}
zx_status_t VnodeMemfs::Setattr(const vnattr_t* attr) {
if ((attr->valid & ~(ATTR_MTIME)) != 0) {
// only attr currently supported
return ZX_ERR_INVALID_ARGS;
}
if (attr->valid & ATTR_MTIME) {
modify_time_ = attr->modify_time;
}
return ZX_OK;
}
zx_status_t VnodeDir::Readdir(fs::vdircookie_t* cookie, void* data, size_t len, size_t* out_actual) {
fs::DirentFiller df(data, len);
if (!IsDirectory()) {
// This WAS a directory, but it has been deleted.
Dnode::ReaddirStart(&df, cookie);
*out_actual = df.BytesFilled();
return ZX_OK;
}
dnode_->Readdir(&df, cookie);
*out_actual = df.BytesFilled();
return ZX_OK;
}
// postcondition: reference taken on vn returned through "out"
zx_status_t VnodeDir::Create(fbl::RefPtr<fs::Vnode>* out, fbl::StringPiece name, uint32_t mode) {
zx_status_t status;
if ((status = CanCreate(name)) != ZX_OK) {
return status;
}
fbl::AllocChecker ac;
fbl::RefPtr<memfs::VnodeMemfs> vn;
if (S_ISDIR(mode)) {
vn = fbl::AdoptRef(new (&ac) memfs::VnodeDir(vfs()));
} else {
vn = fbl::AdoptRef(new (&ac) memfs::VnodeFile(vfs()));
}
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
if ((status = AttachVnode(vn, name, S_ISDIR(mode))) != ZX_OK) {
return status;
}
*out = fbl::move(vn);
return status;
}
zx_status_t VnodeDir::Unlink(fbl::StringPiece name, bool must_be_dir) {
if (!IsDirectory()) {
// Calling unlink from unlinked, empty directory
return ZX_ERR_BAD_STATE;
}
fbl::RefPtr<Dnode> dn;
zx_status_t r;
if ((r = dnode_->Lookup(name, &dn)) != ZX_OK) {
return r;
} else if (dn == nullptr) {
// Cannot unlink directory 'foo' using the argument 'foo/.'
return ZX_ERR_UNAVAILABLE;
} else if (!dn->IsDirectory() && must_be_dir) {
// Path ending in "/" was requested, implying that the dnode must be a directory
return ZX_ERR_NOT_DIR;
} else if ((r = dn->CanUnlink()) != ZX_OK) {
return r;
}
dn->Detach();
return ZX_OK;
}
zx_status_t VnodeFile::Truncate(size_t len) {
zx_status_t status;
if (len > kMemfsMaxFileSize) {
return ZX_ERR_INVALID_ARGS;
}
size_t alignedLen = fbl::round_up(len, static_cast<size_t>(PAGE_SIZE));
if (vmo_ == ZX_HANDLE_INVALID) {
// First access to the file? Allocate it.
if ((status = zx_vmo_create(alignedLen, 0, &vmo_)) != ZX_OK) {
return status;
}
} else if ((len < length_) && (len % PAGE_SIZE != 0)) {
// Currently, if the file is truncated to a 'partial page', an later re-expanded, then the
// partial page is *not necessarily* filled with zeroes. As a consequence, we manually must
// fill the portion between "len" and the next highest page (or vn->length, whichever
// is smaller) with zeroes.
char buf[PAGE_SIZE];
size_t ppage_size = PAGE_SIZE - (len % PAGE_SIZE);
ppage_size = len + ppage_size < length_ ? ppage_size : length_ - len;
memset(buf, 0, ppage_size);
size_t actual;
status = zx_vmo_write(vmo_, buf, len, ppage_size, &actual);
if ((status != ZX_OK) || (actual != ppage_size)) {
return status != ZX_OK ? ZX_ERR_IO : status;
} else if ((status = zx_vmo_set_size(vmo_, alignedLen)) != ZX_OK) {
return status;
}
} else if ((status = zx_vmo_set_size(vmo_, alignedLen)) != ZX_OK) {
return status;
}
length_ = len;
modify_time_ = zx_time_get(ZX_CLOCK_UTC);
return ZX_OK;
}
zx_status_t VnodeDir::Rename(fbl::RefPtr<fs::Vnode> _newdir, fbl::StringPiece oldname,
fbl::StringPiece newname, bool src_must_be_dir,
bool dst_must_be_dir) {
auto newdir = fbl::RefPtr<VnodeMemfs>::Downcast(fbl::move(_newdir));
if (!IsDirectory() || !newdir->IsDirectory())
return ZX_ERR_BAD_STATE;
fbl::RefPtr<Dnode> olddn;
zx_status_t r;
// The source must exist
if ((r = dnode_->Lookup(oldname, &olddn)) != ZX_OK) {
return r;
}
ZX_DEBUG_ASSERT(olddn != nullptr);
if (!olddn->IsDirectory() && (src_must_be_dir || dst_must_be_dir)) {
return ZX_ERR_NOT_DIR;
}
// Verify that the destination is not a subdirectory of the source (if
// both are directories).
if (olddn->IsSubdirectory(newdir->dnode_)) {
return ZX_ERR_INVALID_ARGS;
}
// The destination may or may not exist
fbl::RefPtr<Dnode> targetdn;
r = newdir->dnode_->Lookup(newname, &targetdn);
bool target_exists = (r == ZX_OK);
if (target_exists) {
ZX_DEBUG_ASSERT(targetdn != nullptr);
// The target exists. Validate and unlink it.
if (olddn == targetdn) {
// Cannot rename node to itself
return ZX_ERR_INVALID_ARGS;
}
if (olddn->IsDirectory() != targetdn->IsDirectory()) {
// Cannot rename files to directories (and vice versa)
return ZX_ERR_INVALID_ARGS;
} else if ((r = targetdn->CanUnlink()) != ZX_OK) {
return r;
}
} else if (r != ZX_ERR_NOT_FOUND) {
return r;
}
// Allocate the new name for the dnode, either by
// (1) Stealing it from the previous dnode, if it used the same name, or
// (2) Allocating a new name, if creating a new name.
fbl::unique_ptr<char[]> namebuffer(nullptr);
if (target_exists) {
targetdn->Detach();
namebuffer = fbl::move(targetdn->TakeName());
} else {
fbl::AllocChecker ac;
namebuffer.reset(new (&ac) char[newname.length() + 1]);
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
memcpy(namebuffer.get(), newname.data(), newname.length());
namebuffer[newname.length()] = '\0';
}
// NOTE:
//
// Validation ends here, and modifications begin. Rename should not fail
// beyond this point.
olddn->RemoveFromParent();
olddn->PutName(fbl::move(namebuffer), newname.length());
Dnode::AddChild(newdir->dnode_, fbl::move(olddn));
return ZX_OK;
}
zx_status_t VnodeDir::Link(fbl::StringPiece name, fbl::RefPtr<fs::Vnode> target) {
auto vn = fbl::RefPtr<VnodeMemfs>::Downcast(fbl::move(target));
if (!IsDirectory()) {
// Empty, unlinked parent
return ZX_ERR_BAD_STATE;
}
if (vn->IsDirectory()) {
// The target must not be a directory
return ZX_ERR_NOT_FILE;
}
if (dnode_->Lookup(name, nullptr) == ZX_OK) {
// The destination should not exist
return ZX_ERR_ALREADY_EXISTS;
}
// Make a new dnode for the new name, attach the target vnode to it
fbl::RefPtr<Dnode> targetdn;
if ((targetdn = Dnode::Create(name, vn)) == nullptr) {
return ZX_ERR_NO_MEMORY;
}
// Attach the new dnode to its parent
Dnode::AddChild(dnode_, fbl::move(targetdn));
return ZX_OK;
}
zx_status_t VnodeMemfs::Sync() {
// Since this filesystem is in-memory, all data is already up-to-date in
// the underlying storage
return ZX_OK;
}
constexpr const char kFsName[] = "memfs";
zx_status_t VnodeMemfs::Ioctl(uint32_t op, const void* in_buf, size_t in_len,
void* out_buf, size_t out_len, size_t* out_actual) {
switch (op) {
case IOCTL_VFS_MOUNT_BOOTFS_VMO: {
if (in_len < sizeof(zx_handle_t)) {
return ZX_ERR_INVALID_ARGS;
}
const zx_handle_t* vmo = static_cast<const zx_handle_t*>(in_buf);
ssize_t r = devmgr_add_systemfs_vmo(*vmo);
if (r < 0) {
return zx_status_t(r);
}
*out_actual = static_cast<size_t>(r);
return ZX_OK;
}
case IOCTL_VFS_QUERY_FS: {
if (out_len < sizeof(vfs_query_info_t) + strlen(kFsName)) {
return ZX_ERR_INVALID_ARGS;
}
vfs_query_info_t* info = static_cast<vfs_query_info_t*>(out_buf);
//TODO(planders): eventually report something besides 0.
info->total_bytes = 0;
info->used_bytes = 0;
info->total_nodes = 0;
info->used_nodes = 0;
memcpy(info->name, kFsName, strlen(kFsName));
*out_actual = sizeof(vfs_query_info_t) + strlen(kFsName);
return ZX_OK;
}
default:
return ZX_ERR_NOT_SUPPORTED;
}
}
zx_status_t VnodeDir::Ioctl(uint32_t op, const void* in_buf, size_t in_len,
void* out_buf, size_t out_len, size_t* out_actual) {
switch (op) {
case IOCTL_VFS_VMO_CREATE: {
const auto* config = reinterpret_cast<const vmo_create_config_t*>(in_buf);
size_t namelen = in_len - sizeof(vmo_create_config_t) - 1;
fbl::StringPiece name(config->name, namelen);
if (in_len <= sizeof(vmo_create_config_t) || (namelen > NAME_MAX) ||
(name[namelen] != 0)) {
zx_handle_close(config->vmo);
return ZX_ERR_INVALID_ARGS;
}
// Ensure this is the last handle to this VMO; otherwise, the size
// may change from underneath us.
zx_info_handle_count_t info;
zx_status_t status = zx_object_get_info(config->vmo, ZX_INFO_HANDLE_COUNT,
&info, sizeof(info), nullptr, nullptr);
if (status != ZX_OK || info.handle_count != 1) {
zx_handle_close(config->vmo);
return ZX_ERR_INVALID_ARGS;
}
uint64_t size;
if ((status = zx_vmo_get_size(config->vmo, &size)) != ZX_OK) {
zx_handle_close(config->vmo);
return status;
}
bool vmofile = false;
*out_actual = 0;
return vfs()->CreateFromVmo(this, vmofile, name, config->vmo, 0, size);
}
default:
return VnodeMemfs::Ioctl(op, in_buf, in_len, out_buf, out_len, out_actual);
}
}
zx_status_t VnodeMemfs::AttachRemote(fs::MountChannel h) {
if (!IsDirectory()) {
return ZX_ERR_NOT_DIR;
} else if (IsRemote()) {
return ZX_ERR_ALREADY_BOUND;
}
SetRemote(fbl::move(h.TakeChannel()));
return ZX_OK;
}
static zx_status_t memfs_create_fs(const char* name, memfs::Vfs* vfs, fbl::RefPtr<VnodeDir>* out) {
fbl::AllocChecker ac;
fbl::RefPtr<VnodeDir> fs = fbl::AdoptRef(new (&ac) VnodeDir(vfs));
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
fbl::RefPtr<Dnode> dn = Dnode::Create(name, fs);
if (dn == nullptr) {
return ZX_ERR_NO_MEMORY;
}
fs->dnode_ = dn; // FS root is directory
*out = fs;
return ZX_OK;
}
void VnodeDir::MountSubtree(fbl::RefPtr<VnodeDir> subtree) {
Dnode::AddChild(dnode_, subtree->dnode_);
}
zx_status_t VnodeDir::CreateFromVmo(bool vmofile, fbl::StringPiece name,
zx_handle_t vmo, zx_off_t off, zx_off_t len) {
zx_status_t status;
if ((status = CanCreate(name)) != ZX_OK) {
return status;
}
fbl::AllocChecker ac;
fbl::RefPtr<VnodeMemfs> vn;
if (vmofile) {
vn = fbl::AdoptRef(new (&ac) VnodeVmo(vfs(), vmo, off, len));
} else {
vn = fbl::AdoptRef(new (&ac) VnodeFile(vfs(), vmo, len));
}
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
if ((status = AttachVnode(fbl::move(vn), name, false)) != ZX_OK) {
return status;
}
return ZX_OK;
}
zx_status_t VnodeDir::CanCreate(fbl::StringPiece name) const {
if (!IsDirectory()) {
return ZX_ERR_INVALID_ARGS;
}
zx_status_t status;
if ((status = dnode_->Lookup(name, nullptr)) == ZX_ERR_NOT_FOUND) {
return ZX_OK;
} else if (status == ZX_OK) {
return ZX_ERR_ALREADY_EXISTS;
}
return status;
}
zx_status_t VnodeDir::AttachVnode(fbl::RefPtr<VnodeMemfs> vn, fbl::StringPiece name,
bool isdir) {
// dnode takes a reference to the vnode
fbl::RefPtr<Dnode> dn;
if ((dn = Dnode::Create(name, vn)) == nullptr) {
return ZX_ERR_NO_MEMORY;
}
// Identify that the vnode is a directory (vn->dnode_ != nullptr) so that
// addding a child will also increment the parent link_count (after all,
// directories contain a ".." entry, which is a link to their parent).
if (isdir) {
vn->dnode_ = dn;
}
// parent takes first reference
Dnode::AddChild(dnode_, fbl::move(dn));
return ZX_OK;
}
} // namespace memfs
// The following functions exist outside the memfs namespace so they can
// be exposed to C:
fbl::RefPtr<memfs::VnodeDir> SystemfsRoot() {
if (memfs::systemfs_root == nullptr) {
zx_status_t r = memfs_create_fs("system", &memfs::system_vfs, &memfs::systemfs_root);
if (r < 0) {
printf("fatal error %d allocating 'system' file system\n", r);
__builtin_trap();
}
}
return memfs::systemfs_root;
}
fbl::RefPtr<memfs::VnodeDir> MemfsRoot() {
if (memfs::memfs_root == nullptr) {
zx_status_t r = memfs_create_fs("tmp", &memfs::root_vfs, &memfs::memfs_root);
if (r < 0) {
printf("fatal error %d allocating 'tmp' file system\n", r);
__builtin_trap();
}
}
return memfs::memfs_root;
}
fbl::RefPtr<memfs::VnodeDir> DevfsRoot() {
if (memfs::devfs_root == nullptr) {
zx_status_t r = memfs_create_fs("dev", &memfs::root_vfs, &memfs::devfs_root);
if (r < 0) {
printf("fatal error %d allocating 'device' file system\n", r);
__builtin_trap();
}
}
return memfs::devfs_root;
}
fbl::RefPtr<memfs::VnodeDir> BootfsRoot() {
if (memfs::bootfs_root == nullptr) {
zx_status_t r = memfs_create_fs("boot", &memfs::root_vfs, &memfs::bootfs_root);
if (r < 0) {
printf("fatal error %d allocating 'boot' file system\n", r);
__builtin_trap();
}
}
return memfs::bootfs_root;
}
zx_status_t devfs_mount(zx_handle_t h) {
return DevfsRoot()->AttachRemote(fs::MountChannel(h));
}
VnodeDir* systemfs_get_root() {
return SystemfsRoot().get();
}
void systemfs_set_readonly(bool value) {
SystemfsRoot()->vfs()->SetReadonly(value);
}
static zx_status_t add_vmofile(fbl::RefPtr<VnodeDir> vnb, const char* path, zx_handle_t vmo,
zx_off_t off, size_t len) {
zx_status_t r;
if ((path[0] == '/') || (path[0] == 0))
return ZX_ERR_INVALID_ARGS;
for (;;) {
const char* nextpath = strchr(path, '/');
if (nextpath == nullptr) {
if (path[0] == 0) {
return ZX_ERR_INVALID_ARGS;
}
bool vmofile = true;
return vnb->vfs()->CreateFromVmo(vnb.get(), vmofile,
fbl::StringPiece(path, strlen(path)), vmo, off, len);
} else {
if (nextpath == path) {
return ZX_ERR_INVALID_ARGS;
}
fbl::RefPtr<fs::Vnode> out;
r = vnb->Lookup(&out, fbl::StringPiece(path, nextpath - path));
if (r == ZX_ERR_NOT_FOUND) {
r = vnb->Create(&out, fbl::StringPiece(path, nextpath - path), S_IFDIR);
}
if (r < 0) {
return r;
}
vnb = fbl::RefPtr<VnodeDir>::Downcast(fbl::move(out));
path = nextpath + 1;
}
}
}
zx_status_t bootfs_add_file(const char* path, zx_handle_t vmo, zx_off_t off, size_t len) {
return add_vmofile(BootfsRoot(), path, vmo, off, len);
}
zx_status_t systemfs_add_file(const char* path, zx_handle_t vmo, zx_off_t off, size_t len) {
return add_vmofile(SystemfsRoot(), path, vmo, off, len);
}
// Hardcoded initialization function to create/access global root directory
VnodeDir* vfs_create_global_root() {
if (memfs::global_root == nullptr) {
zx_status_t r = memfs_create_fs("<root>", &memfs::root_vfs, &memfs::global_root);
if (r < 0) {
printf("fatal error %d allocating root file system\n", r);
__builtin_trap();
}
memfs::root_vfs.MountSubtree(memfs::global_root.get(), DevfsRoot());
memfs::root_vfs.MountSubtree(memfs::global_root.get(), BootfsRoot());
memfs::root_vfs.MountSubtree(memfs::global_root.get(), MemfsRoot());
fbl::RefPtr<fs::Vnode> vn;
fbl::StringPiece pathout;
ZX_ASSERT(memfs::root_vfs.Open(memfs::global_root, &vn, fbl::StringPiece("/data"), &pathout,
ZX_FS_RIGHT_READABLE | ZX_FS_FLAG_CREATE, S_IFDIR) == ZX_OK);
ZX_ASSERT(memfs::root_vfs.Open(memfs::global_root, &vn, fbl::StringPiece("/volume"), &pathout,
ZX_FS_RIGHT_READABLE | ZX_FS_FLAG_CREATE, S_IFDIR) == ZX_OK);
memfs::global_loop.reset(new async::Loop());
memfs::global_loop->StartThread("root-dispatcher");
memfs::root_vfs.set_async(memfs::global_loop->async());
memfs::system_vfs.set_async(memfs::global_loop->async());
}
return memfs::global_root.get();
}
void devmgr_vfs_exit() {
memfs::root_vfs.UninstallAll(zx_deadline_after(ZX_SEC(5)));
memfs::system_vfs.UninstallAll(zx_deadline_after(ZX_SEC(5)));
}
zx_status_t memfs_mount(VnodeDir* parent, const char* name, VnodeDir* subtree) {
fbl::RefPtr<fs::Vnode> vn;
zx_status_t status = parent->Create(&vn, fbl::StringPiece(name), S_IFDIR);
if (status != ZX_OK)
return status;
zx_handle_t h = vfs_create_root_handle(subtree);
// TODO(abarth): vfs_create_root_handle can fail but has no way to report
// its failures now that zx_handle_t is unsigned.
return parent->vfs()->InstallRemote(fbl::move(vn), fs::MountChannel(h));
}
// Acquire the root vnode and return a handle to it through the VFS dispatcher
zx_handle_t vfs_create_root_handle(VnodeMemfs* vn) {
zx_status_t r;
zx::channel h1, h2;
if ((r = zx::channel::create(0, &h1, &h2)) != ZX_OK) {
return r;
}
if ((r = vn->vfs()->ServeDirectory(fbl::RefPtr<fs::Vnode>(vn),
fbl::move(h1))) != ZX_OK) {
return r;
}
return h2.release();
}
zx_status_t vfs_connect_root_handle(VnodeMemfs* vn, zx_handle_t h) {
zx::channel ch(h);
return vn->vfs()->ServeDirectory(fbl::RefPtr<fs::Vnode>(vn), fbl::move(ch));
}