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