blob: 46f21a8e4e661ae071bf69a601336ed5695ef38b [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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fs/vfs.h>
#include <fbl/alloc_checker.h>
#include <fbl/ref_ptr.h>
#include <fbl/unique_ptr.h>
#include <lib/memfs/cpp/vnode.h>
#include <utility>
#include "dnode.h"
namespace memfs {
// Create a new dnode and attach it to a vnode
fbl::RefPtr<Dnode> Dnode::Create(fbl::StringPiece name, fbl::RefPtr<VnodeMemfs> vn) {
if ((name.length() > kDnodeNameMax) || (name.length() < 1)) {
return nullptr;
}
fbl::AllocChecker ac;
fbl::unique_ptr<char[]> namebuffer (new (&ac) char[name.length() + 1]);
if (!ac.check()) {
return nullptr;
}
memcpy(namebuffer.get(), name.data(), name.length());
namebuffer[name.length()] = '\0';
fbl::RefPtr<Dnode> dn = fbl::AdoptRef(new (&ac) Dnode(vn, std::move(namebuffer),
static_cast<uint32_t>(name.length())));
if (!ac.check()) {
return nullptr;
}
return dn;
}
void Dnode::RemoveFromParent() {
ZX_DEBUG_ASSERT(vnode_ != nullptr);
// Detach from parent
if (parent_) {
parent_->children_.erase(*this);
if (IsDirectory()) {
// '..' no longer references parent.
parent_->vnode_->link_count_--;
}
parent_->vnode_->UpdateModified();
parent_ = nullptr;
vnode_->link_count_--;
}
}
void Dnode::Detach() {
ZX_DEBUG_ASSERT(children_.is_empty());
if (vnode_ == nullptr) { // Dnode already detached.
return;
}
RemoveFromParent();
// Detach from vnode
vnode_->dnode_ = nullptr;
vnode_ = nullptr;
}
void Dnode::AddChild(fbl::RefPtr<Dnode> parent, fbl::RefPtr<Dnode> child) {
ZX_DEBUG_ASSERT(parent != nullptr);
ZX_DEBUG_ASSERT(child != nullptr);
ZX_DEBUG_ASSERT(child->parent_ == nullptr); // Child shouldn't have a parent
ZX_DEBUG_ASSERT(child != parent);
ZX_DEBUG_ASSERT(parent->IsDirectory());
child->parent_ = parent;
child->vnode_->link_count_++;
if (child->IsDirectory()) {
// Child has '..' pointing back at parent.
parent->vnode_->link_count_++;
}
// Ensure that the ordering of tokens in the children list is absolute.
if (parent->children_.is_empty()) {
child->ordering_token_ = 2; // '0' for '.', '1' for '..'
} else {
child->ordering_token_ = parent->children_.back().ordering_token_ + 1;
}
parent->children_.push_back(std::move(child));
parent->vnode_->UpdateModified();
}
zx_status_t Dnode::Lookup(fbl::StringPiece name, fbl::RefPtr<Dnode>* out) const {
auto dn = children_.find_if([&name](const Dnode& elem) -> bool {
return elem.NameMatch(name);
});
if (dn == children_.end()) {
return ZX_ERR_NOT_FOUND;
}
if (out != nullptr) {
*out = dn.CopyPointer();
}
return ZX_OK;
}
fbl::RefPtr<VnodeMemfs> Dnode::AcquireVnode() const {
return vnode_;
}
zx_status_t Dnode::CanUnlink() const {
if (!children_.is_empty()) {
// Cannot unlink non-empty directory
return ZX_ERR_NOT_EMPTY;
} else if (vnode_->IsRemote()) {
// Cannot unlink mount points
return ZX_ERR_UNAVAILABLE;
}
return ZX_OK;
}
struct dircookie_t {
size_t order; // Minimum 'order' of the next dnode dirent to be read.
};
static_assert(sizeof(dircookie_t) <= sizeof(fs::vdircookie_t),
"MemFS dircookie too large to fit in IO state");
// Read the canned "." and ".." entries that should
// appear at the beginning of a directory.
zx_status_t Dnode::ReaddirStart(fs::DirentFiller* df, void* cookie) {
dircookie_t* c = static_cast<dircookie_t*>(cookie);
zx_status_t r;
if (c->order == 0) {
// TODO(smklein): Return the real ino.
uint64_t ino = fuchsia_io_INO_UNKNOWN;
if ((r = df->Next(".", VTYPE_TO_DTYPE(V_TYPE_DIR), ino)) != ZX_OK) {
return r;
}
c->order++;
}
return ZX_OK;
}
void Dnode::Readdir(fs::DirentFiller* df, void* cookie) const {
dircookie_t* c = static_cast<dircookie_t*>(cookie);
zx_status_t r = 0;
if (c->order < 1) {
if ((r = Dnode::ReaddirStart(df, cookie)) != ZX_OK) {
return;
}
}
for (const auto& dn : children_) {
if (dn.ordering_token_ < c->order) {
continue;
}
uint32_t vtype = dn.IsDirectory() ? V_TYPE_DIR : V_TYPE_FILE;
if ((r = df->Next(fbl::StringPiece(dn.name_.get(), dn.NameLen()),
VTYPE_TO_DTYPE(vtype), dn.AcquireVnode()->ino())) != ZX_OK) {
return;
}
c->order = dn.ordering_token_ + 1;
}
}
// Answers the question: "Is dn a subdirectory of this?"
bool Dnode::IsSubdirectory(fbl::RefPtr<Dnode> dn) const {
if (IsDirectory() && dn->IsDirectory()) {
// Iterate all the way up to root
while (dn->parent_ != nullptr && dn->parent_ != dn) {
if (vnode_ == dn->vnode_) {
return true;
}
dn = dn->parent_;
}
}
return false;
}
fbl::unique_ptr<char[]> Dnode::TakeName() {
return std::move(name_);
}
void Dnode::PutName(fbl::unique_ptr<char[]> name, size_t len) {
flags_ = static_cast<uint32_t>((flags_ & ~kDnodeNameMax) | len);
name_ = std::move(name);
}
bool Dnode::IsDirectory() const { return vnode_->IsDirectory(); }
Dnode::Dnode(fbl::RefPtr<VnodeMemfs> vn, fbl::unique_ptr<char[]> name, uint32_t flags) :
vnode_(std::move(vn)), parent_(nullptr), ordering_token_(0), flags_(flags), name_(std::move(name)) {
}
size_t Dnode::NameLen() const {
return flags_ & kDnodeNameMax;
}
bool Dnode::NameMatch(fbl::StringPiece name) const {
return name == fbl::StringPiece(name_.get(), NameLen());
}
} // namespace memfs