fuse_backend_rs/passthrough/
mount_fd.rs

1// Use of this source code is governed by a BSD-style license that can be
2// found in the LICENSE-BSD-3-Clause file.
3
4use std::collections::{HashMap, HashSet};
5use std::ffi::CString;
6use std::fs::File;
7use std::io::{self, Read, Seek};
8use std::os::fd::{AsFd, BorrowedFd};
9use std::os::unix::io::{AsRawFd, RawFd};
10use std::sync::{Arc, Mutex, RwLock, Weak};
11
12use super::statx::statx;
13use super::util::{einval, is_safe_inode};
14
15const MOUNT_INFO_FILE: &str = "/proc/self/mountinfo";
16
17/// Type alias for mount id.
18pub type MountId = u64;
19
20pub struct MountFd {
21    file: File,
22    mount_id: MountId,
23    map: Weak<RwLock<HashMap<MountId, Weak<MountFd>>>>,
24}
25
26impl AsFd for MountFd {
27    fn as_fd(&self) -> BorrowedFd {
28        self.file.as_fd()
29    }
30}
31
32impl Drop for MountFd {
33    fn drop(&mut self) {
34        debug!(
35            "Dropping MountFd: mount_id={}, mount_fd={}",
36            self.mount_id,
37            self.file.as_raw_fd(),
38        );
39
40        // If `self.map.upgrade()` fails, then the `MountFds` structure was dropped while there was
41        // still an `Arc<MountFd>` alive.  In this case, we don't need to remove it from the map,
42        // because the map doesn't exist anymore.
43        if let Some(map) = self.map.upgrade() {
44            let mut map = map.write().unwrap();
45            // After the refcount reaches zero and before we lock the map, there's a window where
46            // the value can be concurrently replaced by a `Weak` pointer to a new `MountFd`.
47            // Therefore, only remove the value if the refcount in the map is zero, too.
48            if let Some(0) = map.get(&self.mount_id).map(Weak::strong_count) {
49                map.remove(&self.mount_id);
50            }
51        }
52    }
53}
54
55/// This type maintains a map where each entry maps a mount ID to an open FD on that mount.  Other
56/// code can request an `Arc<MountFd>` for any mount ID.  A key gets added to the map, when the
57/// first `Arc<MountFd>` for that mount ID is requested.  A key gets removed from the map, when the
58/// last `Arc<MountFd>` for that mount ID is dropped.  That is, map entries are reference-counted
59/// and other code can keep an entry in the map by holding on to an `Arc<MountFd>`.
60///
61/// We currently have one use case for `MountFds`:
62///
63/// 1. Creating a file handle only returns a mount ID, but opening a file handle requires an open FD
64///    on the respective mount.  So we look that up in the map.
65pub struct MountFds {
66    map: Arc<RwLock<HashMap<MountId, Weak<MountFd>>>>,
67
68    /// /proc/self/mountinfo
69    mount_info: Mutex<File>,
70
71    /// An optional prefix to strip from all mount points in mountinfo
72    mount_prefix: Option<String>,
73
74    /// Set of filesystems for which we have already logged file handle errors
75    error_logged: Arc<RwLock<HashSet<MountId>>>,
76}
77
78impl MountFds {
79    pub fn new(mount_prefix: Option<String>) -> io::Result<Self> {
80        let mount_info_file = File::open(MOUNT_INFO_FILE)?;
81
82        Ok(Self::with_mount_info_file(mount_info_file, mount_prefix))
83    }
84
85    pub fn with_mount_info_file(mount_info: File, mount_prefix: Option<String>) -> Self {
86        MountFds {
87            map: Default::default(),
88            mount_info: Mutex::new(mount_info),
89            mount_prefix,
90            error_logged: Default::default(),
91        }
92    }
93
94    pub fn get<F>(&self, mount_id: MountId, reopen_fd: F) -> MPRResult<Arc<MountFd>>
95    where
96        F: FnOnce(RawFd, libc::c_int, u32) -> io::Result<File>,
97    {
98        let existing_mount_fd = self
99            .map
100            // The `else` branch below (where `existing_mount_fd` matches `None`) takes a write lock
101            // to insert a new mount FD into the hash map.  This doesn't deadlock, because the read
102            // lock taken here doesn't have its lifetime extended beyond the statement, because
103            // `Weak::upgrade` returns a new pointer and not a reference into the read lock.
104            .read()
105            .unwrap()
106            .get(&mount_id)
107            // We treat a failed upgrade just like a non-existent key, because it means that all
108            // strong references to the `MountFd` have disappeared, so it's in the process of being
109            // dropped, but `MountFd::drop()` just did not yet get to remove it from the map.
110            .and_then(Weak::upgrade);
111
112        let mount_fd = if let Some(mount_fd) = existing_mount_fd {
113            mount_fd
114        } else {
115            // `open_by_handle_at()` needs a non-`O_PATH` fd, which we will need to open here.  We
116            // are going to open the filesystem's mount point, but we do not know whether that is a
117            // special file[1], and we must not open special files with anything but `O_PATH`, so
118            // we have to get some `O_PATH` fd first that we can stat to find out whether it is
119            // safe to open.
120            // [1] While mount points are commonly directories, it is entirely possible for a
121            //     filesystem's root inode to be a regular or even special file.
122            let mount_point = self.get_mount_root(mount_id)?;
123
124            // Clone `mount_point` so we can still use it in error messages
125            let c_mount_point = CString::new(mount_point.clone()).map_err(|e| {
126                self.error_for(mount_id, e)
127                    .prefix(format!("Failed to convert \"{mount_point}\" to a CString"))
128            })?;
129
130            let mount_point_fd = unsafe { libc::open(c_mount_point.as_ptr(), libc::O_PATH) };
131            if mount_point_fd < 0 {
132                return Err(self
133                    .error_for(mount_id, io::Error::last_os_error())
134                    .prefix(format!("Failed to open mount point \"{mount_point}\"")));
135            }
136
137            // Check the mount point has the expected `mount_id`.
138            let st_mode = self.validate_mount_id(mount_id, &mount_point_fd, &mount_point)?;
139
140            // Ensure that we can safely reopen `mount_point_path` with `O_RDONLY`
141            let file_type = st_mode & libc::S_IFMT;
142            if !is_safe_inode(file_type) {
143                return Err(self
144                    .error_for(mount_id, io::Error::from_raw_os_error(libc::EIO))
145                    .set_desc(format!(
146                        "Mount point \"{mount_point}\" is not a regular file or directory"
147                    )));
148            }
149
150            // Now that we know that this is a regular file or directory, really open it
151            let file = reopen_fd(
152                mount_point_fd.as_raw_fd(),
153                libc::O_RDONLY | libc::O_NOFOLLOW | libc::O_CLOEXEC,
154                st_mode,
155            )
156            .map_err(|e| {
157                self.error_for(mount_id, e).prefix(format!(
158                    "Failed to reopen mount point \"{mount_point}\" for reading"
159                ))
160            })?;
161
162            let mut mount_fds_locked = self.map.write().unwrap();
163
164            // As above: by calling `and_then(Weak::upgrade)`, we treat a failed upgrade just like a
165            // non-existent key.  If the key exists but upgrade fails, then `HashMap::insert()`
166            // below will update the value.  `MountFd::drop()` takes care to only remove a `MountFd`
167            // without strong references from the map, and hence will not touch the updated one.
168            if let Some(mount_fd) = mount_fds_locked.get(&mount_id).and_then(Weak::upgrade) {
169                // A mount FD was added concurrently while we did not hold a lock on
170                // `mount_fds.map` -- use that entry (`file` will be dropped).
171                mount_fd
172            } else {
173                debug!(
174                    "Creating MountFd: mount_id={}, mount_fd={}",
175                    mount_id,
176                    file.as_raw_fd(),
177                );
178                let mount_fd = Arc::new(MountFd {
179                    file,
180                    mount_id,
181                    map: Arc::downgrade(&self.map),
182                });
183                mount_fds_locked.insert(mount_id, Arc::downgrade(&mount_fd));
184                mount_fd
185            }
186        };
187
188        Ok(mount_fd)
189    }
190
191    // Ensure that `mount_point_path` refers to an inode with the mount ID we need
192    fn validate_mount_id(
193        &self,
194        mount_id: MountId,
195        mount_point_fd: &impl AsRawFd,
196        mount_point: &str,
197    ) -> MPRResult<libc::mode_t> {
198        let stx = statx(mount_point_fd, None).map_err(|e| {
199            self.error_for(mount_id, e)
200                .prefix(format!("Failed to stat mount point \"{mount_point}\""))
201        })?;
202
203        if stx.mnt_id != mount_id {
204            return Err(self
205                .error_for(mount_id, io::Error::from_raw_os_error(libc::EIO))
206                .set_desc(format!(
207                    "Mount point's ({}) mount ID ({}) does not match expected value ({})",
208                    mount_point, stx.mnt_id, mount_id
209                )));
210        }
211
212        Ok(stx.st.st_mode)
213    }
214
215    /// Given a mount ID, return the mount root path (by reading `/proc/self/mountinfo`)
216    fn get_mount_root(&self, mount_id: MountId) -> MPRResult<String> {
217        let mountinfo = {
218            let mountinfo_file = &mut *self.mount_info.lock().unwrap();
219
220            mountinfo_file.rewind().map_err(|e| {
221                self.error_for_nolookup(mount_id, e)
222                    .prefix("Failed to access /proc/self/mountinfo".into())
223            })?;
224
225            let mut mountinfo = String::new();
226            mountinfo_file.read_to_string(&mut mountinfo).map_err(|e| {
227                self.error_for_nolookup(mount_id, e)
228                    .prefix("Failed to read /proc/self/mountinfo".into())
229            })?;
230
231            mountinfo
232        };
233
234        let path = mountinfo.split('\n').find_map(|line| {
235            let mut columns = line.split(char::is_whitespace);
236
237            if columns.next()?.parse::<MountId>().ok()? != mount_id {
238                return None;
239            }
240
241            // Skip parent mount ID, major:minor device ID, and the root within the filesystem
242            // (to get to the mount path)
243            columns.nth(3)
244        });
245
246        match path {
247            Some(p) => {
248                let p = String::from(p);
249                if let Some(prefix) = self.mount_prefix.as_ref() {
250                    if let Some(suffix) = p.strip_prefix(prefix).filter(|s| !s.is_empty()) {
251                        Ok(suffix.into())
252                    } else {
253                        // The shared directory is the mount point (strip_prefix() returned "") or
254                        // mount is outside the shared directory, so it must be the mount the root
255                        // directory is on
256                        Ok("/".into())
257                    }
258                } else {
259                    Ok(p)
260                }
261            }
262
263            None => Err(self
264                .error_for_nolookup(mount_id, einval())
265                .set_desc(format!("Failed to find mount root for mount ID {mount_id}"))),
266        }
267    }
268
269    /// Generate an `MPRError` object for the given `mount_id`, and silence it if we have already
270    /// generated such an object for that `mount_id`.
271    /// (Called `..._nolookup`, because in contrast to `MountFds::error_for()`, this method will
272    /// not try to look up the respective mount root path, and so is safe to call when such a
273    /// lookup would be unwise.)
274    fn error_for_nolookup<E: ToString + Into<io::Error>>(
275        &self,
276        mount_id: MountId,
277        err: E,
278    ) -> MPRError {
279        let err = MPRError::from(err).set_mount_id(mount_id);
280
281        if self.error_logged.read().unwrap().contains(&mount_id) {
282            err.silence()
283        } else {
284            self.error_logged.write().unwrap().insert(mount_id);
285            err
286        }
287    }
288
289    /// Call `self.error_for_nolookup()`, and if the `MPRError` object is not silenced, try to
290    /// obtain the mount root path for the given `mount_id` and add it to the error object.
291    /// (Note: DO NOT call this method from `MountFds::get_mount_root()`, because that may lead to
292    /// an infinite loop.)
293    pub fn error_for<E: ToString + Into<io::Error>>(&self, mount_id: MountId, err: E) -> MPRError {
294        let err = self.error_for_nolookup(mount_id, err);
295
296        if err.silent() {
297            // No need to add more information
298            err
299        } else {
300            // This just adds some information, so ignore errors
301            if let Ok(mount_root) = self.get_mount_root(mount_id) {
302                err.set_mount_root(mount_root)
303            } else {
304                err
305            }
306        }
307    }
308}
309
310/**
311 * Error object (to be used as `Result<T, MPRError>`) for mount-point-related errors (hence MPR).
312 * Includes a description (that is auto-generated from the `io::Error` at first), which can be
313 * overridden with `MPRError::set_desc()`, or given a prefix with `MPRError::prefix()`.
314 *
315 * The full description can be retrieved through the `Display` trait implementation (or the
316 * auto-derived `ToString`).
317 *
318 * `MPRError` objects should generally be logged at some point, because they may indicate an error
319 * in the user's configuration or a bug in virtiofsd.  However, we only want to log them once per
320 * filesystem, and so they can be silenced (setting `silent` to true if we know that we have
321 * already logged an error for the respective filesystem) and then should not be logged.
322 *
323 * Naturally, a "mount-point-related" error should be associated with some mount point, which is
324 * reflected in `fs_mount_id` and `fs_mount_root`.  Setting these values will improve the error
325 * description, because the `Display` implementation will prepend these values to the returned
326 * string.
327 *
328 * To achieve this association, `MPRError` objects should be created through
329 * `MountFds::error_for()`, which obtains the mount root path for the given mount ID, and will thus
330 * try to not only set `fs_mount_id`, but `fs_mount_root` also.  `MountFds::error_for()` will also
331 * take care to set `silent` as appropriate.
332 *
333 * (Sometimes, though, we know an error is associated with a mount point, but we do not know with
334 * which one.  That is why the `fs_mount_id` field is optional.)
335 */
336#[derive(Debug)]
337pub struct MPRError {
338    io: io::Error,
339    description: String,
340    silent: bool,
341
342    fs_mount_id: Option<MountId>,
343    fs_mount_root: Option<String>,
344}
345
346/// Type alias for convenience
347pub type MPRResult<T> = Result<T, MPRError>;
348
349impl<E: ToString + Into<io::Error>> From<E> for MPRError {
350    /// Convert any stringifyable error object that can be converted to an `io::Error` to an
351    /// `MPRError`.  Note that `fs_mount_id` and `fs_mount_root` are not set, so this `MPRError`
352    /// object is not associated with any mount point.
353    /// The initial description is taken from the original error object.
354    fn from(err: E) -> Self {
355        let description = err.to_string();
356        MPRError {
357            io: err.into(),
358            description,
359            silent: false,
360
361            fs_mount_id: None,
362            fs_mount_root: None,
363        }
364    }
365}
366
367impl MPRError {
368    /// Override the current description
369    #[must_use]
370    pub fn set_desc(mut self, s: String) -> Self {
371        self.description = s;
372        self
373    }
374
375    /// Add a prefix to the description
376    #[must_use]
377    pub fn prefix(self, s: String) -> Self {
378        let new_desc = format!("{}: {}", s, self.description);
379        self.set_desc(new_desc)
380    }
381
382    /// To give additional information to the user (when this error is logged), add the mount ID of
383    /// the filesystem associated with this error
384    #[must_use]
385    fn set_mount_id(mut self, mount_id: MountId) -> Self {
386        self.fs_mount_id = Some(mount_id);
387        self
388    }
389
390    /// To give additional information to the user (when this error is logged), add the mount root
391    /// path for the filesystem associated with this error
392    #[must_use]
393    fn set_mount_root(mut self, mount_root: String) -> Self {
394        self.fs_mount_root = Some(mount_root);
395        self
396    }
397
398    /// Mark this error as silent (i.e. not to be logged)
399    #[must_use]
400    fn silence(mut self) -> Self {
401        self.silent = true;
402        self
403    }
404
405    /// Return whether this error is silent (i.e. should not be logged)
406    pub fn silent(&self) -> bool {
407        self.silent
408    }
409
410    /// Return the `io::Error` from an `MPRError` and drop the rest
411    pub fn into_inner(self) -> io::Error {
412        self.io
413    }
414}
415
416impl std::fmt::Display for MPRError {
417    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
418        match (self.fs_mount_id, &self.fs_mount_root) {
419            (None, None) => write!(f, "{}", self.description),
420
421            (Some(id), None) => write!(f, "Filesystem with mount ID {}: {}", id, self.description),
422
423            (None, Some(root)) => write!(
424                f,
425                "Filesystem mounted on \"{}\": {}",
426                root, self.description
427            ),
428
429            (Some(id), Some(root)) => write!(
430                f,
431                "Filesystem mounted on \"{}\" (mount ID: {}): {}",
432                root, id, self.description
433            ),
434        }
435    }
436}
437
438impl std::error::Error for MPRError {}
439
440#[cfg(test)]
441mod tests {
442    use super::*;
443    use crate::passthrough::file_handle::FileHandle;
444
445    #[test]
446    fn test_mount_fd_get() {
447        let topdir = env!("CARGO_MANIFEST_DIR");
448        let dir = File::open(topdir).unwrap();
449        let filename = CString::new("build.rs").unwrap();
450        let mount_fds = MountFds::new(None).unwrap();
451        let handle = FileHandle::from_name_at(&dir, &filename).unwrap().unwrap();
452
453        // Ensure that `MountFds::get()` works for new entry.
454        let fd1 = mount_fds
455            .get(handle.mnt_id, |_fd, _flags, _mode| File::open(topdir))
456            .unwrap();
457        assert_eq!(Arc::strong_count(&fd1), 1);
458        assert_eq!(mount_fds.map.read().unwrap().len(), 1);
459
460        // Ensure that `MountFds::get()` works for existing entry.
461        let fd2 = mount_fds
462            .get(handle.mnt_id, |_fd, _flags, _mode| File::open(topdir))
463            .unwrap();
464        assert_eq!(Arc::strong_count(&fd2), 2);
465        assert_eq!(mount_fds.map.read().unwrap().len(), 1);
466
467        // Ensure fd1 and fd2 are the same object.
468        assert_eq!(fd1.as_fd().as_raw_fd(), fd2.as_fd().as_raw_fd());
469
470        drop(fd1);
471        assert_eq!(Arc::strong_count(&fd2), 1);
472        assert_eq!(mount_fds.map.read().unwrap().len(), 1);
473
474        // Ensure that `MountFd::drop()` works as expected.
475        drop(fd2);
476        assert_eq!(mount_fds.map.read().unwrap().len(), 0);
477    }
478
479    #[test]
480    fn test_mpr_error() {
481        let io_error = io::Error::new(io::ErrorKind::Other, "test");
482        let mpr_error = MPRError::from(io_error);
483
484        assert!(!mpr_error.silent);
485        assert!(mpr_error.fs_mount_id.is_none());
486        assert!(mpr_error.fs_mount_root.is_none());
487        let mpr_error = mpr_error.silence();
488        let msg = format!("{}", mpr_error);
489        assert!(msg.len() > 0);
490        assert!(mpr_error.silent());
491    }
492}