Skip to content

Commit 8223831

Browse files
authored
Rollup merge of rust-lang#146937 - joboet:gethostname, r=Mark-Simulacrum
std: implement `hostname` Resolves rust-lang/libs-team#330 Tracking issue: rust-lang#135142 This is based on rust-lang#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!)
2 parents dc2c356 + 97333f8 commit 8223831

File tree

12 files changed

+224
-78
lines changed

12 files changed

+224
-78
lines changed

library/std/src/net/hostname.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
use crate::ffi::OsString;
2+
3+
/// Returns the system hostname.
4+
///
5+
/// This can error out in platform-specific error cases;
6+
/// for example, uefi and wasm, where hostnames aren't
7+
/// supported.
8+
///
9+
/// # Underlying system calls
10+
///
11+
/// | Platform | System call |
12+
/// |----------|---------------------------------------------------------------------------------------------------------|
13+
/// | UNIX | [`gethostname`](https://www.man7.org/linux/man-pages/man2/gethostname.2.html) |
14+
/// | Windows | [`GetHostNameW`](https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-gethostnamew) |
15+
///
16+
/// Note that platform-specific behavior [may change in the future][changes].
17+
///
18+
/// [changes]: crate::io#platform-specific-behavior
19+
#[unstable(feature = "gethostname", issue = "135142")]
20+
pub fn hostname() -> crate::io::Result<OsString> {
21+
crate::sys::net::hostname()
22+
}

library/std/src/net/mod.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
//! Networking primitives for TCP/UDP communication.
22
//!
33
//! This module provides networking functionality for the Transmission Control and User
4-
//! Datagram Protocols, as well as types for IP and socket addresses.
4+
//! Datagram Protocols, as well as types for IP and socket addresses and functions related
5+
//! to network properties.
56
//!
67
//! # Organization
78
//!
@@ -24,6 +25,8 @@
2425
#[stable(feature = "rust1", since = "1.0.0")]
2526
pub use core::net::AddrParseError;
2627

28+
#[unstable(feature = "gethostname", issue = "135142")]
29+
pub use self::hostname::hostname;
2730
#[stable(feature = "rust1", since = "1.0.0")]
2831
pub use self::ip_addr::{IpAddr, Ipv4Addr, Ipv6Addr, Ipv6MulticastScope};
2932
#[stable(feature = "rust1", since = "1.0.0")]
@@ -35,6 +38,7 @@ pub use self::tcp::{Incoming, TcpListener, TcpStream};
3538
#[stable(feature = "rust1", since = "1.0.0")]
3639
pub use self::udp::UdpSocket;
3740

41+
mod hostname;
3842
mod ip_addr;
3943
mod socket_addr;
4044
mod tcp;

library/std/src/sys/net/connection/socket/windows.rs

Lines changed: 3 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,8 @@ use crate::net::{Shutdown, SocketAddr};
88
use crate::os::windows::io::{
99
AsRawSocket, AsSocket, BorrowedSocket, FromRawSocket, IntoRawSocket, OwnedSocket, RawSocket,
1010
};
11-
use crate::sync::atomic::Atomic;
12-
use crate::sync::atomic::Ordering::{AcqRel, Relaxed};
1311
use crate::sys::c;
12+
use crate::sys::pal::winsock::last_error;
1413
use crate::sys_common::{AsInner, FromInner, IntoInner};
1514
use crate::time::Duration;
1615
use crate::{cmp, mem, ptr, sys};
@@ -112,84 +111,11 @@ pub(super) mod netc {
112111
}
113112
}
114113

114+
pub use crate::sys::pal::winsock::{cleanup, cvt, cvt_gai, cvt_r, startup as init};
115+
115116
#[expect(missing_debug_implementations)]
116117
pub struct Socket(OwnedSocket);
117118

