From 8c6cd728480f28e1a3f53fab798a2e5b6ea2ca80 Mon Sep 17 00:00:00 2001 From: Knight Date: Thu, 11 Aug 2016 00:19:26 +0800 Subject: [PATCH 1/4] who: add entries --- Cargo.toml | 2 ++ Makefile | 3 ++- README.md | 1 - src/who/Cargo.toml | 21 +++++++++++++++++++++ src/who/main.rs | 5 +++++ 5 files changed, 30 insertions(+), 2 deletions(-) create mode 100644 src/who/Cargo.toml create mode 100644 src/who/main.rs diff --git a/Cargo.toml b/Cargo.toml index 1263c63c8..92859ca85 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,6 +34,7 @@ unix = [ "unlink", "uptime", "users", + "who", ] generic = [ "base32", @@ -187,6 +188,7 @@ unlink = { optional=true, path="src/unlink" } uptime = { optional=true, path="src/uptime" } users = { optional=true, path="src/users" } wc = { optional=true, path="src/wc" } +who = { optional=true, path="src/who" } whoami = { optional=true, path="src/whoami" } yes = { optional=true, path="src/yes" } diff --git a/Makefile b/Makefile index 5c766b1ff..38a9e3ec3 100644 --- a/Makefile +++ b/Makefile @@ -129,7 +129,8 @@ UNIX_PROGS := \ uname \ unlink \ uptime \ - users + users \ + who ifneq ($(OS),Windows_NT) PROGS := $(PROGS) $(UNIX_PROGS) diff --git a/README.md b/README.md index 070b31865..99db360b9 100644 --- a/README.md +++ b/README.md @@ -170,7 +170,6 @@ To do - tail (not all features implemented) - test (not all features implemented) - uniq (a couple of missing options) -- who License ------- diff --git a/src/who/Cargo.toml b/src/who/Cargo.toml new file mode 100644 index 000000000..aa769a348 --- /dev/null +++ b/src/who/Cargo.toml @@ -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" diff --git a/src/who/main.rs b/src/who/main.rs new file mode 100644 index 000000000..2c3de969a --- /dev/null +++ b/src/who/main.rs @@ -0,0 +1,5 @@ +extern crate uu_who; + +fn main() { + std::process::exit(uu_who::uumain(std::env::args().collect())); +} From 7637115e519d02ad0945f3c01c8a561042819d5d Mon Sep 17 00:00:00 2001 From: Knight Date: Thu, 11 Aug 2016 15:37:39 +0800 Subject: [PATCH 2/4] who: add tests --- Makefile | 3 +- tests/test_who.rs | 88 +++++++++++++++++++++++++++++++++++++++++++++++ tests/tests.rs | 1 + 3 files changed, 91 insertions(+), 1 deletion(-) create mode 100644 tests/test_who.rs diff --git a/Makefile b/Makefile index 38a9e3ec3..e67a262ad 100644 --- a/Makefile +++ b/Makefile @@ -196,7 +196,8 @@ TEST_PROGS := \ unexpand \ uniq \ unlink \ - wc + wc \ + who TESTS := \ $(sort $(filter $(UTILS),$(filter-out $(SKIP_UTILS),$(TEST_PROGS)))) diff --git a/tests/test_who.rs b/tests/test_who.rs new file mode 100644 index 000000000..f50e50551 --- /dev/null +++ b/tests/test_who.rs @@ -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 +} diff --git a/tests/tests.rs b/tests/tests.rs index 86f489fa0..81336f5a6 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -21,6 +21,7 @@ unix_only! { "stdbuf", test_stdbuf; "touch", test_touch; "unlink", test_unlink; + "who", test_who; // Be aware of the trailing semicolon after the last item "stat", test_stat } From 18da533538983178334961697eda4c61d6019be4 Mon Sep 17 00:00:00 2001 From: Knight Date: Thu, 11 Aug 2016 15:41:12 +0800 Subject: [PATCH 3/4] uucore::utmpx: change API and fix error --- src/pinky/pinky.rs | 4 ++-- src/users/users.rs | 2 +- src/uucore/utmpx.rs | 58 +++++++++++++++++++++++++++++---------------- 3 files changed, 41 insertions(+), 23 deletions(-) diff --git a/src/pinky/pinky.rs b/src/pinky/pinky.rs index 64e78dbd3..ecb5da188 100644 --- a/src/pinky/pinky.rs +++ b/src/pinky/pinky.rs @@ -251,7 +251,7 @@ fn time_string(ut: &Utmpx) -> String { impl Pinky { fn print_entry(&self, ut: &Utmpx) { 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 last_change; @@ -298,7 +298,7 @@ impl Pinky { print!(" {}", time_string(&ut)); 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 host = match res.next() { Some(_) => ut.canon_host().unwrap_or(ut_host.clone()), diff --git a/src/users/users.rs b/src/users/users.rs index 52e6f2f8d..d0b6cfd92 100644 --- a/src/users/users.rs +++ b/src/users/users.rs @@ -67,7 +67,7 @@ fn exec(filename: &str) { let mut users = Utmpx::iter_all_records() .read_from(filename) .filter(|ut| ut.is_user_process()) - .map(|ut| ut.user().into_owned()) + .map(|ut| ut.user()) .collect::>(); if !users.is_empty() { diff --git a/src/uucore/utmpx.rs b/src/uucore/utmpx.rs index 67b959541..0122a1156 100644 --- a/src/uucore/utmpx.rs +++ b/src/uucore/utmpx.rs @@ -2,6 +2,14 @@ //! //! **ONLY** support linux, macos and freebsd for the time being +// This file is part of the uutils coreutils package. +// +// (c) Jian Zeng +// +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. +// + use super::libc; pub extern crate time; use self::time::{Tm, Timespec}; @@ -9,8 +17,6 @@ use self::time::{Tm, Timespec}; use ::std::io::Result as IOResult; use ::std::io::Error as IOError; use ::std::ptr; -use ::std::borrow::Cow; -use ::std::ffi::CStr; use ::std::ffi::CString; pub use self::ut::*; @@ -28,11 +34,10 @@ pub unsafe extern "C" fn utmpxname(_file: *const libc::c_char) -> libc::c_int { 0 } -macro_rules! bytes2cow { - ($name:expr) => ( - unsafe { - CStr::from_ptr($name.as_ref().as_ptr()).to_string_lossy() - } +// In case the c_char array doesn' t end with NULL +macro_rules! chars2string { + ($arr:expr) => ( + $arr.iter().take_while(|i| **i > 0).map(|&i| i as u8 as char).collect::() ) } @@ -138,26 +143,40 @@ impl Utmpx { self.inner.ut_pid as i32 } /// A.K.A. ut.ut_id - pub fn terminal_suffix(&self) -> Cow { - bytes2cow!(self.inner.ut_id) + pub fn terminal_suffix(&self) -> String { + chars2string!(self.inner.ut_id) } /// A.K.A. ut.ut_user - pub fn user(&self) -> Cow { - bytes2cow!(self.inner.ut_user) + pub fn user(&self) -> String { + chars2string!(self.inner.ut_user) } /// A.K.A. ut.ut_host - pub fn host(&self) -> Cow { - bytes2cow!(self.inner.ut_host) + pub fn host(&self) -> String { + chars2string!(self.inner.ut_host) } /// A.K.A. ut.ut_line - pub fn tty_device(&self) -> Cow { - bytes2cow!(self.inner.ut_line) + pub fn tty_device(&self) -> String { + chars2string!(self.inner.ut_line) } /// A.K.A. ut.ut_tv pub fn login_time(&self) -> Tm { time::at(Timespec::new(self.inner.ut_tv.tv_sec as i64, 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 pub fn into_inner(self) -> utmpx { self.inner @@ -170,6 +189,7 @@ impl Utmpx { pub fn canon_host(&self) -> IOResult { const AI_CANONNAME: libc::c_int = 0x2; let host = self.host(); + let host = host.split(':').nth(0).unwrap(); let hints = libc::addrinfo { ai_flags: AI_CANONNAME, ai_family: 0, @@ -180,7 +200,7 @@ impl Utmpx { ai_canonname: 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 status = unsafe { libc::getaddrinfo(c_host.as_ptr(), @@ -194,7 +214,7 @@ impl Utmpx { // says Darwin 7.9.0 getaddrinfo returns 0 but sets // res->ai_canonname to NULL. let ret = if info.ai_canonname.is_null() { - Ok(String::from(host.as_ref())) + Ok(String::from(host)) } else { Ok(unsafe { CString::from_raw(info.ai_canonname).into_string().unwrap() }) }; @@ -236,9 +256,7 @@ impl Iterator for UtmpxIter { unsafe { let res = getutxent(); if !res.is_null() { - Some(Utmpx { - inner: ptr::read(res as *const _) - }) + Some(Utmpx { inner: ptr::read(res as *const _) }) } else { endutxent(); None From 87894dedca6c1f91d72e1887229db1c1367818ed Mon Sep 17 00:00:00 2001 From: Knight Date: Thu, 11 Aug 2016 15:41:28 +0800 Subject: [PATCH 4/4] who: implemented --- src/who/who.rs | 519 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 519 insertions(+) create mode 100644 src/who/who.rs diff --git a/src/who/who.rs b/src/who/who.rs new file mode 100644 index 000000000..e8bb94972 --- /dev/null +++ b/src/who/who.rs @@ -0,0 +1,519 @@ +// This file is part of the uutils coreutils package. +// +// (c) Jian Zeng +// +// 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) -> 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, +} + +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::>(); + 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::(); + + 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"); + } +}