mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-27 19:17:43 +00:00
Merge pull request #6514 from lcheylus/openbsd-uptime
uptime: add support for OpenBSD using utmp
This commit is contained in:
commit
96fa8e9480
8 changed files with 410 additions and 352 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -3406,6 +3406,7 @@ dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"clap",
|
"clap",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
|
"utmp-classic",
|
||||||
"uucore",
|
"uucore",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -22,6 +22,9 @@ clap = { workspace = true }
|
||||||
uucore = { workspace = true, features = ["libc", "utmpx"] }
|
uucore = { workspace = true, features = ["libc", "utmpx"] }
|
||||||
thiserror = { workspace = true }
|
thiserror = { workspace = true }
|
||||||
|
|
||||||
|
[target.'cfg(target_os = "openbsd")'.dependencies]
|
||||||
|
utmp-classic = { workspace = true }
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "uptime"
|
name = "uptime"
|
||||||
path = "src/main.rs"
|
path = "src/main.rs"
|
||||||
|
|
|
@ -1,14 +0,0 @@
|
||||||
// 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.
|
|
||||||
|
|
||||||
#[cfg(not(target_os = "openbsd"))]
|
|
||||||
mod unix;
|
|
||||||
#[cfg(not(target_os = "openbsd"))]
|
|
||||||
pub use self::unix::*;
|
|
||||||
|
|
||||||
#[cfg(target_os = "openbsd")]
|
|
||||||
mod openbsd;
|
|
||||||
#[cfg(target_os = "openbsd")]
|
|
||||||
pub use self::openbsd::*;
|
|
|
@ -1,17 +0,0 @@
|
||||||
// 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.
|
|
||||||
|
|
||||||
// Specific implementation for OpenBSD: tool unsupported (utmpx not supported)
|
|
||||||
|
|
||||||
use crate::uu_app;
|
|
||||||
|
|
||||||
use uucore::error::UResult;
|
|
||||||
|
|
||||||
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
|
||||||
let _matches = uu_app().try_get_matches_from(args)?;
|
|
||||||
|
|
||||||
println!("unsupported command on OpenBSD");
|
|
||||||
Ok(())
|
|
||||||
}
|
|
|
@ -1,309 +0,0 @@
|
||||||
// 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 getloadavg behaviour loadavg uptime upsecs updays upmins uphours boottime nusers utmpxname
|
|
||||||
|
|
||||||
use crate::options;
|
|
||||||
use crate::uu_app;
|
|
||||||
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::error::{UResult, USimpleError};
|
|
||||||
|
|
||||||
#[cfg(unix)]
|
|
||||||
use uucore::libc::getloadavg;
|
|
||||||
#[cfg(windows)]
|
|
||||||
extern "C" {
|
|
||||||
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<()> {
|
|
||||||
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 uptime = get_uptime(boot_time);
|
|
||||||
if matches.get_flag(options::SINCE) {
|
|
||||||
let initial_date = Local
|
|
||||||
.timestamp_opt(Utc::now().timestamp() - uptime, 0)
|
|
||||||
.unwrap();
|
|
||||||
println!("{}", initial_date.format("%Y-%m-%d %H:%M:%S"));
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
if uptime < 0 {
|
|
||||||
return Err(USimpleError::new(1, "could not retrieve system uptime"));
|
|
||||||
}
|
|
||||||
print_time();
|
|
||||||
let upsecs = uptime;
|
|
||||||
print_uptime(upsecs);
|
|
||||||
print_nusers(user_count);
|
|
||||||
print_loadavg();
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(unix)]
|
|
||||||
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 { ", " }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(windows)]
|
|
||||||
fn print_loadavg() {
|
|
||||||
// XXX: currently this is a noop as Windows does not seem to have anything comparable to
|
|
||||||
// getloadavg()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(unix)]
|
|
||||||
fn process_utmpx() -> (Option<time_t>, usize) {
|
|
||||||
use uucore::utmpx::*;
|
|
||||||
|
|
||||||
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)]
|
|
||||||
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)]
|
|
||||||
fn process_utmpx() -> (Option<time_t>, 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, "),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
fn print_time() {
|
|
||||||
let local_time = Local::now().time();
|
|
||||||
|
|
||||||
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)]
|
|
||||||
fn get_uptime(boot_time: Option<time_t>) -> 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<time_t>) -> 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}, "),
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -3,11 +3,32 @@
|
||||||
// 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 getloadavg behaviour loadavg uptime upsecs updays upmins uphours boottime nusers utmpxname gettime clockid
|
||||||
|
|
||||||
|
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::error::{UResult, USimpleError};
|
||||||
|
|
||||||
use clap::{builder::ValueParser, crate_version, Arg, ArgAction, Command, ValueHint};
|
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};
|
||||||
|
|
||||||
mod platform;
|
#[cfg(target_os = "openbsd")]
|
||||||
|
use utmp_classic::{parse_from_path, UtmpEntry};
|
||||||
|
#[cfg(not(target_os = "openbsd"))]
|
||||||
|
use uucore::utmpx::*;
|
||||||
|
|
||||||
const ABOUT: &str = help_about!("uptime.md");
|
const ABOUT: &str = help_about!("uptime.md");
|
||||||
const USAGE: &str = help_usage!("uptime.md");
|
const USAGE: &str = help_usage!("uptime.md");
|
||||||
|
@ -16,8 +37,59 @@ pub mod options {
|
||||||
pub static PATH: &str = "path";
|
pub static PATH: &str = "path";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
use uucore::libc::getloadavg;
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
extern "C" {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[uucore::main]
|
#[uucore::main]
|
||||||
use platform::uumain;
|
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn uu_app() -> Command {
|
pub fn uu_app() -> Command {
|
||||||
Command::new(uucore::util_name())
|
Command::new(uucore::util_name())
|
||||||
|
@ -40,3 +112,320 @@ pub fn uu_app() -> Command {
|
||||||
.value_hint(ValueHint::AnyPath),
|
.value_hint(ValueHint::AnyPath),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[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 user_count;
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "openbsd"))]
|
||||||
|
{
|
||||||
|
let (boot_time, 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 ??:??,");
|
||||||
|
}
|
||||||
|
user_count = count;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "openbsd")]
|
||||||
|
{
|
||||||
|
user_count = process_utmp_from_file(file_path.to_str().expect("invalid utmp path file"));
|
||||||
|
|
||||||
|
let upsecs = get_uptime();
|
||||||
|
if upsecs < 0 {
|
||||||
|
show_error!("couldn't get boot time");
|
||||||
|
set_exit_code(1);
|
||||||
|
|
||||||
|
print!("up ???? days ??:??,");
|
||||||
|
} else {
|
||||||
|
print_uptime(upsecs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
print_nusers(user_count);
|
||||||
|
print_loadavg();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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) {
|
||||||
|
let initial_date = Local
|
||||||
|
.timestamp_opt(Utc::now().timestamp() - uptime, 0)
|
||||||
|
.unwrap();
|
||||||
|
println!("{}", initial_date.format("%Y-%m-%d %H:%M:%S"));
|
||||||
|
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_loadavg();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
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 { ", " }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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<time_t>, 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<time_t>, 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<time_t>, 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, "),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn print_time() {
|
||||||
|
let local_time = Local::now().time();
|
||||||
|
|
||||||
|
print!(" {} ", local_time.format("%H:%M:%S"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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<time_t>) -> 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<time_t>) -> 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}, "),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -8,16 +8,16 @@
|
||||||
|
|
||||||
use crate::common::util::TestScenario;
|
use crate::common::util::TestScenario;
|
||||||
|
|
||||||
#[cfg(not(target_os = "macos"))]
|
#[cfg(not(any(target_os = "macos", target_os = "openbsd")))]
|
||||||
use bincode::serialize;
|
use bincode::serialize;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
#[cfg(not(target_os = "macos"))]
|
#[cfg(not(any(target_os = "macos", target_os = "openbsd")))]
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
#[cfg(not(target_os = "macos"))]
|
#[cfg(not(any(target_os = "macos", target_os = "openbsd")))]
|
||||||
use serde_big_array::BigArray;
|
use serde_big_array::BigArray;
|
||||||
#[cfg(not(target_os = "macos"))]
|
#[cfg(not(any(target_os = "macos", target_os = "openbsd")))]
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
#[cfg(not(target_os = "macos"))]
|
#[cfg(not(any(target_os = "macos", target_os = "openbsd")))]
|
||||||
use std::{io::Write, path::PathBuf};
|
use std::{io::Write, path::PathBuf};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -26,7 +26,6 @@ fn test_invalid_arg() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(not(target_os = "openbsd"))]
|
|
||||||
fn test_uptime() {
|
fn test_uptime() {
|
||||||
TestScenario::new(util_name!())
|
TestScenario::new(util_name!())
|
||||||
.ucmd()
|
.ucmd()
|
||||||
|
@ -83,7 +82,7 @@ fn test_uptime_with_fifo() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(not(any(target_os = "openbsd", target_os = "freebsd")))]
|
#[cfg(not(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()
|
||||||
|
@ -232,7 +231,6 @@ fn test_uptime_with_file_containing_valid_boot_time_utmpx_record() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(not(target_os = "openbsd"))]
|
|
||||||
fn test_uptime_with_extra_argument() {
|
fn test_uptime_with_extra_argument() {
|
||||||
let ts = TestScenario::new(util_name!());
|
let ts = TestScenario::new(util_name!());
|
||||||
|
|
||||||
|
@ -244,7 +242,6 @@ fn test_uptime_with_extra_argument() {
|
||||||
}
|
}
|
||||||
/// 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(target_os = "openbsd"))]
|
|
||||||
fn test_uptime_with_dir() {
|
fn test_uptime_with_dir() {
|
||||||
let ts = TestScenario::new(util_name!());
|
let ts = TestScenario::new(util_name!());
|
||||||
let at = &ts.fixtures;
|
let at = &ts.fixtures;
|
||||||
|
@ -258,7 +255,15 @@ fn test_uptime_with_dir() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(not(target_os = "openbsd"))]
|
#[cfg(target_os = "openbsd")]
|
||||||
|
fn test_uptime_check_users_openbsd() {
|
||||||
|
new_ucmd!()
|
||||||
|
.args(&["openbsd_utmp"])
|
||||||
|
.run()
|
||||||
|
.stdout_contains("4 users");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
fn test_uptime_since() {
|
fn test_uptime_since() {
|
||||||
let re = Regex::new(r"\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}").unwrap();
|
let re = Regex::new(r"\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}").unwrap();
|
||||||
|
|
||||||
|
|
BIN
tests/fixtures/uptime/openbsd_utmp
vendored
Normal file
BIN
tests/fixtures/uptime/openbsd_utmp
vendored
Normal file
Binary file not shown.
Loading…
Add table
Add a link
Reference in a new issue