winapi_util/sysinfo.rs
1use std::{ffi::OsString, io};
2
3use windows_sys::Win32::System::SystemInformation::{
4 GetComputerNameExW, COMPUTER_NAME_FORMAT,
5};
6
7/// The type of name to be retrieved by [`get_computer_name`].
8#[derive(Clone, Copy, Debug)]
9#[non_exhaustive]
10pub enum ComputerNameKind {
11 /// The name of the DNS domain assigned to the local computer. If the local
12 /// computer is a node in a cluster, lpBuffer receives the DNS domain name
13 /// of the cluster virtual server.
14 DnsDomain,
15 /// The fully qualified DNS name that uniquely identifies the local
16 /// computer. This name is a combination of the DNS host name and the DNS
17 /// domain name, using the form HostName.DomainName. If the local computer
18 /// is a node in a cluster, lpBuffer receives the fully qualified DNS name
19 /// of the cluster virtual server.
20 DnsFullyQualified,
21 /// The DNS host name of the local computer. If the local computer is a
22 /// node in a cluster, lpBuffer receives the DNS host name of the cluster
23 /// virtual server.
24 DnsHostname,
25 /// The NetBIOS name of the local computer. If the local computer is a node
26 /// in a cluster, lpBuffer receives the NetBIOS name of the cluster virtual
27 /// server.
28 NetBios,
29 /// The name of the DNS domain assigned to the local computer. If the local
30 /// computer is a node in a cluster, lpBuffer receives the DNS domain name
31 /// of the local computer, not the name of the cluster virtual server.
32 PhysicalDnsDomain,
33 /// The fully qualified DNS name that uniquely identifies the computer. If
34 /// the local computer is a node in a cluster, lpBuffer receives the fully
35 /// qualified DNS name of the local computer, not the name of the cluster
36 /// virtual server.
37 ///
38 /// The fully qualified DNS name is a combination of the DNS host name and
39 /// the DNS domain name, using the form HostName.DomainName.
40 PhysicalDnsFullyQualified,
41 /// The DNS host name of the local computer. If the local computer is a
42 /// node in a cluster, lpBuffer receives the DNS host name of the local
43 /// computer, not the name of the cluster virtual server.
44 PhysicalDnsHostname,
45 /// The NetBIOS name of the local computer. If the local computer is a node
46 /// in a cluster, lpBuffer receives the NetBIOS name of the local computer,
47 /// not the name of the cluster virtual server.
48 PhysicalNetBios,
49}
50
51impl ComputerNameKind {
52 fn to_format(&self) -> COMPUTER_NAME_FORMAT {
53 use self::ComputerNameKind::*;
54 use windows_sys::Win32::System::SystemInformation;
55
56 match *self {
57 DnsDomain => SystemInformation::ComputerNameDnsDomain,
58 DnsFullyQualified => {
59 SystemInformation::ComputerNameDnsFullyQualified
60 }
61 DnsHostname => SystemInformation::ComputerNameDnsHostname,
62 NetBios => SystemInformation::ComputerNameNetBIOS,
63 PhysicalDnsDomain => {
64 SystemInformation::ComputerNamePhysicalDnsDomain
65 }
66 PhysicalDnsFullyQualified => {
67 SystemInformation::ComputerNamePhysicalDnsFullyQualified
68 }
69 PhysicalDnsHostname => {
70 SystemInformation::ComputerNamePhysicalDnsHostname
71 }
72 PhysicalNetBios => SystemInformation::ComputerNamePhysicalNetBIOS,
73 }
74 }
75}
76/// Retrieves a NetBIOS or DNS name associated with the local computer.
77///
78/// The names are established at system startup, when the system reads them
79/// from the registry.
80///
81/// This corresponds to calling [`GetComputerNameExW`].
82///
83/// [`GetComputerNameExW`]: https://learn.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-getcomputernameexw
84pub fn get_computer_name(kind: ComputerNameKind) -> io::Result<OsString> {
85 use std::os::windows::ffi::OsStringExt;
86
87 let format = kind.to_format();
88 let mut len1 = 0;
89 // SAFETY: As documented, we call this with a null pointer which will in
90 // turn cause this routine to write the required buffer size fo `len1`.
91 // Also, we explicitly ignore the return value since we expect this call to
92 // fail given that the destination buffer is too small by design.
93 let _ =
94 unsafe { GetComputerNameExW(format, std::ptr::null_mut(), &mut len1) };
95
96 let len = match usize::try_from(len1) {
97 Ok(len) => len,
98 Err(_) => {
99 return Err(io::Error::new(
100 io::ErrorKind::Other,
101 "GetComputerNameExW buffer length overflowed usize",
102 ))
103 }
104 };
105 let mut buf = vec![0; len];
106 let mut len2 = len1;
107 // SAFETY: We pass a valid pointer to an appropriately sized Vec<u16>.
108 let rc =
109 unsafe { GetComputerNameExW(format, buf.as_mut_ptr(), &mut len2) };
110 if rc == 0 {
111 return Err(io::Error::last_os_error());
112 }
113 // Apparently, the subsequent call writes the number of characters written
114 // to the buffer to `len2` but not including the NUL terminator. Notice
115 // that in the first call above, the length written to `len1` *does*
116 // include the NUL terminator. Therefore, we expect `len1` to be at least
117 // one greater than `len2`. If not, then something weird has happened and
118 // we report an error.
119 if len1 <= len2 {
120 let msg = format!(
121 "GetComputerNameExW buffer length mismatch, \
122 expected length strictly less than {} \
123 but got {}",
124 len1, len2,
125 );
126 return Err(io::Error::new(io::ErrorKind::Other, msg));
127 }
128 let len = usize::try_from(len2).expect("len1 fits implies len2 fits");
129 Ok(OsString::from_wide(&buf[..len]))
130}
131
132#[cfg(test)]
133mod tests {
134 use super::*;
135
136 // This test doesn't really check anything other than that we can
137 // successfully query all kinds of computer names. We just print them out
138 // since there aren't really any properties about the names that we can
139 // assert.
140 //
141 // We specifically run this test in CI with --nocapture so that we can see
142 // the output.
143 #[test]
144 fn itworks() {
145 let kinds = [
146 ComputerNameKind::DnsDomain,
147 ComputerNameKind::DnsFullyQualified,
148 ComputerNameKind::DnsHostname,
149 ComputerNameKind::NetBios,
150 ComputerNameKind::PhysicalDnsDomain,
151 ComputerNameKind::PhysicalDnsFullyQualified,
152 ComputerNameKind::PhysicalDnsHostname,
153 ComputerNameKind::PhysicalNetBios,
154 ];
155 for kind in kinds {
156 let result = get_computer_name(kind);
157 let name = result.unwrap();
158 println!("{kind:?}: {name:?}");
159 }
160 }
161}