blown_fuse/
mount.rs

1use std::{
2    ffi::{OsStr, OsString},
3    io,
4    os::unix::{
5        ffi::OsStrExt,
6        io::{AsRawFd, RawFd},
7        net::UnixStream,
8    },
9    path::{Path, PathBuf},
10    process::Command,
11};
12
13use nix::{
14    self, cmsg_space,
15    fcntl::{fcntl, FcntlArg, FdFlag},
16    sys::socket::{recvmsg, ControlMessageOwned, MsgFlags},
17};
18
19use crate::{error::MountError, session::Start, util::DumbFd};
20
21#[derive(Default)]
22pub struct Options(OsString);
23
24impl Options {
25    pub fn fs_name<O: AsRef<OsStr>>(&mut self, fs_name: O) -> &mut Self {
26        self.push_key_value("fsname", fs_name)
27    }
28
29    pub fn read_only(&mut self) -> &mut Self {
30        self.push("ro")
31    }
32
33    pub fn push<O: AsRef<OsStr>>(&mut self, option: O) -> &mut Self {
34        self.push_parts(&[option.as_ref()])
35    }
36
37    pub fn push_key_value<K, V>(&mut self, key: K, value: V) -> &mut Self
38    where
39        K: AsRef<OsStr>,
40        V: AsRef<OsStr>,
41    {
42        let (key, value) = (key.as_ref(), value.as_ref());
43
44        let assert_valid = |part: &OsStr| {
45            let bytes = part.as_bytes();
46            assert!(
47                !bytes.is_empty() && bytes.iter().all(|b| !matches!(*b, b',' | b'=')),
48                "invalid key or value: {}",
49                part.to_string_lossy()
50            );
51        };
52
53        assert_valid(key);
54        assert_valid(value);
55
56        self.push_parts(&[key, OsStr::new("="), value])
57    }
58
59    fn push_parts(&mut self, segment: &[&OsStr]) -> &mut Self {
60        if !self.0.is_empty() {
61            self.0.push(",");
62        }
63
64        let start = self.0.as_bytes().len();
65        segment.iter().for_each(|part| self.0.push(part));
66
67        let bytes = self.0.as_bytes();
68        let last = bytes.len() - 1;
69
70        assert!(
71            last >= start && bytes[start] != b',' && bytes[last] != b',',
72            "invalid option string: {}",
73            OsStr::from_bytes(&bytes[start..]).to_string_lossy()
74        );
75
76        self
77    }
78}
79
80impl<O: AsRef<OsStr>> Extend<O> for Options {
81    fn extend<I: IntoIterator<Item = O>>(&mut self, iter: I) {
82        iter.into_iter().for_each(|option| {
83            self.push(option);
84        });
85    }
86}
87
88pub fn mount_sync<M>(mountpoint: M, options: &Options) -> Result<Start, MountError>
89where
90    M: AsRef<Path> + Into<PathBuf>,
91{
92    let (left_side, right_side) = UnixStream::pair()?;
93
94    // The fusermount protocol requires us to preserve right_fd across execve()
95    let right_fd = right_side.as_raw_fd();
96    fcntl(
97        right_fd,
98        FcntlArg::F_SETFD(
99            FdFlag::from_bits(fcntl(right_fd, FcntlArg::F_GETFD).unwrap()).unwrap()
100                & !FdFlag::FD_CLOEXEC,
101        ),
102    )
103    .unwrap();
104
105    let mut command = Command::new(FUSERMOUNT_CMD);
106    if !options.0.is_empty() {
107        command.args(&[OsStr::new("-o"), &options.0]);
108    }
109
110    command.args(&[OsStr::new("--"), mountpoint.as_ref().as_ref()]);
111    let mut fusermount = command.env("_FUSE_COMMFD", right_fd.to_string()).spawn()?;
112
113    // recvmsg() should fail if fusermount exits (last open fd is closed)
114    drop(right_side);
115
116    let session_fd = {
117        let mut buffer = cmsg_space!(RawFd);
118        let message = recvmsg(
119            left_side.as_raw_fd(),
120            &[],
121            Some(&mut buffer),
122            MsgFlags::empty(),
123        )
124        .map_err(io::Error::from)?;
125
126        let session_fd = match message.cmsgs().next() {
127            Some(ControlMessageOwned::ScmRights(fds)) => fds.into_iter().next(),
128            _ => None,
129        };
130
131        session_fd.ok_or(MountError::Fusermount)
132    };
133
134    match session_fd {
135        Ok(session_fd) => Ok(Start::new(DumbFd(session_fd), mountpoint.into())),
136
137        Err(error) => {
138            drop(left_side);
139            fusermount.wait()?;
140            Err(error)
141        }
142    }
143}
144
145pub(crate) fn unmount_sync<M: AsRef<OsStr>>(mountpoint: M) -> Result<(), MountError> {
146    let status = Command::new(FUSERMOUNT_CMD)
147        .args(&[OsStr::new("-zuq"), OsStr::new("--"), mountpoint.as_ref()])
148        .status()?;
149
150    if status.success() {
151        Ok(())
152    } else {
153        Err(MountError::Fusermount)
154    }
155}
156
157const FUSERMOUNT_CMD: &str = "fusermount3";