From 3ed49033b7c6f70864759e474e7ddd25d0b23ec2 Mon Sep 17 00:00:00 2001 From: Knight Date: Mon, 25 Jul 2016 20:22:43 +0800 Subject: [PATCH] pinky: implement short format --- src/pinky/Cargo.toml | 1 + src/pinky/pinky.rs | 226 +++++++++++++++++++++++++++++++++++++------ src/pinky/utmp.rs | 58 +++++++++++ src/uucore/utmpx.rs | 12 ++- 4 files changed, 263 insertions(+), 34 deletions(-) create mode 100644 src/pinky/utmp.rs diff --git a/src/pinky/Cargo.toml b/src/pinky/Cargo.toml index b53ca0e94..768190736 100644 --- a/src/pinky/Cargo.toml +++ b/src/pinky/Cargo.toml @@ -9,6 +9,7 @@ path = "pinky.rs" [dependencies] getopts = "*" +time = "*" libc = "^0.2" uucore = { path="../uucore" } diff --git a/src/pinky/pinky.rs b/src/pinky/pinky.rs index 0518eef4f..5bdc8833e 100644 --- a/src/pinky/pinky.rs +++ b/src/pinky/pinky.rs @@ -7,25 +7,38 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. // - #![cfg_attr(feature="clippy", feature(plugin))] #![cfg_attr(feature="clippy", plugin(clippy))] #[macro_use] extern crate uucore; use uucore::c_types::getpwnam; +use uucore::utmpx; extern crate getopts; extern crate libc; use libc::{uid_t, gid_t, c_char}; +use libc::S_IWGRP; + +extern crate time; use std::io::prelude::*; use std::io::BufReader; -use std::ptr; +use std::io::Result as IOResult; + use std::fs::File; +use std::os::unix::fs::MetadataExt; + +use std::ptr; use std::ffi::CStr; +use std::ffi::OsStr; +use std::os::unix::ffi::OsStrExt; + +use std::path::Path; use std::path::PathBuf; +mod utmp; + static NAME: &'static str = "pinky"; static VERSION: &'static str = env!("CARGO_PKG_VERSION"); @@ -80,7 +93,7 @@ pub fn uumain(args: Vec) -> i32 { A lightweight 'finger' program; print user information. The utmp file will be {}", NAME, - "/var/run/utmp"); + utmpx::DEFAULT_FILE); return 0; } @@ -113,17 +126,20 @@ The utmp file will be {}", // if true, use the "short" output format. let do_short_format = !matches.opt_present("l"); + /* if true, display the ut_host field. */ + let mut include_where = true; + if matches.opt_present("w") { include_fullname = false; } if matches.opt_present("i") { include_fullname = false; - // FIXME: + include_where = false; } if matches.opt_present("q") { include_fullname = false; include_idle = false; - // FIXME: + include_where = false; } if !do_short_format && matches.free.is_empty() { @@ -138,12 +154,17 @@ The utmp file will be {}", include_project: include_project, include_plan: include_plan, include_home_and_shell: include_home_and_shell, - do_short_format: do_short_format, - users: matches.free, + include_where: include_where, + names: matches.free, }; if do_short_format { - pk.short_pinky() + if let Err(e) = pk.short_pinky() { + disp_err!("{}", e); + 1 + } else { + 0 + } } else { pk.long_pinky() } @@ -156,13 +177,13 @@ struct Pinky { include_fullname: bool, include_project: bool, include_plan: bool, + include_where: bool, include_home_and_shell: bool, - do_short_format: bool, - users: Vec, + names: Vec, } #[derive(Debug)] -struct Passwd { +pub struct Passwd { pw_name: String, pw_passwd: String, pw_uid: uid_t, @@ -172,35 +193,39 @@ struct Passwd { pw_shell: String, } -fn getpw(u: &str) -> Option { +trait FromChars { + fn from_chars(*const c_char) -> Self; +} + +impl FromChars for String { + #[inline] + fn from_chars(ptr: *const c_char) -> Self { + if ptr.is_null() { + return "".to_owned(); + } + let s = unsafe { CStr::from_ptr(ptr) }; + s.to_string_lossy().into_owned() + } +} + +pub fn getpw(u: &str) -> Option { let pw = unsafe { getpwnam(u.as_ptr() as *const i8) }; if !pw.is_null() { let data = unsafe { ptr::read(pw) }; Some(Passwd { - pw_name: cstr2string(data.pw_name), - pw_passwd: cstr2string(data.pw_passwd), + pw_name: String::from_chars(data.pw_name), + pw_passwd: String::from_chars(data.pw_passwd), pw_uid: data.pw_uid, pw_gid: data.pw_gid, - pw_dir: cstr2string(data.pw_dir), - pw_gecos: cstr2string(data.pw_gecos), - pw_shell: cstr2string(data.pw_shell), + pw_dir: String::from_chars(data.pw_dir), + pw_gecos: String::from_chars(data.pw_gecos), + pw_shell: String::from_chars(data.pw_shell), }) } else { None } } -#[inline] -fn cstr2string(ptr: *const c_char) -> String { - if ptr.is_null() { - println!("null ptr"); - return "".to_owned(); - } - let s = unsafe { CStr::from_ptr(ptr) }; - s.to_string_lossy().into_owned() -} - - trait Capitalize { fn capitalize(&self) -> String; } @@ -219,13 +244,152 @@ impl Capitalize for str { } } +trait UtmpUtils { + fn is_user_process(&self) -> bool; +} + +impl UtmpUtils for utmpx::c_utmp { + fn is_user_process(&self) -> bool { + self.ut_user[0] != 0 && self.ut_type == utmpx::USER_PROCESS + } +} + +fn idle_string(when: i64) -> String { + thread_local! { + static NOW: time::Tm = time::now(); + } + NOW.with(|n| { + let duration = n.to_timespec().sec - when; + if duration < 60 { + // less than 1min + " ".to_owned() + } else if duration < 24 * 3600 { + // less than 1day + let hours = duration / (60 * 60); + let minutes = (duration % (60 * 60)) / 60; + format!("{:02}:{:02}", hours, minutes) + } else { + // more than 1day + let days = duration / (24 * 3600); + format!("{}d", days) + } + }) +} + +fn time_string(ut: &utmpx::c_utmp) -> String { + let tm = time::at(time::Timespec::new(ut.ut_tv.tv_sec as i64, ut.ut_tv.tv_usec)); + time::strftime("%Y-%m-%d %H:%M", &tm).unwrap() +} + impl Pinky { - fn short_pinky(&self) -> i32 { - 0 + fn print_entry(&self, ut: &utmpx::c_utmp) { + let mut pts_path = PathBuf::from("/dev"); + let line: &Path = OsStr::from_bytes(unsafe { + CStr::from_ptr(ut.ut_line.as_ref().as_ptr()).to_bytes() + }).as_ref(); + pts_path.push(line); + + let mesg; + let last_change; + match pts_path.metadata() { + Ok(meta) => { + mesg = if meta.mode() & S_IWGRP != 0 { + ' ' + } else { + '*' + }; + last_change = meta.atime(); + } + _ => { + mesg = '?'; + last_change = 0; + } + } + + let ut_user = String::from_chars(ut.ut_user.as_ref().as_ptr()); + print!("{1:<8.0$}", utmpx::UT_NAMESIZE, ut_user); + + if self.include_fullname { + if let Some(pw) = getpw(&ut_user) { + let mut gecos = pw.pw_gecos; + if let Some(n) = gecos.find(',') { + gecos.truncate(n + 1); + } + print!(" {:<19.19}", gecos.replace("&", &pw.pw_name.capitalize())); + } else { + print!(" {:19}", " ???"); + } + + } + + print!(" {}{:<8.*}", mesg, utmpx::UT_LINESIZE, String::from_chars(ut.ut_line.as_ref().as_ptr())); + + if self.include_idle { + if last_change != 0 { + print!(" {:<6}", idle_string(last_change)); + } else { + print!(" {:<6}", "?????"); + } + } + + print!(" {}", time_string(&ut)); + + if self.include_where && ut.ut_host[0] != 0 { + let ut_host = String::from_chars(ut.ut_host.as_ref().as_ptr()); + //if let Some(n) = ut_host.find(':') { + //ut_host.truncate(n + 1); + //} + print!(" {}", ut_host); + } + + println!(""); + } + + fn print_heading(&self) { + print!("{:<8}", "Login"); + if self.include_fullname { + print!(" {:<19}", "Name"); + } + print!(" {:<9}", " TTY"); + if self.include_idle { + print!(" {:<6}", "Idle"); + } + print!(" {:<16}", "When"); + if self.include_where { + print!(" Where"); + } + println!(""); + } + + fn short_pinky(&self) -> IOResult<()> { + if self.include_heading { + self.print_heading(); + } else { + // @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + // FIXME: WIERD!!! If the following line is removed, + // getpwnam() will return NULL. + // @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + print!(""); + } + for ut in try!(utmp::read_utmps()) { + if ut.is_user_process() { + if self.names.is_empty() { + self.print_entry(&ut) + } else { + let ut_user = unsafe { + CStr::from_ptr(ut.ut_user.as_ref().as_ptr()).to_bytes() + }; + if self.names.iter().any(|n| n.as_bytes() == ut_user) { + self.print_entry(&ut); + } + } + } + } + Ok(()) } fn long_pinky(&self) -> i32 { - for u in &self.users { + for u in &self.names { print!("Login name: {:<28}In real life: ", u); if let Some(pw) = getpw(u) { println!(" {}", pw.pw_gecos.replace("&", &pw.pw_name.capitalize())); diff --git a/src/pinky/utmp.rs b/src/pinky/utmp.rs new file mode 100644 index 000000000..0faf6b662 --- /dev/null +++ b/src/pinky/utmp.rs @@ -0,0 +1,58 @@ +// # Read, Write, BufRead, Seek +//use std::io::prelude::*; + +//use std::io::Result as IOResult; + +//use std::borrow::Cow; +//use std::borrow::Borrow; +//use std::convert::AsRef; + +extern crate libc; + +extern crate uucore; +use uucore::utmpx; +use uucore::utmpx::c_utmp; + +use std::slice; +use std::io::Read; +use std::fs::File; +use std::path::Path; +use std::convert::AsRef; +use std::io::Result as IOResult; +use std::marker::PhantomData; +use std::mem; + +pub struct StIter { + f: File, + size: usize, + _p: PhantomData, +} + +impl Iterator for StIter { + type Item = T; + + fn next(&mut self) -> Option { + unsafe { + let mut s = mem::zeroed(); + let mut buf = slice::from_raw_parts_mut(&mut s as *mut Self::Item as *mut u8, self.size); + if let Ok(()) = self.f.read_exact(buf) { + Some(s) + } else { + mem::forget(s); + None + } + } + } +} + +fn read_structs>(p: P) -> IOResult> { + Ok(StIter { + f: try!(File::open(p)), + size: mem::size_of::(), + _p: PhantomData, + }) +} + +pub fn read_utmps() -> IOResult> { + read_structs(utmpx::DEFAULT_FILE) +} diff --git a/src/uucore/utmpx.rs b/src/uucore/utmpx.rs index 58bedc91a..037c1d514 100644 --- a/src/uucore/utmpx.rs +++ b/src/uucore/utmpx.rs @@ -2,7 +2,7 @@ extern crate libc; -pub use self::utmpx::{DEFAULT_FILE,USER_PROCESS,BOOT_TIME,c_utmp}; +pub use self::utmpx::{UT_NAMESIZE,UT_LINESIZE,DEFAULT_FILE,USER_PROCESS,BOOT_TIME,c_utmp}; #[cfg(target_os = "linux")] mod utmpx { use super::libc; @@ -25,6 +25,12 @@ mod utmpx { pub const DEAD_PROCESS: libc::c_short = 8; pub const ACCOUNTING: libc::c_short = 9; + #[repr(C)] + pub struct timeval { + pub tv_sec: libc::int32_t, + pub tv_usec: libc::int32_t, + } + #[repr(C)] pub struct c_exit_status { pub e_termination: libc::c_short, @@ -41,8 +47,8 @@ mod utmpx { pub ut_user: [libc::c_char; UT_NAMESIZE], pub ut_host: [libc::c_char; UT_HOSTSIZE], pub ut_exit: c_exit_status, - pub ut_session: libc::c_long, - pub ut_tv: libc::timeval, + pub ut_session: libc::int32_t, + pub ut_tv: timeval, pub ut_addr_v6: [libc::int32_t; 4], pub __unused: [libc::c_char; 20],