1use crate::{builtins::PyModule, PyRef, VirtualMachine};
2
3pub use module::raw_set_handle_inheritable;
4
5pub(crate) fn make_module(vm: &VirtualMachine) -> PyRef<PyModule> {
6 let module = module::make_module(vm);
7 super::os::extend_module(vm, &module);
8 module
9}
10
11#[pymodule(name = "nt", with(super::os::_os))]
12pub(crate) mod module {
13 use crate::{
14 builtins::{PyDictRef, PyListRef, PyStrRef, PyTupleRef},
15 common::{crt_fd::Fd, os::last_os_error, suppress_iph},
16 convert::ToPyException,
17 function::{Either, OptionalArg},
18 ospath::OsPath,
19 stdlib::os::{errno_err, DirFd, FollowSymlinks, SupportFunc, TargetIsDirectory, _os},
20 PyResult, TryFromObject, VirtualMachine,
21 };
22 use libc::intptr_t;
23 use std::{
24 env, fs, io,
25 mem::MaybeUninit,
26 os::windows::ffi::{OsStrExt, OsStringExt},
27 };
28 use windows_sys::Win32::{
29 Foundation::{self, INVALID_HANDLE_VALUE},
30 Storage::FileSystem,
31 System::{Console, Threading},
32 };
33
34 #[pyattr]
35 use libc::{O_BINARY, O_TEMPORARY};
36
37 #[pyfunction]
38 pub(super) fn access(path: OsPath, mode: u8, vm: &VirtualMachine) -> PyResult<bool> {
39 let attr = unsafe { FileSystem::GetFileAttributesW(path.to_widecstring(vm)?.as_ptr()) };
40 Ok(attr != FileSystem::INVALID_FILE_ATTRIBUTES
41 && (mode & 2 == 0
42 || attr & FileSystem::FILE_ATTRIBUTE_READONLY == 0
43 || attr & FileSystem::FILE_ATTRIBUTE_DIRECTORY != 0))
44 }
45
46 #[derive(FromArgs)]
47 pub(super) struct SymlinkArgs {
48 src: OsPath,
49 dst: OsPath,
50 #[pyarg(flatten)]
51 target_is_directory: TargetIsDirectory,
52 #[pyarg(flatten)]
53 _dir_fd: DirFd<{ _os::SYMLINK_DIR_FD as usize }>,
54 }
55
56 #[pyfunction]
57 pub(super) fn symlink(args: SymlinkArgs, vm: &VirtualMachine) -> PyResult<()> {
58 use std::os::windows::fs as win_fs;
59 let dir = args.target_is_directory.target_is_directory
60 || args
61 .dst
62 .as_path()
63 .parent()
64 .and_then(|dst_parent| dst_parent.join(&args.src).symlink_metadata().ok())
65 .map_or(false, |meta| meta.is_dir());
66 let res = if dir {
67 win_fs::symlink_dir(args.src.path, args.dst.path)
68 } else {
69 win_fs::symlink_file(args.src.path, args.dst.path)
70 };
71 res.map_err(|err| err.to_pyexception(vm))
72 }
73
74 #[pyfunction]
75 fn set_inheritable(fd: i32, inheritable: bool, vm: &VirtualMachine) -> PyResult<()> {
76 let handle = Fd(fd).to_raw_handle().map_err(|e| e.to_pyexception(vm))?;
77 set_handle_inheritable(handle as _, inheritable, vm)
78 }
79
80 #[pyattr]
81 fn environ(vm: &VirtualMachine) -> PyDictRef {
82 let environ = vm.ctx.new_dict();
83
84 for (key, value) in env::vars() {
85 environ.set_item(&key, vm.new_pyobj(value), vm).unwrap();
86 }
87 environ
88 }
89
90 #[pyfunction]
91 fn chmod(
92 path: OsPath,
93 dir_fd: DirFd<0>,
94 mode: u32,
95 follow_symlinks: FollowSymlinks,
96 vm: &VirtualMachine,
97 ) -> PyResult<()> {
98 const S_IWRITE: u32 = 128;
99 let [] = dir_fd.0;
100 let metadata = if follow_symlinks.0 {
101 fs::metadata(&path)
102 } else {
103 fs::symlink_metadata(&path)
104 };
105 let meta = metadata.map_err(|err| err.to_pyexception(vm))?;
106 let mut permissions = meta.permissions();
107 permissions.set_readonly(mode & S_IWRITE == 0);
108 fs::set_permissions(&path, permissions).map_err(|err| err.to_pyexception(vm))
109 }
110
111 #[cfg(target_env = "msvc")]
113 extern "C" {
114 fn _cwait(termstat: *mut i32, procHandle: intptr_t, action: i32) -> intptr_t;
115 }
116
117 #[cfg(target_env = "msvc")]
118 #[pyfunction]
119 fn waitpid(pid: intptr_t, opt: i32, vm: &VirtualMachine) -> PyResult<(intptr_t, i32)> {
120 let mut status = 0;
121 let pid = unsafe { suppress_iph!(_cwait(&mut status, pid, opt)) };
122 if pid == -1 {
123 Err(errno_err(vm))
124 } else {
125 Ok((pid, status << 8))
126 }
127 }
128
129 #[cfg(target_env = "msvc")]
130 #[pyfunction]
131 fn wait(vm: &VirtualMachine) -> PyResult<(intptr_t, i32)> {
132 waitpid(-1, 0, vm)
133 }
134
135 #[pyfunction]
136 fn kill(pid: i32, sig: isize, vm: &VirtualMachine) -> PyResult<()> {
137 let sig = sig as u32;
138 let pid = pid as u32;
139
140 if sig == Console::CTRL_C_EVENT || sig == Console::CTRL_BREAK_EVENT {
141 let ret = unsafe { Console::GenerateConsoleCtrlEvent(sig, pid) };
142 let res = if ret == 0 { Err(errno_err(vm)) } else { Ok(()) };
143 return res;
144 }
145
146 let h = unsafe { Threading::OpenProcess(Threading::PROCESS_ALL_ACCESS, 0, pid) };
147 if h == 0 {
148 return Err(errno_err(vm));
149 }
150 let ret = unsafe { Threading::TerminateProcess(h, sig) };
151 let res = if ret == 0 { Err(errno_err(vm)) } else { Ok(()) };
152 unsafe { Foundation::CloseHandle(h) };
153 res
154 }
155
156 #[pyfunction]
157 fn get_terminal_size(
158 fd: OptionalArg<i32>,
159 vm: &VirtualMachine,
160 ) -> PyResult<_os::PyTerminalSize> {
161 let (columns, lines) = {
162 let stdhandle = match fd {
163 OptionalArg::Present(0) => Console::STD_INPUT_HANDLE,
164 OptionalArg::Present(1) | OptionalArg::Missing => Console::STD_OUTPUT_HANDLE,
165 OptionalArg::Present(2) => Console::STD_ERROR_HANDLE,
166 _ => return Err(vm.new_value_error("bad file descriptor".to_owned())),
167 };
168 let h = unsafe { Console::GetStdHandle(stdhandle) };
169 if h == 0 {
170 return Err(vm.new_os_error("handle cannot be retrieved".to_owned()));
171 }
172 if h == INVALID_HANDLE_VALUE {
173 return Err(errno_err(vm));
174 }
175 let mut csbi = MaybeUninit::uninit();
176 let ret = unsafe { Console::GetConsoleScreenBufferInfo(h, csbi.as_mut_ptr()) };
177 let csbi = unsafe { csbi.assume_init() };
178 if ret == 0 {
179 return Err(errno_err(vm));
180 }
181 let w = csbi.srWindow;
182 (
183 (w.Right - w.Left + 1) as usize,
184 (w.Bottom - w.Top + 1) as usize,
185 )
186 };
187 Ok(_os::PyTerminalSize { columns, lines })
188 }
189
190 #[cfg(target_env = "msvc")]
191 extern "C" {
192 fn _wexecv(cmdname: *const u16, argv: *const *const u16) -> intptr_t;
193 }
194
195 #[cfg(target_env = "msvc")]
196 #[pyfunction]
197 fn execv(
198 path: PyStrRef,
199 argv: Either<PyListRef, PyTupleRef>,
200 vm: &VirtualMachine,
201 ) -> PyResult<()> {
202 use std::iter::once;
203
204 let make_widestring =
205 |s: &str| widestring::WideCString::from_os_str(s).map_err(|err| err.to_pyexception(vm));
206
207 let path = make_widestring(path.as_str())?;
208
209 let argv = vm.extract_elements_with(argv.as_ref(), |obj| {
210 let arg = PyStrRef::try_from_object(vm, obj)?;
211 make_widestring(arg.as_str())
212 })?;
213
214 let first = argv
215 .first()
216 .ok_or_else(|| vm.new_value_error("execv() arg 2 must not be empty".to_owned()))?;
217
218 if first.is_empty() {
219 return Err(
220 vm.new_value_error("execv() arg 2 first element cannot be empty".to_owned())
221 );
222 }
223
224 let argv_execv: Vec<*const u16> = argv
225 .iter()
226 .map(|v| v.as_ptr())
227 .chain(once(std::ptr::null()))
228 .collect();
229
230 if (unsafe { suppress_iph!(_wexecv(path.as_ptr(), argv_execv.as_ptr())) } == -1) {
231 Err(errno_err(vm))
232 } else {
233 Ok(())
234 }
235 }
236
237 #[pyfunction]
238 fn _getfinalpathname(path: OsPath, vm: &VirtualMachine) -> PyResult {
239 let real = path
240 .as_ref()
241 .canonicalize()
242 .map_err(|e| e.to_pyexception(vm))?;
243 path.mode.process_path(real, vm)
244 }
245
246 #[pyfunction]
247 fn _getfullpathname(path: OsPath, vm: &VirtualMachine) -> PyResult {
248 let wpath = path.to_widecstring(vm)?;
249 let mut buffer = vec![0u16; Foundation::MAX_PATH as usize];
250 let ret = unsafe {
251 FileSystem::GetFullPathNameW(
252 wpath.as_ptr(),
253 buffer.len() as _,
254 buffer.as_mut_ptr(),
255 std::ptr::null_mut(),
256 )
257 };
258 if ret == 0 {
259 return Err(errno_err(vm));
260 }
261 if ret as usize > buffer.len() {
262 buffer.resize(ret as usize, 0);
263 let ret = unsafe {
264 FileSystem::GetFullPathNameW(
265 wpath.as_ptr(),
266 buffer.len() as _,
267 buffer.as_mut_ptr(),
268 std::ptr::null_mut(),
269 )
270 };
271 if ret == 0 {
272 return Err(errno_err(vm));
273 }
274 }
275 let buffer = widestring::WideCString::from_vec_truncate(buffer);
276 path.mode.process_path(buffer.to_os_string(), vm)
277 }
278
279 #[pyfunction]
280 fn _getvolumepathname(path: OsPath, vm: &VirtualMachine) -> PyResult {
281 let wide = path.to_widecstring(vm)?;
282 let buflen = std::cmp::max(wide.len(), Foundation::MAX_PATH as usize);
283 let mut buffer = vec![0u16; buflen];
284 let ret = unsafe {
285 FileSystem::GetVolumePathNameW(wide.as_ptr(), buffer.as_mut_ptr(), buflen as _)
286 };
287 if ret == 0 {
288 return Err(errno_err(vm));
289 }
290 let buffer = widestring::WideCString::from_vec_truncate(buffer);
291 path.mode.process_path(buffer.to_os_string(), vm)
292 }
293
294 #[pyfunction]
295 fn _path_splitroot(path: OsPath, vm: &VirtualMachine) -> PyResult<(String, String)> {
296 let orig: Vec<_> = path.path.encode_wide().collect();
297 if orig.is_empty() {
298 return Ok(("".to_owned(), "".to_owned()));
299 }
300 let backslashed: Vec<_> = orig
301 .iter()
302 .copied()
303 .map(|c| if c == b'/' as u16 { b'\\' as u16 } else { c })
304 .chain(std::iter::once(0)) .collect();
306
307 fn from_utf16(wstr: &[u16], vm: &VirtualMachine) -> PyResult<String> {
308 String::from_utf16(wstr).map_err(|e| vm.new_unicode_decode_error(e.to_string()))
309 }
310
311 let wbuf = windows::core::PCWSTR::from_raw(backslashed.as_ptr());
312 let (root, path) = match unsafe { windows::Win32::UI::Shell::PathCchSkipRoot(wbuf) } {
313 Ok(end) => {
314 assert!(!end.is_null());
315 let len: usize = unsafe { end.as_ptr().offset_from(wbuf.as_ptr()) }
316 .try_into()
317 .expect("len must be non-negative");
318 assert!(
319 len < backslashed.len(), "path: {:?} {} < {}",
321 std::path::PathBuf::from(std::ffi::OsString::from_wide(&backslashed)),
322 len,
323 backslashed.len()
324 );
325 (from_utf16(&orig[..len], vm)?, from_utf16(&orig[len..], vm)?)
326 }
327 Err(_) => ("".to_owned(), from_utf16(&orig, vm)?),
328 };
329 Ok((root, path))
330 }
331
332 #[pyfunction]
333 fn _getdiskusage(path: OsPath, vm: &VirtualMachine) -> PyResult<(u64, u64)> {
334 use FileSystem::GetDiskFreeSpaceExW;
335
336 let wpath = path.to_widecstring(vm)?;
337 let mut _free_to_me: u64 = 0;
338 let mut total: u64 = 0;
339 let mut free: u64 = 0;
340 let ret =
341 unsafe { GetDiskFreeSpaceExW(wpath.as_ptr(), &mut _free_to_me, &mut total, &mut free) };
342 if ret != 0 {
343 return Ok((total, free));
344 }
345 let err = io::Error::last_os_error();
346 if err.raw_os_error() == Some(Foundation::ERROR_DIRECTORY as i32) {
347 if let Some(parent) = path.as_ref().parent() {
348 let parent = widestring::WideCString::from_os_str(parent).unwrap();
349
350 let ret = unsafe {
351 GetDiskFreeSpaceExW(parent.as_ptr(), &mut _free_to_me, &mut total, &mut free)
352 };
353
354 return if ret == 0 {
355 Err(errno_err(vm))
356 } else {
357 Ok((total, free))
358 };
359 }
360 }
361 Err(err.to_pyexception(vm))
362 }
363
364 #[pyfunction]
365 fn get_handle_inheritable(handle: intptr_t, vm: &VirtualMachine) -> PyResult<bool> {
366 let mut flags = 0;
367 if unsafe { Foundation::GetHandleInformation(handle as _, &mut flags) } == 0 {
368 Err(errno_err(vm))
369 } else {
370 Ok(flags & Foundation::HANDLE_FLAG_INHERIT != 0)
371 }
372 }
373
374 pub fn raw_set_handle_inheritable(handle: intptr_t, inheritable: bool) -> std::io::Result<()> {
375 let flags = if inheritable {
376 Foundation::HANDLE_FLAG_INHERIT
377 } else {
378 0
379 };
380 let res = unsafe {
381 Foundation::SetHandleInformation(handle as _, Foundation::HANDLE_FLAG_INHERIT, flags)
382 };
383 if res == 0 {
384 Err(last_os_error())
385 } else {
386 Ok(())
387 }
388 }
389
390 #[pyfunction]
391 fn listdrives(vm: &VirtualMachine) -> PyResult<PyListRef> {
392 use windows_sys::Win32::Foundation::ERROR_MORE_DATA;
393
394 let mut buffer = [0u16; 256];
395 let len =
396 unsafe { FileSystem::GetLogicalDriveStringsW(buffer.len() as _, buffer.as_mut_ptr()) };
397 if len == 0 {
398 return Err(errno_err(vm));
399 }
400 if len as usize >= buffer.len() {
401 return Err(std::io::Error::from_raw_os_error(ERROR_MORE_DATA as _).to_pyexception(vm));
402 }
403 let drives: Vec<_> = buffer[..(len - 1) as usize]
404 .split(|&c| c == 0)
405 .map(|drive| vm.new_pyobj(String::from_utf16_lossy(drive)))
406 .collect();
407 Ok(vm.ctx.new_list(drives))
408 }
409
410 #[pyfunction]
411 fn set_handle_inheritable(
412 handle: intptr_t,
413 inheritable: bool,
414 vm: &VirtualMachine,
415 ) -> PyResult<()> {
416 raw_set_handle_inheritable(handle, inheritable).map_err(|e| e.to_pyexception(vm))
417 }
418
419 #[pyfunction]
420 fn mkdir(
421 path: OsPath,
422 mode: OptionalArg<i32>,
423 dir_fd: DirFd<{ _os::MKDIR_DIR_FD as usize }>,
424 vm: &VirtualMachine,
425 ) -> PyResult<()> {
426 let mode = mode.unwrap_or(0o777);
427 let [] = dir_fd.0;
428 let _ = mode;
429 let wide = path.to_widecstring(vm)?;
430 let res = unsafe { FileSystem::CreateDirectoryW(wide.as_ptr(), std::ptr::null_mut()) };
431 if res == 0 {
432 return Err(errno_err(vm));
433 }
434 Ok(())
435 }
436
437 pub(crate) fn support_funcs() -> Vec<SupportFunc> {
438 Vec::new()
439 }
440}