1// Copyright 2019 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;
28use std::io::Error;
29
30/// Get the standard host name for the current machine.
31///
32/// On Unix simply wrap POSIX [gethostname] in a safe interface. On Windows
33/// return the DNS host name of the local computer, as returned by
34/// [GetComputerNameExW] with `ComputerNamePhysicalDnsHostname` as `NameType`.
35///
36/// This function panics if the buffer allocated for the hostname result of the
37/// operating system is too small; however we take great care to allocate a
38/// buffer of sufficient size:
39///
40/// * On Unix we allocate the buffer using the maximum permitted hostname size,
41/// as returned by [sysconf] via `sysconf(_SC_HOST_NAME_MAX)`, plus an extra
42/// byte for the trailing NUL byte. A hostname cannot exceed this limit, so
43/// this function can't realistically panic.
44/// * On Windows we call `GetComputerNameExW` with a NULL buffer first, which
45/// makes it return the length of the current host name. We then use this
46/// length to allocate a buffer for the actual result; this leaves a tiny
47/// tiny race condition in case the hostname changes to a longer name right
48/// in between those two calls but that's a risk we don't consider of any
49/// practical relevance.
50///
51/// Hence _if_ this function does panic please [report an issue][new].
52///
53/// [gethostname]: http://pubs.opengroup.org/onlinepubs/9699919799/functions/gethostname.html
54/// [sysconf]: http://pubs.opengroup.org/onlinepubs/9699919799/functions/sysconf.html
55/// [GetComputerNameExW]: https://docs.microsoft.com/en-us/windows/desktop/api/sysinfoapi/nf-sysinfoapi-getcomputernameexw
56/// [new]: https://github.com/lunaryorn/gethostname.rs/issues/new
57pub fn gethostname() -> OsString {
58 gethostname_impl()
59}
60
61#[cfg(unix)]
62#[inline]
63fn gethostname_impl() -> OsString {
64 use libc::{c_char, sysconf, _SC_HOST_NAME_MAX};
65 use std::os::unix::ffi::OsStringExt;
66 // Get the maximum size of host names on this system, and account for the
67 // trailing NUL byte.
68 let hostname_max = unsafe { sysconf(_SC_HOST_NAME_MAX) };
69 let mut buffer = vec![0; (hostname_max as usize) + 1];
70 let returncode = unsafe { libc::gethostname(buffer.as_mut_ptr() as *mut c_char, buffer.len()) };
71 if returncode != 0 {
72 // There are no reasonable failures, so lets panic
73 panic!(
74 "gethostname failed: {}
75 Please report an issue to <https://github.com/lunaryorn/gethostname.rs/issues>!",
76 Error::last_os_error()
77 );
78 }
79 // We explicitly search for the trailing NUL byte and cap at the buffer
80 // length: If the buffer's too small (which shouldn't happen since we
81 // explicitly use the max hostname size above but just in case) POSIX
82 // doesn't specify whether there's a NUL byte at the end, so if we didn't
83 // check we might read from memory that's not ours.
84 let end = buffer.iter().position(|&b| b == 0).unwrap_or(buffer.len());
85 buffer.resize(end, 0);
86 OsString::from_vec(buffer)
87}
88
89#[cfg(windows)]
90#[inline]
91fn gethostname_impl() -> OsString {
92 use std::os::windows::ffi::OsStringExt;
93 use winapi::ctypes::{c_ulong, wchar_t};
94 use winapi::um::sysinfoapi::{ComputerNamePhysicalDnsHostname, GetComputerNameExW};
95
96 let mut buffer_size: c_ulong = 0;
97
98 unsafe {
99 // This call always fails with ERROR_MORE_DATA, because we pass NULL to
100 // get the required buffer size.
101 GetComputerNameExW(
102 ComputerNamePhysicalDnsHostname,
103 std::ptr::null_mut(),
104 &mut buffer_size,
105 )
106 };
107
108 let mut buffer = vec![0 as wchar_t; buffer_size as usize];
109 let returncode = unsafe {
110 GetComputerNameExW(
111 ComputerNamePhysicalDnsHostname,
112 buffer.as_mut_ptr() as *mut wchar_t,
113 &mut buffer_size,
114 )
115 };
116 // GetComputerNameExW returns a non-zero value on success!
117 if returncode == 0 {
118 panic!(
119 "GetComputerNameExW failed to read hostname: {}
120Please report this issue to <https://github.com/lunaryorn/gethostname.rs/issues>!",
121 Error::last_os_error()
122 );
123 }
124
125 let end = buffer
126 .iter()
127 .position(|&b| b == 0)
128 .unwrap_or_else(|| buffer.len());
129 OsString::from_wide(&buffer[0..end])
130}
131
132#[cfg(test)]
133mod tests {
134 use std::process::Command;
135
136 #[test]
137 fn gethostname_matches_system_hostname() {
138 let output = Command::new("hostname")
139 .output()
140 .expect("failed to get hostname");
141 if output.status.success() {
142 let hostname = String::from_utf8_lossy(&output.stdout);
143 // Convert both sides to lowercase; hostnames are case-insensitive
144 // anyway.
145 assert_eq!(
146 super::gethostname().into_string().unwrap().to_lowercase(),
147 hostname.trim_end().to_lowercase()
148 );
149 } else {
150 panic!(
151 "Failed to get hostname! {}",
152 String::from_utf8_lossy(&output.stderr)
153 );
154 }
155 }
156
157 #[test]
158 #[ignore]
159 fn gethostname_matches_fixed_hostname() {
160 assert_eq!(
161 super::gethostname().into_string().unwrap().to_lowercase(),
162 "hostname-for-testing"
163 );
164 }
165}
166