fuse/
conn.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4#![cfg(target_os = "linux")]
5// UNSAFETY: Calling (u)mount.
6#![expect(unsafe_code)]
7
8use super::Fuse;
9use crate::reply::ReplySender;
10use crate::request::*;
11use crate::session::Session;
12use crate::util;
13use std::ffi;
14use std::fs;
15use std::io::Read;
16use std::io::Write;
17use std::io::{self};
18use std::os::unix::prelude::*;
19use std::path::Path;
20
21/// A simple driver for a FUSE session using `/dev/fuse`.
22///
23/// Since this library is primarily intended for virtio-fs, `/dev/fuse` support is for testing
24/// purposes only, and the functionality is limited.
25pub struct Connection {
26    fuse_dev: fs::File,
27}
28
29impl Connection {
30    /// Creates a new `Connection` by mounting a file system.
31    pub fn mount(mount_point: impl AsRef<Path>) -> lx::Result<Self> {
32        // Open an fd to /dev/fuse.
33        let fuse_dev = fs::OpenOptions::new()
34            .read(true)
35            .write(true)
36            .open("/dev/fuse")?;
37
38        // Set up the mount options (currently, these can't be customized)
39        let options = format!(
40            "fd={},rootmode=40000,user_id=0,group_id=0",
41            fuse_dev.as_raw_fd()
42        );
43
44        // Perform the mount.
45        let options = util::create_cstr(options)?;
46        let target = util::create_cstr(mount_point.as_ref().as_os_str().as_bytes())?;
47
48        // SAFETY: Calling C API as documented, with no special requirements.
49        unsafe {
50            check_lx_errno(libc::mount(
51                util::create_cstr("none")?.as_ptr(),
52                target.as_ptr(),
53                util::create_cstr("fuse.test")?.as_ptr(),
54                0,
55                options.as_ptr().cast::<ffi::c_void>(),
56            ))?;
57        }
58
59        Ok(Self { fuse_dev })
60    }
61
62    // Unmount a file system.
63    pub fn unmount(mount_point: impl AsRef<Path>, flags: i32) -> lx::Result<()> {
64        // SAFETY: Calling C API as documented, with no special requirements.
65        unsafe {
66            check_lx_errno(libc::umount2(
67                util::create_cstr(mount_point.as_ref().as_os_str().as_bytes())?.as_ptr(),
68                flags,
69            ))?;
70        }
71
72        Ok(())
73    }
74
75    /// Create a FUSE session and run it until the file system is unmounted.
76    pub fn run<T: 'static + Fuse + Send + Sync>(&mut self, fs: T) -> lx::Result<()> {
77        // TODO: the size of the buffer should be adjusted based on max_write.
78        let mut buffer = vec![0u8; 1028 * 1024];
79        let session = Session::new(fs);
80
81        let mut size = self.read(&mut buffer);
82        while size > 0 {
83            let request = Request::new(&buffer[..size])?;
84            session.dispatch(request, self, None);
85
86            size = self.read(&mut buffer);
87        }
88
89        session.destroy();
90        Ok(())
91    }
92
93    /// Read a message from `/dev/fuse`. An error is assumed to mean that the file system was
94    /// unmounted.
95    fn read(&mut self, buffer: &mut [u8]) -> usize {
96        match self.fuse_dev.read(buffer) {
97            Ok(size) => size,
98            Err(e) => {
99                tracing::warn!(
100                    len = buffer.len(),
101                    error = &e as &dyn std::error::Error,
102                    "/dev/fuse read failed",
103                );
104                0
105            }
106        }
107    }
108}
109
110impl ReplySender for Connection {
111    fn send(&mut self, bufs: &[io::IoSlice<'_>]) -> io::Result<()> {
112        let size = self.fuse_dev.write_vectored(bufs)?;
113        if size < bufs.iter().map(|s| s.len()).sum() {
114            return Err(io::Error::other("Failed to write all data"));
115        }
116
117        Ok(())
118    }
119}
120
121// Return an lx::Result if a libc return value is negative. Otherwise, return the value.
122fn check_lx_errno<T: PartialOrd<T> + Default>(result: T) -> lx::Result<T> {
123    if result < Default::default() {
124        Err(lx::Error::last_os_error())
125    } else {
126        Ok(result)
127    }
128}