mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-29 03:57:44 +00:00
uptime: refactor, move some codes to uucore (#7289)
This commit is contained in:
parent
9aa872327b
commit
047ec995f2
7 changed files with 494 additions and 240 deletions
4
Cargo.lock
generated
4
Cargo.lock
generated
|
@ -1308,7 +1308,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"windows-targets 0.48.5",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -3470,6 +3470,7 @@ dependencies = [
|
|||
"thiserror 2.0.11",
|
||||
"utmp-classic",
|
||||
"uucore",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -3566,6 +3567,7 @@ dependencies = [
|
|||
"tempfile",
|
||||
"thiserror 2.0.11",
|
||||
"time",
|
||||
"utmp-classic",
|
||||
"uucore_procs",
|
||||
"walkdir",
|
||||
"wild",
|
||||
|
|
|
@ -19,12 +19,18 @@ path = "src/uptime.rs"
|
|||
[dependencies]
|
||||
chrono = { workspace = true }
|
||||
clap = { workspace = true }
|
||||
uucore = { workspace = true, features = ["libc", "utmpx"] }
|
||||
thiserror = { workspace = true }
|
||||
uucore = { workspace = true, features = ["libc", "utmpx", "uptime"] }
|
||||
|
||||
[target.'cfg(target_os = "openbsd")'.dependencies]
|
||||
utmp-classic = { workspace = true }
|
||||
|
||||
[target.'cfg(target_os="windows")'.dependencies]
|
||||
windows-sys = { workspace = true, features = [
|
||||
"Win32_System_RemoteDesktop",
|
||||
"Wdk_System_SystemInformation",
|
||||
] }
|
||||
|
||||
[[bin]]
|
||||
name = "uptime"
|
||||
path = "src/main.rs"
|
||||
|
|
|
@ -3,30 +3,23 @@
|
|||
// 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 gettime clockid
|
||||
// spell-checker:ignore getloadavg behaviour loadavg uptime upsecs updays upmins uphours boottime nusers utmpxname gettime clockid formated
|
||||
|
||||
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::uptime::*;
|
||||
|
||||
use uucore::error::{UResult, USimpleError};
|
||||
use uucore::error::UResult;
|
||||
|
||||
use clap::{builder::ValueParser, crate_version, Arg, ArgAction, Command, ValueHint};
|
||||
|
||||
use uucore::{format_usage, help_about, help_usage};
|
||||
|
||||
#[cfg(target_os = "openbsd")]
|
||||
use utmp_classic::{parse_from_path, UtmpEntry};
|
||||
#[cfg(unix)]
|
||||
#[cfg(not(target_os = "openbsd"))]
|
||||
use uucore::utmpx::*;
|
||||
|
||||
|
@ -37,12 +30,9 @@ pub mod options {
|
|||
pub static PATH: &str = "path";
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
use uucore::libc::getloadavg;
|
||||
|
||||
#[cfg(windows)]
|
||||
extern "C" {
|
||||
fn GetTickCount() -> uucore::libc::uint32_t;
|
||||
fn GetTickCount() -> u32;
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
|
@ -68,27 +58,38 @@ impl UError for UptimeError {
|
|||
#[uucore::main]
|
||||
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);
|
||||
#[cfg(windows)]
|
||||
return default_uptime(&matches);
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::ffi::OsString;
|
||||
use uucore::error::set_exit_code;
|
||||
use uucore::show_error;
|
||||
|
||||
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)
|
||||
}
|
||||
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 {
|
||||
|
@ -114,7 +115,12 @@ pub fn uu_app() -> Command {
|
|||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn uptime_with_file(file_path: &OsString) -> UResult<()> {
|
||||
fn uptime_with_file(file_path: &std::ffi::OsString) -> UResult<()> {
|
||||
use std::fs;
|
||||
use std::os::unix::fs::FileTypeExt;
|
||||
use uucore::error::set_exit_code;
|
||||
use uucore::show_error;
|
||||
|
||||
// Uptime will print loadavg and time to stderr unless we encounter an extra operand.
|
||||
let mut non_fatal_error = false;
|
||||
|
||||
|
@ -149,7 +155,7 @@ fn uptime_with_file(file_path: &OsString) -> UResult<()> {
|
|||
show_error!("couldn't get boot time");
|
||||
print_time();
|
||||
print!("up ???? days ??:??,");
|
||||
print_nusers(0);
|
||||
print_nusers(Some(0))?;
|
||||
print_loadavg();
|
||||
set_exit_code(1);
|
||||
return Ok(());
|
||||
|
@ -159,7 +165,7 @@ fn uptime_with_file(file_path: &OsString) -> UResult<()> {
|
|||
if non_fatal_error {
|
||||
print_time();
|
||||
print!("up ???? days ??:??,");
|
||||
print_nusers(0);
|
||||
print_nusers(Some(0))?;
|
||||
print_loadavg();
|
||||
return Ok(());
|
||||
}
|
||||
|
@ -169,10 +175,9 @@ fn uptime_with_file(file_path: &OsString) -> UResult<()> {
|
|||
|
||||
#[cfg(not(target_os = "openbsd"))]
|
||||
{
|
||||
let (boot_time, count) = process_utmpx_from_file(file_path);
|
||||
let (boot_time, count) = process_utmpx(Some(file_path));
|
||||
if let Some(time) = boot_time {
|
||||
let upsecs = get_uptime_from_boot_time(time);
|
||||
print_uptime(upsecs);
|
||||
print_uptime(Some(time))?;
|
||||
} else {
|
||||
show_error!("couldn't get boot time");
|
||||
set_exit_code(1);
|
||||
|
@ -184,20 +189,20 @@ fn uptime_with_file(file_path: &OsString) -> UResult<()> {
|
|||
|
||||
#[cfg(target_os = "openbsd")]
|
||||
{
|
||||
user_count = process_utmp_from_file(file_path.to_str().expect("invalid utmp path file"));
|
||||
user_count = get_nusers(file_path.to_str().expect("invalid utmp path file"));
|
||||
|
||||
let upsecs = get_uptime();
|
||||
let upsecs = get_uptime(None);
|
||||
if upsecs < 0 {
|
||||
show_error!("couldn't get boot time");
|
||||
set_exit_code(1);
|
||||
|
||||
print!("up ???? days ??:??,");
|
||||
} else {
|
||||
print_uptime(upsecs);
|
||||
print_uptime(Some(upsecs))?;
|
||||
}
|
||||
}
|
||||
|
||||
print_nusers(user_count);
|
||||
print_nusers(Some(user_count))?;
|
||||
print_loadavg();
|
||||
|
||||
Ok(())
|
||||
|
@ -205,17 +210,18 @@ fn uptime_with_file(file_path: &OsString) -> UResult<()> {
|
|||
|
||||
/// 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) {
|
||||
#[cfg(unix)]
|
||||
#[cfg(not(target_os = "openbsd"))]
|
||||
let (boot_time, _) = process_utmpx(None);
|
||||
|
||||
#[cfg(target_os = "openbsd")]
|
||||
let uptime = get_uptime(None)?;
|
||||
#[cfg(unix)]
|
||||
#[cfg(not(target_os = "openbsd"))]
|
||||
let uptime = get_uptime(boot_time)?;
|
||||
#[cfg(target_os = "windows")]
|
||||
let uptime = get_uptime(None)?;
|
||||
let initial_date = Local
|
||||
.timestamp_opt(Utc::now().timestamp() - uptime, 0)
|
||||
.unwrap();
|
||||
|
@ -223,209 +229,68 @@ fn default_uptime(matches: &ArgMatches) -> UResult<()> {
|
|||
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_uptime(None)?;
|
||||
print_nusers(None)?;
|
||||
print_loadavg();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[inline]
|
||||
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 { ", " }
|
||||
);
|
||||
}
|
||||
match get_formatted_loadavg() {
|
||||
Err(_) => {}
|
||||
Ok(s) => println!("{}", s),
|
||||
}
|
||||
}
|
||||
|
||||
#[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) {
|
||||
fn process_utmpx(file: Option<&std::ffi::OsString>) -> (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, "),
|
||||
let records = match file {
|
||||
Some(f) => Utmpx::iter_all_records_from(f),
|
||||
None => Utmpx::iter_all_records(),
|
||||
};
|
||||
|
||||
for line in 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)
|
||||
}
|
||||
|
||||
fn print_nusers(nusers: Option<usize>) -> UResult<()> {
|
||||
print!(
|
||||
"{}, ",
|
||||
match nusers {
|
||||
None => {
|
||||
get_formatted_nusers()
|
||||
}
|
||||
Some(nusers) => {
|
||||
format_nusers(nusers)
|
||||
}
|
||||
}
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn print_time() {
|
||||
let local_time = Local::now().time();
|
||||
|
||||
print!(" {} ", local_time.format("%H:%M:%S"));
|
||||
print!(" {} ", get_formatted_time());
|
||||
}
|
||||
|
||||
#[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}, "),
|
||||
};
|
||||
fn print_uptime(boot_time: Option<time_t>) -> UResult<()> {
|
||||
print!("up {}, ", get_formated_uptime(boot_time)?);
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -74,11 +74,16 @@ tempfile = { workspace = true }
|
|||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
winapi-util = { workspace = true, optional = true }
|
||||
windows-sys = { workspace = true, optional = true, default-features = false, features = [
|
||||
"Wdk_System_SystemInformation",
|
||||
"Win32_Storage_FileSystem",
|
||||
"Win32_Foundation",
|
||||
"Win32_System_RemoteDesktop",
|
||||
"Win32_System_WindowsProgramming",
|
||||
] }
|
||||
|
||||
[target.'cfg(target_os = "openbsd")'.dependencies]
|
||||
utmp-classic = { workspace = true, optional = true }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
# * non-default features
|
||||
|
@ -122,3 +127,4 @@ version-cmp = []
|
|||
wide = []
|
||||
custom-tz-fmt = []
|
||||
tty = []
|
||||
uptime = ["libc", "windows-sys", "utmpx", "utmp-classic", "thiserror"]
|
||||
|
|
|
@ -34,6 +34,8 @@ pub mod ringbuffer;
|
|||
pub mod sum;
|
||||
#[cfg(feature = "update-control")]
|
||||
pub mod update_control;
|
||||
#[cfg(feature = "uptime")]
|
||||
pub mod uptime;
|
||||
#[cfg(feature = "version-cmp")]
|
||||
pub mod version_cmp;
|
||||
|
||||
|
|
371
src/uucore/src/lib/features/uptime.rs
Normal file
371
src/uucore/src/lib/features/uptime.rs
Normal file
|
@ -0,0 +1,371 @@
|
|||
// 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 gettime BOOTTIME clockid boottime formated nusers loadavg getloadavg
|
||||
|
||||
//! Provides functions to get system uptime, number of users and load average.
|
||||
|
||||
// The code was originally written in uu_uptime
|
||||
// (https://github.com/uutils/coreutils/blob/main/src/uu/uptime/src/uptime.rs)
|
||||
// but was eventually moved here.
|
||||
// See https://github.com/uutils/coreutils/pull/7289 for discussion.
|
||||
|
||||
use crate::error::{UError, UResult};
|
||||
use chrono::Local;
|
||||
use libc::time_t;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum UptimeError {
|
||||
#[error("could not retrieve system uptime")]
|
||||
SystemUptime,
|
||||
#[error("could not retrieve system load average")]
|
||||
SystemLoadavg,
|
||||
#[error("Windows does not have an equivalent to the load average on Unix-like systems")]
|
||||
WindowsLoadavg,
|
||||
#[error("boot time larger than current time")]
|
||||
BootTime,
|
||||
}
|
||||
|
||||
impl UError for UptimeError {
|
||||
fn code(&self) -> i32 {
|
||||
1
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the formatted time string, e.g. "12:34:56"
|
||||
pub fn get_formatted_time() -> String {
|
||||
Local::now().time().format("%H:%M:%S").to_string()
|
||||
}
|
||||
|
||||
/// Get the system uptime
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// boot_time: Option<time_t> - Manually specify the boot time, or None to try to get it from the system.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Returns a UResult with the uptime in seconds if successful, otherwise an UptimeError.
|
||||
#[cfg(target_os = "openbsd")]
|
||||
pub fn get_uptime(_boot_time: Option<time_t>) -> UResult<i64> {
|
||||
use libc::clock_gettime;
|
||||
use libc::CLOCK_BOOTTIME;
|
||||
|
||||
use libc::c_int;
|
||||
use 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();
|
||||
|
||||
Ok(uptime)
|
||||
} else {
|
||||
Err(UptimeError::SystemUptime)
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the system uptime
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// boot_time: Option<time_t> - Manually specify the boot time, or None to try to get it from the system.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Returns a UResult with the uptime in seconds if successful, otherwise an UptimeError.
|
||||
#[cfg(unix)]
|
||||
#[cfg(not(target_os = "openbsd"))]
|
||||
pub fn get_uptime(boot_time: Option<time_t>) -> UResult<i64> {
|
||||
use crate::utmpx::Utmpx;
|
||||
use libc::BOOT_TIME;
|
||||
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::<i64>().ok());
|
||||
|
||||
if let Some(uptime) = proc_uptime {
|
||||
return Ok(uptime);
|
||||
}
|
||||
|
||||
let boot_time = boot_time.or_else(|| {
|
||||
let records = Utmpx::iter_all_records();
|
||||
for line in records {
|
||||
match line.record_type() {
|
||||
BOOT_TIME => {
|
||||
let dt = line.login_time();
|
||||
if dt.unix_timestamp() > 0 {
|
||||
return Some(dt.unix_timestamp() as time_t);
|
||||
}
|
||||
}
|
||||
_ => continue,
|
||||
}
|
||||
}
|
||||
None
|
||||
});
|
||||
|
||||
if let Some(t) = boot_time {
|
||||
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();
|
||||
if now < boottime {
|
||||
Err(UptimeError::BootTime)?;
|
||||
}
|
||||
return Ok(now - boottime);
|
||||
}
|
||||
|
||||
Err(UptimeError::SystemUptime)?
|
||||
}
|
||||
|
||||
/// Get the system uptime
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Returns a UResult with the uptime in seconds if successful, otherwise an UptimeError.
|
||||
#[cfg(windows)]
|
||||
pub fn get_uptime(_boot_time: Option<time_t>) -> UResult<i64> {
|
||||
use windows_sys::Win32::System::SystemInformation::GetTickCount;
|
||||
let uptime = unsafe { GetTickCount() };
|
||||
if uptime < 0 {
|
||||
Err(UptimeError::SystemUptime)?;
|
||||
}
|
||||
Ok(uptime as i64)
|
||||
}
|
||||
|
||||
/// Get the system uptime in a human-readable format
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// boot_time: Option<time_t> - Manually specify the boot time, or None to try to get it from the system.
|
||||
///
|
||||
/// # 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_formated_uptime(boot_time: Option<time_t>) -> UResult<String> {
|
||||
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}")),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the number of users currently logged in
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Returns the number of users currently logged in if successful, otherwise 0.
|
||||
#[cfg(unix)]
|
||||
#[cfg(not(target_os = "openbsd"))]
|
||||
// see: https://gitlab.com/procps-ng/procps/-/blob/4740a0efa79cade867cfc7b32955fe0f75bf5173/library/uptime.c#L63-L115
|
||||
pub fn get_nusers() -> usize {
|
||||
use crate::utmpx::Utmpx;
|
||||
use libc::USER_PROCESS;
|
||||
|
||||
let mut num_user = 0;
|
||||
Utmpx::iter_all_records().for_each(|ut| {
|
||||
if ut.record_type() == USER_PROCESS {
|
||||
num_user += 1;
|
||||
}
|
||||
});
|
||||
num_user
|
||||
}
|
||||
|
||||
/// Get the number of users currently logged in
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Returns the number of users currently logged in if successful, otherwise 0
|
||||
#[cfg(target_os = "openbsd")]
|
||||
pub fn get_nusers(file: &str) -> usize {
|
||||
use utmp_classic::{parse_from_path, UtmpEntry};
|
||||
|
||||
let mut nusers = 0;
|
||||
|
||||
let entries = match parse_from_path(file) {
|
||||
Some(e) => e,
|
||||
None => return 0,
|
||||
};
|
||||
|
||||
for entry in entries {
|
||||
if let UtmpEntry::UTMP {
|
||||
line: _,
|
||||
user,
|
||||
host: _,
|
||||
time: _,
|
||||
} = entry
|
||||
{
|
||||
if !user.is_empty() {
|
||||
nusers += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
nusers
|
||||
}
|
||||
|
||||
/// Get the number of users currently logged in
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Returns the number of users currently logged in if successful, otherwise 0
|
||||
#[cfg(target_os = "windows")]
|
||||
pub fn get_nusers() -> usize {
|
||||
use std::ptr;
|
||||
use windows_sys::Win32::System::RemoteDesktop::*;
|
||||
|
||||
let mut num_user = 0;
|
||||
|
||||
unsafe {
|
||||
let mut session_info_ptr = ptr::null_mut();
|
||||
let mut session_count = 0;
|
||||
|
||||
let result = WTSEnumerateSessionsW(
|
||||
WTS_CURRENT_SERVER_HANDLE,
|
||||
0,
|
||||
1,
|
||||
&mut session_info_ptr,
|
||||
&mut session_count,
|
||||
);
|
||||
if result == 0 {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let sessions = std::slice::from_raw_parts(session_info_ptr, session_count as usize);
|
||||
|
||||
for session in sessions {
|
||||
let mut buffer: *mut u16 = ptr::null_mut();
|
||||
let mut bytes_returned = 0;
|
||||
|
||||
let result = WTSQuerySessionInformationW(
|
||||
WTS_CURRENT_SERVER_HANDLE,
|
||||
session.SessionId,
|
||||
5,
|
||||
&mut buffer,
|
||||
&mut bytes_returned,
|
||||
);
|
||||
if result == 0 || buffer.is_null() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let username = if !buffer.is_null() {
|
||||
let cstr = std::ffi::CStr::from_ptr(buffer as *const i8);
|
||||
cstr.to_string_lossy().to_string()
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
if !username.is_empty() {
|
||||
num_user += 1;
|
||||
}
|
||||
|
||||
WTSFreeMemory(buffer as _);
|
||||
}
|
||||
|
||||
WTSFreeMemory(session_info_ptr as _);
|
||||
}
|
||||
|
||||
num_user
|
||||
}
|
||||
|
||||
/// Format the number of users to a human-readable string
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// e.g. "0 user", "1 user", "2 users"
|
||||
#[inline]
|
||||
pub fn format_nusers(nusers: usize) -> String {
|
||||
match nusers {
|
||||
0 => "0 user".to_string(),
|
||||
1 => "1 user".to_string(),
|
||||
_ => format!("{} users", nusers),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the number of users currently logged in in a human-readable format
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// e.g. "0 user", "1 user", "2 users"
|
||||
#[inline]
|
||||
pub fn get_formatted_nusers() -> String {
|
||||
#[cfg(not(target_os = "openbsd"))]
|
||||
return format_nusers(get_nusers());
|
||||
|
||||
#[cfg(target_os = "openbsd")]
|
||||
format_nusers(get_nusers("/var/run/utmp"))
|
||||
}
|
||||
|
||||
/// Get the system load average
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// 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)]
|
||||
pub fn get_loadavg() -> UResult<(f64, f64, f64)> {
|
||||
use crate::libc::c_double;
|
||||
use libc::getloadavg;
|
||||
|
||||
let mut avg: [c_double; 3] = [0.0; 3];
|
||||
let loads: i32 = unsafe { getloadavg(avg.as_mut_ptr(), 3) };
|
||||
|
||||
if loads == -1 {
|
||||
Err(UptimeError::SystemLoadavg)?
|
||||
} else {
|
||||
Ok((avg[0], avg[1], avg[2]))
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the system load average
|
||||
/// Windows does not have an equivalent to the load average on Unix-like systems.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Returns a UResult with an UptimeError.
|
||||
#[cfg(windows)]
|
||||
pub fn get_loadavg() -> UResult<(f64, f64, f64)> {
|
||||
Err(UptimeError::WindowsLoadavg)?
|
||||
}
|
||||
|
||||
/// Get the system load average in a human-readable format
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Returns a UResult with the load average in a human-readable format if successful, otherwise an UptimeError.
|
||||
/// e.g. "load average: 0.00, 0.00, 0.00"
|
||||
#[inline]
|
||||
pub fn get_formatted_loadavg() -> UResult<String> {
|
||||
let loadavg = get_loadavg()?;
|
||||
Ok(format!(
|
||||
"load average: {:.2}, {:.2}, {:.2}",
|
||||
loadavg.0, loadavg.1, loadavg.2
|
||||
))
|
||||
}
|
|
@ -66,6 +66,8 @@ pub use crate::features::ringbuffer;
|
|||
pub use crate::features::sum;
|
||||
#[cfg(feature = "update-control")]
|
||||
pub use crate::features::update_control;
|
||||
#[cfg(feature = "uptime")]
|
||||
pub use crate::features::uptime;
|
||||
#[cfg(feature = "version-cmp")]
|
||||
pub use crate::features::version_cmp;
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue