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#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Debug)]
16pub struct FilePath(String);
17
18impl FilePath {
19 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 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 pub fn as_str(&self) -> &str {
41 &self.0
42 }
43
44 pub fn parent(&self) -> AxResult<&str> {
46 if self.is_root() {
47 return Ok("/");
48 }
49
50 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 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 pub fn is_root(&self) -> bool {
82 self.0 == "/"
83 }
84
85 pub fn is_dir(&self) -> bool {
87 self.0.ends_with('/')
88 }
89
90 pub fn is_file(&self) -> bool {
92 !self.is_dir()
93 }
94
95 pub fn exists(&self) -> bool {
97 axfs::api::absolute_path_exists(&self.0)
98 }
99
100 pub fn starts_with(&self, prefix: &FilePath) -> bool {
102 self.0.starts_with(&prefix.0)
103 }
104
105 pub fn ends_with(&self, suffix: &FilePath) -> bool {
107 self.0.ends_with(&suffix.0)
108 }
109
110 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 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#[derive(Debug)]
154pub enum LinkError {
155 LinkExists, InvalidPath, NotFound, NotFile, }
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
178pub static HARDLINK_MANAGER: HardlinkManager = HardlinkManager::new();
180
181pub struct HardlinkManager {
183 inner: RwLock<LinkManagerInner>,
184}
185struct LinkManagerInner {
186 links: BTreeMap<String, String>,
187 ref_counts: BTreeMap<String, usize>,
188}
189
190impl 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 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 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 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 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 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}