blob: 81d87141f6b08a1a59ac902212e7371fc6c200b2 [file] [log] [blame]
// Copyright 2017 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 <fbl/algorithm.h>
#include <fbl/alloc_checker.h>
#include <fbl/atomic.h>
#include <fbl/ref_ptr.h>
#include <fbl/unique_ptr.h>
#include <fdio/vfs.h>
#include <fs/vfs.h>
#include <memfs/vnode.h>
#include <zircon/device/vfs.h>
#include "dnode.h"
namespace memfs {
VnodeDir::VnodeDir(Vfs* vfs) : VnodeMemfs(vfs) {
link_count_ = 1; // Implied '.'
}
VnodeDir::~VnodeDir() {}
zx_status_t VnodeDir::ValidateFlags(uint32_t flags) {
if (flags & ZX_FS_RIGHT_WRITABLE) {
return ZX_ERR_NOT_FILE;
}
return ZX_OK;
}
void VnodeDir::Notify(fbl::StringPiece name, unsigned event) { watcher_.Notify(name, event); }
zx_status_t VnodeDir::WatchDir(fs::Vfs* vfs, const vfs_watch_dir_t* cmd) {
return watcher_.WatchDir(vfs, this, cmd);
}
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;
}
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 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 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;
} else if ((newdir->ino() == ino_) && (oldname == newname)) {
// Renaming a file or directory to itself?
// Shortcut success case
return ZX_OK;
}
// 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 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);
}
}
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