1 // Copyright 2017 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 <inttypes.h>
6 #include <fcntl.h>
7 #include <limits.h>
8 #include <stdlib.h>
9 #include <string.h>
10 #include <sys/stat.h>
11 
12 #include <fbl/algorithm.h>
13 #include <fbl/alloc_checker.h>
14 #include <fbl/ref_ptr.h>
15 #include <fbl/string_piece.h>
16 #include <fbl/unique_ptr.h>
17 #include <lib/fdio/vfs.h>
18 #include <fs/vfs.h>
19 #include <lib/memfs/cpp/vnode.h>
20 #include <zircon/device/vfs.h>
21 
22 #include <utility>
23 
24 #include "dnode.h"
25 
26 namespace memfs {
27 namespace {
28 
29 constexpr const char kFsName[] = "memfs";
30 
31 }
32 
VnodeDir(Vfs * vfs)33 VnodeDir::VnodeDir(Vfs* vfs) : VnodeMemfs(vfs) {
34     link_count_ = 1; // Implied '.'
35 }
~VnodeDir()36 VnodeDir::~VnodeDir() {}
37 
ValidateFlags(uint32_t flags)38 zx_status_t VnodeDir::ValidateFlags(uint32_t flags) {
39     if (flags & ZX_FS_RIGHT_WRITABLE) {
40         return ZX_ERR_NOT_FILE;
41     }
42     return ZX_OK;
43 }
44 
Notify(fbl::StringPiece name,unsigned event)45 void VnodeDir::Notify(fbl::StringPiece name, unsigned event) { watcher_.Notify(name, event); }
46 
WatchDir(fs::Vfs * vfs,uint32_t mask,uint32_t options,zx::channel watcher)47 zx_status_t VnodeDir::WatchDir(fs::Vfs* vfs, uint32_t mask, uint32_t options, zx::channel watcher) {
48     return watcher_.WatchDir(vfs, this, mask, options, std::move(watcher));
49 }
50 
QueryFilesystem(fuchsia_io_FilesystemInfo * info)51 zx_status_t VnodeDir::QueryFilesystem(fuchsia_io_FilesystemInfo* info) {
52     static_assert(fbl::constexpr_strlen(kFsName) + 1 < fuchsia_io_MAX_FS_NAME_BUFFER,
53                   "Memfs name too long");
54     memset(info, 0, sizeof(*info));
55     strlcpy(reinterpret_cast<char*>(info->name), kFsName, fuchsia_io_MAX_FS_NAME_BUFFER);
56     info->block_size = kMemfsBlksize;
57     info->max_filename_size = kDnodeNameMax;
58     info->fs_type = VFS_TYPE_MEMFS;
59     info->fs_id = vfs()->GetFsId();
60     size_t total_bytes = 0;
61     if (mul_overflow(vfs()->PagesLimit(), kMemfsBlksize, &total_bytes)) {
62         info->total_bytes = UINT64_MAX;
63     } else {
64         info->total_bytes = total_bytes;
65     }
66     info->used_bytes = vfs()->NumAllocatedPages() * kMemfsBlksize;
67     info->total_nodes = UINT64_MAX;
68     uint64_t deleted_ino_count = GetDeletedInoCounter();
69     uint64_t ino_count = GetInoCounter();
70     ZX_DEBUG_ASSERT(ino_count >= deleted_ino_count);
71     info->used_nodes = ino_count - deleted_ino_count;
72     return ZX_OK;
73 }
74 
GetVmo(int flags,zx_handle_t * out)75 zx_status_t VnodeDir::GetVmo(int flags, zx_handle_t* out) {
76     return ZX_ERR_ACCESS_DENIED;
77 }
78 
IsRemote() const79 bool VnodeDir::IsRemote() const { return remoter_.IsRemote(); }
DetachRemote()80 zx::channel VnodeDir::DetachRemote() { return remoter_.DetachRemote(); }
GetRemote() const81 zx_handle_t VnodeDir::GetRemote() const { return remoter_.GetRemote(); }
SetRemote(zx::channel remote)82 void VnodeDir::SetRemote(zx::channel remote) { return remoter_.SetRemote(std::move(remote)); }
83 
Lookup(fbl::RefPtr<fs::Vnode> * out,fbl::StringPiece name)84 zx_status_t VnodeDir::Lookup(fbl::RefPtr<fs::Vnode>* out, fbl::StringPiece name) {
85     if (!IsDirectory()) {
86         return ZX_ERR_NOT_FOUND;
87     }
88     fbl::RefPtr<Dnode> dn;
89     zx_status_t r = dnode_->Lookup(name, &dn);
90     ZX_DEBUG_ASSERT(r <= 0);
91     if (r == ZX_OK) {
92         if (dn == nullptr) {
93             // Looking up our own vnode
94             *out = fbl::RefPtr<VnodeDir>(this);
95         } else {
96             // Looking up a different vnode
97             *out = dn->AcquireVnode();
98         }
99     }
100     return r;
101 }
102 
Getattr(vnattr_t * attr)103 zx_status_t VnodeDir::Getattr(vnattr_t* attr) {
104     memset(attr, 0, sizeof(vnattr_t));
105     attr->inode = ino_;
106     attr->mode = V_TYPE_DIR | V_IRUSR;
107     attr->size = 0;
108     attr->blksize = kMemfsBlksize;
109     attr->blkcount = fbl::round_up(attr->size, kMemfsBlksize) / VNATTR_BLKSIZE;
110     attr->nlink = link_count_;
111     attr->create_time = create_time_;
112     attr->modify_time = modify_time_;
113     return ZX_OK;
114 }
115 
GetHandles(uint32_t flags,fuchsia_io_NodeInfo * info)116 zx_status_t VnodeDir::GetHandles(uint32_t flags, fuchsia_io_NodeInfo* info) {
117     info->tag = fuchsia_io_NodeInfoTag_directory;
118     return ZX_OK;
119 }
120 
Readdir(fs::vdircookie_t * cookie,void * data,size_t len,size_t * out_actual)121 zx_status_t VnodeDir::Readdir(fs::vdircookie_t* cookie, void* data, size_t len, size_t* out_actual) {
122     fs::DirentFiller df(data, len);
123     if (!IsDirectory()) {
124         // This WAS a directory, but it has been deleted.
125         Dnode::ReaddirStart(&df, cookie);
126         *out_actual = df.BytesFilled();
127         return ZX_OK;
128     }
129     dnode_->Readdir(&df, cookie);
130     *out_actual = df.BytesFilled();
131     return ZX_OK;
132 }
133 
134 // postcondition: reference taken on vn returned through "out"
Create(fbl::RefPtr<fs::Vnode> * out,fbl::StringPiece name,uint32_t mode)135 zx_status_t VnodeDir::Create(fbl::RefPtr<fs::Vnode>* out, fbl::StringPiece name, uint32_t mode) {
136     zx_status_t status;
137     if ((status = CanCreate(name)) != ZX_OK) {
138         return status;
139     }
140 
141     fbl::AllocChecker ac;
142     fbl::RefPtr<memfs::VnodeMemfs> vn;
143     if (S_ISDIR(mode)) {
144         vn = fbl::AdoptRef(new (&ac) memfs::VnodeDir(vfs()));
145     } else {
146         vn = fbl::AdoptRef(new (&ac) memfs::VnodeFile(vfs()));
147     }
148 
149     if (!ac.check()) {
150         return ZX_ERR_NO_MEMORY;
151     }
152 
153     if ((status = AttachVnode(vn, name, S_ISDIR(mode))) != ZX_OK) {
154         return status;
155     }
156     *out = std::move(vn);
157     return status;
158 }
159 
Unlink(fbl::StringPiece name,bool must_be_dir)160 zx_status_t VnodeDir::Unlink(fbl::StringPiece name, bool must_be_dir) {
161     if (!IsDirectory()) {
162         // Calling unlink from unlinked, empty directory
163         return ZX_ERR_BAD_STATE;
164     }
165     fbl::RefPtr<Dnode> dn;
166     zx_status_t r;
167     if ((r = dnode_->Lookup(name, &dn)) != ZX_OK) {
168         return r;
169     } else if (dn == nullptr) {
170         // Cannot unlink directory 'foo' using the argument 'foo/.'
171         return ZX_ERR_UNAVAILABLE;
172     } else if (!dn->IsDirectory() && must_be_dir) {
173         // Path ending in "/" was requested, implying that the dnode must be a directory
174         return ZX_ERR_NOT_DIR;
175     } else if ((r = dn->CanUnlink()) != ZX_OK) {
176         return r;
177     }
178 
179     dn->Detach();
180     return ZX_OK;
181 }
182 
Rename(fbl::RefPtr<fs::Vnode> _newdir,fbl::StringPiece oldname,fbl::StringPiece newname,bool src_must_be_dir,bool dst_must_be_dir)183 zx_status_t VnodeDir::Rename(fbl::RefPtr<fs::Vnode> _newdir, fbl::StringPiece oldname,
184                              fbl::StringPiece newname, bool src_must_be_dir,
185                              bool dst_must_be_dir) {
186     auto newdir = fbl::RefPtr<VnodeMemfs>::Downcast(std::move(_newdir));
187 
188     if (!IsDirectory() || !newdir->IsDirectory())
189         return ZX_ERR_BAD_STATE;
190 
191     fbl::RefPtr<Dnode> olddn;
192     zx_status_t r;
193     // The source must exist
194     if ((r = dnode_->Lookup(oldname, &olddn)) != ZX_OK) {
195         return r;
196     }
197     ZX_DEBUG_ASSERT(olddn != nullptr);
198 
199     if (!olddn->IsDirectory() && (src_must_be_dir || dst_must_be_dir)) {
200         return ZX_ERR_NOT_DIR;
201     } else if ((newdir->ino() == ino_) && (oldname == newname)) {
202         // Renaming a file or directory to itself?
203         // Shortcut success case
204         return ZX_OK;
205     }
206 
207     // Verify that the destination is not a subdirectory of the source (if
208     // both are directories).
209     if (olddn->IsSubdirectory(newdir->dnode_)) {
210         return ZX_ERR_INVALID_ARGS;
211     }
212 
213     // The destination may or may not exist
214     fbl::RefPtr<Dnode> targetdn;
215     r = newdir->dnode_->Lookup(newname, &targetdn);
216     bool target_exists = (r == ZX_OK);
217     if (target_exists) {
218         ZX_DEBUG_ASSERT(targetdn != nullptr);
219         // The target exists. Validate and unlink it.
220         if (olddn == targetdn) {
221             // Cannot rename node to itself
222             return ZX_ERR_INVALID_ARGS;
223         }
224         if (olddn->IsDirectory() != targetdn->IsDirectory()) {
225             // Cannot rename files to directories (and vice versa)
226             return ZX_ERR_INVALID_ARGS;
227         } else if ((r = targetdn->CanUnlink()) != ZX_OK) {
228             return r;
229         }
230     } else if (r != ZX_ERR_NOT_FOUND) {
231         return r;
232     }
233 
234     // Allocate the new name for the dnode, either by
235     // (1) Stealing it from the previous dnode, if it used the same name, or
236     // (2) Allocating a new name, if creating a new name.
237     fbl::unique_ptr<char[]> namebuffer(nullptr);
238     if (target_exists) {
239         targetdn->Detach();
240         namebuffer = targetdn->TakeName();
241     } else {
242         fbl::AllocChecker ac;
243         namebuffer.reset(new (&ac) char[newname.length() + 1]);
244         if (!ac.check()) {
245             return ZX_ERR_NO_MEMORY;
246         }
247         memcpy(namebuffer.get(), newname.data(), newname.length());
248         namebuffer[newname.length()] = '\0';
249     }
250 
251     // NOTE:
252     //
253     // Validation ends here, and modifications begin. Rename should not fail
254     // beyond this point.
255 
256     olddn->RemoveFromParent();
257     olddn->PutName(std::move(namebuffer), newname.length());
258     Dnode::AddChild(newdir->dnode_, std::move(olddn));
259     return ZX_OK;
260 }
261 
Link(fbl::StringPiece name,fbl::RefPtr<fs::Vnode> target)262 zx_status_t VnodeDir::Link(fbl::StringPiece name, fbl::RefPtr<fs::Vnode> target) {
263     auto vn = fbl::RefPtr<VnodeMemfs>::Downcast(std::move(target));
264 
265     if (!IsDirectory()) {
266         // Empty, unlinked parent
267         return ZX_ERR_BAD_STATE;
268     }
269 
270     if (vn->IsDirectory()) {
271         // The target must not be a directory
272         return ZX_ERR_NOT_FILE;
273     }
274 
275     if (dnode_->Lookup(name, nullptr) == ZX_OK) {
276         // The destination should not exist
277         return ZX_ERR_ALREADY_EXISTS;
278     }
279 
280     // Make a new dnode for the new name, attach the target vnode to it
281     fbl::RefPtr<Dnode> targetdn;
282     if ((targetdn = Dnode::Create(name, vn)) == nullptr) {
283         return ZX_ERR_NO_MEMORY;
284     }
285 
286     // Attach the new dnode to its parent
287     Dnode::AddChild(dnode_, std::move(targetdn));
288 
289     return ZX_OK;
290 }
291 
MountSubtree(fbl::RefPtr<VnodeDir> subtree)292 void VnodeDir::MountSubtree(fbl::RefPtr<VnodeDir> subtree) {
293     Dnode::AddChild(dnode_, subtree->dnode_);
294 }
295 
CreateFromVmo(fbl::StringPiece name,zx_handle_t vmo,zx_off_t off,zx_off_t len)296 zx_status_t VnodeDir::CreateFromVmo(fbl::StringPiece name,
297                                     zx_handle_t vmo, zx_off_t off, zx_off_t len) {
298     zx_status_t status;
299     if ((status = CanCreate(name)) != ZX_OK) {
300         return status;
301     }
302 
303     fbl::AllocChecker ac;
304     fbl::RefPtr<VnodeMemfs> vn;
305     vn = fbl::AdoptRef(new (&ac) VnodeVmo(vfs(), vmo, off, len));
306     if (!ac.check()) {
307         return ZX_ERR_NO_MEMORY;
308     }
309     if ((status = AttachVnode(std::move(vn), name, false)) != ZX_OK) {
310         return status;
311     }
312 
313     return ZX_OK;
314 }
315 
CanCreate(fbl::StringPiece name) const316 zx_status_t VnodeDir::CanCreate(fbl::StringPiece name) const {
317     if (!IsDirectory()) {
318         return ZX_ERR_INVALID_ARGS;
319     }
320     zx_status_t status;
321     if ((status = dnode_->Lookup(name, nullptr)) == ZX_ERR_NOT_FOUND) {
322         return ZX_OK;
323     } else if (status == ZX_OK) {
324         return ZX_ERR_ALREADY_EXISTS;
325     }
326     return status;
327 }
328 
AttachVnode(fbl::RefPtr<VnodeMemfs> vn,fbl::StringPiece name,bool isdir)329 zx_status_t VnodeDir::AttachVnode(fbl::RefPtr<VnodeMemfs> vn, fbl::StringPiece name,
330                                   bool isdir) {
331     // dnode takes a reference to the vnode
332     fbl::RefPtr<Dnode> dn;
333     if ((dn = Dnode::Create(name, vn)) == nullptr) {
334         return ZX_ERR_NO_MEMORY;
335     }
336 
337     // Identify that the vnode is a directory (vn->dnode_ != nullptr) so that
338     // addding a child will also increment the parent link_count (after all,
339     // directories contain a ".." entry, which is a link to their parent).
340     if (isdir) {
341         vn->dnode_ = dn;
342     }
343 
344     // parent takes first reference
345     Dnode::AddChild(dnode_, std::move(dn));
346     return ZX_OK;
347 }
348 
349 } // namespace memfs
350