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",
"uniq",
"unlink",
"uptime",
"vdir",
"wc",
"yes",
@ -209,7 +210,7 @@ feat_require_unix = [
]
# "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?>
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 = ["hostid"]
# "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
use chrono::{Local, TimeZone, Utc};
#[cfg(unix)]
use std::ffi::OsString;
use std::io;
use thiserror::Error;
@ -15,13 +14,13 @@ use uucore::libc::time_t;
use uucore::translate;
use uucore::uptime::*;
use clap::{Arg, ArgAction, Command, ValueHint, builder::ValueParser};
use clap::{Arg, ArgAction, Command};
use uucore::LocalizedCommand;
use uucore::format_usage;
#[cfg(unix)]
#[cfg(not(target_os = "openbsd"))]
#[cfg(not(any(target_os = "openbsd", target_os = "redox", target_os = "android")))]
use uucore::utmpx::*;
pub mod options {
@ -83,15 +82,26 @@ pub fn uu_app() -> Command {
.help(translate!("uptime-help-since"))
.action(ArgAction::SetTrue),
);
#[cfg(unix)]
cmd.arg(
Arg::new(options::PATH)
.help(translate!("uptime-help-path"))
.action(ArgAction::Set)
.num_args(0..=1)
.value_parser(ValueParser::os_string())
.value_hint(ValueHint::AnyPath),
)
let cmd = {
use clap::{ValueHint, builder::ValueParser};
cmd.arg(
Arg::new(options::PATH)
.help(translate!("uptime-help-path"))
.action(ArgAction::Set)
.num_args(0..=1)
.value_parser(ValueParser::os_string())
.value_hint(ValueHint::AnyPath),
)
};
cmd
}
#[cfg(windows)]
fn uptime_with_file(_: &OsString) -> UResult<()> {
unreachable!("The function should never be called on Windows")
}
#[cfg(unix)]
@ -153,7 +163,7 @@ fn uptime_with_file(file_path: &OsString) -> UResult<()> {
print_time();
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));
if let Some(time) = boot_time {
@ -167,7 +177,7 @@ fn uptime_with_file(file_path: &OsString) -> UResult<()> {
user_count = count;
}
#[cfg(target_os = "openbsd")]
#[cfg(any(target_os = "openbsd", target_os = "redox", target_os = "android"))]
{
let upsecs = get_uptime(None);
if upsecs >= 0 {
@ -189,12 +199,17 @@ fn uptime_with_file(file_path: &OsString) -> UResult<()> {
fn uptime_since() -> UResult<()> {
#[cfg(unix)]
#[cfg(not(target_os = "openbsd"))]
#[cfg(not(any(target_os = "openbsd", target_os = "redox", target_os = "android")))]
let uptime = {
let (boot_time, _) = process_utmpx(None);
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 since_date = Local
@ -224,7 +239,7 @@ fn print_loadavg() {
}
#[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) {
let mut nusers = 0;
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
///
/// # 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.
#[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> {
use crate::utmpx::Utmpx;
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.
#[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
pub fn get_nusers() -> usize {
use crate::utmpx::Utmpx;
@ -236,6 +242,12 @@ pub fn get_nusers(file: &str) -> usize {
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
///
/// # Returns
@ -334,6 +346,7 @@ pub fn get_formatted_nusers() -> String {
/// 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)]
#[cfg(not(any(target_os = "android", target_os = "redox")))]
pub fn get_loadavg() -> UResult<(f64, f64, f64)> {
use crate::libc::c_double;
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
/// 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
#![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::util::TestScenario;
use uutests::{new_ucmd, util_name};
use uutests::new_ucmd;
use regex::Regex;
@ -20,17 +19,21 @@ fn test_invalid_arg() {
#[test]
fn test_uptime() {
new_ucmd!()
.succeeds()
.stdout_contains("load average:")
.stdout_contains(" up ");
let result = new_ucmd!().succeeds();
result.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
}
/// Checks for files without utmpx records for which boot time cannot be calculated
#[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
// file that is accessed using getutxent()
fn test_uptime_for_file_without_utmpx_records() {
@ -48,6 +51,7 @@ fn test_uptime_for_file_without_utmpx_records() {
#[test]
#[cfg(all(unix, feature = "cp"))]
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
// Sometimes writing to the pipe is broken
let ts = TestScenario::new(util_name!());
@ -74,7 +78,7 @@ fn test_uptime_with_fifo() {
}
#[test]
#[cfg(not(target_os = "freebsd"))]
#[cfg(not(any(windows, target_os = "freebsd")))]
fn test_uptime_with_non_existent_file() {
// Disabled for freebsd, since it doesn't use the utmpxname() sys call to change the default utmpx
// file that is accessed using getutxent()
@ -88,7 +92,7 @@ fn test_uptime_with_non_existent_file() {
// TODO create a similar test for macos
// This will pass
#[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_attr(
all(target_arch = "aarch64", target_os = "linux"),
@ -235,6 +239,7 @@ fn test_uptime_with_file_containing_valid_boot_time_utmpx_record() {
}
#[test]
#[cfg(not(windows))]
fn test_uptime_with_extra_argument() {
new_ucmd!()
.arg("a")
@ -242,8 +247,10 @@ fn test_uptime_with_extra_argument() {
.fails()
.stderr_contains("unexpected value 'b'");
}
/// Checks whether uptime displays the correct stderr msg when its called with a directory
#[test]
#[cfg(not(windows))]
fn test_uptime_with_dir() {
let (at, mut ucmd) = at_and_ucmd!();