From 2fd4f480d3a34764e348e660a46f4094ff8d5a16 Mon Sep 17 00:00:00 2001 From: Bluemangoo Date: Sat, 1 Feb 2025 13:26:58 +0000 Subject: [PATCH 1/3] uptime: refactor --- Cargo.lock | 19 ++ Cargo.toml | 1 + src/uu/uptime/Cargo.toml | 6 + src/uu/uptime/src/uptime.rs | 397 +++++++++++++++++++++++++----------- 4 files changed, 302 insertions(+), 121 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4a1ebec418b..b48f4017d47 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -263,6 +263,12 @@ dependencies = [ "serde", ] +[[package]] +name = "build-env" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1522ac6ee801a11bf9ef3f80403f4ede6eb41291fac3dde3de09989679305f25" + [[package]] name = "bumpalo" version = "3.16.0" @@ -1327,6 +1333,17 @@ dependencies = [ "redox_syscall", ] +[[package]] +name = "libsystemd-sys" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed080163caa59cc29b34bce2209b737149a4bac148cd9a8b04e4c12822798119" +dependencies = [ + "build-env", + "libc", + "pkg-config", +] + [[package]] name = "linux-raw-sys" version = "0.3.8" @@ -3465,9 +3482,11 @@ version = "0.0.29" dependencies = [ "chrono", "clap", + "libsystemd-sys", "thiserror 2.0.11", "utmp-classic", "uucore", + "windows-sys 0.59.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 2a04b8855ff..c0676173327 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -302,6 +302,7 @@ iana-time-zone = "0.1.57" indicatif = "0.17.8" itertools = "0.14.0" libc = "0.2.153" +libsystemd-sys = "0.9.3" lscolors = { version = "0.20.0", default-features = false, features = [ "gnu_legacy", ] } diff --git a/src/uu/uptime/Cargo.toml b/src/uu/uptime/Cargo.toml index 38126a3e956..a4a419a4df6 100644 --- a/src/uu/uptime/Cargo.toml +++ b/src/uu/uptime/Cargo.toml @@ -22,9 +22,15 @@ clap = { workspace = true } uucore = { workspace = true, features = ["libc", "utmpx"] } thiserror = { workspace = true } +[target.'cfg(target_os="linux")'.dependencies] +libsystemd-sys = { workspace = true } + [target.'cfg(target_os = "openbsd")'.dependencies] utmp-classic = { workspace = true } +[target.'cfg(target_os="windows")'.dependencies] +windows-sys = {workspace = true, features = ["Win32_System_RemoteDesktop", "Wdk_System_SystemInformation"]} + [[bin]] name = "uptime" path = "src/main.rs" diff --git a/src/uu/uptime/src/uptime.rs b/src/uu/uptime/src/uptime.rs index feaf2d8a4ef..a4a9b45c5eb 100644 --- a/src/uu/uptime/src/uptime.rs +++ b/src/uu/uptime/src/uptime.rs @@ -7,16 +7,9 @@ use chrono::{Local, TimeZone, Utc}; use clap::ArgMatches; -use std::ffi::OsString; -use std::fs; use std::io; -use std::os::unix::fs::FileTypeExt; use thiserror::Error; -use uucore::error::set_exit_code; use uucore::error::UError; -use uucore::show_error; - -#[cfg(not(target_os = "openbsd"))] use uucore::libc::time_t; use uucore::error::{UResult, USimpleError}; @@ -27,6 +20,7 @@ use uucore::{format_usage, help_about, help_usage}; #[cfg(target_os = "openbsd")] use utmp_classic::{parse_from_path, UtmpEntry}; +#[cfg(unix)] #[cfg(not(target_os = "openbsd"))] use uucore::utmpx::*; @@ -42,7 +36,7 @@ use uucore::libc::getloadavg; #[cfg(windows)] extern "C" { - fn GetTickCount() -> uucore::libc::uint32_t; + fn GetTickCount() -> u32; } #[derive(Debug, Error)] @@ -68,27 +62,38 @@ impl UError for UptimeError { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { let matches = uu_app().try_get_matches_from(args)?; - let argument = matches.get_many::(options::PATH); - // Switches to default uptime behaviour if there is no argument - if argument.is_none() { - return default_uptime(&matches); - } - let mut arg_iter = argument.unwrap(); - - let file_path = arg_iter.next().unwrap(); - if let Some(path) = arg_iter.next() { - // Uptime doesn't attempt to calculate boot time if there is extra arguments. - // Its a fatal error - show_error!( - "{}", - UptimeError::ExtraOperandError(path.to_owned().into_string().unwrap()) - ); - set_exit_code(1); - return Ok(()); - } + #[cfg(windows)] + return default_uptime(&matches); + + #[cfg(unix)] + { + use std::ffi::OsString; + use uucore::error::set_exit_code; + use uucore::show_error; + + let argument = matches.get_many::(options::PATH); + + // Switches to default uptime behaviour if there is no argument + if argument.is_none() { + return default_uptime(&matches); + } + let mut arg_iter = argument.unwrap(); + + let file_path = arg_iter.next().unwrap(); + if let Some(path) = arg_iter.next() { + // Uptime doesn't attempt to calculate boot time if there is extra arguments. + // Its a fatal error + show_error!( + "{}", + UptimeError::ExtraOperandError(path.to_owned().into_string().unwrap()) + ); + set_exit_code(1); + return Ok(()); + } - uptime_with_file(file_path) + uptime_with_file(file_path) + } } pub fn uu_app() -> Command { @@ -114,7 +119,12 @@ pub fn uu_app() -> Command { } #[cfg(unix)] -fn uptime_with_file(file_path: &OsString) -> UResult<()> { +fn uptime_with_file(file_path: &std::ffi::OsString) -> UResult<()> { + use std::fs; + use std::os::unix::fs::FileTypeExt; + use uucore::error::set_exit_code; + use uucore::show_error; + // Uptime will print loadavg and time to stderr unless we encounter an extra operand. let mut non_fatal_error = false; @@ -149,7 +159,7 @@ fn uptime_with_file(file_path: &OsString) -> UResult<()> { show_error!("couldn't get boot time"); print_time(); print!("up ???? days ??:??,"); - print_nusers(0); + print_nusers(Some(0))?; print_loadavg(); set_exit_code(1); return Ok(()); @@ -159,7 +169,7 @@ fn uptime_with_file(file_path: &OsString) -> UResult<()> { if non_fatal_error { print_time(); print!("up ???? days ??:??,"); - print_nusers(0); + print_nusers(Some(0))?; print_loadavg(); return Ok(()); } @@ -169,10 +179,9 @@ fn uptime_with_file(file_path: &OsString) -> UResult<()> { #[cfg(not(target_os = "openbsd"))] { - let (boot_time, count) = process_utmpx_from_file(file_path); + let (boot_time, count) = process_utmpx(Some(file_path)); if let Some(time) = boot_time { - let upsecs = get_uptime_from_boot_time(time); - print_uptime(upsecs); + print_uptime(Some(time))?; } else { show_error!("couldn't get boot time"); set_exit_code(1); @@ -186,18 +195,18 @@ fn uptime_with_file(file_path: &OsString) -> UResult<()> { { user_count = process_utmp_from_file(file_path.to_str().expect("invalid utmp path file")); - let upsecs = get_uptime(); + let upsecs = get_uptime(None); if upsecs < 0 { show_error!("couldn't get boot time"); set_exit_code(1); print!("up ???? days ??:??,"); } else { - print_uptime(upsecs); + print_uptime(Some(upsecs))?; } } - print_nusers(user_count); + print_nusers(Some(user_count))?; print_loadavg(); Ok(()) @@ -205,15 +214,17 @@ fn uptime_with_file(file_path: &OsString) -> UResult<()> { /// Default uptime behaviour i.e. when no file argument is given. fn default_uptime(matches: &ArgMatches) -> UResult<()> { - #[cfg(target_os = "openbsd")] - let user_count = process_utmp_from_file("/var/run/utmp"); + #[cfg(unix)] #[cfg(not(target_os = "openbsd"))] - let (boot_time, user_count) = process_utmpx(); + let (boot_time, _) = process_utmpx(None); #[cfg(target_os = "openbsd")] - let uptime = get_uptime(); + let uptime = get_uptime(None); + #[cfg(unix)] #[cfg(not(target_os = "openbsd"))] let uptime = get_uptime(boot_time); + #[cfg(target_os = "windows")] + let uptime = get_uptime(None); if matches.get_flag(options::SINCE) { let initial_date = Local @@ -223,51 +234,64 @@ fn default_uptime(matches: &ArgMatches) -> UResult<()> { return Ok(()); } - if uptime < 0 { - return Err(USimpleError::new(1, "could not retrieve system uptime")); - } - print_time(); - print_uptime(uptime); - print_nusers(user_count); + print_uptime(None)?; + print_nusers(None)?; print_loadavg(); Ok(()) } #[cfg(unix)] -fn print_loadavg() { +fn get_loadavg() -> (f64, f64, f64) { use uucore::libc::c_double; let mut avg: [c_double; 3] = [0.0; 3]; let loads: i32 = unsafe { getloadavg(avg.as_mut_ptr(), 3) }; if loads == -1 { - println!(); + (-1.0, -1.0, -1.0) } else { - print!("load average: "); - for n in 0..loads { - print!( - "{:.2}{}", - avg[n as usize], - if n == loads - 1 { "\n" } else { ", " } - ); - } + (avg[0], avg[1], avg[2]) } } +/// Windows does not seem to have anything similar. #[cfg(windows)] +fn get_loadavg() -> (f64, f64, f64) { + (-1.0, -1.0, -1.0) +} + +#[inline] +fn get_formatted_loadavg() -> UResult { + let loadavg = get_loadavg(); + if loadavg.0 < 0.0 || loadavg.1 < 0.0 || loadavg.2 < 0.0 { + Err(USimpleError::new(1, "could not retrieve uptime")) + } else { + Ok(format!( + "load average: {:.2}, {:.2}, {:.2}", + loadavg.0, loadavg.1, loadavg.2 + )) + } +} + +#[inline] fn print_loadavg() { - // XXX: currently this is a noop as Windows does not seem to have anything comparable to - // getloadavg() + match get_formatted_loadavg() { + Err(_) => {} + Ok(s) => println!("{}", s), + } } -#[cfg(unix)] #[cfg(target_os = "openbsd")] fn process_utmp_from_file(file: &str) -> usize { let mut nusers = 0; - let entries = parse_from_path(file).unwrap_or_default(); + let entries = match parse_from_path(file) { + Some(e) => e, + None => return 0, + }; + for entry in entries { if let UtmpEntry::UTMP { line: _, @@ -286,32 +310,16 @@ fn process_utmp_from_file(file: &str) -> usize { #[cfg(unix)] #[cfg(not(target_os = "openbsd"))] -fn process_utmpx() -> (Option, usize) { +fn process_utmpx(file: Option<&std::ffi::OsString>) -> (Option, usize) { let mut nusers = 0; let mut boot_time = None; - for line in Utmpx::iter_all_records() { - match line.record_type() { - USER_PROCESS => nusers += 1, - BOOT_TIME => { - let dt = line.login_time(); - if dt.unix_timestamp() > 0 { - boot_time = Some(dt.unix_timestamp() as time_t); - } - } - _ => continue, - } - } - (boot_time, nusers) -} - -#[cfg(unix)] -#[cfg(not(target_os = "openbsd"))] -fn process_utmpx_from_file(file: &OsString) -> (Option, usize) { - let mut nusers = 0; - let mut boot_time = None; + let records = match file { + Some(f) => Utmpx::iter_all_records_from(f), + None => Utmpx::iter_all_records(), + }; - for line in Utmpx::iter_all_records_from(file) { + for line in records { match line.record_type() { USER_PROCESS => nusers += 1, BOOT_TIME => { @@ -331,33 +339,36 @@ fn process_utmpx() -> (Option, usize) { (None, 0) // TODO: change 0 to number of users } -fn print_nusers(nusers: usize) { - match nusers.cmp(&1) { - std::cmp::Ordering::Less => print!(" 0 users, "), - std::cmp::Ordering::Equal => print!("1 user, "), - std::cmp::Ordering::Greater => print!("{nusers} users, "), - }; +fn print_nusers(nusers: Option) -> UResult<()> { + print!( + "{}, ", + match nusers { + None => { + get_formatted_nusers() + } + Some(nusers) => { + format_nusers(nusers) + } + } + ); + Ok(()) } fn print_time() { - let local_time = Local::now().time(); + print!(" {} ", get_formatted_time()); +} - print!(" {} ", local_time.format("%H:%M:%S")); +fn print_uptime(boot_time: Option) -> UResult<()> { + print!("up {}, ", get_formated_uptime(boot_time)?); + Ok(()) } -#[cfg(not(target_os = "openbsd"))] -fn get_uptime_from_boot_time(boot_time: time_t) -> i64 { - let now = Local::now().timestamp(); - #[cfg(target_pointer_width = "64")] - let boottime: i64 = boot_time; - #[cfg(not(target_pointer_width = "64"))] - let boottime: i64 = boot_time.into(); - now - boottime +fn get_formatted_time() -> String { + Local::now().time().format("%H:%M:%S").to_string() } -#[cfg(unix)] #[cfg(target_os = "openbsd")] -fn get_uptime() -> i64 { +pub fn get_uptime(_boot_time: Option) -> i64 { use uucore::libc::clock_gettime; use uucore::libc::CLOCK_BOOTTIME; @@ -387,7 +398,7 @@ fn get_uptime() -> i64 { #[cfg(unix)] #[cfg(not(target_os = "openbsd"))] -fn get_uptime(boot_time: Option) -> i64 { +pub fn get_uptime(boot_time: Option) -> i64 { use std::fs::File; use std::io::Read; @@ -399,33 +410,177 @@ fn get_uptime(boot_time: Option) -> i64 { .and_then(|_| proc_uptime_s.split_whitespace().next()) .and_then(|s| s.split('.').next().unwrap_or("0").parse().ok()); - proc_uptime.unwrap_or_else(|| match boot_time { - Some(t) => { - let now = Local::now().timestamp(); - #[cfg(target_pointer_width = "64")] - let boottime: i64 = t; - #[cfg(not(target_pointer_width = "64"))] - let boottime: i64 = t.into(); - now - boottime + proc_uptime.unwrap_or_else(|| { + let boot_time = boot_time.or_else(|| { + let (boot_time, _) = process_utmpx(None); + boot_time + }); + match boot_time { + Some(t) => { + let now = Local::now().timestamp(); + #[cfg(target_pointer_width = "64")] + let boottime: i64 = t; + #[cfg(not(target_pointer_width = "64"))] + let boottime: i64 = t.into(); + now - boottime + } + None => -1, } - None => -1, }) } #[cfg(windows)] -fn get_uptime(_boot_time: Option) -> i64 { +pub fn get_uptime(_boot_time: Option) -> i64 { unsafe { GetTickCount() as i64 } } -fn print_uptime(upsecs: i64) { - let updays = upsecs / 86400; - let uphours = (upsecs - (updays * 86400)) / 3600; - let upmins = (upsecs - (updays * 86400) - (uphours * 3600)) / 60; - match updays.cmp(&1) { - std::cmp::Ordering::Equal => print!("up {updays:1} day, {uphours:2}:{upmins:02}, "), - std::cmp::Ordering::Greater => { - print!("up {updays:1} days {uphours:2}:{upmins:02}, "); +/// Returns the formatted uptime string, e.g. "1 day, 3:45" +#[inline] +pub fn get_formated_uptime(boot_time: Option) -> UResult { + let up_secs = get_uptime(boot_time); + + if up_secs < 0 { + return Err(USimpleError::new(1, "could not retrieve system uptime")); + } + let up_days = up_secs / 86400; + let up_hours = (up_secs - (up_days * 86400)) / 3600; + let up_mins = (up_secs - (up_days * 86400) - (up_hours * 3600)) / 60; + match up_days.cmp(&1) { + std::cmp::Ordering::Equal => Ok(format!("{up_days:1} day, {up_hours:2}:{up_mins:02}")), + std::cmp::Ordering::Greater => Ok(format!("{up_days:1} days {up_hours:2}:{up_mins:02}")), + _ => Ok(format!("{up_hours:2}:{up_mins:02}")), + } +} + +#[inline] +fn format_nusers(nusers: usize) -> String { + match nusers { + 0 => "0 user".to_string(), + 1 => "1 user".to_string(), + _ => format!("{} users", nusers), + } +} + +#[inline] +fn get_formatted_nusers() -> String { + format_nusers(get_nusers()) +} + +#[cfg(target_os = "windows")] +fn get_nusers() -> usize { + use std::ptr; + use windows_sys::Win32::System::RemoteDesktop::*; + + let mut num_user = 0; + + unsafe { + let mut session_info_ptr = ptr::null_mut(); + let mut session_count = 0; + + let result = WTSEnumerateSessionsW( + WTS_CURRENT_SERVER_HANDLE, + 0, + 1, + &mut session_info_ptr, + &mut session_count, + ); + if result == 0 { + return 0; } - _ => print!("up {uphours:2}:{upmins:02}, "), - }; + + let sessions = std::slice::from_raw_parts(session_info_ptr, session_count as usize); + + for session in sessions { + let mut buffer: *mut u16 = ptr::null_mut(); + let mut bytes_returned = 0; + + let result = WTSQuerySessionInformationW( + WTS_CURRENT_SERVER_HANDLE, + session.SessionId, + 5, + &mut buffer, + &mut bytes_returned, + ); + if result == 0 || buffer.is_null() { + continue; + } + + let username = if !buffer.is_null() { + let cstr = std::ffi::CStr::from_ptr(buffer as *const i8); + cstr.to_string_lossy().to_string() + } else { + String::new() + }; + if !username.is_empty() { + num_user += 1; + } + + WTSFreeMemory(buffer as _); + } + + WTSFreeMemory(session_info_ptr as _); + } + + num_user +} + +#[cfg(unix)] +#[cfg(not(target_os = "openbsd"))] +// see: https://gitlab.com/procps-ng/procps/-/blob/4740a0efa79cade867cfc7b32955fe0f75bf5173/library/uptime.c#L63-L115 +fn get_nusers() -> usize { + use uucore::utmpx::Utmpx; + + #[cfg(target_os = "linux")] + unsafe { + use libsystemd_sys::daemon::sd_booted; + use libsystemd_sys::login::{sd_get_sessions, sd_session_get_class}; + use std::ffi::{c_char, c_void, CStr}; + use std::ptr; + use uucore::libc::free; + // systemd + if sd_booted() > 0 { + let mut sessions_list: *mut *mut c_char = ptr::null_mut(); + let mut num_user = 0; + let sessions = sd_get_sessions(&mut sessions_list); // rust-systemd does not implement this + + if sessions > 0 { + for i in 0..sessions { + let mut class: *mut c_char = ptr::null_mut(); + + if sd_session_get_class( + *sessions_list.add(i as usize) as *const c_char, + &mut class, + ) < 0 + { + continue; + } + if CStr::from_ptr(class).to_str().unwrap().starts_with("user") { + num_user += 1; + } + free(class as *mut c_void); + } + } + + for i in 0..sessions { + free(*sessions_list.add(i as usize) as *mut c_void); + } + free(sessions_list as *mut c_void); + + return num_user; + } + } + + // utmpx + let mut num_user = 0; + Utmpx::iter_all_records().for_each(|ut| { + if ut.record_type() == 7 && !ut.user().is_empty() { + num_user += 1; + } + }); + num_user +} + +#[cfg(target_os = "openbsd")] +fn get_nusers() -> usize { + process_utmp_from_file("/var/run/utmp") } From 5a28db596b03bad17cd7560cdc894354ba02329d Mon Sep 17 00:00:00 2001 From: Bluemangoo Date: Thu, 13 Feb 2025 09:14:54 +0000 Subject: [PATCH 2/3] uptime: move some codes to uucore --- Cargo.lock | 21 +- Cargo.toml | 1 - src/uu/uptime/Cargo.toml | 10 +- src/uu/uptime/src/uptime.rs | 320 ++------------------------ src/uucore/Cargo.toml | 6 + src/uucore/src/lib/features.rs | 2 + src/uucore/src/lib/features/uptime.rs | 283 +++++++++++++++++++++++ src/uucore/src/lib/lib.rs | 2 + 8 files changed, 315 insertions(+), 330 deletions(-) create mode 100644 src/uucore/src/lib/features/uptime.rs diff --git a/Cargo.lock b/Cargo.lock index b48f4017d47..fb9e731c530 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -263,12 +263,6 @@ dependencies = [ "serde", ] -[[package]] -name = "build-env" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1522ac6ee801a11bf9ef3f80403f4ede6eb41291fac3dde3de09989679305f25" - [[package]] name = "bumpalo" version = "3.16.0" @@ -1313,7 +1307,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" dependencies = [ "cfg-if", - "windows-targets 0.48.5", + "windows-targets 0.52.6", ] [[package]] @@ -1333,17 +1327,6 @@ dependencies = [ "redox_syscall", ] -[[package]] -name = "libsystemd-sys" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed080163caa59cc29b34bce2209b737149a4bac148cd9a8b04e4c12822798119" -dependencies = [ - "build-env", - "libc", - "pkg-config", -] - [[package]] name = "linux-raw-sys" version = "0.3.8" @@ -3482,7 +3465,6 @@ version = "0.0.29" dependencies = [ "chrono", "clap", - "libsystemd-sys", "thiserror 2.0.11", "utmp-classic", "uucore", @@ -3583,6 +3565,7 @@ dependencies = [ "tempfile", "thiserror 2.0.11", "time", + "utmp-classic", "uucore_procs", "walkdir", "wild", diff --git a/Cargo.toml b/Cargo.toml index c0676173327..2a04b8855ff 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -302,7 +302,6 @@ iana-time-zone = "0.1.57" indicatif = "0.17.8" itertools = "0.14.0" libc = "0.2.153" -libsystemd-sys = "0.9.3" lscolors = { version = "0.20.0", default-features = false, features = [ "gnu_legacy", ] } diff --git a/src/uu/uptime/Cargo.toml b/src/uu/uptime/Cargo.toml index a4a419a4df6..3ae64b08183 100644 --- a/src/uu/uptime/Cargo.toml +++ b/src/uu/uptime/Cargo.toml @@ -19,17 +19,17 @@ path = "src/uptime.rs" [dependencies] chrono = { workspace = true } clap = { workspace = true } -uucore = { workspace = true, features = ["libc", "utmpx"] } thiserror = { workspace = true } - -[target.'cfg(target_os="linux")'.dependencies] -libsystemd-sys = { workspace = true } +uucore = { workspace = true, features = ["libc", "utmpx", "uptime"] } [target.'cfg(target_os = "openbsd")'.dependencies] utmp-classic = { workspace = true } [target.'cfg(target_os="windows")'.dependencies] -windows-sys = {workspace = true, features = ["Win32_System_RemoteDesktop", "Wdk_System_SystemInformation"]} +windows-sys = { workspace = true, features = [ + "Win32_System_RemoteDesktop", + "Wdk_System_SystemInformation", +] } [[bin]] name = "uptime" diff --git a/src/uu/uptime/src/uptime.rs b/src/uu/uptime/src/uptime.rs index a4a9b45c5eb..0c387bf2d0b 100644 --- a/src/uu/uptime/src/uptime.rs +++ b/src/uu/uptime/src/uptime.rs @@ -3,7 +3,7 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// spell-checker:ignore getloadavg behaviour loadavg uptime upsecs updays upmins uphours boottime nusers utmpxname gettime clockid +// spell-checker:ignore getloadavg behaviour loadavg uptime upsecs updays upmins uphours boottime nusers utmpxname gettime clockid formated use chrono::{Local, TimeZone, Utc}; use clap::ArgMatches; @@ -11,15 +11,14 @@ use std::io; use thiserror::Error; use uucore::error::UError; use uucore::libc::time_t; +use uucore::uptime::*; -use uucore::error::{UResult, USimpleError}; +use uucore::error::UResult; use clap::{builder::ValueParser, crate_version, Arg, ArgAction, Command, ValueHint}; use uucore::{format_usage, help_about, help_usage}; -#[cfg(target_os = "openbsd")] -use utmp_classic::{parse_from_path, UtmpEntry}; #[cfg(unix)] #[cfg(not(target_os = "openbsd"))] use uucore::utmpx::*; @@ -31,9 +30,6 @@ pub mod options { pub static PATH: &str = "path"; } -#[cfg(unix)] -use uucore::libc::getloadavg; - #[cfg(windows)] extern "C" { fn GetTickCount() -> u32; @@ -193,7 +189,7 @@ fn uptime_with_file(file_path: &std::ffi::OsString) -> UResult<()> { #[cfg(target_os = "openbsd")] { - user_count = process_utmp_from_file(file_path.to_str().expect("invalid utmp path file")); + user_count = get_nusers(file_path.to_str().expect("invalid utmp path file")); let upsecs = get_uptime(None); if upsecs < 0 { @@ -214,19 +210,18 @@ fn uptime_with_file(file_path: &std::ffi::OsString) -> UResult<()> { /// Default uptime behaviour i.e. when no file argument is given. fn default_uptime(matches: &ArgMatches) -> UResult<()> { - #[cfg(unix)] - #[cfg(not(target_os = "openbsd"))] - let (boot_time, _) = process_utmpx(None); - - #[cfg(target_os = "openbsd")] - let uptime = get_uptime(None); - #[cfg(unix)] - #[cfg(not(target_os = "openbsd"))] - let uptime = get_uptime(boot_time); - #[cfg(target_os = "windows")] - let uptime = get_uptime(None); - if matches.get_flag(options::SINCE) { + #[cfg(unix)] + #[cfg(not(target_os = "openbsd"))] + let (boot_time, _) = process_utmpx(None); + + #[cfg(target_os = "openbsd")] + let uptime = get_uptime(None)?; + #[cfg(unix)] + #[cfg(not(target_os = "openbsd"))] + let uptime = get_uptime(boot_time)?; + #[cfg(target_os = "windows")] + let uptime = get_uptime(None)?; let initial_date = Local .timestamp_opt(Utc::now().timestamp() - uptime, 0) .unwrap(); @@ -242,39 +237,6 @@ fn default_uptime(matches: &ArgMatches) -> UResult<()> { Ok(()) } -#[cfg(unix)] -fn get_loadavg() -> (f64, f64, f64) { - use uucore::libc::c_double; - - let mut avg: [c_double; 3] = [0.0; 3]; - let loads: i32 = unsafe { getloadavg(avg.as_mut_ptr(), 3) }; - - if loads == -1 { - (-1.0, -1.0, -1.0) - } else { - (avg[0], avg[1], avg[2]) - } -} - -/// Windows does not seem to have anything similar. -#[cfg(windows)] -fn get_loadavg() -> (f64, f64, f64) { - (-1.0, -1.0, -1.0) -} - -#[inline] -fn get_formatted_loadavg() -> UResult { - let loadavg = get_loadavg(); - if loadavg.0 < 0.0 || loadavg.1 < 0.0 || loadavg.2 < 0.0 { - Err(USimpleError::new(1, "could not retrieve uptime")) - } else { - Ok(format!( - "load average: {:.2}, {:.2}, {:.2}", - loadavg.0, loadavg.1, loadavg.2 - )) - } -} - #[inline] fn print_loadavg() { match get_formatted_loadavg() { @@ -283,31 +245,6 @@ fn print_loadavg() { } } -#[cfg(target_os = "openbsd")] -fn process_utmp_from_file(file: &str) -> usize { - let mut nusers = 0; - - let entries = match parse_from_path(file) { - Some(e) => e, - None => return 0, - }; - - for entry in entries { - if let UtmpEntry::UTMP { - line: _, - user, - host: _, - time: _, - } = entry - { - if !user.is_empty() { - nusers += 1; - } - } - } - nusers -} - #[cfg(unix)] #[cfg(not(target_os = "openbsd"))] fn process_utmpx(file: Option<&std::ffi::OsString>) -> (Option, usize) { @@ -334,11 +271,6 @@ fn process_utmpx(file: Option<&std::ffi::OsString>) -> (Option, usize) { (boot_time, nusers) } -#[cfg(windows)] -fn process_utmpx() -> (Option, usize) { - (None, 0) // TODO: change 0 to number of users -} - fn print_nusers(nusers: Option) -> UResult<()> { print!( "{}, ", @@ -362,225 +294,3 @@ fn print_uptime(boot_time: Option) -> UResult<()> { print!("up {}, ", get_formated_uptime(boot_time)?); Ok(()) } - -fn get_formatted_time() -> String { - Local::now().time().format("%H:%M:%S").to_string() -} - -#[cfg(target_os = "openbsd")] -pub fn get_uptime(_boot_time: Option) -> i64 { - use uucore::libc::clock_gettime; - use uucore::libc::CLOCK_BOOTTIME; - - use uucore::libc::c_int; - use uucore::libc::timespec; - - let mut tp: timespec = timespec { - tv_sec: 0, - tv_nsec: 0, - }; - let raw_tp = &mut tp as *mut timespec; - - // OpenBSD prototype: clock_gettime(clk_id: ::clockid_t, tp: *mut ::timespec) -> ::c_int; - let ret: c_int = unsafe { clock_gettime(CLOCK_BOOTTIME, raw_tp) }; - - if ret == 0 { - #[cfg(target_pointer_width = "64")] - let uptime: i64 = tp.tv_sec; - #[cfg(not(target_pointer_width = "64"))] - let uptime: i64 = tp.tv_sec.into(); - - uptime - } else { - -1 - } -} - -#[cfg(unix)] -#[cfg(not(target_os = "openbsd"))] -pub fn get_uptime(boot_time: Option) -> i64 { - use std::fs::File; - use std::io::Read; - - let mut proc_uptime_s = String::new(); - - let proc_uptime = File::open("/proc/uptime") - .ok() - .and_then(|mut f| f.read_to_string(&mut proc_uptime_s).ok()) - .and_then(|_| proc_uptime_s.split_whitespace().next()) - .and_then(|s| s.split('.').next().unwrap_or("0").parse().ok()); - - proc_uptime.unwrap_or_else(|| { - let boot_time = boot_time.or_else(|| { - let (boot_time, _) = process_utmpx(None); - boot_time - }); - match boot_time { - Some(t) => { - let now = Local::now().timestamp(); - #[cfg(target_pointer_width = "64")] - let boottime: i64 = t; - #[cfg(not(target_pointer_width = "64"))] - let boottime: i64 = t.into(); - now - boottime - } - None => -1, - } - }) -} - -#[cfg(windows)] -pub fn get_uptime(_boot_time: Option) -> i64 { - unsafe { GetTickCount() as i64 } -} - -/// Returns the formatted uptime string, e.g. "1 day, 3:45" -#[inline] -pub fn get_formated_uptime(boot_time: Option) -> UResult { - let up_secs = get_uptime(boot_time); - - if up_secs < 0 { - return Err(USimpleError::new(1, "could not retrieve system uptime")); - } - let up_days = up_secs / 86400; - let up_hours = (up_secs - (up_days * 86400)) / 3600; - let up_mins = (up_secs - (up_days * 86400) - (up_hours * 3600)) / 60; - match up_days.cmp(&1) { - std::cmp::Ordering::Equal => Ok(format!("{up_days:1} day, {up_hours:2}:{up_mins:02}")), - std::cmp::Ordering::Greater => Ok(format!("{up_days:1} days {up_hours:2}:{up_mins:02}")), - _ => Ok(format!("{up_hours:2}:{up_mins:02}")), - } -} - -#[inline] -fn format_nusers(nusers: usize) -> String { - match nusers { - 0 => "0 user".to_string(), - 1 => "1 user".to_string(), - _ => format!("{} users", nusers), - } -} - -#[inline] -fn get_formatted_nusers() -> String { - format_nusers(get_nusers()) -} - -#[cfg(target_os = "windows")] -fn get_nusers() -> usize { - use std::ptr; - use windows_sys::Win32::System::RemoteDesktop::*; - - let mut num_user = 0; - - unsafe { - let mut session_info_ptr = ptr::null_mut(); - let mut session_count = 0; - - let result = WTSEnumerateSessionsW( - WTS_CURRENT_SERVER_HANDLE, - 0, - 1, - &mut session_info_ptr, - &mut session_count, - ); - if result == 0 { - return 0; - } - - let sessions = std::slice::from_raw_parts(session_info_ptr, session_count as usize); - - for session in sessions { - let mut buffer: *mut u16 = ptr::null_mut(); - let mut bytes_returned = 0; - - let result = WTSQuerySessionInformationW( - WTS_CURRENT_SERVER_HANDLE, - session.SessionId, - 5, - &mut buffer, - &mut bytes_returned, - ); - if result == 0 || buffer.is_null() { - continue; - } - - let username = if !buffer.is_null() { - let cstr = std::ffi::CStr::from_ptr(buffer as *const i8); - cstr.to_string_lossy().to_string() - } else { - String::new() - }; - if !username.is_empty() { - num_user += 1; - } - - WTSFreeMemory(buffer as _); - } - - WTSFreeMemory(session_info_ptr as _); - } - - num_user -} - -#[cfg(unix)] -#[cfg(not(target_os = "openbsd"))] -// see: https://gitlab.com/procps-ng/procps/-/blob/4740a0efa79cade867cfc7b32955fe0f75bf5173/library/uptime.c#L63-L115 -fn get_nusers() -> usize { - use uucore::utmpx::Utmpx; - - #[cfg(target_os = "linux")] - unsafe { - use libsystemd_sys::daemon::sd_booted; - use libsystemd_sys::login::{sd_get_sessions, sd_session_get_class}; - use std::ffi::{c_char, c_void, CStr}; - use std::ptr; - use uucore::libc::free; - // systemd - if sd_booted() > 0 { - let mut sessions_list: *mut *mut c_char = ptr::null_mut(); - let mut num_user = 0; - let sessions = sd_get_sessions(&mut sessions_list); // rust-systemd does not implement this - - if sessions > 0 { - for i in 0..sessions { - let mut class: *mut c_char = ptr::null_mut(); - - if sd_session_get_class( - *sessions_list.add(i as usize) as *const c_char, - &mut class, - ) < 0 - { - continue; - } - if CStr::from_ptr(class).to_str().unwrap().starts_with("user") { - num_user += 1; - } - free(class as *mut c_void); - } - } - - for i in 0..sessions { - free(*sessions_list.add(i as usize) as *mut c_void); - } - free(sessions_list as *mut c_void); - - return num_user; - } - } - - // utmpx - let mut num_user = 0; - Utmpx::iter_all_records().for_each(|ut| { - if ut.record_type() == 7 && !ut.user().is_empty() { - num_user += 1; - } - }); - num_user -} - -#[cfg(target_os = "openbsd")] -fn get_nusers() -> usize { - process_utmp_from_file("/var/run/utmp") -} diff --git a/src/uucore/Cargo.toml b/src/uucore/Cargo.toml index d24587c0060..e2633ad969a 100644 --- a/src/uucore/Cargo.toml +++ b/src/uucore/Cargo.toml @@ -74,11 +74,16 @@ tempfile = { workspace = true } [target.'cfg(target_os = "windows")'.dependencies] winapi-util = { workspace = true, optional = true } windows-sys = { workspace = true, optional = true, default-features = false, features = [ + "Wdk_System_SystemInformation", "Win32_Storage_FileSystem", "Win32_Foundation", + "Win32_System_RemoteDesktop", "Win32_System_WindowsProgramming", ] } +[target.'cfg(target_os = "openbsd")'.dependencies] +utmp-classic = { workspace = true, optional = true } + [features] default = [] # * non-default features @@ -122,3 +127,4 @@ version-cmp = [] wide = [] custom-tz-fmt = [] tty = [] +uptime = ["libc", "windows-sys", "utmpx", "utmp-classic", "thiserror"] diff --git a/src/uucore/src/lib/features.rs b/src/uucore/src/lib/features.rs index 00079eed886..64adb78d2f6 100644 --- a/src/uucore/src/lib/features.rs +++ b/src/uucore/src/lib/features.rs @@ -34,6 +34,8 @@ pub mod ringbuffer; pub mod sum; #[cfg(feature = "update-control")] pub mod update_control; +#[cfg(feature = "uptime")] +pub mod uptime; #[cfg(feature = "version-cmp")] pub mod version_cmp; diff --git a/src/uucore/src/lib/features/uptime.rs b/src/uucore/src/lib/features/uptime.rs new file mode 100644 index 00000000000..be9a512f9e8 --- /dev/null +++ b/src/uucore/src/lib/features/uptime.rs @@ -0,0 +1,283 @@ +// spell-checker:ignore gettime BOOTTIME clockid boottime formated nusers loadavg getloadavg + +use crate::error::{UError, UResult}; +use chrono::Local; +use libc::time_t; +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum UptimeError { + #[error("could not retrieve system uptime")] + SystemUptime, + #[error("could not retrieve system load average")] + SystemLoadavg, + #[error("boot time larger than current time")] + BootTime, +} + +impl UError for UptimeError { + fn code(&self) -> i32 { + 1 + } +} + +pub fn get_formatted_time() -> String { + Local::now().time().format("%H:%M:%S").to_string() +} + +#[cfg(target_os = "openbsd")] +pub fn get_uptime(_boot_time: Option) -> UResult { + use libc::clock_gettime; + use libc::CLOCK_BOOTTIME; + + use libc::c_int; + use libc::timespec; + + let mut tp: timespec = timespec { + tv_sec: 0, + tv_nsec: 0, + }; + let raw_tp = &mut tp as *mut timespec; + + // OpenBSD prototype: clock_gettime(clk_id: ::clockid_t, tp: *mut ::timespec) -> ::c_int; + let ret: c_int = unsafe { clock_gettime(CLOCK_BOOTTIME, raw_tp) }; + + if ret == 0 { + #[cfg(target_pointer_width = "64")] + let uptime: i64 = tp.tv_sec; + #[cfg(not(target_pointer_width = "64"))] + let uptime: i64 = tp.tv_sec.into(); + + Ok(uptime) + } else { + Err(UptimeError::SystemUptime) + } +} + +#[cfg(unix)] +#[cfg(not(target_os = "openbsd"))] +pub fn get_uptime(boot_time: Option) -> UResult { + use crate::utmpx::Utmpx; + use libc::BOOT_TIME; + use std::fs::File; + use std::io::Read; + + let mut proc_uptime_s = String::new(); + + let proc_uptime = File::open("/proc/uptime") + .ok() + .and_then(|mut f| f.read_to_string(&mut proc_uptime_s).ok()) + .and_then(|_| proc_uptime_s.split_whitespace().next()) + .and_then(|s| s.split('.').next().unwrap_or("0").parse::().ok()); + + if let Some(uptime) = proc_uptime { + return Ok(uptime); + } + + let boot_time = boot_time.or_else(|| { + let records = Utmpx::iter_all_records(); + for line in records { + match line.record_type() { + BOOT_TIME => { + let dt = line.login_time(); + if dt.unix_timestamp() > 0 { + return Some(dt.unix_timestamp() as time_t); + } + } + _ => continue, + } + } + None + }); + + if let Some(t) = boot_time { + let now = Local::now().timestamp(); + #[cfg(target_pointer_width = "64")] + let boottime: i64 = t; + #[cfg(not(target_pointer_width = "64"))] + let boottime: i64 = t.into(); + if now < boottime { + Err(UptimeError::BootTime)?; + } + return Ok(now - boottime); + } + + Err(UptimeError::SystemUptime)? +} + +#[cfg(windows)] +pub fn get_uptime(_boot_time: Option) -> UResult { + use windows_sys::Win32::System::SystemInformation::GetTickCount; + let uptime = unsafe { GetTickCount() }; + if uptime < 0 { + Err(UptimeError::SystemUptime)?; + } + Ok(uptime as i64) +} + +/// Returns the formatted uptime string, e.g. "1 day, 3:45" +#[inline] +pub fn get_formated_uptime(boot_time: Option) -> UResult { + let up_secs = get_uptime(boot_time)?; + + if up_secs < 0 { + Err(UptimeError::SystemUptime)?; + } + let up_days = up_secs / 86400; + let up_hours = (up_secs - (up_days * 86400)) / 3600; + let up_mins = (up_secs - (up_days * 86400) - (up_hours * 3600)) / 60; + match up_days.cmp(&1) { + std::cmp::Ordering::Equal => Ok(format!("{up_days:1} day, {up_hours:2}:{up_mins:02}")), + std::cmp::Ordering::Greater => Ok(format!("{up_days:1} days {up_hours:2}:{up_mins:02}")), + _ => Ok(format!("{up_hours:2}:{up_mins:02}")), + } +} + +#[cfg(unix)] +#[cfg(not(target_os = "openbsd"))] +// see: https://gitlab.com/procps-ng/procps/-/blob/4740a0efa79cade867cfc7b32955fe0f75bf5173/library/uptime.c#L63-L115 +pub fn get_nusers() -> usize { + use crate::utmpx::Utmpx; + use libc::USER_PROCESS; + + let mut num_user = 0; + Utmpx::iter_all_records().for_each(|ut| { + if ut.record_type() == USER_PROCESS { + num_user += 1; + } + }); + num_user +} + +#[cfg(target_os = "openbsd")] +pub fn get_nusers(file: &str) -> usize { + use utmp_classic::{parse_from_path, UtmpEntry}; + + let mut nusers = 0; + + let entries = match parse_from_path(file) { + Some(e) => e, + None => return 0, + }; + + for entry in entries { + if let UtmpEntry::UTMP { + line: _, + user, + host: _, + time: _, + } = entry + { + if !user.is_empty() { + nusers += 1; + } + } + } + nusers +} + +#[cfg(target_os = "windows")] +pub fn get_nusers() -> usize { + use std::ptr; + use windows_sys::Win32::System::RemoteDesktop::*; + + let mut num_user = 0; + + unsafe { + let mut session_info_ptr = ptr::null_mut(); + let mut session_count = 0; + + let result = WTSEnumerateSessionsW( + WTS_CURRENT_SERVER_HANDLE, + 0, + 1, + &mut session_info_ptr, + &mut session_count, + ); + if result == 0 { + return 0; + } + + let sessions = std::slice::from_raw_parts(session_info_ptr, session_count as usize); + + for session in sessions { + let mut buffer: *mut u16 = ptr::null_mut(); + let mut bytes_returned = 0; + + let result = WTSQuerySessionInformationW( + WTS_CURRENT_SERVER_HANDLE, + session.SessionId, + 5, + &mut buffer, + &mut bytes_returned, + ); + if result == 0 || buffer.is_null() { + continue; + } + + let username = if !buffer.is_null() { + let cstr = std::ffi::CStr::from_ptr(buffer as *const i8); + cstr.to_string_lossy().to_string() + } else { + String::new() + }; + if !username.is_empty() { + num_user += 1; + } + + WTSFreeMemory(buffer as _); + } + + WTSFreeMemory(session_info_ptr as _); + } + + num_user +} + +#[inline] +pub fn format_nusers(nusers: usize) -> String { + match nusers { + 0 => "0 user".to_string(), + 1 => "1 user".to_string(), + _ => format!("{} users", nusers), + } +} + +#[inline] +pub fn get_formatted_nusers() -> String { + #[cfg(not(target_os = "openbsd"))] + return format_nusers(get_nusers()); + + #[cfg(target_os = "openbsd")] + format_nusers(get_nusers("/var/run/utmp")) +} + +#[cfg(unix)] +pub fn get_loadavg() -> UResult<(f64, f64, f64)> { + use crate::libc::c_double; + use libc::getloadavg; + + let mut avg: [c_double; 3] = [0.0; 3]; + let loads: i32 = unsafe { getloadavg(avg.as_mut_ptr(), 3) }; + + if loads == -1 { + Err(UptimeError::SystemLoadavg)? + } else { + Ok((avg[0], avg[1], avg[2])) + } +} + +/// Windows does not seem to have anything similar. +#[cfg(windows)] +pub fn get_loadavg() -> UResult<(f64, f64, f64)> { + Err(UptimeError::SystemLoadavg)? +} + +#[inline] +pub fn get_formatted_loadavg() -> UResult { + let loadavg = get_loadavg()?; + Ok(format!( + "load average: {:.2}, {:.2}, {:.2}", + loadavg.0, loadavg.1, loadavg.2 + )) +} diff --git a/src/uucore/src/lib/lib.rs b/src/uucore/src/lib/lib.rs index da29baf0c70..c2ff84b08e0 100644 --- a/src/uucore/src/lib/lib.rs +++ b/src/uucore/src/lib/lib.rs @@ -66,6 +66,8 @@ pub use crate::features::ringbuffer; pub use crate::features::sum; #[cfg(feature = "update-control")] pub use crate::features::update_control; +#[cfg(feature = "uptime")] +pub use crate::features::uptime; #[cfg(feature = "version-cmp")] pub use crate::features::version_cmp; From 999b76787d4ded29ab7370e346c4ce15b48d4463 Mon Sep 17 00:00:00 2001 From: Bluemangoo Date: Tue, 18 Feb 2025 12:54:29 +0000 Subject: [PATCH 3/3] uptime: add rustdoc --- src/uucore/src/lib/features/uptime.rs | 94 ++++++++++++++++++++++++++- 1 file changed, 91 insertions(+), 3 deletions(-) diff --git a/src/uucore/src/lib/features/uptime.rs b/src/uucore/src/lib/features/uptime.rs index be9a512f9e8..e82a767d882 100644 --- a/src/uucore/src/lib/features/uptime.rs +++ b/src/uucore/src/lib/features/uptime.rs @@ -1,5 +1,17 @@ +// This file is part of the uutils coreutils package. +// +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. + // spell-checker:ignore gettime BOOTTIME clockid boottime formated nusers loadavg getloadavg +//! Provides functions to get system uptime, number of users and load average. + +// The code was originally written in uu_uptime +// (https://github.com/uutils/coreutils/blob/main/src/uu/uptime/src/uptime.rs) +// but was eventually moved here. +// See https://github.com/uutils/coreutils/pull/7289 for discussion. + use crate::error::{UError, UResult}; use chrono::Local; use libc::time_t; @@ -11,6 +23,8 @@ pub enum UptimeError { SystemUptime, #[error("could not retrieve system load average")] SystemLoadavg, + #[error("Windows does not have an equivalent to the load average on Unix-like systems")] + WindowsLoadavg, #[error("boot time larger than current time")] BootTime, } @@ -21,10 +35,20 @@ impl UError for UptimeError { } } +/// Returns the formatted time string, e.g. "12:34:56" pub fn get_formatted_time() -> String { Local::now().time().format("%H:%M:%S").to_string() } +/// Get the system uptime +/// +/// # Arguments +/// +/// boot_time: Option - Manually specify the boot time, or None to try to get it from the system. +/// +/// # Returns +/// +/// Returns a UResult with the uptime in seconds if successful, otherwise an UptimeError. #[cfg(target_os = "openbsd")] pub fn get_uptime(_boot_time: Option) -> UResult { use libc::clock_gettime; @@ -54,6 +78,15 @@ pub fn get_uptime(_boot_time: Option) -> UResult { } } +/// Get the system uptime +/// +/// # Arguments +/// +/// boot_time: Option - Manually specify the boot time, or None to try to get it from the system. +/// +/// # Returns +/// +/// Returns a UResult with the uptime in seconds if successful, otherwise an UptimeError. #[cfg(unix)] #[cfg(not(target_os = "openbsd"))] pub fn get_uptime(boot_time: Option) -> UResult { @@ -105,6 +138,11 @@ pub fn get_uptime(boot_time: Option) -> UResult { Err(UptimeError::SystemUptime)? } +/// Get the system uptime +/// +/// # Returns +/// +/// Returns a UResult with the uptime in seconds if successful, otherwise an UptimeError. #[cfg(windows)] pub fn get_uptime(_boot_time: Option) -> UResult { use windows_sys::Win32::System::SystemInformation::GetTickCount; @@ -115,7 +153,15 @@ pub fn get_uptime(_boot_time: Option) -> UResult { Ok(uptime as i64) } -/// Returns the formatted uptime string, e.g. "1 day, 3:45" +/// Get the system uptime in a human-readable format +/// +/// # Arguments +/// +/// boot_time: Option - Manually specify the boot time, or None to try to get it from the system. +/// +/// # Returns +/// +/// Returns a UResult with the uptime in a human-readable format(e.g. "1 day, 3:45") if successful, otherwise an UptimeError. #[inline] pub fn get_formated_uptime(boot_time: Option) -> UResult { let up_secs = get_uptime(boot_time)?; @@ -133,6 +179,11 @@ pub fn get_formated_uptime(boot_time: Option) -> UResult { } } +/// Get the number of users currently logged in +/// +/// # Returns +/// +/// Returns the number of users currently logged in if successful, otherwise 0. #[cfg(unix)] #[cfg(not(target_os = "openbsd"))] // see: https://gitlab.com/procps-ng/procps/-/blob/4740a0efa79cade867cfc7b32955fe0f75bf5173/library/uptime.c#L63-L115 @@ -149,6 +200,11 @@ pub fn get_nusers() -> usize { num_user } +/// Get the number of users currently logged in +/// +/// # Returns +/// +/// Returns the number of users currently logged in if successful, otherwise 0 #[cfg(target_os = "openbsd")] pub fn get_nusers(file: &str) -> usize { use utmp_classic::{parse_from_path, UtmpEntry}; @@ -176,6 +232,11 @@ pub fn get_nusers(file: &str) -> usize { nusers } +/// Get the number of users currently logged in +/// +/// # Returns +/// +/// Returns the number of users currently logged in if successful, otherwise 0 #[cfg(target_os = "windows")] pub fn get_nusers() -> usize { use std::ptr; @@ -234,6 +295,11 @@ pub fn get_nusers() -> usize { num_user } +/// Format the number of users to a human-readable string +/// +/// # Returns +/// +/// e.g. "0 user", "1 user", "2 users" #[inline] pub fn format_nusers(nusers: usize) -> String { match nusers { @@ -243,6 +309,11 @@ pub fn format_nusers(nusers: usize) -> String { } } +/// Get the number of users currently logged in in a human-readable format +/// +/// # Returns +/// +/// e.g. "0 user", "1 user", "2 users" #[inline] pub fn get_formatted_nusers() -> String { #[cfg(not(target_os = "openbsd"))] @@ -252,6 +323,12 @@ pub fn get_formatted_nusers() -> String { format_nusers(get_nusers("/var/run/utmp")) } +/// Get the system load average +/// +/// # Returns +/// +/// Returns a UResult with the load average if successful, otherwise an UptimeError. +/// The load average is a tuple of three floating point numbers representing the 1-minute, 5-minute, and 15-minute load averages. #[cfg(unix)] pub fn get_loadavg() -> UResult<(f64, f64, f64)> { use crate::libc::c_double; @@ -267,12 +344,23 @@ pub fn get_loadavg() -> UResult<(f64, f64, f64)> { } } -/// Windows does not seem to have anything similar. +/// Get the system load average +/// Windows does not have an equivalent to the load average on Unix-like systems. +/// +/// # Returns +/// +/// Returns a UResult with an UptimeError. #[cfg(windows)] pub fn get_loadavg() -> UResult<(f64, f64, f64)> { - Err(UptimeError::SystemLoadavg)? + Err(UptimeError::WindowsLoadavg)? } +/// Get the system load average in a human-readable format +/// +/// # Returns +/// +/// Returns a UResult with the load average in a human-readable format if successful, otherwise an UptimeError. +/// e.g. "load average: 0.00, 0.00, 0.00" #[inline] pub fn get_formatted_loadavg() -> UResult { let loadavg = get_loadavg()?;