platform_info/platform/
unix.rs

1// This file is part of the uutils coreutils package.
2//
3// (c) Jian Zeng <anonymousknight96 AT gmail.com>
4// (c) Alex Lyon <arcterus@mail.com>
5//
6// For the full copyright and license information, please view the LICENSE file
7// that was distributed with this source code.
8
9// spell-checker:ignore (API) domainname nodename osname sysname
10// spell-checker:ignore (libc) libc utsname
11// spell-checker:ignore (jargon) hasher
12// spell-checker:ignore (names) Jian Zeng * anonymousknight96
13// spell-checker:ignore (rust) uninit
14// spell-checker:ignore (uutils) coreutils uutils
15// spell-checker:ignore (VSCode) endregion
16
17// refs:
18// [Byte-to/from-String Conversions](https://nicholasbishop.github.io/rust-conversions) @@ <https://archive.is/AnDCY>
19
20#![warn(unused_results)] // enable warnings for unused results
21
22use std::ffi::{OsStr, OsString};
23use std::fmt;
24use std::fmt::{Debug, Formatter};
25
26use crate::{PlatformInfoAPI, PlatformInfoError, UNameAPI};
27
28use unix_safe::{oss_from_cstr, utsname};
29
30// PlatformInfo
31/// Handles initial retrieval and holds cached information for the current platform (a Unix-like OS in this case).
32#[derive(Clone, Debug, PartialEq, Eq)]
33pub struct PlatformInfo {
34    /// Contains the cached results of the `utsname()` system call.
35    // ref: <https://docs.rs/libc/latest/i686-unknown-linux-gnu/libc/struct.utsname.html>
36    pub utsname: UTSName, /* aka "Unix Time-sharing System Name"; ref: <https://stackoverflow.com/questions/41669397/whats-the-meaning-of-utsname-in-linux> */
37    // * private-use fields
38    sysname: OsString,
39    nodename: OsString,
40    release: OsString,
41    version: OsString,
42    machine: OsString,
43    osname: OsString,
44}
45
46impl PlatformInfoAPI for PlatformInfo {
47    // * note: this function *should* never fail
48    fn new() -> Result<Self, PlatformInfoError> {
49        let utsname = UTSName(utsname()?);
50        Ok(Self {
51            utsname,
52            sysname: oss_from_cstr(&utsname.0.sysname),
53            nodename: oss_from_cstr(&utsname.0.nodename),
54            release: oss_from_cstr(&utsname.0.release),
55            version: oss_from_cstr(&utsname.0.version),
56            machine: oss_from_cstr(&utsname.0.machine),
57            osname: OsString::from(crate::lib_impl::HOST_OS_NAME),
58        })
59    }
60}
61
62impl UNameAPI for PlatformInfo {
63    fn sysname(&self) -> &OsStr {
64        &self.sysname
65    }
66
67    fn nodename(&self) -> &OsStr {
68        &self.nodename
69    }
70
71    fn release(&self) -> &OsStr {
72        &self.release
73    }
74
75    fn version(&self) -> &OsStr {
76        &self.version
77    }
78
79    fn machine(&self) -> &OsStr {
80        &self.machine
81    }
82
83    fn osname(&self) -> &OsStr {
84        &self.osname
85    }
86}
87
88//===
89
90// UTSName
91/// Contains information about the current computer system.
92///
93/// Wraps [`libc::utsname`].
94// ref: <https://docs.rs/libc/latest/i686-unknown-linux-gnu/libc/struct.utsname.html>
95/*
96    pub struct utsname {
97        pub sysname: [::c_char; 65],
98        pub nodename: [::c_char; 65],
99        pub release: [::c_char; 65],
100        pub version: [::c_char; 65],
101        pub machine: [::c_char; 65],
102        pub domainname: [::c_char; 65]
103    }
104*/
105// aka "Unix Time-sharing System Name"; ref: <https://stackoverflow.com/questions/41669397/whats-the-meaning-of-utsname-in-linux>
106#[derive(Clone, Copy /* , Debug, PartialEq, Eq */)]
107pub struct UTSName(libc::utsname);
108
109impl Debug for UTSName {
110    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
111        let mut debug_struct = &mut f.debug_struct("UTSName");
112        debug_struct = debug_struct
113            .field("sysname", &oss_from_cstr(&self.0.sysname))
114            .field("nodename", &oss_from_cstr(&self.0.nodename))
115            .field("release", &oss_from_cstr(&self.0.release))
116            .field("version", &oss_from_cstr(&self.0.version))
117            .field("machine", &oss_from_cstr(&self.0.machine));
118        // The domainname field is not part of the POSIX standard but a GNU extension. Therefor
119        // BSD-like platforms and solaris/illumos are missing the domainname field.
120        #[cfg(not(any(
121            target_os = "aix",
122            target_os = "illumos",
123            target_os = "solaris",
124            target_os = "macos",
125            target_os = "dragonfly",
126            target_os = "freebsd",
127            target_os = "openbsd",
128            target_os = "netbsd",
129            target_os = "haiku"
130        )))]
131        {
132            debug_struct = debug_struct.field("domainname", &oss_from_cstr(&self.0.domainname));
133        }
134        debug_struct.finish()
135    }
136}
137
138impl PartialEq for UTSName {
139    fn eq(&self, other: &Self) -> bool {
140        let mut equal = true; // avoid 'unused-mut' and 'clippy::let-and-return' warnings on MacOS
141        equal = equal
142            && (
143                self.0.sysname,
144                self.0.nodename,
145                self.0.release,
146                self.0.version,
147                self.0.machine,
148            ) == (
149                other.0.sysname,
150                other.0.nodename,
151                other.0.release,
152                other.0.version,
153                other.0.machine,
154            );
155        // The domainname field is not part of the POSIX standard but a GNU extension. Therefor
156        // BSD-like platforms and solaris/illumos are missing the domainname field.
157        #[cfg(not(any(
158            target_os = "aix",
159            target_os = "illumos",
160            target_os = "solaris",
161            target_os = "macos",
162            target_os = "dragonfly",
163            target_os = "freebsd",
164            target_os = "openbsd",
165            target_os = "netbsd",
166            target_os = "haiku"
167        )))]
168        {
169            equal = equal && (self.0.domainname == other.0.domainname);
170        }
171        equal
172    }
173}
174
175impl Eq for UTSName {}
176
177//===
178
179//#region unsafe code
180mod unix_safe {
181    use std::convert::TryFrom;
182    use std::ffi::{CStr, OsStr, OsString};
183    use std::io;
184    use std::mem::MaybeUninit;
185    use std::os::unix::ffi::OsStrExt;
186
187    // oss_from_str()
188    /// *Returns* an `OsString` created from a `libc::c_char` slice.
189    pub fn oss_from_cstr(slice: &[libc::c_char]) -> OsString {
190        assert!(slice.len() < usize::try_from(isize::MAX).unwrap());
191        assert!(slice.iter().position(|&c| c == 0 /* NUL */).unwrap() < slice.len());
192        OsString::from(OsStr::from_bytes(
193            unsafe { CStr::from_ptr(slice.as_ptr()) }.to_bytes(),
194        ))
195    }
196
197    // utsname()
198    /// *Returns* a `libc::utsname` structure containing `uname`-like OS system information.
199    pub fn utsname() -> Result<libc::utsname, std::io::Error> {
200        // ref: <https://docs.rs/libc/latest/i686-unknown-linux-gnu/libc/fn.uname.html>
201        // ref: <https://docs.rs/libc/latest/i686-unknown-linux-gnu/libc/struct.utsname.html>
202        let mut uts = MaybeUninit::<libc::utsname>::uninit();
203        let result = unsafe { libc::uname(uts.as_mut_ptr()) };
204        if result != -1 {
205            // SAFETY: `libc::uname()` succeeded => `uts` was initialized
206            Ok(unsafe { uts.assume_init() })
207        } else {
208            Err(io::Error::last_os_error())
209        }
210    }
211}
212//#endregion (unsafe code)
213
214//=== Tests
215
216#[test]
217fn test_osname() {
218    let info = PlatformInfo::new().unwrap();
219    let osname = info.osname().to_string_lossy();
220    assert!(osname.starts_with(crate::lib_impl::HOST_OS_NAME));
221}
222
223#[test]
224fn structure_clone() {
225    let info = PlatformInfo::new().unwrap();
226    println!("{info:?}");
227    #[allow(clippy::redundant_clone)] // ignore `clippy::redundant_clone` warning for direct testing
228    let info_copy = info.clone();
229    assert_eq!(info_copy, info);
230}