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