From 50fcfac5d1a0ba20d1435def7ca7f19be15561ef Mon Sep 17 00:00:00 2001 From: Knight Date: Tue, 12 Jul 2016 18:40:48 +0800 Subject: [PATCH 01/10] pinky: Add entries --- Cargo.toml | 2 ++ Makefile | 2 ++ README.md | 2 -- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5aea9e268..26eb979ca 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,7 @@ unix = [ "nice", "nohup", "pathchk", + "pinky", "stat", "stdbuf", "timeout", @@ -143,6 +144,7 @@ nproc = { optional=true, path="src/nproc" } od = { optional=true, path="src/od" } paste = { optional=true, path="src/paste" } pathchk = { optional=true, path="src/pathchk" } +pinky = { optional=true, path="src/pinky" } printenv = { optional=true, path="src/printenv" } printf = { optional=true, path="src/printf" } ptx = { optional=true, path="src/ptx" } diff --git a/Makefile b/Makefile index f43ddbb9d..f62aec928 100644 --- a/Makefile +++ b/Makefile @@ -115,6 +115,7 @@ UNIX_PROGS := \ nice \ nohup \ pathchk \ + pinky \ stat \ stdbuf \ timeout \ @@ -162,6 +163,7 @@ TEST_PROGS := \ od \ paste \ pathchk \ + pinky \ printf \ ptx \ pwd \ diff --git a/README.md b/README.md index 43b885be5..d336b6668 100644 --- a/README.md +++ b/README.md @@ -149,7 +149,6 @@ To do - chcon - chgrp -- chown - copy - cp (not much done) - csplit @@ -164,7 +163,6 @@ To do - mv (almost done, one more option) - numfmt - od (in progress, needs lots of work) -- pinky - pr - printf - remove From 7fb3eef5e578a96a0e50208c73315526908c74af Mon Sep 17 00:00:00 2001 From: Knight Date: Tue, 12 Jul 2016 18:41:52 +0800 Subject: [PATCH 02/10] pinky: implement long format --- src/pinky/Cargo.toml | 24 ++++ src/pinky/main.rs | 5 + src/pinky/pinky.rs | 271 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 300 insertions(+) create mode 100644 src/pinky/Cargo.toml create mode 100644 src/pinky/main.rs create mode 100644 src/pinky/pinky.rs diff --git a/src/pinky/Cargo.toml b/src/pinky/Cargo.toml new file mode 100644 index 000000000..b53ca0e94 --- /dev/null +++ b/src/pinky/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "pinky" +version = "0.0.1" +authors = [] + +[lib] +name = "uu_pinky" +path = "pinky.rs" + +[dependencies] +getopts = "*" +libc = "^0.2" +uucore = { path="../uucore" } + +[dependencies.clippy] +version = "*" +optional = true + +[features] +default = [] + +[[bin]] +name = "pinky" +path = "main.rs" diff --git a/src/pinky/main.rs b/src/pinky/main.rs new file mode 100644 index 000000000..57068d670 --- /dev/null +++ b/src/pinky/main.rs @@ -0,0 +1,5 @@ +extern crate uu_pinky; + +fn main() { + std::process::exit(uu_pinky::uumain(std::env::args().collect())); +} diff --git a/src/pinky/pinky.rs b/src/pinky/pinky.rs new file mode 100644 index 000000000..0518eef4f --- /dev/null +++ b/src/pinky/pinky.rs @@ -0,0 +1,271 @@ +#![crate_name = "uu_pinky"] + +// 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. +// + +#![cfg_attr(feature="clippy", feature(plugin))] +#![cfg_attr(feature="clippy", plugin(clippy))] + +#[macro_use] +extern crate uucore; +use uucore::c_types::getpwnam; + +extern crate getopts; +extern crate libc; +use libc::{uid_t, gid_t, c_char}; + +use std::io::prelude::*; +use std::io::BufReader; +use std::ptr; +use std::fs::File; +use std::ffi::CStr; +use std::path::PathBuf; + +static NAME: &'static str = "pinky"; +static VERSION: &'static str = env!("CARGO_PKG_VERSION"); + +const BUFSIZE: usize = 1024; + +pub fn uumain(args: Vec) -> i32 { + let mut opts = getopts::Options::new(); + opts.optflag("l", + "l", + "produce long format output for the specified USERs"); + opts.optflag("b", + "b", + "omit the user's home directory and shell in long format"); + opts.optflag("h", "h", "omit the user's project file in long format"); + opts.optflag("p", "p", "omit the user's plan file in long format"); + opts.optflag("s", "s", "do short format output, this is the default"); + opts.optflag("f", "f", "omit the line of column headings in short format"); + opts.optflag("w", "w", "omit the user's full name in short format"); + opts.optflag("i", + "i", + "omit the user's full name and remote host in short format"); + opts.optflag("q", + "q", + "omit the user's full name, remote host and idle time in short format"); + opts.optflag("", "help", "display this help and exit"); + opts.optflag("", "version", "output version information and exit"); + + let matches = match opts.parse(&args[1..]) { + Ok(m) => m, + Err(f) => { + disp_err!("{}", f); + return 1; + } + }; + + if matches.opt_present("help") { + println!("Usage: {} [OPTION]... [USER]... + + -l produce long format output for the specified USERs + -b omit the user's home directory and shell in long format + -h omit the user's project file in long format + -p omit the user's plan file in long format + -s do short format output, this is the default + -f omit the line of column headings in short format + -w omit the user's full name in short format + -i omit the user's full name and remote host in short format + -q omit the user's full name, remote host and idle time + in short format + --help display this help and exit + --version output version information and exit + +A lightweight 'finger' program; print user information. +The utmp file will be {}", + NAME, + "/var/run/utmp"); + return 0; + } + + if matches.opt_present("version") { + println!("{} {}", NAME, VERSION); + return 0; + } + + // If true, display the hours:minutes since each user has touched + // the keyboard, or blank if within the last minute, or days followed + // by a 'd' if not within the last day. + let mut include_idle = true; + + // If true, display a line at the top describing each field. + let include_heading = !matches.opt_present("f"); + + // if true, display the user's full name from pw_gecos. + let mut include_fullname = true; + + // if true, display the user's ~/.project file when doing long format. + let include_project = !matches.opt_present("h"); + + // if true, display the user's ~/.plan file when doing long format. + let include_plan = !matches.opt_present("p"); + + // if true, display the user's home directory and shell + // when doing long format. + let include_home_and_shell = !matches.opt_present("b"); + + // if true, use the "short" output format. + let do_short_format = !matches.opt_present("l"); + + if matches.opt_present("w") { + include_fullname = false; + } + if matches.opt_present("i") { + include_fullname = false; + // FIXME: + } + if matches.opt_present("q") { + include_fullname = false; + include_idle = false; + // FIXME: + } + + if !do_short_format && matches.free.is_empty() { + disp_err!("no username specified; at least one must be specified when using -l"); + return 1; + } + + let pk = Pinky { + include_idle: include_idle, + include_heading: include_heading, + include_fullname: include_fullname, + 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, + }; + + if do_short_format { + pk.short_pinky() + } else { + pk.long_pinky() + } + +} + +struct Pinky { + include_idle: bool, + include_heading: bool, + include_fullname: bool, + include_project: bool, + include_plan: bool, + include_home_and_shell: bool, + do_short_format: bool, + users: Vec, +} + +#[derive(Debug)] +struct Passwd { + pw_name: String, + pw_passwd: String, + pw_uid: uid_t, + pw_gid: gid_t, + pw_gecos: String, + pw_dir: String, + pw_shell: String, +} + +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_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), + }) + } 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; +} + +impl Capitalize for str { + fn capitalize(&self) -> String { + use std::ascii::AsciiExt; + self.char_indices().fold(String::with_capacity(self.len()), |mut acc, x| { + if x.0 != 0 { + acc.push(x.1) + } else { + acc.push(x.1.to_ascii_uppercase()) + } + acc + }) + } +} + +impl Pinky { + fn short_pinky(&self) -> i32 { + 0 + } + + fn long_pinky(&self) -> i32 { + for u in &self.users { + print!("Login name: {:<28}In real life: ", u); + if let Some(pw) = getpw(u) { + println!(" {}", pw.pw_gecos.replace("&", &pw.pw_name.capitalize())); + if self.include_home_and_shell { + print!("Directory: {:<29}", pw.pw_dir); + println!("Shell: {}", pw.pw_shell); + } + if self.include_project { + let mut p = PathBuf::from(&pw.pw_dir); + p.push(".project"); + if let Ok(f) = File::open(p) { + print!("Project: "); + read_to_console(f); + } + } + if self.include_plan { + let mut p = PathBuf::from(&pw.pw_dir); + p.push(".plan"); + if let Ok(f) = File::open(p) { + println!("Plan:"); + read_to_console(f); + } + } + println!(""); + } else { + println!(" ???"); + } + } + 0 + } +} + +fn read_to_console(f: F) { + let mut reader = BufReader::new(f); + let mut iobuf = [0_u8; BUFSIZE]; + while let Ok(n) = reader.read(&mut iobuf) { + if n == 0 { + break; + } + let s = String::from_utf8_lossy(&iobuf); + print!("{}", s); + } +} From 3ed49033b7c6f70864759e474e7ddd25d0b23ec2 Mon Sep 17 00:00:00 2001 From: Knight Date: Mon, 25 Jul 2016 20:22:43 +0800 Subject: [PATCH 03/10] 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], From 1056542dd3a4ef77a239650ad1ab10c44f73923a Mon Sep 17 00:00:00 2001 From: Knight Date: Tue, 26 Jul 2016 13:32:00 +0800 Subject: [PATCH 04/10] uucore::utmpx: revert the changes --- src/uucore/utmpx.rs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/uucore/utmpx.rs b/src/uucore/utmpx.rs index 037c1d514..8b6bc742e 100644 --- a/src/uucore/utmpx.rs +++ b/src/uucore/utmpx.rs @@ -25,12 +25,6 @@ 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, @@ -47,8 +41,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::int32_t, - pub ut_tv: timeval, + pub ut_session: libc::c_long, + pub ut_tv: libc::timeval, pub ut_addr_v6: [libc::int32_t; 4], pub __unused: [libc::c_char; 20], From fe952d90d8694cf81015c9405f25d33a80082ec1 Mon Sep 17 00:00:00 2001 From: Knight Date: Tue, 26 Jul 2016 13:32:43 +0800 Subject: [PATCH 05/10] pinky: iterate over utmp structs using getutxent() --- src/pinky/pinky.rs | 4 +-- src/pinky/utmp.rs | 71 ++++++++++++++++++---------------------------- 2 files changed, 30 insertions(+), 45 deletions(-) diff --git a/src/pinky/pinky.rs b/src/pinky/pinky.rs index 5bdc8833e..ee175a0a4 100644 --- a/src/pinky/pinky.rs +++ b/src/pinky/pinky.rs @@ -277,7 +277,7 @@ fn idle_string(when: i64) -> String { } 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)); + let tm = time::at(time::Timespec::new(ut.ut_tv.tv_sec, ut.ut_tv.tv_usec as i32)); time::strftime("%Y-%m-%d %H:%M", &tm).unwrap() } @@ -371,7 +371,7 @@ impl Pinky { // @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ print!(""); } - for ut in try!(utmp::read_utmps()) { + for ut in utmp::read_utmps() { if ut.is_user_process() { if self.names.is_empty() { self.print_entry(&ut) diff --git a/src/pinky/utmp.rs b/src/pinky/utmp.rs index 0faf6b662..653b50a7a 100644 --- a/src/pinky/utmp.rs +++ b/src/pinky/utmp.rs @@ -1,58 +1,43 @@ -// # 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; +use std::ptr; -pub struct StIter { - f: File, - size: usize, - _p: PhantomData, +#[cfg(unix)] +extern "C" { + fn getutxent() -> *const c_utmp; + fn setutxent(); + fn endutxent(); } -impl Iterator for StIter { - type Item = T; +pub struct UtmpIter; + +impl UtmpIter { + fn new() -> Self { + unsafe { + setutxent(); + } + UtmpIter + } +} + +impl Iterator for UtmpIter { + type Item = c_utmp; 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 + let line = getutxent(); + + if line.is_null() { + endutxent(); + return None; } + + Some(ptr::read(line)) } } } -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) +pub fn read_utmps() -> UtmpIter { + UtmpIter::new() } From 048daee8b9f5d109ab04a80bbf3c730180e2913b Mon Sep 17 00:00:00 2001 From: Knight Date: Tue, 26 Jul 2016 13:41:59 +0800 Subject: [PATCH 06/10] pinky: make ci happy --- src/pinky/pinky.rs | 6 +++--- src/pinky/utmp.rs | 7 +++++++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/pinky/pinky.rs b/src/pinky/pinky.rs index ee175a0a4..6295b00ae 100644 --- a/src/pinky/pinky.rs +++ b/src/pinky/pinky.rs @@ -256,7 +256,7 @@ impl UtmpUtils for utmpx::c_utmp { fn idle_string(when: i64) -> String { thread_local! { - static NOW: time::Tm = time::now(); + static NOW: time::Tm = time::now() } NOW.with(|n| { let duration = n.to_timespec().sec - when; @@ -293,7 +293,7 @@ impl Pinky { let last_change; match pts_path.metadata() { Ok(meta) => { - mesg = if meta.mode() & S_IWGRP != 0 { + mesg = if meta.mode() & (S_IWGRP as u32) != 0 { ' ' } else { '*' @@ -366,7 +366,7 @@ impl Pinky { self.print_heading(); } else { // @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ - // FIXME: WIERD!!! If the following line is removed, + // FIXME: WEIRD!!! If the following line is removed, // getpwnam() will return NULL. // @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ print!(""); diff --git a/src/pinky/utmp.rs b/src/pinky/utmp.rs index 653b50a7a..93f32bfff 100644 --- a/src/pinky/utmp.rs +++ b/src/pinky/utmp.rs @@ -1,3 +1,10 @@ +// 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. +// extern crate uucore; use uucore::utmpx::c_utmp; From f91c60ff701c76a2388ee7b048eeaa3949288d22 Mon Sep 17 00:00:00 2001 From: Knight Date: Tue, 26 Jul 2016 14:27:38 +0800 Subject: [PATCH 07/10] pinky: fix a weird bug: Strings passed to C functions must ends with NULL. --- src/pinky/pinky.rs | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/pinky/pinky.rs b/src/pinky/pinky.rs index 6295b00ae..9fd355d6e 100644 --- a/src/pinky/pinky.rs +++ b/src/pinky/pinky.rs @@ -30,8 +30,7 @@ use std::fs::File; use std::os::unix::fs::MetadataExt; use std::ptr; -use std::ffi::CStr; -use std::ffi::OsStr; +use std::ffi::{CStr, CString, OsStr}; use std::os::unix::ffi::OsStrExt; use std::path::Path; @@ -209,7 +208,9 @@ impl FromChars for String { } pub fn getpw(u: &str) -> Option { - let pw = unsafe { getpwnam(u.as_ptr() as *const i8) }; + let pw = unsafe { + getpwnam(CString::new(u).unwrap().as_ptr()) + }; if !pw.is_null() { let data = unsafe { ptr::read(pw) }; Some(Passwd { @@ -364,12 +365,6 @@ impl Pinky { fn short_pinky(&self) -> IOResult<()> { if self.include_heading { self.print_heading(); - } else { - // @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ - // FIXME: WEIRD!!! If the following line is removed, - // getpwnam() will return NULL. - // @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ - print!(""); } for ut in utmp::read_utmps() { if ut.is_user_process() { From e0c950e1454afc70dcbeca744d53ee89f29ad485 Mon Sep 17 00:00:00 2001 From: Knight Date: Tue, 26 Jul 2016 15:27:33 +0800 Subject: [PATCH 08/10] pinky: canonicalize host --- src/pinky/pinky.rs | 54 ++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 50 insertions(+), 4 deletions(-) diff --git a/src/pinky/pinky.rs b/src/pinky/pinky.rs index 9fd355d6e..306ac153a 100644 --- a/src/pinky/pinky.rs +++ b/src/pinky/pinky.rs @@ -282,6 +282,47 @@ fn time_string(ut: &utmpx::c_utmp) -> String { time::strftime("%Y-%m-%d %H:%M", &tm).unwrap() } +const AI_CANONNAME: libc::c_int = 0x2; + +fn canon_host(host: &str) -> Option { + let hints = libc::addrinfo { + ai_flags: AI_CANONNAME, + ai_family: 0, + ai_socktype: 0, + ai_protocol: 0, + ai_addrlen: 0, + ai_addr: ptr::null_mut(), + ai_canonname: ptr::null_mut(), + ai_next: ptr::null_mut(), + }; + let c_host = CString::new(host).unwrap(); + let mut res = ptr::null_mut(); + let status = unsafe { + libc::getaddrinfo(c_host.as_ptr(), ptr::null(), &hints as *const _, &mut res as *mut _) + }; + if status == 0 { + let info: libc::addrinfo = unsafe { + ptr::read(res as *const _) + }; + // http://lists.gnu.org/archive/html/bug-coreutils/2006-09/msg00300.html + // says Darwin 7.9.0 getaddrinfo returns 0 but sets + // res->ai_canonname to NULL. + let ret = if info.ai_canonname.is_null() { + Some(String::from(host)) + } else { + Some(unsafe { + CString::from_raw(info.ai_canonname).into_string().unwrap() + }) + }; + unsafe { + libc::freeaddrinfo(res); + } + ret + } else { + None + } +} + impl Pinky { fn print_entry(&self, ut: &utmpx::c_utmp) { let mut pts_path = PathBuf::from("/dev"); @@ -337,10 +378,15 @@ impl Pinky { 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); + let mut res = ut_host.split(':'); + let host = match res.next() { + Some(h) => canon_host(&h).unwrap_or(ut_host.clone()), + None => ut_host.clone(), + }; + match res.next() { + Some(d) => print!(" {}:{}", host, d), + None => print!(" {}", host), + } } println!(""); From 6fff3a7665880a41f25fda1d9cf44212d4fe376b Mon Sep 17 00:00:00 2001 From: Knight Date: Tue, 26 Jul 2016 16:43:03 +0800 Subject: [PATCH 09/10] pinky: cleanup the code --- src/pinky/pinky.rs | 9 +++++---- src/uucore/c_types.rs | 31 ++----------------------------- tests/test_stat.rs | 4 ++++ 3 files changed, 11 insertions(+), 33 deletions(-) diff --git a/src/pinky/pinky.rs b/src/pinky/pinky.rs index 306ac153a..cec6022a3 100644 --- a/src/pinky/pinky.rs +++ b/src/pinky/pinky.rs @@ -17,8 +17,7 @@ use uucore::utmpx; extern crate getopts; extern crate libc; -use libc::{uid_t, gid_t, c_char}; -use libc::S_IWGRP; +use libc::{uid_t, gid_t, c_char, S_IWGRP}; extern crate time; @@ -227,7 +226,7 @@ pub fn getpw(u: &str) -> Option { } } -trait Capitalize { +pub trait Capitalize { fn capitalize(&self) -> String; } @@ -278,7 +277,7 @@ fn idle_string(when: i64) -> String { } fn time_string(ut: &utmpx::c_utmp) -> String { - let tm = time::at(time::Timespec::new(ut.ut_tv.tv_sec, ut.ut_tv.tv_usec as i32)); + let tm = time::at(time::Timespec::new(ut.ut_tv.tv_sec as i64, ut.ut_tv.tv_usec as i32)); time::strftime("%Y-%m-%d %H:%M", &tm).unwrap() } @@ -374,6 +373,8 @@ impl Pinky { } } + // WARNING: Because of the definition of `struct utmp`, + // pinky cannot get the correct value of utmp.ut_tv print!(" {}", time_string(&ut)); if self.include_where && ut.ut_host[0] != 0 { diff --git a/src/uucore/c_types.rs b/src/uucore/c_types.rs index 477927ba0..89169979b 100644 --- a/src/uucore/c_types.rs +++ b/src/uucore/c_types.rs @@ -8,6 +8,8 @@ use self::libc::{ uid_t, gid_t, }; +pub use self::libc::passwd as c_passwd; + #[cfg(any(target_os = "macos", target_os = "freebsd"))] use self::libc::time_t; #[cfg(target_os = "macos")] @@ -22,35 +24,6 @@ use std::vec::Vec; use std::ptr::{null_mut, read}; -#[cfg(any(target_os = "macos", target_os = "freebsd"))] -#[repr(C)] -#[derive(Clone, Copy)] -pub struct c_passwd { - pub pw_name: *const c_char, /* user name */ - pub pw_passwd: *const c_char, /* user name */ - pub pw_uid: uid_t, /* user uid */ - pub pw_gid: gid_t, /* user gid */ - pub pw_change: time_t, - pub pw_class: *const c_char, - pub pw_gecos: *const c_char, - pub pw_dir: *const c_char, - pub pw_shell: *const c_char, - pub pw_expire: time_t -} - -#[cfg(target_os = "linux")] -#[repr(C)] -#[derive(Clone, Copy)] -pub struct c_passwd { - pub pw_name: *const c_char, /* user name */ - pub pw_passwd: *const c_char, /* user name */ - pub pw_uid: uid_t, /* user uid */ - pub pw_gid: gid_t, /* user gid */ - pub pw_gecos: *const c_char, - pub pw_dir: *const c_char, - pub pw_shell: *const c_char, -} - #[cfg(any(target_os = "macos", target_os = "freebsd"))] #[repr(C)] pub struct utsname { diff --git a/tests/test_stat.rs b/tests/test_stat.rs index 37dfe68c9..13e93d14d 100644 --- a/tests/test_stat.rs +++ b/tests/test_stat.rs @@ -145,8 +145,11 @@ fn test_invalid_option() { ucmd.fails(); } +#[allow(unused_variable)] const NORMAL_FMTSTR: &'static str = "%a %A %b %B %d %D %f %F %g %G %h %i %m %n %o %s %u %U %w %W %x %X %y %Y %z %Z"; +#[allow(unused_variable)] const DEV_FMTSTR: &'static str = "%a %A %b %B %d %D %f %F %g %G %h %i %m %n %o %s (%t/%T) %u %U %w %W %x %X %y %Y %z %Z"; +#[allow(unused_variable)] const FS_FMTSTR: &'static str = "%a %b %c %d %f %i %l %n %s %S %t %T"; #[test] @@ -230,6 +233,7 @@ fn test_printf() { assert_eq!(ucmd.run().stdout, "123?\r\"\\\x07\x08\x1B\x0C\x0B /\x12wZJ\n"); } +#[allow(dead_code)] fn expected_result(args: &[&str]) -> String { use std::process::Command; From be20d8605dd72ef367b45312a01e449f6cfed35f Mon Sep 17 00:00:00 2001 From: Knight Date: Tue, 26 Jul 2016 16:43:25 +0800 Subject: [PATCH 10/10] pinky: add tests --- src/pinky/Cargo.toml | 7 ---- tests/test_pinky.rs | 80 ++++++++++++++++++++++++++++++++++++++++++++ tests/tests.rs | 1 + 3 files changed, 81 insertions(+), 7 deletions(-) create mode 100644 tests/test_pinky.rs diff --git a/src/pinky/Cargo.toml b/src/pinky/Cargo.toml index 768190736..0e83fbf4a 100644 --- a/src/pinky/Cargo.toml +++ b/src/pinky/Cargo.toml @@ -13,13 +13,6 @@ time = "*" libc = "^0.2" uucore = { path="../uucore" } -[dependencies.clippy] -version = "*" -optional = true - -[features] -default = [] - [[bin]] name = "pinky" path = "main.rs" diff --git a/tests/test_pinky.rs b/tests/test_pinky.rs new file mode 100644 index 000000000..35af9c777 --- /dev/null +++ b/tests/test_pinky.rs @@ -0,0 +1,80 @@ +use common::util::*; + +static UTIL_NAME: &'static str = "pinky"; + +extern crate uu_pinky; +pub use self::uu_pinky::*; + +#[test] +fn test_capitalize() { + assert_eq!("Zbnmasd", "zbnmasd".capitalize()); + assert_eq!("Abnmasd", "Abnmasd".capitalize()); + assert_eq!("1masd", "1masd".capitalize()); + assert_eq!("", "".capitalize()); +} + +#[test] +#[cfg(target_os = "linux")] +fn test_long_format() { + let (_, mut ucmd) = testing(UTIL_NAME); + ucmd.arg("-l").arg("root"); + let expected = "Login name: root In real life: root\nDirectory: /root Shell: /bin/bash\n\n"; + assert_eq!(expected, ucmd.run().stdout); + + let (_, mut ucmd) = testing(UTIL_NAME); + ucmd.arg("-lb").arg("root"); + let expected = "Login name: root In real life: root\n\n"; + assert_eq!(expected, ucmd.run().stdout); +} + +#[test] +#[cfg(target_os = "macos")] +fn test_long_format() { + let (_, mut ucmd) = testing(UTIL_NAME); + ucmd.arg("-l").arg("root"); + let expected = "Login name: root In real life: System Administrator\nDirectory: /var/root Shell: /bin/sh\n\n"; + assert_eq!(expected, ucmd.run().stdout); + + let (_, mut ucmd) = testing(UTIL_NAME); + ucmd.arg("-lb").arg("root"); + let expected = "Login name: root In real life: System Administrator\n\n"; + assert_eq!(expected, ucmd.run().stdout); +} + +#[cfg(target_os = "linux")] +#[test] +#[ignore] +fn test_short_format() { + let (_, mut ucmd) = testing(UTIL_NAME); + let args = ["-s"]; + ucmd.args(&args); + assert_eq!(expected_result(&args), ucmd.run().stdout); + + let (_, mut ucmd) = testing(UTIL_NAME); + let args = ["-f"]; + ucmd.args(&args); + assert_eq!(expected_result(&args), ucmd.run().stdout); + + let (_, mut ucmd) = testing(UTIL_NAME); + let args = ["-w"]; + ucmd.args(&args); + assert_eq!(expected_result(&args), ucmd.run().stdout); + + let (_, mut ucmd) = testing(UTIL_NAME); + let args = ["-i"]; + ucmd.args(&args); + assert_eq!(expected_result(&args), ucmd.run().stdout); + + let (_, mut ucmd) = testing(UTIL_NAME); + let args = ["-q"]; + ucmd.args(&args); + assert_eq!(expected_result(&args), ucmd.run().stdout); +} + +#[cfg(target_os = "linux")] +fn expected_result(args: &[&str]) -> String { + use std::process::Command; + + let output = Command::new(UTIL_NAME).args(args).output().unwrap(); + String::from_utf8_lossy(&output.stdout).into_owned() +} diff --git a/tests/tests.rs b/tests/tests.rs index d867a934f..cb5a755a6 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -29,6 +29,7 @@ unix_only! { "chown", test_chown; "mv", test_mv; "pathchk", test_pathchk; + "pinky", test_pinky; "stdbuf", test_stdbuf; "touch", test_touch; "unlink", test_unlink;