118-
static WSA_INITIALIZED: Atomic<bool> = Atomic::<bool>::new(false);
119-
120-
/// Checks whether the Windows socket interface has been started already, and
121-
/// if not, starts it.
122-
#[inline]
123-
pub fn init() {
124-
if !WSA_INITIALIZED.load(Relaxed) {
125-
wsa_startup();
126-
}
127-
}
128-
129-
#[cold]
130-
fn wsa_startup() {
131-
unsafe {
132-
let mut data: c::WSADATA = mem::zeroed();
133-
let ret = c::WSAStartup(
134-
0x202, // version 2.2
135-
&mut data,
136-
);
137-
assert_eq!(ret, 0);
138-
if WSA_INITIALIZED.swap(true, AcqRel) {
139-
// If another thread raced with us and called WSAStartup first then call
140-
// WSACleanup so it's as though WSAStartup was only called once.
141-
c::WSACleanup();
142-
}
143-
}
144-
}
145-
146-
pub fn cleanup() {
147-
// We don't need to call WSACleanup here because exiting the process will cause
148-
// the OS to clean everything for us, which is faster than doing it manually.
149-
// See #141799.
150-
}
151-
152-
/// Returns the last error from the Windows socket interface.
153-
fn last_error() -> io::Error {
154-
io::Error::from_raw_os_error(unsafe { c::WSAGetLastError() })
155-
}
156-
157-
#[doc(hidden)]
158-
pub trait IsMinusOne {
159-
fn is_minus_one(&self) -> bool;
160-
}
161-
162-
macro_rules! impl_is_minus_one {
163-
($($t:ident)*) => ($(impl IsMinusOne for $t {
164-
fn is_minus_one(&self) -> bool {
165-
*self == -1
166-
}
167-
})*)
168-
}
169-
170-
impl_is_minus_one! { i8 i16 i32 i64 isize }
171-
172-
/// Checks if the signed integer is the Windows constant `SOCKET_ERROR` (-1)
173-
/// and if so, returns the last error from the Windows socket interface. This
174-
/// function must be called before another call to the socket API is made.
175-
pub fn cvt<T: IsMinusOne>(t: T) -> io::Result<T> {
176-
if t.is_minus_one() { Err(last_error()) } else { Ok(t) }
177-
}
178-
179-
/// A variant of `cvt` for `getaddrinfo` which return 0 for a success.
180-
pub fn cvt_gai(err: c_int) -> io::Result<()> {
181-
if err == 0 { Ok(()) } else { Err(last_error()) }
182-
}
183-
184-
/// Just to provide the same interface as sys/pal/unix/net.rs
185-
pub fn cvt_r<T, F>(mut f: F) -> io::Result<T>
186-
where
187-
T: IsMinusOne,
188-
F: FnMut() -> T,
189-
{
190-
cvt(f())
191-
}
192-
193119
impl Socket {
194120
pub fn new(addr: &SocketAddr, ty: c_int) -> io::Result<Socket> {
195121
let family = match *addr {
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
cfg_select! {
2+
target_family = "unix" => {
3+
mod unix;
4+
pub use unix::hostname;
5+
}
6+
target_os = "windows" => {
7+
mod windows;
8+
pub use windows::hostname;
9+
}
10+
_ => {
11+
mod unsupported;
12+
pub use unsupported::hostname;
13+
}
14+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
use crate::ffi::OsString;
2+
use crate::io;
3+
use crate::os::unix::ffi::OsStringExt;
4+
use crate::sys::pal::os::errno;
5+
6+
pub fn hostname() -> io::Result<OsString> {
7+
// Query the system for the maximum host name length.
8+
let host_name_max = match unsafe { libc::sysconf(libc::_SC_HOST_NAME_MAX) } {
9+
// If this fails (possibly because there is no maximum length), then
10+
// assume a maximum length of _POSIX_HOST_NAME_MAX (255).
11+
-1 => 255,
12+
max => max as usize,
13+
};
14+
15+
// Reserve space for the nul terminator too.
16+
let mut buf = Vec::<u8>::try_with_capacity(host_name_max + 1)?;
17+
loop {
18+
// SAFETY: `buf.capacity()` bytes of `buf` are writable.
19+
let r = unsafe { libc::gethostname(buf.as_mut_ptr().cast(), buf.capacity()) };
20+
match (r != 0).then(errno) {
21+
None => {
22+
// Unfortunately, the UNIX specification says that the name will
23+
// be truncated if it does not fit in the buffer, without returning
24+
// an error. As additionally, the truncated name may still be null-
25+
// terminated, there is no reliable way to detect truncation.
26+
// Fortunately, most platforms ignore what the specification says
27+
// and return an error (mostly ENAMETOOLONG). Should that not be
28+
// the case, the following detects truncation if the null-terminator
29+
// was omitted. Note that this check does not impact performance at
30+
// all as we need to find the length of the string anyways.
31+
//
32+
// Use `strnlen` as it does not place an initialization requirement
33+
// on the bytes after the nul terminator.
34+
//
35+
// SAFETY: `buf.capacity()` bytes of `buf` are accessible, and are
36+
// initialized up to and including a possible nul terminator.
37+
let len = unsafe { libc::strnlen(buf.as_ptr().cast(), buf.capacity()) };
38+
if len < buf.capacity() {
39+
// If the string is nul-terminated, we assume that is has not
40+
// been truncated, as the capacity *should be* enough to hold
41+
// `HOST_NAME_MAX` bytes.
42+
// SAFETY: `len + 1` bytes have been initialized (we exclude
43+
// the nul terminator from the string).
44+
unsafe { buf.set_len(len) };
45+
return Ok(OsString::from_vec(buf));
46+
}
47+
}
48+
// As `buf.capacity()` is always less than or equal to `isize::MAX`
49+
// (Rust allocations cannot exceed that limit), the only way `EINVAL`
50+
// can be returned is if the system uses `EINVAL` to report that the
51+
// name does not fit in the provided buffer. In that case (or in the
52+
// case of `ENAMETOOLONG`), resize the buffer and try again.
53+
Some(libc::EINVAL | libc::ENAMETOOLONG) => {}
54+
// Other error codes (e.g. EPERM) have nothing to do with the buffer
55+
// size and should be returned to the user.
56+
Some(err) => return Err(io::Error::from_raw_os_error(err)),
57+
}
58+
59+
// Resize the buffer (according to `Vec`'s resizing rules) and try again.
60+
buf.try_reserve(buf.capacity() + 1)?;
61+
}
62+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
use crate::ffi::OsString;
2+
use crate::io::{Error, Result};
3+
4+
pub fn hostname() -> Result<OsString> {
5+
Err(Error::UNSUPPORTED_PLATFORM)
6+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
use crate::ffi::OsString;
2+
use crate::io::Result;
3+
use crate::mem::MaybeUninit;
4+
use crate::os::windows::ffi::OsStringExt;
5+
use crate::sys::pal::c;
6+
use crate::sys::pal::winsock::{self, cvt};
7+
8+
pub fn hostname() -> Result<OsString> {
9+
winsock::startup();
10+
11+
// The documentation of GetHostNameW says that a buffer size of 256 is
12+
// always enough.
13+
let mut buffer = [const { MaybeUninit::<u16>::uninit() }; 256];
14+
// SAFETY: these parameters specify a valid, writable region of memory.
15+
cvt(unsafe { c::GetHostNameW(buffer.as_mut_ptr().cast(), buffer.len() as i32) })?;
16+
// Use `lstrlenW` here as it does not require the bytes after the nul
17+
// terminator to be initialized.
18+
// SAFETY: if `GetHostNameW` returns successfully, the name is nul-terminated.
19+
let len = unsafe { c::lstrlenW(buffer.as_ptr().cast()) };
20+
// SAFETY: the length of the name is `len`, hence `len` bytes have been
21+
// initialized by `GetHostNameW`.
22+
let name = unsafe { buffer[..len as usize].assume_init_ref() };
23+
Ok(OsString::from_wide(name))
24+
}

library/std/src/sys/net/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,6 @@
22
/// `UdpSocket` as well as related functionality like DNS resolving.
33
mod connection;
44
pub use connection::*;
5+
6+
mod hostname;
7+
pub use hostname::hostname;

library/std/src/sys/pal/windows/c/bindings.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2170,6 +2170,7 @@ GetFileType
21702170
GETFINALPATHNAMEBYHANDLE_FLAGS
21712171
GetFinalPathNameByHandleW
21722172
GetFullPathNameW
2173+
GetHostNameW
21732174
GetLastError
21742175
GetModuleFileNameW
21752176
GetModuleHandleA
@@ -2270,6 +2271,7 @@ LPPROGRESS_ROUTINE
22702271
LPPROGRESS_ROUTINE_CALLBACK_REASON
22712272
LPTHREAD_START_ROUTINE
22722273
LPWSAOVERLAPPED_COMPLETION_ROUTINE
2274+
lstrlenW
22732275
M128A
22742276
MAX_PATH
22752277
MAXIMUM_REPARSE_DATA_BUFFER_SIZE

library/std/src/sys/pal/windows/c/windows_sys.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ windows_targets::link!("kernel32.dll" "system" fn GetFileSizeEx(hfile : HANDLE,
4949
windows_targets::link!("kernel32.dll" "system" fn GetFileType(hfile : HANDLE) -> FILE_TYPE);
5050
windows_targets::link!("kernel32.dll" "system" fn GetFinalPathNameByHandleW(hfile : HANDLE, lpszfilepath : PWSTR, cchfilepath : u32, dwflags : GETFINALPATHNAMEBYHANDLE_FLAGS) -> u32);
5151
windows_targets::link!("kernel32.dll" "system" fn GetFullPathNameW(lpfilename : PCWSTR, nbufferlength : u32, lpbuffer : PWSTR, lpfilepart : *mut PWSTR) -> u32);
52+
windows_targets::link!("ws2_32.dll" "system" fn GetHostNameW(name : PWSTR, namelen : i32) -> i32);
5253
windows_targets::link!("kernel32.dll" "system" fn GetLastError() -> WIN32_ERROR);
5354
windows_targets::link!("kernel32.dll" "system" fn GetModuleFileNameW(hmodule : HMODULE, lpfilename : PWSTR, nsize : u32) -> u32);
5455
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 : *
134135
windows_targets::link!("ws2_32.dll" "system" fn getsockopt(s : SOCKET, level : i32, optname : i32, optval : PSTR, optlen : *mut i32) -> i32);
135136
windows_targets::link!("ws2_32.dll" "system" fn ioctlsocket(s : SOCKET, cmd : i32, argp : *mut u32) -> i32);
136137
windows_targets::link!("ws2_32.dll" "system" fn listen(s : SOCKET, backlog : i32) -> i32);
138+
windows_targets::link!("kernel32.dll" "system" fn lstrlenW(lpstring : PCWSTR) -> i32);
137139
windows_targets::link!("ws2_32.dll" "system" fn recv(s : SOCKET, buf : PSTR, len : i32, flags : SEND_RECV_FLAGS) -> i32);
138140
windows_targets::link!("ws2_32.dll" "system" fn recvfrom(s : SOCKET, buf : PSTR, len : i32, flags : i32, from : *mut SOCKADDR, fromlen : *mut i32) -> i32);
139141
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);

0 commit comments

Comments
 (0)