Skip to content

Windows: msvcrt, winreg follow-ups (os.utime, text-mode CRLF) - #806

Merged
youknowone merged 3 commits into
mainfrom
nt-followups
Jul 26, 2026
Merged

Windows: msvcrt, winreg follow-ups (os.utime, text-mode CRLF)#806
youknowone merged 3 commits into
mainfrom
nt-followups

Conversation

@youknowone

@youknowone youknowone commented Jul 26, 2026

Copy link
Copy Markdown
Owner

Follow-ups to the nt module (#780).

  • posix: implement os.utime (was a no-op stub) — parses (path, times=None, *, ns=None, dir_fd=None, follow_symlinks=True), converts times/ns to a Duration (both None = now), and calls host_env set_file_times (Windows) / set_file_times_at (unix).
  • _io: text-mode writes translate \n to the platform newline when newline=None (Windows \r\n), matching the writenl rule; explicit \r/\r\n were already handled, ''/\n stay verbatim.
  • msvcrt: new module over host_env::msvcrt (getch/getwch family, putch, ungetch, kbhit, locking, setmode, open_osfhandle, get_osfhandle, heapmin, SetErrorMode).
  • winreg: real key operations over host_env::winreg behind a PyHKEY handle object; predefined HKEY_* roots expose the full sign-extended pointer values.
  • _winapi: add the process/priority/GetStdHandle constants and CloseHandle/WaitForSingleObject/GetExitCodeProcess that subprocess captures as default args at import once msvcrt exists.

All OS bindings go through rustpython_host_env. Verified locally: check.py --backend dynasm 322/322, JIT on/off smoke test for all four features passes.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added Windows support for msvcrt, including console, file-descriptor, locking, and error-mode operations.
    • Expanded Windows registry functionality with key management, value queries, enumeration, and updates.
    • Improved Windows subprocess compatibility and process-handle operations.
    • Added os.utime support for updating file timestamps.
  • Bug Fixes

    • Corrected newline handling so platform line endings are applied consistently, including Windows defaults.

Replace the no-op stub with a real utime: parse
(path, times=None, *, ns=None, dir_fd=None, follow_symlinks=True),
convert times/ns to a Duration (both None means now), and call
host_env set_file_times on Windows / set_file_times_at on unix.
TextIOWrapper.write now substitutes the write newline for '\n' when
newline is None (Windows: '\r\n'; unix: no translation), matching the
writenl rule. Explicit '\r'/'\r\n' were already handled; '' and '\n'
stay verbatim.
… import

msvcrt: new module over host_env::msvcrt (getch/getwch family, putch,
ungetch, kbhit, locking, setmode, open_osfhandle, get_osfhandle,
heapmin, SetErrorMode; LK_*/SEM_* constants).

winreg: install real key operations over host_env::winreg behind a
PyHKEY handle object (Open/Create/Close/Enum/Query/Set/Delete key and
value, connect_registry, expand_environment_strings, reflection). The
predefined HKEY_* roots expose the full sign-extended pointer values.

_winapi: add the process/priority/GetStdHandle constants and the
CloseHandle/WaitForSingleObject/GetExitCodeProcess functions that
subprocess captures as default arguments at import once msvcrt exists.

Cargo: enable windows-sys Win32_System_Threading and add the widestring
dependency the winreg wrappers take.
@coderabbitai

coderabbitai Bot commented Jul 26, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

The interpreter adds Windows msvcrt and expanded _winapi support, implements host-enabled Win32 registry bindings, replaces the os.utime stub with platform-aware behavior, and updates text newline translation.

Changes

Platform standard-library support

Layer / File(s) Summary
Windows runtime modules and registration
pyre/pyre-interpreter/Cargo.toml, pyre/pyre-interpreter/src/importing.rs, pyre/pyre-interpreter/src/module/_winapi/mod.rs, pyre/pyre-interpreter/src/module/mod.rs, pyre/pyre-interpreter/src/module/msvcrt/mod.rs
Windows dependencies and builtin registration now include msvcrt; CRT bindings and additional _winapi constants and functions are exported.
Windows registry bindings
pyre/pyre-interpreter/src/module/winreg/mod.rs
Host-enabled winreg support adds PyHKEY, registry value conversion, Win32 registry operations, predefined handles, and module wiring.
Filesystem timestamp updates
pyre/pyre-interpreter/src/module/posix/interp_posix.rs
os.utime now parses timestamp arguments and dispatches to Windows or Unix file-time backends.
Text newline translation
pyre/pyre-interpreter/src/module/_io/textio.rs
TextIOWrapper centralizes newline selection and applies Windows translation for newline=None.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant PythonCaller
  participant WinregModule
  participant HostWinreg
  PythonCaller->>WinregModule: OpenKey or SetValueEx
  WinregModule->>HostWinreg: invoke Win32 registry operation
  HostWinreg-->>WinregModule: return handle, data, or error
  WinregModule-->>PythonCaller: return PyHKEY, converted value, or PyError
Loading

Possibly related PRs

  • youknowone/pyre#780: Both changes extend the Windows winreg builtin and its bootstrap availability.

Poem

A bunny hops through Windows bright,
With registry keys tucked neat and right.
CRT bells ring, newlines bend,
File times shift from start to end.
“Hop hop!” says Hare, “the modules align!”

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly captures the main Windows follow-up work, including msvcrt, winreg, os.utime, and text-mode CRLF changes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch nt-followups

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d48c45790e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +317 to +318
host_reg::REG_MULTI_SZ => {
let len = unsafe { w_list_len(value) };

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Validate REG_MULTI_SZ values before list access

When SetValueEx receives REG_MULTI_SZ with None or any non-list value, this calls the unsafe w_list_len accessor on an object that is not a W_ListObject; its documented precondition is violated, so malformed Python input can cause invalid layout reads or crash the interpreter instead of raising TypeError (and None should encode an empty value). Check the value type before using list internals.

AGENTS.md reference: AGENTS.md:L194-L195

Useful? React with 👍 / 👎.

Comment on lines +311 to +315
host_reg::REG_SZ | host_reg::REG_EXPAND_SZ => {
if unsafe { !is_str(value) } {
return Err(crate::PyError::type_error("value must be a string"));
}
Ok(encode_utf16z(unsafe { w_str_get_value(value) }))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Encode surrogate-bearing registry strings without panicking

For a REG_SZ or REG_EXPAND_SZ value containing a lone surrogate, w_str_get_value panics because the backing WTF-8 is not valid Rust UTF-8; such values are explicitly exercised by the upstream winreg tests and must round-trip with surrogate-preserving UTF-16 rather than terminating the interpreter. Use the surrogate-aware WTF-8 accessor/conversion here.

AGENTS.md reference: AGENTS.md:L194-L195

Useful? React with 👍 / 👎.

Comment on lines +308 to +310
host_reg::REG_QWORD => Ok((crate::baseobjspace::int_w(value)? as u64)
.to_le_bytes()
.to_vec()),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve the full unsigned REG_QWORD range

Valid registry QWORDs above i64::MAX, such as the upstream test value 0xFEDCBA9876543210, cannot be written because int_w raises OverflowError; the paired read path also casts these values to i64 and returns a negative Python integer. Convert through an unsigned 64-bit/bigint representation so the complete REG_QWORD range round-trips.

AGENTS.md reference: AGENTS.md:L194-L195

Useful? React with 👍 / 👎.

Comment on lines +278 to +280
for chunk in units.split(|&c| c == 0) {
if chunk.is_empty() {
break;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Retain empty strings in REG_MULTI_SZ values

A valid REG_MULTI_SZ may contain empty elements, but this loop treats the first empty chunk as the terminator; consequently values such as ["", "", ""], covered by both bundled winreg test suites, are read back as an empty list. Only the final terminator should be removed while preserving interior and leading empty strings.

AGENTS.md reference: AGENTS.md:L194-L195

Useful? React with 👍 / 👎.

Comment on lines +104 to +106
crate::py_class! {
"PyHKEY",
methods: {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Close PyHKEY handles when objects are finalized

Handles returned by OpenKey, CreateKey, and ConnectRegistry are never closed when their wrapper becomes unreachable because this class registers no finalizer; code that relies on normal PyHKEY ownership semantics will therefore leak one Windows registry handle per object and can exhaust handles in a long-running process. The corresponding PyPy W_HKEY registers _finalize_ to call Close, so the port needs equivalent automatic cleanup.

AGENTS.md reference: AGENTS.md:L194-L195

Useful? React with 👍 / 👎.

pos.len()
)));
}
crate::builtins::kwarg_reject_unknown(kwargs, &["ns", "dir_fd", "follow_symlinks"], "utime")?;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Accept the times keyword in os.utime

The public signature allows os.utime(path, times=(atime, mtime)), and the bundled test_os.py exercises exactly that call, but times is omitted from the accepted keyword list and is rejected as unexpected before parsing. Add it as a positional-or-keyword slot while still detecting duplicate positional and keyword values.

AGENTS.md reference: AGENTS.md:L194-L195

Useful? React with 👍 / 👎.

Comment on lines +1437 to +1441
let dur_from_secs = |v: PyObjectRef| -> Result<std::time::Duration, crate::PyError> {
let f = crate::builtins::builtin_float(&[v])?;
let secs = unsafe { pyre_object::w_float_get_value(f) };
std::time::Duration::try_from_secs_f64(secs)
.map_err(|_| crate::PyError::value_error("utime: timestamp out of range"))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Support timestamps before the Unix epoch

When callers set a legitimate pre-1970 timestamp, Duration::try_from_secs_f64 rejects the negative value (and the ns path explicitly rejects negative nanoseconds), although os.utime and the underlying signed timespec support dates before the epoch. Represent timestamps as signed seconds plus nanoseconds, as the corresponding PyPy conversion does, rather than as an unsigned Duration.

AGENTS.md reference: AGENTS.md:L194-L195

Useful? React with 👍 / 👎.

@github-actions

Copy link
Copy Markdown

🤖 Codex parity review

Static analysis of this diff vs the local RPython/PyPy sources (commit d48c457).
Updated: 2026-07-26T00:38:04.682Z

Files in the reviewed diff
Cargo.lock
pyre/pyre-interpreter/Cargo.toml
pyre/pyre-interpreter/src/importing.rs
pyre/pyre-interpreter/src/module/_io/textio.rs
pyre/pyre-interpreter/src/module/_winapi/mod.rs
pyre/pyre-interpreter/src/module/mod.rs
pyre/pyre-interpreter/src/module/msvcrt/mod.rs
pyre/pyre-interpreter/src/module/posix/interp_posix.rs
pyre/pyre-interpreter/src/module/winreg/mod.rs

1. Regressions to PyPy parity introduced by this patch

None.

2. Other mismatches introduced by this patch

  • pyre/pyre-interpreter/src/module/winreg/mod.rs:598 ↔ pypy/module/_winreg/moduledef.py:47: exports "PyHKEY" rather than PyPy’s public "HKEYType". The Rust type also has no one-integer constructor, handle property, or __repr__; PyPy defines all of these at interp_winreg.py:97-175.

  • pyre/pyre-interpreter/src/module/winreg/mod.rs:104 ↔ pypy/module/_winreg/interp_winreg.py:78-89: PyHKEY stores a raw handle in an instance dictionary but has no finalizer. PyPy registers a finalizer which closes an unclosed registry handle; Rust leaks handles unless callers explicitly close or context-manage them.

  • pyre/pyre-interpreter/src/module/winreg/mod.rs:613 ↔ pypy/module/_winreg/moduledef.py:52-72: the installed API omits CreateKeyEx, DeleteKeyEx, LoadKey, and SaveKey, all exported by PyPy’s module definition.

  • pyre/pyre-interpreter/src/module/winreg/mod.rs:260-283 ↔ pypy/module/_winreg/interp_winreg.py:506-550: REG_QWORD is converted through value as i64, turning values above 2^63-1 negative; PyPy creates an unsigned Python integer. The Rust branch also treats REG_LINK as text and decodes UTF-16 with replacement characters, while PyPy treats REG_LINK as raw binary and decodes text using surrogatepass.

  • pyre/pyre-interpreter/src/module/winreg/mod.rs:304-334 ↔ pypy/module/_winreg/interp_winreg.py:390-481: py2reg rejects None for REG_DWORD, REG_QWORD, REG_SZ, REG_EXPAND_SZ, and REG_MULTI_SZ. PyPy accepts None for each and writes the corresponding zero/empty representation.

  • pyre/pyre-interpreter/src/module/winreg/mod.rs:351-365 ↔ pypy/module/_winreg/interp_winreg.py:761-793: OpenKey ignores positional reserved and always passes 0; it also does not implement the corresponding keyword/signature handling. PyPy forwards the supplied reserved and access values.

  • pyre/pyre-interpreter/src/module/winreg/mod.rs:227-232 ↔ pypy/module/_winreg/interp_winreg.py:70-75: Win32 failures are converted to a generic errno-based OSError, losing the Win32 code and winerror. PyPy raises WindowsError with the original registry error code in both errno and winerror positions.

  • pyre/pyre-interpreter/src/module/posix/interp_posix.rs:1437-1448 ↔ pypy/module/posix/interp_posix.py:1992-2010: utime uses unsigned Duration, rejecting all pre-epoch times/ns values. PyPy splits signed seconds and normalized nanoseconds, so negative timestamps work.

  • pyre/pyre-interpreter/src/module/posix/interp_posix.rs:1402 ↔ pypy/module/posix/interp_posix.py:1859-1864: utime rejects times= as an unknown keyword and accepts only the positional second argument. PyPy’s gateway signature exposes w_times as a normal optional parameter.

  • pyre/pyre-interpreter/src/module/posix/interp_posix.rs:1495 ↔ pypy/module/posix/interp_posix.py:1957-1964: utime attaches the path to syscall errors through io_err. PyPy deliberately uses the no-path error wrapper so an invalid timestamp is not misreported as a path failure.

3. Pre-existing mismatches (already present before this patch)

  • pyre/pyre-interpreter/src/gateway.rs:938-967 ↔ pypy/interpreter/baseobjspace.py:1970-1983: filesystem paths are converted with lossy UTF-8/WTF-8 decoding, replacing invalid bytes and lone surrogates with U+FFFD. PyPy returns the original filesystem byte buffer; this pre-existing behavior also limits the new utime implementation.

  • pyre/pyre-interpreter/src/module/winreg/mod.rs:597-638 ↔ pypy/module/_winreg/moduledef.py:46: the module still does not export PyPy’s public error alias for WindowsError. This was absent in upstream/main; the new registry implementation leaves it absent.

4. Structural adaptations

  • pyre/pyre-interpreter/src/module/msvcrt/mod.rs:19-99 ↔ pypy/tool/generate_stdlib_module_names.py:39-42: msvcrt is a CPython-compatible Windows module required by the newer stdlib, but this PyPy checkout contains no corresponding module implementation. Its Rust-host-environment wrapper is therefore a Python-version/platform adaptation rather than a line-for-line PyPy port.

  • pyre/pyre-interpreter/src/module/_winapi/mod.rs:44-117 ↔ pypy/tool/generate_stdlib_module_names.py:39-42: the added _winapi subprocess constants and handle calls similarly target the newer CPython Windows stdlib; this checkout has no corresponding PyPy implementation.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@pyre/pyre-interpreter/src/module/_winapi/mod.rs`:
- Around line 26-32: Update last_os_error() to preserve the raw native
GetLastError() value when constructing the OSError, while retaining any POSIX
errno mapping needed for compatibility. Ensure failures from CloseHandle and
GetExitCodeProcess expose the original Windows code through winerror, matching
PyErr_SetFromWindowsErr semantics.

In `@pyre/pyre-interpreter/src/module/posix/interp_posix.rs`:
- Around line 1389-1403: Align the advertised `os.supports_fd` capabilities with
`utime_impl`: either add integer file-descriptor handling that routes through
`futimens`/`futimes`, or remove `HAVE_FUTIMENS` and `HAVE_FUTIMES` from
`_have_functions` so `os.utime` is no longer advertised as fd-capable.
- Around line 1412-1422: Extract the duplicated dir_fd parsing into a shared
helper near rename_impl, preserving None handling, integer conversion, and the
existing type-name lookup for TypeError messages. Update both rename_impl and
the shown caller to use this helper with their respective keyword name, removing
the local kwarg_get/present validation blocks.
- Around line 1437-1449: Update the utime timestamp conversion around
dur_from_secs and dur_from_ns to preserve negative, pre-1970 timestamps instead
of rejecting them. Replace the unsigned Duration representation throughout the
related host set_file_times* call path with a signed offset or equivalent
signed-seconds/nanoseconds representation, while retaining range validation and
existing handling for nonnegative timestamps.
- Around line 1465-1471: Update the `(None, None)` branch in the `os.utime`
timestamp handling to use the kernel/host time-setting API’s `UTIME_NOW`
representation when supported, instead of calling `SystemTime::now()` and
constructing a `Duration`. Preserve the existing explicit timestamp behavior for
all other cases.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 165a97b7-5f23-49c4-9583-ec3877e98b92

📥 Commits

Reviewing files that changed from the base of the PR and between cb4f6bd and d48c457.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (8)
  • pyre/pyre-interpreter/Cargo.toml
  • pyre/pyre-interpreter/src/importing.rs
  • pyre/pyre-interpreter/src/module/_io/textio.rs
  • pyre/pyre-interpreter/src/module/_winapi/mod.rs
  • pyre/pyre-interpreter/src/module/mod.rs
  • pyre/pyre-interpreter/src/module/msvcrt/mod.rs
  • pyre/pyre-interpreter/src/module/posix/interp_posix.rs
  • pyre/pyre-interpreter/src/module/winreg/mod.rs

Comment on lines +26 to +32
/// Map the current thread's last OS error to an `OSError`.
fn last_os_error() -> crate::PyError {
crate::PyError::os_error_syscall(
crate::builtins::io_error_posix_errno(&std::io::Error::last_os_error(), 0),
pyre_object::PY_NULL,
)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

Does CPython's _winapi.CloseHandle/GetExitCodeProcess raise OSError with winerror set to the raw Win32 error code?

💡 Result:

Yes, CPython's _winapi.CloseHandle and _winapi.GetExitCodeProcess functions raise OSError when the underlying Win32 API call fails, and this exception includes the raw Windows error code [1][2]. When these functions encounter a failure, they typically use PyErr_SetFromWindowsErr or similar internal CPython helpers [3][4][5]. This mechanism performs the following actions: 1. It retrieves the current Win32 error code using GetLastError [3][5]. 2. It constructs an OSError instance where the winerror attribute is set to the raw Windows error code (e.g., 6 for ERROR_INVALID_HANDLE) [1][2]. 3. It maps this Windows error code to a corresponding POSIX-style errno value, which is stored in the errno attribute of the OSError [6][7]. For example, if _winapi.CloseHandle is called with an invalid handle, it fails with ERROR_INVALID_HANDLE (code 6), resulting in an OSError with [WinError 6] The handle is invalid [1][2]. While the exception message and errno attribute reflect a POSIX-style translation, the winerror attribute provides access to the original Win32 error code [6][7].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf 'Files matching _winapi/mod.rs:\n'
fd -a 'mod.rs$' . | grep 'pyre-interpreter/src/module/_winapi/mod.rs' || true

printf '\nRelevant source around last_os_error usages:\n'
rg -n "last_os_error|io_error_posix_errno|os_error_syscall|CloseHandle|GetExitCodeProcess|WAIT_TIMEOUT" pyre/pyre-interpreter/src/module/_winapi/mod.rs -C 4

printf '\nDefinitions/usages of WinAPI error helpers:\n'
rg -n "io_error_posix_errno|os_error_syscall|last_os_error|Win32|WindowsError|winerror|errno" pyre/pyre-interpreter/src | head -120

Repository: youknowone/pyre

Length of output: 2014


🌐 Web query:

CPython Modules _winapi.c CloseHandle GetLastError PyErr_SetFromWindowsErr OSError winerror

💡 Result:

In the CPython source code, specifically within the Modules/_winapi.c module, the _winapi.CloseHandle function provides a direct wrapper around the Win32 API CloseHandle function [1][2][3]. The implementation in _winapi_CloseHandle_impl works as follows: 1. It calls CloseHandle(handle) wrapped within Py_BEGIN_ALLOW_THREADS and Py_END_ALLOW_THREADS to release the GIL during the potentially blocking system call [1][2][3]. 2. If CloseHandle fails (i.e., returns FALSE), the function calls PyErr_SetFromWindowsErr(0) [1][2][3]. The PyErr_SetFromWindowsErr(int ierr) function is a CPython C-API convenience function that raises an OSError [4][5]. By passing 0 as the ierr argument, CPython automatically invokes the Win32 GetLastError() function to retrieve the most recent error code [4][5]. It then formats this error into an OSError exception where: - The winerror attribute is set to the error code returned by GetLastError() [4][5]. - The strerror attribute contains the descriptive error message obtained via the Win32 FormatMessage function [4][5]. A common manifestation of this behavior is OSError: [WinError 6] The handle is invalid, which occurs when CloseHandle is called on a handle that has already been closed or is otherwise invalid [6][7]. Because this is implemented as a standard exception, it propagates up the call stack unless explicitly caught by the Python code initiating the handle operation [6]. Top results: [1], [4], [6]

Citations:


🌐 Web query:

CPython Modules _winapi.c GetExitCodeProcess GetLastError PyErr_SetFromWindowsErr OSError winerror

💡 Result:

In CPython, the _winapi module provides a thin wrapper around the Windows API function GetExitCodeProcess [1][2]. When _winapi.GetExitCodeProcess is called from Python, it executes the following C logic defined in Modules/_winapi.c [1][3]: 1. It invokes the Win32 API function GetExitCodeProcess [1][4]. 2. If the function fails (returning zero), it retrieves the extended error information using the Win32 GetLastError function [1][4]. 3. It then calls PyErr_SetFromWindowsErr(GetLastError), which raises an OSError exception [1][5]. This convenience function automatically translates the Windows error code into a Python OSError with the winerror attribute populated [5][6]. 4. On failure, the function also sets the return value to a sentinel (PY_DWORD_MAX) [1][7]. The PyErr_SetFromWindowsErr function is a C-API utility that formats the Windows error message using the system's FormatMessage function and constructs the OSError instance with the appropriate winerror and strerror attributes [5][6]. This ensures that when the underlying Windows API fails—for example, due to an invalid handle—the Python caller receives a descriptive exception rather than an silent failure or invalid return value [5][8].

Citations:


Preserve the native Win32 error code in _winapi failures.

last_os_error() converts every failure to a POSIX errno before raising OSError, so CloseHandle and GetExitCodeProcess failures lose the original GetLastError() value. Windows OSError/PyErr_SetFromWindowsErr semantics expose that native code via winerror; keep the raw Windows error alongside any POSIX mapping for these calls.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pyre/pyre-interpreter/src/module/_winapi/mod.rs` around lines 26 - 32, Update
last_os_error() to preserve the raw native GetLastError() value when
constructing the OSError, while retaining any POSIX errno mapping needed for
compatibility. Ensure failures from CloseHandle and GetExitCodeProcess expose
the original Windows code through winerror, matching PyErr_SetFromWindowsErr
semantics.

Comment on lines +1389 to +1403
fn utime_impl(args: &[PyObjectRef]) -> Result<PyObjectRef, crate::PyError> {
let (pos, kwargs) = crate::builtins::split_builtin_kwargs(args);
if pos.is_empty() {
return Err(crate::PyError::type_error(
"utime() missing required argument 'path' (pos 1)",
));
}
if pos.len() > 2 {
return Err(crate::PyError::type_error(format!(
"utime() takes from 1 to 2 positional arguments but {} were given",
pos.len()
)));
}
crate::builtins::kwarg_reject_unknown(kwargs, &["ns", "dir_fd", "follow_symlinks"], "utime")?;
let path = extract_path(pos[0])?;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

utime now rejects fd arguments while _have_functions still advertises HAVE_FUTIMENS/HAVE_FUTIMES.

Lines 589-590 keep both macros in _have_functions, so os.py puts os.utime into os.supports_fd. utime_impl unconditionally runs extract_path(pos[0]), so a caller that follows that capability bit (e.g. shutil.copystat on an open fd) now gets a TypeError instead of the previous silent no-op. Either accept an integer path and route to futimens, or drop the two macros from _have_functions.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pyre/pyre-interpreter/src/module/posix/interp_posix.rs` around lines 1389 -
1403, Align the advertised `os.supports_fd` capabilities with `utime_impl`:
either add integer file-descriptor handling that routes through
`futimens`/`futimes`, or remove `HAVE_FUTIMENS` and `HAVE_FUTIMES` from
`_have_functions` so `os.utime` is no longer advertised as fd-capable.

Comment on lines +1412 to +1422
let dir_fd = match crate::builtins::kwarg_get(kwargs, "dir_fd").and_then(present) {
Some(v) => {
if !unsafe { pyre_object::is_int(v) } {
return Err(crate::PyError::type_error(
"argument should be integer or None, not object",
));
}
Some(unsafe { pyre_object::w_int_get_value(v) } as i32)
}
None => None,
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Reuse rename_impl's dir_fd extractor instead of duplicating it with a degraded message.

This block repeats the dir_fd logic from rename_impl (Lines 1324-1339) but hardcodes not object where the other site reports the real type name. Extract a shared helper so the TypeError text stays consistent.

♻️ Suggested shared helper
fn kwarg_dir_fd(
    kwargs: &[(&str, PyObjectRef)],
    name: &str,
) -> Result<Option<i32>, crate::PyError> {
    match crate::builtins::kwarg_get(kwargs, name) {
        Some(v) if !unsafe { pyre_object::is_none(v) } => {
            if !unsafe { pyre_object::is_int(v) } {
                let type_name = crate::typedef::r#type(v)
                    .map(|t| unsafe { pyre_object::typeobject::w_type_get_name(t.as_ptr()) })
                    .unwrap_or("object");
                return Err(crate::PyError::type_error(format!(
                    "argument should be integer or None, not {type_name}"
                )));
            }
            Ok(Some((unsafe { pyre_object::w_int_get_value(v) }) as i32))
        }
        _ => Ok(None),
    }
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pyre/pyre-interpreter/src/module/posix/interp_posix.rs` around lines 1412 -
1422, Extract the duplicated dir_fd parsing into a shared helper near
rename_impl, preserving None handling, integer conversion, and the existing
type-name lookup for TypeError messages. Update both rename_impl and the shown
caller to use this helper with their respective keyword name, removing the local
kwarg_get/present validation blocks.

Comment on lines +1437 to +1449
let dur_from_secs = |v: PyObjectRef| -> Result<std::time::Duration, crate::PyError> {
let f = crate::builtins::builtin_float(&[v])?;
let secs = unsafe { pyre_object::w_float_get_value(f) };
std::time::Duration::try_from_secs_f64(secs)
.map_err(|_| crate::PyError::value_error("utime: timestamp out of range"))
};
let dur_from_ns = |v: PyObjectRef| -> Result<std::time::Duration, crate::PyError> {
let n = crate::builtins::space_index_w(v)?;
if n < 0 {
return Err(crate::PyError::value_error("utime: timestamp out of range"));
}
Ok(std::time::Duration::from_nanos(n as u64))
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Pre-1970 timestamps are rejected instead of applied.

Duration is unsigned, so dur_from_secs fails try_from_secs_f64 for any negative times value and dur_from_ns explicitly rejects n < 0. CPython/PyPy accept negative timestamps (os.utime(p, (-1, -1)) sets 1969-12-31 on Linux and Windows), and callers such as tarfile/shutil restoring old archives will now see a spurious ValueError: utime: timestamp out of range. The host set_file_times* signatures need a signed offset (or an (i64 secs, u32 nsec) pair) to represent this.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pyre/pyre-interpreter/src/module/posix/interp_posix.rs` around lines 1437 -
1449, Update the utime timestamp conversion around dur_from_secs and dur_from_ns
to preserve negative, pre-1970 timestamps instead of rejecting them. Replace the
unsigned Duration representation throughout the related host set_file_times*
call path with a signed offset or equivalent signed-seconds/nanoseconds
representation, while retaining range validation and existing handling for
nonnegative timestamps.

Comment on lines +1465 to +1471
(None, None) => {
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or(std::time::Duration::ZERO);
(now, now)
}
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🔵 Trivial | ⚖️ Poor tradeoff

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Locate the host_env timestamp APIs and check for a UTIME_NOW / Option<Duration> variant.
fd -t f -e rs . --exec rg -nP -C6 '\bfn\s+set_file_times(_at)?\s*[(<]' {} \;

Repository: youknowone/pyre

Length of output: 2043


🏁 Script executed:

#!/bin/bash
set -u

echo "Repository present:"
pwd
git status --short | head -20 || true

echo
echo "Find target file candidates:"
fd -t f 'interp_posix\.rs$' . || find . -path '*posix*interp_posix.rs' -print

echo
echo "Find host_env files by name:"
fd -t f -i 'host.*env|posix.*env|set_file_times' . || true

echo
echo "Search for set_file_times symbols in tracked rs files:"
git ls-files '*.rs' | xargs rg -n -C5 'set_file_times|FILE_TIMES|times_at|times = None' || true

Repository: youknowone/pyre

Length of output: 2014


🌐 Web query:

CPython os.utime times=None ns=None utimensat UTIME_NOW

💡 Result:

In CPython, when calling os.utime(path, times=None, ns=None), the default behavior is to set both the access and modification times of the file to the current time [1]. This operation is functionally equivalent to the Unix touch command [1]. Regarding the implementation details and the use of utimensat with UTIME_NOW: 1. Mechanism: When times=None and ns=None are passed (or when neither argument is provided), CPython's internal implementation (located in Modules/posixmodule.c) attempts to use the most precise system-level API available on the platform [2][3]. 2. Utimensat usage: On modern POSIX-compliant systems where utimensat is available, CPython often utilizes it to perform the operation [2][3]. When the times are meant to be set to the "current time," the internal C implementation may use the UTIME_NOW constant (specifically for the tv_nsec field of the timespec structure) [4][5][3]. 3. Evolution: Historically, CPython used the older utime or utimes system calls, which had microsecond resolution [2][3]. Since Python 3.3, support was added for higher-resolution nanosecond timestamps, and the implementation was updated to prefer utimensat and related functions that support these nanosecond-precision flags (UTIME_NOW and UTIME_OMIT) where supported by the operating system [2][3]. 4. API Constraints: It is important to note that ns=None is not a valid argument; if the ns keyword is used, it must be a 2-tuple of integers representing nanoseconds [6]. The times=None behavior is the standard mechanism to trigger the "set to now" functionality [1]. In summary, when you invoke os.utime(path) or os.utime(path, times=None), CPython effectively triggers a "set to current time" operation that leverages UTIME_NOW via utimensat on supported platforms [1][5][3].

Citations:


Route the both-None case through a kernel-supplied timestamp.

os.utime(path, times=None, ns=None) is intended to let the filesystem supply the timestamp rather than baking SystemTime::now() into the filesystem metadata. If the host time-setting API can express UTIME_NOW, pass that here instead of reading SystemTime::now() and clamping Duration::ZERO from epoch.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pyre/pyre-interpreter/src/module/posix/interp_posix.rs` around lines 1465 -
1471, Update the `(None, None)` branch in the `os.utime` timestamp handling to
use the kernel/host time-setting API’s `UTIME_NOW` representation when
supported, instead of calling `SystemTime::now()` and constructing a `Duration`.
Preserve the existing explicit timestamp behavior for all other cases.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant