Rollup merge of #146937 - joboet:gethostname, r=Mark-Simulacrum

std: implement `hostname`

Resolves https://github.com/rust-lang/libs-team/issues/330
Tracking issue: https://github.com/rust-lang/rust/issues/135142

This is based on rust-lang/rust#135141, but I've reimplemented the UNIX version, which now:
* uses `sysconf(_SC_HOST_NAME_MAX)` as an initial buffer length
* returns `OutOfMemory` if the `Vec` allocation fails
* retries the operation if it detects that the name returned by `gethostname` was truncated

Additionally, as part of the rebase, I had to move some WinSock abstractions (initialisation and error access) to `sys::pal` so that they can be accessed from `sys::net::hostname`.

CC ``@orowith2os`` (and thank you for your work!)
This commit is contained in:
Matthias Krüger 2025-09-29 21:42:41 +02:00 committed by GitHub
commit 8223831942
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 224 additions and 78 deletions

View file

@ -0,0 +1,22 @@
use crate::ffi::OsString;
/// Returns the system hostname.
///
/// This can error out in platform-specific error cases;
/// for example, uefi and wasm, where hostnames aren't
/// supported.
///
/// # Underlying system calls
///
/// | Platform | System call |
/// |----------|---------------------------------------------------------------------------------------------------------|
/// | UNIX | [`gethostname`](https://www.man7.org/linux/man-pages/man2/gethostname.2.html) |
/// | Windows | [`GetHostNameW`](https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-gethostnamew) |
///
/// Note that platform-specific behavior [may change in the future][changes].
///
/// [changes]: crate::io#platform-specific-behavior
#[unstable(feature = "gethostname", issue = "135142")]
pub fn hostname() -> crate::io::Result<OsString> {
crate::sys::net::hostname()
}

View file

@ -1,7 +1,8 @@
//! Networking primitives for TCP/UDP communication.
//!
//! This module provides networking functionality for the Transmission Control and User
//! Datagram Protocols, as well as types for IP and socket addresses.
//! Datagram Protocols, as well as types for IP and socket addresses and functions related
//! to network properties.
//!
//! # Organization
//!
@ -24,6 +25,8 @@
#[stable(feature = "rust1", since = "1.0.0")]
pub use core::net::AddrParseError;
#[unstable(feature = "gethostname", issue = "135142")]
pub use self::hostname::hostname;
#[stable(feature = "rust1", since = "1.0.0")]
pub use self::ip_addr::{IpAddr, Ipv4Addr, Ipv6Addr, Ipv6MulticastScope};
#[stable(feature = "rust1", since = "1.0.0")]
@ -35,6 +38,7 @@ pub use self::tcp::{Incoming, TcpListener, TcpStream};
#[stable(feature = "rust1", since = "1.0.0")]
pub use self::udp::UdpSocket;
mod hostname;
mod ip_addr;
mod socket_addr;
mod tcp;

View file

