diff --git a/rust/utils/src/hostname.rs b/rust/utils/src/hostname.rs new file mode 100644 index 00000000..9fb24f7c --- /dev/null +++ b/rust/utils/src/hostname.rs @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: MIT +// +// Copyright IBM Corp. 2026 + +use std::{ffi::CStr, io}; + +/// Returns the maximum hostname length supported by the system. +/// +/// # Returns +/// +/// The maximum hostname length in bytes, excluding the NUL terminator. +fn max_hostname_len() -> usize { + const _POSIX_HOST_NAME_MAX: usize = 255; + + // SAFETY: sysconf is safe to call with _SC_HOST_NAME_MAX and only reads system configuration without side effects. + let n = unsafe { libc::sysconf(libc::_SC_HOST_NAME_MAX) }; + if n < 0 { + _POSIX_HOST_NAME_MAX + } else { + n.try_into().unwrap() + } +} + +/// Retrieves the system hostname using libc gethostname. +/// +/// # Returns +/// +/// Returns `Ok(String)` containing the hostname on success, or an `Err(io::Error)` otherwise. +/// +/// # Examples +/// +/// ```rust,no_run +/// use utils::gethostname; +/// +/// let hostname = gethostname().expect("Failed to get hostname"); +/// println!("Hostname: {}", hostname); +/// ``` +pub fn gethostname() -> io::Result { + // Add space for NUL terminator + let buf_len = max_hostname_len().checked_add(1).unwrap(); + let mut buf = vec![0u8; buf_len]; + + // SAFETY: `buf` is a byte array large enough for storing the result of + // `gethostname` as the max length was just checked before. + let result = unsafe { libc::gethostname(buf.as_mut_ptr() as *mut libc::c_char, buf.len()) }; + if result != 0 { + // libc::gethostname returns -1 on error and sets errno + return Err(io::Error::last_os_error()); + } + + // If it is not NUL-terminated, then add the NUL-termination at the end + if !buf.contains(&0) { + if let Some(last) = buf.last_mut() { + *last = 0; + } + } + + assert!(isize::try_from(buf_len).unwrap() <= isize::MAX); + + // SAFETY: We made sure that `buf` is: + // 1. NUL-terminated + // 2. A single allocation (vec![...] was used for the allocation) + // 3. `buf_len` is <= isize::MAX (verified by assertion above) + // Therefore, it's safe to construct a CStr from the buffer pointer. + let cstr = unsafe { CStr::from_ptr(buf.as_ptr() as *const libc::c_char) }; + Ok(cstr.to_string_lossy().into_owned()) +} diff --git a/rust/utils/src/lib.rs b/rust/utils/src/lib.rs index 68a1b43a..fff5395a 100644 --- a/rust/utils/src/lib.rs +++ b/rust/utils/src/lib.rs @@ -7,6 +7,7 @@ mod cli; mod exit_code; mod file; mod hexslice; +mod hostname; mod log; mod tmpfile; @@ -20,6 +21,7 @@ pub use crate::{ exit_code::{docstring, ExitCodeDoc, ExitCodeTrait, ExitCodeVariantDoc}, file::{AtomicFile, AtomicFileOperation}, hexslice::HexSlice, + hostname::gethostname, log::PvLogger, tmpfile::TemporaryDirectory, };