gethostname/
lib.rs

1// Copyright 2019–2023 Sebastian Wiesner <sebastian@swsnr.de>
2
3// Licensed under the Apache License, Version 2.0 (the "License"); you may not
4// use this file except in compliance with the License. You may obtain a copy of
5// the License at
6
7// 	http://www.apache.org/licenses/LICENSE-2.0
8
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12// License for the specific language governing permissions and limitations under
13// the License.
14
15//! [gethostname()][ghn] for all platforms.
16//!
17//! ```
18//! use gethostname::gethostname;
19//!
20//! println!("Hostname: {:?}", gethostname());
21//! ```
22//!
23//! [ghn]: http://pubs.opengroup.org/onlinepubs/9699919799/functions/gethostname.html
24
25#![deny(warnings, missing_docs, clippy::all)]
26
27use std::ffi::OsString;
28
29/// Get the standard host name for the current machine.
30///
31/// On Unix simply wrap POSIX [gethostname] in a safe interface.  On Windows
32/// return the DNS host name of the local computer, as returned by
33/// [GetComputerNameExW] with `ComputerNamePhysicalDnsHostname` as `NameType`.
34///
35/// This function panics if the buffer allocated for the hostname result of the
36/// operating system is too small; however we take great care to allocate a
37/// buffer of sufficient size:
38///
39/// * On Unix we allocate the buffer using the maximum permitted hostname size,
40///     as returned by [sysconf] via `sysconf(_SC_HOST_NAME_MAX)`, plus an extra
41///     byte for the trailing NUL byte.  A hostname cannot exceed this limit, so
42///     this function can't realistically panic.
43/// * On Windows we call `GetComputerNameExW` with a NULL buffer first, which
44///     makes it return the length of the current host name.  We then use this
45///     length to allocate a buffer for the actual result; this leaves a tiny
46///     tiny race condition in case the hostname changes to a longer name right
47///     in between those two calls but that's a risk we don't consider of any
48///     practical relevance.
49///
50/// Hence _if_ this function does panic please [report an issue][new].
51///
52/// [gethostname]: http://pubs.opengroup.org/onlinepubs/9699919799/functions/gethostname.html
53/// [sysconf]: http://pubs.opengroup.org/onlinepubs/9699919799/functions/sysconf.html
54/// [GetComputerNameExW]: https://docs.microsoft.com/en-us/windows/desktop/api/sysinfoapi/nf-sysinfoapi-getcomputernameexw
55/// [new]: https://github.com/swsnr/gethostname.rs/issues/new
56pub fn gethostname() -> OsString {
57    gethostname_impl()
58}
59
60#[cfg(unix)]
61#[inline]
62fn gethostname_impl() -> OsString {
63    use libc::{c_char, sysconf, _SC_HOST_NAME_MAX};
64    use std::os::unix::ffi::OsStringExt;
65    // Get the maximum size of host names on this system, and account for the
66    // trailing NUL byte.
67    let hostname_max = unsafe { sysconf(_SC_HOST_NAME_MAX) };
68    let mut buffer = vec![0; (hostname_max as usize) + 1];
69    let returncode = unsafe { libc::gethostname(buffer.as_mut_ptr() as *mut c_char, buffer.len()) };
70    if returncode != 0 {
71        // There are no reasonable failures, so lets panic
72        panic!(
73            "gethostname failed: {}
74    Please report an issue to <https://github.com/swsnr/gethostname.rs/issues>!",
75            std::io::Error::last_os_error()
76        );
77    }
78    // We explicitly search for the trailing NUL byte and cap at the buffer
79    // length: If the buffer's too small (which shouldn't happen since we
80    // explicitly use the max hostname size above but just in case) POSIX
81    // doesn't specify whether there's a NUL byte at the end, so if we didn't
82    // check we might read from memory that's not ours.
83    let end = buffer.iter().position(|&b| b == 0).unwrap_or(buffer.len());
84    buffer.resize(end, 0);
85    OsString::from_vec(buffer)
86}
87
88#[cfg(windows)]
89#[inline]
90fn gethostname_impl() -> OsString {
91    use std::os::windows::ffi::OsStringExt;
92
93    // The DNS host name of the local computer. If the local computer is a node
94    // in a cluster, lpBuffer receives the DNS host name of the local computer,
95    // not the name of the cluster virtual server.
96    pub const COMPUTER_NAME_PHYSICAL_DNS_HOSTNAME: i32 = 5;
97
98    // https://learn.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-getcomputernameexw
99    ::windows_targets::link!("kernel32.dll" "system" fn GetComputerNameExW(nametype: i32, lpbuffer: *mut u16, nsize: *mut u32) -> i32);
100
101    let mut buffer_size: u32 = 0;
102
103    unsafe {
104        // This call always fails with ERROR_MORE_DATA, because we pass NULL to
105        // get the required buffer size.  GetComputerNameExW then fills buffer_size with the size
106        // of the host name string plus a trailing zero byte.
107        GetComputerNameExW(
108            COMPUTER_NAME_PHYSICAL_DNS_HOSTNAME,
109            std::ptr::null_mut(),
110            &mut buffer_size,
111        )
112    };
113    assert!(
114        0 < buffer_size,
115        "GetComputerNameExW did not provide buffer size"
116    );
117
118    let mut buffer = vec![0_u16; buffer_size as usize];
119    unsafe {
120        if GetComputerNameExW(
121            COMPUTER_NAME_PHYSICAL_DNS_HOSTNAME,
122            buffer.as_mut_ptr(),
123            &mut buffer_size,
124        ) == 0
125        {
126            panic!(
127                "GetComputerNameExW failed to read hostname.
128        Please report this issue to <https://github.com/swsnr/gethostname.rs/issues>!"
129            );
130        }
131    }
132    assert!(
133        // GetComputerNameExW returns the size _without_ the trailing zero byte on the second call
134        buffer_size as usize == buffer.len() - 1,
135        "GetComputerNameExW changed the buffer size unexpectedly"
136    );
137
138    let end = buffer.iter().position(|&b| b == 0).unwrap_or(buffer.len());
139    OsString::from_wide(&buffer[0..end])
140}
141
142#[cfg(test)]
143mod tests {
144    use std::process::Command;
145
146    #[test]
147    fn gethostname_matches_system_hostname() {
148        let output = Command::new("hostname")
149            .output()
150            .expect("failed to get hostname");
151        if output.status.success() {
152            let hostname = String::from_utf8_lossy(&output.stdout);
153            assert!(
154                !hostname.is_empty(),
155                "Failed to get hostname: hostname empty?"
156            );
157            // Convert both sides to lowercase; hostnames are case-insensitive
158            // anyway.
159            assert_eq!(
160                super::gethostname().into_string().unwrap().to_lowercase(),
161                hostname.trim_end().to_lowercase()
162            );
163        } else {
164            panic!(
165                "Failed to get hostname! {}",
166                String::from_utf8_lossy(&output.stderr)
167            );
168        }
169    }
170
171    #[test]
172    #[ignore]
173    fn gethostname_matches_fixed_hostname() {
174        assert_eq!(
175            super::gethostname().into_string().unwrap().to_lowercase(),
176            "hostname-for-testing"
177        );
178    }
179}