mirror of
https://github.com/asterinas/asterinas.git
synced 2025-06-14 15:56:47 +00:00
433 lines
15 KiB
Rust
433 lines
15 KiB
Rust
use crate::prelude::*;
|
|
use alloc::str;
|
|
|
|
use super::file_table::FileDescripter;
|
|
use super::inode_handle::InodeHandle;
|
|
use super::procfs::ProcFS;
|
|
use super::ramfs::RamFS;
|
|
use super::utils::{
|
|
AccessMode, CreationFlags, Dentry, FileSystem, InodeMode, InodeType, StatusFlags, Vnode,
|
|
PATH_MAX, SYMLINKS_MAX,
|
|
};
|
|
|
|
lazy_static! {
|
|
static ref ROOT_FS: Arc<dyn FileSystem> = RamFS::new(true);
|
|
static ref ROOT_DENTRY: Arc<Dentry> = {
|
|
let vnode = Vnode::new(ROOT_FS.root_inode()).unwrap();
|
|
Dentry::new_root(vnode)
|
|
};
|
|
static ref PROC_FS: Arc<dyn FileSystem> = ProcFS::new();
|
|
static ref PROC_DENTRY: Arc<Dentry> = {
|
|
let vnode = Vnode::new(PROC_FS.root_inode()).unwrap();
|
|
Dentry::new_root(vnode)
|
|
};
|
|
static ref DEV_FS: Arc<dyn FileSystem> = RamFS::new(false);
|
|
static ref DEV_DENTRY: Arc<Dentry> = {
|
|
let vnode = Vnode::new(DEV_FS.root_inode()).unwrap();
|
|
Dentry::new_root(vnode)
|
|
};
|
|
}
|
|
|
|
pub struct FsResolver {
|
|
root: Arc<Dentry>,
|
|
cwd: Arc<Dentry>,
|
|
}
|
|
|
|
impl Clone for FsResolver {
|
|
fn clone(&self) -> Self {
|
|
Self {
|
|
root: self.root.clone(),
|
|
cwd: self.cwd.clone(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl FsResolver {
|
|
pub fn new() -> Self {
|
|
Self {
|
|
root: ROOT_DENTRY.clone(),
|
|
cwd: ROOT_DENTRY.clone(),
|
|
}
|
|
}
|
|
|
|
/// Get the root directory
|
|
pub fn root(&self) -> &Arc<Dentry> {
|
|
&self.root
|
|
}
|
|
|
|
/// Get the current working directory
|
|
pub fn cwd(&self) -> &Arc<Dentry> {
|
|
&self.cwd
|
|
}
|
|
|
|
/// Set the current working directory.
|
|
pub fn set_cwd(&mut self, dentry: Arc<Dentry>) {
|
|
self.cwd = dentry;
|
|
}
|
|
|
|
/// Open or create a file inode handler.
|
|
pub fn open(&self, path: &FsPath, flags: u32, mode: u16) -> Result<InodeHandle> {
|
|
let creation_flags = CreationFlags::from_bits_truncate(flags);
|
|
let status_flags = StatusFlags::from_bits_truncate(flags);
|
|
let access_mode = AccessMode::from_u32(flags)?;
|
|
let inode_mode = InodeMode::from_bits_truncate(mode);
|
|
|
|
let follow_tail_link = !creation_flags.contains(CreationFlags::O_NOFOLLOW);
|
|
let dentry = match self.lookup_inner(path, follow_tail_link) {
|
|
Ok(dentry) => {
|
|
let vnode = dentry.vnode();
|
|
if vnode.inode_type() == InodeType::SymLink
|
|
&& !status_flags.contains(StatusFlags::O_PATH)
|
|
{
|
|
return_errno_with_message!(Errno::ELOOP, "file is a symlink");
|
|
}
|
|
if creation_flags.contains(CreationFlags::O_CREAT)
|
|
&& creation_flags.contains(CreationFlags::O_EXCL)
|
|
{
|
|
return_errno_with_message!(Errno::EEXIST, "file exists");
|
|
}
|
|
if creation_flags.contains(CreationFlags::O_DIRECTORY)
|
|
&& vnode.inode_type() != InodeType::Dir
|
|
{
|
|
return_errno_with_message!(
|
|
Errno::ENOTDIR,
|
|
"O_DIRECTORY is specified but file is not a directory"
|
|
);
|
|
}
|
|
dentry
|
|
}
|
|
Err(e)
|
|
if e.error() == Errno::ENOENT
|
|
&& creation_flags.contains(CreationFlags::O_CREAT) =>
|
|
{
|
|
if creation_flags.contains(CreationFlags::O_DIRECTORY) {
|
|
return_errno_with_message!(Errno::ENOTDIR, "cannot create directory");
|
|
}
|
|
let (dir_dentry, file_name) =
|
|
self.lookup_dir_and_base_name_inner(path, follow_tail_link)?;
|
|
if file_name.ends_with("/") {
|
|
return_errno_with_message!(Errno::EISDIR, "path refers to a directory");
|
|
}
|
|
if !dir_dentry.vnode().inode_mode().is_writable() {
|
|
return_errno_with_message!(Errno::EPERM, "file cannot be created");
|
|
}
|
|
let new_dentry = dir_dentry.create(&file_name, InodeType::File, inode_mode)?;
|
|
new_dentry
|
|
}
|
|
Err(e) => return Err(e),
|
|
};
|
|
|
|
let inode_handle = InodeHandle::new(dentry, access_mode, status_flags)?;
|
|
Ok(inode_handle)
|
|
}
|
|
|
|
/// Lookup dentry according to FsPath, always follow symlinks
|
|
pub fn lookup(&self, path: &FsPath) -> Result<Arc<Dentry>> {
|
|
self.lookup_inner(path, true)
|
|
}
|
|
|
|
/// Lookup dentry according to FsPath, do not follow it if last component is a symlink
|
|
pub fn lookup_no_follow(&self, path: &FsPath) -> Result<Arc<Dentry>> {
|
|
self.lookup_inner(path, false)
|
|
}
|
|
|
|
fn lookup_inner(&self, path: &FsPath, follow_tail_link: bool) -> Result<Arc<Dentry>> {
|
|
let dentry = match path.inner {
|
|
FsPathInner::Absolute(path) => {
|
|
// TODO: Mount procfs at "/proc" if mount feature is ready
|
|
if path.starts_with("/proc") {
|
|
let path = path.strip_prefix("/proc").unwrap();
|
|
self.lookup_from_parent(
|
|
&PROC_DENTRY,
|
|
path.trim_start_matches('/'),
|
|
follow_tail_link,
|
|
)?
|
|
} else if path.starts_with("/dev") {
|
|
let path = path.strip_prefix("/dev").unwrap();
|
|
self.lookup_from_parent(
|
|
&DEV_DENTRY,
|
|
path.trim_start_matches('/'),
|
|
follow_tail_link,
|
|
)?
|
|
} else {
|
|
self.lookup_from_parent(
|
|
&self.root,
|
|
path.trim_start_matches('/'),
|
|
follow_tail_link,
|
|
)?
|
|
}
|
|
}
|
|
FsPathInner::CwdRelative(path) => {
|
|
self.lookup_from_parent(&self.cwd, path, follow_tail_link)?
|
|
}
|
|
FsPathInner::Cwd => self.cwd.clone(),
|
|
FsPathInner::FdRelative(fd, path) => {
|
|
let parent = self.lookup_from_fd(fd)?;
|
|
self.lookup_from_parent(&parent, path, follow_tail_link)?
|
|
}
|
|
FsPathInner::Fd(fd) => self.lookup_from_fd(fd)?,
|
|
};
|
|
|
|
Ok(dentry)
|
|
}
|
|
|
|
/// Lookup dentry from parent
|
|
///
|
|
/// The length of `path` cannot exceed PATH_MAX.
|
|
/// If `path` ends with `/`, then the returned inode must be a directory inode.
|
|
///
|
|
/// While looking up the dentry, symbolic links will be followed for
|
|
/// at most `SYMLINKS_MAX` times.
|
|
///
|
|
/// If `follow_tail_link` is true and the trailing component is a symlink,
|
|
/// it will be followed.
|
|
/// Symlinks in earlier components of the path will always be followed.
|
|
fn lookup_from_parent(
|
|
&self,
|
|
parent: &Arc<Dentry>,
|
|
relative_path: &str,
|
|
follow_tail_link: bool,
|
|
) -> Result<Arc<Dentry>> {
|
|
debug_assert!(!relative_path.starts_with("/"));
|
|
|
|
if relative_path.len() > PATH_MAX {
|
|
return_errno_with_message!(Errno::ENAMETOOLONG, "path is too long");
|
|
}
|
|
|
|
// To handle symlinks
|
|
let mut link_path = String::new();
|
|
let mut follows = 0;
|
|
|
|
// Initialize the first dentry and the relative path
|
|
let (mut dentry, mut relative_path) = (parent.clone(), relative_path);
|
|
|
|
while !relative_path.is_empty() {
|
|
let (next_name, path_remain, must_be_dir) =
|
|
if let Some((prefix, suffix)) = relative_path.split_once('/') {
|
|
let suffix = suffix.trim_start_matches('/');
|
|
(prefix, suffix, true)
|
|
} else {
|
|
(relative_path, "", false)
|
|
};
|
|
|
|
// Iterate next dentry
|
|
let next_dentry = dentry.lookup(next_name)?;
|
|
let next_type = next_dentry.vnode().inode_type();
|
|
let next_is_tail = path_remain.is_empty();
|
|
|
|
// If next inode is a symlink, follow symlinks at most `SYMLINKS_MAX` times.
|
|
if next_type == InodeType::SymLink && (follow_tail_link || !next_is_tail) {
|
|
if follows >= SYMLINKS_MAX {
|
|
return_errno_with_message!(Errno::ELOOP, "too many symlinks");
|
|
}
|
|
let link_path_remain = {
|
|
let mut tmp_link_path = next_dentry.vnode().read_link()?;
|
|
if tmp_link_path.is_empty() {
|
|
return_errno_with_message!(Errno::ENOENT, "empty symlink");
|
|
}
|
|
if !path_remain.is_empty() {
|
|
tmp_link_path += "/";
|
|
tmp_link_path += path_remain;
|
|
} else if must_be_dir {
|
|
tmp_link_path += "/";
|
|
}
|
|
tmp_link_path
|
|
};
|
|
|
|
// Change the dentry and relative path according to symlink
|
|
if link_path_remain.starts_with("/") {
|
|
dentry = self.root.clone();
|
|
}
|
|
link_path.clear();
|
|
link_path.push_str(&link_path_remain.trim_start_matches('/'));
|
|
relative_path = &link_path;
|
|
follows += 1;
|
|
} else {
|
|
// If path ends with `/`, the inode must be a directory
|
|
if must_be_dir && next_type != InodeType::Dir {
|
|
return_errno_with_message!(Errno::ENOTDIR, "inode is not dir");
|
|
}
|
|
dentry = next_dentry;
|
|
relative_path = path_remain;
|
|
}
|
|
}
|
|
|
|
Ok(dentry)
|
|
}
|
|
|
|
/// Lookup dentry from the giving fd
|
|
pub fn lookup_from_fd(&self, fd: FileDescripter) -> Result<Arc<Dentry>> {
|
|
let current = current!();
|
|
let file_table = current.file_table().lock();
|
|
let inode_handle = file_table
|
|
.get_file(fd)?
|
|
.downcast_ref::<InodeHandle>()
|
|
.ok_or(Error::with_message(Errno::EBADF, "not inode"))?;
|
|
Ok(inode_handle.dentry().clone())
|
|
}
|
|
|
|
/// Lookup the dir dentry and base file name of the giving path.
|
|
///
|
|
/// If the last component is a symlink, do not deference it
|
|
pub fn lookup_dir_and_base_name(&self, path: &FsPath) -> Result<(Arc<Dentry>, String)> {
|
|
self.lookup_dir_and_base_name_inner(path, false)
|
|
}
|
|
|
|
fn lookup_dir_and_base_name_inner(
|
|
&self,
|
|
path: &FsPath,
|
|
follow_tail_link: bool,
|
|
) -> Result<(Arc<Dentry>, String)> {
|
|
let (mut dir_dentry, mut base_name) = match path.inner {
|
|
FsPathInner::Absolute(path) => {
|
|
let (dir, file_name) = split_path(path);
|
|
(
|
|
self.lookup_from_parent(&self.root, dir.trim_start_matches('/'), true)?,
|
|
String::from(file_name),
|
|
)
|
|
}
|
|
FsPathInner::CwdRelative(path) => {
|
|
let (dir, file_name) = split_path(path);
|
|
(
|
|
self.lookup_from_parent(&self.cwd, dir, true)?,
|
|
String::from(file_name),
|
|
)
|
|
}
|
|
FsPathInner::FdRelative(fd, path) => {
|
|
let (dir, file_name) = split_path(path);
|
|
let parent = self.lookup_from_fd(fd)?;
|
|
(
|
|
self.lookup_from_parent(&parent, dir, true)?,
|
|
String::from(file_name),
|
|
)
|
|
}
|
|
_ => return_errno!(Errno::ENOENT),
|
|
};
|
|
if !follow_tail_link {
|
|
return Ok((dir_dentry, base_name));
|
|
}
|
|
|
|
// Dereference the tail symlinks if needed
|
|
loop {
|
|
match dir_dentry.lookup(&base_name.trim_end_matches('/')) {
|
|
Ok(dentry) if dentry.vnode().inode_type() == InodeType::SymLink => {
|
|
let link = {
|
|
let mut link = dentry.vnode().read_link()?;
|
|
if link.is_empty() {
|
|
return_errno_with_message!(Errno::ENOENT, "invalid symlink");
|
|
}
|
|
if base_name.ends_with("/") && !link.ends_with("/") {
|
|
link += "/";
|
|
}
|
|
link
|
|
};
|
|
let (dir, file_name) = split_path(&link);
|
|
if dir.starts_with("/") {
|
|
dir_dentry =
|
|
self.lookup_from_parent(&self.root, dir.trim_start_matches('/'), true)?;
|
|
base_name = String::from(file_name);
|
|
} else {
|
|
dir_dentry = self.lookup_from_parent(&dir_dentry, dir, true)?;
|
|
base_name = String::from(file_name);
|
|
}
|
|
}
|
|
_ => break,
|
|
}
|
|
}
|
|
|
|
Ok((dir_dentry, base_name))
|
|
}
|
|
}
|
|
|
|
pub const AT_FDCWD: FileDescripter = -100;
|
|
|
|
#[derive(Debug)]
|
|
pub struct FsPath<'a> {
|
|
inner: FsPathInner<'a>,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
enum FsPathInner<'a> {
|
|
// absolute path
|
|
Absolute(&'a str),
|
|
// path is relative to Cwd
|
|
CwdRelative(&'a str),
|
|
// Cwd
|
|
Cwd,
|
|
// path is relative to DirFd
|
|
FdRelative(FileDescripter, &'a str),
|
|
// Fd
|
|
Fd(FileDescripter),
|
|
}
|
|
|
|
impl<'a> FsPath<'a> {
|
|
pub fn new(dirfd: FileDescripter, path: &'a str) -> Result<Self> {
|
|
if path.len() > PATH_MAX {
|
|
return_errno_with_message!(Errno::ENAMETOOLONG, "path name too long");
|
|
}
|
|
|
|
let fs_path_inner = if path.starts_with("/") {
|
|
FsPathInner::Absolute(path)
|
|
} else if dirfd >= 0 {
|
|
if path.is_empty() {
|
|
FsPathInner::Fd(dirfd)
|
|
} else {
|
|
FsPathInner::FdRelative(dirfd, path)
|
|
}
|
|
} else if dirfd == AT_FDCWD {
|
|
if path.is_empty() {
|
|
FsPathInner::Cwd
|
|
} else {
|
|
FsPathInner::CwdRelative(path)
|
|
}
|
|
} else {
|
|
return_errno_with_message!(Errno::EBADF, "invalid dirfd number");
|
|
};
|
|
|
|
Ok(Self {
|
|
inner: fs_path_inner,
|
|
})
|
|
}
|
|
}
|
|
|
|
impl<'a> TryFrom<&'a str> for FsPath<'a> {
|
|
type Error = crate::error::Error;
|
|
|
|
fn try_from(path: &'a str) -> Result<FsPath> {
|
|
if path.is_empty() {
|
|
return_errno_with_message!(Errno::ENOENT, "path is an empty string");
|
|
}
|
|
FsPath::new(AT_FDCWD, path)
|
|
}
|
|
}
|
|
|
|
/// Split a `path` to (`dir_path`, `file_name`).
|
|
///
|
|
/// The `dir_path` must be a directory.
|
|
///
|
|
/// The `file_name` is the last component. It can be suffixed by "/".
|
|
///
|
|
/// Example:
|
|
///
|
|
/// The path "/dir/file/" will be split to ("/dir", "file/").
|
|
fn split_path(path: &str) -> (&str, &str) {
|
|
let file_name = path
|
|
.split_inclusive('/')
|
|
.filter(|&x| x != "/")
|
|
.last()
|
|
.unwrap_or(".");
|
|
|
|
let mut split = path.trim_end_matches('/').rsplitn(2, '/');
|
|
let dir_path = if split.next().unwrap().is_empty() {
|
|
"/"
|
|
} else {
|
|
let mut dir = split.next().unwrap_or(".").trim_end_matches('/');
|
|
if dir.is_empty() {
|
|
dir = "/";
|
|
}
|
|
dir
|
|
};
|
|
|
|
(dir_path, file_name)
|
|
}
|