diff --git a/Cargo.lock b/Cargo.lock index 7dc5056bc..1600b76b3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1308,7 +1308,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]] @@ -3470,6 +3470,7 @@ dependencies = [ "thiserror 2.0.11", "utmp-classic", "uucore", + "windows-sys 0.59.0", ] [[package]] @@ -3566,6 +3567,7 @@ dependencies = [ "tempfile", "thiserror 2.0.11", "time", + "utmp-classic", "uucore_procs", "walkdir", "wild", diff --git a/src/uu/uptime/Cargo.toml b/src/uu/uptime/Cargo.toml index 38126a3e9..3ae64b081 100644 --- a/src/uu/uptime/Cargo.toml +++ b/src/uu/uptime/Cargo.toml @@ -19,12 +19,18 @@ path = "src/uptime.rs" [dependencies] chrono = { workspace = true } clap = { workspace = true } -uucore = { workspace = true, features = ["libc", "utmpx"] } thiserror = { 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", +] } + [[bin]] name = "uptime" path = "src/main.rs" diff --git a/src/uu/uptime/src/uptime.rs b/src/uu/uptime/src/uptime.rs index feaf2d8a4..0c387bf2d 100644 --- a/src/uu/uptime/src/uptime.rs +++ b/src/uu/uptime/src/uptime.rs @@ -3,30 +3,23 @@ // 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; -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::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::*; @@ -37,12 +30,9 @@ pub mod options { pub static PATH: &str = "path"; } -#[cfg(unix)] -use uucore::libc::getloadavg; - #[cfg(windows)] extern "C" { - fn GetTickCount() -> uucore::libc::uint32_t; + fn GetTickCount() -> u32; } #[derive(Debug, Error)] @@ -68,27 +58,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); + #[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) } - 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) } pub fn uu_app() -> Command { @@ -114,7 +115,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 +155,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 +165,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 +175,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); @@ -184,20 +189,20 @@ fn uptime_with_file(file_path: &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(); + 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,17 +210,18 @@ 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(not(target_os = "openbsd"))] - let (boot_time, user_count) = process_utmpx(); - - #[cfg(target_os = "openbsd")] - let uptime = get_uptime(); - #[cfg(not(target_os = "openbsd"))] - let uptime = get_uptime(boot_time); - 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(); @@ -223,209 +229,68 @@ 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)] +#[inline] fn print_loadavg() { - 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!(); - } else { - print!("load average: "); - for n in 0..loads { - print!( - "{:.2}{}", - avg[n as usize], - if n == loads - 1 { "\n" } else { ", " } - ); - } + match get_formatted_loadavg() { + Err(_) => {} + Ok(s) => println!("{}", s), } } -#[cfg(windows)] -fn print_loadavg() { - // XXX: currently this is a noop as Windows does not seem to have anything comparable to - // getloadavg() -} - -#[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(); - 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() -> (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; - - for line in Utmpx::iter_all_records_from(file) { - 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(windows)] -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, "), + let records = match file { + Some(f) => Utmpx::iter_all_records_from(f), + None => Utmpx::iter_all_records(), }; + + for line in 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) +} + +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!(" {} ", local_time.format("%H:%M:%S")); + print!(" {} ", get_formatted_time()); } -#[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 -} - -#[cfg(unix)] -#[cfg(target_os = "openbsd")] -fn get_uptime() -> 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"))] -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(|| 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)] -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}, "); - } - _ => print!("up {uphours:2}:{upmins:02}, "), - }; +fn print_uptime(boot_time: Option) -> UResult<()> { + print!("up {}, ", get_formated_uptime(boot_time)?); + Ok(()) } diff --git a/src/uucore/Cargo.toml b/src/uucore/Cargo.toml index d24587c00..e2633ad96 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 00079eed8..64adb78d2 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 000000000..e82a767d8 --- /dev/null +++ b/src/uucore/src/lib/features/uptime.rs @@ -0,0 +1,371 @@ +// 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; +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("Windows does not have an equivalent to the load average on Unix-like systems")] + WindowsLoadavg, + #[error("boot time larger than current time")] + BootTime, +} + +impl UError for UptimeError { + fn code(&self) -> i32 { + 1 + } +} + +/// 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; + 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) + } +} + +/// 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 { + 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)? +} + +/// 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; + let uptime = unsafe { GetTickCount() }; + if uptime < 0 { + Err(UptimeError::SystemUptime)?; + } + Ok(uptime as i64) +} + +/// 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)?; + + 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}")), + } +} + +/// 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 +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 +} + +/// 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}; + + 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 +} + +/// 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; + 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 +} + +/// 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 { + 0 => "0 user".to_string(), + 1 => "1 user".to_string(), + _ => format!("{} users", nusers), + } +} + +/// 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"))] + return format_nusers(get_nusers()); + + #[cfg(target_os = "openbsd")] + 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; + 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])) + } +} + +/// 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::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()?; + 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 da29baf0c..c2ff84b08 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;