516 lines
17 KiB
Rust
516 lines
17 KiB
Rust
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
#![expect(dead_code)]
|
|
|
|
use core::{
|
|
sync::atomic::{AtomicU32, Ordering},
|
|
time::Duration,
|
|
};
|
|
|
|
use hashbrown::HashMap;
|
|
use inherit_methods_macro::inherit_methods;
|
|
use ostd::sync::RwMutexWriteGuard;
|
|
|
|
use super::is_dot_or_dotdot;
|
|
use crate::{
|
|
fs::utils::{
|
|
FileSystem, Inode, InodeMode, InodeType, Metadata, MknodType, XattrName, XattrNamespace,
|
|
XattrSetFlags,
|
|
},
|
|
prelude::*,
|
|
process::{Gid, Uid},
|
|
};
|
|
|
|
/// A `Dentry` represents a cached filesystem node in the VFS tree.
|
|
pub(super) struct Dentry {
|
|
inode: Arc<dyn Inode>,
|
|
type_: InodeType,
|
|
name_and_parent: RwLock<Option<(String, Arc<Dentry>)>>,
|
|
children: RwMutex<DentryChildren>,
|
|
flags: AtomicU32,
|
|
mount_count: AtomicU32,
|
|
this: Weak<Dentry>,
|
|
}
|
|
|
|
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)
|
|
}
|
|
|
|
fn new(inode: Arc<dyn Inode>, options: DentryOptions) -> Arc<Self> {
|
|
Arc::new_cyclic(|weak_self| Self {
|
|
type_: inode.type_(),
|
|
inode,
|
|
name_and_parent: match options {
|
|
DentryOptions::Leaf(name_and_parent) => RwLock::new(Some(name_and_parent)),
|
|
_ => RwLock::new(None),
|
|
},
|
|
children: RwMutex::new(DentryChildren::new()),
|
|
flags: AtomicU32::new(DentryFlags::empty().bits()),
|
|
mount_count: AtomicU32::new(0),
|
|
this: weak_self.clone(),
|
|
})
|
|
}
|
|
|
|
/// 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 {
|
|
match self.name_and_parent.read().as_ref() {
|
|
Some(name_and_parent) => name_and_parent.0.clone(),
|
|
None => String::from("/"),
|
|
}
|
|
}
|
|
|
|
/// Gets the parent `Dentry`.
|
|
///
|
|
/// Returns `None` if it is a root `Dentry`.
|
|
pub(super) fn parent(&self) -> Option<Arc<Self>> {
|
|
self.name_and_parent
|
|
.read()
|
|
.as_ref()
|
|
.map(|name_and_parent| name_and_parent.1.clone())
|
|
}
|
|
|
|
fn set_name_and_parent(&self, name: &str, parent: Arc<Self>) {
|
|
let mut name_and_parent = self.name_and_parent.write();
|
|
*name_and_parent = Some((String::from(name), 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
|
|
}
|
|
|
|
fn flags(&self) -> DentryFlags {
|
|
let flags = self.flags.load(Ordering::Relaxed);
|
|
DentryFlags::from_bits(flags).unwrap()
|
|
}
|
|
|
|
/// Checks if this dentry is a descendant (child, grandchild, or
|
|
/// great-grandchild, etc.) of another dentry.
|
|
pub(super) fn is_descendant_of(&self, ancestor: &Arc<Self>) -> bool {
|
|
let mut parent = self.parent();
|
|
while let Some(p) = parent {
|
|
if Arc::ptr_eq(&p, ancestor) {
|
|
return true;
|
|
}
|
|
parent = p.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);
|
|
}
|
|
}
|
|
|
|
/// 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<Self>> {
|
|
if self.type_() != InodeType::Dir {
|
|
return_errno!(Errno::ENOTDIR);
|
|
}
|
|
|
|
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();
|
|
|
|
let inode = match self.inode.lookup(name) {
|
|
Ok(inode) => inode,
|
|
Err(e) => {
|
|
if e.error() == Errno::ENOENT && self.is_dentry_cacheable() {
|
|
children.upgrade().insert_negative(String::from(name));
|
|
}
|
|
return Err(e);
|
|
}
|
|
};
|
|
let name = String::from(name);
|
|
let target = Self::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<Self>> {
|
|
if self.type_() != InodeType::Dir {
|
|
return_errno!(Errno::ENOTDIR);
|
|
}
|
|
|
|
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 name for the `Dentry` by `link()` the inner inode.
|
|
pub(super) fn link(&self, old: &Arc<Self>, name: &str) -> Result<()> {
|
|
if self.type_() != InodeType::Dir {
|
|
return_errno!(Errno::ENOTDIR);
|
|
}
|
|
|
|
let children = self.children.upread();
|
|
if children.contains_valid(name) {
|
|
return_errno!(Errno::EEXIST);
|
|
}
|
|
|
|
let old_inode = old.inode();
|
|
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, dentry.clone());
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
/// Deletes a `Dentry` by `unlink()` the inner inode.
|
|
pub(super) fn unlink(&self, name: &str) -> Result<()> {
|
|
if self.type_() != InodeType::Dir {
|
|
return_errno!(Errno::ENOTDIR);
|
|
}
|
|
|
|
let children = self.children.upread();
|
|
children.check_mountpoint(name)?;
|
|
|
|
self.inode.unlink(name)?;
|
|
|
|
let mut children = children.upgrade();
|
|
children.delete(name);
|
|
Ok(())
|
|
}
|
|
|
|
/// Deletes a directory `Dentry` by `rmdir()` the inner inode.
|
|
pub(super) fn rmdir(&self, name: &str) -> Result<()> {
|
|
if self.type_() != InodeType::Dir {
|
|
return_errno!(Errno::ENOTDIR);
|
|
}
|
|
|
|
let children = self.children.upread();
|
|
children.check_mountpoint(name)?;
|
|
|
|
self.inode.rmdir(name)?;
|
|
|
|
let mut children = children.upgrade();
|
|
children.delete(name);
|
|
Ok(())
|
|
}
|
|
|
|
/// Renames a `Dentry` to the new `Dentry` by `rename()` the inner inode.
|
|
pub(super) fn rename(&self, old_name: &str, new_dir: &Arc<Self>, new_name: &str) -> Result<()> {
|
|
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");
|
|
}
|
|
if self.type_() != InodeType::Dir || new_dir.type_() != InodeType::Dir {
|
|
return_errno!(Errno::ENOTDIR);
|
|
}
|
|
|
|
// The two are the same dentry, we just modify the name
|
|
if Arc::ptr_eq(&self.this(), new_dir) {
|
|
if old_name == new_name {
|
|
return Ok(());
|
|
}
|
|
|
|
let children = self.children.upread();
|
|
let old_dentry = children.check_mountpoint_then_find(old_name)?;
|
|
children.check_mountpoint(new_name)?;
|
|
|
|
self.inode.rename(old_name, &self.inode, new_name)?;
|
|
|
|
let mut children = children.upgrade();
|
|
match old_dentry.as_ref() {
|
|
Some(dentry) => {
|
|
children.delete(old_name);
|
|
dentry.set_name_and_parent(new_name, self.this());
|
|
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(self, new_dir);
|
|
let old_dentry = self_children.check_mountpoint_then_find(old_name)?;
|
|
new_dir_children.check_mountpoint(new_name)?;
|
|
|
|
self.inode.rename(old_name, &new_dir.inode, new_name)?;
|
|
match old_dentry.as_ref() {
|
|
Some(dentry) => {
|
|
self_children.delete(old_name);
|
|
dentry.set_name_and_parent(new_name, new_dir.this());
|
|
if dentry.is_dentry_cacheable() {
|
|
new_dir_children.insert(String::from(new_name), dentry.clone());
|
|
}
|
|
}
|
|
None => {
|
|
new_dir_children.delete(new_name);
|
|
}
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
#[inherit_methods(from = "self.inode")]
|
|
impl Dentry {
|
|
pub(super) fn fs(&self) -> Arc<dyn FileSystem>;
|
|
pub(super) fn sync_all(&self) -> Result<()>;
|
|
pub(super) fn sync_data(&self) -> Result<()>;
|
|
pub(super) fn metadata(&self) -> Metadata;
|
|
pub(super) fn mode(&self) -> Result<InodeMode>;
|
|
pub(super) fn set_mode(&self, mode: InodeMode) -> Result<()>;
|
|
pub(super) fn size(&self) -> usize;
|
|
pub(super) fn resize(&self, size: usize) -> Result<()>;
|
|
pub(super) fn owner(&self) -> Result<Uid>;
|
|
pub(super) fn set_owner(&self, uid: Uid) -> Result<()>;
|
|
pub(super) fn group(&self) -> Result<Gid>;
|
|
pub(super) fn set_group(&self, gid: Gid) -> Result<()>;
|
|
pub(super) fn atime(&self) -> Duration;
|
|
pub(super) fn set_atime(&self, time: Duration);
|
|
pub(super) fn mtime(&self) -> Duration;
|
|
pub(super) fn set_mtime(&self, time: Duration);
|
|
pub(super) fn ctime(&self) -> Duration;
|
|
pub(super) fn set_ctime(&self, time: Duration);
|
|
pub(super) fn is_dentry_cacheable(&self) -> bool;
|
|
pub(super) fn set_xattr(
|
|
&self,
|
|
name: XattrName,
|
|
value_reader: &mut VmReader,
|
|
flags: XattrSetFlags,
|
|
) -> Result<()>;
|
|
pub(super) fn get_xattr(&self, name: XattrName, value_writer: &mut VmWriter) -> Result<usize>;
|
|
pub(super) fn list_xattr(
|
|
&self,
|
|
namespace: XattrNamespace,
|
|
list_writer: &mut VmWriter,
|
|
) -> Result<usize>;
|
|
pub(super) fn remove_xattr(&self, name: XattrName) -> Result<()>;
|
|
}
|
|
|
|
impl Debug for Dentry {
|
|
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
|
|
f.debug_struct("Dentry")
|
|
.field("inode", &self.inode)
|
|
.field("flags", &self.flags())
|
|
.finish()
|
|
}
|
|
}
|
|
|
|
/// `DentryKey` is the unique identifier for the corresponding `Dentry`.
|
|
///
|
|
/// For none-root dentries, it uses self's name and parent's pointer to form the key,
|
|
/// meanwhile, the root `Dentry` uses "/" 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`.
|
|
pub(super) fn new(dentry: &Dentry) -> Self {
|
|
let (name, parent) = match dentry.name_and_parent.read().as_ref() {
|
|
Some(name_and_parent) => name_and_parent.clone(),
|
|
None => (String::from("/"), 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>)),
|
|
}
|
|
|
|
/// 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.
|
|
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.
|
|
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) {
|
|
if 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 Dentry,
|
|
other: &'a Dentry,
|
|
) -> (
|
|
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)
|
|
}
|
|
}
|