From 75cb22e68ff0de296abbc54a1f5b324fe7ea5269 Mon Sep 17 00:00:00 2001 From: George Tokmaji Date: Sat, 10 Jan 2026 03:35:44 +0100 Subject: [PATCH] Win: Add GetHostNameW fallback for win7 using gethostname --- library/std/src/net/hostname.rs | 3 +- library/std/src/sys/net/hostname/mod.rs | 3 +- library/std/src/sys/pal/windows/c.rs | 22 +++++-- .../std/src/sys/pal/windows/c/bindings.txt | 2 + .../std/src/sys/pal/windows/c/windows_sys.rs | 2 + library/std/src/sys/pal/windows/winsock.rs | 62 +++++++++++++++++++ 6 files changed, 87 insertions(+), 7 deletions(-) diff --git a/library/std/src/net/hostname.rs b/library/std/src/net/hostname.rs index 4042496d534fd..6cb418ea42349 100644 --- a/library/std/src/net/hostname.rs +++ b/library/std/src/net/hostname.rs @@ -11,7 +11,8 @@ use crate::ffi::OsString; /// | Platform | System call | /// |--------------|---------------------------------------------------------------------------------------------------------| /// | UNIX | [`gethostname`](https://www.man7.org/linux/man-pages/man2/gethostname.2.html) | -/// | Windows (8+) | [`GetHostNameW`](https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-gethostnamew) | +/// | Windows 8+ | [`GetHostNameW`](https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-gethostnamew) | +/// | Windows 7 | [`gethostname`](https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-gethostname) | /// /// Note that platform-specific behavior [may change in the future][changes]. /// diff --git a/library/std/src/sys/net/hostname/mod.rs b/library/std/src/sys/net/hostname/mod.rs index 65fcd6bfb00d7..b6844dd135454 100644 --- a/library/std/src/sys/net/hostname/mod.rs +++ b/library/std/src/sys/net/hostname/mod.rs @@ -3,8 +3,7 @@ cfg_select! { mod unix; pub use unix::hostname; } - // `GetHostNameW` is only available starting with Windows 8. - all(target_os = "windows", not(target_vendor = "win7")) => { + all(target_os = "windows") => { mod windows; pub use windows::hostname; } diff --git a/library/std/src/sys/pal/windows/c.rs b/library/std/src/sys/pal/windows/c.rs index 9f1a51c7dd3f6..2f122038030f9 100644 --- a/library/std/src/sys/pal/windows/c.rs +++ b/library/std/src/sys/pal/windows/c.rs @@ -227,6 +227,21 @@ compat_fn_with_fallback! { } } +#[cfg(target_vendor = "win7")] +compat_fn_with_fallback! { + pub static WS2_32: &CStr = c"ws2_32"; + + pub fn GetHostNameW(name: PWSTR, namelen: i32) -> i32 { + unsafe { + crate::sys::winsock::hostname_fallback(name, namelen) + } + } +} + +// Only available starting with Windows 8. +#[cfg(not(target_vendor = "win7"))] +windows_link::link!("ws2_32.dll" "system" fn GetHostNameW(name : PWSTR, namelen : i32) -> i32); + cfg_select! { target_vendor = "uwp" => { windows_link::link_raw_dylib!("ntdll.dll" "system" fn NtCreateFile(filehandle : *mut HANDLE, desiredaccess : FILE_ACCESS_RIGHTS, objectattributes : *const OBJECT_ATTRIBUTES, iostatusblock : *mut IO_STATUS_BLOCK, allocationsize : *const i64, fileattributes : FILE_FLAGS_AND_ATTRIBUTES, shareaccess : FILE_SHARE_MODE, createdisposition : NTCREATEFILE_CREATE_DISPOSITION, createoptions : NTCREATEFILE_CREATE_OPTIONS, eabuffer : *const core::ffi::c_void, ealength : u32) -> NTSTATUS); @@ -235,9 +250,8 @@ cfg_select! { windows_link::link_raw_dylib!("ntdll.dll" "system" fn NtWriteFile(filehandle : HANDLE, event : HANDLE, apcroutine : PIO_APC_ROUTINE, apccontext : *const core::ffi::c_void, iostatusblock : *mut IO_STATUS_BLOCK, buffer : *const core::ffi::c_void, length : u32, byteoffset : *const i64, key : *const u32) -> NTSTATUS); windows_link::link_raw_dylib!("ntdll.dll" "system" fn RtlNtStatusToDosError(status : NTSTATUS) -> u32); } + target_vendor = "win7" => { + windows_link::link!("ws2_32.dll" "system" fn gethostname(name : PSTR, namelen : i32) -> i32); + } _ => {} } - -// Only available starting with Windows 8. -#[cfg(not(target_vendor = "win7"))] -windows_link::link!("ws2_32.dll" "system" fn GetHostNameW(name : PWSTR, namelen : i32) -> i32); diff --git a/library/std/src/sys/pal/windows/c/bindings.txt b/library/std/src/sys/pal/windows/c/bindings.txt index 7b5abbc363786..204cf16459bb1 100644 --- a/library/std/src/sys/pal/windows/c/bindings.txt +++ b/library/std/src/sys/pal/windows/c/bindings.txt @@ -33,6 +33,7 @@ CONSOLE_MODE CONSOLE_READCONSOLE_CONTROL CONTEXT CopyFileExW +CP_ACP CP_UTF8 CREATE_ALWAYS CREATE_BREAKAWAY_FROM_JOB @@ -2624,6 +2625,7 @@ WSAPROTOCOLCHAIN WSARecv WSASend WSASERVICE_NOT_FOUND +WSASetLastError WSASocketW WSAStartup WSASYSCALLFAILURE diff --git a/library/std/src/sys/pal/windows/c/windows_sys.rs b/library/std/src/sys/pal/windows/c/windows_sys.rs index 7cef71097a784..b54657d17fde9 100644 --- a/library/std/src/sys/pal/windows/c/windows_sys.rs +++ b/library/std/src/sys/pal/windows/c/windows_sys.rs @@ -114,6 +114,7 @@ windows_link::link!("ws2_32.dll" "system" fn WSADuplicateSocketW(s : SOCKET, dwp windows_link::link!("ws2_32.dll" "system" fn WSAGetLastError() -> WSA_ERROR); windows_link::link!("ws2_32.dll" "system" fn WSARecv(s : SOCKET, lpbuffers : *const WSABUF, dwbuffercount : u32, lpnumberofbytesrecvd : *mut u32, lpflags : *mut u32, lpoverlapped : *mut OVERLAPPED, lpcompletionroutine : LPWSAOVERLAPPED_COMPLETION_ROUTINE) -> i32); windows_link::link!("ws2_32.dll" "system" fn WSASend(s : SOCKET, lpbuffers : *const WSABUF, dwbuffercount : u32, lpnumberofbytessent : *mut u32, dwflags : u32, lpoverlapped : *mut OVERLAPPED, lpcompletionroutine : LPWSAOVERLAPPED_COMPLETION_ROUTINE) -> i32); +windows_link::link!("ws2_32.dll" "system" fn WSASetLastError(ierror : i32)); windows_link::link!("ws2_32.dll" "system" fn WSASocketW(af : i32, r#type : i32, protocol : i32, lpprotocolinfo : *const WSAPROTOCOL_INFOW, g : u32, dwflags : u32) -> SOCKET); windows_link::link!("ws2_32.dll" "system" fn WSAStartup(wversionrequested : u16, lpwsadata : *mut WSADATA) -> i32); windows_link::link!("kernel32.dll" "system" fn WaitForMultipleObjects(ncount : u32, lphandles : *const HANDLE, bwaitall : BOOL, dwmilliseconds : u32) -> WAIT_EVENT); @@ -443,6 +444,7 @@ pub struct CONTEXT_0_0 { pub type CONTEXT_FLAGS = u32; pub type COPYFILE_FLAGS = u32; pub type COPYPROGRESSROUTINE_PROGRESS = u32; +pub const CP_ACP: u32 = 0u32; pub const CP_UTF8: u32 = 65001u32; pub const CREATE_ALWAYS: FILE_CREATION_DISPOSITION = 2u32; pub const CREATE_BREAKAWAY_FROM_JOB: PROCESS_CREATION_FLAGS = 16777216u32; diff --git a/library/std/src/sys/pal/windows/winsock.rs b/library/std/src/sys/pal/windows/winsock.rs index b110a43ef3aa8..4e3277bb93b8d 100644 --- a/library/std/src/sys/pal/windows/winsock.rs +++ b/library/std/src/sys/pal/windows/winsock.rs @@ -78,3 +78,65 @@ where { cvt(f()) } + +#[cfg(target_vendor = "win7")] +pub unsafe fn hostname_fallback(name: c::PWSTR, namelen: i32) -> i32 { + // The buffer needs to have space for at least the null byte. + if namelen < 1 { + // SAFETY: SetLastError is always safe to call. + unsafe { + c::WSASetLastError(c::WSAEFAULT); + } + + return c::SOCKET_ERROR; + } + + // The documentation of gethostname says that a buffer size of 256 is + // always enough. + let mut buffer = [const { mem::MaybeUninit::::uninit() }; 256]; + + // SAFETY: these parameters specify a valid, writable region of memory. + unsafe { + if c::gethostname(buffer.as_mut_ptr().cast(), buffer.len() as i32) == c::SOCKET_ERROR { + return c::SOCKET_ERROR; + } + } + + // Subtract one to leave space for the null terminator, as MultiByteToWideChar doesn't terminate the output buffer + // if the number of output characters is equal to the buffer length. + // SAFETY: The buffer is at least namelen characters large, thus namelen - 1 characters are always writable. + let len = unsafe { + c::MultiByteToWideChar( + c::CP_ACP, + c::MB_ERR_INVALID_CHARS, + buffer.as_mut_ptr().cast(), + -1, + name, + namelen - 1, + ) + }; + + if len == 0 { + // GetHostNameW reports WSAEFAULT if the buffer is too small, and WSAENETDOWN on internal errors. + // SAFETY: GetLastError and SetLastError are always safe to call. + unsafe { + if c::WSAGetLastError() == c::ERROR_INSUFFICIENT_BUFFER as _ { + c::WSASetLastError(c::WSAEFAULT); + } else { + c::WSASetLastError(c::WSAENETDOWN); + } + } + return c::SOCKET_ERROR; + } + + // Ensure the output is always null terminated. + // If MultiByteToWideChar has already written a null terminator, that null terminator will be included in len + // and this will add a second one, but writing a zero is cheap enough to omit the length comparison. + // SAFETY: len is always less than namelen as MultiByteToWideChar only writes namelen - 1 characters. + unsafe { + name.add(len as _).write(0); + } + + // Success + 0 +}