hostname/
nix.rs

1use std::io;
2#[cfg(feature = "set")]
3use std::ffi::OsStr;
4use std::ffi::OsString;
5#[cfg(feature = "set")]
6use std::os::unix::ffi::OsStrExt;
7use std::os::unix::ffi::OsStringExt;
8
9use libc;
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 size =
17        unsafe { libc::sysconf(libc::_SC_HOST_NAME_MAX) as libc::size_t };
18
19    let mut buffer = vec![0u8; size];
20
21    let result = unsafe {
22        libc::gethostname(buffer.as_mut_ptr() as *mut libc::c_char, size)
23    };
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_else(|| 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(target_os = "dragonfly",
48                     target_os = "freebsd",
49                     target_os = "ios",
50                     target_os = "macos")))]
51    #[allow(non_camel_case_types)]
52    type hostname_len_t = libc::size_t;
53
54    #[cfg(any(target_os = "dragonfly",
55                     target_os = "freebsd",
56                     target_os = "ios",
57                     target_os = "macos"))]
58    #[allow(non_camel_case_types)]
59    type hostname_len_t = libc::c_int;
60
61    let size = hostname.len() as hostname_len_t;
62
63    let result = unsafe {
64        libc::sethostname(
65            hostname.as_bytes().as_ptr() as *const libc::c_char,
66            size,
67        )
68    };
69
70    if result != 0 {
71        Err(io::Error::last_os_error())
72    } else {
73        Ok(())
74    }
75}
76
77#[cfg(test)]
78mod tests {
79    use std::ffi::OsStr;
80
81    use super::wrap_buffer;
82
83    // Happy path case: there is a correct null terminated C string in a buffer
84    // and a bunch of NULL characters from the pre-allocated buffer
85    #[test]
86    fn test_non_overflowed_buffer() {
87        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();
88
89        assert_eq!(wrap_buffer(buf), OsStr::new("potato"));
90    }
91
92    #[test]
93    fn test_empty_buffer() {
94        let buf = b"".to_vec();
95
96        assert_eq!(wrap_buffer(buf), OsStr::new(""));
97    }
98
99    #[test]
100    fn test_filled_with_null_buffer() {
101        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();
102
103        assert_eq!(wrap_buffer(buf), OsStr::new(""));
104    }
105
106    // Hostname value had overflowed the buffer, so it was truncated
107    // and according to the POSIX documentation of the `gethostname`:
108    //
109    // > it is unspecified whether the returned name is null-terminated.
110    #[test]
111    fn test_overflowed_buffer() {
112        let buf = b"potat".to_vec();
113
114        assert_eq!(wrap_buffer(buf), OsStr::new("potat"));
115    }
116}