Skip to content

Commit a99164f

Browse files
authored
nt is_dir,is_file,listmount,listvolume (RustPython#6373)
* is_dir/is_file for windows * listmount/listvolume * check_env_var_len
1 parent cc534d2 commit a99164f

File tree

4 files changed

+126
-23
lines changed

4 files changed

+126
-23
lines changed

Lib/test/test_os.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1152,7 +1152,6 @@ def test_putenv_unsetenv(self):
11521152
self.assertEqual(proc.stdout.rstrip(), repr(None))
11531153

11541154
# On OS X < 10.6, unsetenv() doesn't return a value (bpo-13415).
1155-
@unittest.expectedFailureIfWindows('TODO: RUSTPYTHON; (AssertionError: ValueError not raised by putenv)')
11561155
@support.requires_mac_ver(10, 6)
11571156
def test_putenv_unsetenv_error(self):
11581157
# Empty variable name is invalid.

crates/common/src/windows.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ use std::{
33
os::windows::ffi::{OsStrExt, OsStringExt},
44
};
55

6+
/// _MAX_ENV from Windows CRT stdlib.h - maximum environment variable size
7+
pub const _MAX_ENV: usize = 32767;
8+
69
pub trait ToWideString {
710
fn to_wide(&self) -> Vec<u16>;
811
fn to_wide_with_nul(&self) -> Vec<u16>;

crates/vm/src/stdlib/nt.rs

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -682,6 +682,87 @@ pub(crate) mod module {
682682
Ok(vm.ctx.new_list(drives))
683683
}
684684

685+
#[pyfunction]
686+
fn listvolumes(vm: &VirtualMachine) -> PyResult<PyListRef> {
687+
use windows_sys::Win32::Foundation::ERROR_NO_MORE_FILES;
688+
689+
let mut result = Vec::new();
690+
let mut buffer = [0u16; Foundation::MAX_PATH as usize + 1];
691+
692+
let find = unsafe { FileSystem::FindFirstVolumeW(buffer.as_mut_ptr(), buffer.len() as _) };
693+
if find == INVALID_HANDLE_VALUE {
694+
return Err(errno_err(vm));
695+
}
696+
697+
loop {
698+
// Find the null terminator
699+
let len = buffer.iter().position(|&c| c == 0).unwrap_or(buffer.len());
700+
let volume = String::from_utf16_lossy(&buffer[..len]);
701+
result.push(vm.new_pyobj(volume));
702+
703+
let ret = unsafe {
704+
FileSystem::FindNextVolumeW(find, buffer.as_mut_ptr(), buffer.len() as _)
705+
};
706+
if ret == 0 {
707+
let err = io::Error::last_os_error();
708+
unsafe { FileSystem::FindVolumeClose(find) };
709+
if err.raw_os_error() == Some(ERROR_NO_MORE_FILES as i32) {
710+
break;
711+
}
712+
return Err(err.to_pyexception(vm));
713+
}
714+
}
715+
716+
Ok(vm.ctx.new_list(result))
717+
}
718+
719+
#[pyfunction]
720+
fn listmounts(volume: OsPath, vm: &VirtualMachine) -> PyResult<PyListRef> {
721+
use windows_sys::Win32::Foundation::ERROR_MORE_DATA;
722+
723+
let wide = volume.to_wide_cstring(vm)?;
724+
let mut buflen: u32 = Foundation::MAX_PATH + 1;
725+
let mut buffer: Vec<u16> = vec![0; buflen as usize];
726+
727+
loop {
728+
let success = unsafe {
729+
FileSystem::GetVolumePathNamesForVolumeNameW(
730+
wide.as_ptr(),
731+
buffer.as_mut_ptr(),
732+
buflen,
733+
&mut buflen,
734+
)
735+
};
736+
if success != 0 {
737+
break;
738+
}
739+
let err = io::Error::last_os_error();
740+
if err.raw_os_error() == Some(ERROR_MORE_DATA as i32) {
741+
buffer.resize(buflen as usize, 0);
742+
continue;
743+
}
744+
return Err(err.to_pyexception(vm));
745+
}
746+
747+
// Parse null-separated strings
748+
let mut result = Vec::new();
749+
let mut start = 0;
750+
for (i, &c) in buffer.iter().enumerate() {
751+
if c == 0 {
752+
if i > start {
753+
let mount = String::from_utf16_lossy(&buffer[start..i]);
754+
result.push(vm.new_pyobj(mount));
755+
}
756+
start = i + 1;
757+
if start < buffer.len() && buffer[start] == 0 {
758+
break; // Double null = end
759+
}
760+
}
761+
}
762+
763+
Ok(vm.ctx.new_list(result))
764+
}
765+
685766
#[pyfunction]
686767
fn set_handle_inheritable(
687768
handle: intptr_t,

crates/vm/src/stdlib/os.rs

Lines changed: 42 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -428,6 +428,20 @@ pub(super) mod _os {
428428
}
429429
}
430430

431+
/// Check if environment variable length exceeds Windows limit.
432+
/// size should be key.len() + value.len() + 2 (for '=' and null terminator)
433+
#[cfg(windows)]
434+
fn check_env_var_len(size: usize, vm: &VirtualMachine) -> PyResult<()> {
435+
use crate::common::windows::_MAX_ENV;
436+
if size > _MAX_ENV {
437+
return Err(vm.new_value_error(format!(
438+
"the environment variable is longer than {} characters",
439+
_MAX_ENV
440+
)));
441+
}
442+
Ok(())
443+
}
444+
431445
#[pyfunction]
432446
fn putenv(
433447
key: Either<PyStrRef, PyBytesRef>,
@@ -442,6 +456,8 @@ pub(super) mod _os {
442456
if key.is_empty() || key.contains(&b'=') {
443457
return Err(vm.new_value_error("illegal environment variable name"));
444458
}
459+
#[cfg(windows)]
460+
check_env_var_len(key.len() + value.len() + 2, vm)?;
445461
let key = super::bytes_as_os_str(key, vm)?;
446462
let value = super::bytes_as_os_str(value, vm)?;
447463
// SAFETY: requirements forwarded from the caller
@@ -464,6 +480,9 @@ pub(super) mod _os {
464480
),
465481
));
466482
}
483+
// For unsetenv, size is key + '=' (no value, just clearing)
484+
#[cfg(windows)]
485+
check_env_var_len(key.len() + 1, vm)?;
467486
let key = super::bytes_as_os_str(key, vm)?;
468487
// SAFETY: requirements forwarded from the caller
469488
unsafe { env::remove_var(key) };
@@ -507,17 +526,17 @@ pub(super) mod _os {
507526
Ok(self.mode.process_path(&self.pathval, vm))
508527
}
509528

510-
fn perform_on_metadata(
511-
&self,
512-
follow_symlinks: FollowSymlinks,
513-
action: fn(fs::Metadata) -> bool,
514-
vm: &VirtualMachine,
515-
) -> PyResult<bool> {
529+
#[pymethod]
530+
fn is_dir(&self, follow_symlinks: FollowSymlinks, vm: &VirtualMachine) -> PyResult<bool> {
516531
match super::fs_metadata(&self.pathval, follow_symlinks.0) {
517-
Ok(meta) => Ok(action(meta)),
532+
Ok(meta) => Ok(meta.is_dir()),
518533
Err(e) => {
519-
// FileNotFoundError is caught and not raised
520534
if e.kind() == io::ErrorKind::NotFound {
535+
// On Windows, use cached file_type when file is removed
536+
#[cfg(windows)]
537+
if let Ok(file_type) = &self.file_type {
538+
return Ok(file_type.is_dir());
539+
}
521540
Ok(false)
522541
} else {
523542
Err(e.into_pyexception(vm))
@@ -526,22 +545,23 @@ pub(super) mod _os {
526545
}
527546
}
528547

529-
#[pymethod]
530-
fn is_dir(&self, follow_symlinks: FollowSymlinks, vm: &VirtualMachine) -> PyResult<bool> {
531-
self.perform_on_metadata(
532-
follow_symlinks,
533-
|meta: fs::Metadata| -> bool { meta.is_dir() },
534-
vm,
535-
)
536-
}
537-
538548
#[pymethod]
539549
fn is_file(&self, follow_symlinks: FollowSymlinks, vm: &VirtualMachine) -> PyResult<bool> {
540-
self.perform_on_metadata(
541-
follow_symlinks,
542-
|meta: fs::Metadata| -> bool { meta.is_file() },
543-
vm,
544-
)
550+
match super::fs_metadata(&self.pathval, follow_symlinks.0) {
551+
Ok(meta) => Ok(meta.is_file()),
552+
Err(e) => {
553+
if e.kind() == io::ErrorKind::NotFound {
554+
// On Windows, use cached file_type when file is removed
555+
#[cfg(windows)]
556+
if let Ok(file_type) = &self.file_type {
557+
return Ok(file_type.is_file());
558+
}
559+
Ok(false)
560+
} else {
561+
Err(e.into_pyexception(vm))
562+
}
563+
}
564+
}
545565
}
546566

547567
#[pymethod]

0 commit comments

Comments
 (0)