You've already forked util-linux
mirror of
https://github.com/uutils/util-linux.git
synced 2026-06-10 16:13:52 -07:00
Add last Utility (#65)
This commit is contained in:
Generated
+215
-80
File diff suppressed because it is too large
Load Diff
@@ -31,6 +31,7 @@ feat_common_core = [
|
||||
"lsmem",
|
||||
"ctrlaltdel",
|
||||
"rev",
|
||||
"last"
|
||||
]
|
||||
|
||||
[workspace.dependencies]
|
||||
@@ -50,6 +51,7 @@ rand = { version = "0.8", features = ["small_rng"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0.122"
|
||||
tabled = "0.16.0"
|
||||
dns-lookup = "2.0.4"
|
||||
|
||||
[dependencies]
|
||||
clap = { workspace = true }
|
||||
@@ -58,6 +60,7 @@ clap_mangen = { workspace = true }
|
||||
uucore = { workspace = true }
|
||||
phf = { workspace = true }
|
||||
textwrap = { workspace = true }
|
||||
dns-lookup = { workspace = true }
|
||||
|
||||
#
|
||||
lscpu = { optional = true, version = "0.0.1", package = "uu_lscpu", path = "src/uu/lscpu" }
|
||||
@@ -65,6 +68,7 @@ lsmem = { optional = true, version = "0.0.1", package = "uu_lsmem", path = "src/
|
||||
mountpoint = { optional = true, version = "0.0.1", package = "uu_mountpoint", path = "src/uu/mountpoint" }
|
||||
ctrlaltdel = { optional = true, version = "0.0.1", package = "uu_ctrlaltdel", path = "src/uu/ctrlaltdel" }
|
||||
rev = { optional = true, version = "0.0.1", package = "uu_rev", path = "src/uu/rev" }
|
||||
last = { optional = true, version = "0.0.1", package = "uu_last", path = "src/uu/last" }
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = "1"
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "uu_last"
|
||||
version = "0.0.1"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
path = "src/last.rs"
|
||||
|
||||
[dependencies]
|
||||
uucore = { workspace = true, features = ["utmpx"] }
|
||||
clap = { workspace = true}
|
||||
dns-lookup = { workspace = true }
|
||||
@@ -0,0 +1,8 @@
|
||||
# last
|
||||
|
||||
```
|
||||
Usage:
|
||||
[options] [<username>...] [<tty>...]
|
||||
```
|
||||
|
||||
Show a listing of last logged in users.
|
||||
@@ -0,0 +1,94 @@
|
||||
// This file is part of the uutils util-linux package.
|
||||
//
|
||||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
|
||||
use clap::{crate_version, Arg, ArgAction, Command};
|
||||
use uucore::{format_usage, help_about, help_usage};
|
||||
|
||||
mod platform;
|
||||
|
||||
mod options {
|
||||
pub const SYSTEM: &str = "system";
|
||||
pub const HOSTLAST: &str = "hostlast";
|
||||
pub const NO_HOST: &str = "nohostname";
|
||||
pub const LIMIT: &str = "limit";
|
||||
pub const DNS: &str = "dns";
|
||||
pub const TIME_FORMAT: &str = "time-format";
|
||||
pub const USER_TTY: &str = "username";
|
||||
pub const FILE: &str = "file";
|
||||
}
|
||||
|
||||
const ABOUT: &str = help_about!("last.md");
|
||||
const USAGE: &str = help_usage!("last.md");
|
||||
|
||||
#[uucore::main]
|
||||
use platform::uumain;
|
||||
|
||||
pub fn uu_app() -> Command {
|
||||
Command::new(uucore::util_name())
|
||||
.version(crate_version!())
|
||||
.about(ABOUT)
|
||||
.override_usage(format_usage(USAGE))
|
||||
.infer_long_args(true)
|
||||
.arg(
|
||||
Arg::new(options::FILE)
|
||||
.short('f')
|
||||
.long("file")
|
||||
.action(ArgAction::Set)
|
||||
.default_value("/var/log/wtmp")
|
||||
.help("use a specific file instead of /var/log/wtmp")
|
||||
.required(false),
|
||||
)
|
||||
.arg(
|
||||
Arg::new(options::SYSTEM)
|
||||
.short('x')
|
||||
.long(options::SYSTEM)
|
||||
.action(ArgAction::SetTrue)
|
||||
.required(false)
|
||||
.help("display system shutdown entries and run level changes"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new(options::DNS)
|
||||
.short('d')
|
||||
.long(options::DNS)
|
||||
.action(ArgAction::SetTrue)
|
||||
.required(false)
|
||||
.help("translate the IP number back into a hostname"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new(options::HOSTLAST)
|
||||
.short('a')
|
||||
.long(options::HOSTLAST)
|
||||
.action(ArgAction::SetTrue)
|
||||
.required(false)
|
||||
.help("display hostnames in the last column"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new(options::NO_HOST)
|
||||
.short('R')
|
||||
.long(options::NO_HOST)
|
||||
.action(ArgAction::SetTrue)
|
||||
.required(false)
|
||||
.help("don't display the hostname field"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new(options::LIMIT)
|
||||
.short('n')
|
||||
.long(options::LIMIT)
|
||||
.action(ArgAction::Set)
|
||||
.required(false)
|
||||
.help("how many lines to show")
|
||||
.value_parser(clap::value_parser!(i32))
|
||||
.allow_negative_numbers(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::new(options::TIME_FORMAT)
|
||||
.long(options::TIME_FORMAT)
|
||||
.action(ArgAction::Set)
|
||||
.required(false)
|
||||
.help("show timestamps in the specified <format>: notime|short|full|iso")
|
||||
.default_value("short"),
|
||||
)
|
||||
.arg(Arg::new(options::USER_TTY).action(ArgAction::Append))
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
uucore::bin!(uu_last);
|
||||
@@ -0,0 +1,19 @@
|
||||
// This file is part of the uutils util-linux package.
|
||||
//
|
||||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
|
||||
#[cfg(unix)]
|
||||
mod unix;
|
||||
#[cfg(unix)]
|
||||
pub use self::unix::*;
|
||||
|
||||
#[cfg(target_os = "openbsd")]
|
||||
mod openbsd;
|
||||
#[cfg(target_os = "openbsd")]
|
||||
pub use self::openbsd::*;
|
||||
|
||||
#[cfg(windows)]
|
||||
mod windows;
|
||||
#[cfg(windows)]
|
||||
pub use self::windows::*;
|
||||
@@ -0,0 +1,17 @@
|
||||
// This file is part of the uutils util-linux 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(())
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,17 @@
|
||||
// This file is part of the uutils util-linux package.
|
||||
//
|
||||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
|
||||
// Specific implementation for Windows: 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 Windows");
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
// This file is part of the uutils util-linux package.
|
||||
//
|
||||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
// spell-checker:ignore (words) symdir somefakedir
|
||||
|
||||
use crate::common::util::TestScenario;
|
||||
|
||||
use regex::Regex;
|
||||
use std::fs;
|
||||
use std::io::Write;
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn test_invalid_arg() {
|
||||
new_ucmd!().arg("--definitely-invalid").fails().code_is(1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn test_last() {
|
||||
let regex = Regex::new("still running|still logged in").unwrap();
|
||||
TestScenario::new(util_name!())
|
||||
.ucmd()
|
||||
.succeeds()
|
||||
.stdout_matches(®ex);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn test_limit_arg() {
|
||||
let line_check = |input: &str| input.lines().count() == 3;
|
||||
new_ucmd!()
|
||||
.arg("--limit=1")
|
||||
.succeeds()
|
||||
.stdout_str_check(line_check);
|
||||
}
|
||||
|
||||
#[test]
|
||||
// The -x flag generally adds two rows "shutdown" and "runlevel"
|
||||
// "shutdown" cannot be checked for since not every machine will have shutdown
|
||||
// "runlevel" only makes sense for Linux systems, so only Linux is included for
|
||||
// this test.
|
||||
#[cfg(target_os = "linux")]
|
||||
#[ignore = "fails on Arch Linux"]
|
||||
fn test_system_arg() {
|
||||
new_ucmd!().arg("-x").succeeds().stdout_contains("runlevel");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn test_timestamp_format_no_time() {
|
||||
let regex = Regex::new(" [0-9][0-9]:[0-9][0-9] ").unwrap();
|
||||
new_ucmd!()
|
||||
.arg("--time-format=notime")
|
||||
.succeeds()
|
||||
.stdout_does_not_match(®ex);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn test_timestamp_format_short() {
|
||||
let regex = Regex::new(" [0-9][0-9]:[0-9][0-9] ").unwrap();
|
||||
new_ucmd!()
|
||||
.arg("--time-format=short")
|
||||
.succeeds()
|
||||
.stdout_matches(®ex);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn test_timestamp_format_full() {
|
||||
let regex = Regex::new(" [0-9][0-9]:[0-9][0-9]:[0-9][0-9] ").unwrap();
|
||||
new_ucmd!()
|
||||
.arg("--time-format=full")
|
||||
.succeeds()
|
||||
.stdout_matches(®ex);
|
||||
}
|
||||
|
||||
// 2024-07-11T19:30:44+08:00
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn test_timestamp_format_iso() {
|
||||
let regex =
|
||||
Regex::new(" [0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]T[0-9][0-9]:[0-9][0-9]:[0-9][0-9]")
|
||||
.unwrap();
|
||||
new_ucmd!()
|
||||
.arg("--time-format=iso")
|
||||
.succeeds()
|
||||
.stdout_matches(®ex);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn test_short_invalid_utmp_file() {
|
||||
let filepath = "/tmp/testfile";
|
||||
let testfile = fs::File::create(filepath);
|
||||
// Random bytes
|
||||
let data: Vec<u8> = vec![
|
||||
4, 5, 6, 16, 8, 13, 2, 12, 5, 3, 11, 5, 1, 13, 1, 1, 0, 9, 5, 5, 2, 8, 4,
|
||||
];
|
||||
let _ = testfile.unwrap().write_all(&data);
|
||||
|
||||
let regex = Regex::new(r"\n\S*\sbegins\s*(Mon|Tue|Wed|Thu|Fri|Sat|Sun)\s*(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s*[0-9][0-9]?\s*[0-9][0-9]:[0-9][0-9]:[0-9][0-9]\s*[0-9]*")
|
||||
.unwrap();
|
||||
|
||||
new_ucmd!()
|
||||
.arg(format!("--file={filepath}"))
|
||||
.succeeds()
|
||||
.stdout_matches(®ex);
|
||||
}
|
||||
@@ -24,3 +24,7 @@ mod test_ctrlaltdel;
|
||||
#[cfg(feature = "rev")]
|
||||
#[path = "by-util/test_rev.rs"]
|
||||
mod test_rev;
|
||||
|
||||
#[cfg(feature = "last")]
|
||||
#[path = "by-util/test_last.rs"]
|
||||
mod test_last;
|
||||
|
||||
Reference in New Issue
Block a user