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}