hostname/
nix.rs

1#[cfg(feature = "set")]
2use std::ffi::OsStr;
3use std::ffi::OsString;
4use std::io;
5#[cfg(feature = "set")]
6use std::os::unix::ffi::OsStrExt;
7use std::os::unix::ffi::OsStringExt;
8
9const _POSIX_HOST_NAME_MAX: libc::c_long = 255;
10
11pub fn get() -> io::Result<OsString> {
12    // According to the POSIX specification,
13    // host names are limited to `HOST_NAME_MAX` bytes
14    //
15    // https://pubs.opengroup.org/onlinepubs/9699919799/functions/gethostname.html
16    let limit = unsafe { libc::sysconf(libc::_SC_HOST_NAME_MAX) };
17    let size = libc::c_long::max(limit, _POSIX_HOST_NAME_MAX) as usize;
18
19    // Reserve additional space for terminating nul byte.
20    let mut buffer = vec![0u8; size + 1];
21
22    #[allow(trivial_casts)]
23    let result = unsafe { libc::gethostname(buffer.as_mut_ptr() as *mut libc::c_char, size) };
24
25    if result != 0 {
26        return Err(io::Error::last_os_error());
27    }
28
29    Ok(wrap_buffer(buffer))
30}
31
32fn wrap_buffer(mut bytes: Vec<u8>) -> OsString {
33    // Returned name might be truncated if it does not fit
34    // and `buffer` will not contain the trailing \0 in that case.
35    // Manually capping the buffer length here.
36    let end = bytes
37        .iter()
38        .position(|&byte| byte == 0x00)
39        .unwrap_or(bytes.len());
40    bytes.resize(end, 0x00);
41
42    OsString::from_vec(bytes)
43}
44
45#[cfg(feature = "set")]
46pub fn set(hostname: &OsStr) -> io::Result<()> {
47    #[cfg(not(any(
48        target_os = "dragonfly",
49        target_os = "freebsd",
50        target_os = "ios",
51        target_os = "macos",
52        target_os = "solaris",
53        target_os = "illumos"
54    )))]
55    #[allow(non_camel_case_types)]
56    type hostname_len_t = libc::size_t;
57
58    #[cfg(any(
59        target_os = "dragonfly",
60        target_os = "freebsd",
61        target_os = "ios",
62        target_os = "macos",
63        target_os = "solaris",
64        target_os = "illumos"
65    ))]
66    #[allow(non_camel_case_types)]
67    type hostname_len_t = libc::c_int;
68
69    #[allow(clippy::unnecessary_cast)]
70    // Cast is needed for the `libc::c_int` type
71    if hostname.len() > hostname_len_t::MAX as usize {
72        return Err(io::Error::other("hostname too long"));
73    }
74
75    let size = hostname.len() as hostname_len_t;
76
77    #[allow(trivial_casts)]
78    let result =
79        unsafe { libc::sethostname(hostname.as_bytes().as_ptr() as *const libc::c_char, size) };
80
81    if result != 0 {
82        Err(io::Error::last_os_error())
83    } else {
84        Ok(())
85    }
86}
87
88#[cfg(test)]
89mod tests {
90    use std::ffi::OsStr;
91
92    use super::wrap_buffer;
93
94    // Happy path case: there is a correct null terminated C string in a buffer
95    // and a bunch of NULL characters from the pre-allocated buffer
96    #[test]
97    fn test_non_overflowed_buffer() {
98        let buf = b"potato\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0".to_vec();
99
100        assert_eq!(wrap_buffer(buf), OsStr::new("potato"));
101    }
102
103    #[test]
104    fn test_empty_buffer() {
105        let buf = b"".to_vec();
106
107        assert_eq!(wrap_buffer(buf), OsStr::new(""));
108    }
109
110    #[test]
111    fn test_filled_with_null_buffer() {
112        let buf = b"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0".to_vec();
113
114        assert_eq!(wrap_buffer(buf), OsStr::new(""));
115    }
116
117    // Hostname value had overflowed the buffer, so it was truncated
118    // and according to the POSIX documentation of the `gethostname`:
119    //
120    // > it is unspecified whether the returned name is null-terminated.
121    #[test]
122    fn test_overflowed_buffer() {
123        let buf = b"potat".to_vec();
124
125        assert_eq!(wrap_buffer(buf), OsStr::new("potat"));
126    }
127}