axfs/
root.rs

1//! Root directory of the filesystem
2//!
3//! TODO: it doesn't work very well if the mount points have containment relationships.
4
5use alloc::{string::String, sync::Arc, vec::Vec};
6use axerrno::{AxError, AxResult, ax_err};
7use axfs_vfs::{VfsNodeAttr, VfsNodeOps, VfsNodeRef, VfsNodeType, VfsOps, VfsResult};
8use axns::{ResArc, def_resource};
9use axsync::Mutex;
10use lazyinit::LazyInit;
11use spin::RwLock;
12
13use crate::{
14    api::FileType,
15    fs::{self},
16    mounts,
17};
18
19def_resource! {
20    pub static CURRENT_DIR_PATH: ResArc<Mutex<String>> = ResArc::new();
21    pub static CURRENT_DIR: ResArc<Mutex<VfsNodeRef>> = ResArc::new();
22}
23
24impl CURRENT_DIR_PATH {
25    /// Return a copy of the inner path.
26    pub fn copy_inner(&self) -> Mutex<String> {
27        Mutex::new(self.lock().clone())
28    }
29}
30
31impl CURRENT_DIR {
32    /// Return a copy of the CURRENT_DIR_NODE.
33    pub fn copy_inner(&self) -> Mutex<VfsNodeRef> {
34        Mutex::new(self.lock().clone())
35    }
36}
37
38struct MountPoint {
39    path: &'static str,
40    fs: Arc<dyn VfsOps>,
41}
42
43struct RootDirectory {
44    main_fs: Arc<dyn VfsOps>,
45    mounts: RwLock<Vec<MountPoint>>,
46}
47
48static ROOT_DIR: LazyInit<Arc<RootDirectory>> = LazyInit::new();
49
50impl MountPoint {
51    pub fn new(path: &'static str, fs: Arc<dyn VfsOps>) -> Self {
52        Self { path, fs }
53    }
54}
55
56impl Drop for MountPoint {
57    fn drop(&mut self) {
58        self.fs.umount().ok();
59    }
60}
61
62impl RootDirectory {
63    pub const fn new(main_fs: Arc<dyn VfsOps>) -> Self {
64        Self {
65            main_fs,
66            mounts: RwLock::new(Vec::new()),
67        }
68    }
69
70    pub fn mount(&self, path: &'static str, fs: Arc<dyn VfsOps>) -> AxResult {
71        if path == "/" {
72            return ax_err!(InvalidInput, "cannot mount root filesystem");
73        }
74        if !path.starts_with('/') {
75            return ax_err!(InvalidInput, "mount path must start with '/'");
76        }
77        if self.mounts.read().iter().any(|mp| mp.path == path) {
78            return ax_err!(InvalidInput, "mount point already exists");
79        }
80        // create the mount point in the main filesystem if it does not exist
81        self.main_fs.root_dir().create(path, FileType::Dir)?;
82        fs.mount(path, self.main_fs.root_dir().lookup(path)?)?;
83        self.mounts.write().push(MountPoint::new(path, fs));
84        Ok(())
85    }
86
87    pub fn _umount(&self, path: &str) {
88        self.mounts.write().retain(|mp| mp.path != path);
89    }
90
91    pub fn contains(&self, path: &str) -> bool {
92        self.mounts.read().iter().any(|mp| mp.path == path)
93    }
94
95    fn lookup_mounted_fs<F, T>(&self, path: &str, f: F) -> AxResult<T>
96    where
97        F: FnOnce(Arc<dyn VfsOps>, &str) -> AxResult<T>,
98    {
99        debug!("lookup at root: {}", path);
100        let path = path.trim_matches('/');
101        if let Some(rest) = path.strip_prefix("./") {
102            return self.lookup_mounted_fs(rest, f);
103        }
104
105        let mut idx = 0;
106        let mut max_len = 0;
107
108        // Find the filesystem that has the longest mounted path match
109        // TODO: more efficient, e.g. trie
110        for (i, mp) in self.mounts.read().iter().enumerate() {
111            // skip the first '/'
112            if path.starts_with(&mp.path[1..]) && mp.path.len() - 1 > max_len {
113                max_len = mp.path.len() - 1;
114                idx = i;
115            }
116        }
117
118        if max_len == 0 {
119            f(self.main_fs.clone(), path) // not matched any mount point
120        } else {
121            f(self.mounts.read()[idx].fs.clone(), &path[max_len..]) // matched at `idx`
122        }
123    }
124}
125
126impl VfsNodeOps for RootDirectory {
127    axfs_vfs::impl_vfs_dir_default! {}
128
129    fn get_attr(&self) -> VfsResult<VfsNodeAttr> {
130        self.main_fs.root_dir().get_attr()
131    }
132
133    fn lookup(self: Arc<Self>, path: &str) -> VfsResult<VfsNodeRef> {
134        self.lookup_mounted_fs(path, |fs, rest_path| fs.root_dir().lookup(rest_path))
135    }
136
137    fn create(&self, path: &str, ty: VfsNodeType) -> VfsResult {
138        self.lookup_mounted_fs(path, |fs, rest_path| {
139            if rest_path.is_empty() {
140                Ok(()) // already exists
141            } else {
142                fs.root_dir().create(rest_path, ty)
143            }
144        })
145    }
146
147    fn remove(&self, path: &str) -> VfsResult {
148        self.lookup_mounted_fs(path, |fs, rest_path| {
149            if rest_path.is_empty() {
150                ax_err!(PermissionDenied) // cannot remove mount points
151            } else {
152                fs.root_dir().remove(rest_path)
153            }
154        })
155    }
156
157    fn rename(&self, src_path: &str, dst_path: &str) -> VfsResult {
158        self.lookup_mounted_fs(src_path, |fs, rest_path| {
159            if rest_path.is_empty() {
160                ax_err!(PermissionDenied) // cannot rename mount points
161            } else {
162                fs.root_dir().rename(rest_path, dst_path)
163            }
164        })
165    }
166}
167
168pub(crate) fn init_rootfs(disk: crate::dev::Disk) {
169    cfg_if::cfg_if! {
170        if #[cfg(feature = "myfs")] { // override the default filesystem
171            let main_fs = fs::myfs::new_myfs(disk);
172        } else if #[cfg(feature = "lwext4_rs")] {
173            static EXT4_FS: LazyInit<Arc<fs::lwext4_rust::Ext4FileSystem>> = LazyInit::new();
174            EXT4_FS.init_once(Arc::new(fs::lwext4_rust::Ext4FileSystem::new(disk)));
175            let main_fs = EXT4_FS.clone();
176        } else if #[cfg(feature = "fatfs")] {
177            static FAT_FS: LazyInit<Arc<fs::fatfs::FatFileSystem>> = LazyInit::new();
178            FAT_FS.init_once(Arc::new(fs::fatfs::FatFileSystem::new(disk)));
179            FAT_FS.init();
180            let main_fs = FAT_FS.clone();
181        }
182    }
183
184    let root_dir = RootDirectory::new(main_fs);
185
186    #[cfg(feature = "devfs")]
187    root_dir
188        .mount("/dev", mounts::devfs())
189        .expect("failed to mount devfs at /dev");
190
191    #[cfg(feature = "ramfs")]
192    root_dir
193        .mount("/tmp", mounts::ramfs())
194        .expect("failed to mount ramfs at /tmp");
195
196    // Mount another ramfs as procfs
197    #[cfg(feature = "procfs")]
198    root_dir // should not fail
199        .mount("/proc", mounts::procfs().unwrap())
200        .expect("fail to mount procfs at /proc");
201
202    // Mount another ramfs as sysfs
203    #[cfg(feature = "sysfs")]
204    root_dir // should not fail
205        .mount("/sys", mounts::sysfs().unwrap())
206        .expect("fail to mount sysfs at /sys");
207
208    ROOT_DIR.init_once(Arc::new(root_dir));
209    info!("rootfs initialized");
210    CURRENT_DIR.init_new(Mutex::new(ROOT_DIR.clone()));
211    info!("test");
212    CURRENT_DIR_PATH.init_new(Mutex::new("/".into()));
213}
214
215fn parent_node_of(dir: Option<&VfsNodeRef>, path: &str) -> VfsNodeRef {
216    if path.starts_with('/') {
217        ROOT_DIR.clone()
218    } else {
219        dir.cloned().unwrap_or_else(|| CURRENT_DIR.lock().clone())
220    }
221}
222
223pub(crate) fn absolute_path(path: &str) -> AxResult<String> {
224    if path.starts_with('/') {
225        Ok(axfs_vfs::path::canonicalize(path))
226    } else {
227        let path = CURRENT_DIR_PATH.lock().clone() + path;
228        Ok(axfs_vfs::path::canonicalize(&path))
229    }
230}
231
232pub(crate) fn lookup(dir: Option<&VfsNodeRef>, path: &str) -> AxResult<VfsNodeRef> {
233    if path.is_empty() {
234        return ax_err!(NotFound);
235    }
236    let node = parent_node_of(dir, path).lookup(path)?;
237    if path.ends_with('/') && !node.get_attr()?.is_dir() {
238        ax_err!(NotADirectory)
239    } else {
240        Ok(node)
241    }
242}
243
244pub(crate) fn create_file(dir: Option<&VfsNodeRef>, path: &str) -> AxResult<VfsNodeRef> {
245    if path.is_empty() {
246        return ax_err!(NotFound);
247    } else if path.ends_with('/') {
248        return ax_err!(NotADirectory);
249    }
250    let parent = parent_node_of(dir, path);
251    parent.create(path, VfsNodeType::File)?;
252    parent.lookup(path)
253}
254
255pub(crate) fn create_dir(dir: Option<&VfsNodeRef>, path: &str) -> AxResult {
256    match lookup(dir, path) {
257        Ok(_) => ax_err!(AlreadyExists),
258        Err(AxError::NotFound) => parent_node_of(dir, path).create(path, VfsNodeType::Dir),
259        Err(e) => Err(e),
260    }
261}
262
263pub(crate) fn remove_file(dir: Option<&VfsNodeRef>, path: &str) -> AxResult {
264    let node = lookup(dir, path)?;
265    let attr = node.get_attr()?;
266    if attr.is_dir() {
267        ax_err!(IsADirectory)
268    } else if !attr.perm().owner_writable() {
269        ax_err!(PermissionDenied)
270    } else {
271        parent_node_of(dir, path).remove(path)
272    }
273}
274
275pub(crate) fn remove_dir(dir: Option<&VfsNodeRef>, path: &str) -> AxResult {
276    if path.is_empty() {
277        return ax_err!(NotFound);
278    }
279    let path_check = path.trim_matches('/');
280    if path_check.is_empty() {
281        return ax_err!(DirectoryNotEmpty); // rm -d '/'
282    } else if path_check == "."
283        || path_check == ".."
284        || path_check.ends_with("/.")
285        || path_check.ends_with("/..")
286    {
287        return ax_err!(InvalidInput);
288    }
289    if ROOT_DIR.contains(&absolute_path(path)?) {
290        return ax_err!(PermissionDenied);
291    }
292
293    let node = lookup(dir, path)?;
294    let attr = node.get_attr()?;
295    if !attr.is_dir() {
296        ax_err!(NotADirectory)
297    } else if !attr.perm().owner_writable() {
298        ax_err!(PermissionDenied)
299    } else {
300        parent_node_of(dir, path).remove(path)
301    }
302}
303
304pub(crate) fn current_dir() -> AxResult<String> {
305    Ok(CURRENT_DIR_PATH.lock().clone())
306}
307
308pub(crate) fn set_current_dir(path: &str) -> AxResult {
309    let mut abs_path = absolute_path(path)?;
310    if !abs_path.ends_with('/') {
311        abs_path += "/";
312    }
313    if abs_path == "/" {
314        *CURRENT_DIR.lock() = ROOT_DIR.clone();
315        *CURRENT_DIR_PATH.lock() = "/".into();
316        return Ok(());
317    }
318
319    let node = lookup(None, &abs_path)?;
320    let attr = node.get_attr()?;
321    if !attr.is_dir() {
322        ax_err!(NotADirectory)
323    } else if !attr.perm().owner_executable() {
324        ax_err!(PermissionDenied)
325    } else {
326        *CURRENT_DIR.lock() = node;
327        *CURRENT_DIR_PATH.lock() = abs_path;
328        Ok(())
329    }
330}
331
332pub(crate) fn rename(old: &str, new: &str) -> AxResult {
333    if parent_node_of(None, new).lookup(new).is_ok() {
334        warn!("dst file already exist, now remove it");
335        remove_file(None, new)?;
336    }
337    parent_node_of(None, old).rename(old, new)
338}