Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions crates/host_env/src/crt_fd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -355,9 +355,8 @@ pub fn ftruncate(fd: Borrowed<'_>, len: Offset) -> io::Result<()> {
cfg_select! {
windows => {
if ret != 0 {
// _chsize_s returns errno directly, convert to Windows error code
let winerror = crate::os::errno_to_winerror(ret);
return Err(io::Error::from_raw_os_error(winerror));
// _chsize_s returns errno directly; preserve it exactly.
return Err(crate::os::io_error_from_errno(ret));
}
}
_ => cvt(ret)?,
Expand Down
14 changes: 9 additions & 5 deletions crates/host_env/src/fileutils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,11 +94,15 @@ pub mod windows {

// _Py_fstat_noraise in cpython
pub fn fstat(fd: crt_fd::Borrowed<'_>) -> std::io::Result<StatStruct> {
let h = crt_fd::as_handle(fd);
if h.is_err() {
unsafe { SetLastError(ERROR_INVALID_HANDLE) };
}
let h = h?;
let h = match crt_fd::as_handle(fd) {
Ok(h) => h,
Err(_) => {
// An invalid fd is reported as a Win32 handle error so the
// OSError carries winerror = ERROR_INVALID_HANDLE.
unsafe { SetLastError(ERROR_INVALID_HANDLE) };
return Err(std::io::Error::last_os_error());
}
};
let h = h.as_raw_handle();
// reset stat?

Expand Down
46 changes: 43 additions & 3 deletions crates/host_env/src/os.rs
Original file line number Diff line number Diff line change
Expand Up @@ -340,11 +340,53 @@ impl ErrorExt for io::Error {
}
#[cfg(windows)]
fn posix_errno(&self) -> i32 {
// A C runtime error carries its exact errno as the payload; report it
// directly instead of round-tripping through a Win32 error code.
if let Some(crt) = self.get_ref().and_then(|e| e.downcast_ref::<CrtErrno>()) {
return crt.0;
}
let winerror = self.raw_os_error().unwrap_or(0);
winerror_to_errno(winerror)
}
}

/// Wraps a raw C runtime `errno` inside an [`io::Error`].
///
/// CRT functions (`open`, `read`, `dup`, ...) report failures through `errno`,
/// not `GetLastError`. Translating that `errno` into a Win32 error code is
/// lossy — any value missing from [`errno_to_winerror`] collapses to `EINVAL` —
/// and also attaches a spurious `winerror` to the resulting `OSError`. Carrying
/// the `errno` as the error payload lets [`ErrorExt::posix_errno`] recover it
/// exactly while leaving `raw_os_error()` empty, so no `winerror` is reported.
#[cfg(windows)]
#[derive(Debug)]
struct CrtErrno(i32);

#[cfg(windows)]
impl core::fmt::Display for CrtErrno {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match crate::errno::strerror_string(self.0) {
Some(msg) => f.write_str(&msg),
None => write!(f, "os error {}", self.0),
}
}
}

#[cfg(windows)]
impl core::error::Error for CrtErrno {}

/// Build an [`io::Error`] that preserves a raw C runtime `errno`.
///
/// The [`io::ErrorKind`] is derived from the closest Win32 mapping so callers
/// matching on `kind()` keep working, while the exact `errno` is preserved for
/// [`ErrorExt::posix_errno`].
#[cfg(windows)]
#[must_use]
pub fn io_error_from_errno(errno: i32) -> io::Error {
let kind = io::Error::from_raw_os_error(errno_to_winerror(errno)).kind();
io::Error::new(kind, CrtErrno(errno))
}

#[cfg(all(not(windows), not(target_arch = "wasm32")))]
impl ErrorExt for rustix::io::Errno {
fn posix_errno(&self) -> i32 {
Expand All @@ -357,9 +399,7 @@ impl ErrorExt for rustix::io::Errno {
#[cfg(windows)]
#[must_use]
pub fn errno_io_error() -> io::Error {
let errno: i32 = get_errno();
let winerror = errno_to_winerror(errno);
io::Error::from_raw_os_error(winerror)
io_error_from_errno(get_errno())
}

#[cfg(not(windows))]
Expand Down
39 changes: 26 additions & 13 deletions crates/vm/src/getpath.rs
Original file line number Diff line number Diff line change
Expand Up @@ -180,17 +180,30 @@ pub fn init_path_config(settings: &Settings) -> Paths {
paths
}

/// Get default prefix value
fn default_prefix() -> String {
std::option_env!("RUSTPYTHON_PREFIX")
.map(String::from)
.unwrap_or_else(|| {
if cfg!(windows) {
"C:".to_owned()
} else {
"/usr/local".to_owned()
}
})
/// Get default prefix value used when landmark search fails.
///
/// A compile-time `RUSTPYTHON_PREFIX` always wins. Otherwise POSIX uses the
/// conventional install prefix, while Windows has no meaningful compile-time
/// prefix and falls back to the executable's directory (ref: getpath.py).
///
/// A bare drive root must never be returned on Windows: pip walks up from
/// `<prefix>/Lib/site-packages` and would otherwise probe the drive root for
/// writability, which fails for standard users (see issue #8246).
fn default_prefix(exe_dir: Option<&PathBuf>) -> String {
if let Some(prefix) = std::option_env!("RUSTPYTHON_PREFIX") {
return prefix.to_owned();
}

if cfg!(windows) {
if let Some(dir) = exe_dir {
return dir.to_string_lossy().into_owned();
}
// Executable directory is unknown; use a valid absolute root as a last
// resort rather than a drive-relative bare "C:".
"C:\\".to_owned()
} else {
"/usr/local".to_owned()
}
}

/// Detect virtual environment by looking for pyvenv.cfg
Expand Down Expand Up @@ -262,7 +275,7 @@ fn calculate_prefix(exe_dir: Option<&PathBuf>, build_prefix: Option<&PathBuf>) -
}

// 4. Fallback to default
default_prefix()
default_prefix(exe_dir)
}

/// Calculate exec_prefix
Expand Down Expand Up @@ -410,7 +423,7 @@ mod tests {

#[test]
fn default_prefix_basic() {
let prefix = default_prefix();
let prefix = default_prefix(None);
assert!(!prefix.is_empty());
}
}
Loading