diff --git a/library/std/src/net/hostname.rs b/library/std/src/net/hostname.rs new file mode 100644 index 0000000000000..b1010cec60058 --- /dev/null +++ b/library/std/src/net/hostname.rs @@ -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 { + crate::sys::net::hostname() +} diff --git a/library/std/src/net/mod.rs b/library/std/src/net/mod.rs index 40f1a93e39ded..3e4447eb33f2a 100644 --- a/library/std/src/net/mod.rs +++ b/library/std/src/net/mod.rs @@ -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; diff --git a/library/std/src/sys/net/connection/socket/windows.rs b/library/std/src/sys/net/connection/socket/windows.rs index b71d8b1357b5a..5b6f4cedf1b77 100644 --- a/library/std/src/sys/net/connection/socket/windows.rs +++ b/library/std/src/sys/net/connection/socket/windows.rs @@ -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 = Atomic::::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: T) -> io::Result { - 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(mut f: F) -> io::Result -where - T: IsMinusOne, - F: FnMut() -> T, -{ - cvt(f()) -} - impl Socket { pub fn new(addr: &SocketAddr, ty: c_int) -> io::Result { let family = match *addr { diff --git a/library/std/src/sys/net/hostname/mod.rs b/library/std/src/sys/net/hostname/mod.rs new file mode 100644 index 0000000000000..a4b5b76059d30 --- /dev/null +++ b/library/std/src/sys/net/hostname/mod.rs @@ -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; + } +} diff --git a/library/std/src/sys/net/hostname/unix.rs b/library/std/src/sys/net/hostname/unix.rs new file mode 100644 index 0000000000000..bc6fa82a38f0d --- /dev/null +++ b/library/std/src/sys/net/hostname/unix.rs @@ -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 { + // 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::::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)?; + } +} diff --git a/library/std/src/sys/net/hostname/unsupported.rs b/library/std/src/sys/net/hostname/unsupported.rs new file mode 100644 index 0000000000000..d868f68f32dd2 --- /dev/null +++ b/library/std/src/sys/net/hostname/unsupported.rs @@ -0,0 +1,6 @@ +use crate::ffi::OsString; +use crate::io::{Error, Result}; + +pub fn hostname() -> Result { + Err(Error::UNSUPPORTED_PLATFORM) +} diff --git a/library/std/src/sys/net/hostname/windows.rs b/library/std/src/sys/net/hostname/windows.rs new file mode 100644 index 0000000000000..24eed100f32d4 --- /dev/null +++ b/library/std/src/sys/net/hostname/windows.rs @@ -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 { + winsock::startup(); + + // The documentation of GetHostNameW says that a buffer size of 256 is + // always enough. + let mut buffer = [const { MaybeUninit::::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)) +} diff --git a/library/std/src/sys/net/mod.rs b/library/std/src/sys/net/mod.rs index dffc4ea7f81ad..bfe5cf5312875 100644 --- a/library/std/src/sys/net/mod.rs +++ b/library/std/src/sys/net/mod.rs @@ -2,3 +2,6 @@ /// `UdpSocket` as well as related functionality like DNS resolving. mod connection; pub use connection::*; + +mod hostname; +pub use hostname::hostname; diff --git a/library/std/src/sys/pal/windows/c/bindings.txt b/library/std/src/sys/pal/windows/c/bindings.txt index abc1c19827fe7..9009aa09f48ed 100644 --- a/library/std/src/sys/pal/windows/c/bindings.txt +++ b/library/std/src/sys/pal/windows/c/bindings.txt @@ -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 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 989a1246650cd..98f277b33780c 100644 --- a/library/std/src/sys/pal/windows/c/windows_sys.rs +++ b/library/std/src/sys/pal/windows/c/windows_sys.rs @@ -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); diff --git a/library/std/src/sys/pal/windows/mod.rs b/library/std/src/sys/pal/windows/mod.rs index 3357946b8f71d..85a4d3ca6db27 100644 --- a/library/std/src/sys/pal/windows/mod.rs +++ b/library/std/src/sys/pal/windows/mod.rs @@ -30,6 +30,7 @@ cfg_select! { pub use self::stack_overflow_uwp as stack_overflow; } } +pub mod winsock; /// Map a [`Result`] to [`io::Result`](crate::io::Result). pub trait IoResult { diff --git a/library/std/src/sys/pal/windows/winsock.rs b/library/std/src/sys/pal/windows/winsock.rs new file mode 100644 index 0000000000000..b110a43ef3aa8 --- /dev/null +++ b/library/std/src/sys/pal/windows/winsock.rs @@ -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 = Atomic::::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: T) -> io::Result { + 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(mut f: F) -> io::Result +where + T: IsMinusOne, + F: FnMut() -> T, +{ + cvt(f()) +}