cberner / fuser

Filesystem in Userspace (FUSE) for Rust
MIT License
836 stars 114 forks source link

Add 'umount()' function #179

Closed cehteh closed 2 years ago

cehteh commented 3 years ago

Eventually one wants to unmount a filesystem. Besides manual unmounting by the user, a programm may decide to unmount the filesystem reacting to some event or signal. This can be done by

            std::process::Command::new("fusermount")
                .arg("-u")
                .arg(&umountpoint)
                .status()
                .expect("umount");

But this can be inconsistent/fallible depending on where the fuse 'fusermount' tool is installed. This Library already does some detection thereof and it would be more ergonomic to have a 'umount' as API.

cberner commented 3 years ago

Have you tried using spawn_mount? I believe it provides this functionality if you drop the BackgroundSession object that it returns

cehteh commented 3 years ago

On 2021-09-21 18:25, Christopher Berner wrote:

Have you tried using spawn_mount? I believe it provides this functionality if you drop the BackgroundSession object that it returns

having the main thread just linger for closing/unmounting the filesystem thread looked a bit unelegant to me. I am calling process::Command("fusermount -u ...") so far. That the filesystem unmounts itself is only a rare case anyway (ctrl-c handler in foreground/debug mode)

-- You are receiving this because you authored the thread. Reply to this email directly or view it on GitHub: https://github.com/cberner/fuser/issues/179#issuecomment-924507459

cehteh commented 3 years ago

Just some thinking: since fuser implements the event loop this could use some (atomic) bool or enum for the loop condition to be shutting down in a controlled way. I may implement such an event loop for my own filesystem, ping me when you are interested in merging such an facility.

cehteh commented 3 years ago

Just some thinking: since fuser implements the event loop this could use some (atomic) bool or enum for the loop condition to be shutting down in a controlled way. I may implement such an event loop for my own filesystem, ping me when you are interested in merging such an facility.

.. which is not as trivial as I thought because the Request api's are not public, one can't write his own event loop. I am postponing this now but will revisit it in future.

wmanley commented 3 years ago

I've got some plans in this area. Roughly:

  1. Separate out mounting the filesystem and running init from actually running the main loop. This would be a natural place to return errors, and would solve #175 as well. This would involve the typestate pattern to distinguish mounts that have been inited from ones that haven't. It would be separate from session as it would not need to be generic over FS type.
  2. Have a separate function for running the main loop. We'd pass a token into the main loop function to allow interrupting the main loop. This would be implemented in terms of a UNIX pipe, then in the main loop instead of read()ing from the FUSE fd we'd poll() the FUSE fd and the interrupt FD exiting the main loop when the write end of the pipe is closed. This would still be generic over FS type. We could then also implement different mainloop function implementations for different requirements - multi-threaded, async, etc.

This was going to be a part of #136, but I haven't had time to work on fuse-rs for quite some time.

cehteh commented 3 years ago
1. Separate out mounting the filesystem and running init from actually running the main loop. This would be a natural place to return errors, and would solve [RFC: Callback before entering the event loop #175](https://github.com/cberner/fuser/issues/175) as well. This would involve the typestate pattern to distinguish mounts that have been inited from ones that haven't.  It would be separate from session as it would not need to be generic over FS type.

Sounds good to me, i'd like when the mainloop body would be exposed too, then one could write a custom main loop as well.

2. Have a separate function for running the main loop. We'd pass a token into the main loop function to allow interrupting the main loop. This would be implemented in terms of a UNIX pipe, then in the main loop instead of `read()`ing from the FUSE fd we'd `poll()` the FUSE fd and the interrupt FD exiting the main loop when the write end of the pipe is closed.  This would still be generic over FS type.  We could then also implement different mainloop function implementations for different requirements - multi-threaded, async, etc.

Ideally one could opt out from the pipe, because these take resources (fd) while being only used very rarely. For in-process communication an atomic bool (plus perhaps caching an result/error within the session) to abort the mainloop could be enough. Maybe a signal to abort the wait for the kernel request.

Profpatsch commented 3 years ago

I want to add that currently there’s no way to install a race-free signal handler that will always unmount the fuse filesystem, as far as I can see:

let _guard = fuser::mount2(…)
… install signal handler here using the guard …

this pseudo-code has a toctou issue between getting the guard and installing the signal handler. But until you have the guard, you can’t install the signal handler.

You could pass auto_unmount to the mount2 call, but then you run into https://github.com/cberner/fuser/issues/167 :(

Profpatsch commented 3 years ago

I kinda worked around it now by using this:

fn main() {
  let (send, recv) = std::sync::mpsc::channel();
  ctrlc::set_handler(move || {
    println!("ctrl+c handler running");
    send.send(()).unwrap();
  })
  .unwrap();
  let guard = fuser::spawn_mount(
    fuse_client(),
    &"/home/philip/tmp/fuse",
    &vec![],
  )
  .unwrap();
  let () = recv.recv().unwrap();
  drop(guard)
}

but no idea how racy it is in practice. It seems to work for ctrl+c.

cberner commented 2 years ago

That looks race free to me, and is how I would recommend doing it

abspoel commented 2 years ago

@Profpatsch This works well for Ctrl+C, but how do you handle the case where the filesystem is unmounted and the background thread finishes? Ideally one would like the program to quit, but here the main thread will hang on recv.recv() because it never receives a signal.

I've worked around this by patching fuser::spawn_mount() to accept a Option<std::sync::mpsc::Sender<()>>, so that a message also gets sent to the channel when the thread quits. See https://github.com/abspoel/fuser

fn main() {
  let (send, recv) = std::sync::mpsc::channel();
  let send_ctrlc = send.clone();
  ctrlc::set_handler(move || {
    println!("ctrl+c handler running");
    send_ctrlc.send(()).unwrap();
  })
  .unwrap();
  let send_finished = send.clone();
  let guard = fuser::spawn_mount(
    fuse_client(),
    &"/home/philip/tmp/fuse",
    &vec![],
    Some(send_finished),
  )
  .unwrap();
  let () = recv.recv().unwrap();
  drop(guard)
}

Is this the most straightforward way or is there a better alternative? (Maybe one that does not involve modifying the spawn_mount function.)

cberner commented 2 years ago

@abspoel I think you can pass the Sender object into your Filesystem and call it in Filesystem::destroy(), without patching spawn_mount. That's called when the Session is dropped

abspoel commented 2 years ago

@cberner Thanks, that works!

cberner commented 2 years ago

I'm going to close this, since all the use cases so far can be supported with the existing spawn_mount function. Feel free to re-open if there's a use case that it does not support