| // Copyright 2017 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 "src/storage/memfs/vnode_dir.h" |
| |
| #include <sys/stat.h> |
| |
| #include "src/storage/memfs/dnode.h" |
| #include "src/storage/memfs/memfs.h" |
| #include "src/storage/memfs/vnode_file.h" |
| #include "src/storage/memfs/vnode_vmo.h" |
| |
| namespace memfs { |
| |
| VnodeDir::VnodeDir(Memfs& memfs) : Vnode(memfs), memfs_(memfs) { |
| link_count_ = 1; // Implied '.' |
| } |
| |
| VnodeDir::~VnodeDir() = default; |
| |
| fuchsia_io::NodeProtocolKinds VnodeDir::GetProtocols() const { |
| return fuchsia_io::NodeProtocolKinds::kDirectory; |
| } |
| |
| void VnodeDir::Notify(std::string_view name, fuchsia_io::wire::WatchEvent event) { |
| watcher_.Notify(name, event); |
| } |
| |
| zx_status_t VnodeDir::WatchDir(fs::FuchsiaVfs* vfs, fuchsia_io::wire::WatchMask mask, |
| uint32_t options, |
| fidl::ServerEnd<fuchsia_io::DirectoryWatcher> watcher) { |
| return watcher_.WatchDir(vfs, this, mask, options, std::move(watcher)); |
| } |
| |
| zx_status_t VnodeDir::GetVmo(fuchsia_io::wire::VmoFlags flags, zx::vmo* out_vmo) { |
| return ZX_ERR_ACCESS_DENIED; |
| } |
| |
| zx_status_t VnodeDir::Lookup(std::string_view name, fbl::RefPtr<fs::Vnode>* out) { |
| if (!IsDirectory()) { |
| return ZX_ERR_NOT_FOUND; |
| } |
| Dnode* dn; |
| zx_status_t r = dnode_->Lookup(name, &dn); |
| ZX_DEBUG_ASSERT(r <= 0); |
| if (r == ZX_OK) { |
| if (dn == nullptr) { |
| // Looking up our own vnode |
| *out = fbl::RefPtr<VnodeDir>(this); |
| } else { |
| // Looking up a different vnode |
| *out = dn->AcquireVnode(); |
| } |
| } |
| return r; |
| } |
| |
| zx::result<fs::VnodeAttributes> VnodeDir::GetAttributes() const { |
| return zx::ok(fs::VnodeAttributes{ |
| .id = ino_, |
| .content_size = 0, |
| .storage_size = 0, |
| .link_count = link_count_, |
| .creation_time = create_time_, |
| .modification_time = modify_time_, |
| .mode = mode_, |
| .uid = uid_, |
| .gid = gid_, |
| }); |
| } |
| |
| zx_status_t VnodeDir::Readdir(fs::VdirCookie* cookie, void* data, size_t len, size_t* out_actual) { |
| fs::DirentFiller df(data, len); |
| if (!IsDirectory()) { |
| // This WAS a directory, but it has been deleted. |
| *out_actual = 0; |
| return ZX_OK; |
| } |
| dnode_->Readdir(&df, cookie); |
| *out_actual = df.BytesFilled(); |
| return ZX_OK; |
| } |
| |
| zx::result<fbl::RefPtr<fs::Vnode>> VnodeDir::Create(std::string_view name, fs::CreationType type) { |
| if (zx_status_t status = CanCreate(name); status != ZX_OK) { |
| return zx::error(status); |
| } |
| |
| fbl::AllocChecker ac; |
| fbl::RefPtr<memfs::Vnode> vn; |
| bool is_dir = false; |
| switch (type) { |
| case fs::CreationType::kDirectory: { |
| vn = fbl::AdoptRef(new (&ac) memfs::VnodeDir(memfs_)); |
| is_dir = true; |
| break; |
| } |
| case fs::CreationType::kFile: { |
| vn = fbl::AdoptRef(new (&ac) memfs::VnodeFile(memfs_)); |
| break; |
| } |
| } |
| |
| if (!ac.check()) { |
| return zx::error(ZX_ERR_NO_MEMORY); |
| } |
| |
| if (zx_status_t status = AttachVnode(vn, name, is_dir); status != ZX_OK) { |
| return zx::error(status); |
| } |
| |
| return zx::ok(std::move(vn)); |
| } |
| |
| zx_status_t VnodeDir::Unlink(std::string_view name, bool must_be_dir) { |
| if (!IsDirectory()) { |
| // Calling unlink from unlinked, empty directory |
| return ZX_ERR_BAD_STATE; |
| } |
| Dnode* dn; |
| zx_status_t r; |
| if ((r = dnode_->Lookup(name, &dn)) != ZX_OK) { |
| return r; |
| } else if (dn == nullptr) { |
| // Cannot unlink directory 'foo' using the argument 'foo/.' |
| return ZX_ERR_UNAVAILABLE; |
| } else if (!dn->IsDirectory() && must_be_dir) { |
| // Path ending in "/" was requested, implying that the dnode must be a directory |
| return ZX_ERR_NOT_DIR; |
| } else if ((r = dn->CanUnlink()) != ZX_OK) { |
| return r; |
| } |
| |
| dn->Detach(); |
| return ZX_OK; |
| } |
| |
| zx_status_t VnodeDir::Rename(fbl::RefPtr<fs::Vnode> _newdir, std::string_view oldname, |
| std::string_view newname, bool src_must_be_dir, bool dst_must_be_dir) { |
| auto newdir = fbl::RefPtr<Vnode>::Downcast(std::move(_newdir)); |
| |
| if (!IsDirectory() || !newdir->IsDirectory()) { |
| // Not linked into the directory hierarchy. |
| return ZX_ERR_NOT_FOUND; |
| } |
| |
| Dnode* olddn; |
| zx_status_t r; |
| // The source must exist |
| if ((r = dnode_->Lookup(oldname, &olddn)) != ZX_OK) { |
| return r; |
| } |
| ZX_DEBUG_ASSERT(olddn != nullptr); |
| |
| if (!olddn->IsDirectory() && (src_must_be_dir || dst_must_be_dir)) { |
| return ZX_ERR_NOT_DIR; |
| } else if ((newdir->ino() == ino_) && (oldname == newname)) { |
| // Renaming a file or directory to itself? |
| // Shortcut success case |
| return ZX_OK; |
| } |
| |
| // Verify that the destination is not a subdirectory of the source (if |
| // both are directories). |
| if (olddn->IsSubdirectory(newdir->dnode_)) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| // The destination may or may not exist |
| Dnode* targetdn; |
| r = newdir->dnode_->Lookup(newname, &targetdn); |
| bool target_exists = (r == ZX_OK); |
| if (target_exists) { |
| ZX_DEBUG_ASSERT(targetdn != nullptr); |
| // The target exists. Validate and unlink it. |
| if (olddn == targetdn) { |
| // Cannot rename node to itself |
| return ZX_ERR_INVALID_ARGS; |
| } |
| if (olddn->IsDirectory() != targetdn->IsDirectory()) { |
| // Cannot rename files to directories (and vice versa) |
| return olddn->IsDirectory() ? ZX_ERR_NOT_DIR : ZX_ERR_NOT_FILE; |
| } else if ((r = targetdn->CanUnlink()) != ZX_OK) { |
| return r; |
| } |
| } else if (r != ZX_ERR_NOT_FOUND) { |
| return r; |
| } |
| |
| // Allocate the new name for the dnode, either by |
| // (1) Stealing it from the previous dnode, if it used the same name, or |
| // (2) Allocating a new name, if creating a new name. |
| std::unique_ptr<char[]> namebuffer(nullptr); |
| if (target_exists) { |
| namebuffer = targetdn->TakeName(); |
| targetdn->Detach(); |
| } else { |
| fbl::AllocChecker ac; |
| namebuffer.reset(new (&ac) char[newname.length() + 1]); |
| if (!ac.check()) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| memcpy(namebuffer.get(), newname.data(), newname.length()); |
| namebuffer[newname.length()] = '\0'; |
| } |
| |
| // NOTE: |
| // |
| // Validation ends here, and modifications begin. Rename should not fail |
| // beyond this point. |
| |
| std::unique_ptr<Dnode> moved_node = olddn->RemoveFromParent(); |
| olddn->PutName(std::move(namebuffer), newname.length()); |
| Dnode::AddChild(newdir->dnode_, std::move(moved_node)); |
| return ZX_OK; |
| } |
| |
| zx_status_t VnodeDir::Link(std::string_view name, fbl::RefPtr<fs::Vnode> target) { |
| auto vn = fbl::RefPtr<Vnode>::Downcast(std::move(target)); |
| |
| if (!IsDirectory()) { |
| // Empty, unlinked parent |
| return ZX_ERR_BAD_STATE; |
| } |
| |
| if (vn->IsDirectory()) { |
| // The target must not be a directory |
| return ZX_ERR_NOT_FILE; |
| } |
| |
| if (dnode_->Lookup(name, nullptr) == ZX_OK) { |
| // The destination should not exist |
| return ZX_ERR_ALREADY_EXISTS; |
| } |
| |
| // Make a new dnode for the new name, attach the target vnode to it |
| std::unique_ptr<Dnode> targetdn; |
| if ((targetdn = Dnode::Create(name, vn)) == nullptr) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| |
| // Attach the new dnode to its parent |
| Dnode::AddChild(dnode_, std::move(targetdn)); |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t VnodeDir::CreateFromVmo(std::string_view name, zx_handle_t vmo, zx_off_t off, |
| zx_off_t len) { |
| zx_status_t status; |
| if ((status = CanCreate(name)) != ZX_OK) { |
| return status; |
| } |
| |
| std::lock_guard lock(mutex_); |
| |
| fbl::AllocChecker ac; |
| fbl::RefPtr<Vnode> vn; |
| vn = fbl::AdoptRef(new (&ac) VnodeVmo(memfs_, vmo, off, len)); |
| if (!ac.check()) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| if ((status = AttachVnode(vn, name, false)) != ZX_OK) { |
| return status; |
| } |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t VnodeDir::CanCreate(std::string_view name) const { |
| if (!IsDirectory()) { |
| return ZX_ERR_BAD_STATE; |
| } |
| zx_status_t status; |
| if ((status = dnode_->Lookup(name, nullptr)) == ZX_ERR_NOT_FOUND) { |
| return ZX_OK; |
| } else if (status == ZX_OK) { |
| return ZX_ERR_ALREADY_EXISTS; |
| } |
| return status; |
| } |
| |
| zx_status_t VnodeDir::AttachVnode(const fbl::RefPtr<Vnode>& vn, std::string_view name, bool isdir) { |
| // dnode takes a reference to the vnode |
| std::unique_ptr<Dnode> dn; |
| if ((dn = Dnode::Create(name, vn)) == nullptr) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| |
| // Identify that the vnode is a directory (vn->dnode_ != nullptr) so that |
| // addding a child will also increment the parent link_count (after all, |
| // directories contain a ".." entry, which is a link to their parent). |
| if (isdir) { |
| vn->dnode_ = dn.get(); |
| } |
| |
| // parent takes first reference |
| Dnode::AddChild(dnode_, std::move(dn)); |
| return ZX_OK; |
| } |
| |
| } // namespace memfs |