From 42a5e5d87db9a436aac3cc8e2eac452e922938fa Mon Sep 17 00:00:00 2001 From: Irene Brown <7233887+irbeam256@users.noreply.github.com> Date: Sat, 10 May 2025 18:14:56 -0700 Subject: [PATCH 1/4] uptime: Add `-p, --pretty` argument --- src/uu/uptime/src/uptime.rs | 23 +++++++- src/uucore/src/lib/features/uptime.rs | 76 ++++++++++++++++++++++++--- tests/by-util/test_uptime.rs | 9 ++++ 3 files changed, 99 insertions(+), 9 deletions(-) diff --git a/src/uu/uptime/src/uptime.rs b/src/uu/uptime/src/uptime.rs index e001a64a8ef..86342c2b735 100644 --- a/src/uu/uptime/src/uptime.rs +++ b/src/uu/uptime/src/uptime.rs @@ -38,6 +38,7 @@ const USAGE: &str = help_usage!("uptime.md"); pub mod options { pub static SINCE: &str = "since"; pub static PATH: &str = "path"; + pub static PRETTY: &str = "pretty"; } #[derive(Debug, Error)] @@ -68,6 +69,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { if matches.get_flag(options::SINCE) { uptime_since() + } else if matches.get_flag(options::PRETTY) { + pretty_print_uptime(None) } else if let Some(path) = file_path { uptime_with_file(path) } else { @@ -87,6 +90,13 @@ pub fn uu_app() -> Command { .long(options::SINCE) .help("system up since") .action(ArgAction::SetTrue), + ) + .arg( + Arg::new(options::PRETTY) + .short('p') + .long(options::PRETTY) + .help("show uptime in pretty format") + .action(ArgAction::SetTrue), ); #[cfg(unix)] cmd.arg( @@ -276,6 +286,17 @@ fn print_time() { } fn print_uptime(boot_time: Option) -> UResult<()> { - print!("up {}, ", get_formatted_uptime(boot_time)?); + print!( + "up {}, ", + get_formatted_uptime(boot_time, OutputFormat::HumanReadable)? + ); + Ok(()) +} + +fn pretty_print_uptime(boot_time: Option) -> UResult<()> { + print!( + "up {}", + get_formatted_uptime(boot_time, OutputFormat::PrettyPrint)? + ); Ok(()) } diff --git a/src/uucore/src/lib/features/uptime.rs b/src/uucore/src/lib/features/uptime.rs index 91fa9dd7de9..70a96d47116 100644 --- a/src/uucore/src/lib/features/uptime.rs +++ b/src/uucore/src/lib/features/uptime.rs @@ -155,29 +155,89 @@ pub fn get_uptime(_boot_time: Option) -> UResult { Ok(uptime as i64 / 1000) } +pub enum OutputFormat { + HumanReadable, + PrettyPrint, +} + +struct FormattedUptime { + up_days: i64, + up_hours: i64, + up_mins: i64, +} + +impl FormattedUptime { + fn new(up_secs: i64) -> Self { + 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; + + FormattedUptime { + up_days, + up_hours, + up_mins, + } + } + + fn get_human_readable_uptime(&self) -> String { + match self.up_days.cmp(&1) { + std::cmp::Ordering::Equal => format!( + "{} day, {:2}:{:02}", + self.up_days, self.up_hours, self.up_mins + ), + std::cmp::Ordering::Greater => format!( + "{} days, {:2}:{:02}", + self.up_days, self.up_hours, self.up_mins + ), + _ => format!("{:2}:{:02}", self.up_hours, self.up_mins), + } + } + + fn get_pretty_print_uptime(&self) -> String { + let day_string = match self.up_days.cmp(&1) { + std::cmp::Ordering::Equal => format!("{} day, ", self.up_days), + std::cmp::Ordering::Greater => format!("{} days, ", self.up_days), + _ => String::new(), + }; + let hour_string = match self.up_hours.cmp(&1) { + std::cmp::Ordering::Equal => format!("{} hour, ", self.up_hours), + std::cmp::Ordering::Greater => format!("{} hours, ", self.up_hours), + _ => String::new(), + }; + let min_string = match self.up_mins.cmp(&1) { + std::cmp::Ordering::Equal => format!("{} min", self.up_mins), + _ => format!("{} mins", self.up_mins), + }; + format!("{}{}{}", day_string, hour_string, min_string) + } +} + /// 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. +/// output_format: OutputFormat - Selects the format of the output string. /// /// # 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_formatted_uptime(boot_time: Option) -> UResult { +pub fn get_formatted_uptime( + boot_time: Option, + output_format: OutputFormat, +) -> 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}")), + + let formatted_uptime = FormattedUptime::new(up_secs); + + match output_format { + OutputFormat::HumanReadable => Ok(formatted_uptime.get_human_readable_uptime()), + OutputFormat::PrettyPrint => Ok(formatted_uptime.get_pretty_print_uptime()), } } diff --git a/tests/by-util/test_uptime.rs b/tests/by-util/test_uptime.rs index 7ec71cebad9..1cb8c8156a6 100644 --- a/tests/by-util/test_uptime.rs +++ b/tests/by-util/test_uptime.rs @@ -282,3 +282,12 @@ fn test_uptime_since() { new_ucmd!().arg("--since").succeeds().stdout_matches(&re); } + +#[test] +fn test_uptime_pretty_print() { + new_ucmd!() + .arg("-p") + .succeeds() + .stdout_contains("up") + .stdout_contains("min"); +} From 07c5f25647fab27bf0565ff67684551ecbb58ec2 Mon Sep 17 00:00:00 2001 From: Irene Brown <7233887+irbeam256@users.noreply.github.com> Date: Sun, 11 May 2025 18:57:08 -0700 Subject: [PATCH 2/4] Add doc comments to OutputFormat --- src/uucore/src/lib/features/uptime.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/uucore/src/lib/features/uptime.rs b/src/uucore/src/lib/features/uptime.rs index 70a96d47116..a242a0d7e2e 100644 --- a/src/uucore/src/lib/features/uptime.rs +++ b/src/uucore/src/lib/features/uptime.rs @@ -155,8 +155,12 @@ pub fn get_uptime(_boot_time: Option) -> UResult { Ok(uptime as i64 / 1000) } +/// The format used to display a FormattedUptime. pub enum OutputFormat { + /// Typical `uptime` output (e.g. 2 days, 3:04). HumanReadable, + + /// Pretty printed output (e.g. 2 days, 3 hours, 04 minutes). PrettyPrint, } From 9b84d71bd68659bc442bdb607ab8fc0353ba4a8d Mon Sep 17 00:00:00 2001 From: Irene Brown <7233887+irbeam256@users.noreply.github.com> Date: Mon, 12 May 2025 15:16:54 -0700 Subject: [PATCH 3/4] Update extensions.md with uptime `-p`/`--pretty` flag --- docs/src/extensions.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/src/extensions.md b/docs/src/extensions.md index 6cd7b8b443d..72130d708c9 100644 --- a/docs/src/extensions.md +++ b/docs/src/extensions.md @@ -92,7 +92,8 @@ also provides a `-v`/`--verbose` flag. ## `uptime` -Similar to the proc-ps implementation and unlike GNU/Coreutils, `uptime` provides `-s`/`--since` to show since when the system is up. +Similar to the proc-ps implementation and unlike GNU/Coreutils, `uptime` provides `-s`/`--since` to show since when the system is up +and `-p`/`--pretty` to display uptime in a pretty-printed format. ## `base32/base64/basenc` From 177fae1208c77814c57b2fb045f7b470241c0b8f Mon Sep 17 00:00:00 2001 From: Irene Brown <7233887+irbeam256@users.noreply.github.com> Date: Mon, 12 May 2025 09:47:16 -0700 Subject: [PATCH 4/4] uptime: Change get_uptime return to Duration --- src/uu/uptime/src/uptime.rs | 9 +++--- src/uucore/src/lib/features/uptime.rs | 45 +++++++++++---------------- 2 files changed, 24 insertions(+), 30 deletions(-) diff --git a/src/uu/uptime/src/uptime.rs b/src/uu/uptime/src/uptime.rs index 86342c2b735..1d417118134 100644 --- a/src/uu/uptime/src/uptime.rs +++ b/src/uu/uptime/src/uptime.rs @@ -184,9 +184,9 @@ fn uptime_with_file(file_path: &OsString) -> UResult<()> { #[cfg(target_os = "openbsd")] { - let upsecs = get_uptime(None); - if upsecs >= 0 { - print_uptime(Some(upsecs))?; + let uptime = get_uptime(None); + if uptime.is_ok() >= 0 { + print_uptime(uptime.unwrap())?; } else { show_error!("couldn't get boot time"); set_exit_code(1); @@ -215,8 +215,9 @@ fn uptime_since() -> UResult<()> { #[cfg(target_os = "windows")] let uptime = get_uptime(None)?; + // It is safe to cast Unix timestamps from u64->i64 at least for several trillion centuries. let initial_date = Local - .timestamp_opt(Utc::now().timestamp() - uptime, 0) + .timestamp_opt(Utc::now().timestamp() - uptime.as_secs() as i64, 0) .unwrap(); println!("{}", initial_date.format("%Y-%m-%d %H:%M:%S")); diff --git a/src/uucore/src/lib/features/uptime.rs b/src/uucore/src/lib/features/uptime.rs index a242a0d7e2e..1b271257f08 100644 --- a/src/uucore/src/lib/features/uptime.rs +++ b/src/uucore/src/lib/features/uptime.rs @@ -15,6 +15,7 @@ use crate::error::{UError, UResult}; use chrono::Local; use libc::time_t; +use std::time::Duration; use thiserror::Error; #[derive(Debug, Error)] @@ -48,9 +49,9 @@ pub fn get_formatted_time() -> String { /// /// # Returns /// -/// Returns a UResult with the uptime in seconds if successful, otherwise an UptimeError. +/// Returns a UResult with the uptime as a Duration if successful, otherwise an UptimeError. #[cfg(target_os = "openbsd")] -pub fn get_uptime(_boot_time: Option) -> UResult { +pub fn get_uptime(_boot_time: Option) -> UResult { use libc::CLOCK_BOOTTIME; use libc::clock_gettime; @@ -67,12 +68,7 @@ pub fn get_uptime(_boot_time: Option) -> UResult { 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) + Duration::new(tp.tv_sec, tp.tv_nsec) } else { Err(UptimeError::SystemUptime) } @@ -86,10 +82,10 @@ pub fn get_uptime(_boot_time: Option) -> UResult { /// /// # Returns /// -/// Returns a UResult with the uptime in seconds if successful, otherwise an UptimeError. +/// Returns a UResult with the uptime as a Duration if successful, otherwise an UptimeError. #[cfg(unix)] #[cfg(not(target_os = "openbsd"))] -pub fn get_uptime(boot_time: Option) -> UResult { +pub fn get_uptime(boot_time: Option) -> UResult { use crate::utmpx::Utmpx; use libc::BOOT_TIME; use std::fs::File; @@ -101,7 +97,8 @@ pub fn get_uptime(boot_time: Option) -> UResult { .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()); + .and_then(|s| s.parse::().ok()) + .and_then(|f| Duration::try_from_secs_f64(f).ok()); if let Some(uptime) = proc_uptime { return Ok(uptime); @@ -132,7 +129,7 @@ pub fn get_uptime(boot_time: Option) -> UResult { if now < boottime { Err(UptimeError::BootTime)?; } - return Ok(now - boottime); + return Ok(Duration::from_secs(u64::try_from(now - boottime).unwrap())); } Err(UptimeError::SystemUptime)? @@ -146,13 +143,13 @@ pub fn get_uptime(boot_time: Option) -> UResult { /// /// # Returns /// -/// Returns a UResult with the uptime in seconds if successful, otherwise an UptimeError. +/// Returns a UResult with the uptime as a Duration if successful, otherwise an UptimeError. #[cfg(windows)] -pub fn get_uptime(_boot_time: Option) -> UResult { +pub fn get_uptime(_boot_time: Option) -> UResult { use windows_sys::Win32::System::SystemInformation::GetTickCount; // SAFETY: always return u32 let uptime = unsafe { GetTickCount() }; - Ok(uptime as i64 / 1000) + Duration::from_secs(uptime) } /// The format used to display a FormattedUptime. @@ -165,13 +162,14 @@ pub enum OutputFormat { } struct FormattedUptime { - up_days: i64, - up_hours: i64, - up_mins: i64, + up_days: u64, + up_hours: u64, + up_mins: u64, } impl FormattedUptime { - fn new(up_secs: i64) -> Self { + fn new(uptime: Duration) -> Self { + let up_secs = uptime.as_secs(); 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; @@ -231,13 +229,8 @@ pub fn get_formatted_uptime( boot_time: Option, output_format: OutputFormat, ) -> UResult { - let up_secs = get_uptime(boot_time)?; - - if up_secs < 0 { - Err(UptimeError::SystemUptime)?; - } - - let formatted_uptime = FormattedUptime::new(up_secs); + let uptime = get_uptime(boot_time)?; + let formatted_uptime = FormattedUptime::new(uptime); match output_format { OutputFormat::HumanReadable => Ok(formatted_uptime.get_human_readable_uptime()),