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 | |
27 | use std::ffi::OsString; |
28 | use 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 |
57 | pub fn gethostname() -> OsString { |
58 | gethostname_impl() |
59 | } |
60 | |
61 | #[cfg (unix)] |
62 | #[inline ] |
63 | fn 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 ] |
91 | fn 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: {} |
120 | Please 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)] |
133 | mod 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 | |