filepath/
lib.rs

1//! # filepath
2//!
3//! `filepath` contains an extension trait for `std::fs::File` providing a `path` method.
4//!
5
6use std::fs::File;
7use std::io;
8use std::path::PathBuf;
9
10#[cfg(unix)]
11use std::os::unix::io::AsRawFd;
12
13/// An extension trait for `std::fs::File` providing a `path` method.
14pub trait FilePath {
15    /// Returns the path of this file.
16    ///
17    /// The path might be wrong for example after moving a file.
18    ///
19    /// # Platform-specific behavior
20    /// This function currently uses `/proc/self/fd/` on Linux, `fcntl` with `F_GETPATH` on macOS
21    /// and `GetFinalPathNameByHandle` on Windows.
22    ///
23    /// # Examples
24    ///
25    /// ```no_run
26    /// use std::fs::File;
27    /// use std::io;
28    /// use filepath::FilePath;
29    ///
30    /// fn main() -> io::Result<()> {
31    ///     let file = File::open("some_file")?;
32    ///     let path = file.path()?;
33    ///     Ok(())
34    /// }
35    /// ```
36    fn path(&self) -> io::Result<PathBuf>;
37}
38
39impl FilePath for File {
40    #[cfg(target_os = "linux")]
41    fn path(&self) -> io::Result<PathBuf> {
42        use std::path::Path;
43
44        let fd = self.as_raw_fd();
45        let path = Path::new("/proc/self/fd/").join(fd.to_string());
46        std::fs::read_link(path)
47    }
48
49    #[cfg(any(target_os = "macos", target_os = "ios"))]
50    fn path(&self) -> io::Result<PathBuf> {
51        use std::ffi::OsString;
52        use std::os::unix::ffi::OsStringExt;
53        const F_GETPATH: i32 = 50;
54
55        let fd = self.as_raw_fd();
56        let mut path = vec![0; libc::PATH_MAX as usize + 1];
57
58        unsafe {
59            if libc::fcntl(fd, F_GETPATH, path.as_mut_ptr()) < 0 {
60                return Err(io::Error::last_os_error());
61            }
62        }
63
64        path.retain(|&c| c != 0);
65        Ok(PathBuf::from(OsString::from_vec(path)))
66    }
67
68    #[cfg(windows)]
69    fn path(&self) -> std::io::Result<PathBuf> {
70        use std::ffi::OsString;
71        use std::os::windows::{ffi::OsStringExt, io::AsRawHandle};
72        use windows::Win32::{
73            Foundation,
74            Storage::FileSystem::{GetFinalPathNameByHandleW, GETFINALPATHNAMEBYHANDLE_FLAGS},
75        };
76
77        // Call with null to get the required size.
78        let len = unsafe {
79            let handle = Foundation::HANDLE(self.as_raw_handle());
80            GetFinalPathNameByHandleW(handle, &mut [], GETFINALPATHNAMEBYHANDLE_FLAGS(0))
81        };
82        if len == 0 {
83            return Err(io::Error::last_os_error());
84        }
85
86        let mut path = vec![0; len as usize];
87        let len2 = unsafe {
88            let handle = Foundation::HANDLE(self.as_raw_handle());
89            GetFinalPathNameByHandleW(handle, &mut path, GETFINALPATHNAMEBYHANDLE_FLAGS(0))
90        };
91        // Handle unlikely case that path length changed between those two calls.
92        if len2 == 0 || len2 >= len {
93            return Err(io::Error::last_os_error());
94        }
95        path.truncate(len2 as usize);
96
97        // Turn the \\?\UNC\ network path prefix into \\.
98        let prefix = [
99            '\\' as _, '\\' as _, '?' as _, '\\' as _, 'U' as _, 'N' as _, 'C' as _, '\\' as _,
100        ];
101        if path.starts_with(&prefix) {
102            let mut network_path: Vec<u16> = vec!['\\' as u16, '\\' as u16];
103            network_path.extend_from_slice(&path[prefix.len()..]);
104            return Ok(PathBuf::from(OsString::from_wide(&network_path)));
105        }
106
107        // Remove the \\?\ prefix.
108        let prefix = ['\\' as _, '\\' as _, '?' as _, '\\' as _];
109        if path.starts_with(&prefix) {
110            return Ok(PathBuf::from(OsString::from_wide(&path[prefix.len()..])));
111        }
112
113        Ok(PathBuf::from(OsString::from_wide(&path)))
114    }
115}
116
117#[cfg(test)]
118mod tests {
119    use crate::FilePath;
120    use std::fs::{remove_file, File};
121    use std::io::prelude::*;
122
123    #[test]
124    fn simple() {
125        let file = File::create("foobar").unwrap();
126        assert_eq!(file.path().unwrap().file_name().unwrap(), "foobar");
127        remove_file("foobar").unwrap();
128    }
129
130    #[test]
131    fn roundtrip() {
132        let mut file = File::create("bar").unwrap();
133        file.write(b"abc").unwrap();
134        file.flush().unwrap();
135
136        let mut file2 = File::open(file.path().unwrap()).unwrap();
137        let mut buffer = String::new();
138        file2.read_to_string(&mut buffer).unwrap();
139
140        assert_eq!(buffer, "abc");
141        remove_file("bar").unwrap();
142    }
143}