gethostname/lib.rs
1// Copyright 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, clippy::pedantic)]
26// On the Windows target our doc links don't necessarily work.
27#![cfg_attr(windows, allow(rustdoc::broken_intra_doc_links))]
28
29use std::ffi::OsString;
30
31/// Get the standard host name for the current machine.
32///
33/// On Unix call [`rustix::system::uname`] to obtain the node name, see
34/// [`rustix::system::Uname::nodename`].
35///
36/// On Windows return the DNS host name of the local computer, as returned by
37/// [GetComputerNameExW] with `ComputerNamePhysicalDnsHostname` as `NameType`.
38/// We call this function twice to obtain the appropriate buffer size; there is
39/// a race condition window between these two calls where a change to the node
40/// name would result in a wrong buffer size which could cause this function to
41/// panic.
42///
43/// Note that this host name does not have a well-defined meaning in terms of
44/// network name resolution. Specifically, it's not guaranteed that the
45/// returned name can be resolved in any particular way, e.g. DNS.
46///
47/// [GetComputerNameExW]: https://docs.microsoft.com/en-us/windows/desktop/api/sysinfoapi/nf-sysinfoapi-getcomputernameexw
48#[must_use]
49pub fn gethostname() -> OsString {
50 #[cfg(unix)]
51 {
52 use std::os::unix::ffi::OsStringExt;
53 OsString::from_vec(rustix::system::uname().nodename().to_bytes().to_vec())
54 }
55 #[cfg(windows)]
56 {
57 get_computer_physical_dns_hostname()
58 }
59}
60
61#[cfg(windows)]
62fn get_computer_physical_dns_hostname() -> OsString {
63 use std::os::windows::ffi::OsStringExt;
64
65 // The DNS host name of the local computer. If the local computer is a node
66 // in a cluster, lpBuffer receives the DNS host name of the local computer,
67 // not the name of the cluster virtual server.
68 pub const COMPUTER_NAME_PHYSICAL_DNS_HOSTNAME: i32 = 5;
69
70 // https://learn.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-getcomputernameexw
71 ::windows_targets::link!("kernel32.dll" "system" fn GetComputerNameExW(nametype: i32, lpbuffer: *mut u16, nsize: *mut u32) -> i32);
72
73 let mut buffer_size: u32 = 0;
74
75 unsafe {
76 // This call always fails with ERROR_MORE_DATA, because we pass NULL to
77 // get the required buffer size. GetComputerNameExW then fills buffer_size with the size
78 // of the host name string plus a trailing zero byte.
79 GetComputerNameExW(
80 COMPUTER_NAME_PHYSICAL_DNS_HOSTNAME,
81 std::ptr::null_mut(),
82 &mut buffer_size,
83 )
84 };
85 assert!(
86 0 < buffer_size,
87 "GetComputerNameExW did not provide buffer size"
88 );
89
90 let mut buffer = vec![0_u16; buffer_size as usize];
91 unsafe {
92 if GetComputerNameExW(
93 COMPUTER_NAME_PHYSICAL_DNS_HOSTNAME,
94 buffer.as_mut_ptr(),
95 &mut buffer_size,
96 ) == 0
97 {
98 panic!(
99 "GetComputerNameExW failed to read hostname.
100 Please report this issue to <https://codeberg.org/swsnr/gethostname.rs/issues>!"
101 );
102 }
103 }
104 assert!(
105 // GetComputerNameExW returns the size _without_ the trailing zero byte on the second call
106 buffer_size as usize == buffer.len() - 1,
107 "GetComputerNameExW changed the buffer size unexpectedly"
108 );
109
110 let end = buffer.iter().position(|&b| b == 0).unwrap_or(buffer.len());
111 OsString::from_wide(&buffer[0..end])
112}
113
114#[cfg(test)]
115mod tests {
116 use std::process::Command;
117
118 #[test]
119 fn gethostname_matches_system_hostname() {
120 let mut command = if cfg!(windows) {
121 Command::new("hostname")
122 } else {
123 let mut uname = Command::new("uname");
124 uname.arg("-n");
125 uname
126 };
127 let output = command.output().expect("failed to get hostname");
128 if output.status.success() {
129 let hostname = String::from_utf8_lossy(&output.stdout);
130 assert!(
131 !hostname.is_empty(),
132 "Failed to get hostname: hostname empty?"
133 );
134 // Convert both sides to lowercase; hostnames are case-insensitive
135 // anyway.
136 assert_eq!(
137 super::gethostname().into_string().unwrap().to_lowercase(),
138 hostname.trim_end().to_lowercase()
139 );
140 } else {
141 panic!(
142 "Failed to get hostname! {}",
143 String::from_utf8_lossy(&output.stderr)
144 );
145 }
146 }
147
148 #[test]
149 #[ignore]
150 fn gethostname_matches_fixed_hostname() {
151 assert_eq!(
152 super::gethostname().into_string().unwrap().to_lowercase(),
153 "hostname-for-testing"
154 );
155 }
156}