blob: 9a0920213dffadfc8c6e5182ad45a749af9642ad [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 <ddk/device.h>
#include <fs/vfs.h>
#include <magenta/device/vfs.h>
#include <magenta/thread_annotations.h>
#include <mxalloc/new.h>
#include <mxio/debug.h>
#include <mxio/vfs.h>
#include <mxtl/algorithm.h>
#include <mxtl/auto_lock.h>
#include <mxtl/ref_ptr.h>
#include <mxtl/unique_ptr.h>
#include "devmgr.h"
#include "dnode.h"
#include "memfs-private.h"
#define MXDEBUG 0
namespace memfs {
mxtl::unique_ptr<fs::Dispatcher> memfs_global_dispatcher;
constexpr size_t kMemfsMaxFileSize = (8192 * 8192);
static mxtl::RefPtr<VnodeDir> vfs_root = nullptr;
static mxtl::RefPtr<VnodeDir> memfs_root = nullptr;
static mxtl::RefPtr<VnodeDir> devfs_root = nullptr;
static mxtl::RefPtr<VnodeDir> bootfs_root = nullptr;
static mxtl::RefPtr<VnodeDir> systemfs_root = nullptr;
static bool WindowMatchesVMO(mx_handle_t vmo, mx_off_t offset, mx_off_t length) {
if (offset != 0)
return false;
uint64_t size;
if (mx_vmo_get_size(vmo, &size) < 0)
return false;
return size == length;
}
VnodeMemfs::VnodeMemfs() : seqcount_(0), dnode_(nullptr), link_count_(0) {
create_time_ = modify_time_ = mx_time_get(MX_CLOCK_UTC);
}
VnodeMemfs::~VnodeMemfs() {
}
fs::Dispatcher* VnodeMemfs::GetDispatcher() {
return memfs_global_dispatcher.get();
}
VnodeFile::VnodeFile() : vmo_(MX_HANDLE_INVALID), length_(0) {}
VnodeFile::VnodeFile(mx_handle_t vmo, mx_off_t length) : vmo_(vmo), length_(length) {}
VnodeFile::~VnodeFile() {
if (vmo_ != MX_HANDLE_INVALID) {
mx_handle_close(vmo_);
}
}
VnodeDir::VnodeDir() {
link_count_ = 1; // Implied '.'
}
VnodeDir::~VnodeDir() {}
VnodeVmo::VnodeVmo(mx_handle_t vmo, mx_off_t offset, mx_off_t length) :
vmo_(vmo), offset_(offset), length_(length), have_local_clone_(false) {}
VnodeVmo::~VnodeVmo() {
if (have_local_clone_) {
mx_handle_close(vmo_);
}
}
mx_status_t VnodeDir::Open(uint32_t flags) {
switch (flags & O_ACCMODE) {
case O_WRONLY:
case O_RDWR:
return MX_ERR_NOT_FILE;
}
return MX_OK;
}
mx_status_t VnodeFile::Open(uint32_t flags) {
if (flags & O_DIRECTORY) {
return MX_ERR_NOT_DIR;
}
return MX_OK;
}
mx_status_t VnodeVmo::Open(uint32_t flags) {
if (flags & O_DIRECTORY) {
return MX_ERR_NOT_DIR;
}
switch (flags & O_ACCMODE) {
case O_WRONLY:
case O_RDWR:
return MX_ERR_ACCESS_DENIED;
}
return MX_OK;
}
mx_status_t VnodeVmo::Serve(mx_handle_t h, uint32_t flags) {
mx_handle_close(h);
return MX_OK;
}
mx_status_t VnodeVmo::GetHandles(uint32_t flags, mx_handle_t* hnds,
uint32_t* type, void* extra, uint32_t* esize) {
mx_off_t* off = static_cast<mx_off_t*>(extra);
mx_off_t* len = off + 1;
mx_handle_t vmo;
mx_status_t status;
if (!have_local_clone_ && !WindowMatchesVMO(vmo_, offset_, length_)) {
status = mx_vmo_clone(vmo_, MX_VMO_CLONE_COPY_ON_WRITE, offset_, length_, &vmo_);
if (status < 0)
return status;
offset_ = 0;
have_local_clone_ = true;
}
status = mx_handle_duplicate(
vmo_,
MX_RIGHT_READ | MX_RIGHT_EXECUTE | MX_RIGHT_MAP |
MX_RIGHT_DUPLICATE | MX_RIGHT_TRANSFER | MX_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 = MXIO_PROTOCOL_VMOFILE;
*esize = sizeof(mx_off_t) * 2;
return 1;
}
ssize_t VnodeFile::Read(void* data, size_t len, size_t off) {
if ((off >= length_) || (vmo_ == MX_HANDLE_INVALID)) {
return 0;
}
size_t actual;
mx_status_t status;
if ((status = mx_vmo_read(vmo_, data, off, len, &actual)) != MX_OK) {
return status;
}
return actual;
}
ssize_t VnodeVmo::Read(void* data, size_t len, size_t off) {
if (off > length_)
return 0;
size_t rlen = length_ - off;
if (len > rlen)
len = rlen;
mx_status_t r = mx_vmo_read(vmo_, data, offset_ + off, len, &len);
if (r < 0) {
return r;
}
return len;
}
ssize_t VnodeFile::Write(const void* data, size_t len, size_t off) {
mx_status_t status;
size_t newlen = off + len;
newlen = newlen > kMemfsMaxFileSize ? kMemfsMaxFileSize : newlen;
// TODO(smklein): Round up to PAGE_SIZE increments to reduce overhead on a series of small
// writes.
if (vmo_ == MX_HANDLE_INVALID) {
// First access to the file? Allocate it.
if ((status = mx_vmo_create(newlen, 0, &vmo_)) != MX_OK) {
return status;
}
} else if (newlen > length_) {
// Accessing beyond the end of the file? Extend it.
if ((status = mx_vmo_set_size(vmo_, newlen)) != MX_OK) {
return status;
}
}
size_t actual;
if ((status = mx_vmo_write(vmo_, data, off, len, &actual)) != MX_OK) {
return status;
}
if (newlen > length_) {
length_ = newlen;
}
if (actual == 0 && off >= kMemfsMaxFileSize) {
// short write because we're beyond the end of the permissible length
return MX_ERR_FILE_BIG;
}
modify_time_ = mx_time_get(MX_CLOCK_UTC);
return actual;
}
bool VnodeDir::IsRemote() const { return remoter_.IsRemote(); }
mx_handle_t VnodeDir::DetachRemote() { return remoter_.DetachRemote(flags_); }
mx_handle_t VnodeDir::WaitForRemote() { return remoter_.WaitForRemote(flags_); }
mx_handle_t VnodeDir::GetRemote() const { return remoter_.GetRemote(); }
void VnodeDir::SetRemote(mx_handle_t remote) { return remoter_.SetRemote(remote); }
mx_status_t VnodeDir::Lookup(mxtl::RefPtr<fs::Vnode>* out, const char* name, size_t len) {
if (!IsDirectory()) {
return MX_ERR_NOT_FOUND;
}
mxtl::RefPtr<Dnode> dn;
mx_status_t r = dnode_->Lookup(name, len, &dn);
MX_DEBUG_ASSERT(r <= 0);
if (r == MX_OK) {
if (dn == nullptr) {
// Looking up our own vnode
*out = mxtl::RefPtr<VnodeDir>(this);
} else {
// Looking up a different vnode
*out = dn->AcquireVnode();
}
}
return r;
}
constexpr uint64_t kMemfsBlksize = PAGE_SIZE;
mx_status_t VnodeFile::Getattr(vnattr_t* attr) {
memset(attr, 0, sizeof(vnattr_t));
attr->mode = V_TYPE_FILE | V_IRUSR | V_IWUSR;
attr->size = length_;
attr->blksize = kMemfsBlksize;
attr->blkcount = mxtl::roundup(attr->size, kMemfsBlksize) / VNATTR_BLKSIZE;
attr->nlink = link_count_;
attr->create_time = create_time_;
attr->modify_time = modify_time_;
return MX_OK;
}
mx_status_t VnodeDir::Getattr(vnattr_t* attr) {
memset(attr, 0, sizeof(vnattr_t));
attr->mode = V_TYPE_DIR | V_IRUSR;
attr->size = 0;
attr->blksize = kMemfsBlksize;
attr->blkcount = mxtl::roundup(attr->size, kMemfsBlksize) / VNATTR_BLKSIZE;
attr->nlink = link_count_;
attr->create_time = create_time_;
attr->modify_time = modify_time_;
return MX_OK;
}
mx_status_t VnodeVmo::Getattr(vnattr_t* attr) {
memset(attr, 0, sizeof(vnattr_t));
attr->mode = V_TYPE_FILE | V_IRUSR;
attr->size = length_;
attr->blksize = kMemfsBlksize;
attr->blkcount = mxtl::roundup(attr->size, kMemfsBlksize) / VNATTR_BLKSIZE;
attr->nlink = link_count_;
attr->create_time = create_time_;
attr->modify_time = modify_time_;
return MX_OK;
}
mx_status_t VnodeMemfs::Setattr(vnattr_t* attr) {
if ((attr->valid & ~(ATTR_MTIME)) != 0) {
// only attr currently supported
return MX_ERR_INVALID_ARGS;
}
if (attr->valid & ATTR_MTIME) {
modify_time_ = attr->modify_time;
}
return MX_OK;
}
mx_status_t VnodeDir::Readdir(void* cookie, void* data, size_t len) {
fs::DirentFiller df(data, len);
if (!IsDirectory()) {
// This WAS a directory, but it has been deleted.
Dnode::ReaddirStart(&df, cookie);
return df.BytesFilled();
}
dnode_->Readdir(&df, cookie);
return df.BytesFilled();
}
// postcondition: reference taken on vn returned through "out"
mx_status_t VnodeDir::Create(mxtl::RefPtr<fs::Vnode>* out, const char* name, size_t len, uint32_t mode) {
mx_status_t status;
if ((status = CanCreate(name, len)) != MX_OK) {
return status;
}
AllocChecker ac;
mxtl::RefPtr<memfs::VnodeMemfs> vn;
if (S_ISDIR(mode)) {
vn = mxtl::AdoptRef(new (&ac) memfs::VnodeDir());
} else {
vn = mxtl::AdoptRef(new (&ac) memfs::VnodeFile());
}
if (!ac.check()) {
return MX_ERR_NO_MEMORY;
}
if ((status = AttachVnode(vn, name, len, S_ISDIR(mode))) != MX_OK) {
return status;
}
*out = mxtl::move(vn);
return status;
}
mx_status_t VnodeDir::Unlink(const char* name, size_t len, bool must_be_dir) {
xprintf("memfs_unlink(%p,'%.*s')\n", this, (int)len, name);
if (!IsDirectory()) {
// Calling unlink from unlinked, empty directory
return MX_ERR_BAD_STATE;
}
mxtl::RefPtr<Dnode> dn;
mx_status_t r;
if ((r = dnode_->Lookup(name, len, &dn)) != MX_OK) {
return r;
} else if (dn == nullptr) {
// Cannot unlink directory 'foo' using the argument 'foo/.'
return MX_ERR_UNAVAILABLE;
} else if (!dn->IsDirectory() && must_be_dir) {
// Path ending in "/" was requested, implying that the dnode must be a directory
return MX_ERR_NOT_DIR;
} else if ((r = dn->CanUnlink()) != MX_OK) {
return r;
}
dn->Detach();
return MX_OK;
}
mx_status_t VnodeFile::Truncate(size_t len) {
mx_status_t status;
len = len > kMemfsMaxFileSize ? kMemfsMaxFileSize : len;
if (vmo_ == MX_HANDLE_INVALID) {
// First access to the file? Allocate it.
if ((status = mx_vmo_create(len, 0, &vmo_)) != MX_OK) {
return status;
}
} else if ((len < length_) && (len % PAGE_SIZE != 0)) {
// TODO(smklein): Remove this case when the VMO system causes 'shrinking to a partial page'
// to fill the end of that page with zeroes.
//
// 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 = mx_vmo_write(vmo_, buf, len, ppage_size, &actual);
if ((status != MX_OK) || (actual != ppage_size)) {
return status != MX_OK ? MX_ERR_IO : status;
} else if ((status = mx_vmo_set_size(vmo_, len)) != MX_OK) {
return status;
}
} else if ((status = mx_vmo_set_size(vmo_, len)) != MX_OK) {
return status;
}
length_ = len;
modify_time_ = mx_time_get(MX_CLOCK_UTC);
return MX_OK;
}
mx_status_t VnodeDir::Rename(mxtl::RefPtr<fs::Vnode> _newdir, const char* oldname, size_t oldlen,
const char* newname, size_t newlen, bool src_must_be_dir,
bool dst_must_be_dir) {
auto newdir = mxtl::RefPtr<VnodeMemfs>::Downcast(mxtl::move(_newdir));
if (!IsDirectory() || !newdir->IsDirectory())
return MX_ERR_BAD_STATE;
if ((oldlen == 1) && (oldname[0] == '.'))
return MX_ERR_BAD_STATE;
if ((oldlen == 2) && (oldname[0] == '.') && (oldname[1] == '.'))
return MX_ERR_BAD_STATE;
if ((newlen == 1) && (newname[0] == '.'))
return MX_ERR_BAD_STATE;
if ((newlen == 2) && (newname[0] == '.') && (newname[1] == '.'))
return MX_ERR_BAD_STATE;
mxtl::RefPtr<Dnode> olddn;
mx_status_t r;
// The source must exist
if ((r = dnode_->Lookup(oldname, oldlen, &olddn)) != MX_OK) {
return r;
}
MX_DEBUG_ASSERT(olddn != nullptr);
if (!olddn->IsDirectory() && (src_must_be_dir || dst_must_be_dir)) {
return MX_ERR_NOT_DIR;
}
// Verify that the destination is not a subdirectory of the source (if
// both are directories).
if (olddn->IsSubdirectory(newdir->dnode_)) {
return MX_ERR_INVALID_ARGS;
}
// The destination may or may not exist
mxtl::RefPtr<Dnode> targetdn;
r = newdir->dnode_->Lookup(newname, newlen, &targetdn);
bool target_exists = (r == MX_OK);
if (target_exists) {
MX_DEBUG_ASSERT(targetdn != nullptr);
// The target exists. Validate and unlink it.
if (olddn == targetdn) {
// Cannot rename node to itself
return MX_ERR_INVALID_ARGS;
}
if (olddn->IsDirectory() != targetdn->IsDirectory()) {
// Cannot rename files to directories (and vice versa)
return MX_ERR_INVALID_ARGS;
} else if ((r = targetdn->CanUnlink()) != MX_OK) {
return r;
}
} else if (r != MX_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.
mxtl::unique_ptr<char[]> namebuffer(nullptr);
if (target_exists) {
targetdn->Detach();
namebuffer = mxtl::move(targetdn->TakeName());
} else {
AllocChecker ac;
namebuffer.reset(new (&ac) char[newlen + 1]);
if (!ac.check()) {
return MX_ERR_NO_MEMORY;
}
memcpy(namebuffer.get(), newname, newlen);
namebuffer[newlen] = '\0';
}
// NOTE:
//
// Validation ends here, and modifications begin. Rename should not fail
// beyond this point.
olddn->RemoveFromParent();
olddn->PutName(mxtl::move(namebuffer), newlen);
Dnode::AddChild(newdir->dnode_, mxtl::move(olddn));
return MX_OK;
}
mx_status_t VnodeDir::Link(const char* name, size_t len, mxtl::RefPtr<fs::Vnode> target) {
auto vn = mxtl::RefPtr<VnodeMemfs>::Downcast(mxtl::move(target));
if ((len == 1) && (name[0] == '.')) {
return MX_ERR_BAD_STATE;
} else if ((len == 2) && (name[0] == '.') && (name[1] == '.')) {
return MX_ERR_BAD_STATE;
} else if (!IsDirectory()) {
// Empty, unlinked parent
return MX_ERR_BAD_STATE;
}
if (vn->IsDirectory()) {
// The target must not be a directory
return MX_ERR_NOT_FILE;
}
if (dnode_->Lookup(name, len, nullptr) == MX_OK) {
// The destination should not exist
return MX_ERR_ALREADY_EXISTS;
}
// Make a new dnode for the new name, attach the target vnode to it
mxtl::RefPtr<Dnode> targetdn;
if ((targetdn = Dnode::Create(name, len, vn)) == nullptr) {
return MX_ERR_NO_MEMORY;
}
// Attach the new dnode to its parent
Dnode::AddChild(dnode_, mxtl::move(targetdn));
return MX_OK;
}
mx_status_t VnodeMemfs::Sync() {
// Since this filesystem is in-memory, all data is already up-to-date in
// the underlying storage
return MX_OK;
}
constexpr const char kFsName[] = "memfs";
ssize_t VnodeMemfs::Ioctl(uint32_t op, const void* in_buf, size_t in_len,
void* out_buf, size_t out_len) {
switch (op) {
case IOCTL_VFS_MOUNT_BOOTFS_VMO: {
if (in_len < sizeof(mx_handle_t)) {
return MX_ERR_INVALID_ARGS;
}
const mx_handle_t* vmo = static_cast<const mx_handle_t*>(in_buf);
return devmgr_add_systemfs_vmo(*vmo);
}
case IOCTL_VFS_QUERY_FS: {
if (out_len < sizeof(vfs_query_info_t)) {
return MX_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;
strcpy(info->name, kFsName);
return sizeof(*info);
}
default:
return MX_ERR_NOT_SUPPORTED;
}
}
ssize_t VnodeDir::Ioctl(uint32_t op, const void* in_buf, size_t in_len,
void* out_buf, size_t out_len) {
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;
const char* name = config->name;
if (in_len <= sizeof(vmo_create_config_t) || (namelen > NAME_MAX) ||
(name[namelen] != 0)) {
mx_handle_close(config->vmo);
return MX_ERR_INVALID_ARGS;
}
// Ensure this is the last handle to this VMO; otherwise, the size
// may change from underneath us.
mx_signals_t observed;
mx_status_t status = mx_object_wait_one(config->vmo, MX_SIGNAL_LAST_HANDLE, 0u, &observed);
if ((status != MX_OK) || (observed != MX_SIGNAL_LAST_HANDLE)) {
mx_handle_close(config->vmo);
return MX_ERR_INVALID_ARGS;
}
uint64_t size;
if ((status = mx_vmo_get_size(config->vmo, &size)) != MX_OK) {
mx_handle_close(config->vmo);
return status;
}
mxtl::AutoLock lock(&vfs_lock);
bool vmofile = false;
return CreateFromVmo(vmofile, name, namelen, config->vmo, 0, size);
}
default:
return VnodeMemfs::Ioctl(op, in_buf, in_len, out_buf, out_len);
}
}
mx_status_t VnodeMemfs::AttachRemote(mx_handle_t h) {
if (!IsDirectory()) {
return MX_ERR_NOT_DIR;
} else if (IsRemote()) {
return MX_ERR_ALREADY_BOUND;
}
SetRemote(h);
return MX_OK;
}
static mx_status_t memfs_create_fs(const char* name, mxtl::RefPtr<VnodeDir>* out) {
AllocChecker ac;
mxtl::RefPtr<VnodeDir> fs = mxtl::AdoptRef(new (&ac) VnodeDir());
if (!ac.check()) {
return MX_ERR_NO_MEMORY;
}
mxtl::RefPtr<Dnode> dn = Dnode::Create(name, strlen(name), fs);
if (dn == nullptr) {
return MX_ERR_NO_MEMORY;
}
fs->dnode_ = dn; // FS root is directory
*out = fs;
return MX_OK;
}
static void memfs_mount_locked(mxtl::RefPtr<VnodeDir> parent, mxtl::RefPtr<VnodeDir> subtree) TA_REQ(vfs_lock) {
Dnode::AddChild(parent->dnode_, subtree->dnode_);
}
mx_status_t VnodeDir::CreateFromVmo(bool vmofile, const char* name, size_t namelen,
mx_handle_t vmo, mx_off_t off, mx_off_t len) {
mx_status_t status;
if ((status = CanCreate(name, namelen)) != MX_OK) {
return status;
}
AllocChecker ac;
mxtl::RefPtr<VnodeMemfs> vn;
if (vmofile) {
vn = mxtl::AdoptRef(new (&ac) VnodeVmo(vmo, off, len));
} else {
vn = mxtl::AdoptRef(new (&ac) VnodeFile(vmo, len));
}
if (!ac.check()) {
return MX_ERR_NO_MEMORY;
}
if ((status = AttachVnode(mxtl::move(vn), name, namelen, false)) != MX_OK) {
return status;
}
return MX_OK;
}
mx_status_t VnodeDir::CanCreate(const char* name, size_t namelen) const {
if (!IsDirectory()) {
return MX_ERR_INVALID_ARGS;
}
mx_status_t status;
if ((status = dnode_->Lookup(name, namelen, nullptr)) == MX_ERR_NOT_FOUND) {
return MX_OK;
} else if (status == MX_OK) {
return MX_ERR_ALREADY_EXISTS;
}
return status;
}
mx_status_t VnodeDir::AttachVnode(mxtl::RefPtr<VnodeMemfs> vn, const char* name, size_t namelen,
bool isdir) {
// dnode takes a reference to the vnode
mxtl::RefPtr<Dnode> dn;
if ((dn = Dnode::Create(name, namelen, vn)) == nullptr) {
return MX_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_, mxtl::move(dn));
return MX_OK;
}
} // namespace memfs
// The following functions exist outside the memfs namespace so they can
// be exposed to C:
// postcondition: new vnode linked into namespace
mx_status_t memfs_create_directory(const char* path, uint32_t flags) {
mx_status_t r;
const char* pathout;
mxtl::RefPtr<fs::Vnode> parent_vn;
if ((r = fs::Vfs::Walk(memfs::vfs_root, &parent_vn, path, &pathout)) < 0) {
return r;
}
mxtl::RefPtr<memfs::VnodeDir> parent =
mxtl::RefPtr<memfs::VnodeDir>::Downcast(mxtl::move(parent_vn));
if (strcmp(pathout, "") == 0) {
return MX_ERR_ALREADY_EXISTS;
}
mxtl::RefPtr<fs::Vnode> out;
r = parent->Create(&out, pathout, strlen(pathout), S_IFDIR);
if (r < 0) {
return r;
}
return r;
}
mxtl::RefPtr<memfs::VnodeDir> SystemfsRoot() {
if (memfs::systemfs_root == nullptr) {
mx_status_t r = memfs_create_fs("system", &memfs::systemfs_root);
if (r < 0) {
printf("fatal error %d allocating 'system' file system\n", r);
panic();
}
}
return memfs::systemfs_root;
}
mxtl::RefPtr<memfs::VnodeDir> MemfsRoot() {
if (memfs::memfs_root == nullptr) {
mx_status_t r = memfs_create_fs("tmp", &memfs::memfs_root);
if (r < 0) {
printf("fatal error %d allocating 'tmp' file system\n", r);
panic();
}
}
return memfs::memfs_root;
}
mxtl::RefPtr<memfs::VnodeDir> DevfsRoot() {
if (memfs::devfs_root == nullptr) {
mx_status_t r = memfs_create_fs("dev", &memfs::devfs_root);
if (r < 0) {
printf("fatal error %d allocating 'device' file system\n", r);
panic();
}
}
return memfs::devfs_root;
}
mxtl::RefPtr<memfs::VnodeDir> BootfsRoot() {
if (memfs::bootfs_root == nullptr) {
mx_status_t r = memfs_create_fs("boot", &memfs::bootfs_root);
if (r < 0) {
printf("fatal error %d allocating 'boot' file system\n", r);
panic();
}
}
return memfs::bootfs_root;
}
mx_status_t devfs_mount(mx_handle_t h) {
return DevfsRoot()->AttachRemote(h);
}
VnodeDir* systemfs_get_root() {
return SystemfsRoot().get();
}
// Hardcoded initialization function to create/access global root directory
VnodeDir* vfs_create_global_root() {
if (memfs::vfs_root == nullptr) {
mx_status_t r = memfs_create_fs("<root>", &memfs::vfs_root);
if (r < 0) {
printf("fatal error %d allocating root file system\n", r);
panic();
}
memfs_mount_locked(memfs::vfs_root, DevfsRoot());
memfs_mount_locked(memfs::vfs_root, BootfsRoot());
memfs_mount_locked(memfs::vfs_root, MemfsRoot());
memfs_create_directory("/data", 0);
memfs_create_directory("/volume", 0);
memfs_create_directory("/dev/socket", 0);
}
return memfs::vfs_root.get();
}
void memfs_mount(memfs::VnodeDir* parent, memfs::VnodeDir* subtree) {
mxtl::AutoLock lock(&vfs_lock);
memfs_mount_locked(mxtl::RefPtr<VnodeDir>(parent), mxtl::RefPtr<VnodeDir>(subtree));
}