Use the `windows` crate facade consistently (#15737)

The initial motivation for this change was that we were using both the
`windows`, the `window_sys` and the `windows_core` crate in various
places. These crates have slightly unconventional versioning scheme
where there is a large workspace with the same version in general, but
only some crates get breaking releases when a new breaking release
happens, the others stay on the previous breaking version. The `windows`
crate is a shim for all three of them, with a single version. This
simplifies handling the versions.

Using `windows` over `windows_sys` has the advantage of a higher level
error interface, we now get a `Result` for all windows API calls instead
of C-style int-returns and get-last-error calls. This makes the
uv-keyring crate more resilient.

We keep using the `windows_registry` crate, which provides a higher
level interface to windows registry access.
This commit is contained in:
konsti 2025-09-09 17:07:14 +02:00 committed by GitHub
parent 12764df8b2
commit cd49e1d11f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 123 additions and 130 deletions

10
Cargo.lock generated
View File

@ -5097,7 +5097,6 @@ dependencies = [
"which", "which",
"whoami", "whoami",
"windows 0.59.0", "windows 0.59.0",
"windows-result",
"wiremock", "wiremock",
"zip", "zip",
] ]
@ -5711,7 +5710,6 @@ dependencies = [
"tokio", "tokio",
"tracing", "tracing",
"windows 0.59.0", "windows 0.59.0",
"windows-core 0.59.0",
] ]
[[package]] [[package]]
@ -5858,7 +5856,7 @@ dependencies = [
"security-framework", "security-framework",
"thiserror 2.0.16", "thiserror 2.0.16",
"tokio", "tokio",
"windows-sys 0.59.0", "windows 0.59.0",
"zeroize", "zeroize",
] ]
@ -6144,9 +6142,8 @@ dependencies = [
"uv-trampoline-builder", "uv-trampoline-builder",
"uv-warnings", "uv-warnings",
"which", "which",
"windows 0.59.0",
"windows-registry", "windows-registry",
"windows-result",
"windows-sys 0.59.0",
] ]
[[package]] [[package]]
@ -6357,9 +6354,8 @@ dependencies = [
"tracing", "tracing",
"uv-fs", "uv-fs",
"uv-static", "uv-static",
"windows 0.59.0",
"windows-registry", "windows-registry",
"windows-result",
"windows-sys 0.59.0",
] ]
[[package]] [[package]]

View File

@ -195,11 +195,8 @@ uuid = { version = "1.16.0" }
version-ranges = { git = "https://github.com/astral-sh/pubgrub", rev = "06ec5a5f59ffaeb6cf5079c6cb184467da06c9db" } version-ranges = { git = "https://github.com/astral-sh/pubgrub", rev = "06ec5a5f59ffaeb6cf5079c6cb184467da06c9db" }
walkdir = { version = "2.5.0" } walkdir = { version = "2.5.0" }
which = { version = "8.0.0", features = ["regex"] } which = { version = "8.0.0", features = ["regex"] }
windows = { version = "0.59.0", features = ["Win32_Globalization", "Win32_System_Console", "Win32_System_Kernel", "Win32_System_Diagnostics_Debug", "Win32_Storage_FileSystem"] } windows = { version = "0.59.0", features = ["Win32_Globalization", "Win32_Security", "Win32_System_Console", "Win32_System_Kernel", "Win32_System_Diagnostics_Debug", "Win32_Storage_FileSystem", "Win32_System_Registry", "Win32_System_IO", "Win32_System_Ioctl"] }
windows-core = { version = "0.59.0" }
windows-registry = { version = "0.5.0" } windows-registry = { version = "0.5.0" }
windows-result = { version = "0.3.0" }
windows-sys = { version = "0.59.0", features = ["Win32_Foundation", "Win32_Security", "Win32_Storage_FileSystem", "Win32_System_Ioctl", "Win32_System_IO", "Win32_System_Registry"] }
wiremock = { version = "0.6.4" } wiremock = { version = "0.6.4" }
xz2 = { version = "0.1.7" } xz2 = { version = "0.1.7" }
zeroize = { version = "1.8.1" } zeroize = { version = "1.8.1" }

View File

@ -37,7 +37,6 @@ rustix = { workspace = true }
backon = { workspace = true } backon = { workspace = true }
junction = { workspace = true } junction = { workspace = true }
windows = { workspace = true } windows = { workspace = true }
windows-core = { workspace = true }
[features] [features]
default = [] default = []

View File

