Ensure we get the last error from Windows on the same thread (#15564)

Reverts #15552
Closes https://github.com/astral-sh/uv/pull/15562
Closes https://github.com/astral-sh/uv/issues/15558

The `GetLastError` calls must be on the same thread, or we can pull the
wrong last error!
This commit is contained in:
Zanie Blue
2025-08-27 16:42:37 -05:00
committed by GitHub
parent 7d49571336
commit 17a86d83ca
+30 -31
View File
@@ -45,8 +45,8 @@ use std::collections::HashMap;
use std::iter::once;
use std::str;
use windows_sys::Win32::Foundation::{
ERROR_BAD_USERNAME, ERROR_ENVVAR_NOT_FOUND, ERROR_INVALID_FLAGS, ERROR_INVALID_PARAMETER,
ERROR_NO_SUCH_LOGON_SESSION, ERROR_NOT_FOUND, FILETIME, GetLastError,
ERROR_BAD_USERNAME, ERROR_INVALID_FLAGS, ERROR_INVALID_PARAMETER, ERROR_NO_SUCH_LOGON_SESSION,
ERROR_NOT_FOUND, FILETIME, GetLastError,
};
use windows_sys::Win32::Security::Credentials::{
CRED_FLAGS, CRED_MAX_CREDENTIAL_BLOB_SIZE, CRED_MAX_GENERIC_TARGET_NAME_LENGTH,
@@ -162,15 +162,16 @@ impl CredentialApi for WinCredential {
self.validate_attributes(None, None)?;
let target_name = to_wstr(&self.target_name);
let cred_type = CRED_TYPE_GENERIC;
// SAFETY: Calling Windows API
match crate::blocking::spawn_blocking(move || unsafe {
Ok(CredDeleteW(target_name.as_ptr(), cred_type, 0))
crate::blocking::spawn_blocking(move || {
// SAFETY: Calling Windows API
if unsafe { CredDeleteW(target_name.as_ptr(), cred_type, 0) } != 0 {
Ok(())
} else {
// SAFETY: Calling Windows API
Err(Error::from_win32_code(unsafe { GetLastError() }).into())
}
})
.await?
{
0 => Err(decode_error()),
_ => Ok(()),
}
.await
}
/// Return the underlying concrete object with an `Any` type so that it can
@@ -276,7 +277,7 @@ impl WinCredential {
};
// SAFETY: Calling Windows API
let result = match unsafe { CredWriteW(&raw const credential, 0) } {
0 => Err(decode_error()),
0 => Err(Error::from_win32_code(unsafe { GetLastError() }).into()),
_ => Ok(()),
};
// erase the copy of the secret
@@ -310,7 +311,7 @@ impl WinCredential {
unsafe { CredReadW(target_name.as_ptr(), cred_type, 0, &raw mut p_credential) };
if result == 0 {
// `CredReadW` failed, so no allocation has been done, so no free needs to be done
Err(decode_error())
Err(Error::from_win32_code(unsafe { GetLastError() }).into())
} else {
// SAFETY: `CredReadW` succeeded, so p_credential points at an allocated credential. Apply
// the passed extractor function to it.
@@ -476,6 +477,23 @@ unsafe fn from_wstr(ws: *const u16) -> String {
#[derive(Debug)]
pub struct Error(pub u32);
impl Error {
/// Create a Windows error from a Win32 error code.
pub fn from_win32_code(code: u32) -> Self {
Self(code)
}
}
impl From<Error> for ErrorCode {
fn from(err: Error) -> Self {
match err.0 {
ERROR_NOT_FOUND => Self::NoEntry,
ERROR_NO_SUCH_LOGON_SESSION => Self::NoStorageAccess(Box::new(err)),
_ => Self::PlatformFailure(Box::new(err)),
}
}
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self.0 {
@@ -495,25 +513,6 @@ impl std::error::Error for Error {
}
}
/// Map the last encountered Windows API error to a crate error with appropriate annotation.
pub fn decode_error() -> ErrorCode {
// SAFETY: Calling Windows API
match unsafe { GetLastError() } {
ERROR_NOT_FOUND => ErrorCode::NoEntry,
// N.B. It's not clear why `ERROR_ENVVAR_NOT_FOUND` would be returned rather than
// `ERROR_NOT_FOUND`, but this was encountered on a Windows CI machine.
ERROR_ENVVAR_NOT_FOUND => ErrorCode::NoEntry,
ERROR_NO_SUCH_LOGON_SESSION => {
ErrorCode::NoStorageAccess(wrap(ERROR_NO_SUCH_LOGON_SESSION))
}
err => ErrorCode::PlatformFailure(wrap(err)),
}
}
fn wrap(code: u32) -> Box<dyn std::error::Error + Send + Sync> {
Box::new(Error(code))
}
#[cfg(feature = "keyring-tests")]
#[cfg(test)]
mod tests {