Add fdinfo files to proc filesystem

This commit is contained in:
Chen Chengjun 2025-11-19 07:20:11 +00:00 committed by Tate, Hongliang Tian
parent 668876aeee
commit 61a5bb7241
5 changed files with 163 additions and 33 deletions

View File

@ -4,11 +4,16 @@
//! Opened File Handle
use core::fmt::Display;
use ostd::io::IoMem;
use super::inode_handle::InodeHandle;
use crate::{
fs::utils::{AccessMode, FallocMode, Inode, IoctlCmd, SeekFrom, StatusFlags},
fs::{
file_table::FdFlags,
utils::{AccessMode, FallocMode, Inode, IoctlCmd, SeekFrom, StatusFlags},
},
net::socket::Socket,
prelude::*,
process::signal::Pollable,
@ -92,6 +97,19 @@ pub trait FileLike: Pollable + Send + Sync + Any {
}
fn inode(&self) -> &Arc<dyn Inode>;
/// Dumps information to appear in the `fdinfo` file under procfs.
///
/// This method must not break atomic mode because it will be called with the file table's spin
/// lock held. There are two strategies for implementing this method:
/// - If the necessary information can be obtained without breaking atomic mode, the method
/// can collect and return the information directly. `Arc<Self>` should be dropped and
/// should not appear in the returned `Box<dyn Display>`.
/// - Otherwise, if the file can be dropped asynchronously in another process, the method can
/// return a `Box<dyn Display>` containing the `Arc<Self>`, so that the information can be
/// collected later in its `Display::display()` method, after dropping the file table's spin
/// lock.
fn dump_proc_fdinfo(self: Arc<Self>, fd_flags: FdFlags) -> Box<dyn Display>;
}
impl dyn FileLike {

View File

@ -1,14 +1,20 @@
// SPDX-License-Identifier: MPL-2.0
use aster_util::slot_vec::SlotVec;
use core::marker::PhantomData;
use aster_util::{printer::VmPrinter, slot_vec::SlotVec};
use ostd::sync::RwMutexUpgradeableGuard;
use super::TidDirOps;
use crate::{
fs::{
file_handle::FileLike,
file_table::FileDesc,
inode_handle::InodeHandle,
procfs::{template::ProcSym, DirOps, ProcDir, ProcDirBuilder, ProcSymBuilder, SymOps},
procfs::{
template::{FileOps, ProcFile, ProcFileBuilder, ProcSym},
DirOps, ProcDir, ProcDirBuilder, ProcSymBuilder, SymOps,
},
utils::{chmod, mkmod, AccessMode, DirEntryVecExt, Inode, SymbolicLink},
},
prelude::*,
@ -16,19 +22,28 @@ use crate::{
};
/// Represents the inode at `/proc/[pid]/task/[tid]/fd` (and also `/proc/[pid]/fd`).
pub struct FdDirOps(TidDirOps);
pub(super) struct FdDirOps<T> {
dir: TidDirOps,
marker: PhantomData<T>,
}
impl FdDirOps {
impl<T: FdOps> FdDirOps<T> {
pub fn new_inode(dir: &TidDirOps, parent: Weak<dyn Inode>) -> Arc<dyn Inode> {
// Reference: <https://elixir.bootlin.com/linux/v6.16.5/source/fs/proc/base.c#L3317>
ProcDirBuilder::new(Self(dir.clone()), mkmod!(u+rx))
.parent(parent)
.build()
.unwrap()
ProcDirBuilder::new(
Self {
dir: dir.clone(),
marker: PhantomData,
},
// Reference: <https://elixir.bootlin.com/linux/v6.16.5/source/fs/proc/base.c#L3317>
mkmod!(u+rx),
)
.parent(parent)
.build()
.unwrap()
}
}
impl DirOps for FdDirOps {
impl<T: FdOps> DirOps for FdDirOps<T> {
// Lock order: cached entries -> file table
//
// Note that inverting the lock order is non-trivial because the file table is protected by a
@ -41,7 +56,7 @@ impl DirOps for FdDirOps {
let mut cached_children = dir.cached_children().write();
let thread = self.0.thread();
let thread = self.dir.thread();
let posix_thread = thread.as_posix_thread().unwrap();
let access_mode = if let Some(file_table) = posix_thread.file_table().lock().as_ref()
@ -52,8 +67,8 @@ impl DirOps for FdDirOps {
return_errno_with_message!(Errno::ENOENT, "the file does not exist");
};
let child = FileSymOps::new_inode(
self.0.clone(),
let child = T::new_inode(
self.dir.clone(),
file_desc,
access_mode,
dir.this_weak().clone(),
@ -73,7 +88,7 @@ impl DirOps for FdDirOps {
) -> RwMutexUpgradeableGuard<'a, SlotVec<(String, Arc<dyn Inode>)>> {
let mut cached_children = dir.cached_children().write();
let thread = self.0.thread();
let thread = self.dir.thread();
let posix_thread = thread.as_posix_thread().unwrap();
let file_table = posix_thread.file_table().lock();
@ -89,24 +104,23 @@ impl DirOps for FdDirOps {
let Some((_, child)) = cached_children.get(i) else {
continue;
};
let child = child.downcast_ref::<ProcSym<FileSymOps>>().unwrap();
let child = child.downcast_ref::<T::NodeType>().unwrap();
let child_ops = T::ref_from_inode(child);
let Ok(file) = file_table.get_file(child.inner().file_desc) else {
let Ok(file) = file_table.get_file(child_ops.file_desc()) else {
cached_children.remove(i);
continue;
};
if file.access_mode() != child.inner().access_mode {
if !child_ops.is_valid(file) {
cached_children.remove(i);
}
// We'll reuse the old entry if the access mode is the same, even if the file is
// different.
}
// Add new entries.
for (file_desc, file) in file_table.fds_and_files() {
cached_children.put_entry_if_not_found(&file_desc.to_string(), || {
FileSymOps::new_inode(
self.0.clone(),
T::new_inode(
self.dir.clone(),
file_desc,
file.access_mode(),
dir.this_weak().clone(),
@ -118,17 +132,16 @@ impl DirOps for FdDirOps {
}
fn validate_child(&self, child: &dyn Inode) -> bool {
let ops = child.downcast_ref::<ProcSym<FileSymOps>>().unwrap();
let child = child.downcast_ref::<T::NodeType>().unwrap();
let child_ops = T::ref_from_inode(child);
let thread = self.0.thread();
let thread = self.dir.thread();
let posix_thread = thread.as_posix_thread().unwrap();
let is_valid = if let Some(file_table) = posix_thread.file_table().lock().as_ref()
&& let Ok(file) = file_table.read().get_file(ops.inner().file_desc)
&& let Ok(file) = file_table.read().get_file(child_ops.file_desc())
{
// We'll reuse the old entry if the access mode is the same, even if the file is
// different.
file.access_mode() == ops.inner().access_mode
child_ops.is_valid(file)
} else {
false
};
@ -137,15 +150,34 @@ impl DirOps for FdDirOps {
}
}
pub(super) trait FdOps: Send + Sync + 'static {
type NodeType: Inode;
fn new_inode(
tid_dir_ops: TidDirOps,
file_desc: FileDesc,
access_mode: AccessMode,
parent: Weak<dyn Inode>,
) -> Arc<dyn Inode>;
fn file_desc(&self) -> FileDesc;
fn is_valid(&self, correspond_file: &Arc<dyn FileLike>) -> bool;
fn ref_from_inode(inode: &Self::NodeType) -> &Self;
}
/// Represents the inode at `/proc/[pid]/task/[tid]/fd/[n]` (and also `/proc/[pid]/fd/[n]`).
struct FileSymOps {
pub(super) struct FileSymOps {
tid_dir_ops: TidDirOps,
file_desc: FileDesc,
access_mode: AccessMode,
}
impl FileSymOps {
pub fn new_inode(
impl FdOps for FileSymOps {
type NodeType = ProcSym<FileSymOps>;
fn new_inode(
tid_dir_ops: TidDirOps,
file_desc: FileDesc,
access_mode: AccessMode,
@ -172,6 +204,20 @@ impl FileSymOps {
.build()
.unwrap()
}
fn file_desc(&self) -> FileDesc {
self.file_desc
}
fn is_valid(&self, correspond_file: &Arc<dyn FileLike>) -> bool {
// We'll treat the old entry as valid and reuse it if the access mode is the same,
// even if the file is different.
self.access_mode == correspond_file.access_mode()
}
fn ref_from_inode(inode: &Self::NodeType) -> &Self {
inode.inner()
}
}
impl SymOps for FileSymOps {
@ -197,3 +243,64 @@ impl SymOps for FileSymOps {
Ok(res)
}
}
/// Represents the inode at `/proc/[pid]/task/[tid]/fdinfo/[n]` (and also `/proc/[pid]/fdinfo/[n]`).
pub(super) struct FileInfoOps {
tid_dir_ops: TidDirOps,
file_desc: FileDesc,
}
impl FdOps for FileInfoOps {
type NodeType = ProcFile<FileInfoOps>;
fn new_inode(
tid_dir_ops: TidDirOps,
file_desc: FileDesc,
_access_mode: AccessMode,
parent: Weak<dyn Inode>,
) -> Arc<dyn Inode> {
ProcFileBuilder::new(
Self {
tid_dir_ops,
file_desc,
},
// Reference: <https://elixir.bootlin.com/linux/v6.16.5/source/fs/proc/fd.c#L383>.
mkmod!(a+r),
)
.parent(parent)
.build()
.unwrap()
}
fn file_desc(&self) -> FileDesc {
self.file_desc
}
fn is_valid(&self, _correspond_file: &Arc<dyn FileLike>) -> bool {
true
}
fn ref_from_inode(inode: &Self::NodeType) -> &Self {
inode.inner()
}
}
impl FileOps for FileInfoOps {
fn read_at(&self, offset: usize, writer: &mut VmWriter) -> Result<usize> {
let thread = self.tid_dir_ops.thread();
let posix_thread = thread.as_posix_thread().unwrap();
let info = if let Some(file_table) = posix_thread.file_table().lock().as_ref()
&& let Ok(entry) = file_table.read().get_entry(self.file_desc)
{
entry.file().clone().dump_proc_fdinfo(entry.flags())
} else {
return_errno_with_message!(Errno::ENOENT, "the file does not exist");
};
let mut printer = VmPrinter::new_skip(writer, offset);
write!(printer, "{}", info)?;
Ok(printer.bytes_written())
}
}

View File

@ -103,7 +103,8 @@ impl TidDirOps {
("comm", CommFileOps::new_inode),
("environ", EnvironFileOps::new_inode),
("exe", ExeSymOps::new_inode),
("fd", FdDirOps::new_inode),
("fd", FdDirOps::<fd::FileSymOps>::new_inode),
("fdinfo", FdDirOps::<fd::FileInfoOps>::new_inode),
("gid_map", GidMapFileOps::new_inode),
("mem", MemFileOps::new_inode),
("mountinfo", MountInfoFileOps::new_inode),

View File

@ -36,6 +36,10 @@ impl<F: FileOps> ProcFile<F> {
common,
})
}
pub fn inner(&self) -> &F {
&self.inner
}
}
impl<F: FileOps + 'static> InodeIo for ProcFile<F> {

View File

@ -5,7 +5,7 @@ use core::time::Duration;
pub(super) use self::{
builder::{ProcDirBuilder, ProcFileBuilder, ProcSymBuilder},
dir::{lookup_child_from_table, populate_children_from_table, DirOps, ProcDir},
file::FileOps,
file::{FileOps, ProcFile},
sym::{ProcSym, SymOps},
};
use super::{ProcFs, BLOCK_SIZE};