@ -5,7 +5,7 @@ use std::path::Path;
fn get_binary_type(path: &Path) -> windows::core::Result<u32> { fn get_binary_type(path: &Path) -> windows::core::Result<u32> {
use std::os::windows::ffi::OsStrExt; use std::os::windows::ffi::OsStrExt;
use windows::Win32::Storage::FileSystem::GetBinaryTypeW; use windows::Win32::Storage::FileSystem::GetBinaryTypeW;
use windows_core::PCWSTR; use windows::core::PCWSTR;
// References: // References:
// https://github.com/denoland/deno/blob/01a6379505712be34ebf2cdc874fa7f54a6e9408/runtime/permissions/which.rs#L131-L154 // https://github.com/denoland/deno/blob/01a6379505712be34ebf2cdc874fa7f54a6e9408/runtime/permissions/which.rs#L131-L154

View File

@ -18,7 +18,7 @@ apple-native = ["dep:security-framework"]
## Use the secret-service on *nix. ## Use the secret-service on *nix.
secret-service = ["dep:secret-service"] secret-service = ["dep:secret-service"]
## Use the built-in credential store on Windows ## Use the built-in credential store on Windows
windows-native = ["dep:windows-sys", "dep:byteorder"] windows-native = ["dep:windows", "dep:byteorder"]
[dependencies] [dependencies]
async-trait = { workspace = true } async-trait = { workspace = true }
@ -33,7 +33,7 @@ secret-service = { workspace = true, features = ["rt-tokio-crypto-rust"], option
[target.'cfg(target_os = "windows")'.dependencies] [target.'cfg(target_os = "windows")'.dependencies]
byteorder = { workspace = true, optional = true } byteorder = { workspace = true, optional = true }
windows-sys = { workspace = true, features = ["Win32_Foundation", "Win32_Security_Credentials"], optional = true } windows = { workspace = true, features = ["Win32_Foundation", "Win32_Security_Credentials"], optional = true }
zeroize = { workspace = true } zeroize = { workspace = true }
[dev-dependencies] [dev-dependencies]

View File

@ -44,15 +44,16 @@ use byteorder::{ByteOrder, LittleEndian};
use std::collections::HashMap; use std::collections::HashMap;
use std::iter::once; use std::iter::once;
use std::str; use std::str;
use windows_sys::Win32::Foundation::{ use windows::Win32::Foundation::{
ERROR_BAD_USERNAME, ERROR_INVALID_FLAGS, ERROR_INVALID_PARAMETER, ERROR_NO_SUCH_LOGON_SESSION, ERROR_BAD_USERNAME, ERROR_INVALID_FLAGS, ERROR_INVALID_PARAMETER, ERROR_NO_SUCH_LOGON_SESSION,
ERROR_NOT_FOUND, FILETIME, GetLastError, ERROR_NOT_FOUND, FILETIME, WIN32_ERROR,
}; };
use windows_sys::Win32::Security::Credentials::{ use windows::Win32::Security::Credentials::{
CRED_FLAGS, CRED_MAX_CREDENTIAL_BLOB_SIZE, CRED_MAX_GENERIC_TARGET_NAME_LENGTH, CRED_FLAGS, CRED_MAX_CREDENTIAL_BLOB_SIZE, CRED_MAX_GENERIC_TARGET_NAME_LENGTH,
CRED_MAX_STRING_LENGTH, CRED_MAX_USERNAME_LENGTH, CRED_PERSIST_ENTERPRISE, CRED_TYPE_GENERIC, CRED_MAX_STRING_LENGTH, CRED_MAX_USERNAME_LENGTH, CRED_PERSIST_ENTERPRISE, CRED_TYPE_GENERIC,
CREDENTIAL_ATTRIBUTEW, CREDENTIALW, CredDeleteW, CredFree, CredReadW, CredWriteW, CREDENTIAL_ATTRIBUTEW, CREDENTIALW, CredDeleteW, CredFree, CredReadW, CredWriteW,
}; };
use windows::core::PWSTR;
use zeroize::Zeroize; use zeroize::Zeroize;
/// The representation of a Windows Generic credential. /// The representation of a Windows Generic credential.
@ -160,15 +161,13 @@ impl CredentialApi for WinCredential {
/// credential in the store. /// credential in the store.
async fn delete_credential(&self) -> Result<()> { async fn delete_credential(&self) -> Result<()> {
self.validate_attributes(None, None)?; self.validate_attributes(None, None)?;
let target_name = to_wstr(&self.target_name); let mut target_name = to_wstr(&self.target_name);
let cred_type = CRED_TYPE_GENERIC; let cred_type = CRED_TYPE_GENERIC;
crate::blocking::spawn_blocking(move || { crate::blocking::spawn_blocking(move || {
// SAFETY: Calling Windows API // SAFETY: Calling Windows API
if unsafe { CredDeleteW(target_name.as_ptr(), cred_type, 0) } != 0 { unsafe {
Ok(()) CredDeleteW(PWSTR(target_name.as_mut_ptr()), cred_type, None)
} else { .map_err(|err| Error(err).into())
// SAFETY: Calling Windows API
Err(Error::from_win32_code(unsafe { GetLastError() }).into())
} }
}) })
.await .await
@ -264,22 +263,20 @@ impl WinCredential {
let credential = CREDENTIALW { let credential = CREDENTIALW {
Flags: flags, Flags: flags,
Type: cred_type, Type: cred_type,
TargetName: target_name.as_mut_ptr(), TargetName: PWSTR(target_name.as_mut_ptr()),
Comment: comment.as_mut_ptr(), Comment: PWSTR(comment.as_mut_ptr()),
LastWritten: last_written, LastWritten: last_written,
CredentialBlobSize: blob_len, CredentialBlobSize: blob_len,
CredentialBlob: blob.as_mut_ptr(), CredentialBlob: blob.as_mut_ptr(),
Persist: persist, Persist: persist,
AttributeCount: attribute_count, AttributeCount: attribute_count,
Attributes: attributes, Attributes: attributes,
TargetAlias: target_alias.as_mut_ptr(), TargetAlias: PWSTR(target_alias.as_mut_ptr()),
UserName: username.as_mut_ptr(), UserName: PWSTR(username.as_mut_ptr()),
}; };
// SAFETY: Calling Windows API // SAFETY: Calling Windows API
let result = match unsafe { CredWriteW(&raw const credential, 0) } { let result =
0 => Err(Error::from_win32_code(unsafe { GetLastError() }).into()), unsafe { CredWriteW(&raw const credential, 0) }.map_err(|err| Error(err).into());
_ => Ok(()),
};
// erase the copy of the secret // erase the copy of the secret
blob.zeroize(); blob.zeroize();
result result
@ -300,31 +297,33 @@ impl WinCredential {
T: Send + 'static, T: Send + 'static,
{ {
self.validate_attributes(None, None)?; self.validate_attributes(None, None)?;
let target_name = to_wstr(&self.target_name); let mut target_name = to_wstr(&self.target_name);
crate::blocking::spawn_blocking(move || { crate::blocking::spawn_blocking(move || {
let mut p_credential = std::ptr::null_mut(); let mut p_credential = std::ptr::null_mut();
// at this point, p_credential is just a pointer to nowhere. // at this point, p_credential is just a pointer to nowhere.
// The allocation happens in the `CredReadW` call below. // The allocation happens in the `CredReadW` call below.
let cred_type = CRED_TYPE_GENERIC; let cred_type = CRED_TYPE_GENERIC;
// SAFETY: Calling windows API // SAFETY: Calling windows API
let result = unsafe {
unsafe { CredReadW(target_name.as_ptr(), cred_type, 0, &raw mut p_credential) }; CredReadW(
if result == 0 { PWSTR(target_name.as_mut_ptr()),
// `CredReadW` failed, so no allocation has been done, so no free needs to be done cred_type,
Err(Error::from_win32_code(unsafe { GetLastError() }).into()) None,
} else { &raw mut p_credential,
// SAFETY: `CredReadW` succeeded, so p_credential points at an allocated credential. Apply )
// the passed extractor function to it.
let ref_cred: &mut CREDENTIALW = unsafe { &mut *p_credential };
let result = f(ref_cred);
// Finally, we erase the secret and free the allocated credential.
erase_secret(ref_cred);
let p_credential = p_credential;
// SAFETY: `CredReadW` succeeded, so p_credential points at an allocated credential.
// Free the allocation.
unsafe { CredFree(p_credential.cast()) }
result
} }
.map_err(Error)?;
// SAFETY: `CredReadW` succeeded, so p_credential points at an allocated credential. Apply
// the passed extractor function to it.
let ref_cred: &mut CREDENTIALW = unsafe { &mut *p_credential };
let result = f(ref_cred);
// Finally, we erase the secret and free the allocated credential.
erase_secret(ref_cred);
let p_credential = p_credential;
// SAFETY: `CredReadW` succeeded, so p_credential points at an allocated credential.
// Free the allocation.
unsafe { CredFree(p_credential.cast()) }
result
}) })
.await .await
} }
@ -332,10 +331,10 @@ impl WinCredential {
#[allow(clippy::unnecessary_wraps)] #[allow(clippy::unnecessary_wraps)]
fn extract_credential(w_credential: &CREDENTIALW) -> Result<Self> { fn extract_credential(w_credential: &CREDENTIALW) -> Result<Self> {
Ok(Self { Ok(Self {
username: unsafe { from_wstr(w_credential.UserName) }, username: unsafe { from_wstr(w_credential.UserName.as_ptr()) },
target_name: unsafe { from_wstr(w_credential.TargetName) }, target_name: unsafe { from_wstr(w_credential.TargetName.as_ptr()) },
target_alias: unsafe { from_wstr(w_credential.TargetAlias) }, target_alias: unsafe { from_wstr(w_credential.TargetAlias.as_ptr()) },
comment: unsafe { from_wstr(w_credential.Comment) }, comment: unsafe { from_wstr(w_credential.Comment.as_ptr()) },
}) })
} }
@ -475,34 +474,40 @@ unsafe fn from_wstr(ws: *const u16) -> String {
/// Windows error codes are `DWORDS` which are 32-bit unsigned ints. /// Windows error codes are `DWORDS` which are 32-bit unsigned ints.
#[derive(Debug)] #[derive(Debug)]
pub struct Error(pub u32); pub struct Error(windows::core::Error);
impl Error { impl From<WIN32_ERROR> for Error {
/// Create a Windows error from a Win32 error code. fn from(error: WIN32_ERROR) -> Self {
pub fn from_win32_code(code: u32) -> Self { Self(windows::core::Error::from(error))
Self(code)
} }
} }
impl From<Error> for ErrorCode { impl From<Error> for ErrorCode {
fn from(err: Error) -> Self { fn from(err: Error) -> Self {
match err.0 { if err.0 == ERROR_NOT_FOUND.into() {
ERROR_NOT_FOUND => Self::NoEntry, Self::NoEntry
ERROR_NO_SUCH_LOGON_SESSION => Self::NoStorageAccess(Box::new(err)), } else if err.0 == ERROR_NO_SUCH_LOGON_SESSION.into() {
_ => Self::PlatformFailure(Box::new(err)), Self::NoStorageAccess(Box::new(err))
} else {
Self::PlatformFailure(Box::new(err))
} }
} }
} }
impl std::fmt::Display for Error { impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self.0 { if self.0 == ERROR_NO_SUCH_LOGON_SESSION.into() {
ERROR_NO_SUCH_LOGON_SESSION => write!(f, "Windows ERROR_NO_SUCH_LOGON_SESSION"), write!(f, "Windows ERROR_NO_SUCH_LOGON_SESSION")
ERROR_NOT_FOUND => write!(f, "Windows ERROR_NOT_FOUND"), } else if self.0 == ERROR_NOT_FOUND.into() {
ERROR_BAD_USERNAME => write!(f, "Windows ERROR_BAD_USERNAME"), write!(f, "Windows ERROR_NOT_FOUND")
ERROR_INVALID_FLAGS => write!(f, "Windows ERROR_INVALID_FLAGS"), } else if self.0 == ERROR_BAD_USERNAME.into() {
ERROR_INVALID_PARAMETER => write!(f, "Windows ERROR_INVALID_PARAMETER"), write!(f, "Windows ERROR_BAD_USERNAME")
err => write!(f, "Windows error code {err}"), } else if self.0 == ERROR_INVALID_FLAGS.into() {
write!(f, "Windows ERROR_INVALID_FLAGS")
} else if self.0 == ERROR_INVALID_PARAMETER.into() {
write!(f, "Windows ERROR_INVALID_PARAMETER")
} else {
write!(f, "Windows error code {}", self.0)
} }
} }
} }
@ -545,18 +550,18 @@ mod tests {
let attribute_count = 0; let attribute_count = 0;
let attributes: *mut CREDENTIAL_ATTRIBUTEW = std::ptr::null_mut(); let attributes: *mut CREDENTIAL_ATTRIBUTEW = std::ptr::null_mut();
CREDENTIALW { CREDENTIALW {
Flags: 0, Flags: CRED_FLAGS(0),
Type: CRED_TYPE_GENERIC, Type: CRED_TYPE_GENERIC,
TargetName: std::ptr::null_mut(), TargetName: PWSTR::null(),
Comment: std::ptr::null_mut(), Comment: PWSTR::null(),
LastWritten: last_written, LastWritten: last_written,
CredentialBlobSize: password.len() as u32, CredentialBlobSize: password.len() as u32,
CredentialBlob: password.as_mut_ptr(), CredentialBlob: password.as_mut_ptr(),
Persist: CRED_PERSIST_ENTERPRISE, Persist: CRED_PERSIST_ENTERPRISE,
AttributeCount: attribute_count, AttributeCount: attribute_count,
Attributes: attributes, Attributes: attributes,
TargetAlias: std::ptr::null_mut(), TargetAlias: PWSTR::null(),
UserName: std::ptr::null_mut(), UserName: PWSTR::null(),
} }
} }
// the first malformed sequence can't be UTF-16 because it has an odd number of bytes. // the first malformed sequence can't be UTF-16 because it has an odd number of bytes.

View File

@ -70,8 +70,7 @@ once_cell = { workspace = true }
[target.'cfg(target_os = "windows")'.dependencies] [target.'cfg(target_os = "windows")'.dependencies]
windows-registry = { workspace = true } windows-registry = { workspace = true }
windows-result = { workspace = true } windows = { workspace = true }
windows-sys = { workspace = true }
[dev-dependencies] [dev-dependencies]
anyhow = { workspace = true } anyhow = { workspace = true }

View File

@ -8,9 +8,6 @@ use std::{env, io, iter};
use std::{path::Path, path::PathBuf, str::FromStr}; use std::{path::Path, path::PathBuf, str::FromStr};
use thiserror::Error; use thiserror::Error;
use tracing::{debug, instrument, trace}; use tracing::{debug, instrument, trace};
use uv_preview::Preview;
use which::{which, which_all};
use uv_cache::Cache; use uv_cache::Cache;
use uv_fs::Simplified; use uv_fs::Simplified;
use uv_fs::which::is_executable; use uv_fs::which::is_executable;
@ -18,8 +15,10 @@ use uv_pep440::{
LowerBound, Prerelease, UpperBound, Version, VersionSpecifier, VersionSpecifiers, LowerBound, Prerelease, UpperBound, Version, VersionSpecifier, VersionSpecifiers,
release_specifiers_to_ranges, release_specifiers_to_ranges,
}; };
use uv_preview::Preview;
use uv_static::EnvVars; use uv_static::EnvVars;
use uv_warnings::warn_user_once; use uv_warnings::warn_user_once;
use which::{which, which_all};
use crate::downloads::{PlatformRequest, PythonDownloadRequest}; use crate::downloads::{PlatformRequest, PythonDownloadRequest};
use crate::implementation::ImplementationName; use crate::implementation::ImplementationName;
@ -251,7 +250,7 @@ pub enum Error {
#[cfg(windows)] #[cfg(windows)]
#[error("Failed to query installed Python versions from the Windows registry")] #[error("Failed to query installed Python versions from the Windows registry")]
RegistryError(#[from] windows_result::Error), RegistryError(#[from] windows::core::Error),
/// An invalid version request was given /// An invalid version request was given
#[error("Invalid version request: {0}")] #[error("Invalid version request: {0}")]
@ -1502,13 +1501,15 @@ fn warn_on_unsupported_python(interpreter: &Interpreter) {
pub(crate) fn is_windows_store_shim(path: &Path) -> bool { pub(crate) fn is_windows_store_shim(path: &Path) -> bool {
use std::os::windows::fs::MetadataExt; use std::os::windows::fs::MetadataExt;
use std::os::windows::prelude::OsStrExt; use std::os::windows::prelude::OsStrExt;
use windows_sys::Win32::Foundation::{CloseHandle, INVALID_HANDLE_VALUE}; use windows::Win32::Foundation::CloseHandle;
use windows_sys::Win32::Storage::FileSystem::{ use windows::Win32::Storage::FileSystem::{
CreateFileW, FILE_ATTRIBUTE_REPARSE_POINT, FILE_FLAG_BACKUP_SEMANTICS, CreateFileW, FILE_ATTRIBUTE_REPARSE_POINT, FILE_FLAG_BACKUP_SEMANTICS,
FILE_FLAG_OPEN_REPARSE_POINT, MAXIMUM_REPARSE_DATA_BUFFER_SIZE, OPEN_EXISTING, FILE_FLAG_OPEN_REPARSE_POINT, FILE_SHARE_MODE, MAXIMUM_REPARSE_DATA_BUFFER_SIZE,
OPEN_EXISTING,
}; };
use windows_sys::Win32::System::IO::DeviceIoControl; use windows::Win32::System::IO::DeviceIoControl;
use windows_sys::Win32::System::Ioctl::FSCTL_GET_REPARSE_POINT; use windows::Win32::System::Ioctl::FSCTL_GET_REPARSE_POINT;
use windows::core::PCWSTR;
// The path must be absolute. // The path must be absolute.
if !path.is_absolute() { if !path.is_absolute() {
@ -1553,7 +1554,7 @@ pub(crate) fn is_windows_store_shim(path: &Path) -> bool {
let Ok(md) = fs_err::symlink_metadata(path) else { let Ok(md) = fs_err::symlink_metadata(path) else {
return false; return false;
}; };
if md.file_attributes() & FILE_ATTRIBUTE_REPARSE_POINT == 0 { if md.file_attributes() & FILE_ATTRIBUTE_REPARSE_POINT.0 == 0 {
return false; return false;
} }
@ -1567,19 +1568,19 @@ pub(crate) fn is_windows_store_shim(path: &Path) -> bool {
#[allow(unsafe_code)] #[allow(unsafe_code)]
let reparse_handle = unsafe { let reparse_handle = unsafe {
CreateFileW( CreateFileW(
path_encoded.as_mut_ptr(), PCWSTR(path_encoded.as_mut_ptr()),
0, 0,
0, FILE_SHARE_MODE(0),
std::ptr::null_mut(), None,
OPEN_EXISTING, OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT,
std::ptr::null_mut(), None,
) )
}; };
if reparse_handle == INVALID_HANDLE_VALUE { let Ok(reparse_handle) = reparse_handle else {
return false; return false;
} };
let mut buf = [0u16; MAXIMUM_REPARSE_DATA_BUFFER_SIZE as usize]; let mut buf = [0u16; MAXIMUM_REPARSE_DATA_BUFFER_SIZE as usize];
let mut bytes_returned = 0; let mut bytes_returned = 0;
@ -1590,19 +1591,20 @@ pub(crate) fn is_windows_store_shim(path: &Path) -> bool {
DeviceIoControl( DeviceIoControl(
reparse_handle, reparse_handle,
FSCTL_GET_REPARSE_POINT, FSCTL_GET_REPARSE_POINT,
std::ptr::null_mut(), None,
0, 0,
buf.as_mut_ptr().cast(), Some(buf.as_mut_ptr().cast()),
buf.len() as u32 * 2, buf.len() as u32 * 2,
&raw mut bytes_returned, Some(&raw mut bytes_returned),
std::ptr::null_mut(), None,
) != 0 )
.is_ok()
}; };
// SAFETY: The handle is valid. // SAFETY: The handle is valid.
#[allow(unsafe_code)] #[allow(unsafe_code)]
unsafe { unsafe {
CloseHandle(reparse_handle); let _ = CloseHandle(reparse_handle);
} }
// If the operation failed, assume it's not a reparse point. // If the operation failed, assume it's not a reparse point.

View File

@ -34,7 +34,7 @@ use crate::{
}; };
#[cfg(windows)] #[cfg(windows)]
use windows_sys::Win32::Foundation::{APPMODEL_ERROR_NO_PACKAGE, ERROR_CANT_ACCESS_FILE}; use windows::Win32::Foundation::{APPMODEL_ERROR_NO_PACKAGE, ERROR_CANT_ACCESS_FILE, WIN32_ERROR};
/// A Python executable and its associated platform markers. /// A Python executable and its associated platform markers.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -928,8 +928,10 @@ impl InterpreterInfo {
_ => {} _ => {}
} }
#[cfg(windows)] #[cfg(windows)]
if let Some(APPMODEL_ERROR_NO_PACKAGE | ERROR_CANT_ACCESS_FILE) = if let Some(APPMODEL_ERROR_NO_PACKAGE | ERROR_CANT_ACCESS_FILE) = err
err.raw_os_error().and_then(|code| u32::try_from(code).ok()) .raw_os_error()
.and_then(|code| u32::try_from(code).ok())
.map(WIN32_ERROR)
{ {
// These error codes are returned if the Python interpreter is a corrupt MSIX // These error codes are returned if the Python interpreter is a corrupt MSIX
// package, which we want to differentiate from a typical spawn failure. // package, which we want to differentiate from a typical spawn failure.

View File

@ -15,7 +15,7 @@ use thiserror::Error;
use tracing::{debug, warn}; use tracing::{debug, warn};
use uv_preview::{Preview, PreviewFeatures}; use uv_preview::{Preview, PreviewFeatures};
#[cfg(windows)] #[cfg(windows)]
use windows_sys::Win32::Storage::FileSystem::FILE_ATTRIBUTE_REPARSE_POINT; use windows::Win32::Storage::FileSystem::FILE_ATTRIBUTE_REPARSE_POINT;
use uv_fs::{LockedFile, Simplified, replace_symlink, symlink_or_copy_file}; use uv_fs::{LockedFile, Simplified, replace_symlink, symlink_or_copy_file};
use uv_platform::{Error as PlatformError, Os}; use uv_platform::{Error as PlatformError, Os};
@ -857,7 +857,7 @@ impl PythonMinorVersionLink {
.is_ok_and(|metadata| { .is_ok_and(|metadata| {
// Check that this is a reparse point, which indicates this // Check that this is a reparse point, which indicates this
// is a symlink or junction. // is a symlink or junction.
(metadata.file_attributes() & FILE_ATTRIBUTE_REPARSE_POINT) != 0 (metadata.file_attributes() & FILE_ATTRIBUTE_REPARSE_POINT.0) != 0
}) })
} }
} }

View File

@ -12,13 +12,10 @@ use thiserror::Error;
use tracing::debug; use tracing::debug;
use uv_platform::Arch; use uv_platform::Arch;
use uv_warnings::{warn_user, warn_user_once}; use uv_warnings::{warn_user, warn_user_once};
use windows::Win32::Foundation::ERROR_FILE_NOT_FOUND;
use windows::Win32::System::Registry::{KEY_WOW64_32KEY, KEY_WOW64_64KEY};
use windows::core::HRESULT;
use windows_registry::{CURRENT_USER, HSTRING, Key, LOCAL_MACHINE, Value}; use windows_registry::{CURRENT_USER, HSTRING, Key, LOCAL_MACHINE, Value};
use windows_result::HRESULT;
use windows_sys::Win32::Foundation::ERROR_FILE_NOT_FOUND;
use windows_sys::Win32::System::Registry::{KEY_WOW64_32KEY, KEY_WOW64_64KEY};
/// Code returned when the registry key doesn't exist.
const ERROR_NOT_FOUND: HRESULT = HRESULT::from_win32(ERROR_FILE_NOT_FOUND);
/// A Python interpreter found in the Windows registry through PEP 514 or from a known Microsoft /// A Python interpreter found in the Windows registry through PEP 514 or from a known Microsoft
/// Store path. /// Store path.
@ -32,7 +29,7 @@ pub(crate) struct WindowsPython {
} }
/// Find all Pythons registered in the Windows registry following PEP 514. /// Find all Pythons registered in the Windows registry following PEP 514.
pub(crate) fn registry_pythons() -> Result<Vec<WindowsPython>, windows_result::Error> { pub(crate) fn registry_pythons() -> Result<Vec<WindowsPython>, windows::core::Error> {
let mut registry_pythons = Vec::new(); let mut registry_pythons = Vec::new();
// Prefer `HKEY_CURRENT_USER` over `HKEY_LOCAL_MACHINE`. // Prefer `HKEY_CURRENT_USER` over `HKEY_LOCAL_MACHINE`.
// By default, a 64-bit program does not see a 32-bit global (HKLM) installation of Python in // By default, a 64-bit program does not see a 32-bit global (HKLM) installation of Python in
@ -47,7 +44,7 @@ pub(crate) fn registry_pythons() -> Result<Vec<WindowsPython>, windows_result::E
let mut open_options = root_key.options(); let mut open_options = root_key.options();
open_options.read(); open_options.read();
if let Some(access_modifier) = access_modifier { if let Some(access_modifier) = access_modifier {
open_options.access(access_modifier); open_options.access(access_modifier.0);
} }
let Ok(key_python) = open_options.open(r"Software\Python") else { let Ok(key_python) = open_options.open(r"Software\Python") else {
continue; continue;
@ -131,7 +128,7 @@ pub enum ManagedPep514Error {
#[error("Windows has an unknown pointer width for arch: `{_0}`")] #[error("Windows has an unknown pointer width for arch: `{_0}`")]
InvalidPointerSize(Arch), InvalidPointerSize(Arch),
#[error("Failed to write registry entry: {0}")] #[error("Failed to write registry entry: {0}")]
WriteError(#[from] windows_result::Error), WriteError(#[from] windows::core::Error),
} }
/// Register a managed Python installation in the Windows registry following PEP 514. /// Register a managed Python installation in the Windows registry following PEP 514.
@ -216,7 +213,7 @@ pub fn remove_registry_entry<'a>(
if all { if all {
debug!("Removing registry key HKCU:\\{}", astral_key); debug!("Removing registry key HKCU:\\{}", astral_key);
if let Err(err) = CURRENT_USER.remove_tree(&astral_key) { if let Err(err) = CURRENT_USER.remove_tree(&astral_key) {
if err.code() == ERROR_NOT_FOUND { if err.code() == HRESULT::from(ERROR_FILE_NOT_FOUND) {
debug!("No registry entries to remove, no registry key {astral_key}"); debug!("No registry entries to remove, no registry key {astral_key}");
} else { } else {
warn_user!("Failed to clear registry entries under {astral_key}: {err}"); warn_user!("Failed to clear registry entries under {astral_key}: {err}");
@ -230,7 +227,7 @@ pub fn remove_registry_entry<'a>(
let python_entry = format!("{astral_key}\\{python_tag}"); let python_entry = format!("{astral_key}\\{python_tag}");
debug!("Removing registry key HKCU:\\{}", python_entry); debug!("Removing registry key HKCU:\\{}", python_entry);
if let Err(err) = CURRENT_USER.remove_tree(&python_entry) { if let Err(err) = CURRENT_USER.remove_tree(&python_entry) {
if err.code() == ERROR_NOT_FOUND { if err.code() == HRESULT::from(ERROR_FILE_NOT_FOUND) {
debug!( debug!(
"No registry entries to remove for {}, no registry key {}", "No registry entries to remove for {}, no registry key {}",
installation.key(), installation.key(),
@ -256,7 +253,7 @@ pub fn remove_orphan_registry_entries(installations: &[ManagedPythonInstallation
let astral_key = format!("Software\\Python\\{COMPANY_KEY}"); let astral_key = format!("Software\\Python\\{COMPANY_KEY}");
let key = match CURRENT_USER.open(&astral_key) { let key = match CURRENT_USER.open(&astral_key) {
Ok(subkeys) => subkeys, Ok(subkeys) => subkeys,
Err(err) if err.code() == ERROR_NOT_FOUND => { Err(err) if err.code() == HRESULT::from(ERROR_FILE_NOT_FOUND) => {
return; return;
} }
Err(err) => { Err(err) => {
@ -268,7 +265,7 @@ pub fn remove_orphan_registry_entries(installations: &[ManagedPythonInstallation
// Separate assignment since `keys()` creates a borrow. // Separate assignment since `keys()` creates a borrow.
let subkeys = match key.keys() { let subkeys = match key.keys() {
Ok(subkeys) => subkeys, Ok(subkeys) => subkeys,
Err(err) if err.code() == ERROR_NOT_FOUND => { Err(err) if err.code() == HRESULT::from(ERROR_FILE_NOT_FOUND) => {
return; return;
} }
Err(err) => { Err(err) => {
@ -284,7 +281,7 @@ pub fn remove_orphan_registry_entries(installations: &[ManagedPythonInstallation
let python_entry = format!("{astral_key}\\{subkey}"); let python_entry = format!("{astral_key}\\{subkey}");
debug!("Removing orphan registry key HKCU:\\{}", python_entry); debug!("Removing orphan registry key HKCU:\\{}", python_entry);
if let Err(err) = CURRENT_USER.remove_tree(&python_entry) { if let Err(err) = CURRENT_USER.remove_tree(&python_entry) {
if err.code() == ERROR_NOT_FOUND { if err.code() == HRESULT::from(ERROR_FILE_NOT_FOUND) {
continue; continue;
} }
// TODO(konsti): We don't have an installation key here. // TODO(konsti): We don't have an installation key here.

View File

@ -24,9 +24,8 @@ tracing = { workspace = true }
nix = { workspace = true } nix = { workspace = true }
[target.'cfg(windows)'.dependencies] [target.'cfg(windows)'.dependencies]
windows = { workspace = true }
windows-registry = { workspace = true } windows-registry = { workspace = true }
windows-result = { workspace = true }
windows-sys = { workspace = true }
[dev-dependencies] [dev-dependencies]
fs-err = { workspace = true } fs-err = { workspace = true }

View File

@ -8,9 +8,9 @@ use std::path::Path;
use anyhow::Context; use anyhow::Context;
use tracing::warn; use tracing::warn;
use windows::Win32::Foundation::{ERROR_FILE_NOT_FOUND, ERROR_INVALID_DATA};
use windows::core::HRESULT;
use windows_registry::{CURRENT_USER, HSTRING}; use windows_registry::{CURRENT_USER, HSTRING};
use windows_result::HRESULT;
use windows_sys::Win32::Foundation::{ERROR_FILE_NOT_FOUND, ERROR_INVALID_DATA};
use uv_static::EnvVars; use uv_static::EnvVars;
@ -60,13 +60,11 @@ fn get_windows_path_var() -> anyhow::Result<Option<HSTRING>> {
let reg_value = environment.get_hstring(EnvVars::PATH); let reg_value = environment.get_hstring(EnvVars::PATH);
match reg_value { match reg_value {
Ok(reg_value) => Ok(Some(reg_value)), Ok(reg_value) => Ok(Some(reg_value)),
Err(err) if err.code() == HRESULT::from_win32(ERROR_INVALID_DATA) => { Err(err) if err.code() == HRESULT::from(ERROR_INVALID_DATA) => {
warn!("`HKEY_CURRENT_USER\\Environment\\PATH` is a non-string"); warn!("`HKEY_CURRENT_USER\\Environment\\PATH` is a non-string");
Ok(None) Ok(None)
} }
Err(err) if err.code() == HRESULT::from_win32(ERROR_FILE_NOT_FOUND) => { Err(err) if err.code() == HRESULT::from(ERROR_FILE_NOT_FOUND) => Ok(Some(HSTRING::new())),
Ok(Some(HSTRING::new()))
}
Err(err) => Err(err.into()), Err(err) => Err(err.into()),
} }
} }

View File

@ -119,7 +119,6 @@ zip = { workspace = true }
arrayvec = { workspace = true } arrayvec = { workspace = true }
self-replace = { workspace = true } self-replace = { workspace = true }
windows = { workspace = true } windows = { workspace = true }
windows-result = { workspace = true }
[dev-dependencies] [dev-dependencies]
assert_cmd = { workspace = true } assert_cmd = { workspace = true }

View File

@ -54,11 +54,11 @@ enum ExceptionSafeStderr {
} }
impl ExceptionSafeStderr { impl ExceptionSafeStderr {
fn new() -> Result<Self, windows_result::Error> { fn new() -> Result<Self, windows::core::Error> {
// SAFETY: winapi call, no interesting parameters // SAFETY: winapi call, no interesting parameters
let handle = unsafe { GetStdHandle(STD_ERROR_HANDLE) }?; let handle = unsafe { GetStdHandle(STD_ERROR_HANDLE) }?;
if handle.is_invalid() { if handle.is_invalid() {
return Err(windows_result::Error::empty()); return Err(windows::core::Error::empty());
} }
let mut mode = CONSOLE_MODE::default(); let mut mode = CONSOLE_MODE::default();
// SAFETY: winapi calls, no interesting parameters // SAFETY: winapi calls, no interesting parameters
@ -73,7 +73,7 @@ impl ExceptionSafeStderr {
} }
} }
fn write_winerror(&mut self, s: &str) -> Result<(), windows_result::Error> { fn write_winerror(&mut self, s: &str) -> Result<(), windows::core::Error> {
match self { match self {
Self::WriteConsole(handle) => { Self::WriteConsole(handle) => {
// According to comments in the ReactOS source, NT's behavior is that writes of 80 // According to comments in the ReactOS source, NT's behavior is that writes of 80