sys_info/
lib.rs

1//! #Introduction
2//! This crate focuses on geting system information.
3//!
4//! For now it supports Linux, Mac OS X and Windows.
5//! And now it can get information of kernel/cpu/memory/disk/load/hostname and so on.
6//!
7
8extern crate libc;
9
10use std::ffi;
11use std::fmt;
12use std::io::{self, Read};
13use std::fs::File;
14#[cfg(any(target_os = "windows", target_vendor = "apple", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd"))]
15use std::os::raw::c_char;
16#[cfg(not(any(target_os = "windows", target_os = "linux")))]
17use std::os::raw::{c_int, c_double};
18
19#[cfg(any(target_vendor = "apple", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd"))]
20use libc::sysctl;
21#[cfg(any(target_vendor = "apple", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd"))]
22use std::mem::size_of_val;
23#[cfg(any(target_vendor = "apple", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd"))]
24use std::ptr::null_mut;
25#[cfg(not(target_os = "windows"))]
26use libc::timeval;
27#[cfg(any(target_os = "solaris", target_os = "illumos"))]
28use std::time::SystemTime;
29#[cfg(target_os = "linux")]
30use std::collections::HashMap;
31
32#[cfg(any(target_os = "solaris", target_os = "illumos"))]
33mod kstat;
34
35#[cfg(any(target_vendor = "apple", target_os="freebsd", target_os = "openbsd", target_os = "netbsd"))]
36static OS_CTL_KERN: libc::c_int = 1;
37#[cfg(any(target_vendor = "apple", target_os="freebsd", target_os = "openbsd", target_os = "netbsd"))]
38static OS_KERN_BOOTTIME: libc::c_int = 21;
39
40/// System load average value.
41#[repr(C)]
42#[derive(Debug)]
43pub struct LoadAvg {
44    /// Average load within one minutes.
45    pub one: f64,
46    /// Average load within five minutes.
47    pub five: f64,
48    /// Average load within fifteen minutes.
49    pub fifteen: f64,
50}
51
52/// System memory information.
53#[repr(C)]
54#[derive(Debug)]
55pub struct MemInfo {
56    /// Total physical memory.
57    pub total: u64,
58    pub free: u64,
59    pub avail: u64,
60
61    pub buffers: u64,
62    pub cached: u64,
63
64    /// Total swap memory.
65    pub swap_total: u64,
66    pub swap_free: u64,
67}
68
69/// The os release info of Linux.
70///
71/// See [man os-release](https://www.freedesktop.org/software/systemd/man/os-release.html).
72#[derive(Debug)]
73#[derive(Default)]
74pub struct LinuxOSReleaseInfo {
75    /// A lower-case string (no spaces or other characters outside of 0–9, a–z, ".", "_" and "-")
76    /// identifying the operating system, excluding any version information and suitable for
77    /// processing by scripts or usage in generated filenames.
78    ///
79    /// Note that we don't verify that the string is lower-case and can be used in file-names. If
80    /// the /etc/os-release file has an invalid value, you will get this value.
81    ///
82    /// If not set, defaults to "ID=linux". Use `self.id()` to fallback to the default.
83    ///
84    /// Example: "fedora" or "debian".
85    pub id: Option<String>,
86
87    /// A space-separated list of operating system identifiers in the same syntax as the ID=
88    /// setting. It should list identifiers of operating systems that are closely related to the
89    /// local operating system in regards to packaging and programming interfaces, for example
90    /// listing one or more OS identifiers the local OS is a derivative from. An OS should
91    /// generally only list other OS identifiers it itself is a derivative of, and not any OSes
92    /// that are derived from it, though symmetric relationships are possible. Build scripts and
93    /// similar should check this variable if they need to identify the local operating system and
94    /// the value of ID= is not recognized. Operating systems should be listed in order of how
95    /// closely the local operating system relates to the listed ones, starting with the closest.
96    ///
97    /// This field is optional.
98    ///
99    /// Example: for an operating system with `ID=centos`, an assignment of `ID_LIKE="rhel fedora"`
100    /// would be appropriate. For an operating system with `ID=ubuntu`, an assignment of
101    /// `ID_LIKE=debian` is appropriate.
102    pub id_like: Option<String>,
103
104    /// A string identifying the operating system, without a version component, and suitable for
105    /// presentation to the user.
106    ///
107    /// If not set, defaults to "NAME=Linux".Use `self.id()` to fallback to the default.
108    ///
109    /// Example: "Fedora" or "Debian GNU/Linux".
110    pub name: Option<String>,
111
112    /// A pretty operating system name in a format suitable for presentation to the user. May or
113    /// may not contain a release code name or OS version of some kind, as suitable.
114    ///
115    /// If not set, defaults to "Linux". Use `self.id()` to fallback to the default.
116    ///
117    /// Example: "Fedora 17 (Beefy Miracle)".
118    pub pretty_name: Option<String>,
119
120    /// A string identifying the operating system version, excluding any OS name information,
121    /// possibly including a release code name, and suitable for presentation to the user.
122    ///
123    /// This field is optional.
124    ///
125    /// Example: "17" or "17 (Beefy Miracle)"
126    pub version: Option<String>,
127
128    /// A lower-case string (mostly numeric, no spaces or other characters outside of 0–9, a–z,
129    /// ".", "_" and "-") identifying the operating system version, excluding any OS name
130    /// information or release code name, and suitable for processing by scripts or usage in
131    /// generated filenames.
132    ///
133    /// This field is optional.
134    ///
135    /// Example: "17" or "11.04".
136    pub version_id: Option<String>,
137
138    /// A lower-case string (no spaces or other characters outside of 0–9, a–z, ".", "_" and "-")
139    /// identifying the operating system release code name, excluding any OS name information or
140    /// release version, and suitable for processing by scripts or usage in generated filenames.
141    ///
142    /// This field is optional and may not be implemented on all systems.
143    ///
144    /// Examples: "buster", "xenial".
145    pub version_codename: Option<String>,
146
147    /// A suggested presentation color when showing the OS name on the console. This should be
148    /// specified as string suitable for inclusion in the ESC [ m ANSI/ECMA-48 escape code for
149    /// setting graphical rendition.
150    ///
151    /// This field is optional.
152    ///
153    /// Example: "0;31" for red, "1;34" for light blue, or "0;38;2;60;110;180" for Fedora blue.
154    pub ansi_color: Option<String>,
155
156    /// A string, specifying the name of an icon as defined by freedesktop.org Icon Theme
157    /// Specification. This can be used by graphical applications to display an operating
158    /// system's or distributor's logo.
159    ///
160    /// This field is optional and may not necessarily be implemented on all systems.
161    ///
162    /// Examples: "LOGO=fedora-logo", "LOGO=distributor-logo-opensuse".
163    pub logo: Option<String>,
164
165    /// A CPE name for the operating system, in URI binding syntax, following the Common Platform
166    /// Enumeration Specification as proposed by the NIST.
167    ///
168    /// This field is optional.
169    ///
170    /// Example: "cpe:/o:fedoraproject:fedora:17".
171    pub cpe_name: Option<String>,
172
173    /// A string uniquely identifying the system image used as the origin for a distribution (it is
174    /// not updated with system updates). The field can be identical between different VERSION_IDs
175    /// as BUILD_ID is an only a unique identifier to a specific version. Distributions that
176    /// release each update as a new version would only need to use VERSION_ID as each build is
177    /// already distinct based on the VERSION_ID.
178    ///
179    /// This field is optional.
180    ///
181    /// Example: "2013-03-20.3" or "BUILD_ID=201303203".
182    pub build_id: Option<String>,
183
184    /// A string identifying a specific variant or edition of the operating system suitable for
185    /// presentation to the user. This field may be used to inform the user that the configuration
186    /// of this system is subject to a specific divergent set of rules or default configuration
187    /// settings.
188    ///
189    /// This field is optional and may not be implemented on all systems.
190    ///
191    /// Examples: "Server Edition", "Smart Refrigerator Edition".
192    ///
193    /// Note: this field is for display purposes only. The VARIANT_ID field should be used for
194    /// making programmatic decisions.
195    pub variant: Option<String>,
196
197    /// A lower-case string (no spaces or other characters outside of 0–9, a–z, ".", "_" and "-"),
198    /// identifying a specific variant or edition of the operating system. This may be interpreted
199    /// by other packages in order to determine a divergent default configuration.
200    ///
201    /// This field is optional and may not be implemented on all systems.
202    ///
203    /// Examples: "server", "embedded".
204    pub variant_id: Option<String>,
205
206    /// HOME_URL= should refer to the homepage of the operating system, or alternatively some homepage of
207    /// the specific version of the operating system.
208    ///
209    /// These URLs are intended to be exposed in "About this system" UIs behind links with captions
210    /// such as "About this Operating System", "Obtain Support", "Report a Bug", or "Privacy
211    /// Policy". The values should be in RFC3986 format, and should be "http:" or "https:" URLs,
212    /// and possibly "mailto:" or "tel:". Only one URL shall be listed in each setting. If multiple
213    /// resources need to be referenced, it is recommended to provide an online landing page
214    /// linking all available resources.
215    ///
216    /// Example: "https://fedoraproject.org/".
217    pub home_url: Option<String>,
218
219    /// DOCUMENTATION_URL= should refer to the main documentation page for this operating system.
220    ///
221    /// See also `home_url`.
222    pub documentation_url: Option<String>,
223
224    /// SUPPORT_URL= should refer to the main support page for the operating system, if there is
225    /// any. This is primarily intended for operating systems which vendors provide support for.
226    ///
227    /// See also `home_url`.
228    pub support_url: Option<String>,
229
230    /// BUG_REPORT_URL= should refer to the main bug reporting page for the operating system, if
231    /// there is any. This is primarily intended for operating systems that rely on community QA.
232    ///
233    /// Example: "https://bugzilla.redhat.com/".
234    ///
235    /// See also `home_url`.
236    pub bug_report_url: Option<String>,
237
238    /// PRIVACY_POLICY_URL= should refer to the main privacy policy page for the operating system,
239    /// if there is any. These settings are optional, and providing only some of these settings is
240    /// common.
241    ///
242    /// See also `home_url`.
243    pub privacy_policy_url: Option<String>,
244}
245
246macro_rules! os_release_defaults {
247    (
248        $(
249            $(#[$meta:meta])*
250            $vis:vis fn $field:ident => $default:literal
251        )*
252    ) => {
253        $(
254            $(#[$meta])*
255            $vis fn $field(&self) -> &str {
256                match self.$field.as_ref() {
257                    Some(value) => value,
258                    None => $default,
259                }
260            }
261        )*
262    }
263}
264
265impl LinuxOSReleaseInfo {
266    os_release_defaults!(
267        /// Returns the value of `self.id` or, if `None`, "linux" (the default value).
268        pub fn id => "linux"
269        /// Returns the value of `self.name` or, if `None`, "Linux" (the default value).
270        pub fn name => "Linux"
271        /// Returns the value of `self.pretty_name` or, if `None`, "Linux" (the default value).
272        pub fn pretty_name => "Linux"
273    );
274}
275
276/// Disk information.
277#[repr(C)]
278#[derive(Debug)]
279pub struct DiskInfo {
280    pub total: u64,
281    pub free: u64,
282}
283
284/// Error types
285#[derive(Debug)]
286pub enum Error {
287    UnsupportedSystem,
288    ExecFailed(io::Error),
289    IO(io::Error),
290    SystemTime(std::time::SystemTimeError),
291    General(String),
292    Unknown,
293}
294
295impl fmt::Display for Error {
296    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
297        use self::Error::*;
298        match *self {
299            UnsupportedSystem => write!(fmt, "System is not supported"),
300            ExecFailed(ref e) => write!(fmt, "Execution failed: {}", e),
301            IO(ref e) => write!(fmt, "IO error: {}", e),
302            SystemTime(ref e) => write!(fmt, "System time error: {}", e),
303            General(ref e) => write!(fmt, "Error: {}", e),
304            Unknown => write!(fmt, "An unknown error occurred"),
305        }
306    }
307}
308
309impl std::error::Error for Error {
310    fn description(&self) -> &str {
311        use self::Error::*;
312        match *self {
313            UnsupportedSystem => "unsupported system",
314            ExecFailed(_) => "execution failed",
315            IO(_) => "io error",
316            SystemTime(_) => "system time",
317            General(_) => "general error",
318            Unknown => "unknown error",
319        }
320    }
321
322    fn cause(&self) -> Option<&dyn std::error::Error> {
323        use self::Error::*;
324        match *self {
325            UnsupportedSystem => None,
326            ExecFailed(ref e) => Some(e),
327            IO(ref e) => Some(e),
328            SystemTime(ref e) => Some(e),
329            General(_) => None,
330            Unknown => None,
331        }
332    }
333}
334
335impl From<io::Error> for Error {
336    fn from(e: io::Error) -> Error {
337        Error::IO(e)
338    }
339}
340
341impl From<std::time::SystemTimeError> for Error {
342    fn from(e: std::time::SystemTimeError) -> Error {
343        Error::SystemTime(e)
344    }
345}
346
347impl From<Box<dyn std::error::Error>> for Error {
348    fn from(e: Box<dyn std::error::Error>) -> Error {
349        Error::General(e.to_string())
350    }
351}
352
353extern "C" {
354    #[cfg(any(target_vendor = "apple", target_os = "windows"))]
355    fn get_os_type() -> *const i8;
356    #[cfg(any(target_vendor = "apple", target_os = "windows", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd"))]
357    fn get_os_release() -> *const i8;
358
359    #[cfg(all(not(any(target_os = "solaris", target_os = "illumos", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd")), any(unix, windows)))]
360    fn get_cpu_num() -> u32;
361    #[cfg(any(all(target_vendor = "apple", not(any(target_arch = "aarch64", target_arch = "arm"))), target_os = "windows", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd"))]
362    fn get_cpu_speed() -> u64;
363
364    #[cfg(target_os = "windows")]
365    fn get_loadavg() -> LoadAvg;
366    #[cfg(any(target_vendor = "apple", target_os = "windows", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd"))]
367    fn get_proc_total() -> u64;
368
369    #[cfg(any(target_vendor = "apple", target_os = "windows"))]
370    fn get_mem_info() -> MemInfo;
371    #[cfg(any(target_os = "freebsd", target_os = "openbsd", target_os = "netbsd"))]
372    fn get_mem_info_bsd(mi: &mut MemInfo) ->i32;
373
374    #[cfg(any(target_os = "linux", target_vendor = "apple", target_os = "windows"))]
375    fn get_disk_info() -> DiskInfo;
376    #[cfg(any(target_os = "freebsd", target_os = "openbsd", target_os = "netbsd"))]
377    fn get_disk_info_bsd(di: &mut DiskInfo) -> i32;
378}
379
380
381/// Get operation system type.
382///
383/// Such as "Linux", "Darwin", "Windows".
384pub fn os_type() -> Result<String, Error> {
385    #[cfg(target_os = "linux")]
386    {
387        let mut s = String::new();
388        File::open("/proc/sys/kernel/ostype")?.read_to_string(&mut s)?;
389        s.pop(); // pop '\n'
390        Ok(s)
391    }
392    #[cfg(any(target_vendor = "apple", target_os = "windows"))]
393    {
394        let typ = unsafe { ffi::CStr::from_ptr(get_os_type() as *const c_char).to_bytes() };
395        Ok(String::from_utf8_lossy(typ).into_owned())
396    }
397    #[cfg(target_os = "solaris")]
398    {
399        Ok("solaris".to_string())
400    }
401    #[cfg(target_os = "illumos")]
402    {
403        Ok("illumos".to_string())
404    }
405    #[cfg(target_os = "freebsd")]
406    {
407        Ok("freebsd".to_string())
408    }
409    #[cfg(target_os = "openbsd")]
410    {
411        Ok("openbsd".to_string())
412    }
413    #[cfg(target_os = "netbsd")]
414    {
415        Ok("netbsd".to_string())
416    }
417    #[cfg(not(any(target_os = "linux", target_vendor = "apple", target_os = "windows", target_os = "solaris", target_os = "illumos", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd")))]
418    {
419        Err(Error::UnsupportedSystem)
420    }
421}
422
423/// Get operation system release version.
424///
425/// Such as "3.19.0-gentoo"
426pub fn os_release() -> Result<String, Error> {
427    #[cfg(target_os = "linux")]
428    {
429        let mut s = String::new();
430        File::open("/proc/sys/kernel/osrelease")?.read_to_string(&mut s)?;
431        s.pop(); // pop '\n'
432        Ok(s)
433    }
434    #[cfg(any(target_vendor = "apple", target_os = "windows", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd"))]
435    {
436        unsafe {
437	    let rp = get_os_release() as *const c_char;
438	    if rp == std::ptr::null() {
439		Err(Error::Unknown)
440	    } else {
441		let typ = ffi::CStr::from_ptr(rp).to_bytes();
442		Ok(String::from_utf8_lossy(typ).into_owned())
443	    }
444	}
445    }
446    #[cfg(any(target_os = "solaris", target_os = "illumos"))]
447    {
448        let release: Option<String> = unsafe {
449            let mut name: libc::utsname = std::mem::zeroed();
450            if libc::uname(&mut name) < 0 {
451                None
452            } else {
453                let cstr = std::ffi::CStr::from_ptr(name.release.as_mut_ptr());
454                Some(cstr.to_string_lossy().to_string())
455            }
456        };
457        match release {
458            None => Err(Error::Unknown),
459            Some(release) => Ok(release),
460        }
461    }
462    #[cfg(not(any(target_os = "linux", target_vendor = "apple", target_os = "windows", target_os = "solaris", target_os = "illumos", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd")))]
463    {
464        Err(Error::UnsupportedSystem)
465    }
466}
467
468/// Get the os release note of Linux
469///
470/// Information in /etc/os-release, such as name and version of distribution.
471///
472/// See `LinuxOSReleaseInfo` for more documentation.
473pub fn linux_os_release() -> Result<LinuxOSReleaseInfo, Error> {
474    if !cfg!(target_os = "linux") {
475        return Err(Error::UnsupportedSystem);
476    }
477
478    let mut s = String::new();
479    File::open("/etc/os-release")?.read_to_string(&mut s)?;
480
481    let mut info: LinuxOSReleaseInfo = Default::default();
482    for l in s.split('\n') {
483        match parse_line_for_linux_os_release(l.trim().to_string()) {
484            Some((key, value)) =>
485                match (key.as_ref(), value) {
486                    ("ID", val) => info.id = Some(val),
487                    ("ID_LIKE", val) => info.id_like = Some(val),
488                    ("NAME", val) => info.name = Some(val),
489                    ("PRETTY_NAME", val) => info.pretty_name = Some(val),
490
491                    ("VERSION", val) => info.version = Some(val),
492                    ("VERSION_ID", val) => info.version_id = Some(val),
493                    ("VERSION_CODENAME", val) => info.version_codename = Some(val),
494
495                    ("ANSI_COLOR", val) => info.ansi_color = Some(val),
496                    ("LOGO", val) => info.logo = Some(val),
497
498                    ("CPE_NAME", val) => info.cpe_name = Some(val),
499                    ("BUILD_ID", val) => info.build_id = Some(val),
500                    ("VARIANT", val) => info.variant = Some(val),
501                    ("VARIANT_ID", val) => info.variant_id = Some(val),
502
503                    ("HOME_URL", val) => info.home_url = Some(val),
504                    ("BUG_REPORT_URL", val) => info.bug_report_url = Some(val),
505                    ("SUPPORT_URL", val) => info.support_url = Some(val),
506                    ("DOCUMENTATION_URL", val) => info.documentation_url = Some(val),
507                    ("PRIVACY_POLICY_URL", val) => info.privacy_policy_url = Some(val),
508                    _ => {}
509                }
510            None => {}
511        }
512    }
513
514    Ok(info)
515}
516
517fn parse_line_for_linux_os_release(l: String) -> Option<(String, String)> {
518    let words: Vec<&str> = l.splitn(2, '=').collect();
519    if words.len() < 2 {
520        return None
521    }
522    let mut trim_value = String::from(words[1]);
523
524    if trim_value.starts_with('"') {
525        trim_value.remove(0);
526    }
527    if trim_value.ends_with('"') {
528        let len = trim_value.len();
529        trim_value.remove(len - 1);
530    }
531
532    return Some((String::from(words[0]), trim_value))
533}
534
535/// Get cpu num quantity.
536///
537/// Notice, it returns the logical cpu quantity.
538pub fn cpu_num() -> Result<u32, Error> {
539    #[cfg(any(target_os = "solaris", target_os = "illumos", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd"))]
540    {
541        let ret = unsafe { libc::sysconf(libc::_SC_NPROCESSORS_ONLN) };
542        if ret < 1 || ret as i64 > std::u32::MAX as i64 {
543            Err(Error::IO(io::Error::last_os_error()))
544        } else {
545            Ok(ret as u32)
546        }
547    }
548    #[cfg(all(not(any(target_os = "solaris", target_os = "illumos", target_os="freebsd", target_os = "openbsd", target_os = "netbsd")), any(unix, windows)))]
549    {
550        unsafe { Ok(get_cpu_num()) }
551    }
552    #[cfg(not(any(target_os = "solaris", target_os = "illumos", unix, windows)))]
553    {
554        Err(Error::UnsupportedSystem)
555    }
556}
557
558/// Get cpu speed.
559///
560/// Such as 2500, that is 2500 MHz.
561pub fn cpu_speed() -> Result<u64, Error> {
562    #[cfg(any(target_os = "solaris", target_os = "illumos"))]
563    {
564       Ok(kstat::cpu_mhz()?)
565    }
566    #[cfg(target_os = "linux")]
567    {
568        // /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_cur_freq
569        let mut s = String::new();
570        File::open("/proc/cpuinfo")?.read_to_string(&mut s)?;
571
572        let find_cpu_mhz = s.split('\n').find(|line|
573            line.starts_with("cpu MHz\t") ||
574                line.starts_with("BogoMIPS") ||
575                line.starts_with("clock\t") ||
576                line.starts_with("bogomips per cpu")
577        );
578
579        find_cpu_mhz.and_then(|line| line.split(':').last())
580            .and_then(|val| val.replace("MHz", "").trim().parse::<f64>().ok())
581            .map(|speed| speed as u64)
582            .ok_or(Error::Unknown)
583    }
584    #[cfg(any(all(target_vendor = "apple", not(any(target_arch = "aarch64", target_arch = "arm"))), target_os = "windows"))]
585    {
586        unsafe { Ok(get_cpu_speed()) }
587    }
588    #[cfg(any(target_os = "freebsd", target_os = "openbsd", target_os = "netbsd"))]
589    {
590	let res: u64 = unsafe { get_cpu_speed() };
591	match res {
592	    0 => Err(Error::IO(io::Error::last_os_error())),
593	    _ => Ok(res),
594	}
595    }
596    #[cfg(not(any(target_os = "solaris", target_os = "illumos", target_os = "linux", all(target_vendor = "apple", not(any(target_arch = "aarch64", target_arch = "arm"))), target_os = "windows", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd")))]
597    {
598        Err(Error::UnsupportedSystem)
599    }
600}
601
602/// Get system load average value.
603///
604/// Notice, on windows, one/five/fifteen of the LoadAvg returned are the current load.
605pub fn loadavg() -> Result<LoadAvg, Error> {
606    #[cfg(target_os = "linux")]
607    {
608        let mut s = String::new();
609        File::open("/proc/loadavg")?.read_to_string(&mut s)?;
610        let loads = s.trim().split(' ')
611            .take(3)
612            .map(|val| val.parse::<f64>().unwrap())
613            .collect::<Vec<f64>>();
614        Ok(LoadAvg {
615            one: loads[0],
616            five: loads[1],
617            fifteen: loads[2],
618        })
619    }
620    #[cfg(any(target_os = "solaris", target_os = "illumos", target_vendor = "apple", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd"))]
621    {
622        let mut l: [c_double; 3] = [0f64; 3];
623        if unsafe { libc::getloadavg(l.as_mut_ptr(), l.len() as c_int) } < 3 {
624            Err(Error::Unknown)
625        } else {
626            Ok(LoadAvg {
627                one: l[0],
628                five: l[1],
629                fifteen: l[2],
630            })
631        }
632    }
633    #[cfg(any(target_os = "windows"))]
634    {
635        Ok(unsafe { get_loadavg() })
636    }
637    #[cfg(not(any(target_os = "linux", target_os = "solaris", target_os = "illumos", target_vendor = "apple", target_os = "windows", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd")))]
638    {
639        Err(Error::UnsupportedSystem)
640    }
641}
642
643/// Get current processes quantity.
644pub fn proc_total() -> Result<u64, Error> {
645    #[cfg(any(target_os = "solaris", target_os = "illumos"))]
646    {
647        Ok(kstat::nproc()?)
648    }
649    #[cfg(target_os = "linux")]
650    {
651        let mut s = String::new();
652        File::open("/proc/loadavg")?.read_to_string(&mut s)?;
653        s.split(' ')
654            .nth(3)
655            .and_then(|val| val.split('/').last())
656            .and_then(|val| val.parse::<u64>().ok())
657            .ok_or(Error::Unknown)
658    }
659    #[cfg(any(target_vendor = "apple", target_os = "windows"))]
660    {
661        Ok(unsafe { get_proc_total() })
662    }
663    #[cfg(any(target_os = "freebsd", target_os = "openbsd", target_os = "netbsd"))]
664    {
665	let res: u64 = unsafe { get_proc_total() };
666	match res {
667	    0 => Err(Error::IO(io::Error::last_os_error())),
668	    _ => Ok(res),
669	}
670    }
671    #[cfg(not(any(target_os = "linux", target_os = "solaris", target_os = "illumos", target_vendor = "apple", target_os = "windows", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd")))]
672    {
673        Err(Error::UnsupportedSystem)
674    }
675}
676
677#[cfg(any(target_os = "solaris", target_os = "illumos"))]
678fn pagesize() -> Result<u32, Error> {
679    let ret = unsafe { libc::sysconf(libc::_SC_PAGESIZE) };
680    if ret < 1 || ret > std::u32::MAX as i64 {
681        Err(Error::Unknown)
682    } else {
683        Ok(ret as u32)
684    }
685}
686
687/// Get memory information.
688///
689/// On Mac OS X and Windows, the buffers and cached variables of the MemInfo returned are zero.
690pub fn mem_info() -> Result<MemInfo, Error> {
691    #[cfg(target_os = "linux")]
692    {
693        let mut s = String::new();
694        File::open("/proc/meminfo")?.read_to_string(&mut s)?;
695        let mut meminfo_hashmap = HashMap::new();
696        for line in s.lines() {
697            let mut split_line = line.split_whitespace();
698            let label = split_line.next();
699            let value = split_line.next();
700            if value.is_some() && label.is_some() {
701                let label = label.unwrap().split(':').nth(0).ok_or(Error::Unknown)?;
702                let value = value.unwrap().parse::<u64>().ok().ok_or(Error::Unknown)?;
703                meminfo_hashmap.insert(label, value);
704            }
705        }
706        let total = *meminfo_hashmap.get("MemTotal").ok_or(Error::Unknown)?;
707        let free = *meminfo_hashmap.get("MemFree").ok_or(Error::Unknown)?;
708        let buffers = *meminfo_hashmap.get("Buffers").ok_or(Error::Unknown)?;
709        let cached = *meminfo_hashmap.get("Cached").ok_or(Error::Unknown)?;
710        let avail = meminfo_hashmap.get("MemAvailable").map(|v| v.clone()).or_else(|| {
711            let sreclaimable = *meminfo_hashmap.get("SReclaimable")?;
712            let shmem = *meminfo_hashmap.get("Shmem")?;
713            Some(free + buffers + cached + sreclaimable - shmem)
714        }).ok_or(Error::Unknown)?;
715        let swap_total = *meminfo_hashmap.get("SwapTotal").ok_or(Error::Unknown)?;
716        let swap_free = *meminfo_hashmap.get("SwapFree").ok_or(Error::Unknown)?;
717        Ok(MemInfo {
718            total,
719            free,
720            avail,
721            buffers,
722            cached,
723            swap_total,
724            swap_free,
725        })
726    }
727    #[cfg(any(target_os = "solaris", target_os = "illumos"))]
728    {
729        let pagesize = pagesize()? as u64;
730        let pages = kstat::pages()?;
731        return Ok(MemInfo {
732            total: pages.physmem * pagesize / 1024,
733            avail: 0,
734            free: pages.freemem * pagesize / 1024,
735            cached: 0,
736            buffers: 0,
737            swap_total: 0,
738            swap_free: 0,
739        });
740    }
741    #[cfg(any(target_vendor = "apple", target_os = "windows"))]
742    {
743        Ok(unsafe { get_mem_info() })
744    }
745    #[cfg(any(target_os = "freebsd", target_os = "openbsd", target_os = "netbsd"))]
746    {
747	let mut mi:MemInfo = MemInfo{total: 0, free: 0, avail: 0, buffers: 0,
748				     cached: 0, swap_total: 0, swap_free: 0};
749	let res: i32 = unsafe { get_mem_info_bsd(&mut mi) };
750	match res {
751	    -1 => Err(Error::IO(io::Error::last_os_error())),
752	    0 => Ok(mi),
753	    _ => Err(Error::Unknown),
754	}
755    }
756    #[cfg(not(any(target_os = "linux", target_os = "solaris", target_os = "illumos", target_vendor = "apple", target_os = "windows", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd")))]
757    {
758        Err(Error::UnsupportedSystem)
759    }
760}
761
762/// Get disk information.
763///
764/// Notice, it just calculate current disk on Windows.
765pub fn disk_info() -> Result<DiskInfo, Error> {
766    #[cfg(any(target_os = "linux", target_vendor = "apple", target_os = "windows"))]
767    {
768        Ok(unsafe { get_disk_info() })
769    }
770    #[cfg(any(target_os = "freebsd", target_os = "openbsd", target_os = "netbsd"))]
771    {
772	let mut di:DiskInfo = DiskInfo{total: 0, free: 0};
773	let res: i32 = unsafe { get_disk_info_bsd(&mut di) };
774	match res {
775	    -1 => Err(Error::IO(io::Error::last_os_error())),
776	    0 => Ok(di),
777	    _ => Err(Error::Unknown),
778	}
779    }
780    #[cfg(not(any(target_os = "linux", target_vendor = "apple", target_os = "windows", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd")))]
781    {
782        Err(Error::UnsupportedSystem)
783    }
784}
785
786/// Get hostname.
787#[cfg(target_family = "unix")]
788pub fn hostname() -> Result<String, Error> {
789    unsafe {
790        let buf_size = libc::sysconf(libc::_SC_HOST_NAME_MAX) as usize;
791        let mut buf = Vec::<u8>::with_capacity(buf_size + 1);
792        if libc::gethostname(buf.as_mut_ptr() as *mut libc::c_char, buf_size) < 0 {
793            return Err(Error::IO(io::Error::last_os_error()));
794        }
795        let hostname_len = libc::strnlen(buf.as_ptr() as *const libc::c_char, buf_size);
796        buf.set_len(hostname_len);
797        Ok(ffi::CString::new(buf).unwrap().into_string().unwrap())
798    }
799}
800
801#[cfg(target_family = "windows")]
802pub fn hostname() -> Result<String, Error> {
803    use std::process::Command;
804    Command::new("hostname")
805        .output()
806        .map_err(Error::ExecFailed)
807        .map(|output| String::from_utf8(output.stdout).unwrap().trim().to_string())
808}
809
810/// Get system boottime
811#[cfg(not(windows))]
812pub fn boottime() -> Result<timeval, Error> {
813    let mut bt = timeval {
814        tv_sec: 0,
815        tv_usec: 0
816    };
817
818    #[cfg(any(target_os = "linux", target_os="android"))]
819    {
820        let mut s = String::new();
821        File::open("/proc/uptime")?.read_to_string(&mut s)?;
822        let secs = s.trim().split(' ')
823            .take(2)
824            .map(|val| val.parse::<f64>().unwrap())
825            .collect::<Vec<f64>>();
826        bt.tv_sec = secs[0] as libc::time_t;
827        bt.tv_usec = secs[1] as libc::suseconds_t;
828	    return Ok(bt);
829    }
830    #[cfg(any(target_vendor = "apple", target_os="freebsd", target_os = "openbsd", target_os = "netbsd"))]
831    {
832        let mut mib = [OS_CTL_KERN, OS_KERN_BOOTTIME];
833        let mut size: libc::size_t = size_of_val(&bt) as libc::size_t;
834        unsafe {
835            if sysctl(&mut mib[0], 2,
836                   &mut bt as *mut timeval as *mut libc::c_void,
837                   &mut size, null_mut(), 0) == -1 {
838                return Err(Error::IO(io::Error::last_os_error()));
839            } else {
840                return Ok(bt);
841            }
842        }
843    }
844    #[cfg(any(target_os = "solaris", target_os = "illumos"))]
845    {
846        let start = kstat::boot_time()?;
847        let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?;
848        let now = now.as_secs();
849        if now < start {
850            return Err(Error::General("time went backwards".into()));
851        }
852        bt.tv_sec = (now - start) as i64;
853	    return Ok(bt);
854    }
855
856    #[warn(unreachable_code)]
857    Err(Error::UnsupportedSystem)
858}
859
860#[cfg(test)]
861mod test {
862    use super::*;
863
864    #[test]
865    pub fn test_os_type() {
866        let typ = os_type().unwrap();
867        assert!(typ.len() > 0);
868        println!("os_type(): {}", typ);
869    }
870
871    #[test]
872    pub fn test_os_release() {
873        let release = os_release().unwrap();
874        assert!(release.len() > 0);
875        println!("os_release(): {}", release);
876    }
877
878    #[test]
879    pub fn test_cpu_num() {
880        let num = cpu_num().unwrap();
881        assert!(num > 0);
882        println!("cpu_num(): {}", num);
883    }
884
885    #[test]
886    #[cfg(not(all(target_vendor = "apple", target_arch = "aarch64")))]
887    pub fn test_cpu_speed() {
888        let speed = cpu_speed().unwrap();
889        assert!(speed > 0);
890        println!("cpu_speed(): {}", speed);
891    }
892
893    #[test]
894    pub fn test_loadavg() {
895        let load = loadavg().unwrap();
896        println!("loadavg(): {:?}", load);
897    }
898
899    #[test]
900    pub fn test_proc_total() {
901        let procs = proc_total().unwrap();
902        assert!(procs > 0);
903        println!("proc_total(): {}", procs);
904    }
905
906    #[test]
907    pub fn test_mem_info() {
908        let mem = mem_info().unwrap();
909        assert!(mem.total > 0);
910        println!("mem_info(): {:?}", mem);
911    }
912
913    #[test]
914    #[cfg(not(any(target_os = "solaris", target_os = "illumos")))]
915    pub fn test_disk_info() {
916        let info = disk_info().unwrap();
917        println!("disk_info(): {:?}", info);
918    }
919
920    #[test]
921    pub fn test_hostname() {
922        let host = hostname().unwrap();
923        assert!(host.len() > 0);
924        println!("hostname(): {}", host);
925    }
926
927    #[test]
928    #[cfg(not(windows))]
929    pub fn test_boottime() {
930        let bt = boottime().unwrap();
931        println!("boottime(): {} {}", bt.tv_sec, bt.tv_usec);
932        assert!(bt.tv_sec > 0 || bt.tv_usec > 0);
933    }
934
935    #[test]
936    #[cfg(target_os = "linux")]
937    pub fn test_linux_os_release() {
938        let os_release = linux_os_release().unwrap();
939        println!("linux_os_release(): {:?}", os_release.name)
940    }
941}