1// Copyright 2016 The Fuchsia Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include <stdio.h> 6#include <stdlib.h> 7#include <string.h> 8 9#include <fs/vfs.h> 10#include <fbl/alloc_checker.h> 11#include <fbl/ref_ptr.h> 12#include <fbl/unique_ptr.h> 13#include <lib/memfs/cpp/vnode.h> 14 15#include "dnode.h" 16 17namespace memfs { 18 19// Create a new dnode and attach it to a vnode 20fbl::RefPtr<Dnode> Dnode::Create(fbl::StringPiece name, fbl::RefPtr<VnodeMemfs> vn) { 21 if ((name.length() > kDnodeNameMax) || (name.length() < 1)) { 22 return nullptr; 23 } 24 25 fbl::AllocChecker ac; 26 fbl::unique_ptr<char[]> namebuffer (new (&ac) char[name.length() + 1]); 27 if (!ac.check()) { 28 return nullptr; 29 } 30 memcpy(namebuffer.get(), name.data(), name.length()); 31 namebuffer[name.length()] = '\0'; 32 fbl::RefPtr<Dnode> dn = fbl::AdoptRef(new (&ac) Dnode(vn, fbl::move(namebuffer), 33 static_cast<uint32_t>(name.length()))); 34 if (!ac.check()) { 35 return nullptr; 36 } 37 38 return dn; 39} 40 41void Dnode::RemoveFromParent() { 42 ZX_DEBUG_ASSERT(vnode_ != nullptr); 43 44 // Detach from parent 45 if (parent_) { 46 parent_->children_.erase(*this); 47 if (IsDirectory()) { 48 // '..' no longer references parent. 49 parent_->vnode_->link_count_--; 50 } 51 parent_->vnode_->UpdateModified(); 52 parent_ = nullptr; 53 vnode_->link_count_--; 54 } 55} 56 57void Dnode::Detach() { 58 ZX_DEBUG_ASSERT(children_.is_empty()); 59 if (vnode_ == nullptr) { // Dnode already detached. 60 return; 61 } 62 63 RemoveFromParent(); 64 // Detach from vnode 65 vnode_->dnode_ = nullptr; 66 vnode_ = nullptr; 67} 68 69void Dnode::AddChild(fbl::RefPtr<Dnode> parent, fbl::RefPtr<Dnode> child) { 70 ZX_DEBUG_ASSERT(parent != nullptr); 71 ZX_DEBUG_ASSERT(child != nullptr); 72 ZX_DEBUG_ASSERT(child->parent_ == nullptr); // Child shouldn't have a parent 73 ZX_DEBUG_ASSERT(child != parent); 74 ZX_DEBUG_ASSERT(parent->IsDirectory()); 75 76 child->parent_ = parent; 77 child->vnode_->link_count_++; 78 if (child->IsDirectory()) { 79 // Child has '..' pointing back at parent. 80 parent->vnode_->link_count_++; 81 } 82 // Ensure that the ordering of tokens in the children list is absolute. 83 if (parent->children_.is_empty()) { 84 child->ordering_token_ = 2; // '0' for '.', '1' for '..' 85 } else { 86 child->ordering_token_ = parent->children_.back().ordering_token_ + 1; 87 } 88 parent->children_.push_back(fbl::move(child)); 89 parent->vnode_->UpdateModified(); 90} 91 92zx_status_t Dnode::Lookup(fbl::StringPiece name, fbl::RefPtr<Dnode>* out) const { 93 auto dn = children_.find_if([&name](const Dnode& elem) -> bool { 94 return elem.NameMatch(name); 95 }); 96 if (dn == children_.end()) { 97 return ZX_ERR_NOT_FOUND; 98 } 99 100 if (out != nullptr) { 101 *out = dn.CopyPointer(); 102 } 103 return ZX_OK; 104} 105 106fbl::RefPtr<VnodeMemfs> Dnode::AcquireVnode() const { 107 return vnode_; 108} 109 110zx_status_t Dnode::CanUnlink() const { 111 if (!children_.is_empty()) { 112 // Cannot unlink non-empty directory 113 return ZX_ERR_NOT_EMPTY; 114 } else if (vnode_->IsRemote()) { 115 // Cannot unlink mount points 116 return ZX_ERR_UNAVAILABLE; 117 } 118 return ZX_OK; 119} 120 121struct dircookie_t { 122 size_t order; // Minimum 'order' of the next dnode dirent to be read. 123}; 124 125static_assert(sizeof(dircookie_t) <= sizeof(fs::vdircookie_t), 126 "MemFS dircookie too large to fit in IO state"); 127 128// Read the canned "." and ".." entries that should 129// appear at the beginning of a directory. 130zx_status_t Dnode::ReaddirStart(fs::DirentFiller* df, void* cookie) { 131 dircookie_t* c = static_cast<dircookie_t*>(cookie); 132 zx_status_t r; 133 134 if (c->order == 0) { 135 // TODO(smklein): Return the real ino. 136 uint64_t ino = fuchsia_io_INO_UNKNOWN; 137 if ((r = df->Next(".", VTYPE_TO_DTYPE(V_TYPE_DIR), ino)) != ZX_OK) { 138 return r; 139 } 140 c->order++; 141 } 142 return ZX_OK; 143} 144 145void Dnode::Readdir(fs::DirentFiller* df, void* cookie) const { 146 dircookie_t* c = static_cast<dircookie_t*>(cookie); 147 zx_status_t r = 0; 148 149 if (c->order < 1) { 150 if ((r = Dnode::ReaddirStart(df, cookie)) != ZX_OK) { 151 return; 152 } 153 } 154 155 for (const auto& dn : children_) { 156 if (dn.ordering_token_ < c->order) { 157 continue; 158 } 159 uint32_t vtype = dn.IsDirectory() ? V_TYPE_DIR : V_TYPE_FILE; 160 if ((r = df->Next(fbl::StringPiece(dn.name_.get(), dn.NameLen()), 161 VTYPE_TO_DTYPE(vtype), dn.AcquireVnode()->ino())) != ZX_OK) { 162 return; 163 } 164 c->order = dn.ordering_token_ + 1; 165 } 166} 167 168// Answers the question: "Is dn a subdirectory of this?" 169bool Dnode::IsSubdirectory(fbl::RefPtr<Dnode> dn) const { 170 if (IsDirectory() && dn->IsDirectory()) { 171 // Iterate all the way up to root 172 while (dn->parent_ != nullptr && dn->parent_ != dn) { 173 if (vnode_ == dn->vnode_) { 174 return true; 175 } 176 dn = dn->parent_; 177 } 178 } 179 return false; 180} 181 182fbl::unique_ptr<char[]> Dnode::TakeName() { 183 return fbl::move(name_); 184} 185 186void Dnode::PutName(fbl::unique_ptr<char[]> name, size_t len) { 187 flags_ = static_cast<uint32_t>((flags_ & ~kDnodeNameMax) | len); 188 name_ = fbl::move(name); 189} 190 191bool Dnode::IsDirectory() const { return vnode_->IsDirectory(); } 192 193Dnode::Dnode(fbl::RefPtr<VnodeMemfs> vn, fbl::unique_ptr<char[]> name, uint32_t flags) : 194 vnode_(fbl::move(vn)), parent_(nullptr), ordering_token_(0), flags_(flags), name_(fbl::move(name)) { 195}; 196 197size_t Dnode::NameLen() const { 198 return flags_ & kDnodeNameMax; 199} 200 201bool Dnode::NameMatch(fbl::StringPiece name) const { 202 return name == fbl::StringPiece(name_.get(), NameLen()); 203} 204 205} // namespace memfs 206