asterinas/kernel/src/fs/path/dentry.rs

667 lines
21 KiB
Rust

// SPDX-License-Identifier: MPL-2.0
use core::{
ops::Deref,
sync::atomic::{AtomicU32, Ordering},
};
use hashbrown::HashMap;
use ostd::sync::RwMutexWriteGuard;
use super::{is_dot, is_dot_or_dotdot, is_dotdot};
use crate::{
fs::{
self,
utils::{Inode, InodeExt, InodeMode, InodeType, MknodType},
},
prelude::*,
};
/// A `Dentry` represents a cached filesystem node in the VFS tree.
pub(super) struct Dentry {
inode: Arc<dyn Inode>,
type_: InodeType,
name_and_parent: NameAndParent,
children: Option<RwMutex<DentryChildren>>,
flags: AtomicU32,
mount_count: AtomicU32,
this: Weak<Dentry>,
}
/// The name and parent of a `Dentry`.
enum NameAndParent {
Real(Option<RwLock<(String, Arc<Dentry>)>>),
Pseudo(fn(&dyn Inode) -> String),
}
/// An error returned by [`NameAndParent::set`].
#[derive(Debug)]
struct SetNameAndParentError;
impl NameAndParent {
fn name(&self, inode: &dyn Inode) -> String {
match self {
NameAndParent::Real(name_and_parent) => match name_and_parent {
Some(name_and_parent) => name_and_parent.read().0.clone(),
None => String::from("/"),
},
NameAndParent::Pseudo(name_fn) => (name_fn)(inode),
}
}
fn parent(&self) -> Option<Arc<Dentry>> {
if let NameAndParent::Real(Some(name_and_parent)) = self {
Some(name_and_parent.read().1.clone())
} else {
None
}
}
/// Sets the name and parent of the `Dentry`.
///
/// # Errors
///
/// Returns `SetNameAndParentError` if the `Dentry` is a root or pseudo `Dentry`.
fn set(
&self,
name: &str,
parent: Arc<Dentry>,
) -> core::result::Result<(), SetNameAndParentError> {
if let NameAndParent::Real(Some(name_and_parent)) = self {
let mut name_and_parent = name_and_parent.write();
*name_and_parent = (String::from(name), parent);
Ok(())
} else {
Err(SetNameAndParentError)
}
}
}
impl Dentry {
/// Creates a new root `Dentry` with the given inode.
///
/// It is been created during the construction of the `Mount`.
/// The `Mount` holds an arc reference to this root `Dentry`.
pub(super) fn new_root(inode: Arc<dyn Inode>) -> Arc<Self> {
Self::new(inode, DentryOptions::Root)
}
/// Creates a new pseudo `Dentry` with the given inode and name function.
pub(super) fn new_pseudo(
inode: Arc<dyn Inode>,
name_fn: fn(&dyn Inode) -> String,
) -> Arc<Self> {
Self::new(inode, DentryOptions::Pseudo(name_fn))
}
fn new(inode: Arc<dyn Inode>, options: DentryOptions) -> Arc<Self> {
let name_and_parent = match options {
DentryOptions::Root => NameAndParent::Real(None),
DentryOptions::Leaf(name_and_parent) => {
NameAndParent::Real(Some(RwLock::new(name_and_parent)))
}
DentryOptions::Pseudo(name_fn) => NameAndParent::Pseudo(name_fn),
};
let children =
matches!(inode.type_(), InodeType::Dir).then(|| RwMutex::new(DentryChildren::new()));
Arc::new_cyclic(|weak_self| Self {
type_: inode.type_(),
inode,
name_and_parent,
children,
flags: AtomicU32::new(DentryFlags::empty().bits()),
mount_count: AtomicU32::new(0),
this: weak_self.clone(),
})
}
pub(super) fn is_pseudo(&self) -> bool {
matches!(self.name_and_parent, NameAndParent::Pseudo(_))
}
/// Gets the type of the `Dentry`.
pub(super) fn type_(&self) -> InodeType {
self.type_
}
/// Gets the name of the `Dentry`.
///
/// Returns "/" if it is a root `Dentry`.
pub(super) fn name(&self) -> String {
self.name_and_parent.name(self.inode.as_ref())
}
/// Gets the parent `Dentry`.
///
/// Returns `None` if it is a root or pseudo `Dentry`.
pub(super) fn parent(&self) -> Option<Arc<Self>> {
self.name_and_parent.parent()
}
fn this(&self) -> Arc<Self> {
self.this.upgrade().unwrap()
}
/// Gets the corresponding unique `DentryKey`.
pub(super) fn key(&self) -> DentryKey {
DentryKey::new(self)
}
/// Gets the inner inode.
pub(super) fn inode(&self) -> &Arc<dyn Inode> {
&self.inode
}
/// Returns whether the dentry can be cached.
fn is_dentry_cacheable(&self) -> bool {
// Should we store it as a dentry flag?
self.inode.is_dentry_cacheable()
}
fn flags(&self) -> DentryFlags {
let flags = self.flags.load(Ordering::Relaxed);
DentryFlags::from_bits(flags).unwrap()
}
/// Checks if this dentry is a descendant of or the same as the given
/// ancestor dentry.
pub(super) fn is_equal_or_descendant_of(&self, ancestor: &Arc<Self>) -> bool {
let mut current = Some(self.this());
while let Some(node) = current {
if Arc::ptr_eq(&node, ancestor) {
return true;
}
current = node.parent();
}
false
}
pub(super) fn is_mountpoint(&self) -> bool {
self.flags().contains(DentryFlags::MOUNTED)
}
pub(super) fn inc_mount_count(&self) {
// FIXME: Theoretically, an overflow could occur. In the future,
// we could prevent this by implementing a global maximum mount limit.
let old_count = self.mount_count.fetch_add(1, Ordering::Relaxed);
if old_count == 0 {
self.flags
.fetch_or(DentryFlags::MOUNTED.bits(), Ordering::Relaxed);
}
}
pub(super) fn dec_mount_count(&self) {
let old_count = self.mount_count.fetch_sub(1, Ordering::Relaxed);
if old_count == 1 {
self.flags
.fetch_and(!(DentryFlags::MOUNTED.bits()), Ordering::Relaxed);
}
}
/// Gets the absolute path name of this `Dentry` within the filesystem.
pub(super) fn path_name(&self) -> String {
let mut path_name = self.name().to_string();
let mut current_dir = self.this();
while let Some(parent_dir) = current_dir.parent() {
path_name = {
let parent_name = parent_dir.name();
if parent_name != "/" {
parent_name + "/" + &path_name
} else {
parent_name + &path_name
}
};
current_dir = parent_dir;
}
debug_assert!(path_name.starts_with('/') || self.is_pseudo());
path_name
}
pub(super) fn as_dir_dentry_or_err(&self) -> Result<DirDentry<'_>> {
debug_assert_eq!(self.children.is_some(), self.type_ == InodeType::Dir);
let Some(children) = &self.children else {
return_errno_with_message!(
Errno::ENOTDIR,
"the dentry is not related to a directory inode"
);
};
Ok(DirDentry {
inner: self,
children,
})
}
}
/// A `Dentry` wrapper that has been validated to represent a directory.
pub(super) struct DirDentry<'a> {
inner: &'a Dentry,
children: &'a RwMutex<DentryChildren>,
}
impl Deref for DirDentry<'_> {
type Target = Dentry;
fn deref(&self) -> &Self::Target {
self.inner
}
}
impl DirDentry<'_> {
/// Creates a `Dentry` by creating a new inode of the `type_` with the `mode`.
pub(super) fn create(
&self,
name: &str,
type_: InodeType,
mode: InodeMode,
) -> Result<Arc<Dentry>> {
let children = self.children.upread();
if children.contains_valid(name) {
return_errno!(Errno::EEXIST);
}
let new_inode = self.inode.create(name, type_, mode)?;
let name = String::from(name);
let new_child = Dentry::new(new_inode, DentryOptions::Leaf((name.clone(), self.this())));
if new_child.is_dentry_cacheable() {
children.upgrade().insert(name, new_child.clone());
}
Ok(new_child)
}
/// Lookups a target `Dentry` from the cache in children.
pub(super) fn lookup_via_cache(&self, name: &str) -> Result<Option<Arc<Dentry>>> {
let children = self.children.read();
children.find(name)
}
/// Lookups a target `Dentry` from the file system.
pub(super) fn lookup_via_fs(&self, name: &str) -> Result<Arc<Dentry>> {
let children = self.children.upread();
// TODO: Add a right implementation to cache negative dentry.
let inode = self.inode.lookup(name)?;
let name = String::from(name);
let target = Dentry::new(inode, DentryOptions::Leaf((name.clone(), self.this())));
if target.is_dentry_cacheable() {
children.upgrade().insert(name, target.clone());
}
Ok(target)
}
/// Creates a `Dentry` by making an inode of the `type_` with the `mode`.
pub(super) fn mknod(
&self,
name: &str,
mode: InodeMode,
type_: MknodType,
) -> Result<Arc<Dentry>> {
let children = self.children.upread();
if children.contains_valid(name) {
return_errno!(Errno::EEXIST);
}
let inode = self.inode.mknod(name, mode, type_)?;
let name = String::from(name);
let new_child = Dentry::new(inode, DentryOptions::Leaf((name.clone(), self.this())));
if new_child.is_dentry_cacheable() {
children.upgrade().insert(name, new_child.clone());
}
Ok(new_child)
}
/// Links a new `Dentry` by `link()` the old inode.
pub(super) fn link(&self, old_inode: &Arc<dyn Inode>, name: &str) -> Result<()> {
let children = self.children.upread();
if children.contains_valid(name) {
return_errno!(Errno::EEXIST);
}
self.inode.link(old_inode, name)?;
let name = String::from(name);
let dentry = Dentry::new(
old_inode.clone(),
DentryOptions::Leaf((name.clone(), self.this())),
);
if dentry.is_dentry_cacheable() {
children.upgrade().insert(name.clone(), dentry.clone());
}
fs::notify::on_link(dentry.parent().unwrap().inode(), dentry.inode(), || name);
Ok(())
}
/// Deletes a `Dentry` by `unlink()` the inner inode.
pub(super) fn unlink(&self, name: &str) -> Result<()> {
if is_dot_or_dotdot(name) {
return_errno_with_message!(Errno::EINVAL, "unlink on . or ..");
}
let children = self.children.upread();
children.check_mountpoint(name)?;
let mut children = children.upgrade();
let cached_child = children.delete(name);
let dir_inode = &self.inode;
let child_inode = match cached_child {
Some(child) => {
// Cache hit: use the cached dentry
child.inode().clone()
}
None => {
// Cache miss: need to lookup from the underlying filesystem
drop(children);
dir_inode.lookup(name)?
}
};
dir_inode.unlink(name)?;
let nlinks = child_inode.metadata().nr_hard_links;
fs::notify::on_link_count(&child_inode);
if nlinks == 0 {
// FIXME: `DELETE_SELF` should be generated after closing the last FD.
fs::notify::on_inode_removed(&child_inode);
}
fs::notify::on_delete(dir_inode, &child_inode, || name.to_string());
if nlinks == 0 {
// Ideally, we would use `fs_event_publisher()` here to avoid creating a
// `FsEventPublisher` instance on a dying inode. However, it isn't possible because we
// need to disable new subscribers.
let publisher = child_inode.fs_event_publisher_or_init();
let removed_nr_subscribers = publisher.disable_new_and_remove_subscribers();
child_inode
.fs()
.fs_event_subscriber_stats()
.remove_subscribers(removed_nr_subscribers);
}
Ok(())
}
/// Deletes a directory `Dentry` by `rmdir()` the inner inode.
pub(super) fn rmdir(&self, name: &str) -> Result<()> {
if is_dot(name) {
return_errno_with_message!(Errno::EINVAL, "rmdir on .");
}
if is_dotdot(name) {
return_errno_with_message!(Errno::ENOTEMPTY, "rmdir on ..");
}
let children = self.children.upread();
children.check_mountpoint(name)?;
let mut children = children.upgrade();
let cached_child = children.delete(name);
let dir_inode = &self.inode;
let child_inode = match cached_child {
Some(child) => {
// Cache hit: use the cached dentry
child.inode().clone()
}
None => {
// Cache miss: need to lookup from the underlying filesystem
drop(children);
dir_inode.lookup(name)?
}
};
dir_inode.rmdir(name)?;
let nlinks = child_inode.metadata().nr_hard_links;
if nlinks == 0 {
// FIXME: `DELETE_SELF` should be generated after closing the last FD.
fs::notify::on_inode_removed(&child_inode);
}
fs::notify::on_delete(dir_inode, &child_inode, || name.to_string());
if nlinks == 0 {
// Ideally, we would use `fs_event_publisher()` here to avoid creating a
// `FsEventPublisher` instance on a dying inode. However, it isn't possible because we
// need to disable new subscribers.
let publisher = child_inode.fs_event_publisher_or_init();
let removed_nr_subscribers = publisher.disable_new_and_remove_subscribers();
child_inode
.fs()
.fs_event_subscriber_stats()
.remove_subscribers(removed_nr_subscribers);
}
Ok(())
}
/// Renames a `Dentry` to the new `Dentry` by `rename()` the inner inode.
pub(super) fn rename(
old_dir_arc: &Arc<Dentry>,
old_name: &str,
new_dir_arc: &Arc<Dentry>,
new_name: &str,
) -> Result<()> {
let old_dir = old_dir_arc.as_dir_dentry_or_err()?;
let new_dir = new_dir_arc.as_dir_dentry_or_err()?;
if is_dot_or_dotdot(old_name) || is_dot_or_dotdot(new_name) {
return_errno_with_message!(Errno::EISDIR, "old_name or new_name is a directory");
}
let old_dir_inode = old_dir.inode();
let new_dir_inode = new_dir.inode();
// The two are the same dentry, we just modify the name
if Arc::ptr_eq(old_dir_arc, new_dir_arc) {
if old_name == new_name {
return Ok(());
}
let children = old_dir.children.upread();
let old_dentry = children.check_mountpoint_then_find(old_name)?;
children.check_mountpoint(new_name)?;
old_dir_inode.rename(old_name, old_dir_inode, new_name)?;
let mut children = children.upgrade();
match old_dentry.as_ref() {
Some(dentry) => {
children.delete(old_name);
dentry
.name_and_parent
.set(new_name, old_dir_arc.clone())
.unwrap();
if dentry.is_dentry_cacheable() {
children.insert(String::from(new_name), dentry.clone());
}
}
None => {
children.delete(new_name);
}
}
} else {
// The two are different dentries
let (mut self_children, mut new_dir_children) =
write_lock_children_on_two_dentries(&old_dir, &new_dir);
let old_dentry = self_children.check_mountpoint_then_find(old_name)?;
new_dir_children.check_mountpoint(new_name)?;
old_dir_inode.rename(old_name, new_dir_inode, new_name)?;
match old_dentry.as_ref() {
Some(dentry) => {
self_children.delete(old_name);
dentry
.name_and_parent
.set(new_name, new_dir_arc.clone())
.unwrap();
if dentry.is_dentry_cacheable() {
new_dir_children.insert(String::from(new_name), dentry.clone());
}
}
None => {
new_dir_children.delete(new_name);
}
}
}
Ok(())
}
}
impl Debug for Dentry {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
f.debug_struct("Dentry")
.field("inode", &self.inode)
.field("type_", &self.type_)
.field("flags", &self.flags)
.field("mount_count", &self.mount_count)
.finish_non_exhaustive()
}
}
/// `DentryKey` is the unique identifier for the corresponding `Dentry`.
///
/// - For non-root dentries, it uses self's name and parent's pointer to form the key,
/// - For the root dentry, it uses "/" and self's pointer to form the key.
/// - For pseudo dentries, it uses self's name and self's pointer to form the key.
#[derive(Debug, Clone, Hash, PartialOrd, Ord, Eq, PartialEq)]
pub(super) struct DentryKey {
name: String,
parent_ptr: usize,
}
impl DentryKey {
/// Forms a `DentryKey` from the corresponding `Dentry`.
fn new(dentry: &Dentry) -> Self {
let name = dentry.name();
let parent = dentry.parent().unwrap_or_else(|| dentry.this());
Self {
name,
parent_ptr: Arc::as_ptr(&parent) as usize,
}
}
}
bitflags! {
struct DentryFlags: u32 {
const MOUNTED = 1 << 0;
}
}
enum DentryOptions {
Root,
Leaf((String, Arc<Dentry>)),
Pseudo(fn(&dyn Inode) -> String),
}
/// Manages child dentries, including both valid and negative entries.
///
/// A _negative_ dentry reflects a failed filename lookup, saving potential
/// repeated and costly lookups in the future.
// TODO: Address the issue of negative dentry bloating. See the reference
// https://lwn.net/Articles/894098/ for more details.
struct DentryChildren {
dentries: HashMap<String, Option<Arc<Dentry>>>,
}
impl DentryChildren {
/// Creates an empty dentry cache.
fn new() -> Self {
Self {
dentries: HashMap::new(),
}
}
/// Checks if a valid dentry with the given name exists.
fn contains_valid(&self, name: &str) -> bool {
self.dentries.get(name).is_some_and(|child| child.is_some())
}
/// Checks if a negative dentry with the given name exists.
#[expect(dead_code)]
fn contains_negative(&self, name: &str) -> bool {
self.dentries.get(name).is_some_and(|child| child.is_none())
}
/// Finds a dentry by name. Returns error for negative entries.
fn find(&self, name: &str) -> Result<Option<Arc<Dentry>>> {
match self.dentries.get(name) {
Some(Some(child)) => Ok(Some(child.clone())),
Some(None) => return_errno_with_message!(Errno::ENOENT, "found a negative dentry"),
None => Ok(None),
}
}
/// Inserts a valid cacheable dentry.
fn insert(&mut self, name: String, dentry: Arc<Dentry>) {
// Assume the caller has checked that the dentry is cacheable
// and will be newly created if looked up from the parent.
debug_assert!(dentry.is_dentry_cacheable());
let _ = self.dentries.insert(name, Some(dentry));
}
/// Inserts a negative dentry.
#[expect(dead_code)]
fn insert_negative(&mut self, name: String) {
let _ = self.dentries.insert(name, None);
}
/// Deletes a dentry by name, turning it into a negative entry if exists.
fn delete(&mut self, name: &str) -> Option<Arc<Dentry>> {
self.dentries.get_mut(name).and_then(Option::take)
}
/// Checks whether the dentry is a mount point. Returns an error if it is.
fn check_mountpoint(&self, name: &str) -> Result<()> {
if let Some(Some(dentry)) = self.dentries.get(name)
&& dentry.is_mountpoint()
{
return_errno_with_message!(Errno::EBUSY, "dentry is mountpint");
}
Ok(())
}
/// Checks if dentry is a mount point, then retrieves it.
fn check_mountpoint_then_find(&self, name: &str) -> Result<Option<Arc<Dentry>>> {
match self.dentries.get(name) {
Some(Some(dentry)) => {
if dentry.is_mountpoint() {
return_errno_with_message!(Errno::EBUSY, "dentry is mountpoint");
}
Ok(Some(dentry.clone()))
}
Some(None) => return_errno_with_message!(Errno::ENOENT, "found a negative dentry"),
None => Ok(None),
}
}
}
fn write_lock_children_on_two_dentries<'a>(
this: &'a DirDentry,
other: &'a DirDentry,
) -> (
RwMutexWriteGuard<'a, DentryChildren>,
RwMutexWriteGuard<'a, DentryChildren>,
) {
let this_key = this.key();
let other_key = other.key();
if this_key < other_key {
let this = this.children.write();
let other = other.children.write();
(this, other)
} else {
let other = other.children.write();
let this = this.children.write();
(this, other)
}
}