use std::{ffi::OsString, io}; use windows_sys::Win32::System::SystemInformation::{ GetComputerNameExW, COMPUTER_NAME_FORMAT, }; /// The type of name to be retrieved by [`get_computer_name`]. #[derive(Clone, Copy, Debug)] #[non_exhaustive] pub enum ComputerNameKind { /// The name of the DNS domain assigned to the local computer. If the local /// computer is a node in a cluster, lpBuffer receives the DNS domain name /// of the cluster virtual server. DnsDomain, /// The fully qualified DNS name that uniquely identifies the local /// computer. This name is a combination of the DNS host name and the DNS /// domain name, using the form HostName.DomainName. If the local computer /// is a node in a cluster, lpBuffer receives the fully qualified DNS name /// of the cluster virtual server. DnsFullyQualified, /// The DNS host name of the local computer. If the local computer is a /// node in a cluster, lpBuffer receives the DNS host name of the cluster /// virtual server. DnsHostname, /// The NetBIOS name of the local computer. If the local computer is a node /// in a cluster, lpBuffer receives the NetBIOS name of the cluster virtual /// server. NetBios, /// The name of the DNS domain assigned to the local computer. If the local /// computer is a node in a cluster, lpBuffer receives the DNS domain name /// of the local computer, not the name of the cluster virtual server. PhysicalDnsDomain, /// The fully qualified DNS name that uniquely identifies the computer. If /// the local computer is a node in a cluster, lpBuffer receives the fully /// qualified DNS name of the local computer, not the name of the cluster /// virtual server. /// /// The fully qualified DNS name is a combination of the DNS host name and /// the DNS domain name, using the form HostName.DomainName. PhysicalDnsFullyQualified, /// The DNS host name of the local computer. If the local computer is a /// node in a cluster, lpBuffer receives the DNS host name of the local /// computer, not the name of the cluster virtual server. PhysicalDnsHostname, /// The NetBIOS name of the local computer. If the local computer is a node /// in a cluster, lpBuffer receives the NetBIOS name of the local computer, /// not the name of the cluster virtual server. PhysicalNetBios, } impl ComputerNameKind { fn to_format(&self) -> COMPUTER_NAME_FORMAT { use self::ComputerNameKind::*; use windows_sys::Win32::System::SystemInformation; match *self { DnsDomain => SystemInformation::ComputerNameDnsDomain, DnsFullyQualified => { SystemInformation::ComputerNameDnsFullyQualified } DnsHostname => SystemInformation::ComputerNameDnsHostname, NetBios => SystemInformation::ComputerNameNetBIOS, PhysicalDnsDomain => { SystemInformation::ComputerNamePhysicalDnsDomain } PhysicalDnsFullyQualified => { SystemInformation::ComputerNamePhysicalDnsFullyQualified } PhysicalDnsHostname => { SystemInformation::ComputerNamePhysicalDnsHostname } PhysicalNetBios => SystemInformation::ComputerNamePhysicalNetBIOS, } } } /// Retrieves a NetBIOS or DNS name associated with the local computer. /// /// The names are established at system startup, when the system reads them /// from the registry. /// /// This corresponds to calling [`GetComputerNameExW`]. /// /// [`GetComputerNameExW`]: https://learn.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-getcomputernameexw pub fn get_computer_name(kind: ComputerNameKind) -> io::Result { use std::os::windows::ffi::OsStringExt; let format = kind.to_format(); let mut len1 = 0; // SAFETY: As documented, we call this with a null pointer which will in // turn cause this routine to write the required buffer size fo `len1`. // Also, we explicitly ignore the return value since we expect this call to // fail given that the destination buffer is too small by design. let _ = unsafe { GetComputerNameExW(format, std::ptr::null_mut(), &mut len1) }; let len = match usize::try_from(len1) { Ok(len) => len, Err(_) => { return Err(io::Error::new( io::ErrorKind::Other, "GetComputerNameExW buffer length overflowed usize", )) } }; let mut buf = vec![0; len]; let mut len2 = len1; // SAFETY: We pass a valid pointer to an appropriately sized Vec. let rc = unsafe { GetComputerNameExW(format, buf.as_mut_ptr(), &mut len2) }; if rc == 0 { return Err(io::Error::last_os_error()); } // Apparently, the subsequent call writes the number of characters written // to the buffer to `len2` but not including the NUL terminator. Notice // that in the first call above, the length written to `len1` *does* // include the NUL terminator. Therefore, we expect `len1` to be at least // one greater than `len2`. If not, then something weird has happened and // we report an error. if len1 <= len2 { let msg = format!( "GetComputerNameExW buffer length mismatch, \ expected length strictly less than {} \ but got {}", len1, len2, ); return Err(io::Error::new(io::ErrorKind::Other, msg)); } let len = usize::try_from(len2).expect("len1 fits implies len2 fits"); Ok(OsString::from_wide(&buf[..len])) } #[cfg(test)] mod tests { use super::*; // This test doesn't really check anything other than that we can // successfully query all kinds of computer names. We just print them out // since there aren't really any properties about the names that we can // assert. // // We specifically run this test in CI with --nocapture so that we can see // the output. #[test] fn itworks() { let kinds = [ ComputerNameKind::DnsDomain, ComputerNameKind::DnsFullyQualified, ComputerNameKind::DnsHostname, ComputerNameKind::NetBios, ComputerNameKind::PhysicalDnsDomain, ComputerNameKind::PhysicalDnsFullyQualified, ComputerNameKind::PhysicalDnsHostname, ComputerNameKind::PhysicalNetBios, ]; for kind in kinds { let result = get_computer_name(kind); let name = result.unwrap(); println!("{kind:?}: {name:?}"); } } }