arceos_posix_api/imp/
path_link.rsuse alloc::collections::BTreeMap;
use alloc::format;
use core::fmt;
use core::ops::Deref;
use spin::RwLock;
use alloc::string::{String, ToString};
use axerrno::{AxError, AxResult};
use axfs::api::{canonicalize, current_dir};
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Debug)]
pub struct FilePath(String);
impl FilePath {
pub fn new<P: AsRef<str>>(path: P) -> AxResult<Self> {
let path = path.as_ref();
let canonical = canonicalize(path).map_err(|_| AxError::NotFound)?;
let mut new_path = canonical.trim().to_string();
if path.ends_with('/') && !new_path.ends_with('/') {
new_path.push('/');
}
assert!(
new_path.starts_with('/'),
"canonical path should start with /"
);
Ok(Self(HARDLINK_MANAGER.real_path(&new_path)))
}
pub fn as_str(&self) -> &str {
&self.0
}
pub fn parent(&self) -> AxResult<&str> {
if self.is_root() {
return Ok("/");
}
let mut path = self.as_str();
if path.ends_with('/') {
path = path.strip_suffix('/').unwrap();
}
let pos = path.rfind('/').ok_or(AxError::NotFound)?;
Ok(&path[..=pos])
}
pub fn name(&self) -> AxResult<&str> {
if self.is_root() {
return Ok("/");
}
let mut path = self.as_str();
if path.ends_with('/') {
path = path.strip_suffix('/').unwrap();
}
let start_pos = path.rfind('/').ok_or(AxError::NotFound)?;
let end_pos = if path.ends_with('/') {
path.len() - 1
} else {
path.len()
};
Ok(&path[start_pos + 1..end_pos])
}
pub fn is_root(&self) -> bool {
self.0 == "/"
}
pub fn is_dir(&self) -> bool {
self.0.ends_with('/')
}
pub fn is_file(&self) -> bool {
!self.is_dir()
}
pub fn exists(&self) -> bool {
axfs::api::absolute_path_exists(&self.0)
}
pub fn starts_with(&self, prefix: &FilePath) -> bool {
self.0.starts_with(&prefix.0)
}
pub fn ends_with(&self, suffix: &FilePath) -> bool {
self.0.ends_with(&suffix.0)
}
pub fn join<P: AsRef<str>>(&self, path: P) -> AxResult<Self> {
let mut new_path = self.0.clone();
if !new_path.ends_with('/') {
new_path.push('/');
}
new_path.push_str(path.as_ref());
FilePath::new(new_path)
}
pub fn components(&self) -> impl Iterator<Item = &str> {
self.0.trim_matches('/').split('/')
}
}
impl fmt::Display for FilePath {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.0)
}
}
impl AsRef<str> for FilePath {
fn as_ref(&self) -> &str {
&self.0
}
}
impl From<&str> for FilePath {
fn from(s: &str) -> Self {
FilePath::new(s).unwrap()
}
}
impl Deref for FilePath {
type Target = str;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[derive(Debug)]
pub enum LinkError {
LinkExists, InvalidPath, NotFound, NotFile, }
impl From<LinkError> for AxError {
fn from(err: LinkError) -> AxError {
match err {
LinkError::LinkExists => AxError::AlreadyExists,
LinkError::InvalidPath => AxError::InvalidInput,
LinkError::NotFound => AxError::NotFound,
LinkError::NotFile => AxError::InvalidInput,
}
}
}
pub static HARDLINK_MANAGER: HardlinkManager = HardlinkManager::new();
pub struct HardlinkManager {
inner: RwLock<LinkManagerInner>,
}
struct LinkManagerInner {
links: BTreeMap<String, String>,
ref_counts: BTreeMap<String, usize>,
}
impl HardlinkManager {
pub const fn new() -> Self {
Self {
inner: RwLock::new(LinkManagerInner {
links: BTreeMap::new(),
ref_counts: BTreeMap::new(),
}),
}
}
pub fn create_link(&self, src: &FilePath, dst: &FilePath) -> Result<(), LinkError> {
if !dst.exists() {
return Err(LinkError::NotFound);
}
if !dst.is_dir() {
return Err(LinkError::NotFile);
}
let mut inner = self.inner.write();
self.atomic_link_update(&mut inner, src, dst);
Ok(())
}
pub fn remove_link(&self, src: &FilePath) -> Option<String> {
let mut inner = self.inner.write();
self.atomic_link_remove(&mut inner, src).or_else(|| {
axfs::api::remove_file(src.as_str())
.ok()
.map(|_| src.to_string())
})
}
pub fn real_path(&self, path: &str) -> String {
self.inner
.read()
.links
.get(path)
.cloned()
.unwrap_or_else(|| path.to_string())
}
pub fn link_count(&self, path: &FilePath) -> usize {
let inner = self.inner.read();
inner
.ref_counts
.get(path.as_str())
.copied()
.unwrap_or_else(|| if path.exists() { 1 } else { 0 })
}
fn atomic_link_update(&self, inner: &mut LinkManagerInner, src: &FilePath, dst: &FilePath) {
if let Some(old_dst) = inner.links.get(src.as_str()) {
if old_dst == dst.as_str() {
return;
}
self.decrease_ref_count(inner, &old_dst.to_string());
}
inner.links.insert(src.to_string(), dst.to_string());
*inner.ref_counts.entry(dst.to_string()).or_insert(0) += 1;
}
fn atomic_link_remove(&self, inner: &mut LinkManagerInner, src: &FilePath) -> Option<String> {
inner.links.remove(src.as_str()).inspect(|dst| {
self.decrease_ref_count(inner, dst);
})
}
fn decrease_ref_count(&self, inner: &mut LinkManagerInner, path: &str) -> Option<()> {
match inner.ref_counts.get_mut(path) {
Some(count) => {
*count -= 1;
if *count == 0 {
inner.ref_counts.remove(path);
axfs::api::remove_file(path).ok()?
}
Some(())
}
None => {
axlog::error!("link exists but ref count is zero");
None
}
}
}
}
pub const AT_FDCWD: isize = -100;
pub fn handle_file_path(
dir_fd: isize,
path_addr: Option<*const u8>,
force_dir: bool,
) -> AxResult<FilePath> {
let path = match path_addr {
Some(addr) => {
if addr.is_null() {
axlog::warn!("路径地址为空");
return Err(AxError::BadAddress);
}
crate::utils::char_ptr_to_str(addr as *const _)
.map_err(|_| AxError::NotFound)?
.to_string()
}
None => String::new(),
};
let mut path = if path.is_empty() {
handle_empty_path(dir_fd)?
} else {
path
};
if !path.starts_with('/') {
if dir_fd == AT_FDCWD {
path = prepend_cwd(&path)?;
} else {
path = handle_relative_path(dir_fd, &path)?;
}
}
path = adjust_path_suffix(path, force_dir);
FilePath::new(&path)
}
fn handle_empty_path(dir_fd: isize) -> AxResult<String> {
const AT_FDCWD: isize = -100;
if dir_fd == AT_FDCWD {
return Ok(String::from("."));
}
super::fs::Directory::from_fd(dir_fd as i32)
.map(|dir| dir.path().to_string())
.map_err(|_| AxError::NotFound)
}
fn handle_relative_path(dir_fd: isize, path: &str) -> AxResult<String> {
match super::fs::Directory::from_fd(dir_fd as i32) {
Ok(dir) => {
let combined_path = format!("{}{}", dir.path(), path);
axlog::info!("处理后的路径: {} (目录: {})", combined_path, dir.path());
Ok(combined_path)
}
Err(_) => {
axlog::warn!("文件描述符不存在");
Err(AxError::NotFound)
}
}
}
fn prepend_cwd(path: &str) -> AxResult<String> {
let cwd = current_dir().map_err(|_| AxError::NotFound)?;
debug_assert!(cwd.ends_with('/'), "当前工作目录路径应以 '/' 结尾");
Ok(format!("{}{}", cwd, path))
}
fn adjust_path_suffix(mut path: String, force_dir: bool) -> String {
if force_dir && !path.ends_with('/') {
path.push('/');
}
if path.ends_with('.') {
path.push('/');
}
path
}