1use std::fs::File;
7use std::io;
8use std::path::PathBuf;
9
10#[cfg(unix)]
11use std::os::unix::io::AsRawFd;
12
13pub trait FilePath {
15 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 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 if len2 == 0 || len2 >= len {
93 return Err(io::Error::last_os_error());
94 }
95 path.truncate(len2 as usize);
96
97 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 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}