starry_api/
path.rs

1use core::{ffi::c_int, fmt, ops::Deref};
2
3use alloc::{
4    collections::btree_map::BTreeMap,
5    string::{String, ToString},
6};
7use axerrno::{AxError, AxResult, LinuxError, LinuxResult};
8use axfs::api::canonicalize;
9use linux_raw_sys::general::AT_FDCWD;
10use spin::RwLock;
11
12use crate::file::{Directory, File, FileLike};
13
14/// 一个规范化的文件路径表示
15#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Debug)]
16pub struct FilePath(String);
17
18impl FilePath {
19    /// 从路径字符串创建一个新的 `FilePath`,路径将被规范化。
20    /// 输入路径可以是绝对路径或相对路径。
21    pub fn new<P: AsRef<str>>(path: P) -> AxResult<Self> {
22        let path = path.as_ref();
23        let canonical = canonicalize(path).map_err(|_| AxError::NotFound)?;
24        let mut new_path = canonical.trim().to_string();
25
26        // 如果原始路径以 '/' 结尾,那么规范化后的路径也应以 '/' 结尾
27        if path.ends_with('/') && !new_path.ends_with('/') {
28            new_path.push('/');
29        }
30
31        assert!(
32            new_path.starts_with('/'),
33            "canonical path should start with /"
34        );
35
36        Ok(Self(HARDLINK_MANAGER.real_path(&new_path)))
37    }
38
39    /// 返回底层路径的字符串切片
40    pub fn as_str(&self) -> &str {
41        &self.0
42    }
43
44    /// 返回父目录路径
45    pub fn parent(&self) -> AxResult<&str> {
46        if self.is_root() {
47            return Ok("/");
48        }
49
50        // 查找最后一个斜杠,考虑可能的尾部斜杠
51        let mut path = self.as_str();
52        if path.ends_with('/') {
53            path = path.strip_suffix('/').unwrap();
54        }
55        let pos = path.rfind('/').ok_or(AxError::NotFound)?;
56
57        Ok(&path[..=pos])
58    }
59
60    /// 返回文件名或目录名组件
61    pub fn name(&self) -> AxResult<&str> {
62        if self.is_root() {
63            return Ok("/");
64        }
65
66        let mut path = self.as_str();
67        if path.ends_with('/') {
68            path = path.strip_suffix('/').unwrap();
69        }
70        let start_pos = path.rfind('/').ok_or(AxError::NotFound)?;
71
72        let end_pos = if path.ends_with('/') {
73            path.len() - 1
74        } else {
75            path.len()
76        };
77        Ok(&path[start_pos + 1..end_pos])
78    }
79
80    /// 判断是否为根目录
81    pub fn is_root(&self) -> bool {
82        self.0 == "/"
83    }
84
85    /// 判断是否为目录(以 '/' 结尾)
86    pub fn is_dir(&self) -> bool {
87        self.0.ends_with('/')
88    }
89
90    /// 判断是否为常规文件(不以 '/' 结尾)
91    pub fn is_file(&self) -> bool {
92        !self.is_dir()
93    }
94
95    /// Whether the path exists
96    pub fn exists(&self) -> bool {
97        axfs::api::absolute_path_exists(&self.0)
98    }
99
100    /// 判断此路径是否以给定前缀路径开头
101    pub fn starts_with(&self, prefix: &FilePath) -> bool {
102        self.0.starts_with(&prefix.0)
103    }
104
105    /// 判断此路径是否以给定后缀路径结尾
106    pub fn ends_with(&self, suffix: &FilePath) -> bool {
107        self.0.ends_with(&suffix.0)
108    }
109
110    /// 将此路径与相对路径组件连接
111    pub fn join<P: AsRef<str>>(&self, path: P) -> AxResult<Self> {
112        let mut new_path = self.0.clone();
113        if !new_path.ends_with('/') {
114            new_path.push('/');
115        }
116        new_path.push_str(path.as_ref());
117        FilePath::new(new_path)
118    }
119
120    /// 返回此路径组件的迭代器
121    pub fn components(&self) -> impl Iterator<Item = &str> {
122        self.0.trim_matches('/').split('/')
123    }
124}
125
126impl fmt::Display for FilePath {
127    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
128        f.write_str(&self.0)
129    }
130}
131
132impl AsRef<str> for FilePath {
133    fn as_ref(&self) -> &str {
134        &self.0
135    }
136}
137
138impl From<&str> for FilePath {
139    fn from(s: &str) -> Self {
140        FilePath::new(s).unwrap()
141    }
142}
143
144impl Deref for FilePath {
145    type Target = str;
146
147    fn deref(&self) -> &Self::Target {
148        &self.0
149    }
150}
151
152/// 错误类型
153#[derive(Debug)]
154pub enum LinkError {
155    LinkExists,  // 链接已存在
156    InvalidPath, // 无效路径
157    NotFound,    // 文件不存在
158    NotFile,     // 不是文件
159}
160
161impl From<LinkError> for AxError {
162    fn from(err: LinkError) -> AxError {
163        match err {
164            LinkError::LinkExists => AxError::AlreadyExists,
165            LinkError::InvalidPath => AxError::InvalidInput,
166            LinkError::NotFound => AxError::NotFound,
167            LinkError::NotFile => AxError::InvalidInput,
168        }
169    }
170}
171
172impl From<LinkError> for LinuxError {
173    fn from(err: LinkError) -> LinuxError {
174        AxError::from(err).into()
175    }
176}
177
178/// A global hardlink manager
179pub static HARDLINK_MANAGER: HardlinkManager = HardlinkManager::new();
180
181/// A manager for hardlinks
182pub struct HardlinkManager {
183    inner: RwLock<LinkManagerInner>,
184}
185struct LinkManagerInner {
186    links: BTreeMap<String, String>,
187    ref_counts: BTreeMap<String, usize>,
188}
189
190// 关于innner的操作都在atomic_开头的函数中
191impl HardlinkManager {
192    const fn new() -> Self {
193        Self {
194            inner: RwLock::new(LinkManagerInner {
195                links: BTreeMap::new(),
196                ref_counts: BTreeMap::new(),
197            }),
198        }
199    }
200
201    /// 创建链接
202    /// 如果目标路径不存在,则返回 `LinkError::NotFound`
203    /// 如果目标路径不是文件,则返回 `LinkError::NotFile`
204    pub fn create_link(&self, src: &FilePath, dst: &FilePath) -> Result<(), LinkError> {
205        if !dst.exists() {
206            return Err(LinkError::NotFound);
207        }
208        if !dst.is_dir() {
209            return Err(LinkError::NotFile);
210        }
211
212        let mut inner = self.inner.write();
213        self.atomic_link_update(&mut inner, src, dst);
214        Ok(())
215    }
216
217    /// 移除链接
218    /// 链接数量为零 或 没有链接时, 删除文件
219    /// 如果路径对应的链接不存在 或 路径对应的文件不存在,则返回 `None`
220    /// 否则返回链接的目标路径
221    pub fn remove_link(&self, src: &FilePath) -> Option<String> {
222        let mut inner = self.inner.write();
223        self.atomic_link_remove(&mut inner, src).or_else(|| {
224            axfs::api::remove_file(src.as_str())
225                .ok()
226                .map(|_| src.to_string())
227        })
228    }
229
230    pub fn real_path(&self, path: &str) -> String {
231        self.inner
232            .read()
233            .links
234            .get(path)
235            .cloned()
236            .unwrap_or_else(|| path.to_string())
237    }
238
239    pub fn link_count(&self, path: &FilePath) -> usize {
240        let inner = self.inner.read();
241        inner
242            .ref_counts
243            .get(path.as_str())
244            .copied()
245            .unwrap_or_else(|| if path.exists() { 1 } else { 0 })
246    }
247
248    // 原子操作helpers
249
250    /// 创建或更新链接
251    /// 如果链接已存在,则更新目标路径
252    /// 如果目标路径不存在,则返回 `LinkError::NotFound`
253    fn atomic_link_update(&self, inner: &mut LinkManagerInner, src: &FilePath, dst: &FilePath) {
254        if let Some(old_dst) = inner.links.get(src.as_str()) {
255            if old_dst == dst.as_str() {
256                return;
257            }
258            self.decrease_ref_count(inner, &old_dst.to_string());
259        }
260        inner.links.insert(src.to_string(), dst.to_string());
261        *inner.ref_counts.entry(dst.to_string()).or_insert(0) += 1;
262    }
263
264    /// 移除链接
265    /// 如果链接不存在,则返回 `None`,否则返回链接的目标路径
266    fn atomic_link_remove(&self, inner: &mut LinkManagerInner, src: &FilePath) -> Option<String> {
267        inner.links.remove(src.as_str()).inspect(|dst| {
268            self.decrease_ref_count(inner, dst);
269        })
270    }
271
272    /// 减少引用计数
273    /// 如果引用计数为零,则删除链接,并删除文件,如果删除文件失败,则返回 `None`
274    /// 如果链接不存在,则返回 `None`
275    fn decrease_ref_count(&self, inner: &mut LinkManagerInner, path: &str) -> Option<()> {
276        match inner.ref_counts.get_mut(path) {
277            Some(count) => {
278                *count -= 1;
279                if *count == 0 {
280                    inner.ref_counts.remove(path);
281                    axfs::api::remove_file(path).ok()?
282                }
283                Some(())
284            }
285            None => {
286                axlog::error!("link exists but ref count is zero");
287                None
288            }
289        }
290    }
291}
292
293pub fn handle_file_path(dirfd: c_int, path: &str) -> LinuxResult<FilePath> {
294    if path.starts_with('/') {
295        Ok(FilePath::new(path)?)
296    } else if path.is_empty() {
297        Ok(FilePath::new(File::from_fd(dirfd)?.path())?)
298    } else {
299        let base = if dirfd == AT_FDCWD {
300            FilePath::new("")?
301        } else {
302            FilePath::new(Directory::from_fd(dirfd)?.path())?
303        };
304        Ok(base.join(path)?)
305    }
306}