1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
use std::{ffi::OsString, io};

use winapi::um::sysinfoapi::{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 winapi::um::sysinfoapi;

        match *self {
            DnsDomain => sysinfoapi::ComputerNameDnsDomain,
            DnsFullyQualified => sysinfoapi::ComputerNameDnsFullyQualified,
            DnsHostname => sysinfoapi::ComputerNameDnsHostname,
            NetBios => sysinfoapi::ComputerNameNetBIOS,
            PhysicalDnsDomain => sysinfoapi::ComputerNamePhysicalDnsDomain,
            PhysicalDnsFullyQualified => {
                sysinfoapi::ComputerNamePhysicalDnsFullyQualified
            }
            PhysicalDnsHostname => sysinfoapi::ComputerNamePhysicalDnsHostname,
            PhysicalNetBios => sysinfoapi::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<OsString> {
    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<u16>.
    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:?}");
        }
    }
}