This commit is contained in:
Daniel Hofstetter 2025-08-22 15:23:41 -07:00 committed by GitHub
commit 2012ec0bb7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 71 additions and 29 deletions

View file

@ -131,6 +131,7 @@ feat_common_core = [
"unexpand", "unexpand",
"uniq", "uniq",
"unlink", "unlink",
"uptime",
"vdir", "vdir",
"wc", "wc",
"yes", "yes",
@ -209,7 +210,7 @@ feat_require_unix = [
] ]
# "feat_require_unix_utmpx" == set of utilities requiring unix utmp/utmpx support # "feat_require_unix_utmpx" == set of utilities requiring unix utmp/utmpx support
# * ref: <https://wiki.musl-libc.org/faq.html#Q:-Why-is-the-utmp/wtmp-functionality-only-implemented-as-stubs?> # * ref: <https://wiki.musl-libc.org/faq.html#Q:-Why-is-the-utmp/wtmp-functionality-only-implemented-as-stubs?>
feat_require_unix_utmpx = ["pinky", "uptime", "users", "who"] feat_require_unix_utmpx = ["pinky", "users", "who"]
# "feat_require_unix_hostid" == set of utilities requiring gethostid in libc (only some unixes provide) # "feat_require_unix_hostid" == set of utilities requiring gethostid in libc (only some unixes provide)
feat_require_unix_hostid = ["hostid"] feat_require_unix_hostid = ["hostid"]
# "feat_require_selinux" == set of utilities depending on SELinux. # "feat_require_selinux" == set of utilities depending on SELinux.

View file

