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
|
||||
|
||||
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,109 +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 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()
|
||||
}
|
||||
use platform::uumain;
|
||||
|
||||
pub fn uu_app() -> Command {
|
||||
Command::new(uucore::util_name())
|
||||
|
@ -258,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",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue