-
Notifications
You must be signed in to change notification settings - Fork 181
[draft] Add fallible unmounting #648
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
2d38e06
tests: write test for expected behavior of unmounting
khanhtranngoccva 361b3df
test: demonstration tests with current behavior
khanhtranngoccva 83fcb54
feat: running crate integration tests as root
khanhtranngoccva cffd046
fix: minor CI fix for test case
khanhtranngoccva 80f5b2e
Merge branch 'master' of https://github.com/cberner/fuser into blocki…
khanhtranngoccva de71048
misc: adapt main branch
khanhtranngoccva File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,140 @@ | ||
| use std::ffi::OsStr; | ||
| use std::path::PathBuf; | ||
| use std::time::Duration; | ||
| use std::time::UNIX_EPOCH; | ||
|
|
||
| use clap::Parser; | ||
| use fuser::Errno; | ||
| use fuser::FileAttr; | ||
| use fuser::FileHandle; | ||
| use fuser::FileType; | ||
| use fuser::Filesystem; | ||
| use fuser::INodeNo; | ||
| use fuser::LockOwner; | ||
| use fuser::OpenFlags; | ||
| use fuser::ReplyAttr; | ||
| use fuser::ReplyData; | ||
| use fuser::ReplyDirectory; | ||
| use fuser::ReplyEntry; | ||
| use fuser::Request; | ||
|
|
||
| #[derive(Parser)] | ||
| #[command(version, author = "Christopher Berner")] | ||
| struct Args { | ||
| /// Act as a client, and mount FUSE at given path | ||
| mount_point: PathBuf, | ||
|
|
||
| /// Automatically unmount on process exit | ||
| #[clap(long)] | ||
| auto_unmount: bool, | ||
|
|
||
| /// Allow root user to access filesystem | ||
| #[clap(long)] | ||
| allow_root: bool, | ||
| } | ||
|
|
||
| const TTL: Duration = Duration::from_secs(1); // 1 second | ||
|
|
||
| const HELLO_DIR_ATTR: FileAttr = FileAttr { | ||
| ino: INodeNo::ROOT, | ||
| size: 0, | ||
| blocks: 0, | ||
| atime: UNIX_EPOCH, // 1970-01-01 00:00:00 | ||
| mtime: UNIX_EPOCH, | ||
| ctime: UNIX_EPOCH, | ||
| crtime: UNIX_EPOCH, | ||
| kind: FileType::Directory, | ||
| perm: 0o755, | ||
| nlink: 2, | ||
| uid: 501, | ||
| gid: 20, | ||
| rdev: 0, | ||
| flags: 0, | ||
| blksize: 512, | ||
| }; | ||
|
|
||
| const HELLO_TXT_CONTENT: &str = "Hello World!\n"; | ||
|
|
||
| const HELLO_TXT_ATTR: FileAttr = FileAttr { | ||
| ino: INodeNo(2), | ||
| size: 13, | ||
| blocks: 1, | ||
| atime: UNIX_EPOCH, // 1970-01-01 00:00:00 | ||
| mtime: UNIX_EPOCH, | ||
| ctime: UNIX_EPOCH, | ||
| crtime: UNIX_EPOCH, | ||
| kind: FileType::RegularFile, | ||
| perm: 0o644, | ||
| nlink: 1, | ||
| uid: 501, | ||
| gid: 20, | ||
| rdev: 0, | ||
| flags: 0, | ||
| blksize: 512, | ||
| }; | ||
|
|
||
| pub struct HelloFS; | ||
|
|
||
| impl Filesystem for HelloFS { | ||
| fn lookup(&self, _req: &Request, parent: INodeNo, name: &OsStr, reply: ReplyEntry) { | ||
| if u64::from(parent) == 1 && name.to_str() == Some("hello.txt") { | ||
| reply.entry(&TTL, &HELLO_TXT_ATTR, fuser::Generation(0)); | ||
| } else { | ||
| reply.error(Errno::ENOENT); | ||
| } | ||
| } | ||
|
|
||
| fn getattr(&self, _req: &Request, ino: INodeNo, _fh: Option<FileHandle>, reply: ReplyAttr) { | ||
| match u64::from(ino) { | ||
| 1 => reply.attr(&TTL, &HELLO_DIR_ATTR), | ||
| 2 => reply.attr(&TTL, &HELLO_TXT_ATTR), | ||
| _ => reply.error(Errno::ENOENT), | ||
| } | ||
| } | ||
|
|
||
| fn read( | ||
| &self, | ||
| _req: &Request, | ||
| ino: INodeNo, | ||
| _fh: FileHandle, | ||
| offset: u64, | ||
| _size: u32, | ||
| _flags: OpenFlags, | ||
| _lock_owner: Option<LockOwner>, | ||
| reply: ReplyData, | ||
| ) { | ||
| if u64::from(ino) == 2 { | ||
| reply.data(&HELLO_TXT_CONTENT.as_bytes()[offset as usize..]); | ||
| } else { | ||
| reply.error(Errno::ENOENT); | ||
| } | ||
| } | ||
|
|
||
| fn readdir( | ||
| &self, | ||
| _req: &Request, | ||
| ino: INodeNo, | ||
| _fh: FileHandle, | ||
| offset: u64, | ||
| mut reply: ReplyDirectory, | ||
| ) { | ||
| if u64::from(ino) != 1 { | ||
| reply.error(Errno::ENOENT); | ||
| return; | ||
| } | ||
|
|
||
| let entries = vec![ | ||
| (1, FileType::Directory, "."), | ||
| (1, FileType::Directory, ".."), | ||
| (2, FileType::RegularFile, "hello.txt"), | ||
| ]; | ||
|
|
||
| for (i, entry) in entries.into_iter().enumerate().skip(offset as usize) { | ||
| // i + 1 means the index of the next entry | ||
| if reply.add(INodeNo(entry.0), (i + 1) as u64, entry.1, entry.2) { | ||
| break; | ||
| } | ||
| } | ||
| reply.ok(); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| pub mod hello_fs; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,112 @@ | ||
| mod fixtures; | ||
|
|
||
| use std::io::Read; | ||
| use std::time::Duration; | ||
|
|
||
| use fixtures::hello_fs::HelloFS; | ||
| use fuser::Config; | ||
| use fuser::MountOption; | ||
| use fuser::SessionACL; | ||
|
|
||
| #[test_log::test] | ||
| fn should_prompt_unmount_retry_while_file_is_open_without_autounmount() { | ||
| let mut mountpoint = tempfile::tempdir().unwrap(); | ||
| mountpoint.disable_cleanup(true); | ||
| let mut cfg = Config::default(); | ||
| cfg.acl = SessionACL::RootAndOwner; | ||
| cfg.n_threads = Some(2); | ||
| let session = fuser::spawn_mount(HelloFS, &mountpoint, &cfg).unwrap(); | ||
| let hello_file = mountpoint.path().join("hello.txt"); | ||
|
|
||
| let (handle_open_done_tx, handle_open_done_rx) = std::sync::mpsc::channel::<()>(); | ||
| let (umount_completed_tx, unmount_completed_rx) = std::sync::mpsc::channel::<()>(); | ||
|
|
||
| let main_thread = std::thread::spawn(move || { | ||
| // Attempt to unmount while the file is open | ||
| handle_open_done_rx.recv().expect("recv handle open done"); | ||
| // TODO: the outstanding handle must be closed for the unmount to finish, for which the thread owning the outstanding | ||
| // handle must receive a message from umount_and_join that it would fail with EBUSY (otherwise, the outstanding | ||
| // handle might be closed before an unmount is attempted, which causes the unmount to succeed immediately and | ||
| // makes the test flaky). The only way to mitigate the flakiness without non-blocking cooperation from umount_and_join | ||
| // is to make the handle thread wait for some time. | ||
|
|
||
| // In previous candidate PRs, this is done by creating an interface that does not perform a lazy/detach | ||
| // unmount, which returns EBUSY, allowing the thread to send a signal to the thread owning the outstanding | ||
| // handle to inform it that it cannot unmount. | ||
| session.umount_and_join().expect("unmount should succeed"); | ||
| umount_completed_tx | ||
| .send(()) | ||
| .expect("send unmount completed"); | ||
| }); | ||
| let hello_thread = std::thread::spawn(move || { | ||
| let mut file = std::fs::File::open(hello_file).expect("open hello file"); | ||
| let mut buffer = Vec::new(); | ||
| file.read_to_end(&mut buffer).expect("read hello file"); | ||
| // Notify the main thread that the file is opened - the session should try to unmount while the handle is open | ||
| handle_open_done_tx.send(()).expect("send handle open done"); | ||
| // FIXME: this part should have waited for the main thread to send a busy/blocking error | ||
| std::thread::sleep(Duration::from_secs_f64(0.25)); | ||
| drop(file); | ||
| }); | ||
|
|
||
| let res = unmount_completed_rx.recv_timeout(Duration::from_secs(5)); | ||
| if let Err(e) = res { | ||
| let _ = main_thread.join(); | ||
| let _ = hello_thread.join(); | ||
| panic!("unmount completed rx error: {:?}", e); | ||
| } | ||
| main_thread.join().expect("join main thread"); | ||
| hello_thread.join().expect("join hello thread"); | ||
| } | ||
|
|
||
| #[test_log::test] | ||
| fn should_prompt_unmount_retry_while_file_is_open_with_autounmount() { | ||
| let mut mountpoint = tempfile::tempdir().unwrap(); | ||
| mountpoint.disable_cleanup(true); | ||
| let mut cfg = Config::default(); | ||
| cfg.acl = SessionACL::RootAndOwner; | ||
| cfg.n_threads = Some(2); | ||
| cfg.mount_options.push(MountOption::AutoUnmount); | ||
| let session = fuser::spawn_mount(HelloFS, &mountpoint, &cfg).unwrap(); | ||
| let hello_file = mountpoint.path().join("hello.txt"); | ||
|
|
||
| let (handle_open_done_tx, handle_open_done_rx) = std::sync::mpsc::channel::<()>(); | ||
| let (umount_completed_tx, unmount_completed_rx) = std::sync::mpsc::channel::<()>(); | ||
|
|
||
| let main_thread = std::thread::spawn(move || { | ||
| // Attempt to unmount while the file is open | ||
| handle_open_done_rx.recv().expect("recv handle open done"); | ||
| // TODO: the outstanding handle must be closed for the unmount to finish, for which the thread owning the outstanding | ||
| // handle must receive a message from umount_and_join that it would fail with EBUSY (otherwise, the outstanding | ||
| // handle might be closed before an unmount is attempted, which causes the unmount to succeed immediately and | ||
| // makes the test flaky). The only way to mitigate the flakiness without non-blocking cooperation from umount_and_join | ||
| // is to make the handle thread wait for some time. | ||
|
|
||
| // In previous candidate PRs, this is done by creating an interface that does not perform a lazy/detach | ||
| // unmount, which returns EBUSY, allowing the thread to send a signal to the thread owning the outstanding | ||
| // handle to inform it that it cannot unmount. | ||
| session.umount_and_join().expect("unmount should succeed"); | ||
| umount_completed_tx | ||
| .send(()) | ||
| .expect("send unmount completed"); | ||
| }); | ||
| let hello_thread = std::thread::spawn(move || { | ||
| let mut file = std::fs::File::open(hello_file).expect("open hello file"); | ||
| let mut buffer = Vec::new(); | ||
| file.read_to_end(&mut buffer).expect("read hello file"); | ||
| // Notify the main thread that the file is opened - the session should try to unmount while the handle is open | ||
| handle_open_done_tx.send(()).expect("send handle open done"); | ||
| // FIXME: this part should have waited for the main thread to send a busy/blocking error | ||
| std::thread::sleep(Duration::from_secs_f64(0.25)); | ||
| drop(file); | ||
| }); | ||
|
|
||
| let res = unmount_completed_rx.recv_timeout(Duration::from_secs(5)); | ||
| if let Err(e) = res { | ||
| let _ = main_thread.join(); | ||
| let _ = hello_thread.join(); | ||
| panic!("unmount completed rx error: {:?}", e); | ||
| } | ||
| main_thread.join().expect("join main thread"); | ||
| hello_thread.join().expect("join hello thread"); | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The new test file declares
mod fixtures;, which requires eithertests/fixtures.rsortests/fixtures/mod.rs, but this commit does not add either file. That causes theunmountintegration test target to fail compilation with an unresolved module error before any test logic can run.Useful? React with 👍 / 👎.