@ -6,7 +6,6 @@
// spell-checker:ignore getloadavg behaviour loadavg uptime upsecs updays upmins uphours boottime nusers utmpxname gettime clockid couldnt // spell-checker:ignore getloadavg behaviour loadavg uptime upsecs updays upmins uphours boottime nusers utmpxname gettime clockid couldnt
use chrono::{Local, TimeZone, Utc}; use chrono::{Local, TimeZone, Utc};
#[cfg(unix)]
use std::ffi::OsString; use std::ffi::OsString;
use std::io; use std::io;
use thiserror::Error; use thiserror::Error;
@ -15,13 +14,13 @@ use uucore::libc::time_t;
use uucore::translate; use uucore::translate;
use uucore::uptime::*; use uucore::uptime::*;
use clap::{Arg, ArgAction, Command, ValueHint, builder::ValueParser}; use clap::{Arg, ArgAction, Command};
use uucore::LocalizedCommand; use uucore::LocalizedCommand;
use uucore::format_usage; use uucore::format_usage;
#[cfg(unix)] #[cfg(unix)]
#[cfg(not(target_os = "openbsd"))] #[cfg(not(any(target_os = "openbsd", target_os = "redox", target_os = "android")))]
use uucore::utmpx::*; use uucore::utmpx::*;
pub mod options { pub mod options {
@ -83,7 +82,10 @@ pub fn uu_app() -> Command {
.help(translate!("uptime-help-since")) .help(translate!("uptime-help-since"))
.action(ArgAction::SetTrue), .action(ArgAction::SetTrue),
); );
#[cfg(unix)] #[cfg(unix)]
let cmd = {
use clap::{ValueHint, builder::ValueParser};
cmd.arg( cmd.arg(
Arg::new(options::PATH) Arg::new(options::PATH)
.help(translate!("uptime-help-path")) .help(translate!("uptime-help-path"))
@ -92,6 +94,14 @@ pub fn uu_app() -> Command {
.value_parser(ValueParser::os_string()) .value_parser(ValueParser::os_string())
.value_hint(ValueHint::AnyPath), .value_hint(ValueHint::AnyPath),
) )
};
cmd
}
#[cfg(windows)]
fn uptime_with_file(_: &OsString) -> UResult<()> {
unreachable!("The function should never be called on Windows")
} }
#[cfg(unix)] #[cfg(unix)]
@ -153,7 +163,7 @@ fn uptime_with_file(file_path: &OsString) -> UResult<()> {
print_time(); print_time();
let user_count; let user_count;
#[cfg(not(target_os = "openbsd"))] #[cfg(not(any(target_os = "openbsd", target_os = "redox", target_os = "android")))]
{ {
let (boot_time, count) = process_utmpx(Some(file_path)); let (boot_time, count) = process_utmpx(Some(file_path));
if let Some(time) = boot_time { if let Some(time) = boot_time {
@ -167,7 +177,7 @@ fn uptime_with_file(file_path: &OsString) -> UResult<()> {
user_count = count; user_count = count;
} }
#[cfg(target_os = "openbsd")] #[cfg(any(target_os = "openbsd", target_os = "redox", target_os = "android"))]
{ {
let upsecs = get_uptime(None); let upsecs = get_uptime(None);
if upsecs >= 0 { if upsecs >= 0 {
@ -189,12 +199,17 @@ fn uptime_with_file(file_path: &OsString) -> UResult<()> {
fn uptime_since() -> UResult<()> { fn uptime_since() -> UResult<()> {
#[cfg(unix)] #[cfg(unix)]
#[cfg(not(target_os = "openbsd"))] #[cfg(not(any(target_os = "openbsd", target_os = "redox", target_os = "android")))]
let uptime = { let uptime = {
let (boot_time, _) = process_utmpx(None); let (boot_time, _) = process_utmpx(None);
get_uptime(boot_time)? get_uptime(boot_time)?
}; };
#[cfg(any(windows, target_os = "openbsd"))] #[cfg(any(
windows,
target_os = "openbsd",
target_os = "redox",
target_os = "android"
))]
let uptime = get_uptime(None)?; let uptime = get_uptime(None)?;
let since_date = Local let since_date = Local
@ -224,7 +239,7 @@ fn print_loadavg() {
} }
#[cfg(unix)] #[cfg(unix)]
#[cfg(not(target_os = "openbsd"))] #[cfg(not(any(target_os = "openbsd", target_os = "redox", target_os = "android")))]
fn process_utmpx(file: Option<&OsString>) -> (Option<time_t>, usize) { fn process_utmpx(file: Option<&OsString>) -> (Option<time_t>, usize) {
let mut nusers = 0; let mut nusers = 0;
let mut boot_time = None; let mut boot_time = None;

View file

@ -79,6 +79,12 @@ pub fn get_uptime(_boot_time: Option<time_t>) -> UResult<i64> {
} }
} }
// TODO implement functionality
#[cfg(any(target_os = "android", target_os = "redox"))]
pub fn get_uptime(_boot_time: Option<time_t>) -> UResult<i64> {
Err(UptimeError::SystemUptime)?
}
/// Get the system uptime /// Get the system uptime
/// ///
/// # Arguments /// # Arguments
@ -89,7 +95,7 @@ pub fn get_uptime(_boot_time: Option<time_t>) -> UResult<i64> {
/// ///
/// Returns a UResult with the uptime in seconds if successful, otherwise an UptimeError. /// Returns a UResult with the uptime in seconds if successful, otherwise an UptimeError.
#[cfg(unix)] #[cfg(unix)]
#[cfg(not(target_os = "openbsd"))] #[cfg(not(any(target_os = "openbsd", target_os = "android", target_os = "redox")))]
pub fn get_uptime(boot_time: Option<time_t>) -> UResult<i64> { pub fn get_uptime(boot_time: Option<time_t>) -> UResult<i64> {
use crate::utmpx::Utmpx; use crate::utmpx::Utmpx;
use libc::BOOT_TIME; use libc::BOOT_TIME;
@ -189,7 +195,7 @@ pub fn get_formatted_uptime(boot_time: Option<time_t>) -> UResult<String> {
/// ///
/// Returns the number of users currently logged in if successful, otherwise 0. /// Returns the number of users currently logged in if successful, otherwise 0.
#[cfg(unix)] #[cfg(unix)]
#[cfg(not(target_os = "openbsd"))] #[cfg(not(any(target_os = "openbsd", target_os = "android", target_os = "redox")))]
// see: https://gitlab.com/procps-ng/procps/-/blob/4740a0efa79cade867cfc7b32955fe0f75bf5173/library/uptime.c#L63-L115 // see: https://gitlab.com/procps-ng/procps/-/blob/4740a0efa79cade867cfc7b32955fe0f75bf5173/library/uptime.c#L63-L115
pub fn get_nusers() -> usize { pub fn get_nusers() -> usize {
use crate::utmpx::Utmpx; use crate::utmpx::Utmpx;
@ -236,6 +242,12 @@ pub fn get_nusers(file: &str) -> usize {
nusers nusers
} }
// TODO implement functionality
#[cfg(any(target_os = "android", target_os = "redox"))]
pub fn get_nusers() -> usize {
0
}
/// Get the number of users currently logged in /// Get the number of users currently logged in
/// ///
/// # Returns /// # Returns
@ -334,6 +346,7 @@ pub fn get_formatted_nusers() -> String {
/// Returns a UResult with the load average if successful, otherwise an UptimeError. /// 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. /// The load average is a tuple of three floating point numbers representing the 1-minute, 5-minute, and 15-minute load averages.
#[cfg(unix)] #[cfg(unix)]
#[cfg(not(any(target_os = "android", target_os = "redox")))]
pub fn get_loadavg() -> UResult<(f64, f64, f64)> { pub fn get_loadavg() -> UResult<(f64, f64, f64)> {
use crate::libc::c_double; use crate::libc::c_double;
use libc::getloadavg; use libc::getloadavg;
@ -349,6 +362,12 @@ pub fn get_loadavg() -> UResult<(f64, f64, f64)> {
} }
} }
// TODO implement functionality
#[cfg(any(target_os = "android", target_os = "redox"))]
pub fn get_loadavg() -> UResult<(f64, f64, f64)> {
Err(UptimeError::SystemLoadavg)?
}
/// Get the system load average /// Get the system load average
/// Windows does not have an equivalent to the load average on Unix-like systems. /// Windows does not have an equivalent to the load average on Unix-like systems.
/// ///

View file

@ -6,10 +6,9 @@
// spell-checker:ignore bincode serde utmp runlevel testusr testx // spell-checker:ignore bincode serde utmp runlevel testusr testx
#![allow(clippy::cast_possible_wrap, clippy::unreadable_literal)] #![allow(clippy::cast_possible_wrap, clippy::unreadable_literal)]
#[cfg(not(target_os = "openbsd"))] #[cfg(not(any(windows, target_os = "openbsd", target_os = "freebsd")))]
use uutests::at_and_ucmd; use uutests::at_and_ucmd;
use uutests::util::TestScenario; use uutests::new_ucmd;
use uutests::{new_ucmd, util_name};
use regex::Regex; use regex::Regex;
@ -20,17 +19,21 @@ fn test_invalid_arg() {
#[test] #[test]
fn test_uptime() { fn test_uptime() {
new_ucmd!() let result = new_ucmd!().succeeds();
.succeeds()
.stdout_contains("load average:") result.stdout_contains(" up ");
.stdout_contains(" up ");
#[cfg(not(windows))]
result.stdout_contains("load average:");
#[cfg(windows)]
result.stdout_does_not_contain("load average:");
// Don't check for users as it doesn't show in some CI // Don't check for users as it doesn't show in some CI
} }
/// Checks for files without utmpx records for which boot time cannot be calculated /// Checks for files without utmpx records for which boot time cannot be calculated
#[test] #[test]
#[cfg(not(any(target_os = "openbsd", target_os = "freebsd")))] #[cfg(not(any(windows, target_os = "openbsd", target_os = "freebsd")))]
// Disabled for freebsd, since it doesn't use the utmpxname() sys call to change the default utmpx // Disabled for freebsd, since it doesn't use the utmpxname() sys call to change the default utmpx
// file that is accessed using getutxent() // file that is accessed using getutxent()
fn test_uptime_for_file_without_utmpx_records() { fn test_uptime_for_file_without_utmpx_records() {
@ -48,6 +51,7 @@ fn test_uptime_for_file_without_utmpx_records() {
#[test] #[test]
#[cfg(all(unix, feature = "cp"))] #[cfg(all(unix, feature = "cp"))]
fn test_uptime_with_fifo() { fn test_uptime_with_fifo() {
use uutests::{util::TestScenario, util_name};
// This test can go on forever in the CI in some cases, might need aborting // This test can go on forever in the CI in some cases, might need aborting
// Sometimes writing to the pipe is broken // Sometimes writing to the pipe is broken
let ts = TestScenario::new(util_name!()); let ts = TestScenario::new(util_name!());
@ -74,7 +78,7 @@ fn test_uptime_with_fifo() {
} }
#[test] #[test]
#[cfg(not(target_os = "freebsd"))] #[cfg(not(any(windows, target_os = "freebsd")))]
fn test_uptime_with_non_existent_file() { fn test_uptime_with_non_existent_file() {
// Disabled for freebsd, since it doesn't use the utmpxname() sys call to change the default utmpx // Disabled for freebsd, since it doesn't use the utmpxname() sys call to change the default utmpx
// file that is accessed using getutxent() // file that is accessed using getutxent()
@ -88,7 +92,7 @@ fn test_uptime_with_non_existent_file() {
// TODO create a similar test for macos // TODO create a similar test for macos
// This will pass // This will pass
#[test] #[test]
#[cfg(not(any(target_os = "openbsd", target_os = "macos")))] #[cfg(not(any(windows, target_os = "openbsd", target_os = "macos")))]
#[cfg(not(target_env = "musl"))] #[cfg(not(target_env = "musl"))]
#[cfg_attr( #[cfg_attr(
all(target_arch = "aarch64", target_os = "linux"), all(target_arch = "aarch64", target_os = "linux"),
@ -235,6 +239,7 @@ fn test_uptime_with_file_containing_valid_boot_time_utmpx_record() {
} }
#[test] #[test]
#[cfg(not(windows))]
fn test_uptime_with_extra_argument() { fn test_uptime_with_extra_argument() {
new_ucmd!() new_ucmd!()
.arg("a") .arg("a")
@ -242,8 +247,10 @@ fn test_uptime_with_extra_argument() {
.fails() .fails()
.stderr_contains("unexpected value 'b'"); .stderr_contains("unexpected value 'b'");
} }
/// Checks whether uptime displays the correct stderr msg when its called with a directory /// Checks whether uptime displays the correct stderr msg when its called with a directory
#[test] #[test]
#[cfg(not(windows))]
fn test_uptime_with_dir() { fn test_uptime_with_dir() {
let (at, mut ucmd) = at_and_ucmd!(); let (at, mut ucmd) = at_and_ucmd!();