Skip to content

Commit d658428

Browse files
committed
implement unmount
1 parent a466783 commit d658428

7 files changed

Lines changed: 214 additions & 12 deletions

File tree

etc/syscalls_linux_aarch64.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
| 0x24 (36) | symlinkat | (const char *oldname, int newdfd, const char *newname) | __arm64_sys_symlinkat | true |
4141
| 0x25 (37) | linkat | (int olddfd, const char *oldname, int newdfd, const char *newname, int flags) | __arm64_sys_linkat | true |
4242
| 0x26 (38) | renameat | (int olddfd, const char *oldname, int newdfd, const char *newname) | __arm64_sys_renameat | true |
43-
| 0x27 (39) | umount | (char *name, int flags) | __arm64_sys_umount | false |
43+
| 0x27 (39) | umount | (char *name, int flags) | __arm64_sys_umount | true |
4444
| 0x28 (40) | mount | (char *dev_name, char *dir_name, char *type, unsigned long flags, void *data) | __arm64_sys_mount | partial |
4545
| 0x29 (41) | pivot_root | (const char *new_root, const char *put_old) | __arm64_sys_pivot_root | partial |
4646
| 0x2b (43) | statfs | (const char *pathname, struct statfs *buf) | __arm64_sys_statfs | partial |

src/arch/arm64/exceptions/syscall.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ use crate::{
4646
statfs::{sys_fstatfs, sys_statfs},
4747
sync::{sys_fdatasync, sys_fsync, sys_sync, sys_syncfs},
4848
trunc::{sys_ftruncate, sys_truncate},
49+
umount::sys_umount2,
4950
},
5051
},
5152
kernel::{
@@ -285,6 +286,7 @@ pub async fn handle_syscall(mut ctx: ProcessCtx) {
285286
)
286287
.await
287288
}
289+
0x27 => sys_umount2(&ctx, TUA::from_value(arg1 as _), arg2 as _).await,
288290
0x28 => {
289291
sys_mount(
290292
&ctx,

src/fs/mod.rs

Lines changed: 95 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use crate::clock::realtime::date;
22
use crate::{
33
drivers::{DM, Driver, block::get_block_device_by_descriptor},
4-
process::Task,
4+
process::{TASK_LIST, Task},
55
sync::SpinLock,
66
};
77
use alloc::{borrow::ToOwned, boxed::Box, collections::btree_map::BTreeMap, sync::Arc, vec::Vec};
@@ -45,6 +45,7 @@ impl Inode for DummyInode {
4545
}
4646

4747
/// Represents a mounted filesystem.
48+
#[derive(Clone)]
4849
struct Mount {
4950
fs: Arc<dyn Filesystem>,
5051
root_inode: Arc<dyn Inode>,
@@ -88,10 +89,40 @@ impl VfsState {
8889
}
8990

9091
/// Removes a mount point by its inode ID.
91-
fn remove_mount(&mut self, mount_point_id: &InodeId) -> Option<()> {
92+
fn remove_mount(&mut self, mount_point_id: &InodeId) -> Option<Mount> {
9293
let mount = self.mounts.remove(mount_point_id)?;
9394
self.filesystems.remove(&mount.fs.id())?;
94-
Some(())
95+
Some(mount)
96+
}
97+
98+
/// Collects the mount identified by `mount_point_id` and every nested mount
99+
/// reachable beneath it.
100+
fn collect_mount_subtree(&self, mount_point_id: InodeId) -> Option<Vec<(InodeId, Mount)>> {
101+
let mut pending = Vec::new();
102+
let mut subtree = Vec::new();
103+
104+
pending.push(mount_point_id);
105+
106+
while let Some(current_mount_point_id) = pending.pop() {
107+
let mount = self.mounts.get(&current_mount_point_id)?.clone();
108+
let current_fs_id = mount.fs.id();
109+
110+
subtree.push((current_mount_point_id, mount));
111+
112+
for child_mount_point_id in self.mounts.keys().copied() {
113+
if child_mount_point_id != current_mount_point_id
114+
&& child_mount_point_id.fs_id() == current_fs_id
115+
&& !subtree
116+
.iter()
117+
.any(|(seen, _)| *seen == child_mount_point_id)
118+
&& !pending.contains(&child_mount_point_id)
119+
{
120+
pending.push(child_mount_point_id);
121+
}
122+
}
123+
}
124+
125+
Some(subtree)
95126
}
96127

97128
/// Checks if an inode is a mount point and returns the root inode of the
@@ -192,15 +223,68 @@ impl VFS {
192223
Ok(())
193224
}
194225

195-
#[expect(unused)]
196-
pub async fn umount(&self, mount_point: Arc<dyn Inode>) -> Result<()> {
197-
let mount_point_id = mount_point.id();
226+
pub async fn umount(&self, mount_point: Arc<dyn Inode>, detach: bool) -> Result<()> {
227+
let mount_point_id = self
228+
.mount_point_for_root(mount_point.id())
229+
.unwrap_or(mount_point.id());
198230

199-
// Lock the state and remove the mount.
200-
self.state
201-
.lock_save_irq()
202-
.remove_mount(&mount_point_id)
203-
.ok_or(FsError::NotFound)?;
231+
if mount_point_id
232+
== self
233+
.root_inode
234+
.lock_save_irq()
235+
.as_ref()
236+
.ok_or(FsError::NotFound)?
237+
.id()
238+
{
239+
return Err(KernelError::InUse);
240+
}
241+
242+
let subtree = {
243+
let state = self.state.lock_save_irq();
244+
state
245+
.collect_mount_subtree(mount_point_id)
246+
.ok_or(KernelError::InvalidValue)?
247+
};
248+
249+
if !detach && subtree.len() > 1 {
250+
return Err(KernelError::InUse);
251+
}
252+
253+
let target_fs_id = subtree
254+
.first()
255+
.map(|(_, mount)| mount.fs.id())
256+
.ok_or(KernelError::InvalidValue)?;
257+
258+
if !detach {
259+
let tasks: Vec<_> = TASK_LIST
260+
.lock_save_irq()
261+
.values()
262+
.filter_map(|work| work.upgrade())
263+
.collect();
264+
265+
for work in tasks {
266+
let task = work.task.t_shared.clone();
267+
268+
if task.root.lock_save_irq().0.id().fs_id() == target_fs_id
269+
|| task.cwd.lock_save_irq().0.id().fs_id() == target_fs_id
270+
|| task.fd_table.lock_save_irq().any_inode_on_fs(target_fs_id)
271+
{
272+
return Err(KernelError::InUse);
273+
}
274+
}
275+
}
276+
277+
let filesystems: Vec<_> = subtree.iter().map(|(_, mount)| mount.fs.clone()).collect();
278+
for fs in filesystems {
279+
fs.sync().await?;
280+
}
281+
282+
let mut state = self.state.lock_save_irq();
283+
for (mount_point_id, _) in subtree {
284+
state
285+
.remove_mount(&mount_point_id)
286+
.ok_or(KernelError::InvalidValue)?;
287+
}
204288

205289
Ok(())
206290
}

src/fs/syscalls/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,4 @@ pub mod stat;
2020
pub mod statfs;
2121
pub mod sync;
2222
pub mod trunc;
23+
pub mod umount;

src/fs/syscalls/umount.rs

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
use crate::{fs::VFS, memory::uaccess::cstr::UserCStr, sched::syscall_ctx::ProcessCtx};
2+
use bitflags::bitflags;
3+
use core::ffi::c_char;
4+
use libkernel::{
5+
error::{FsError, KernelError, Result},
6+
fs::path::Path,
7+
memory::address::TUA,
8+
proc::caps::CapabilitiesFlags,
9+
};
10+
11+
bitflags! {
12+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13+
struct UmountFlags: u32 {
14+
const MNT_FORCE = 0x1;
15+
const MNT_DETACH = 0x2;
16+
const MNT_EXPIRE = 0x4;
17+
const UMOUNT_NOFOLLOW = 0x8;
18+
}
19+
}
20+
21+
pub async fn sys_umount2(ctx: &ProcessCtx, target: TUA<c_char>, flags: i64) -> Result<usize> {
22+
let flags = u32::try_from(flags)
23+
.ok()
24+
.and_then(UmountFlags::from_bits)
25+
.ok_or(KernelError::InvalidValue)?;
26+
27+
if flags.contains(UmountFlags::MNT_EXPIRE)
28+
&& flags.intersects(UmountFlags::MNT_FORCE | UmountFlags::MNT_DETACH)
29+
{
30+
return Err(KernelError::InvalidValue);
31+
}
32+
33+
if flags.contains(UmountFlags::MNT_EXPIRE) {
34+
// TODO: Implement two-phase expiry semantics.
35+
return Err(KernelError::TryAgain);
36+
}
37+
38+
let task = ctx.shared().clone();
39+
task.creds
40+
.lock_save_irq()
41+
.caps()
42+
.check_capable(CapabilitiesFlags::CAP_SYS_ADMIN)?;
43+
44+
let mut buf = [0u8; 1024];
45+
let target = UserCStr::from_ptr(target).copy_from_user(&mut buf).await?;
46+
if target.is_empty() {
47+
return Err(FsError::NotFound.into());
48+
}
49+
50+
let cwd = task.cwd.lock_save_irq().0.clone();
51+
let target_path = Path::new(target);
52+
let target = if flags.contains(UmountFlags::UMOUNT_NOFOLLOW) {
53+
VFS.resolve_path_nofollow(target_path, cwd, &task).await?
54+
} else {
55+
VFS.resolve_path(target_path, cwd, &task).await?
56+
};
57+
58+
VFS.umount(target, flags.contains(UmountFlags::MNT_DETACH))
59+
.await?;
60+
61+
Ok(0)
62+
}

src/process/fd_table.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,17 @@ impl FileDescriptorTable {
201201
}
202202
}
203203

204+
/// Returns `true` if any open file descriptor refers to an inode on the
205+
/// given filesystem.
206+
pub fn any_inode_on_fs(&self, fs_id: u64) -> bool {
207+
self.entries.iter().flatten().any(|entry| {
208+
entry
209+
.file
210+
.inode()
211+
.is_some_and(|inode| inode.id().fs_id() == fs_id)
212+
})
213+
}
214+
204215
/// Number of file descriptors in use.
205216
pub fn len(&self) -> usize {
206217
self.entries.iter().filter(|e| e.is_some()).count()

usertest/src/fs.rs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,48 @@ fn test_chroot() {
109109

110110
register_test!(test_chroot);
111111

112+
fn test_mount_umount() {
113+
use std::path::Path;
114+
115+
let pid = unsafe { libc::getpid() };
116+
let mount_point = format!("/tmp/mount_umount_test_{pid}");
117+
let test_file = format!("{mount_point}/hello.txt");
118+
let c_mount_point = CString::new(mount_point.clone()).unwrap();
119+
let c_source = CString::new("tmpfs").unwrap();
120+
let c_type = CString::new("tmpfs").unwrap();
121+
122+
fs::create_dir(&mount_point).expect("Failed to create mount point");
123+
124+
unsafe {
125+
if libc::mount(
126+
c_source.as_ptr(),
127+
c_mount_point.as_ptr(),
128+
c_type.as_ptr(),
129+
0,
130+
std::ptr::null(),
131+
) != 0
132+
{
133+
panic!("mount failed: {}", std::io::Error::last_os_error());
134+
}
135+
}
136+
137+
fs::write(&test_file, b"hello").expect("Failed to write file on mounted fs");
138+
assert!(Path::new(&test_file).exists());
139+
140+
unsafe {
141+
if libc::umount(c_mount_point.as_ptr()) != 0 {
142+
panic!("umount failed: {}", std::io::Error::last_os_error());
143+
}
144+
}
145+
146+
assert!(!Path::new(&test_file).exists());
147+
assert_eq!(fs::read_dir(&mount_point).unwrap().count(), 0);
148+
149+
fs::remove_dir(&mount_point).expect("Failed to remove mount point");
150+
}
151+
152+
register_test!(test_mount_umount);
153+
112154
fn test_chmod() {
113155
let dir_path = "/tmp/chmod_test";
114156
let c_dir_path = CString::new(dir_path).unwrap();

0 commit comments

Comments
 (0)