Skip to content

Commit

Permalink
sysinfo: add safe routine for querying the computer name
Browse files Browse the repository at this point in the history
The plan is to use this in ripgrep as part of supporting hyperlinks.

Ref BurntSushi/ripgrep#2483 (comment)
  • Loading branch information
BurntSushi committed Sep 20, 2023
1 parent 161ed5d commit 69df880
Show file tree
Hide file tree
Showing 4 changed files with 159 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ jobs:
- run: cargo build --verbose
- run: cargo doc --verbose
- run: cargo test --verbose
- name: Show all computer names
run: cargo test --lib sysinfo::tests::itworks -- --nocapture

rustfmt:
name: rustfmt
Expand Down
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ features = [
"fileapi",
"minwindef",
"processenv",
"sysinfoapi",
"winbase",
"wincon",
"winerror",
Expand Down
3 changes: 3 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,7 @@ pub mod console;
#[cfg(windows)]
pub mod file;
#[cfg(windows)]
/// Safe routines for querying various Windows specific properties.
pub mod sysinfo;
#[cfg(windows)]
mod win;
153 changes: 153 additions & 0 deletions src/sysinfo.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,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:?}");
}
}
}

0 comments on commit 69df880

Please sign in to comment.