@ -8,9 +8,8 @@ use crate::net::{Shutdown, SocketAddr};
use crate::os::windows::io::{
AsRawSocket, AsSocket, BorrowedSocket, FromRawSocket, IntoRawSocket, OwnedSocket, RawSocket,
};
use crate::sync::atomic::Atomic;
use crate::sync::atomic::Ordering::{AcqRel, Relaxed};
use crate::sys::c;
use crate::sys::pal::winsock::last_error;
use crate::sys_common::{AsInner, FromInner, IntoInner};
use crate::time::Duration;
use crate::{cmp, mem, ptr, sys};
@ -112,84 +111,11 @@ pub(super) mod netc {
}
}
pub use crate::sys::pal::winsock::{cleanup, cvt, cvt_gai, cvt_r, startup as init};
#[expect(missing_debug_implementations)]
pub struct Socket(OwnedSocket);
static WSA_INITIALIZED: Atomic<bool> = Atomic::<bool>::new(false);
/// Checks whether the Windows socket interface has been started already, and
/// if not, starts it.
#[inline]
pub fn init() {
if !WSA_INITIALIZED.load(Relaxed) {
wsa_startup();
}
}
#[cold]
fn wsa_startup() {
unsafe {
let mut data: c::WSADATA = mem::zeroed();
let ret = c::WSAStartup(
0x202, // version 2.2
&mut data,
);
assert_eq!(ret, 0);
if WSA_INITIALIZED.swap(true, AcqRel) {
// If another thread raced with us and called WSAStartup first then call
// WSACleanup so it's as though WSAStartup was only called once.
c::WSACleanup();
}
}
}
pub fn cleanup() {
// We don't need to call WSACleanup here because exiting the process will cause
// the OS to clean everything for us, which is faster than doing it manually.
// See #141799.
}
/// Returns the last error from the Windows socket interface.
fn last_error() -> io::Error {
io::Error::from_raw_os_error(unsafe { c::WSAGetLastError() })
}
#[doc(hidden)]
pub trait IsMinusOne {
fn is_minus_one(&self) -> bool;
}
macro_rules! impl_is_minus_one {
($($t:ident)*) => ($(impl IsMinusOne for $t {
fn is_minus_one(&self) -> bool {
*self == -1
}
})*)
}
impl_is_minus_one! { i8 i16 i32 i64 isize }
/// Checks if the signed integer is the Windows constant `SOCKET_ERROR` (-1)
/// and if so, returns the last error from the Windows socket interface. This
/// function must be called before another call to the socket API is made.
pub fn cvt<T: IsMinusOne>(t: T) -> io::Result<T> {
if t.is_minus_one() { Err(last_error()) } else { Ok(t) }
}
/// A variant of `cvt` for `getaddrinfo` which return 0 for a success.
pub fn cvt_gai(err: c_int) -> io::Result<()> {
if err == 0 { Ok(()) } else { Err(last_error()) }
}
/// Just to provide the same interface as sys/pal/unix/net.rs
pub fn cvt_r<T, F>(mut f: F) -> io::Result<T>
where
T: IsMinusOne,
F: FnMut() -> T,
{
cvt(f())
}
impl Socket {
pub fn new(addr: &SocketAddr, ty: c_int) -> io::Result<Socket> {
let family = match *addr {

View file

@ -0,0 +1,14 @@
cfg_select! {
target_family = "unix" => {
mod unix;
pub use unix::hostname;
}
target_os = "windows" => {
mod windows;
pub use windows::hostname;
}
_ => {
mod unsupported;
pub use unsupported::hostname;
}
}

View file

@ -0,0 +1,62 @@
use crate::ffi::OsString;
use crate::io;
use crate::os::unix::ffi::OsStringExt;
use crate::sys::pal::os::errno;
pub fn hostname() -> io::Result<OsString> {
// Query the system for the maximum host name length.
let host_name_max = match unsafe { libc::sysconf(libc::_SC_HOST_NAME_MAX) } {
// If this fails (possibly because there is no maximum length), then
// assume a maximum length of _POSIX_HOST_NAME_MAX (255).
-1 => 255,
max => max as usize,
};
// Reserve space for the nul terminator too.
let mut buf = Vec::<u8>::try_with_capacity(host_name_max + 1)?;
loop {
// SAFETY: `buf.capacity()` bytes of `buf` are writable.
let r = unsafe { libc::gethostname(buf.as_mut_ptr().cast(), buf.capacity()) };
match (r != 0).then(errno) {
None => {
// Unfortunately, the UNIX specification says that the name will
// be truncated if it does not fit in the buffer, without returning
// an error. As additionally, the truncated name may still be null-
// terminated, there is no reliable way to detect truncation.
// Fortunately, most platforms ignore what the specification says
// and return an error (mostly ENAMETOOLONG). Should that not be
// the case, the following detects truncation if the null-terminator
// was omitted. Note that this check does not impact performance at
// all as we need to find the length of the string anyways.
//
// Use `strnlen` as it does not place an initialization requirement
// on the bytes after the nul terminator.
//
// SAFETY: `buf.capacity()` bytes of `buf` are accessible, and are
// initialized up to and including a possible nul terminator.
let len = unsafe { libc::strnlen(buf.as_ptr().cast(), buf.capacity()) };
if len < buf.capacity() {
// If the string is nul-terminated, we assume that is has not
// been truncated, as the capacity *should be* enough to hold
// `HOST_NAME_MAX` bytes.
// SAFETY: `len + 1` bytes have been initialized (we exclude
// the nul terminator from the string).
unsafe { buf.set_len(len) };
return Ok(OsString::from_vec(buf));
}
}
// As `buf.capacity()` is always less than or equal to `isize::MAX`
// (Rust allocations cannot exceed that limit), the only way `EINVAL`
// can be returned is if the system uses `EINVAL` to report that the
// name does not fit in the provided buffer. In that case (or in the
// case of `ENAMETOOLONG`), resize the buffer and try again.
Some(libc::EINVAL | libc::ENAMETOOLONG) => {}
// Other error codes (e.g. EPERM) have nothing to do with the buffer
// size and should be returned to the user.
Some(err) => return Err(io::Error::from_raw_os_error(err)),
}
// Resize the buffer (according to `Vec`'s resizing rules) and try again.
buf.try_reserve(buf.capacity() + 1)?;
}
}

View file

@ -0,0 +1,6 @@
use crate::ffi::OsString;
use crate::io::{Error, Result};
pub fn hostname() -> Result<OsString> {
Err(Error::UNSUPPORTED_PLATFORM)
}

View file

@ -0,0 +1,24 @@
use crate::ffi::OsString;
use crate::io::Result;
use crate::mem::MaybeUninit;
use crate::os::windows::ffi::OsStringExt;
use crate::sys::pal::c;
use crate::sys::pal::winsock::{self, cvt};
pub fn hostname() -> Result<OsString> {
winsock::startup();
// The documentation of GetHostNameW says that a buffer size of 256 is
// always enough.
let mut buffer = [const { MaybeUninit::<u16>::uninit() }; 256];
// SAFETY: these parameters specify a valid, writable region of memory.
cvt(unsafe { c::GetHostNameW(buffer.as_mut_ptr().cast(), buffer.len() as i32) })?;
// Use `lstrlenW` here as it does not require the bytes after the nul
// terminator to be initialized.
// SAFETY: if `GetHostNameW` returns successfully, the name is nul-terminated.
let len = unsafe { c::lstrlenW(buffer.as_ptr().cast()) };
// SAFETY: the length of the name is `len`, hence `len` bytes have been
// initialized by `GetHostNameW`.
let name = unsafe { buffer[..len as usize].assume_init_ref() };
Ok(OsString::from_wide(name))
}

View file

@ -2,3 +2,6 @@
/// `UdpSocket` as well as related functionality like DNS resolving.
mod connection;
pub use connection::*;
mod hostname;
pub use hostname::hostname;

View file

@ -2170,6 +2170,7 @@ GetFileType
GETFINALPATHNAMEBYHANDLE_FLAGS
GetFinalPathNameByHandleW
GetFullPathNameW
GetHostNameW
GetLastError
GetModuleFileNameW
GetModuleHandleA
@ -2270,6 +2271,7 @@ LPPROGRESS_ROUTINE
LPPROGRESS_ROUTINE_CALLBACK_REASON
LPTHREAD_START_ROUTINE
LPWSAOVERLAPPED_COMPLETION_ROUTINE
lstrlenW
M128A
MAX_PATH
MAXIMUM_REPARSE_DATA_BUFFER_SIZE

View file

@ -49,6 +49,7 @@ windows_targets::link!("kernel32.dll" "system" fn GetFileSizeEx(hfile : HANDLE,
windows_targets::link!("kernel32.dll" "system" fn GetFileType(hfile : HANDLE) -> FILE_TYPE);
windows_targets::link!("kernel32.dll" "system" fn GetFinalPathNameByHandleW(hfile : HANDLE, lpszfilepath : PWSTR, cchfilepath : u32, dwflags : GETFINALPATHNAMEBYHANDLE_FLAGS) -> u32);
windows_targets::link!("kernel32.dll" "system" fn GetFullPathNameW(lpfilename : PCWSTR, nbufferlength : u32, lpbuffer : PWSTR, lpfilepart : *mut PWSTR) -> u32);
windows_targets::link!("ws2_32.dll" "system" fn GetHostNameW(name : PWSTR, namelen : i32) -> i32);
windows_targets::link!("kernel32.dll" "system" fn GetLastError() -> WIN32_ERROR);
windows_targets::link!("kernel32.dll" "system" fn GetModuleFileNameW(hmodule : HMODULE, lpfilename : PWSTR, nsize : u32) -> u32);
windows_targets::link!("kernel32.dll" "system" fn GetModuleHandleA(lpmodulename : PCSTR) -> HMODULE);
@ -134,6 +135,7 @@ windows_targets::link!("ws2_32.dll" "system" fn getsockname(s : SOCKET, name : *
windows_targets::link!("ws2_32.dll" "system" fn getsockopt(s : SOCKET, level : i32, optname : i32, optval : PSTR, optlen : *mut i32) -> i32);
windows_targets::link!("ws2_32.dll" "system" fn ioctlsocket(s : SOCKET, cmd : i32, argp : *mut u32) -> i32);
windows_targets::link!("ws2_32.dll" "system" fn listen(s : SOCKET, backlog : i32) -> i32);
windows_targets::link!("kernel32.dll" "system" fn lstrlenW(lpstring : PCWSTR) -> i32);
windows_targets::link!("ws2_32.dll" "system" fn recv(s : SOCKET, buf : PSTR, len : i32, flags : SEND_RECV_FLAGS) -> i32);
windows_targets::link!("ws2_32.dll" "system" fn recvfrom(s : SOCKET, buf : PSTR, len : i32, flags : i32, from : *mut SOCKADDR, fromlen : *mut i32) -> i32);
windows_targets::link!("ws2_32.dll" "system" fn select(nfds : i32, readfds : *mut FD_SET, writefds : *mut FD_SET, exceptfds : *mut FD_SET, timeout : *const TIMEVAL) -> i32);

View file

@ -31,6 +31,7 @@ cfg_select! {
pub use self::stack_overflow_uwp as stack_overflow;
}
}
pub mod winsock;
/// Map a [`Result<T, WinError>`] to [`io::Result<T>`](crate::io::Result<T>).
pub trait IoResult<T> {

View file

@ -0,0 +1,80 @@
use super::c;
use crate::ffi::c_int;
use crate::sync::atomic::Atomic;
use crate::sync::atomic::Ordering::{AcqRel, Relaxed};
use crate::{io, mem};
static WSA_STARTED: Atomic<bool> = Atomic::<bool>::new(false);
/// Checks whether the Windows socket interface has been started already, and
/// if not, starts it.
#[inline]
pub fn startup() {
if !WSA_STARTED.load(Relaxed) {
wsa_startup();
}
}
#[cold]
fn wsa_startup() {
unsafe {
let mut data: c::WSADATA = mem::zeroed();
let ret = c::WSAStartup(
0x202, // version 2.2
&mut data,
);
assert_eq!(ret, 0);
if WSA_STARTED.swap(true, AcqRel) {
// If another thread raced with us and called WSAStartup first then call
// WSACleanup so it's as though WSAStartup was only called once.
c::WSACleanup();
}
}
}
pub fn cleanup() {
// We don't need to call WSACleanup here because exiting the process will cause
// the OS to clean everything for us, which is faster than doing it manually.
// See #141799.
}
/// Returns the last error from the Windows socket interface.
pub fn last_error() -> io::Error {
io::Error::from_raw_os_error(unsafe { c::WSAGetLastError() })
}
#[doc(hidden)]
pub trait IsMinusOne {
fn is_minus_one(&self) -> bool;
}
macro_rules! impl_is_minus_one {
($($t:ident)*) => ($(impl IsMinusOne for $t {
fn is_minus_one(&self) -> bool {
*self == -1
}
})*)
}
impl_is_minus_one! { i8 i16 i32 i64 isize }
/// Checks if the signed integer is the Windows constant `SOCKET_ERROR` (-1)
/// and if so, returns the last error from the Windows socket interface. This
/// function must be called before another call to the socket API is made.
pub fn cvt<T: IsMinusOne>(t: T) -> io::Result<T> {
if t.is_minus_one() { Err(last_error()) } else { Ok(t) }
}
/// A variant of `cvt` for `getaddrinfo` which return 0 for a success.
pub fn cvt_gai(err: c_int) -> io::Result<()> {
if err == 0 { Ok(()) } else { Err(last_error()) }
}
/// Just to provide the same interface as sys/pal/unix/net.rs
pub fn cvt_r<T, F>(mut f: F) -> io::Result<T>
where
T: IsMinusOne,
F: FnMut() -> T,
{
cvt(f())
}