blob: 82a90491d03dbe77afa1a12dea3d34cb7c599ce0 [file] [log] [blame]
// 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.
#ifndef FS_PSEUDO_DIR_H_
#define FS_PSEUDO_DIR_H_
#include <fbl/intrusive_wavl_tree.h>
#include <fbl/macros.h>
#include <fbl/mutex.h>
#include <fbl/ref_counted.h>
#include <fbl/string.h>
#include <fbl/unique_ptr.h>
#include "vnode.h"
#include "watcher.h"
namespace fs {
// A pseudo-directory is a directory-like object whose entries are constructed
// by a program at runtime. The client can lookup, enumerate, and watch these
// directory entries but it cannot create, remove, or rename them.
//
// This class is designed to allow programs to publish a relatively small number
// of entries (up to a few dozen) such as services, file-system roots,
// debugging pseudo-files, or other vnodes. It is not suitable for very large
// directories (hundreds of entries).
//
// This class is thread-safe.
class PseudoDir : public Vnode {
public:
// Creates a directory which is initially empty.
PseudoDir();
// Destroys the directory and releases the nodes it contains.
~PseudoDir() override;
// Adds a directory entry associating the given |name| with |vn|.
// It is ok to add the same Vnode multiple times with different names.
//
// Returns |ZX_OK| on success.
// Returns |ZX_ERR_ALREADY_EXISTS| if there is already a node with the given name.
zx_status_t AddEntry(fbl::String name, fbl::RefPtr<fs::Vnode> vn);
// Removes a directory entry with the given |name|.
//
// Returns |ZX_OK| on success.
// Returns |ZX_ERR_NOT_FOUND| if there is no node with the given name.
zx_status_t RemoveEntry(fbl::StringPiece name);
// An extension of |RemoveEntry| which additionally verifies
// that the target vnode is |vn|.
//
// Returns |ZX_OK| on success.
// Returns |ZX_ERR_NOT_FOUND| if there is no node with the given name/vn
// pair.
zx_status_t RemoveEntry(fbl::StringPiece name, fs::Vnode* vn);
// Removes all directory entries.
void RemoveAllEntries();
// Checks if directory is empty.
// Be careful while using this function if using this Dir in multiple
// threads.
bool IsEmpty() const;
// |Vnode| implementation:
zx_status_t Open(uint32_t flags, fbl::RefPtr<Vnode>* out_redirect) final;
zx_status_t Getattr(vnattr_t* a) final;
zx_status_t Lookup(fbl::RefPtr<fs::Vnode>* out, fbl::StringPiece name) final;
void Notify(fbl::StringPiece name, unsigned event) final;
zx_status_t WatchDir(fs::Vfs* vfs, uint32_t mask, uint32_t options, zx::channel watcher) final;
zx_status_t Readdir(vdircookie_t* cookie, void* dirents, size_t len, size_t* out_actual) final;
bool IsDirectory() const final { return true; }
zx_status_t GetNodeInfo(uint32_t flags, fuchsia_io_NodeInfo* info) final;
private:
static constexpr uint64_t kDotId = 1u;
struct NameTreeTraits;
struct IdTreeTraits;
class Entry {
public:
Entry(uint64_t id, fbl::String name, fbl::RefPtr<fs::Vnode> node);
~Entry();
uint64_t id() const { return id_; }
const fbl::String& name() const { return name_; }
const fbl::RefPtr<fs::Vnode>& node() const { return node_; }
private:
uint64_t const id_;
fbl::String name_;
fbl::RefPtr<fs::Vnode> node_;
// Node states.
friend IdTreeTraits;
friend NameTreeTraits;
fbl::WAVLTreeNodeState<fbl::unique_ptr<Entry>> id_tree_state_;
fbl::WAVLTreeNodeState<Entry*> name_tree_state_;
};
struct KeyByIdTraits {
static uint64_t GetKey(const Entry& entry) {
return entry.id();
}
static bool LessThan(uint64_t key1, uint64_t key2) { return key1 < key2; }
static bool EqualTo(uint64_t key1, uint64_t key2) { return key1 == key2; }
};
struct KeyByNameTraits {
static fbl::String GetKey(const Entry& entry) {
return entry.name();
}
static bool LessThan(const fbl::String& key1, const fbl::String& key2) {
return key1 < key2;
}
static bool EqualTo(const fbl::String& key1, const fbl::String& key2) {
return key1 == key2;
}
};
struct IdTreeTraits {
using PtrTraits = fbl::internal::ContainerPtrTraits<fbl::unique_ptr<Entry>>;
static fbl::WAVLTreeNodeState<fbl::unique_ptr<Entry>>& node_state(
Entry& entry) {
return entry.id_tree_state_;
}
};
struct NameTreeTraits {
using PtrTraits = fbl::internal::ContainerPtrTraits<Entry*>;
static fbl::WAVLTreeNodeState<Entry*>& node_state(Entry& entry) {
return entry.name_tree_state_;
}
};
using EntryByIdMap = fbl::WAVLTree<uint64_t, fbl::unique_ptr<Entry>,
KeyByIdTraits, IdTreeTraits>;
using EntryByNameMap = fbl::WAVLTree<fbl::String, Entry*,
KeyByNameTraits, NameTreeTraits>;
mutable fbl::Mutex mutex_;
uint64_t next_node_id_ __TA_GUARDED(mutex_) = kDotId + 1;
EntryByIdMap entries_by_id_ __TA_GUARDED(mutex_);
EntryByNameMap entries_by_name_ __TA_GUARDED(mutex_);
fs::WatcherContainer watcher_; // note: uses its own internal mutex
DISALLOW_COPY_ASSIGN_AND_MOVE(PseudoDir);
};
} // namespace fs
#endif // FS_PSEUDO_DIR_H_