1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#[cfg(unix)]
use super::super::Error;

#[cfg(unix)]
use std::path::{Path, PathBuf};

/// Find the mountpoint for the volume containing the (previously canonicalized) provided path.
///
/// This version relies on Unix-only extensions, but is safe.
///
/// It presumes that you will be passing in fully-qualified canonical paths.
#[cfg(unix)]
pub fn find_mountpoint_pre_canonicalized(path: &Path) -> Result<&Path, Error> {
    use std::fs::symlink_metadata;
    // needed to get dev for metadata (aliased as st_dev on Linux)
    use std::os::unix::fs::MetadataExt;

    let mut lstats = symlink_metadata(path)?;
    let start_dev = lstats.dev();

    let mut mountpoint = path;
    loop {
        let current = match mountpoint.parent() {
            Some(p) => p,
            None => return Ok(mountpoint),
        };
        lstats = symlink_metadata(current)?;
        if lstats.dev() != start_dev {
            break;
        }
        mountpoint = current;
    }

    // I may remove these later, but for now I want to verify that these invariants hold in the
    // wild.
    assert!(path.starts_with(mountpoint));
    assert!(!mountpoint.as_os_str().is_empty());

    Ok(mountpoint)
}

/// Find the mountpoint for the volume containing the provided path.
///
/// Canonicalizes the path before calling `find_mountpoint_pre_canonicalized`. Because
/// canonicalization produces a `PathBuf`, lifetime management requires returning an owned path,
/// hence returns a `PathBuf` instead of a reference to a Path.
#[cfg(unix)]
pub fn find_mountpoint(path: &Path) -> Result<PathBuf, Error> {
    let canonicalized = path.canonicalize()?;
    let found = find_mountpoint_pre_canonicalized(canonicalized.as_path())?;
    Ok(found.to_path_buf())
}