mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-30 04:27:45 +00:00
Merge pull request #5620 from lcheylus/openbsd-utmpx
Support unix feature on OpenBSD (utmpx not supported)
This commit is contained in:
commit
35ae43e71f
21 changed files with 1083 additions and 895 deletions
|
@ -251,7 +251,7 @@ fn set_main_group(group: &str) -> UResult<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(any(target_vendor = "apple", target_os = "freebsd"))]
|
||||
#[cfg(any(target_vendor = "apple", target_os = "freebsd", target_os = "openbsd"))]
|
||||
fn set_groups(groups: &[libc::gid_t]) -> libc::c_int {
|
||||
unsafe { setgroups(groups.len() as libc::c_int, groups.as_ptr()) }
|
||||
}
|
||||
|
|
|
@ -507,7 +507,7 @@ fn pline(possible_uid: Option<uid_t>) {
|
|||
);
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
#[cfg(any(target_os = "linux", target_os = "android", target_os = "openbsd"))]
|
||||
fn pline(possible_uid: Option<uid_t>) {
|
||||
let uid = possible_uid.unwrap_or_else(getuid);
|
||||
let pw = Passwd::locate(uid).unwrap();
|
||||
|
@ -524,10 +524,10 @@ fn pline(possible_uid: Option<uid_t>) {
|
|||
);
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
#[cfg(any(target_os = "linux", target_os = "android", target_os = "openbsd"))]
|
||||
fn auditid() {}
|
||||
|
||||
#[cfg(not(any(target_os = "linux", target_os = "android")))]
|
||||
#[cfg(not(any(target_os = "linux", target_os = "android", target_os = "openbsd")))]
|
||||
fn auditid() {
|
||||
use std::mem::MaybeUninit;
|
||||
|
||||
|
@ -624,7 +624,7 @@ fn id_print(state: &State, groups: &[u32]) {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "linux", target_os = "android")))]
|
||||
#[cfg(not(any(target_os = "linux", target_os = "android", target_os = "openbsd")))]
|
||||
mod audit {
|
||||
use super::libc::{c_int, c_uint, dev_t, pid_t, uid_t};
|
||||
|
||||
|
|
|
@ -198,7 +198,12 @@ extern "C" {
|
|||
fn _vprocmgr_detach_from_console(flags: u32) -> *const libc::c_int;
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "android", target_os = "freebsd"))]
|
||||
#[cfg(any(
|
||||
target_os = "linux",
|
||||
target_os = "android",
|
||||
target_os = "freebsd",
|
||||
target_os = "openbsd"
|
||||
))]
|
||||
unsafe fn _vprocmgr_detach_from_console(_: u32) -> *const libc::c_int {
|
||||
std::ptr::null()
|
||||
}
|
||||
|
|
|
@ -5,21 +5,11 @@
|
|||
|
||||
// spell-checker:ignore (ToDO) BUFSIZE gecos fullname, mesg iobuf
|
||||
|
||||
use uucore::entries::{Locate, Passwd};
|
||||
use uucore::error::{FromIo, UResult};
|
||||
use uucore::libc::S_IWGRP;
|
||||
use uucore::utmpx::{self, time, Utmpx};
|
||||
|
||||
use std::io::prelude::*;
|
||||
use std::io::BufReader;
|
||||
|
||||
use std::fs::File;
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
|
||||
use clap::{crate_version, Arg, ArgAction, Command};
|
||||
use std::path::PathBuf;
|
||||
use uucore::{format_usage, help_about, help_usage};
|
||||
|
||||
mod platform;
|
||||
|
||||
const ABOUT: &str = help_about!("pinky.md");
|
||||
const USAGE: &str = help_usage!("pinky.md");
|
||||
|
||||
|
@ -37,86 +27,8 @@ mod options {
|
|||
pub const HELP: &str = "help";
|
||||
}
|
||||
|
||||
fn get_long_usage() -> String {
|
||||
format!(
|
||||
"A lightweight 'finger' program; print user information.\n\
|
||||
The utmp file will be {}.",
|
||||
utmpx::DEFAULT_FILE
|
||||
)
|
||||
}
|
||||
|
||||
#[uucore::main]
|
||||
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||
let matches = uu_app()
|
||||
.after_help(get_long_usage())
|
||||
.try_get_matches_from(args)?;
|
||||
|
||||
let users: Vec<String> = matches
|
||||
.get_many::<String>(options::USER)
|
||||
.map(|v| v.map(ToString::to_string).collect())
|
||||
.unwrap_or_default();
|
||||
|
||||
// If true, display the hours:minutes since each user has touched
|
||||
// the keyboard, or blank if within the last minute, or days followed
|
||||
// by a 'd' if not within the last day.
|
||||
let mut include_idle = true;
|
||||
|
||||
// If true, display a line at the top describing each field.
|
||||
let include_heading = !matches.get_flag(options::OMIT_HEADINGS);
|
||||
|
||||
// if true, display the user's full name from pw_gecos.
|
||||
let mut include_fullname = true;
|
||||
|
||||
// if true, display the user's ~/.project file when doing long format.
|
||||
let include_project = !matches.get_flag(options::OMIT_PROJECT_FILE);
|
||||
|
||||
// if true, display the user's ~/.plan file when doing long format.
|
||||
let include_plan = !matches.get_flag(options::OMIT_PLAN_FILE);
|
||||
|
||||
// if true, display the user's home directory and shell
|
||||
// when doing long format.
|
||||
let include_home_and_shell = !matches.get_flag(options::OMIT_HOME_DIR);
|
||||
|
||||
// if true, use the "short" output format.
|
||||
let do_short_format = !matches.get_flag(options::LONG_FORMAT);
|
||||
|
||||
/* if true, display the ut_host field. */
|
||||
let mut include_where = true;
|
||||
|
||||
if matches.get_flag(options::OMIT_NAME) {
|
||||
include_fullname = false;
|
||||
}
|
||||
if matches.get_flag(options::OMIT_NAME_HOST) {
|
||||
include_fullname = false;
|
||||
include_where = false;
|
||||
}
|
||||
if matches.get_flag(options::OMIT_NAME_HOST_TIME) {
|
||||
include_fullname = false;
|
||||
include_idle = false;
|
||||
include_where = false;
|
||||
}
|
||||
|
||||
let pk = Pinky {
|
||||
include_idle,
|
||||
include_heading,
|
||||
include_fullname,
|
||||
include_project,
|
||||
include_plan,
|
||||
include_home_and_shell,
|
||||
include_where,
|
||||
names: users,
|
||||
};
|
||||
|
||||
if do_short_format {
|
||||
match pk.short_pinky() {
|
||||
Ok(_) => Ok(()),
|
||||
Err(e) => Err(e.map_err_context(String::new)),
|
||||
}
|
||||
} else {
|
||||
pk.long_pinky();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
use platform::uumain;
|
||||
|
||||
pub fn uu_app() -> Command {
|
||||
Command::new(uucore::util_name())
|
||||
|
@ -195,17 +107,6 @@ pub fn uu_app() -> Command {
|
|||
)
|
||||
}
|
||||
|
||||
struct Pinky {
|
||||
include_idle: bool,
|
||||
include_heading: bool,
|
||||
include_fullname: bool,
|
||||
include_project: bool,
|
||||
include_plan: bool,
|
||||
include_where: bool,
|
||||
include_home_and_shell: bool,
|
||||
names: Vec<String>,
|
||||
}
|
||||
|
||||
pub trait Capitalize {
|
||||
fn capitalize(&self) -> String;
|
||||
}
|
||||
|
@ -223,180 +124,3 @@ impl Capitalize for str {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn idle_string(when: i64) -> String {
|
||||
thread_local! {
|
||||
static NOW: time::OffsetDateTime = time::OffsetDateTime::now_local().unwrap();
|
||||
}
|
||||
NOW.with(|n| {
|
||||
let duration = n.unix_timestamp() - when;
|
||||
if duration < 60 {
|
||||
// less than 1min
|
||||
" ".to_owned()
|
||||
} else if duration < 24 * 3600 {
|
||||
// less than 1day
|
||||
let hours = duration / (60 * 60);
|
||||
let minutes = (duration % (60 * 60)) / 60;
|
||||
format!("{hours:02}:{minutes:02}")
|
||||
} else {
|
||||
// more than 1day
|
||||
let days = duration / (24 * 3600);
|
||||
format!("{days}d")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn time_string(ut: &Utmpx) -> String {
|
||||
// "%b %e %H:%M"
|
||||
let time_format: Vec<time::format_description::FormatItem> =
|
||||
time::format_description::parse("[month repr:short] [day padding:space] [hour]:[minute]")
|
||||
.unwrap();
|
||||
ut.login_time().format(&time_format).unwrap() // LC_ALL=C
|
||||
}
|
||||
|
||||
fn gecos_to_fullname(pw: &Passwd) -> Option<String> {
|
||||
let mut gecos = if let Some(gecos) = &pw.user_info {
|
||||
gecos.clone()
|
||||
} else {
|
||||
return None;
|
||||
};
|
||||
if let Some(n) = gecos.find(',') {
|
||||
gecos.truncate(n);
|
||||
}
|
||||
Some(gecos.replace('&', &pw.name.capitalize()))
|
||||
}
|
||||
|
||||
impl Pinky {
|
||||
fn print_entry(&self, ut: &Utmpx) -> std::io::Result<()> {
|
||||
let mut pts_path = PathBuf::from("/dev");
|
||||
pts_path.push(ut.tty_device().as_str());
|
||||
|
||||
let mesg;
|
||||
let last_change;
|
||||
|
||||
match pts_path.metadata() {
|
||||
#[allow(clippy::unnecessary_cast)]
|
||||
Ok(meta) => {
|
||||
mesg = if meta.mode() & S_IWGRP as u32 == 0 {
|
||||
'*'
|
||||
} else {
|
||||
' '
|
||||
};
|
||||
last_change = meta.atime();
|
||||
}
|
||||
_ => {
|
||||
mesg = '?';
|
||||
last_change = 0;
|
||||
}
|
||||
}
|
||||
|
||||
print!("{1:<8.0$}", utmpx::UT_NAMESIZE, ut.user());
|
||||
|
||||
if self.include_fullname {
|
||||
let fullname = if let Ok(pw) = Passwd::locate(ut.user().as_ref()) {
|
||||
gecos_to_fullname(&pw)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
if let Some(fullname) = fullname {
|
||||
print!(" {fullname:<19.19}");
|
||||
} else {
|
||||
print!(" {:19}", " ???");
|
||||
}
|
||||
}
|
||||
|
||||
print!(" {}{:<8.*}", mesg, utmpx::UT_LINESIZE, ut.tty_device());
|
||||
|
||||
if self.include_idle {
|
||||
if last_change == 0 {
|
||||
print!(" {:<6}", "?????");
|
||||
} else {
|
||||
print!(" {:<6}", idle_string(last_change));
|
||||
}
|
||||
}
|
||||
|
||||
print!(" {}", time_string(ut));
|
||||
|
||||
let mut s = ut.host();
|
||||
if self.include_where && !s.is_empty() {
|
||||
s = ut.canon_host()?;
|
||||
print!(" {s}");
|
||||
}
|
||||
|
||||
println!();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn print_heading(&self) {
|
||||
print!("{:<8}", "Login");
|
||||
if self.include_fullname {
|
||||
print!(" {:<19}", "Name");
|
||||
}
|
||||
print!(" {:<9}", " TTY");
|
||||
if self.include_idle {
|
||||
print!(" {:<6}", "Idle");
|
||||
}
|
||||
print!(" {:<16}", "When");
|
||||
if self.include_where {
|
||||
print!(" Where");
|
||||
}
|
||||
println!();
|
||||
}
|
||||
|
||||
fn short_pinky(&self) -> std::io::Result<()> {
|
||||
if self.include_heading {
|
||||
self.print_heading();
|
||||
}
|
||||
for ut in Utmpx::iter_all_records() {
|
||||
if ut.is_user_process()
|
||||
&& (self.names.is_empty() || self.names.iter().any(|n| n.as_str() == ut.user()))
|
||||
{
|
||||
self.print_entry(&ut)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn long_pinky(&self) {
|
||||
for u in &self.names {
|
||||
print!("Login name: {u:<28}In real life: ");
|
||||
if let Ok(pw) = Passwd::locate(u.as_str()) {
|
||||
let fullname = gecos_to_fullname(&pw).unwrap_or_default();
|
||||
let user_dir = pw.user_dir.unwrap_or_default();
|
||||
let user_shell = pw.user_shell.unwrap_or_default();
|
||||
println!(" {fullname}");
|
||||
if self.include_home_and_shell {
|
||||
print!("Directory: {user_dir:<29}");
|
||||
println!("Shell: {user_shell}");
|
||||
}
|
||||
if self.include_project {
|
||||
let mut p = PathBuf::from(&user_dir);
|
||||
p.push(".project");
|
||||
if let Ok(f) = File::open(p) {
|
||||
print!("Project: ");
|
||||
read_to_console(f);
|
||||
}
|
||||
}
|
||||
if self.include_plan {
|
||||
let mut p = PathBuf::from(&user_dir);
|
||||
p.push(".plan");
|
||||
if let Ok(f) = File::open(p) {
|
||||
println!("Plan:");
|
||||
read_to_console(f);
|
||||
}
|
||||
}
|
||||
println!();
|
||||
} else {
|
||||
println!(" ???");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn read_to_console<F: Read>(f: F) {
|
||||
let mut reader = BufReader::new(f);
|
||||
let mut iobuf = Vec::new();
|
||||
if reader.read_to_end(&mut iobuf).is_ok() {
|
||||
print!("{}", String::from_utf8_lossy(&iobuf));
|
||||
}
|
||||
}
|
||||
|
|
14
src/uu/pinky/src/platform/mod.rs
Normal file
14
src/uu/pinky/src/platform/mod.rs
Normal file
|
@ -0,0 +1,14 @@
|
|||
// 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::*;
|
17
src/uu/pinky/src/platform/openbsd.rs
Normal file
17
src/uu/pinky/src/platform/openbsd.rs
Normal file
|
@ -0,0 +1,17 @@
|
|||
// 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(())
|
||||
}
|
291
src/uu/pinky/src/platform/unix.rs
Normal file
291
src/uu/pinky/src/platform/unix.rs
Normal file
|
@ -0,0 +1,291 @@
|
|||
// 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 (ToDO) BUFSIZE gecos fullname, mesg iobuf
|
||||
|
||||
use crate::options;
|
||||
use crate::uu_app;
|
||||
use crate::Capitalize;
|
||||
|
||||
use uucore::entries::{Locate, Passwd};
|
||||
use uucore::error::{FromIo, UResult};
|
||||
use uucore::libc::S_IWGRP;
|
||||
use uucore::utmpx::{self, time, Utmpx};
|
||||
|
||||
use std::io::prelude::*;
|
||||
use std::io::BufReader;
|
||||
|
||||
use std::fs::File;
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn get_long_usage() -> String {
|
||||
format!(
|
||||
"A lightweight 'finger' program; print user information.\n\
|
||||
The utmp file will be {}.",
|
||||
utmpx::DEFAULT_FILE
|
||||
)
|
||||
}
|
||||
|
||||
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||
let matches = uu_app()
|
||||
.after_help(get_long_usage())
|
||||
.try_get_matches_from(args)?;
|
||||
|
||||
let users: Vec<String> = matches
|
||||
.get_many::<String>(options::USER)
|
||||
.map(|v| v.map(ToString::to_string).collect())
|
||||
.unwrap_or_default();
|
||||
|
||||
// If true, display the hours:minutes since each user has touched
|
||||
// the keyboard, or blank if within the last minute, or days followed
|
||||
// by a 'd' if not within the last day.
|
||||
let mut include_idle = true;
|
||||
|
||||
// If true, display a line at the top describing each field.
|
||||
let include_heading = !matches.get_flag(options::OMIT_HEADINGS);
|
||||
|
||||
// if true, display the user's full name from pw_gecos.
|
||||
let mut include_fullname = true;
|
||||
|
||||
// if true, display the user's ~/.project file when doing long format.
|
||||
let include_project = !matches.get_flag(options::OMIT_PROJECT_FILE);
|
||||
|
||||
// if true, display the user's ~/.plan file when doing long format.
|
||||
let include_plan = !matches.get_flag(options::OMIT_PLAN_FILE);
|
||||
|
||||
// if true, display the user's home directory and shell
|
||||
// when doing long format.
|
||||
let include_home_and_shell = !matches.get_flag(options::OMIT_HOME_DIR);
|
||||
|
||||
// if true, use the "short" output format.
|
||||
let do_short_format = !matches.get_flag(options::LONG_FORMAT);
|
||||
|
||||
/* if true, display the ut_host field. */
|
||||
let mut include_where = true;
|
||||
|
||||
if matches.get_flag(options::OMIT_NAME) {
|
||||
include_fullname = false;
|
||||
}
|
||||
if matches.get_flag(options::OMIT_NAME_HOST) {
|
||||
include_fullname = false;
|
||||
include_where = false;
|
||||
}
|
||||
if matches.get_flag(options::OMIT_NAME_HOST_TIME) {
|
||||
include_fullname = false;
|
||||
include_idle = false;
|
||||
include_where = false;
|
||||
}
|
||||
|
||||
let pk = Pinky {
|
||||
include_idle,
|
||||
include_heading,
|
||||
include_fullname,
|
||||
include_project,
|
||||
include_plan,
|
||||
include_home_and_shell,
|
||||
include_where,
|
||||
names: users,
|
||||
};
|
||||
|
||||
if do_short_format {
|
||||
match pk.short_pinky() {
|
||||
Ok(_) => Ok(()),
|
||||
Err(e) => Err(e.map_err_context(String::new)),
|
||||
}
|
||||
} else {
|
||||
pk.long_pinky();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
struct Pinky {
|
||||
include_idle: bool,
|
||||
include_heading: bool,
|
||||
include_fullname: bool,
|
||||
include_project: bool,
|
||||
include_plan: bool,
|
||||
include_where: bool,
|
||||
include_home_and_shell: bool,
|
||||
names: Vec<String>,
|
||||
}
|
||||
|
||||
fn idle_string(when: i64) -> String {
|
||||
thread_local! {
|
||||
static NOW: time::OffsetDateTime = time::OffsetDateTime::now_local().unwrap();
|
||||
}
|
||||
NOW.with(|n| {
|
||||
let duration = n.unix_timestamp() - when;
|
||||
if duration < 60 {
|
||||
// less than 1min
|
||||
" ".to_owned()
|
||||
} else if duration < 24 * 3600 {
|
||||
// less than 1day
|
||||
let hours = duration / (60 * 60);
|
||||
let minutes = (duration % (60 * 60)) / 60;
|
||||
format!("{hours:02}:{minutes:02}")
|
||||
} else {
|
||||
// more than 1day
|
||||
let days = duration / (24 * 3600);
|
||||
format!("{days}d")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn time_string(ut: &Utmpx) -> String {
|
||||
// "%b %e %H:%M"
|
||||
let time_format: Vec<time::format_description::FormatItem> =
|
||||
time::format_description::parse("[month repr:short] [day padding:space] [hour]:[minute]")
|
||||
.unwrap();
|
||||
ut.login_time().format(&time_format).unwrap() // LC_ALL=C
|
||||
}
|
||||
|
||||
fn gecos_to_fullname(pw: &Passwd) -> Option<String> {
|
||||
let mut gecos = if let Some(gecos) = &pw.user_info {
|
||||
gecos.clone()
|
||||
} else {
|
||||
return None;
|
||||
};
|
||||
if let Some(n) = gecos.find(',') {
|
||||
gecos.truncate(n);
|
||||
}
|
||||
Some(gecos.replace('&', &pw.name.capitalize()))
|
||||
}
|
||||
|
||||
impl Pinky {
|
||||
fn print_entry(&self, ut: &Utmpx) -> std::io::Result<()> {
|
||||
let mut pts_path = PathBuf::from("/dev");
|
||||
pts_path.push(ut.tty_device().as_str());
|
||||
|
||||
let mesg;
|
||||
let last_change;
|
||||
|
||||
match pts_path.metadata() {
|
||||
#[allow(clippy::unnecessary_cast)]
|
||||
Ok(meta) => {
|
||||
mesg = if meta.mode() & S_IWGRP as u32 == 0 {
|
||||
'*'
|
||||
} else {
|
||||
' '
|
||||
};
|
||||
last_change = meta.atime();
|
||||
}
|
||||
_ => {
|
||||
mesg = '?';
|
||||
last_change = 0;
|
||||
}
|
||||
}
|
||||
|
||||
print!("{1:<8.0$}", utmpx::UT_NAMESIZE, ut.user());
|
||||
|
||||
if self.include_fullname {
|
||||
let fullname = if let Ok(pw) = Passwd::locate(ut.user().as_ref()) {
|
||||
gecos_to_fullname(&pw)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
if let Some(fullname) = fullname {
|
||||
print!(" {fullname:<19.19}");
|
||||
} else {
|
||||
print!(" {:19}", " ???");
|
||||
}
|
||||
}
|
||||
|
||||
print!(" {}{:<8.*}", mesg, utmpx::UT_LINESIZE, ut.tty_device());
|
||||
|
||||
if self.include_idle {
|
||||
if last_change == 0 {
|
||||
print!(" {:<6}", "?????");
|
||||
} else {
|
||||
print!(" {:<6}", idle_string(last_change));
|
||||
}
|
||||
}
|
||||
|
||||
print!(" {}", time_string(ut));
|
||||
|
||||
let mut s = ut.host();
|
||||
if self.include_where && !s.is_empty() {
|
||||
s = ut.canon_host()?;
|
||||
print!(" {s}");
|
||||
}
|
||||
|
||||
println!();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn print_heading(&self) {
|
||||
print!("{:<8}", "Login");
|
||||
if self.include_fullname {
|
||||
print!(" {:<19}", "Name");
|
||||
}
|
||||
print!(" {:<9}", " TTY");
|
||||
if self.include_idle {
|
||||
print!(" {:<6}", "Idle");
|
||||
}
|
||||
print!(" {:<16}", "When");
|
||||
if self.include_where {
|
||||
print!(" Where");
|
||||
}
|
||||
println!();
|
||||
}
|
||||
|
||||
fn short_pinky(&self) -> std::io::Result<()> {
|
||||
if self.include_heading {
|
||||
self.print_heading();
|
||||
}
|
||||
for ut in Utmpx::iter_all_records() {
|
||||
if ut.is_user_process()
|
||||
&& (self.names.is_empty() || self.names.iter().any(|n| n.as_str() == ut.user()))
|
||||
{
|
||||
self.print_entry(&ut)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn long_pinky(&self) {
|
||||
for u in &self.names {
|
||||
print!("Login name: {u:<28}In real life: ");
|
||||
if let Ok(pw) = Passwd::locate(u.as_str()) {
|
||||
let fullname = gecos_to_fullname(&pw).unwrap_or_default();
|
||||
let user_dir = pw.user_dir.unwrap_or_default();
|
||||
let user_shell = pw.user_shell.unwrap_or_default();
|
||||
println!(" {fullname}");
|
||||
if self.include_home_and_shell {
|
||||
print!("Directory: {user_dir:<29}");
|
||||
println!("Shell: {user_shell}");
|
||||
}
|
||||
if self.include_project {
|
||||
let mut p = PathBuf::from(&user_dir);
|
||||
p.push(".project");
|
||||
if let Ok(f) = File::open(p) {
|
||||
print!("Project: ");
|
||||
read_to_console(f);
|
||||
}
|
||||
}
|
||||
if self.include_plan {
|
||||
let mut p = PathBuf::from(&user_dir);
|
||||
p.push(".plan");
|
||||
if let Ok(f) = File::open(p) {
|
||||
println!("Plan:");
|
||||
read_to_console(f);
|
||||
}
|
||||
}
|
||||
println!();
|
||||
} else {
|
||||
println!(" ???");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn read_to_console<F: Read>(f: F) {
|
||||
let mut reader = BufReader::new(f);
|
||||
let mut iobuf = Vec::new();
|
||||
if reader.read_to_end(&mut iobuf).is_ok() {
|
||||
print!("{}", String::from_utf8_lossy(&iobuf));
|
||||
}
|
||||
}
|
14
src/uu/uptime/src/platform/mod.rs
Normal file
14
src/uu/uptime/src/platform/mod.rs
Normal file
|
@ -0,0 +1,14 @@
|
|||
// 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::*;
|
17
src/uu/uptime/src/platform/openbsd.rs
Normal file
17
src/uu/uptime/src/platform/openbsd.rs
Normal file
|
@ -0,0 +1,17 @@
|
|||
// 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(())
|
||||
}
|
161
src/uu/uptime/src/platform/unix.rs
Normal file
161
src/uu/uptime/src/platform/unix.rs
Normal file
|
@ -0,0 +1,161 @@
|
|||
// 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 (ToDO) getloadavg upsecs updays nusers loadavg boottime uphours upmins
|
||||
|
||||
use crate::options;
|
||||
use crate::uu_app;
|
||||
|
||||
use chrono::{Local, TimeZone, Utc};
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||
let matches = uu_app().try_get_matches_from(args)?;
|
||||
|
||||
let (boot_time, user_count) = process_utmpx();
|
||||
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) {
|
||||
let initial_date = Local
|
||||
.timestamp_opt(Utc::now().timestamp() - uptime, 0)
|
||||
.unwrap();
|
||||
println!("{}", initial_date.format("%Y-%m-%d %H:%M:%S"));
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
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(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::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(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,15 +3,11 @@
|
|||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
|
||||
// spell-checker:ignore (ToDO) getloadavg upsecs updays nusers loadavg boottime uphours upmins
|
||||
|
||||
use chrono::{Local, TimeZone, Utc};
|
||||
use clap::{crate_version, Arg, ArgAction, Command};
|
||||
|
||||
use uucore::libc::time_t;
|
||||
use uucore::{format_usage, help_about, help_usage};
|
||||
|
||||
use uucore::error::{UResult, USimpleError};
|
||||
mod platform;
|
||||
|
||||
const ABOUT: &str = help_about!("uptime.md");
|
||||
const USAGE: &str = help_usage!("uptime.md");
|
||||
|
@ -19,40 +15,8 @@ pub mod options {
|
|||
pub static SINCE: &str = "since";
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
use uucore::libc::getloadavg;
|
||||
|
||||
#[cfg(windows)]
|
||||
extern "C" {
|
||||
fn GetTickCount() -> uucore::libc::uint32_t;
|
||||
}
|
||||
|
||||
#[uucore::main]
|
||||
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||
let matches = uu_app().try_get_matches_from(args)?;
|
||||
|
||||
let (boot_time, user_count) = process_utmpx();
|
||||
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) {
|
||||
let initial_date = Local
|
||||
.timestamp_opt(Utc::now().timestamp() - uptime, 0)
|
||||
.unwrap();
|
||||
println!("{}", initial_date.format("%Y-%m-%d %H:%M:%S"));
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
print_time();
|
||||
let upsecs = uptime;
|
||||
print_uptime(upsecs);
|
||||
print_nusers(user_count);
|
||||
print_loadavg();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
use platform::uumain;
|
||||
|
||||
pub fn uu_app() -> Command {
|
||||
Command::new(uucore::util_name())
|
||||
|
@ -68,115 +32,3 @@ pub fn uu_app() -> Command {
|
|||
.action(ArgAction::SetTrue),
|
||||
)
|
||||
}
|
||||
|
||||
#[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(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::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(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}, "),
|
||||
};
|
||||
}
|
||||
|
|
14
src/uu/users/src/platform/mod.rs
Normal file
14
src/uu/users/src/platform/mod.rs
Normal file
|
@ -0,0 +1,14 @@
|
|||
// 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::*;
|
17
src/uu/users/src/platform/openbsd.rs
Normal file
17
src/uu/users/src/platform/openbsd.rs
Normal file
|
@ -0,0 +1,17 @@
|
|||
// 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(())
|
||||
}
|
53
src/uu/users/src/platform/unix.rs
Normal file
53
src/uu/users/src/platform/unix.rs
Normal file
|
@ -0,0 +1,53 @@
|
|||
// 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 (paths) wtmp
|
||||
|
||||
use crate::uu_app;
|
||||
|
||||
use std::ffi::OsString;
|
||||
use std::path::Path;
|
||||
|
||||
use uucore::error::UResult;
|
||||
use uucore::utmpx::{self, Utmpx};
|
||||
|
||||
static ARG_FILES: &str = "files";
|
||||
|
||||
fn get_long_usage() -> String {
|
||||
format!(
|
||||
"Output who is currently logged in according to FILE.
|
||||
If FILE is not specified, use {}. /var/log/wtmp as FILE is common.",
|
||||
utmpx::DEFAULT_FILE
|
||||
)
|
||||
}
|
||||
|
||||
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||
let matches = uu_app()
|
||||
.after_help(get_long_usage())
|
||||
.try_get_matches_from(args)?;
|
||||
|
||||
let files: Vec<&Path> = matches
|
||||
.get_many::<OsString>(ARG_FILES)
|
||||
.map(|v| v.map(AsRef::as_ref).collect())
|
||||
.unwrap_or_default();
|
||||
|
||||
let filename = if files.is_empty() {
|
||||
utmpx::DEFAULT_FILE.as_ref()
|
||||
} else {
|
||||
files[0]
|
||||
};
|
||||
|
||||
let mut users = Utmpx::iter_all_records_from(filename)
|
||||
.filter(Utmpx::is_user_process)
|
||||
.map(|ut| ut.user())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if !users.is_empty() {
|
||||
users.sort();
|
||||
println!("{}", users.join(" "));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -5,57 +5,19 @@
|
|||
|
||||
// spell-checker:ignore (paths) wtmp
|
||||
|
||||
use std::ffi::OsString;
|
||||
use std::path::Path;
|
||||
|
||||
use clap::builder::ValueParser;
|
||||
use clap::{crate_version, Arg, Command};
|
||||
use uucore::error::UResult;
|
||||
use uucore::utmpx::{self, Utmpx};
|
||||
use uucore::{format_usage, help_about, help_usage};
|
||||
|
||||
mod platform;
|
||||
|
||||
const ABOUT: &str = help_about!("users.md");
|
||||
const USAGE: &str = help_usage!("users.md");
|
||||
|
||||
static ARG_FILES: &str = "files";
|
||||
|
||||
fn get_long_usage() -> String {
|
||||
format!(
|
||||
"Output who is currently logged in according to FILE.
|
||||
If FILE is not specified, use {}. /var/log/wtmp as FILE is common.",
|
||||
utmpx::DEFAULT_FILE
|
||||
)
|
||||
}
|
||||
|
||||
#[uucore::main]
|
||||
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||
let matches = uu_app()
|
||||
.after_help(get_long_usage())
|
||||
.try_get_matches_from(args)?;
|
||||
|
||||
let files: Vec<&Path> = matches
|
||||
.get_many::<OsString>(ARG_FILES)
|
||||
.map(|v| v.map(AsRef::as_ref).collect())
|
||||
.unwrap_or_default();
|
||||
|
||||
let filename = if files.is_empty() {
|
||||
utmpx::DEFAULT_FILE.as_ref()
|
||||
} else {
|
||||
files[0]
|
||||
};
|
||||
|
||||
let mut users = Utmpx::iter_all_records_from(filename)
|
||||
.filter(Utmpx::is_user_process)
|
||||
.map(|ut| ut.user())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if !users.is_empty() {
|
||||
users.sort();
|
||||
println!("{}", users.join(" "));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
use platform::uumain;
|
||||
|
||||
pub fn uu_app() -> Command {
|
||||
Command::new(uucore::util_name())
|
||||
|
|
14
src/uu/who/src/platform/mod.rs
Normal file
14
src/uu/who/src/platform/mod.rs
Normal file
|
@ -0,0 +1,14 @@
|
|||
// 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::*;
|
17
src/uu/who/src/platform/openbsd.rs
Normal file
17
src/uu/who/src/platform/openbsd.rs
Normal file
|
@ -0,0 +1,17 @@
|
|||
// 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(())
|
||||
}
|
430
src/uu/who/src/platform/unix.rs
Normal file
430
src/uu/who/src/platform/unix.rs
Normal file
|
@ -0,0 +1,430 @@
|
|||
// 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 (ToDO) ttyname hostnames runlevel mesg wtmp statted boottime deadprocs initspawn clockchange curr runlvline pidstr exitstr hoststr
|
||||
|
||||
use crate::options;
|
||||
use crate::uu_app;
|
||||
|
||||
use uucore::display::Quotable;
|
||||
use uucore::error::{FromIo, UResult};
|
||||
use uucore::libc::{ttyname, STDIN_FILENO, S_IWGRP};
|
||||
use uucore::utmpx::{self, time, Utmpx};
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::ffi::CStr;
|
||||
use std::fmt::Write;
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn get_long_usage() -> String {
|
||||
format!(
|
||||
"If FILE is not specified, use {}. /var/log/wtmp as FILE is common.\n\
|
||||
If ARG1 ARG2 given, -m presumed: 'am i' or 'mom likes' are usual.",
|
||||
utmpx::DEFAULT_FILE,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||
let matches = uu_app()
|
||||
.after_help(get_long_usage())
|
||||
.try_get_matches_from(args)?;
|
||||
|
||||
let files: Vec<String> = matches
|
||||
.get_many::<String>(options::FILE)
|
||||
.map(|v| v.map(ToString::to_string).collect())
|
||||
.unwrap_or_default();
|
||||
|
||||
// If true, attempt to canonicalize hostnames via a DNS lookup.
|
||||
let do_lookup = matches.get_flag(options::LOOKUP);
|
||||
|
||||
// If true, display only a list of usernames and count of
|
||||
// the users logged on.
|
||||
// Ignored for 'who am i'.
|
||||
let short_list = matches.get_flag(options::COUNT);
|
||||
|
||||
let all = matches.get_flag(options::ALL);
|
||||
|
||||
// If true, display a line at the top describing each field.
|
||||
let include_heading = matches.get_flag(options::HEADING);
|
||||
|
||||
// If true, display a '+' for each user if mesg y, a '-' if mesg n,
|
||||
// or a '?' if their tty cannot be statted.
|
||||
let include_mesg = all || matches.get_flag(options::MESG);
|
||||
|
||||
// If true, display the last boot time.
|
||||
let need_boottime = all || matches.get_flag(options::BOOT);
|
||||
|
||||
// If true, display dead processes.
|
||||
let need_deadprocs = all || matches.get_flag(options::DEAD);
|
||||
|
||||
// If true, display processes waiting for user login.
|
||||
let need_login = all || matches.get_flag(options::LOGIN);
|
||||
|
||||
// If true, display processes started by init.
|
||||
let need_initspawn = all || matches.get_flag(options::PROCESS);
|
||||
|
||||
// If true, display the last clock change.
|
||||
let need_clockchange = all || matches.get_flag(options::TIME);
|
||||
|
||||
// If true, display the current runlevel.
|
||||
let need_runlevel = all || matches.get_flag(options::RUNLEVEL);
|
||||
|
||||
let use_defaults = !(all
|
||||
|| need_boottime
|
||||
|| need_deadprocs
|
||||
|| need_login
|
||||
|| need_initspawn
|
||||
|| need_runlevel
|
||||
|| need_clockchange
|
||||
|| matches.get_flag(options::USERS));
|
||||
|
||||
// If true, display user processes.
|
||||
let need_users = all || matches.get_flag(options::USERS) || use_defaults;
|
||||
|
||||
// If true, display the hours:minutes since each user has touched
|
||||
// the keyboard, or "." if within the last minute, or "old" if
|
||||
// not within the last day.
|
||||
let include_idle = need_deadprocs || need_login || need_runlevel || need_users;
|
||||
|
||||
// If true, display process termination & exit status.
|
||||
let include_exit = need_deadprocs;
|
||||
|
||||
// If true, display only name, line, and time fields.
|
||||
let short_output = !include_exit && use_defaults;
|
||||
|
||||
// If true, display info only for the controlling tty.
|
||||
let my_line_only = matches.get_flag(options::ONLY_HOSTNAME_USER) || files.len() == 2;
|
||||
|
||||
let mut who = Who {
|
||||
do_lookup,
|
||||
short_list,
|
||||
short_output,
|
||||
include_idle,
|
||||
include_heading,
|
||||
include_mesg,
|
||||
include_exit,
|
||||
need_boottime,
|
||||
need_deadprocs,
|
||||
need_login,
|
||||
need_initspawn,
|
||||
need_clockchange,
|
||||
need_runlevel,
|
||||
need_users,
|
||||
my_line_only,
|
||||
args: files,
|
||||
};
|
||||
|
||||
who.exec()
|
||||
}
|
||||
|
||||
struct Who {
|
||||
do_lookup: bool,
|
||||
short_list: bool,
|
||||
short_output: bool,
|
||||
include_idle: bool,
|
||||
include_heading: bool,
|
||||
include_mesg: bool,
|
||||
include_exit: bool,
|
||||
need_boottime: bool,
|
||||
need_deadprocs: bool,
|
||||
need_login: bool,
|
||||
need_initspawn: bool,
|
||||
need_clockchange: bool,
|
||||
need_runlevel: bool,
|
||||
need_users: bool,
|
||||
my_line_only: bool,
|
||||
args: Vec<String>,
|
||||
}
|
||||
|
||||
fn idle_string<'a>(when: i64, boottime: i64) -> Cow<'a, str> {
|
||||
thread_local! {
|
||||
static NOW: time::OffsetDateTime = time::OffsetDateTime::now_local().unwrap();
|
||||
}
|
||||
NOW.with(|n| {
|
||||
let now = n.unix_timestamp();
|
||||
if boottime < when && now - 24 * 3600 < when && when <= now {
|
||||
let seconds_idle = now - when;
|
||||
if seconds_idle < 60 {
|
||||
" . ".into()
|
||||
} else {
|
||||
format!(
|
||||
"{:02}:{:02}",
|
||||
seconds_idle / 3600,
|
||||
(seconds_idle % 3600) / 60
|
||||
)
|
||||
.into()
|
||||
}
|
||||
} else {
|
||||
" old ".into()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn time_string(ut: &Utmpx) -> String {
|
||||
// "%b %e %H:%M"
|
||||
let time_format: Vec<time::format_description::FormatItem> =
|
||||
time::format_description::parse("[month repr:short] [day padding:space] [hour]:[minute]")
|
||||
.unwrap();
|
||||
ut.login_time().format(&time_format).unwrap() // LC_ALL=C
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn current_tty() -> String {
|
||||
unsafe {
|
||||
let res = ttyname(STDIN_FILENO);
|
||||
if res.is_null() {
|
||||
String::new()
|
||||
} else {
|
||||
CStr::from_ptr(res as *const _)
|
||||
.to_string_lossy()
|
||||
.trim_start_matches("/dev/")
|
||||
.to_owned()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Who {
|
||||
#[allow(clippy::cognitive_complexity)]
|
||||
fn exec(&mut self) -> UResult<()> {
|
||||
let run_level_chk = |_record: i16| {
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
return false;
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
return _record == utmpx::RUN_LVL;
|
||||
};
|
||||
|
||||
let f = if self.args.len() == 1 {
|
||||
self.args[0].as_ref()
|
||||
} else {
|
||||
utmpx::DEFAULT_FILE
|
||||
};
|
||||
if self.short_list {
|
||||
let users = Utmpx::iter_all_records_from(f)
|
||||
.filter(Utmpx::is_user_process)
|
||||
.map(|ut| ut.user())
|
||||
.collect::<Vec<_>>();
|
||||
println!("{}", users.join(" "));
|
||||
println!("# users={}", users.len());
|
||||
} else {
|
||||
let records = Utmpx::iter_all_records_from(f);
|
||||
|
||||
if self.include_heading {
|
||||
self.print_heading();
|
||||
}
|
||||
let cur_tty = if self.my_line_only {
|
||||
current_tty()
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
|
||||
for ut in records {
|
||||
if !self.my_line_only || cur_tty == ut.tty_device() {
|
||||
if self.need_users && ut.is_user_process() {
|
||||
self.print_user(&ut)?;
|
||||
} else if self.need_runlevel && run_level_chk(ut.record_type()) {
|
||||
if cfg!(target_os = "linux") {
|
||||
self.print_runlevel(&ut);
|
||||
}
|
||||
} else if self.need_boottime && ut.record_type() == utmpx::BOOT_TIME {
|
||||
self.print_boottime(&ut);
|
||||
} else if self.need_clockchange && ut.record_type() == utmpx::NEW_TIME {
|
||||
self.print_clockchange(&ut);
|
||||
} else if self.need_initspawn && ut.record_type() == utmpx::INIT_PROCESS {
|
||||
self.print_initspawn(&ut);
|
||||
} else if self.need_login && ut.record_type() == utmpx::LOGIN_PROCESS {
|
||||
self.print_login(&ut);
|
||||
} else if self.need_deadprocs && ut.record_type() == utmpx::DEAD_PROCESS {
|
||||
self.print_deadprocs(&ut);
|
||||
}
|
||||
}
|
||||
|
||||
if ut.record_type() == utmpx::BOOT_TIME {}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn print_runlevel(&self, ut: &Utmpx) {
|
||||
let last = (ut.pid() / 256) as u8 as char;
|
||||
let curr = (ut.pid() % 256) as u8 as char;
|
||||
let runlvline = format!("run-level {curr}");
|
||||
let comment = format!("last={}", if last == 'N' { 'S' } else { 'N' });
|
||||
|
||||
self.print_line(
|
||||
"",
|
||||
' ',
|
||||
&runlvline,
|
||||
&time_string(ut),
|
||||
"",
|
||||
"",
|
||||
if last.is_control() { "" } else { &comment },
|
||||
"",
|
||||
);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn print_clockchange(&self, ut: &Utmpx) {
|
||||
self.print_line("", ' ', "clock change", &time_string(ut), "", "", "", "");
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn print_login(&self, ut: &Utmpx) {
|
||||
let comment = format!("id={}", ut.terminal_suffix());
|
||||
let pidstr = format!("{}", ut.pid());
|
||||
self.print_line(
|
||||
"LOGIN",
|
||||
' ',
|
||||
&ut.tty_device(),
|
||||
&time_string(ut),
|
||||
"",
|
||||
&pidstr,
|
||||
&comment,
|
||||
"",
|
||||
);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn print_deadprocs(&self, ut: &Utmpx) {
|
||||
let comment = format!("id={}", ut.terminal_suffix());
|
||||
let pidstr = format!("{}", ut.pid());
|
||||
let e = ut.exit_status();
|
||||
let exitstr = format!("term={} exit={}", e.0, e.1);
|
||||
self.print_line(
|
||||
"",
|
||||
' ',
|
||||
&ut.tty_device(),
|
||||
&time_string(ut),
|
||||
"",
|
||||
&pidstr,
|
||||
&comment,
|
||||
&exitstr,
|
||||
);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn print_initspawn(&self, ut: &Utmpx) {
|
||||
let comment = format!("id={}", ut.terminal_suffix());
|
||||
let pidstr = format!("{}", ut.pid());
|
||||
self.print_line(
|
||||
"",
|
||||
' ',
|
||||
&ut.tty_device(),
|
||||
&time_string(ut),
|
||||
"",
|
||||
&pidstr,
|
||||
&comment,
|
||||
"",
|
||||
);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn print_boottime(&self, ut: &Utmpx) {
|
||||
self.print_line("", ' ', "system boot", &time_string(ut), "", "", "", "");
|
||||
}
|
||||
|
||||
fn print_user(&self, ut: &Utmpx) -> UResult<()> {
|
||||
let mut p = PathBuf::from("/dev");
|
||||
p.push(ut.tty_device().as_str());
|
||||
let mesg;
|
||||
let last_change;
|
||||
match p.metadata() {
|
||||
Ok(meta) => {
|
||||
#[cfg(all(
|
||||
not(target_os = "android"),
|
||||
not(target_os = "freebsd"),
|
||||
not(target_vendor = "apple")
|
||||
))]
|
||||
let iwgrp = S_IWGRP;
|
||||
#[cfg(any(target_os = "android", target_os = "freebsd", target_vendor = "apple"))]
|
||||
let iwgrp = S_IWGRP as u32;
|
||||
mesg = if meta.mode() & iwgrp == 0 { '-' } else { '+' };
|
||||
last_change = meta.atime();
|
||||
}
|
||||
_ => {
|
||||
mesg = '?';
|
||||
last_change = 0;
|
||||
}
|
||||
}
|
||||
|
||||
let idle = if last_change == 0 {
|
||||
" ?".into()
|
||||
} else {
|
||||
idle_string(last_change, 0)
|
||||
};
|
||||
|
||||
let s = if self.do_lookup {
|
||||
ut.canon_host().map_err_context(|| {
|
||||
let host = ut.host();
|
||||
format!(
|
||||
"failed to canonicalize {}",
|
||||
host.split(':').next().unwrap_or(&host).quote()
|
||||
)
|
||||
})?
|
||||
} else {
|
||||
ut.host()
|
||||
};
|
||||
let hoststr = if s.is_empty() { s } else { format!("({s})") };
|
||||
|
||||
self.print_line(
|
||||
ut.user().as_ref(),
|
||||
mesg,
|
||||
ut.tty_device().as_ref(),
|
||||
time_string(ut).as_str(),
|
||||
idle.as_ref(),
|
||||
format!("{}", ut.pid()).as_str(),
|
||||
hoststr.as_str(),
|
||||
"",
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn print_line(
|
||||
&self,
|
||||
user: &str,
|
||||
state: char,
|
||||
line: &str,
|
||||
time: &str,
|
||||
idle: &str,
|
||||
pid: &str,
|
||||
comment: &str,
|
||||
exit: &str,
|
||||
) {
|
||||
let mut buf = String::with_capacity(64);
|
||||
let msg = vec![' ', state].into_iter().collect::<String>();
|
||||
|
||||
write!(buf, "{user:<8}").unwrap();
|
||||
if self.include_mesg {
|
||||
buf.push_str(&msg);
|
||||
}
|
||||
write!(buf, " {line:<12}").unwrap();
|
||||
// "%b %e %H:%M" (LC_ALL=C)
|
||||
let time_size = 3 + 2 + 2 + 1 + 2;
|
||||
write!(buf, " {time:<time_size$}").unwrap();
|
||||
|
||||
if !self.short_output {
|
||||
if self.include_idle {
|
||||
write!(buf, " {idle:<6}").unwrap();
|
||||
}
|
||||
write!(buf, " {pid:>10}").unwrap();
|
||||
}
|
||||
write!(buf, " {comment:<8}").unwrap();
|
||||
if self.include_exit {
|
||||
write!(buf, " {exit:<12}").unwrap();
|
||||
}
|
||||
println!("{}", buf.trim_end());
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn print_heading(&self) {
|
||||
self.print_line(
|
||||
"NAME", ' ', "LINE", "TIME", "IDLE", "PID", "COMMENT", "EXIT",
|
||||
);
|
||||
}
|
||||
}
|
|
@ -5,19 +5,11 @@
|
|||
|
||||
// spell-checker:ignore (ToDO) ttyname hostnames runlevel mesg wtmp statted boottime deadprocs initspawn clockchange curr runlvline pidstr exitstr hoststr
|
||||
|
||||
use uucore::display::Quotable;
|
||||
use uucore::error::{FromIo, UResult};
|
||||
use uucore::libc::{ttyname, STDIN_FILENO, S_IWGRP};
|
||||
use uucore::utmpx::{self, time, Utmpx};
|
||||
|
||||
use clap::{crate_version, Arg, ArgAction, Command};
|
||||
use std::borrow::Cow;
|
||||
use std::ffi::CStr;
|
||||
use std::fmt::Write;
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
use std::path::PathBuf;
|
||||
use uucore::{format_usage, help_about, help_usage};
|
||||
|
||||
mod platform;
|
||||
|
||||
mod options {
|
||||
pub const ALL: &str = "all";
|
||||
pub const BOOT: &str = "boot";
|
||||
|
@ -44,107 +36,8 @@ static RUNLEVEL_HELP: &str = "print current runlevel";
|
|||
#[cfg(not(target_os = "linux"))]
|
||||
static RUNLEVEL_HELP: &str = "print current runlevel (This is meaningless on non Linux)";
|
||||
|
||||
fn get_long_usage() -> String {
|
||||
format!(
|
||||
"If FILE is not specified, use {}. /var/log/wtmp as FILE is common.\n\
|
||||
If ARG1 ARG2 given, -m presumed: 'am i' or 'mom likes' are usual.",
|
||||
utmpx::DEFAULT_FILE,
|
||||
)
|
||||
}
|
||||
|
||||
#[uucore::main]
|
||||
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||
let matches = uu_app()
|
||||
.after_help(get_long_usage())
|
||||
.try_get_matches_from(args)?;
|
||||
|
||||
let files: Vec<String> = matches
|
||||
.get_many::<String>(options::FILE)
|
||||
.map(|v| v.map(ToString::to_string).collect())
|
||||
.unwrap_or_default();
|
||||
|
||||
// If true, attempt to canonicalize hostnames via a DNS lookup.
|
||||
let do_lookup = matches.get_flag(options::LOOKUP);
|
||||
|
||||
// If true, display only a list of usernames and count of
|
||||
// the users logged on.
|
||||
// Ignored for 'who am i'.
|
||||
let short_list = matches.get_flag(options::COUNT);
|
||||
|
||||
let all = matches.get_flag(options::ALL);
|
||||
|
||||
// If true, display a line at the top describing each field.
|
||||
let include_heading = matches.get_flag(options::HEADING);
|
||||
|
||||
// If true, display a '+' for each user if mesg y, a '-' if mesg n,
|
||||
// or a '?' if their tty cannot be statted.
|
||||
let include_mesg = all || matches.get_flag(options::MESG);
|
||||
|
||||
// If true, display the last boot time.
|
||||
let need_boottime = all || matches.get_flag(options::BOOT);
|
||||
|
||||
// If true, display dead processes.
|
||||
let need_deadprocs = all || matches.get_flag(options::DEAD);
|
||||
|
||||
// If true, display processes waiting for user login.
|
||||
let need_login = all || matches.get_flag(options::LOGIN);
|
||||
|
||||
// If true, display processes started by init.
|
||||
let need_initspawn = all || matches.get_flag(options::PROCESS);
|
||||
|
||||
// If true, display the last clock change.
|
||||
let need_clockchange = all || matches.get_flag(options::TIME);
|
||||
|
||||
// If true, display the current runlevel.
|
||||
let need_runlevel = all || matches.get_flag(options::RUNLEVEL);
|
||||
|
||||
let use_defaults = !(all
|
||||
|| need_boottime
|
||||
|| need_deadprocs
|
||||
|| need_login
|
||||
|| need_initspawn
|
||||
|| need_runlevel
|
||||
|| need_clockchange
|
||||
|| matches.get_flag(options::USERS));
|
||||
|
||||
// If true, display user processes.
|
||||
let need_users = all || matches.get_flag(options::USERS) || use_defaults;
|
||||
|
||||
// If true, display the hours:minutes since each user has touched
|
||||
// the keyboard, or "." if within the last minute, or "old" if
|
||||
// not within the last day.
|
||||
let include_idle = need_deadprocs || need_login || need_runlevel || need_users;
|
||||
|
||||
// If true, display process termination & exit status.
|
||||
let include_exit = need_deadprocs;
|
||||
|
||||
// If true, display only name, line, and time fields.
|
||||
let short_output = !include_exit && use_defaults;
|
||||
|
||||
// If true, display info only for the controlling tty.
|
||||
let my_line_only = matches.get_flag(options::ONLY_HOSTNAME_USER) || files.len() == 2;
|
||||
|
||||
let mut who = Who {
|
||||
do_lookup,
|
||||
short_list,
|
||||
short_output,
|
||||
include_idle,
|
||||
include_heading,
|
||||
include_mesg,
|
||||
include_exit,
|
||||
need_boottime,
|
||||
need_deadprocs,
|
||||
need_login,
|
||||
need_initspawn,
|
||||
need_clockchange,
|
||||
need_runlevel,
|
||||
need_users,
|
||||
my_line_only,
|
||||
args: files,
|
||||
};
|
||||
|
||||
who.exec()
|
||||
}
|
||||
use platform::uumain;
|
||||
|
||||
pub fn uu_app() -> Command {
|
||||
Command::new(uucore::util_name())
|
||||
|
@ -256,312 +149,3 @@ pub fn uu_app() -> Command {
|
|||
.value_hint(clap::ValueHint::FilePath),
|
||||
)
|
||||
}
|
||||
|
||||
struct Who {
|
||||
do_lookup: bool,
|
||||
short_list: bool,
|
||||
short_output: bool,
|
||||
include_idle: bool,
|
||||
include_heading: bool,
|
||||
include_mesg: bool,
|
||||
include_exit: bool,
|
||||
need_boottime: bool,
|
||||
need_deadprocs: bool,
|
||||
need_login: bool,
|
||||
need_initspawn: bool,
|
||||
need_clockchange: bool,
|
||||
need_runlevel: bool,
|
||||
need_users: bool,
|
||||
my_line_only: bool,
|
||||
args: Vec<String>,
|
||||
}
|
||||
|
||||
fn idle_string<'a>(when: i64, boottime: i64) -> Cow<'a, str> {
|
||||
thread_local! {
|
||||
static NOW: time::OffsetDateTime = time::OffsetDateTime::now_local().unwrap();
|
||||
}
|
||||
NOW.with(|n| {
|
||||
let now = n.unix_timestamp();
|
||||
if boottime < when && now - 24 * 3600 < when && when <= now {
|
||||
let seconds_idle = now - when;
|
||||
if seconds_idle < 60 {
|
||||
" . ".into()
|
||||
} else {
|
||||
format!(
|
||||
"{:02}:{:02}",
|
||||
seconds_idle / 3600,
|
||||
(seconds_idle % 3600) / 60
|
||||
)
|
||||
.into()
|
||||
}
|
||||
} else {
|
||||
" old ".into()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn time_string(ut: &Utmpx) -> String {
|
||||
// "%b %e %H:%M"
|
||||
let time_format: Vec<time::format_description::FormatItem> =
|
||||
time::format_description::parse("[month repr:short] [day padding:space] [hour]:[minute]")
|
||||
.unwrap();
|
||||
ut.login_time().format(&time_format).unwrap() // LC_ALL=C
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn current_tty() -> String {
|
||||
unsafe {
|
||||
let res = ttyname(STDIN_FILENO);
|
||||
if res.is_null() {
|
||||
String::new()
|
||||
} else {
|
||||
CStr::from_ptr(res as *const _)
|
||||
.to_string_lossy()
|
||||
.trim_start_matches("/dev/")
|
||||
.to_owned()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Who {
|
||||
#[allow(clippy::cognitive_complexity)]
|
||||
fn exec(&mut self) -> UResult<()> {
|
||||
let run_level_chk = |_record: i16| {
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
return false;
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
return _record == utmpx::RUN_LVL;
|
||||
};
|
||||
|
||||
let f = if self.args.len() == 1 {
|
||||
self.args[0].as_ref()
|
||||
} else {
|
||||
utmpx::DEFAULT_FILE
|
||||
};
|
||||
if self.short_list {
|
||||
let users = Utmpx::iter_all_records_from(f)
|
||||
.filter(Utmpx::is_user_process)
|
||||
.map(|ut| ut.user())
|
||||
.collect::<Vec<_>>();
|
||||
println!("{}", users.join(" "));
|
||||
println!("# users={}", users.len());
|
||||
} else {
|
||||
let records = Utmpx::iter_all_records_from(f);
|
||||
|
||||
if self.include_heading {
|
||||
self.print_heading();
|
||||
}
|
||||
let cur_tty = if self.my_line_only {
|
||||
current_tty()
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
|
||||
for ut in records {
|
||||
if !self.my_line_only || cur_tty == ut.tty_device() {
|
||||
if self.need_users && ut.is_user_process() {
|
||||
self.print_user(&ut)?;
|
||||
} else if self.need_runlevel && run_level_chk(ut.record_type()) {
|
||||
if cfg!(target_os = "linux") {
|
||||
self.print_runlevel(&ut);
|
||||
}
|
||||
} else if self.need_boottime && ut.record_type() == utmpx::BOOT_TIME {
|
||||
self.print_boottime(&ut);
|
||||
} else if self.need_clockchange && ut.record_type() == utmpx::NEW_TIME {
|
||||
self.print_clockchange(&ut);
|
||||
} else if self.need_initspawn && ut.record_type() == utmpx::INIT_PROCESS {
|
||||
self.print_initspawn(&ut);
|
||||
} else if self.need_login && ut.record_type() == utmpx::LOGIN_PROCESS {
|
||||
self.print_login(&ut);
|
||||
} else if self.need_deadprocs && ut.record_type() == utmpx::DEAD_PROCESS {
|
||||
self.print_deadprocs(&ut);
|
||||
}
|
||||
}
|
||||
|
||||
if ut.record_type() == utmpx::BOOT_TIME {}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn print_runlevel(&self, ut: &Utmpx) {
|
||||
let last = (ut.pid() / 256) as u8 as char;
|
||||
let curr = (ut.pid() % 256) as u8 as char;
|
||||
let runlvline = format!("run-level {curr}");
|
||||
let comment = format!("last={}", if last == 'N' { 'S' } else { 'N' });
|
||||
|
||||
self.print_line(
|
||||
"",
|
||||
' ',
|
||||
&runlvline,
|
||||
&time_string(ut),
|
||||
"",
|
||||
"",
|
||||
if last.is_control() { "" } else { &comment },
|
||||
"",
|
||||
);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn print_clockchange(&self, ut: &Utmpx) {
|
||||
self.print_line("", ' ', "clock change", &time_string(ut), "", "", "", "");
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn print_login(&self, ut: &Utmpx) {
|
||||
let comment = format!("id={}", ut.terminal_suffix());
|
||||
let pidstr = format!("{}", ut.pid());
|
||||
self.print_line(
|
||||
"LOGIN",
|
||||
' ',
|
||||
&ut.tty_device(),
|
||||
&time_string(ut),
|
||||
"",
|
||||
&pidstr,
|
||||
&comment,
|
||||
"",
|
||||
);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn print_deadprocs(&self, ut: &Utmpx) {
|
||||
let comment = format!("id={}", ut.terminal_suffix());
|
||||
let pidstr = format!("{}", ut.pid());
|
||||
let e = ut.exit_status();
|
||||
let exitstr = format!("term={} exit={}", e.0, e.1);
|
||||
self.print_line(
|
||||
"",
|
||||
' ',
|
||||
&ut.tty_device(),
|
||||
&time_string(ut),
|
||||
"",
|
||||
&pidstr,
|
||||
&comment,
|
||||
&exitstr,
|
||||
);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn print_initspawn(&self, ut: &Utmpx) {
|
||||
let comment = format!("id={}", ut.terminal_suffix());
|
||||
let pidstr = format!("{}", ut.pid());
|
||||
self.print_line(
|
||||
"",
|
||||
' ',
|
||||
&ut.tty_device(),
|
||||
&time_string(ut),
|
||||
"",
|
||||
&pidstr,
|
||||
&comment,
|
||||
"",
|
||||
);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn print_boottime(&self, ut: &Utmpx) {
|
||||
self.print_line("", ' ', "system boot", &time_string(ut), "", "", "", "");
|
||||
}
|
||||
|
||||
fn print_user(&self, ut: &Utmpx) -> UResult<()> {
|
||||
let mut p = PathBuf::from("/dev");
|
||||
p.push(ut.tty_device().as_str());
|
||||
let mesg;
|
||||
let last_change;
|
||||
match p.metadata() {
|
||||
Ok(meta) => {
|
||||
#[cfg(all(
|
||||
not(target_os = "android"),
|
||||
not(target_os = "freebsd"),
|
||||
not(target_vendor = "apple")
|
||||
))]
|
||||
let iwgrp = S_IWGRP;
|
||||
#[cfg(any(target_os = "android", target_os = "freebsd", target_vendor = "apple"))]
|
||||
let iwgrp = S_IWGRP as u32;
|
||||
mesg = if meta.mode() & iwgrp == 0 { '-' } else { '+' };
|
||||
last_change = meta.atime();
|
||||
}
|
||||
_ => {
|
||||
mesg = '?';
|
||||
last_change = 0;
|
||||
}
|
||||
}
|
||||
|
||||
let idle = if last_change == 0 {
|
||||
" ?".into()
|
||||
} else {
|
||||
idle_string(last_change, 0)
|
||||
};
|
||||
|
||||
let s = if self.do_lookup {
|
||||
ut.canon_host().map_err_context(|| {
|
||||
let host = ut.host();
|
||||
format!(
|
||||
"failed to canonicalize {}",
|
||||
host.split(':').next().unwrap_or(&host).quote()
|
||||
)
|
||||
})?
|
||||
} else {
|
||||
ut.host()
|
||||
};
|
||||
let hoststr = if s.is_empty() { s } else { format!("({s})") };
|
||||
|
||||
self.print_line(
|
||||
ut.user().as_ref(),
|
||||
mesg,
|
||||
ut.tty_device().as_ref(),
|
||||
time_string(ut).as_str(),
|
||||
idle.as_ref(),
|
||||
format!("{}", ut.pid()).as_str(),
|
||||
hoststr.as_str(),
|
||||
"",
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn print_line(
|
||||
&self,
|
||||
user: &str,
|
||||
state: char,
|
||||
line: &str,
|
||||
time: &str,
|
||||
idle: &str,
|
||||
pid: &str,
|
||||
comment: &str,
|
||||
exit: &str,
|
||||
) {
|
||||
let mut buf = String::with_capacity(64);
|
||||
let msg = vec![' ', state].into_iter().collect::<String>();
|
||||
|
||||
write!(buf, "{user:<8}").unwrap();
|
||||
if self.include_mesg {
|
||||
buf.push_str(&msg);
|
||||
}
|
||||
write!(buf, " {line:<12}").unwrap();
|
||||
// "%b %e %H:%M" (LC_ALL=C)
|
||||
let time_size = 3 + 2 + 2 + 1 + 2;
|
||||
write!(buf, " {time:<time_size$}").unwrap();
|
||||
|
||||
if !self.short_output {
|
||||
if self.include_idle {
|
||||
write!(buf, " {idle:<6}").unwrap();
|
||||
}
|
||||
write!(buf, " {pid:>10}").unwrap();
|
||||
}
|
||||
write!(buf, " {comment:<8}").unwrap();
|
||||
if self.include_exit {
|
||||
write!(buf, " {exit:<12}").unwrap();
|
||||
}
|
||||
println!("{}", buf.trim_end());
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn print_heading(&self) {
|
||||
self.print_line(
|
||||
"NAME", ' ', "LINE", "TIME", "IDLE", "PID", "COMMENT", "EXIT",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -52,6 +52,7 @@ pub mod signals;
|
|||
unix,
|
||||
not(target_os = "android"),
|
||||
not(target_os = "fuchsia"),
|
||||
not(target_os = "openbsd"),
|
||||
not(target_os = "redox"),
|
||||
not(target_env = "musl"),
|
||||
feature = "utmpx"
|
||||
|
|
|
@ -79,6 +79,7 @@ pub use crate::features::signals;
|
|||
unix,
|
||||
not(target_os = "android"),
|
||||
not(target_os = "fuchsia"),
|
||||
not(target_os = "openbsd"),
|
||||
not(target_os = "redox"),
|
||||
not(target_env = "musl"),
|
||||
feature = "utmpx"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue