mirror of
https://github.com/uutils/coreutils
synced 2024-12-14 07:12:44 +00:00
`uptime
`: Support files in uptime (#6400)
This commit is contained in:
parent
32c5d23f91
commit
2774274cc2
6 changed files with 454 additions and 33 deletions
32
Cargo.lock
generated
32
Cargo.lock
generated
|
@ -156,6 +156,15 @@ dependencies = [
|
||||||
"compare",
|
"compare",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bincode"
|
||||||
|
version = "1.3.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bindgen"
|
name = "bindgen"
|
||||||
version = "0.69.4"
|
version = "0.69.4"
|
||||||
|
@ -423,6 +432,7 @@ checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
|
||||||
name = "coreutils"
|
name = "coreutils"
|
||||||
version = "0.0.26"
|
version = "0.0.26"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"bincode",
|
||||||
"chrono",
|
"chrono",
|
||||||
"clap",
|
"clap",
|
||||||
"clap_complete",
|
"clap_complete",
|
||||||
|
@ -444,6 +454,8 @@ dependencies = [
|
||||||
"rlimit",
|
"rlimit",
|
||||||
"rstest",
|
"rstest",
|
||||||
"selinux",
|
"selinux",
|
||||||
|
"serde",
|
||||||
|
"serde-big-array",
|
||||||
"sha1",
|
"sha1",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
"textwrap",
|
"textwrap",
|
||||||
|
@ -2085,18 +2097,27 @@ checksum = "e25dfac463d778e353db5be2449d1cce89bd6fd23c9f1ea21310ce6e5a1b29c4"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.193"
|
version = "1.0.202"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89"
|
checksum = "226b61a0d411b2ba5ff6d7f73a476ac4f8bb900373459cd00fab8512828ba395"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_derive"
|
name = "serde-big-array"
|
||||||
version = "1.0.193"
|
version = "0.5.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3"
|
checksum = "11fc7cc2c76d73e0f27ee52abbd64eec84d46f370c88371120433196934e4b7f"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_derive"
|
||||||
|
version = "1.0.202"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6048858004bcff69094cd972ed40a32500f153bd3be9f716b2eed2e8217c4838"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
@ -3360,6 +3381,7 @@ version = "0.0.26"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"clap",
|
"clap",
|
||||||
|
"thiserror",
|
||||||
"uucore",
|
"uucore",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
13
Cargo.toml
13
Cargo.toml
|
@ -1,7 +1,7 @@
|
||||||
# coreutils (uutils)
|
# coreutils (uutils)
|
||||||
# * see the repository LICENSE, README, and CONTRIBUTING files for more information
|
# * see the repository LICENSE, README, and CONTRIBUTING files for more information
|
||||||
|
|
||||||
# spell-checker:ignore (libs) bigdecimal datetime fundu gethostid kqueue libselinux mangen memmap procfs uuhelp
|
# spell-checker:ignore (libs) bigdecimal datetime serde bincode fundu gethostid kqueue libselinux mangen memmap procfs uuhelp
|
||||||
|
|
||||||
[package]
|
[package]
|
||||||
name = "coreutils"
|
name = "coreutils"
|
||||||
|
@ -491,10 +491,11 @@ tempfile = { workspace = true }
|
||||||
time = { workspace = true, features = ["local-offset"] }
|
time = { workspace = true, features = ["local-offset"] }
|
||||||
unindent = "0.2.3"
|
unindent = "0.2.3"
|
||||||
uucore = { workspace = true, features = [
|
uucore = { workspace = true, features = [
|
||||||
"entries",
|
|
||||||
"mode",
|
"mode",
|
||||||
|
"entries",
|
||||||
"process",
|
"process",
|
||||||
"signals",
|
"signals",
|
||||||
|
"utmpx",
|
||||||
] }
|
] }
|
||||||
walkdir = { workspace = true }
|
walkdir = { workspace = true }
|
||||||
hex-literal = "0.4.1"
|
hex-literal = "0.4.1"
|
||||||
|
@ -509,6 +510,14 @@ rlimit = "0.10.1"
|
||||||
rand_pcg = "0.3.1"
|
rand_pcg = "0.3.1"
|
||||||
xattr = { workspace = true }
|
xattr = { workspace = true }
|
||||||
|
|
||||||
|
# Specifically used in test_uptime::test_uptime_with_file_containing_valid_boot_time_utmpx_record
|
||||||
|
# to deserialize a utmpx struct into a binary file
|
||||||
|
[target.'cfg(all(target_family= "unix",not(target_os = "macos")))'.dev-dependencies]
|
||||||
|
serde = { version = "1.0.202", features = ["derive"] }
|
||||||
|
bincode = { version = "1.3.3" }
|
||||||
|
serde-big-array = "0.5.1"
|
||||||
|
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
phf_codegen = { workspace = true }
|
phf_codegen = { workspace = true }
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@ path = "src/uptime.rs"
|
||||||
chrono = { workspace = true }
|
chrono = { workspace = true }
|
||||||
clap = { workspace = true }
|
clap = { workspace = true }
|
||||||
uucore = { workspace = true, features = ["libc", "utmpx"] }
|
uucore = { workspace = true, features = ["libc", "utmpx"] }
|
||||||
|
thiserror = { workspace = true }
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "uptime"
|
name = "uptime"
|
||||||
|
|
|
@ -3,12 +3,20 @@
|
||||||
// For the full copyright and license information, please view the LICENSE
|
// For the full copyright and license information, please view the LICENSE
|
||||||
// file that was distributed with this source code.
|
// file that was distributed with this source code.
|
||||||
|
|
||||||
// spell-checker:ignore (ToDO) getloadavg upsecs updays nusers loadavg boottime uphours upmins
|
// spell-checker:ignore getloadavg behaviour loadavg uptime upsecs updays upmins uphours boottime nusers utmpxname
|
||||||
|
|
||||||
use crate::options;
|
use crate::options;
|
||||||
use crate::uu_app;
|
use crate::uu_app;
|
||||||
|
|
||||||
use chrono::{Local, TimeZone, Utc};
|
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;
|
||||||
|
|
||||||
use uucore::libc::time_t;
|
use uucore::libc::time_t;
|
||||||
|
|
||||||
|
@ -16,20 +24,127 @@ use uucore::error::{UResult, USimpleError};
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
use uucore::libc::getloadavg;
|
use uucore::libc::getloadavg;
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
extern "C" {
|
extern "C" {
|
||||||
fn GetTickCount() -> uucore::libc::uint32_t;
|
fn GetTickCount() -> uucore::libc::uint32_t;
|
||||||
}
|
}
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum UptimeError {
|
||||||
|
// io::Error wrapper
|
||||||
|
#[error("couldn't get boot time: {0}")]
|
||||||
|
IoErr(#[from] io::Error),
|
||||||
|
|
||||||
|
#[error("couldn't get boot time: Is a directory")]
|
||||||
|
TargetIsDir,
|
||||||
|
|
||||||
|
#[error("couldn't get boot time: Illegal seek")]
|
||||||
|
TargetIsFifo,
|
||||||
|
#[error("extra operand '{0}'")]
|
||||||
|
ExtraOperandError(String),
|
||||||
|
}
|
||||||
|
impl UError for UptimeError {
|
||||||
|
fn code(&self) -> i32 {
|
||||||
|
1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||||
let matches = uu_app().try_get_matches_from(args)?;
|
let matches = uu_app().try_get_matches_from(args)?;
|
||||||
|
let argument = matches.get_many::<OsString>(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)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
fn uptime_with_file(file_path: &OsString) -> UResult<()> {
|
||||||
|
// Uptime will print loadavg and time to stderr unless we encounter an extra operand.
|
||||||
|
let mut non_fatal_error = false;
|
||||||
|
|
||||||
|
// process_utmpx_from_file() doesn't detect or report failures, we check if the path is valid
|
||||||
|
// before proceeding with more operations.
|
||||||
|
let md_res = fs::metadata(file_path);
|
||||||
|
if let Ok(md) = md_res {
|
||||||
|
if md.is_dir() {
|
||||||
|
show_error!("{}", UptimeError::TargetIsDir);
|
||||||
|
non_fatal_error = true;
|
||||||
|
set_exit_code(1);
|
||||||
|
}
|
||||||
|
if md.file_type().is_fifo() {
|
||||||
|
show_error!("{}", UptimeError::TargetIsFifo);
|
||||||
|
non_fatal_error = true;
|
||||||
|
set_exit_code(1);
|
||||||
|
}
|
||||||
|
} else if let Err(e) = md_res {
|
||||||
|
non_fatal_error = true;
|
||||||
|
set_exit_code(1);
|
||||||
|
show_error!("{}", UptimeError::IoErr(e));
|
||||||
|
}
|
||||||
|
// utmpxname() returns an -1 , when filename doesn't end with 'x' or its too long.
|
||||||
|
// Reference: `<https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man3/utmpxname.3.html>`
|
||||||
|
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
{
|
||||||
|
use std::os::unix::ffi::OsStrExt;
|
||||||
|
let bytes = file_path.as_os_str().as_bytes();
|
||||||
|
|
||||||
|
if bytes[bytes.len() - 1] != b'x' {
|
||||||
|
show_error!("couldn't get boot time");
|
||||||
|
print_time();
|
||||||
|
print!("up ???? days ??:??,");
|
||||||
|
print_nusers(0);
|
||||||
|
print_loadavg();
|
||||||
|
set_exit_code(1);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if non_fatal_error {
|
||||||
|
print_time();
|
||||||
|
print!("up ???? days ??:??,");
|
||||||
|
print_nusers(0);
|
||||||
|
print_loadavg();
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
print_time();
|
||||||
|
let (boot_time, user_count) = process_utmpx_from_file(file_path);
|
||||||
|
if let Some(time) = boot_time {
|
||||||
|
let upsecs = get_uptime_from_boot_time(time);
|
||||||
|
print_uptime(upsecs);
|
||||||
|
} else {
|
||||||
|
show_error!("couldn't get boot time");
|
||||||
|
set_exit_code(1);
|
||||||
|
|
||||||
|
print!("up ???? days ??:??,");
|
||||||
|
}
|
||||||
|
|
||||||
|
print_nusers(user_count);
|
||||||
|
print_loadavg();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Default uptime behaviour i.e. when no file argument is given.
|
||||||
|
fn default_uptime(matches: &ArgMatches) -> UResult<()> {
|
||||||
let (boot_time, user_count) = process_utmpx();
|
let (boot_time, user_count) = process_utmpx();
|
||||||
let uptime = get_uptime(boot_time);
|
let uptime = get_uptime(boot_time);
|
||||||
if uptime < 0 {
|
|
||||||
Err(USimpleError::new(1, "could not retrieve system uptime"))
|
|
||||||
} else {
|
|
||||||
if matches.get_flag(options::SINCE) {
|
if matches.get_flag(options::SINCE) {
|
||||||
let initial_date = Local
|
let initial_date = Local
|
||||||
.timestamp_opt(Utc::now().timestamp() - uptime, 0)
|
.timestamp_opt(Utc::now().timestamp() - uptime, 0)
|
||||||
|
@ -38,6 +153,9 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if uptime < 0 {
|
||||||
|
return Err(USimpleError::new(1, "could not retrieve system uptime"));
|
||||||
|
}
|
||||||
print_time();
|
print_time();
|
||||||
let upsecs = uptime;
|
let upsecs = uptime;
|
||||||
print_uptime(upsecs);
|
print_uptime(upsecs);
|
||||||
|
@ -45,7 +163,6 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||||
print_loadavg();
|
print_loadavg();
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
|
@ -97,6 +214,28 @@ fn process_utmpx() -> (Option<time_t>, usize) {
|
||||||
(boot_time, nusers)
|
(boot_time, nusers)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
fn process_utmpx_from_file(file: &OsString) -> (Option<time_t>, usize) {
|
||||||
|
use uucore::utmpx::*;
|
||||||
|
|
||||||
|
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)]
|
#[cfg(windows)]
|
||||||
fn process_utmpx() -> (Option<time_t>, usize) {
|
fn process_utmpx() -> (Option<time_t>, usize) {
|
||||||
(None, 0) // TODO: change 0 to number of users
|
(None, 0) // TODO: change 0 to number of users
|
||||||
|
@ -104,9 +243,9 @@ fn process_utmpx() -> (Option<time_t>, usize) {
|
||||||
|
|
||||||
fn print_nusers(nusers: usize) {
|
fn print_nusers(nusers: usize) {
|
||||||
match nusers.cmp(&1) {
|
match nusers.cmp(&1) {
|
||||||
|
std::cmp::Ordering::Less => print!(" 0 users, "),
|
||||||
std::cmp::Ordering::Equal => print!("1 user, "),
|
std::cmp::Ordering::Equal => print!("1 user, "),
|
||||||
std::cmp::Ordering::Greater => print!("{nusers} users, "),
|
std::cmp::Ordering::Greater => print!("{nusers} users, "),
|
||||||
_ => {}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -116,6 +255,15 @@ fn print_time() {
|
||||||
print!(" {} ", local_time.format("%H:%M:%S"));
|
print!(" {} ", local_time.format("%H:%M:%S"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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(unix)]
|
||||||
fn get_uptime(boot_time: Option<time_t>) -> i64 {
|
fn get_uptime(boot_time: Option<time_t>) -> i64 {
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
|
@ -154,7 +302,7 @@ fn print_uptime(upsecs: i64) {
|
||||||
match updays.cmp(&1) {
|
match updays.cmp(&1) {
|
||||||
std::cmp::Ordering::Equal => print!("up {updays:1} day, {uphours:2}:{upmins:02}, "),
|
std::cmp::Ordering::Equal => print!("up {updays:1} day, {uphours:2}:{upmins:02}, "),
|
||||||
std::cmp::Ordering::Greater => {
|
std::cmp::Ordering::Greater => {
|
||||||
print!("up {updays:1} days, {uphours:2}:{upmins:02}, ");
|
print!("up {updays:1} days {uphours:2}:{upmins:02}, ");
|
||||||
}
|
}
|
||||||
_ => print!("up {uphours:2}:{upmins:02}, "),
|
_ => print!("up {uphours:2}:{upmins:02}, "),
|
||||||
};
|
};
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
// For the full copyright and license information, please view the LICENSE
|
// For the full copyright and license information, please view the LICENSE
|
||||||
// file that was distributed with this source code.
|
// file that was distributed with this source code.
|
||||||
|
|
||||||
use clap::{crate_version, Arg, ArgAction, Command};
|
use clap::{builder::ValueParser, crate_version, Arg, ArgAction, Command, ValueHint};
|
||||||
|
|
||||||
use uucore::{format_usage, help_about, help_usage};
|
use uucore::{format_usage, help_about, help_usage};
|
||||||
|
|
||||||
|
@ -13,6 +13,7 @@ const ABOUT: &str = help_about!("uptime.md");
|
||||||
const USAGE: &str = help_usage!("uptime.md");
|
const USAGE: &str = help_usage!("uptime.md");
|
||||||
pub mod options {
|
pub mod options {
|
||||||
pub static SINCE: &str = "since";
|
pub static SINCE: &str = "since";
|
||||||
|
pub static PATH: &str = "path";
|
||||||
}
|
}
|
||||||
|
|
||||||
#[uucore::main]
|
#[uucore::main]
|
||||||
|
@ -31,4 +32,11 @@ pub fn uu_app() -> Command {
|
||||||
.help("system up since")
|
.help("system up since")
|
||||||
.action(ArgAction::SetTrue),
|
.action(ArgAction::SetTrue),
|
||||||
)
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::new(options::PATH)
|
||||||
|
.help("file to search boot time from")
|
||||||
|
.action(ArgAction::Append)
|
||||||
|
.value_parser(ValueParser::os_string())
|
||||||
|
.value_hint(ValueHint::AnyPath),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,8 +2,22 @@
|
||||||
//
|
//
|
||||||
// For the full copyright and license information, please view the LICENSE
|
// For the full copyright and license information, please view the LICENSE
|
||||||
// file that was distributed with this source code.
|
// file that was distributed with this source code.
|
||||||
|
//
|
||||||
|
// spell-checker:ignore bincode serde utmp runlevel testusr testx
|
||||||
|
|
||||||
use crate::common::util::TestScenario;
|
use crate::common::util::TestScenario;
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "macos"))]
|
||||||
|
use bincode::serialize;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
|
#[cfg(not(target_os = "macos"))]
|
||||||
|
use serde::Serialize;
|
||||||
|
#[cfg(not(target_os = "macos"))]
|
||||||
|
use serde_big_array::BigArray;
|
||||||
|
#[cfg(not(target_os = "macos"))]
|
||||||
|
use std::fs::File;
|
||||||
|
#[cfg(not(target_os = "macos"))]
|
||||||
|
use std::{io::Write, path::PathBuf};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_invalid_arg() {
|
fn test_invalid_arg() {
|
||||||
|
@ -22,6 +36,225 @@ fn test_uptime() {
|
||||||
// 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
|
||||||
|
#[test]
|
||||||
|
#[cfg(not(any(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() {
|
||||||
|
let (at, mut ucmd) = at_and_ucmd!();
|
||||||
|
at.write("file1", "hello");
|
||||||
|
|
||||||
|
ucmd.arg(at.plus_as_string("file1"))
|
||||||
|
.fails()
|
||||||
|
.stderr_contains("uptime: couldn't get boot time")
|
||||||
|
.stdout_contains("up ???? days ??:??")
|
||||||
|
.stdout_contains("load average");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks whether uptime displays the correct stderr msg when its called with a fifo
|
||||||
|
#[test]
|
||||||
|
#[cfg(all(unix, feature = "cp"))]
|
||||||
|
fn test_uptime_with_fifo() {
|
||||||
|
// 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!());
|
||||||
|
|
||||||
|
let at = &ts.fixtures;
|
||||||
|
at.mkfifo("fifo1");
|
||||||
|
|
||||||
|
at.write("a", "hello");
|
||||||
|
// Creating a child process to write to the fifo
|
||||||
|
let mut child = ts
|
||||||
|
.ccmd("cp")
|
||||||
|
.arg(at.plus_as_string("a"))
|
||||||
|
.arg(at.plus_as_string("fifo1"))
|
||||||
|
.run_no_wait();
|
||||||
|
|
||||||
|
ts.ucmd()
|
||||||
|
.arg("fifo1")
|
||||||
|
.fails()
|
||||||
|
.stderr_contains("uptime: couldn't get boot time: Illegal seek")
|
||||||
|
.stdout_contains("up ???? days ??:??")
|
||||||
|
.stdout_contains("load average");
|
||||||
|
|
||||||
|
child.kill();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(not(any(target_os = "openbsd", 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()
|
||||||
|
let ts = TestScenario::new(util_name!());
|
||||||
|
|
||||||
|
ts.ucmd()
|
||||||
|
.arg("file1")
|
||||||
|
.fails()
|
||||||
|
.stderr_contains("uptime: couldn't get boot time: No such file or directory")
|
||||||
|
.stdout_contains("up ???? days ??:??");
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO create a similar test for macos
|
||||||
|
// This will pass
|
||||||
|
#[test]
|
||||||
|
#[cfg(not(any(target_os = "openbsd", target_os = "macos")))]
|
||||||
|
fn test_uptime_with_file_containing_valid_boot_time_utmpx_record() {
|
||||||
|
// This test will pass for freebsd but we currently don't support changing the utmpx file for
|
||||||
|
// freebsd.
|
||||||
|
let ts = TestScenario::new(util_name!());
|
||||||
|
let at = &ts.fixtures;
|
||||||
|
// Regex matches for "up 00::00" ,"up 12 days 00::00", the time can be any valid time and
|
||||||
|
// the days can be more than 1 digit or not there. This will match even if the amount of whitespace is
|
||||||
|
// wrong between the days and the time.
|
||||||
|
|
||||||
|
let re = Regex::new(r"up [(\d){1,} days]*\d{1,2}:\d\d").unwrap();
|
||||||
|
utmp(&at.plus("testx"));
|
||||||
|
ts.ucmd()
|
||||||
|
.arg("testx")
|
||||||
|
.succeeds()
|
||||||
|
.stdout_matches(&re)
|
||||||
|
.stdout_contains("load average");
|
||||||
|
|
||||||
|
// Helper function to create byte sequences
|
||||||
|
fn slice_32(slice: &[u8]) -> [i8; 32] {
|
||||||
|
let mut arr: [i8; 32] = [0; 32];
|
||||||
|
|
||||||
|
for (i, val) in slice.iter().enumerate() {
|
||||||
|
arr[i] = *val as i8;
|
||||||
|
}
|
||||||
|
arr
|
||||||
|
}
|
||||||
|
// Creates a file utmp records of three different types including a valid BOOT_TIME entry
|
||||||
|
fn utmp(path: &PathBuf) {
|
||||||
|
// Definitions of our utmpx structs
|
||||||
|
const BOOT_TIME: i32 = 2;
|
||||||
|
const RUN_LVL: i32 = 1;
|
||||||
|
const USER_PROCESS: i32 = 7;
|
||||||
|
#[derive(Serialize)]
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct TimeVal {
|
||||||
|
pub tv_sec: i32,
|
||||||
|
pub tv_usec: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct ExitStatus {
|
||||||
|
e_termination: i16,
|
||||||
|
e_exit: i16,
|
||||||
|
}
|
||||||
|
#[derive(Serialize)]
|
||||||
|
#[repr(C, align(4))]
|
||||||
|
pub struct Utmp {
|
||||||
|
pub ut_type: i32,
|
||||||
|
pub ut_pid: i32,
|
||||||
|
pub ut_line: [i8; 32],
|
||||||
|
pub ut_id: [i8; 4],
|
||||||
|
|
||||||
|
pub ut_user: [i8; 32],
|
||||||
|
#[serde(with = "BigArray")]
|
||||||
|
pub ut_host: [i8; 256],
|
||||||
|
pub ut_exit: ExitStatus,
|
||||||
|
pub ut_session: i32,
|
||||||
|
pub ut_tv: TimeVal,
|
||||||
|
|
||||||
|
pub ut_addr_v6: [i32; 4],
|
||||||
|
glibc_reserved: [i8; 20],
|
||||||
|
}
|
||||||
|
|
||||||
|
let utmp = Utmp {
|
||||||
|
ut_type: BOOT_TIME,
|
||||||
|
ut_pid: 0,
|
||||||
|
ut_line: slice_32("~".as_bytes()),
|
||||||
|
ut_id: [126, 126, 0, 0],
|
||||||
|
ut_user: slice_32("reboot".as_bytes()),
|
||||||
|
ut_host: [0; 256],
|
||||||
|
ut_exit: ExitStatus {
|
||||||
|
e_termination: 0,
|
||||||
|
e_exit: 0,
|
||||||
|
},
|
||||||
|
ut_session: 0,
|
||||||
|
ut_tv: TimeVal {
|
||||||
|
tv_sec: 1716371201,
|
||||||
|
tv_usec: 290913,
|
||||||
|
},
|
||||||
|
ut_addr_v6: [127, 0, 0, 1],
|
||||||
|
glibc_reserved: [0; 20],
|
||||||
|
};
|
||||||
|
let utmp1 = Utmp {
|
||||||
|
ut_type: RUN_LVL,
|
||||||
|
ut_pid: std::process::id() as i32,
|
||||||
|
ut_line: slice_32("~".as_bytes()),
|
||||||
|
ut_id: [126, 126, 0, 0],
|
||||||
|
ut_user: slice_32("runlevel".as_bytes()),
|
||||||
|
ut_host: [0; 256],
|
||||||
|
ut_exit: ExitStatus {
|
||||||
|
e_termination: 0,
|
||||||
|
e_exit: 0,
|
||||||
|
},
|
||||||
|
ut_session: 0,
|
||||||
|
ut_tv: TimeVal {
|
||||||
|
tv_sec: 1716371209,
|
||||||
|
tv_usec: 162250,
|
||||||
|
},
|
||||||
|
ut_addr_v6: [0, 0, 0, 0],
|
||||||
|
glibc_reserved: [0; 20],
|
||||||
|
};
|
||||||
|
let utmp2 = Utmp {
|
||||||
|
ut_type: USER_PROCESS,
|
||||||
|
ut_pid: std::process::id() as i32,
|
||||||
|
ut_line: slice_32(":1".as_bytes()),
|
||||||
|
ut_id: [126, 126, 0, 0],
|
||||||
|
ut_user: slice_32("testusr".as_bytes()),
|
||||||
|
ut_host: [0; 256],
|
||||||
|
ut_exit: ExitStatus {
|
||||||
|
e_termination: 0,
|
||||||
|
e_exit: 0,
|
||||||
|
},
|
||||||
|
ut_session: 0,
|
||||||
|
ut_tv: TimeVal {
|
||||||
|
tv_sec: 1716371283,
|
||||||
|
tv_usec: 858764,
|
||||||
|
},
|
||||||
|
ut_addr_v6: [0, 0, 0, 0],
|
||||||
|
glibc_reserved: [0; 20],
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut buf = serialize(&utmp).unwrap();
|
||||||
|
buf.append(&mut serialize(&utmp1).unwrap());
|
||||||
|
buf.append(&mut serialize(&utmp2).unwrap());
|
||||||
|
let mut f = File::create(path).unwrap();
|
||||||
|
f.write_all(&buf).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(not(target_os = "openbsd"))]
|
||||||
|
fn test_uptime_with_extra_argument() {
|
||||||
|
let ts = TestScenario::new(util_name!());
|
||||||
|
|
||||||
|
ts.ucmd()
|
||||||
|
.arg("a")
|
||||||
|
.arg("b")
|
||||||
|
.fails()
|
||||||
|
.stderr_contains("extra operand 'b'");
|
||||||
|
}
|
||||||
|
/// Checks whether uptime displays the correct stderr msg when its called with a directory
|
||||||
|
#[test]
|
||||||
|
#[cfg(not(target_os = "openbsd"))]
|
||||||
|
fn test_uptime_with_dir() {
|
||||||
|
let ts = TestScenario::new(util_name!());
|
||||||
|
let at = &ts.fixtures;
|
||||||
|
at.mkdir("dir1");
|
||||||
|
|
||||||
|
ts.ucmd()
|
||||||
|
.arg("dir1")
|
||||||
|
.fails()
|
||||||
|
.stderr_contains("uptime: couldn't get boot time: Is a directory")
|
||||||
|
.stdout_contains("up ???? days ??:??");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(not(target_os = "openbsd"))]
|
#[cfg(not(target_os = "openbsd"))]
|
||||||
fn test_uptime_since() {
|
fn test_uptime_since() {
|
||||||
|
|
Loading…
Reference in a new issue