8000 fileutils and fstat · RustPython/RustPython@030b093 · GitHub
[go: up one dir, main page]

Skip to content

Commit 030b093

Browse files
committed
fileutils and fstat
1 parent 6a7be1e commit 030b093

File tree

3 files changed

+273
-31
lines changed

3 files changed

+273
-31
lines changed

vm/src/fileutils.rs

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

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