mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-28 11:37:44 +00:00
commit
8632e19b44
11 changed files with 681 additions and 26 deletions
|
@ -34,6 +34,7 @@ unix = [
|
||||||
"unlink",
|
"unlink",
|
||||||
"uptime",
|
"uptime",
|
||||||
"users",
|
"users",
|
||||||
|
"who",
|
||||||
]
|
]
|
||||||
generic = [
|
generic = [
|
||||||
"base32",
|
"base32",
|
||||||
|
@ -187,6 +188,7 @@ unlink = { optional=true, path="src/unlink" }
|
||||||
uptime = { optional=true, path="src/uptime" }
|
uptime = { optional=true, path="src/uptime" }
|
||||||
users = { optional=true, path="src/users" }
|
users = { optional=true, path="src/users" }
|
||||||
wc = { optional=true, path="src/wc" }
|
wc = { optional=true, path="src/wc" }
|
||||||
|
who = { optional=true, path="src/who" }
|
||||||
whoami = { optional=true, path="src/whoami" }
|
whoami = { optional=true, path="src/whoami" }
|
||||||
yes = { optional=true, path="src/yes" }
|
yes = { optional=true, path="src/yes" }
|
||||||
|
|
||||||
|
|
6
Makefile
6
Makefile
|
@ -129,7 +129,8 @@ UNIX_PROGS := \
|
||||||
uname \
|
uname \
|
||||||
unlink \
|
unlink \
|
||||||
uptime \
|
uptime \
|
||||||
users
|
users \
|
||||||
|
who
|
||||||
|
|
||||||
ifneq ($(OS),Windows_NT)
|
ifneq ($(OS),Windows_NT)
|
||||||
PROGS := $(PROGS) $(UNIX_PROGS)
|
PROGS := $(PROGS) $(UNIX_PROGS)
|
||||||
|
@ -195,7 +196,8 @@ TEST_PROGS := \
|
||||||
unexpand \
|
unexpand \
|
||||||
uniq \
|
uniq \
|
||||||
unlink \
|
unlink \
|
||||||
wc
|
wc \
|
||||||
|
who
|
||||||
|
|
||||||
TESTS := \
|
TESTS := \
|
||||||
$(sort $(filter $(UTILS),$(filter-out $(SKIP_UTILS),$(TEST_PROGS))))
|
$(sort $(filter $(UTILS),$(filter-out $(SKIP_UTILS),$(TEST_PROGS))))
|
||||||
|
|
|
@ -170,7 +170,6 @@ To do
|
||||||
- tail (not all features implemented)
|
- tail (not all features implemented)
|
||||||
- test (not all features implemented)
|
- test (not all features implemented)
|
||||||
- uniq (a couple of missing options)
|
- uniq (a couple of missing options)
|
||||||
- who
|
|
||||||
|
|
||||||
License
|
License
|
||||||
-------
|
-------
|
||||||
|
|
|
@ -251,7 +251,7 @@ fn time_string(ut: &Utmpx) -> String {
|
||||||
impl Pinky {
|
impl Pinky {
|
||||||
fn print_entry(&self, ut: &Utmpx) {
|
fn print_entry(&self, ut: &Utmpx) {
|
||||||
let mut pts_path = PathBuf::from("/dev");
|
let mut pts_path = PathBuf::from("/dev");
|
||||||
pts_path.push(ut.tty_device().as_ref());
|
pts_path.push(ut.tty_device().as_str());
|
||||||
|
|
||||||
let mesg;
|
let mesg;
|
||||||
let last_change;
|
let last_change;
|
||||||
|
@ -298,7 +298,7 @@ impl Pinky {
|
||||||
print!(" {}", time_string(&ut));
|
print!(" {}", time_string(&ut));
|
||||||
|
|
||||||
if self.include_where && !ut.host().is_empty() {
|
if self.include_where && !ut.host().is_empty() {
|
||||||
let ut_host = ut.host().into_owned();
|
let ut_host = ut.host();
|
||||||
let mut res = ut_host.split(':');
|
let mut res = ut_host.split(':');
|
||||||
let host = match res.next() {
|
let host = match res.next() {
|
||||||
Some(_) => ut.canon_host().unwrap_or(ut_host.clone()),
|
Some(_) => ut.canon_host().unwrap_or(ut_host.clone()),
|
||||||
|
|
|
@ -67,7 +67,7 @@ fn exec(filename: &str) {
|
||||||
let mut users = Utmpx::iter_all_records()
|
let mut users = Utmpx::iter_all_records()
|
||||||
.read_from(filename)
|
.read_from(filename)
|
||||||
.filter(|ut| ut.is_user_process())
|
.filter(|ut| ut.is_user_process())
|
||||||
.map(|ut| ut.user().into_owned())
|
.map(|ut| ut.user())
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
if !users.is_empty() {
|
if !users.is_empty() {
|
||||||
|
|
|
@ -2,6 +2,14 @@
|
||||||
//!
|
//!
|
||||||
//! **ONLY** support linux, macos and freebsd for the time being
|
//! **ONLY** support linux, macos and freebsd for the time being
|
||||||
|
|
||||||
|
// This file is part of the uutils coreutils package.
|
||||||
|
//
|
||||||
|
// (c) Jian Zeng <anonymousknight96@gmail.com>
|
||||||
|
//
|
||||||
|
// For the full copyright and license information, please view the LICENSE
|
||||||
|
// file that was distributed with this source code.
|
||||||
|
//
|
||||||
|
|
||||||
use super::libc;
|
use super::libc;
|
||||||
pub extern crate time;
|
pub extern crate time;
|
||||||
use self::time::{Tm, Timespec};
|
use self::time::{Tm, Timespec};
|
||||||
|
@ -9,8 +17,6 @@ use self::time::{Tm, Timespec};
|
||||||
use ::std::io::Result as IOResult;
|
use ::std::io::Result as IOResult;
|
||||||
use ::std::io::Error as IOError;
|
use ::std::io::Error as IOError;
|
||||||
use ::std::ptr;
|
use ::std::ptr;
|
||||||
use ::std::borrow::Cow;
|
|
||||||
use ::std::ffi::CStr;
|
|
||||||
use ::std::ffi::CString;
|
use ::std::ffi::CString;
|
||||||
|
|
||||||
pub use self::ut::*;
|
pub use self::ut::*;
|
||||||
|
@ -28,11 +34,10 @@ pub unsafe extern "C" fn utmpxname(_file: *const libc::c_char) -> libc::c_int {
|
||||||
0
|
0
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! bytes2cow {
|
// In case the c_char array doesn' t end with NULL
|
||||||
($name:expr) => (
|
macro_rules! chars2string {
|
||||||
unsafe {
|
($arr:expr) => (
|
||||||
CStr::from_ptr($name.as_ref().as_ptr()).to_string_lossy()
|
$arr.iter().take_while(|i| **i > 0).map(|&i| i as u8 as char).collect::<String>()
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -138,26 +143,40 @@ impl Utmpx {
|
||||||
self.inner.ut_pid as i32
|
self.inner.ut_pid as i32
|
||||||
}
|
}
|
||||||
/// A.K.A. ut.ut_id
|
/// A.K.A. ut.ut_id
|
||||||
pub fn terminal_suffix(&self) -> Cow<str> {
|
pub fn terminal_suffix(&self) -> String {
|
||||||
bytes2cow!(self.inner.ut_id)
|
chars2string!(self.inner.ut_id)
|
||||||
}
|
}
|
||||||
/// A.K.A. ut.ut_user
|
/// A.K.A. ut.ut_user
|
||||||
pub fn user(&self) -> Cow<str> {
|
pub fn user(&self) -> String {
|
||||||
bytes2cow!(self.inner.ut_user)
|
chars2string!(self.inner.ut_user)
|
||||||
}
|
}
|
||||||
/// A.K.A. ut.ut_host
|
/// A.K.A. ut.ut_host
|
||||||
pub fn host(&self) -> Cow<str> {
|
pub fn host(&self) -> String {
|
||||||
bytes2cow!(self.inner.ut_host)
|
chars2string!(self.inner.ut_host)
|
||||||
}
|
}
|
||||||
/// A.K.A. ut.ut_line
|
/// A.K.A. ut.ut_line
|
||||||
pub fn tty_device(&self) -> Cow<str> {
|
pub fn tty_device(&self) -> String {
|
||||||
bytes2cow!(self.inner.ut_line)
|
chars2string!(self.inner.ut_line)
|
||||||
}
|
}
|
||||||
/// A.K.A. ut.ut_tv
|
/// A.K.A. ut.ut_tv
|
||||||
pub fn login_time(&self) -> Tm {
|
pub fn login_time(&self) -> Tm {
|
||||||
time::at(Timespec::new(self.inner.ut_tv.tv_sec as i64,
|
time::at(Timespec::new(self.inner.ut_tv.tv_sec as i64,
|
||||||
self.inner.ut_tv.tv_usec as i32))
|
self.inner.ut_tv.tv_usec as i32))
|
||||||
}
|
}
|
||||||
|
/// A.K.A. ut.ut_exit
|
||||||
|
///
|
||||||
|
/// Return (e_termination, e_exit)
|
||||||
|
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||||
|
pub fn exit_status(&self) -> (i16, i16) {
|
||||||
|
(self.inner.ut_exit.e_termination, self.inner.ut_exit.e_exit)
|
||||||
|
}
|
||||||
|
/// A.K.A. ut.ut_exit
|
||||||
|
///
|
||||||
|
/// Return (0, 0) on Non-Linux platform
|
||||||
|
#[cfg(not(any(target_os = "linux", target_os = "android")))]
|
||||||
|
pub fn exit_status(&self) -> (i16, i16) {
|
||||||
|
(0, 0)
|
||||||
|
}
|
||||||
/// Consumes the `Utmpx`, returning the underlying C struct utmpx
|
/// Consumes the `Utmpx`, returning the underlying C struct utmpx
|
||||||
pub fn into_inner(self) -> utmpx {
|
pub fn into_inner(self) -> utmpx {
|
||||||
self.inner
|
self.inner
|
||||||
|
@ -170,6 +189,7 @@ impl Utmpx {
|
||||||
pub fn canon_host(&self) -> IOResult<String> {
|
pub fn canon_host(&self) -> IOResult<String> {
|
||||||
const AI_CANONNAME: libc::c_int = 0x2;
|
const AI_CANONNAME: libc::c_int = 0x2;
|
||||||
let host = self.host();
|
let host = self.host();
|
||||||
|
let host = host.split(':').nth(0).unwrap();
|
||||||
let hints = libc::addrinfo {
|
let hints = libc::addrinfo {
|
||||||
ai_flags: AI_CANONNAME,
|
ai_flags: AI_CANONNAME,
|
||||||
ai_family: 0,
|
ai_family: 0,
|
||||||
|
@ -180,7 +200,7 @@ impl Utmpx {
|
||||||
ai_canonname: ptr::null_mut(),
|
ai_canonname: ptr::null_mut(),
|
||||||
ai_next: ptr::null_mut(),
|
ai_next: ptr::null_mut(),
|
||||||
};
|
};
|
||||||
let c_host = CString::new(host.as_ref()).unwrap();
|
let c_host = CString::new(host).unwrap();
|
||||||
let mut res = ptr::null_mut();
|
let mut res = ptr::null_mut();
|
||||||
let status = unsafe {
|
let status = unsafe {
|
||||||
libc::getaddrinfo(c_host.as_ptr(),
|
libc::getaddrinfo(c_host.as_ptr(),
|
||||||
|
@ -194,7 +214,7 @@ impl Utmpx {
|
||||||
// says Darwin 7.9.0 getaddrinfo returns 0 but sets
|
// says Darwin 7.9.0 getaddrinfo returns 0 but sets
|
||||||
// res->ai_canonname to NULL.
|
// res->ai_canonname to NULL.
|
||||||
let ret = if info.ai_canonname.is_null() {
|
let ret = if info.ai_canonname.is_null() {
|
||||||
Ok(String::from(host.as_ref()))
|
Ok(String::from(host))
|
||||||
} else {
|
} else {
|
||||||
Ok(unsafe { CString::from_raw(info.ai_canonname).into_string().unwrap() })
|
Ok(unsafe { CString::from_raw(info.ai_canonname).into_string().unwrap() })
|
||||||
};
|
};
|
||||||
|
@ -236,9 +256,7 @@ impl Iterator for UtmpxIter {
|
||||||
unsafe {
|
unsafe {
|
||||||
let res = getutxent();
|
let res = getutxent();
|
||||||
if !res.is_null() {
|
if !res.is_null() {
|
||||||
Some(Utmpx {
|
Some(Utmpx { inner: ptr::read(res as *const _) })
|
||||||
inner: ptr::read(res as *const _)
|
|
||||||
})
|
|
||||||
} else {
|
} else {
|
||||||
endutxent();
|
endutxent();
|
||||||
None
|
None
|
||||||
|
|
21
src/who/Cargo.toml
Normal file
21
src/who/Cargo.toml
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
[package]
|
||||||
|
name = "who"
|
||||||
|
version = "0.0.1"
|
||||||
|
authors = []
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "uu_who"
|
||||||
|
path = "who.rs"
|
||||||
|
|
||||||
|
[dependencies.uucore]
|
||||||
|
path = "../uucore"
|
||||||
|
default-features = false
|
||||||
|
features = ["utmpx"]
|
||||||
|
|
||||||
|
[dependencies.clippy]
|
||||||
|
version = "*"
|
||||||
|
optional = true
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "who"
|
||||||
|
path = "main.rs"
|
5
src/who/main.rs
Normal file
5
src/who/main.rs
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
extern crate uu_who;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
std::process::exit(uu_who::uumain(std::env::args().collect()));
|
||||||
|
}
|
519
src/who/who.rs
Normal file
519
src/who/who.rs
Normal file
|
@ -0,0 +1,519 @@
|
||||||
|
// This file is part of the uutils coreutils package.
|
||||||
|
//
|
||||||
|
// (c) Jian Zeng <anonymousknight96@gmail.com>
|
||||||
|
//
|
||||||
|
// For the full copyright and license information, please view the LICENSE
|
||||||
|
// file that was distributed with this source code.
|
||||||
|
//
|
||||||
|
#![crate_name = "uu_who"]
|
||||||
|
#![cfg_attr(feature="clippy", feature(plugin))]
|
||||||
|
#![cfg_attr(feature="clippy", plugin(clippy))]
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
extern crate uucore;
|
||||||
|
use uucore::utmpx::{self, time, Utmpx};
|
||||||
|
use uucore::coreopts;
|
||||||
|
use uucore::libc::{STDIN_FILENO, time_t, ttyname, S_IWGRP};
|
||||||
|
|
||||||
|
use std::borrow::Cow;
|
||||||
|
use std::io::prelude::*;
|
||||||
|
use std::ffi::CStr;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::os::unix::fs::MetadataExt;
|
||||||
|
|
||||||
|
static NAME: &'static str = "who";
|
||||||
|
|
||||||
|
pub fn uumain(args: Vec<String>) -> i32 {
|
||||||
|
|
||||||
|
let mut opts = coreopts::CoreOptions::new(NAME);
|
||||||
|
opts.optflag("a", "all", "same as -b -d --login -p -r -t -T -u");
|
||||||
|
opts.optflag("b", "boot", "time of last system boot");
|
||||||
|
opts.optflag("d", "dead", "print dead processes");
|
||||||
|
opts.optflag("H", "heading", "print line of column headings");
|
||||||
|
opts.optflag("l", "login", "print system login processes");
|
||||||
|
opts.optflag("", "lookup", "attempt to canonicalize hostnames via DNS");
|
||||||
|
opts.optflag("m", "m", "only hostname and user associated with stdin");
|
||||||
|
opts.optflag("p", "process", "print active processes spawned by init");
|
||||||
|
opts.optflag("q",
|
||||||
|
"count",
|
||||||
|
"all login names and number of users logged on");
|
||||||
|
opts.optflag("r", "runlevel", "print current runlevel");
|
||||||
|
opts.optflag("s", "short", "print only name, line, and time (default)");
|
||||||
|
opts.optflag("t", "time", "print last system clock change");
|
||||||
|
opts.optflag("u", "users", "list users logged in");
|
||||||
|
opts.optflag("w", "mesg", "add user's message status as +, - or ?");
|
||||||
|
// --message, --writable are the same as --mesg
|
||||||
|
opts.optflag("T", "message", "");
|
||||||
|
opts.optflag("T", "writable", "");
|
||||||
|
|
||||||
|
opts.optflag("", "help", "display this help and exit");
|
||||||
|
opts.optflag("", "version", "output version information and exit");
|
||||||
|
|
||||||
|
opts.help(format!("Usage: {} [OPTION]... [ FILE | ARG1 ARG2 ]
|
||||||
|
Print information about users who are currently logged in.
|
||||||
|
|
||||||
|
-a, --all same as -b -d --login -p -r -t -T -u
|
||||||
|
-b, --boot time of last system boot
|
||||||
|
-d, --dead print dead processes
|
||||||
|
-H, --heading print line of column headings
|
||||||
|
-l, --login print system login processes
|
||||||
|
--lookup attempt to canonicalize hostnames via DNS
|
||||||
|
-m only hostname and user associated with stdin
|
||||||
|
-p, --process print active processes spawned by init
|
||||||
|
-q, --count all login names and number of users logged on
|
||||||
|
-r, --runlevel print current runlevel
|
||||||
|
-s, --short print only name, line, and time (default)
|
||||||
|
-t, --time print last system clock change
|
||||||
|
-T, -w, --mesg add user's message status as +, - or ?
|
||||||
|
-u, --users list users logged in
|
||||||
|
--message same as -T
|
||||||
|
--writable same as -T
|
||||||
|
--help display this help and exit
|
||||||
|
--version output version information and exit
|
||||||
|
|
||||||
|
If FILE is not specified, use /var/run/utmp. /var/log/wtmp as FILE is common.
|
||||||
|
If ARG1 ARG2 given, -m presumed: 'am i' or 'mom likes' are usual.",
|
||||||
|
NAME));
|
||||||
|
|
||||||
|
let matches = opts.parse(args);
|
||||||
|
|
||||||
|
// If true, attempt to canonicalize hostnames via a DNS lookup.
|
||||||
|
let do_lookup = matches.opt_present("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.opt_present("q");
|
||||||
|
|
||||||
|
// If true, display only name, line, and time fields.
|
||||||
|
let mut short_output = false;
|
||||||
|
|
||||||
|
// 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 mut include_idle = false;
|
||||||
|
|
||||||
|
// If true, display a line at the top describing each field.
|
||||||
|
let include_heading = matches.opt_present("H");
|
||||||
|
|
||||||
|
// 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 = matches.opt_present("a") || matches.opt_present("T") || matches.opt_present("w");
|
||||||
|
|
||||||
|
// If true, display process termination & exit status.
|
||||||
|
let mut include_exit = false;
|
||||||
|
|
||||||
|
// If true, display the last boot time.
|
||||||
|
let mut need_boottime = false;
|
||||||
|
|
||||||
|
// If true, display dead processes.
|
||||||
|
let mut need_deadprocs = false;
|
||||||
|
|
||||||
|
// If true, display processes waiting for user login.
|
||||||
|
let mut need_login = false;
|
||||||
|
|
||||||
|
// If true, display processes started by init.
|
||||||
|
let mut need_initspawn = false;
|
||||||
|
|
||||||
|
// If true, display the last clock change.
|
||||||
|
let mut need_clockchange = false;
|
||||||
|
|
||||||
|
// If true, display the current runlevel.
|
||||||
|
let mut need_runlevel = false;
|
||||||
|
|
||||||
|
// If true, display user processes.
|
||||||
|
let mut need_users = false;
|
||||||
|
|
||||||
|
// If true, display info only for the controlling tty.
|
||||||
|
let mut my_line_only = false;
|
||||||
|
|
||||||
|
let mut assumptions = true;
|
||||||
|
|
||||||
|
if matches.opt_present("a") {
|
||||||
|
need_boottime = true;
|
||||||
|
need_deadprocs = true;
|
||||||
|
need_login = true;
|
||||||
|
need_initspawn = true;
|
||||||
|
need_runlevel = true;
|
||||||
|
need_clockchange = true;
|
||||||
|
need_users = true;
|
||||||
|
include_idle = true;
|
||||||
|
include_exit = true;
|
||||||
|
assumptions = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if matches.opt_present("b") {
|
||||||
|
need_boottime = true;
|
||||||
|
assumptions = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if matches.opt_present("d") {
|
||||||
|
need_deadprocs = true;
|
||||||
|
include_idle = true;
|
||||||
|
include_exit = true;
|
||||||
|
assumptions = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if matches.opt_present("l") {
|
||||||
|
need_login = true;
|
||||||
|
include_idle = true;
|
||||||
|
assumptions = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if matches.opt_present("m") || matches.free.len() == 2 {
|
||||||
|
my_line_only = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if matches.opt_present("p") {
|
||||||
|
need_initspawn = true;
|
||||||
|
assumptions = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if matches.opt_present("r") {
|
||||||
|
need_runlevel = true;
|
||||||
|
include_idle = true;
|
||||||
|
assumptions = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if matches.opt_present("s") {
|
||||||
|
short_output = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if matches.opt_present("t") {
|
||||||
|
need_clockchange = true;
|
||||||
|
assumptions = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if matches.opt_present("u") {
|
||||||
|
need_users = true;
|
||||||
|
include_idle = true;
|
||||||
|
assumptions = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if assumptions {
|
||||||
|
need_users = true;
|
||||||
|
short_output = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if include_exit {
|
||||||
|
short_output = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if matches.free.len() > 2 {
|
||||||
|
disp_err!("{}", msg_wrong_number_of_arguments!());
|
||||||
|
exit!(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
let who = Who {
|
||||||
|
do_lookup: do_lookup,
|
||||||
|
short_list: short_list,
|
||||||
|
short_output: short_output,
|
||||||
|
include_idle: include_idle,
|
||||||
|
include_heading: include_heading,
|
||||||
|
include_mesg: include_mesg,
|
||||||
|
include_exit: include_exit,
|
||||||
|
need_boottime: need_boottime,
|
||||||
|
need_deadprocs: need_deadprocs,
|
||||||
|
need_login: need_login,
|
||||||
|
need_initspawn: need_initspawn,
|
||||||
|
need_clockchange: need_clockchange,
|
||||||
|
need_runlevel: need_runlevel,
|
||||||
|
need_users: need_users,
|
||||||
|
my_line_only: my_line_only,
|
||||||
|
args: matches.free,
|
||||||
|
};
|
||||||
|
|
||||||
|
who.exec();
|
||||||
|
|
||||||
|
0
|
||||||
|
}
|
||||||
|
|
||||||
|
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: time_t, boottime: time_t) -> Cow<'a, str> {
|
||||||
|
thread_local! {
|
||||||
|
static NOW: time::Tm = time::now()
|
||||||
|
}
|
||||||
|
NOW.with(|n| {
|
||||||
|
let now = n.to_timespec().sec;
|
||||||
|
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 {
|
||||||
|
time::strftime("%Y-%m-%d %H:%M", &ut.login_time()).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn current_tty() -> String {
|
||||||
|
unsafe {
|
||||||
|
let res = ttyname(STDIN_FILENO);
|
||||||
|
if !res.is_null() {
|
||||||
|
CStr::from_ptr(res as *const _).to_string_lossy().trim_left_matches("/dev/").to_owned()
|
||||||
|
} else {
|
||||||
|
"".to_owned()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Who {
|
||||||
|
fn exec(&self) {
|
||||||
|
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()
|
||||||
|
.read_from(f)
|
||||||
|
.filter(|ut| ut.is_user_process())
|
||||||
|
.map(|ut| ut.user())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
println!("{}", users.join(" "));
|
||||||
|
println!("# users={}", users.len());
|
||||||
|
} else {
|
||||||
|
if self.include_heading {
|
||||||
|
self.print_heading()
|
||||||
|
}
|
||||||
|
let cur_tty = if self.my_line_only {
|
||||||
|
current_tty()
|
||||||
|
} else {
|
||||||
|
"".to_owned()
|
||||||
|
};
|
||||||
|
|
||||||
|
for ut in Utmpx::iter_all_records().read_from(f) {
|
||||||
|
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 && ut.record_type() == utmpx::RUN_LVL {
|
||||||
|
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 {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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() {
|
||||||
|
&comment
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
},
|
||||||
|
"");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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) {
|
||||||
|
let mut p = PathBuf::from("/dev");
|
||||||
|
p.push(ut.tty_device().as_str());
|
||||||
|
let mesg;
|
||||||
|
let last_change;
|
||||||
|
match p.metadata() {
|
||||||
|
Ok(meta) => {
|
||||||
|
mesg = if meta.mode() & (S_IWGRP as u32) != 0 {
|
||||||
|
'+'
|
||||||
|
} else {
|
||||||
|
'-'
|
||||||
|
};
|
||||||
|
last_change = meta.atime();
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
mesg = '?';
|
||||||
|
last_change = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let idle = if last_change != 0 {
|
||||||
|
idle_string(last_change, 0)
|
||||||
|
} else {
|
||||||
|
" ?".into()
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut buf = vec![];
|
||||||
|
let ut_host = ut.host();
|
||||||
|
let mut res = ut_host.split(':');
|
||||||
|
if let Some(h) = res.next() {
|
||||||
|
if self.do_lookup {
|
||||||
|
buf.push(ut.canon_host().unwrap_or(h.to_owned()));
|
||||||
|
} else {
|
||||||
|
buf.push(h.to_owned());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(h) = res.next() {
|
||||||
|
buf.push(h.to_owned());
|
||||||
|
}
|
||||||
|
let s = buf.join(":");
|
||||||
|
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(),
|
||||||
|
"");
|
||||||
|
}
|
||||||
|
|
||||||
|
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>();
|
||||||
|
|
||||||
|
buf.push_str(&format!("{:<8}", user));
|
||||||
|
if self.include_mesg {
|
||||||
|
buf.push_str(&msg);
|
||||||
|
}
|
||||||
|
buf.push_str(&format!(" {:<12}", line));
|
||||||
|
// "%Y-%m-%d %H:%M"
|
||||||
|
buf.push_str(&format!(" {:<1$}", time, 4 + 1 + 2 + 1 + 2 + 1 + 2 + 1 + 2));
|
||||||
|
|
||||||
|
if !self.short_output {
|
||||||
|
if self.include_idle {
|
||||||
|
buf.push_str(&format!(" {:<6}", idle));
|
||||||
|
}
|
||||||
|
buf.push_str(&format!(" {:>10}", pid));
|
||||||
|
}
|
||||||
|
buf.push_str(&format!(" {:<8}", comment));
|
||||||
|
if self.include_exit {
|
||||||
|
buf.push_str(&format!(" {:<12}", exit));
|
||||||
|
}
|
||||||
|
println!("{}", buf.trim_right());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn print_heading(&self) {
|
||||||
|
self.print_line("NAME",
|
||||||
|
' ',
|
||||||
|
"LINE",
|
||||||
|
"TIME",
|
||||||
|
"IDLE",
|
||||||
|
"PID",
|
||||||
|
"COMMENT",
|
||||||
|
"EXIT");
|
||||||
|
}
|
||||||
|
}
|
88
tests/test_who.rs
Normal file
88
tests/test_who.rs
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
use common::util::*;
|
||||||
|
|
||||||
|
static UTIL_NAME: &'static str = "who";
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
#[test]
|
||||||
|
fn test_count() {
|
||||||
|
for opt in ["-q", "--count"].into_iter() {
|
||||||
|
let scene = TestScenario::new(UTIL_NAME);
|
||||||
|
let args = [*opt];
|
||||||
|
scene.ucmd().args(&args).run().stdout_is(expected_result(&args));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
#[test]
|
||||||
|
fn test_boot() {
|
||||||
|
for opt in ["-b", "--boot"].into_iter() {
|
||||||
|
let scene = TestScenario::new(UTIL_NAME);
|
||||||
|
let args = [*opt];
|
||||||
|
scene.ucmd().args(&args).run().stdout_is(expected_result(&args));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
#[test]
|
||||||
|
fn test_heading() {
|
||||||
|
for opt in ["-H"].into_iter() {
|
||||||
|
let scene = TestScenario::new(UTIL_NAME);
|
||||||
|
let args = [*opt];
|
||||||
|
scene.ucmd().args(&args).run().stdout_is(expected_result(&args));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
#[test]
|
||||||
|
fn test_short() {
|
||||||
|
for opt in ["-s", "--short"].into_iter() {
|
||||||
|
let scene = TestScenario::new(UTIL_NAME);
|
||||||
|
let args = [*opt];
|
||||||
|
scene.ucmd().args(&args).run().stdout_is(expected_result(&args));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
#[test]
|
||||||
|
fn test_login() {
|
||||||
|
for opt in ["-l", "--login"].into_iter() {
|
||||||
|
let scene = TestScenario::new(UTIL_NAME);
|
||||||
|
let args = [*opt];
|
||||||
|
scene.ucmd().args(&args).run().stdout_is(expected_result(&args));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
#[test]
|
||||||
|
fn test_m() {
|
||||||
|
for opt in ["-m"].into_iter() {
|
||||||
|
let scene = TestScenario::new(UTIL_NAME);
|
||||||
|
let args = [*opt];
|
||||||
|
scene.ucmd().args(&args).run().stdout_is(expected_result(&args));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
#[test]
|
||||||
|
fn test_dead() {
|
||||||
|
for opt in ["-d", "--dead"].into_iter() {
|
||||||
|
let scene = TestScenario::new(UTIL_NAME);
|
||||||
|
let args = [*opt];
|
||||||
|
scene.ucmd().args(&args).run().stdout_is(expected_result(&args));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
#[test]
|
||||||
|
fn test_all() {
|
||||||
|
for opt in ["-a", "--all"].into_iter() {
|
||||||
|
let scene = TestScenario::new(UTIL_NAME);
|
||||||
|
let args = [*opt];
|
||||||
|
scene.ucmd().args(&args).run().stdout_is(expected_result(&args));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
fn expected_result(args: &[&str]) -> String {
|
||||||
|
TestScenario::new(UTIL_NAME).cmd_keepenv(UTIL_NAME).args(args).run().stdout
|
||||||
|
}
|
|
@ -21,6 +21,7 @@ unix_only! {
|
||||||
"stdbuf", test_stdbuf;
|
"stdbuf", test_stdbuf;
|
||||||
"touch", test_touch;
|
"touch", test_touch;
|
||||||
"unlink", test_unlink;
|
"unlink", test_unlink;
|
||||||
|
"who", test_who;
|
||||||
// Be aware of the trailing semicolon after the last item
|
// Be aware of the trailing semicolon after the last item
|
||||||
"stat", test_stat
|
"stat", test_stat
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue