Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add os::windows::ffi::OsStrExt::system_cmp and system_eq #86008

Closed
wants to merge 2 commits into from
Closed
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
90 changes: 90 additions & 0 deletions library/std/src/os/windows/ffi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,9 @@

#![stable(feature = "rust1", since = "1.0.0")]

use crate::cmp::Ordering;
use crate::ffi::{OsStr, OsString};
use crate::io;
use crate::sealed::Sealed;
use crate::sys::os_str::Buf;
use crate::sys_common::wtf8::Wtf8Buf;
Expand Down Expand Up @@ -124,11 +126,99 @@ pub trait OsStrExt: Sealed {
/// ```
#[stable(feature = "rust1", since = "1.0.0")]
fn encode_wide(&self) -> EncodeWide<'_>;

/// Compares two `OsStr` by using the Windows system implementation.
/// This performs a case-insensitive comparison of UTF-16 code units using the system case mappings.
/// The comparison is locale-independent, but exact results may depend on the Windows version,
/// file system, and system settings.
///
/// This is the correct way to compare various strings on Windows:
/// environment variable keys, registry keys and resource handle names are all case-insensitive.
/// Note that this does not include file names or paths; those can be case-sensitive depending on
/// the system, file system or directory settings.
///
/// Note that this operation requires encoding both strings to UTF-16 and potentially performing system calls.
/// This operation is thus more computationally expensive than a normal comparison using [`Ord`].
///
/// # Errors
///
/// This function will return an error in the following situations, but is not limited to just these cases:
/// - If the string contains any null characters.
///
/// # Examples
/// ```
/// #![feature(windows_case_insensitive)]
///
/// use std::ffi::OsString;
/// use std::os::windows::prelude::*;
///
/// let list = [ OsString::from("A"), OsString::from("Z"), OsString::from("a") ];
///
/// let mut sorted = list.clone();
/// sorted.sort();
///
/// let mut sorted_with_system_cmp = list.clone();
/// sorted_with_system_cmp.sort_by(|a, b| a.system_cmp(b).unwrap());
///
/// assert_eq!(sorted, list); // unchanged, since `Z` < `a`
/// assert_eq!(sorted_with_system_cmp, [ OsString::from("A"), OsString::from("a"), OsString::from("Z") ]);
/// ```
#[unstable(feature = "windows_case_insensitive", issue = "86007")]
fn system_cmp(&self, other: &Self) -> io::Result<Ordering>;

/// Checks two `OsStr` for equality by using the Windows system implementation.
/// This performs a case-insensitive comparison of UTF-16 code units using the system case mappings.
/// The comparison is locale-independent, but exact results may depend on the Windows version,
/// file system, and system settings.
///
/// This is the correct way to compare various strings on Windows:
/// environment variable keys, registry keys and resource handle names are all case-insensitive.
/// Note that this does not include file names or paths; those can be case-sensitive depending on
/// the system, file system or directory settings.
///
/// Note that this operation requires encoding both strings to UTF-16 and potentially performing system calls.
/// This operation is thus more computationally expensive than a normal comparison using [`Eq`].
///
/// # Errors
///
/// This function will return an error in the following situations, but is not limited to just these cases:
/// - If the string contains any null characters.
///
/// # Examples
/// ```
/// #![feature(windows_case_insensitive)]
///
/// use std::ffi::OsString;
/// use std::os::windows::prelude::*;
///
/// let a = OsString::from("Path");
/// let b = OsString::from("PATH");
///
/// assert!(a.eq(&b) == false);
/// assert!(a.system_eq(&b).unwrap() == true);
Copy link
Member

Choose a reason for hiding this comment

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

I think you're missing a closing ```?

#[unstable(feature = "windows_case_insensitive", issue = "86007")]
fn system_eq(&self, other: &Self) -> io::Result<bool>;
}

#[stable(feature = "rust1", since = "1.0.0")]
impl OsStrExt for OsStr {
fn encode_wide(&self) -> EncodeWide<'_> {
self.as_inner().inner.encode_wide()
}

fn system_cmp(&self, other: &Self) -> io::Result<Ordering> {
crate::sys::compare_case_insensitive(self, other)
}

fn system_eq(&self, other: &Self) -> io::Result<bool> {
if self.len() == other.len() {
Ok(crate::sys::compare_case_insensitive(self, other)? == Ordering::Equal)
} else {
// The system implementation performs an "ordinal" check, so directly comparing every
// code unit in the same position in the two strings. As a consequence, two strings
// with different lengths can never be equal, even if they contain characters that
// change length when changing case according to Unicode.
Ok(false)
}
}
}
12 changes: 12 additions & 0 deletions library/std/src/sys/windows/c.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ pub type ADDRESS_FAMILY = USHORT;
pub const TRUE: BOOL = 1;
pub const FALSE: BOOL = 0;

pub const CSTR_LESS_THAN: c_int = 1;
pub const CSTR_EQUAL: c_int = 2;
pub const CSTR_GREATER_THAN: c_int = 3;

pub const FILE_ATTRIBUTE_READONLY: DWORD = 0x1;
pub const FILE_ATTRIBUTE_DIRECTORY: DWORD = 0x10;
pub const FILE_ATTRIBUTE_REPARSE_POINT: DWORD = 0x400;
Expand Down Expand Up @@ -996,6 +1000,14 @@ extern "system" {
pub fn ReleaseSRWLockShared(SRWLock: PSRWLOCK);
pub fn TryAcquireSRWLockExclusive(SRWLock: PSRWLOCK) -> BOOLEAN;
pub fn TryAcquireSRWLockShared(SRWLock: PSRWLOCK) -> BOOLEAN;

pub fn CompareStringOrdinal(
lpString1: LPCWSTR,
cchCount1: c_int,
lpString2: LPCWSTR,
cchCount2: c_int,
bIgnoreCase: BOOL,
) -> c_int;
}

#[link(name = "ws2_32")]
Expand Down
19 changes: 19 additions & 0 deletions library/std/src/sys/windows/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,25 @@ pub fn to_u16s<S: AsRef<OsStr>>(s: S) -> crate::io::Result<Vec<u16>> {
inner(s.as_ref())
}

pub fn compare_case_insensitive<A: AsRef<OsStr>, B: AsRef<OsStr>>(
a: A,
b: B,
) -> crate::io::Result<crate::cmp::Ordering> {
let a = crate::sys::to_u16s(a.as_ref())?;
let b = crate::sys::to_u16s(b.as_ref())?;

let result = unsafe {
c::CompareStringOrdinal(a.as_ptr(), a.len() as _, b.as_ptr(), b.len() as _, c::TRUE)
};

match result {
c::CSTR_LESS_THAN => Ok(crate::cmp::Ordering::Less),
c::CSTR_EQUAL => Ok(crate::cmp::Ordering::Equal),
c::CSTR_GREATER_THAN => Ok(crate::cmp::Ordering::Greater),
_ => Err(crate::io::Error::last_os_error()),
}
}

// Many Windows APIs follow a pattern of where we hand a buffer and then they
// will report back to us how large the buffer should be or how many bytes
// currently reside in the buffer. This function is an abstraction over these
Expand Down