Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,7 @@ feat_os_unix_musl = [
"feat_require_unix_utmpx",
]
# "feat_os_windows" == set of utilities which can be built/run on modern windows platforms
feat_os_windows = ["feat_Tier1", "kill", "stdbuf", "timeout"]
feat_os_windows = ["feat_Tier1", "kill", "stdbuf", "timeout", "uptime"]
## (secondary platforms) feature sets
# "feat_os_unix_gnueabihf" == set of utilities which can be built/run on the "arm-unknown-linux-gnueabihf" target (ARMv6 Linux [hardfloat])
feat_os_unix_gnueabihf = [
Expand Down
21 changes: 21 additions & 0 deletions src/uu/uptime/src/platform/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// 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.

//! Platform-specific pieces of `uptime`: the utmp-file operand (which only
//! exists where utmp files do) and the boot-time source for `--since`. The
//! shared control flow in `uptime.rs` only talks to the facade functions
//! re-exported here, which both submodules provide with identical
//! signatures; on Windows the utmp-file facades degenerate to
//! identity/`None`.

#[cfg(unix)]
mod unix;
#[cfg(unix)]
pub(crate) use unix::*;

#[cfg(windows)]
mod windows;
#[cfg(windows)]
pub(crate) use windows::*;
175 changes: 175 additions & 0 deletions src/uu/uptime/src/platform/unix.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
// 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 loadavg nusers upsecs utmpxname couldnt

//! Unix implementation of `uptime`'s platform facade: the utmp-file operand
//! and the utmpx-derived boot time for `--since` (on OpenBSD, which has no
//! utmpx binding, boot time comes from [`get_uptime`] directly).

use clap::{Arg, ArgAction, ArgMatches, Command, ValueHint, builder::ValueParser};
use std::ffi::OsString;
use std::io::{Write, stdout};
use uucore::error::UResult;
#[cfg(not(target_os = "openbsd"))]
use uucore::libc::time_t;
use uucore::translate;
use uucore::uptime::get_uptime;
#[cfg(not(target_os = "openbsd"))]
use uucore::utmpx::{BOOT_TIME, USER_PROCESS, Utmpx};

use crate::{UptimeError, options, print_loadavg, print_nusers, print_time, print_uptime};

/// Register platform-only CLI arguments: the utmp file operand, which only
/// makes sense where utmp files exist.
pub(crate) fn add_platform_args(cmd: Command) -> Command {
cmd.arg(
Arg::new(options::PATH)
.help(translate!("uptime-help-path"))
.action(ArgAction::Set)
.num_args(0..=1)
.value_parser(ValueParser::os_string())
.value_hint(ValueHint::AnyPath),
)
}

/// Run `uptime` against a user-supplied utmp file if the file operand was
/// given, or return `None` to fall through to the default system sources.
pub(crate) fn maybe_uptime_from_file(matches: &ArgMatches) -> Option<UResult<()>> {
matches
.get_one::<OsString>(options::PATH)
.map(uptime_with_file)
}

/// The system uptime in seconds, for `--since`: derived from the utmpx
/// `BOOT_TIME` record where utmpx is available (on OpenBSD, from
/// [`get_uptime`] directly).
pub(crate) fn system_uptime_seconds() -> UResult<i64> {
#[cfg(not(target_os = "openbsd"))]
{
let (boot_time, _) = process_utmpx(None);
get_uptime(boot_time)
}
#[cfg(target_os = "openbsd")]
get_uptime(None)
}

fn uptime_with_file(file_path: &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;

// 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!("{}", translate!("uptime-error-couldnt-get-boot-time"));
print_time()?;
write!(stdout(), "{}", translate!("uptime-output-unknown-uptime"))?;
print_nusers(Some(0))?;
print_loadavg()?;
set_exit_code(1);
return Ok(());
}
}

if non_fatal_error {
print_time()?;
write!(stdout(), "{}", translate!("uptime-output-unknown-uptime"))?;
print_nusers(Some(0))?;
print_loadavg()?;
return Ok(());
}

print_time()?;
let user_count;

#[cfg(not(target_os = "openbsd"))]
{
let (boot_time, count) = process_utmpx(Some(file_path));
if let Some(time) = boot_time {
print_uptime(Some(time))?;
} else {
show_error!("{}", translate!("uptime-error-couldnt-get-boot-time"));
set_exit_code(1);

write!(stdout(), "{}", translate!("uptime-output-unknown-uptime"))?;
}
user_count = count;
}

#[cfg(target_os = "openbsd")]
{
let upsecs = get_uptime(None)?;
if upsecs >= 0 {
print_uptime(Some(upsecs))?;
} else {
show_error!("{}", translate!("uptime-error-couldnt-get-boot-time"));
set_exit_code(1);

write!(stdout(), "{}", translate!("uptime-output-unknown-uptime"))?;
}
user_count =
uucore::uptime::get_nusers(file_path.to_str().expect("invalid utmp path file"));
}

print_nusers(Some(user_count))?;
print_loadavg()?;

Ok(())
}

#[cfg(not(target_os = "openbsd"))]
fn process_utmpx(file: Option<&OsString>) -> (Option<time_t>, usize) {
let mut nusers = 0;
let mut boot_time = None;

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() {
x if x == USER_PROCESS => nusers += 1,
x if x == BOOT_TIME => {
let dt = line.login_time();
if dt.unix_timestamp() > 0 {
boot_time = Some(dt.unix_timestamp() as time_t);
}
}
_ => (),
}
}
(boot_time, nusers)
}
28 changes: 28 additions & 0 deletions src/uu/uptime/src/platform/windows.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// 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.

//! Windows implementation of `uptime`'s platform facade. There are no utmp
//! files on Windows, so the file-operand facades degenerate, and `--since`
//! reads the uptime directly from [`uucore::uptime::get_uptime`]
//! (`GetTickCount64`).

use clap::{ArgMatches, Command};
use uucore::error::UResult;
use uucore::uptime::get_uptime;

/// No platform-only CLI arguments on Windows (no utmp files).
pub(crate) fn add_platform_args(cmd: Command) -> Command {
cmd
}

/// The utmp file operand does not exist on Windows; never handled here.
pub(crate) fn maybe_uptime_from_file(_matches: &ArgMatches) -> Option<UResult<()>> {
None
}

/// The system uptime in seconds, for `--since`.
pub(crate) fn system_uptime_seconds() -> UResult<i64> {
get_uptime(None)
}
Loading
Loading