8000 fileutils and fstat (#5242) · RustPython/RustPython@192b0a8 · GitHub
[go: up one dir, main page]

Skip to content

Commit 192b0a8

Browse files
authored
fileutils and fstat (#5242)
1 parent 6a7be1e commit 192b0a8

File tree

4 files changed

+248
-32
lines changed

4 files changed

+248
-32
lines changed

Lib/test/test_ntpath.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -806,7 +806,6 @@ def check_error(exc, paths):
806806
self.assertRaises(TypeError, ntpath.commonpath,
807807
['Program Files', b'C:\\Program Files\\Foo'])
808808

809-
@unittest.expectedFailureIfWindows("TODO: RUSTPYTHON")
810809
@unittest.skipIf(is_emscripten, "Emscripten cannot fstat unnamed files.")
811810
def test_sameopenfile(self):
812811
with TemporaryFile() as tf1, TemporaryFile() as tf2:

vm/src/fileutils.rs

Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
// Python/fileutils.c in CPython
2+
#[cfg(not(windows))]
3+
pub use libc::stat as StatStruct;
4+
5+
#[cfg(windows)]
6+
pub use windows::{fstat, StatStruct};
7+
8+
#[cfg(windows)]
9+
mod windows {
10+
use crate::common::suppress_iph;
11+
use libc::{S_IFCHR, S_IFMT};
12+
use windows_sys::Win32::Foundation::SetLastError;
13+
use windows_sys::Win32::Foundation::FILETIME;
14+
use windows_sys::Win32::Foundation::{ERROR_INVALID_HANDLE, HANDLE, INVALID_HANDLE_VALUE};
15+
use windows_sys::Win32::Storage::FileSystem::{
16+
FileBasicInfo, FileIdInfo, GetFileInformationByHandle, GetFileInformationByHandleEx,
17+
GetFileType, FILE_TYPE_CHAR, FILE_TYPE_DISK, FILE_TYPE_PIPE, FILE_TYPE_UNKNOWN,
18+
};
19+
use windows_sys::Win32::Storage::FileSystem::{
20+
BY_HANDLE_FILE_INFORMATION, FILE_ATTRIBUTE_DIRECTORY, FILE_ATTRIBUTE_READONLY,
21+
FILE_ATTRIBUTE_REPARSE_POINT, FILE_BASIC_INFO, FILE_ID_INFO,
22+
};
23+
24+
pub const S_IFIFO: libc::c_int = 0o010000;
25+
pub const S_IFLNK: libc::c_int = 0o120000;
26+
27+
pub const SECS_BETWEEN_EPOCHS: i64 = 11644473600; // Seconds between 1.1.1601 and 1.1.1970
28+
29+
#[derive(Default)]
30+
pub struct StatStruct {
31+
pub st_dev: libc::c_ulong,
32+
pub st_ino: u64,
33+
pub st_mode: libc::c_ushort,
34+
pub st_nlink: i32,
35+
pub st_uid: i32,
36+
pub st_gid: i32,
37+
pub st_rdev: libc::c_ulong,
38+
pub st_size: u64,
39+
pub st_atime: libc::time_t,
40+
pub st_atime_nsec: i32,
41+
pub st_mtime: libc::time_t,
42+
pub st_mtime_nsec: i32,
43+
pub st_ctime: libc::time_t,
44+
pub st_ctime_nsec: i32,
45+
pub st_birthtime: libc::time_t,
46+
pub st_birthtime_nsec: i32,
47+
pub st_file_attributes: libc::c_ulong,
48+
pub st_reparse_tag: u32,
49+
pub st_ino_high: u64,
50+
}
51+
52+
extern "C" {
53+
fn _get_osfhandle(fd: i32) -> libc::intptr_t;
54+
}
55+
56+
fn get_osfhandle(fd: i32) -> std::io::Result<isize> {
57+
let ret = unsafe { suppress_iph!(_get_osfhandle(fd)) };
58+
if ret as HANDLE == INVALID_HANDLE_VALUE {
59+
Err(crate::common::os::last_os_error())
60+
} else {
61+
Ok(ret)
62+
}
63+
}
64+
65+
// _Py_fstat_noraise in cpython
66+
pub fn fstat(fd: libc::c_int) -> std::io::Result<StatStruct> {
67+
let h = get_osfhandle(fd);
68+
if h.is_err() {
69+
unsafe { SetLastError(ERROR_INVALID_HANDLE) };
70+
}
71+
let h = h?;
72+
// reset stat?
73+
74+
let file_type = unsafe { GetFileType(h) };
75+
if file_type == FILE_TYPE_UNKNOWN {
76+
return Err(std::io::Error::last_os_error());
77+
}
78+
if file_type != FILE_TYPE_DISK {
79+
let st_mode = if file_type == FILE_TYPE_CHAR {
80+
S_IFCHR
81+
} else if file_type == FILE_TYPE_PIPE {
82+
S_IFIFO
83+
} else {
84+
0
85+
} as u16;
86+
return Ok(StatStruct {
87+
st_mode,
88+
..Default::default()
89+
});
90+
}
91+
92+
let mut info = unsafe { std::mem::zeroed() };
93+
let mut basic_info: FILE_BASIC_INFO = unsafe { std::mem::zeroed() };
94+
let mut id_info: FILE_ID_INFO = unsafe { std::mem::zeroed() };
95+
96+
if unsafe { GetFileInformationByHandle(h, &mut info) } == 0
97+
|| unsafe {
98+
GetFileInformationByHandleEx(
99+
h,
100+
FileBasicInfo,
101+
&mut basic_info as *mut _ as *mut _,
102+
std::mem::size_of_val(&basic_info) as u32,
103+
)
104+
} == 0
105+
{
106+
return Err(std::io::Error::last_os_error());
107+
}
108+
109+
let p_id_info = if unsafe {
110+
GetFileInformationByHandleEx(
111+
h,
112+
FileIdInfo,
113+
&mut id_info as *mut _ as *mut _,
114+
std::mem::size_of_val(&id_info) as u32,
115+
)
116+
} == 0
117+
{
118+
None
119+
} else {
120+
Some(&id_info)
121+
};
122+
123+
Ok(attribute_data_to_stat(
124+
&info,
125+
0,
126+
Some(&basic_info),
127+
p_id_info,
128+
))
129+
}
130+
131+
fn i64_to_time_t_nsec(input: i64) -> (libc::time_t, libc::c_int) {
132+
let nsec_out = (input % 10_000_000) * 100; // FILETIME is in units of 100 nsec.
133+
let time_out = ((input / 10_000_000) - SECS_BETWEEN_EPOCHS) as libc::time_t;
134+
(time_out, nsec_out as _)
135+
}
136+
137+
fn file_time_to_time_t_nsec(in_ptr: &FILETIME) -> (libc::time_t, libc::c_int) {
138+
let in_val: i64 = unsafe { std::mem::transmute_copy(in_ptr) };
139+
let nsec_out = (in_val % 10_000_000) * 100; // FILETIME is in units of 100 nsec.
140+
let time_out = (in_val / 10_000_000) - SECS_BETWEEN_EPOCHS;
141+
(time_out, nsec_out as _)
142+
}
143+
144+
fn attribute_data_to_stat(
145+
info: &BY_HANDLE_FILE_INFORMATION,
146+
reparse_tag: u32,
147+
basic_info: Option<&FILE_BASIC_INFO>,
148+
id_info: Option<&FILE_ID_INFO>,
149+
) -> StatStruct {
150+
use windows_sys::Win32::System::SystemServices::IO_REPARSE_TAG_SYMLINK;
151+
152+
let mut st_mode: u16 = attributes_to_mode(info.dwFileAttributes) as _;
153+
let st_size = ((info.nFileSizeHigh as u64) << 32) + info.nFileSizeLow as u64;
154+
let st_dev: libc::c_ulong = if let Some(id_info) = id_info {
155+
id_info.VolumeSerialNumber as _
156+
} else {
157+
info.dwVolumeSerialNumber
158+
};
159+
let st_rdev = 0;
160+
161+
let (st_birth_time, st_ctime, st_mtime, st_atime) = if let Some(basic_info) = basic_info {
162+
(
163+
i64_to_time_t_nsec(basic_info.CreationTime),
164+
i64_to_time_t_nsec(basic_info.ChangeTime),
165+
i64_to_time_t_nsec(basic_info.LastWriteTime),
166+
i64_to_time_t_nsec(basic_info.LastAccessTime),
167+
)
168+
} else {
169+
(
170+
file_time_to_time_t_nsec(&info.ftCreationTime),
171+
(0, 0),
172+
file_time_to_time_t_nsec(&info.ftLastWriteTime),
173+
file_time_to_time_t_nsec(&info.ftLastAccessTime),
174+
)
175+
};
176+
let st_nlink = info.nNumberOfLinks as i32;
177+
178+
let st_ino = if let Some(id_info) = id_info {
179+
let file_id: [u64; 2] = unsafe { std::mem::transmute_copy(&id_info.FileId) };
180+
file_id
181+
} else {
182+
let ino = ((info.nFileIndexHigh as u64) << 32) + info.nFileIndexLow as u64;
183+
[ino, 0]
184+
};
185+
186+
if info.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT != 0
187+
&& reparse_tag == IO_REPARSE_TAG_SYMLINK
188+
{
189+
st_mode = (st_mode & !(S_IFMT as u16)) | (S_IFLNK as u16);
190+
}
191+
let st_file_attributes = info.dwFileAttributes;
192+
193+
StatStruct {
194+
st_dev,
195+
st_ino: st_ino[0],
196+
st_mode,
197+
st_nlink,
198+
st_uid: 0,
199+
st_gid: 0,
200+
st_rdev,
201+
st_size,
202+
st_atime: st_atime.0,
203+
st_atime_nsec: st_atime.1,
204+
st_mtime: st_mtime.0,
205+
st_mtime_nsec: st_mtime.1,
206+
st_ctime: st_ctime.0,
207+
st_ctime_nsec: st_ctime.1,
208+
st_birthtime: st_birth_time.0,
209+
st_birthtime_nsec: st_birth_time.1,
210+
st_file_attributes,
211+
st_reparse_tag: reparse_tag,
212+
st_ino_high: st_ino[1],
213+
}
214+
}
215+
216+
fn attributes_to_mode(attr: u32) -> libc::c_int {
217+
let mut m = 0;
218+
if attr & FILE_ATTRIBUTE_DIRECTORY != 0 {
219+
m |= libc::S_IFDIR | 0o111; // IFEXEC for user,group,other
220+
} else {
221+
m |= libc::S_IFREG;
222+
}
223+
if attr & FILE_ATTRIBUTE_READONLY != 0 {
224+
m |= 0o444;
225+
} else {
226+
m |= 0o666;
227+
}
228+
m
229+
}
230+
}

vm/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ mod dictdatatype;
5353
#[cfg(feature = "rustpython-compiler")]
5454
pub mod eval;
5555
pub mod exceptions;
56+
#[cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))]
57+
mod fileutils;
5658
pub mod format;
5759
pub mod frame;
5860
pub mod function;

vm/src/stdlib/os.rs

Lines changed: 16 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ pub(super) mod _os {
127127
common::lock::{OnceCell, PyRwLock},
128128
common::suppress_iph,
129129
convert::{IntoPyException, ToPyObject},
130+
fileutils::StatStruct,
130131
function::{ArgBytesLike, Either, FsPath, FuncArgs, OptionalArg},
131132
ospath::{IOErrorBuilder, OsPath, OsPathOrFd, OutputMode},
132133
protocol::PyIterReturn,
@@ -724,6 +725,8 @@ pub(super) mod _os {
724725
pub st_mtime_ns: i128,
725726
#[pyarg(any, default)]
726727
pub st_ctime_ns: i128,
728+
#[pyarg(any, default)]
729+
pub st_reparse_tag: u32,
727730
}
728731

729732
#[pyclass(with(PyStructSequence))]
@@ -753,6 +756,12 @@ pub(super) mod _os {
753756
const NANOS_PER_SEC: u32 = 1_000_000_000;
754757
let to_f64 = |(s, ns)| (s as f64) + (ns as f64) / (NANOS_PER_SEC as f64);
755758
let to_ns = |(s, ns)| s as i128 * NANOS_PER_SEC as i128 + ns as i128;
759+
760+
#[cfg(windows)]
761+
let st_reparse_tag = stat.st_reparse_tag;
762+
#[cfg(not(windows))]
763+
let st_reparse_tag = 0;
764+
756765
StatResult {
757766
st_mode: vm.ctx.new_pyref(stat.st_mode),
758767
st_ino: vm.ctx.new_pyref(stat.st_ino),
@@ -770,6 +779,7 @@ pub(super) mod _os {
770779
st_atime_ns: to_ns(atime),
771780
st_mtime_ns: to_ns(mtime),
772781
st_ctime_ns: to_ns(ctime),
782+
st_reparse_tag,
773783
}
774784
}
775785

@@ -800,26 +810,6 @@ pub(super) mod _os {
800810
}
801811
}
802812

803-
#[cfg(not(windows))]
804-
use libc::stat as StatStruct;
805-
806-
#[cfg(windows)]
807-
struct StatStruct {
808-
st_dev: libc::c_ulong,
809-
st_ino: u64,
810-
st_mode: libc::c_ushort,
811-
st_nlink: i32,
812-
st_uid: i32,
813-
st_gid: i32,
814-
st_size: u64,
815-
st_atime: libc::time_t,
816-
st_atime_nsec: i32,
817-
st_mtime: libc::time_t,
818-
st_mtime_nsec: i32,
819-
st_ctime: libc::time_t,
820-
st_ctime_nsec: i32,
821-
}
822-
823813
#[cfg(windows)]
824814
fn meta_to_stat(meta: &fs::Metadata) -> io::Result<StatStruct> {
825815
let st_mode = {
@@ -860,6 +850,7 @@ pub(super) mod _os {
860850
st_atime_nsec: nsec(atime),
861851
st_mtime_nsec: nsec(mtime),
862852
st_ctime_nsec: nsec(ctime),
853+
..Default::default()
863854
})
864855
}
865856

@@ -871,17 +862,11 @@ pub(super) mod _os {
871862
) -> io::Result<Option<StatStruct>> {
872863
// TODO: replicate CPython's win32_xstat
873864
let [] = dir_fd.0;
874-
let meta = match file {
875-
OsPathOrFd::Path(path) => super::fs_metadata(path, follow_symlinks.0)?,
876-
OsPathOrFd::Fd(fno) => {
877-
use std::os::windows::io::FromRawHandle;
878-
let handle = Fd(fno).to_raw_handle()?;
879-
let file =
880-
std::mem::ManuallyDrop::new(unsafe { std::fs::File::from_raw_handle(handle) });
881-
file.metadata()?
882-
}
883-
};
884-
meta_to_stat(&meta).map(Some)
865+
match file {
866+
OsPathOrFd::Path(path) => meta_to_stat(&super::fs_metadata(path, follow_symlinks.0)?),
867+
OsPathOrFd::Fd(fd) => crate::fileutils::fstat(fd),
868+
}
869+
.map(Some)
885870
}
886871

887872
#[cfg(not(windows))]

0 commit comments

Comments
 (0)
0