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