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:
commit
8223831942
12 changed files with 224 additions and 78 deletions
22
library/std/src/net/hostname.rs
Normal file
22
library/std/src/net/hostname.rs
Normal 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()
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
14
library/std/src/sys/net/hostname/mod.rs
Normal file
14
library/std/src/sys/net/hostname/mod.rs
Normal 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;
|
||||
}
|
||||
}
|
||||
62
library/std/src/sys/net/hostname/unix.rs
Normal file
62
library/std/src/sys/net/hostname/unix.rs
Normal 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)?;
|
||||
}
|
||||
}
|
||||
6
library/std/src/sys/net/hostname/unsupported.rs
Normal file
6
library/std/src/sys/net/hostname/unsupported.rs
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
use crate::ffi::OsString;
|
||||
use crate::io::{Error, Result};
|
||||
|
||||
pub fn hostname() -> Result<OsString> {
|
||||
Err(Error::UNSUPPORTED_PLATFORM)
|
||||
}
|
||||
24
library/std/src/sys/net/hostname/windows.rs
Normal file
24
library/std/src/sys/net/hostname/windows.rs
Normal 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))
|
||||
}
|
||||
|
|
@ -2,3 +2,6 @@
|
|||
/// `UdpSocket` as well as related functionality like DNS resolving.
|
||||
mod connection;
|
||||
pub use connection::*;
|
||||
|
||||
mod hostname;
|
||||
pub use hostname::hostname;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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> {
|
||||
|
|
|
|||
80
library/std/src/sys/pal/windows/winsock.rs
Normal file
80
library/std/src/sys/pal/windows/winsock.rs
Normal 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())
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue