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