mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-09-17 20:26:18 +00:00
who: tool unsupported on OpenBSD
- utmpx not supported on OpenBSD - add src/uu/who/src/platform directory and platform/mod.rs for conditional compilation according to target_os - platform/openbsd.rs: implementation on OpenBSD (unsupported tool) - platform/unix.rs: implementation on other OS - src/uu/who/src/who.rs: use platform module for uucore::main function
This commit is contained in:
parent
fdd6ecb713
commit
e1032e1f06
4 changed files with 466 additions and 421 deletions
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(())
|
||||||
|
}
|
432
src/uu/who/src/platform/unix.rs
Normal file
432
src/uu/who/src/platform/unix.rs
Normal file
|
@ -0,0 +1,432 @@
|
||||||
|
// 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 args = args.collect_ignore();
|
||||||
|
|
||||||
|
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
|
// 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 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};
|
use uucore::{format_usage, help_about, help_usage};
|
||||||
|
|
||||||
|
mod platform;
|
||||||
|
|
||||||
mod options {
|
mod options {
|
||||||
pub const ALL: &str = "all";
|
pub const ALL: &str = "all";
|
||||||
pub const BOOT: &str = "boot";
|
pub const BOOT: &str = "boot";
|
||||||
|
@ -44,109 +36,8 @@ static RUNLEVEL_HELP: &str = "print current runlevel";
|
||||||
#[cfg(not(target_os = "linux"))]
|
#[cfg(not(target_os = "linux"))]
|
||||||
static RUNLEVEL_HELP: &str = "print current runlevel (This is meaningless on non 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]
|
#[uucore::main]
|
||||||
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
use platform::uumain;
|
||||||
let args = args.collect_ignore();
|
|
||||||
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn uu_app() -> Command {
|
pub fn uu_app() -> Command {
|
||||||
Command::new(uucore::util_name())
|
Command::new(uucore::util_name())
|
||||||
|
@ -258,312 +149,3 @@ pub fn uu_app() -> Command {
|
||||||
.value_hint(clap::ValueHint::FilePath),
|
.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",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue