blob: 4d4056d2ff8b4bfe7ba506e2a22028e86dc5782f [file] [log] [blame]
// Copyright 2021 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.
use once_cell::sync::OnceCell;
use std::collections::hash_map::Entry;
use std::collections::HashMap;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Arc, Weak};
use super::*;
use crate::lock::Mutex;
use crate::types::*;
/// A file system that can be mounted in a namespace.
pub struct FileSystem {
root: OnceCell<DirEntryHandle>,
next_inode: AtomicU64,
ops: Box<dyn FileSystemOps>,
/// Whether DirEntries added to this filesystem should be considered permanent, instead of a
/// cache of the backing storage. An example is tmpfs: the DirEntry tree *is* the backing
/// storage, as opposed to ext4, which uses the DirEntry tree as a cache and removes unused
/// nodes from it.
permanent_entries: bool,
/// A file-system global mutex to serialize rename operations.
///
/// This mutex is useful because the invariants enforced during a rename
/// operation involve many DirEntry objects. In the future, we might be
/// able to remove this mutex, but we will need to think carefully about
/// how rename operations can interleave.
///
/// See DirEntry::rename.
pub rename_mutex: Mutex<()>,
/// The FsNode cache for this file system.
///
/// When two directory entries are hard links to the same underlying inode,
/// this cache lets us re-use the same FsNode object for both directory
/// entries.
///
/// Rather than calling FsNode::new directly, file systems should call
/// FileSystem::get_or_create_node to see if the FsNode already exists in
/// the cache.
nodes: Mutex<HashMap<ino_t, Weak<FsNode>>>,
/// DirEntryHandle cache for the filesystem. Currently only used by filesystems that set the
/// permanent_entries flag, to store every node and make sure it doesn't get freed without
/// being explicitly unlinked.
entries: Mutex<HashMap<usize, DirEntryHandle>>,
}
impl FileSystem {
/// Create a new filesystem.
pub fn new(ops: impl FileSystemOps + 'static) -> FileSystemHandle {
Self::new_internal(ops, false)
}
/// Create a new filesystem with the permanent_entries flag set.
pub fn new_with_permanent_entries(ops: impl FileSystemOps + 'static) -> FileSystemHandle {
Self::new_internal(ops, true)
}
/// Create a new filesystem and call set_root in one step.
pub fn new_with_root(ops: impl FileSystemOps + 'static, root_node: FsNode) -> FileSystemHandle {
let fs = Self::new_with_permanent_entries(ops);
fs.set_root_node(root_node);
fs
}
pub fn set_root(self: &FileSystemHandle, root: impl FsNodeOps + 'static) {
self.set_root_node(FsNode::new_root(root));
}
/// Set up the root of the filesystem. Must not be called more than once.
pub fn set_root_node(self: &FileSystemHandle, mut root: FsNode) {
if root.inode_num == 0 {
root.inode_num = self.next_inode_num();
}
root.set_fs(self);
let root_node = Arc::new(root);
self.nodes.lock().insert(root_node.inode_num, Arc::downgrade(&root_node));
let root = DirEntry::new(root_node, None, FsString::new());
assert!(self.root.set(root).is_ok(), "FileSystem::set_root can't be called more than once");
}
fn new_internal(
ops: impl FileSystemOps + 'static,
permanent_entries: bool,
) -> FileSystemHandle {
Arc::new(FileSystem {
root: OnceCell::new(),
next_inode: AtomicU64::new(1),
ops: Box::new(ops),
permanent_entries,
rename_mutex: Mutex::new(()),
nodes: Mutex::new(HashMap::new()),
entries: Mutex::new(HashMap::new()),
})
}
/// The root directory entry of this file system.
///
/// Panics if this file system does not have a root directory.
pub fn root(&self) -> &DirEntryHandle {
self.root.get().unwrap()
}
/// Get or create an FsNode for this file system.
///
/// If inode_num is Some, then this function checks the node cache to
/// determine whether this node is already open. If so, the function
/// returns the existing FsNode. If not, the function calls the given
/// create_fn function to create the FsNode.
///
/// If inode_num is None, then this function assigns a new inode number
/// and calls the given create_fn function to create the FsNode with the
/// assigned number.
///
/// Returns Err only if create_fn returns Err.
pub fn get_or_create_node<F>(
&self,
inode_num: Option<ino_t>,
create_fn: F,
) -> Result<FsNodeHandle, Errno>
where
F: FnOnce(ino_t) -> Result<FsNodeHandle, Errno>,
{
let inode_num = inode_num.unwrap_or_else(|| self.next_inode_num());
let mut nodes = self.nodes.lock();
match nodes.entry(inode_num) {
Entry::Vacant(entry) => {
let node = create_fn(inode_num)?;
entry.insert(Arc::downgrade(&node));
Ok(node)
}
Entry::Occupied(mut entry) => {
if let Some(node) = entry.get().upgrade() {
return Ok(node);
}
let node = create_fn(inode_num)?;
entry.insert(Arc::downgrade(&node));
Ok(node)
}
}
}
// File systems that produce their own IDs for nodes should invoke this
// function. The ones who leave to this object to assign the IDs should
// call |create_node|.
pub fn create_node_with_id(
self: &Arc<Self>,
ops: Box<dyn FsNodeOps>,
mode: FileMode,
id: ino_t,
) -> FsNodeHandle {
let node = FsNode::new(ops, self, id, mode);
self.nodes.lock().insert(node.inode_num, Arc::downgrade(&node));
node
}
pub fn create_node(self: &Arc<Self>, ops: Box<dyn FsNodeOps>, mode: FileMode) -> FsNodeHandle {
let inode_num = self.next_inode_num();
self.create_node_with_id(ops, mode, inode_num)
}
pub fn create_node_with_ops(
self: &Arc<Self>,
ops: impl FsNodeOps + 'static,
mode: FileMode,
) -> FsNodeHandle {
self.create_node(Box::new(ops), mode)
}
/// Remove the given FsNode from the node cache.
///
/// Called from the Drop trait of FsNode.
pub fn remove_node(&self, node: &mut FsNode) {
let mut nodes = self.nodes.lock();
if let Some(weak_node) = nodes.get(&node.inode_num) {
if std::ptr::eq(weak_node.as_ptr(), node) {
nodes.remove(&node.inode_num);
}
}
}
pub fn next_inode_num(&self) -> ino_t {
assert!(!self.ops.generate_node_ids());
self.next_inode.fetch_add(1, Ordering::Relaxed)
}
/// Move |renamed| that is at |old_name| in |old_parent| to |new_name| in |new_parent|
/// replacing |replaced|.
/// If |replaced| exists and is a directory, this function must check that |renamed| is n
/// directory and that |replaced| is empty.
pub fn rename(
&self,
old_parent: &FsNodeHandle,
old_name: &FsStr,
new_parent: &FsNodeHandle,
new_name: &FsStr,
renamed: &FsNodeHandle,
replaced: Option<&FsNodeHandle>,
) -> Result<(), Errno> {
self.ops.rename(self, old_parent, old_name, new_parent, new_name, renamed, replaced)
}
/// Returns the `statfs` for this filesystem.
///
/// Each `FileSystemOps` impl is expected to override this to return the specific statfs for
/// the filesystem.
///
/// Returns `ENOSYS` if the `FileSystemOps` don't implement `stat`.
pub fn statfs(&self) -> Result<statfs, Errno> {
self.ops.statfs(self)
}
pub fn did_create_dir_entry(&self, entry: &DirEntryHandle) {
if self.permanent_entries {
self.entries.lock().insert(Arc::as_ptr(&entry) as usize, entry.clone());
}
}
pub fn will_destroy_dir_entry(&self, entry: &DirEntryHandle) {
if self.permanent_entries {
self.entries.lock().remove(&(Arc::as_ptr(entry) as usize));
}
}
}
/// The filesystem-implementation-specific data for FileSystem.
pub trait FileSystemOps: Send + Sync {
// Rename the given node.
//
// The node to be renamed is passed as "renamed". It currently has
// old_name in old_parent. After the rename operation, it should have
// new_name in new_parent.
//
// If new_parent already has a child named new_name, that node is passed as
// "replaced". In that case, both "renamed" and "replaced" will be
// directories and the rename operation should succeed only if "replaced"
// is empty. The VFS will check that there are no children of "replaced" in
// the DirEntry cache, but the implementation of this function is
// responsible for checking that there are no children of replaced that are
// known only to the file system implementation (e.g., present on-disk but
// not in the DirEntry cache).
fn rename(
&self,
_fs: &FileSystem,
_old_parent: &FsNodeHandle,
_old_name: &FsStr,
_new_parent: &FsNodeHandle,
_new_name: &FsStr,
_renamed: &FsNodeHandle,
_replaced: Option<&FsNodeHandle>,
) -> Result<(), Errno> {
error!(EROFS)
}
/// Return the `statfs` containing information about this filesystem.
///
/// If a filesystem cannot generate a `statfs`, the default behavior is to return `Err(ENOSYS)`.
fn statfs(&self, _fs: &FileSystem) -> Result<statfs, Errno> {
// TODO: This should return ENOSYS, but that currently breaks a bunch of tests (since not
// enough `FileSystemOps` implement `stat`).
Ok(statfs::default())
}
/// Whether this file system generates its own node IDs.
fn generate_node_ids(&self) -> bool {
false
}
}
pub type FileSystemHandle = Arc<FileSystem>;