1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-08-04 06:57:47 +00:00

pinky: implement short format

This commit is contained in:
Knight 2016-07-25 20:22:43 +08:00
parent 7fb3eef5e5
commit 3ed49033b7
4 changed files with 263 additions and 34 deletions

View file

@ -9,6 +9,7 @@ path = "pinky.rs"
[dependencies] [dependencies]
getopts = "*" getopts = "*"
time = "*"
libc = "^0.2" libc = "^0.2"
uucore = { path="../uucore" } uucore = { path="../uucore" }

View file

@ -7,25 +7,38 @@
// For the full copyright and license information, please view the LICENSE // For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code. // file that was distributed with this source code.
// //
#![cfg_attr(feature="clippy", feature(plugin))] #![cfg_attr(feature="clippy", feature(plugin))]
#![cfg_attr(feature="clippy", plugin(clippy))] #![cfg_attr(feature="clippy", plugin(clippy))]
#[macro_use] #[macro_use]
extern crate uucore; extern crate uucore;
use uucore::c_types::getpwnam; use uucore::c_types::getpwnam;
use uucore::utmpx;
extern crate getopts; extern crate getopts;
extern crate libc; extern crate libc;
use libc::{uid_t, gid_t, c_char}; use libc::{uid_t, gid_t, c_char};
use libc::S_IWGRP;
extern crate time;
use std::io::prelude::*; use std::io::prelude::*;
use std::io::BufReader; use std::io::BufReader;
use std::ptr; use std::io::Result as IOResult;
use std::fs::File; use std::fs::File;
use std::os::unix::fs::MetadataExt;
use std::ptr;
use std::ffi::CStr; use std::ffi::CStr;
use std::ffi::OsStr;
use std::os::unix::ffi::OsStrExt;
use std::path::Path;
use std::path::PathBuf; use std::path::PathBuf;
mod utmp;
static NAME: &'static str = "pinky"; static NAME: &'static str = "pinky";
static VERSION: &'static str = env!("CARGO_PKG_VERSION"); static VERSION: &'static str = env!("CARGO_PKG_VERSION");
@ -80,7 +93,7 @@ pub fn uumain(args: Vec<String>) -> i32 {
A lightweight 'finger' program; print user information. A lightweight 'finger' program; print user information.
The utmp file will be {}", The utmp file will be {}",
NAME, NAME,
"/var/run/utmp"); utmpx::DEFAULT_FILE);
return 0; return 0;
} }
@ -113,17 +126,20 @@ The utmp file will be {}",
// if true, use the "short" output format. // if true, use the "short" output format.
let do_short_format = !matches.opt_present("l"); 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") { if matches.opt_present("w") {
include_fullname = false; include_fullname = false;
} }
if matches.opt_present("i") { if matches.opt_present("i") {
include_fullname = false; include_fullname = false;
// FIXME: include_where = false;
} }
if matches.opt_present("q") { if matches.opt_present("q") {
include_fullname = false; include_fullname = false;
include_idle = false; include_idle = false;
// FIXME: include_where = false;
} }
if !do_short_format && matches.free.is_empty() { if !do_short_format && matches.free.is_empty() {
@ -138,12 +154,17 @@ The utmp file will be {}",
include_project: include_project, include_project: include_project,
include_plan: include_plan, include_plan: include_plan,
include_home_and_shell: include_home_and_shell, include_home_and_shell: include_home_and_shell,
do_short_format: do_short_format, include_where: include_where,
users: matches.free, names: matches.free,
}; };
if do_short_format { if do_short_format {
pk.short_pinky() if let Err(e) = pk.short_pinky() {
disp_err!("{}", e);
1
} else {
0
}
} else { } else {
pk.long_pinky() pk.long_pinky()
} }
@ -156,13 +177,13 @@ struct Pinky {
include_fullname: bool, include_fullname: bool,
include_project: bool, include_project: bool,
include_plan: bool, include_plan: bool,
include_where: bool,
include_home_and_shell: bool, include_home_and_shell: bool,
do_short_format: bool, names: Vec<String>,
users: Vec<String>,
} }
#[derive(Debug)] #[derive(Debug)]
struct Passwd { pub struct Passwd {
pw_name: String, pw_name: String,
pw_passwd: String, pw_passwd: String,
pw_uid: uid_t, pw_uid: uid_t,
@ -172,35 +193,39 @@ struct Passwd {
pw_shell: String, pw_shell: String,
} }
fn getpw(u: &str) -> Option<Passwd> { 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<Passwd> {
let pw = unsafe { getpwnam(u.as_ptr() as *const i8) }; let pw = unsafe { getpwnam(u.as_ptr() as *const i8) };
if !pw.is_null() { if !pw.is_null() {
let data = unsafe { ptr::read(pw) }; let data = unsafe { ptr::read(pw) };
Some(Passwd { Some(Passwd {
pw_name: cstr2string(data.pw_name), pw_name: String::from_chars(data.pw_name),
pw_passwd: cstr2string(data.pw_passwd), pw_passwd: String::from_chars(data.pw_passwd),
pw_uid: data.pw_uid, pw_uid: data.pw_uid,
pw_gid: data.pw_gid, pw_gid: data.pw_gid,
pw_dir: cstr2string(data.pw_dir), pw_dir: String::from_chars(data.pw_dir),
pw_gecos: cstr2string(data.pw_gecos), pw_gecos: String::from_chars(data.pw_gecos),
pw_shell: cstr2string(data.pw_shell), pw_shell: String::from_chars(data.pw_shell),
}) })
} else { } else {
None 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 { trait Capitalize {
fn capitalize(&self) -> String; 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 { impl Pinky {
fn short_pinky(&self) -> i32 { fn print_entry(&self, ut: &utmpx::c_utmp) {
0 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 { fn long_pinky(&self) -> i32 {
for u in &self.users { for u in &self.names {
print!("Login name: {:<28}In real life: ", u); print!("Login name: {:<28}In real life: ", u);
if let Some(pw) = getpw(u) { if let Some(pw) = getpw(u) {
println!(" {}", pw.pw_gecos.replace("&", &pw.pw_name.capitalize())); println!(" {}", pw.pw_gecos.replace("&", &pw.pw_name.capitalize()));

58
src/pinky/utmp.rs Normal file
View file

@ -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<T> {
f: File,
size: usize,
_p: PhantomData<T>,
}
impl<T> Iterator for StIter<T> {
type Item = T;
fn next(&mut self) -> Option<Self::Item> {
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<T, P: AsRef<Path>>(p: P) -> IOResult<StIter<T>> {
Ok(StIter {
f: try!(File::open(p)),
size: mem::size_of::<T>(),
_p: PhantomData,
})
}
pub fn read_utmps() -> IOResult<StIter<c_utmp>> {
read_structs(utmpx::DEFAULT_FILE)
}

View file

@ -2,7 +2,7 @@
extern crate libc; 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")] #[cfg(target_os = "linux")]
mod utmpx { mod utmpx {
use super::libc; use super::libc;
@ -25,6 +25,12 @@ mod utmpx {
pub const DEAD_PROCESS: libc::c_short = 8; pub const DEAD_PROCESS: libc::c_short = 8;
pub const ACCOUNTING: libc::c_short = 9; 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)] #[repr(C)]
pub struct c_exit_status { pub struct c_exit_status {
pub e_termination: libc::c_short, pub e_termination: libc::c_short,
@ -41,8 +47,8 @@ mod utmpx {
pub ut_user: [libc::c_char; UT_NAMESIZE], pub ut_user: [libc::c_char; UT_NAMESIZE],
pub ut_host: [libc::c_char; UT_HOSTSIZE], pub ut_host: [libc::c_char; UT_HOSTSIZE],
pub ut_exit: c_exit_status, pub ut_exit: c_exit_status,
pub ut_session: libc::c_long, pub ut_session: libc::int32_t,
pub ut_tv: libc::timeval, pub ut_tv: timeval,
pub ut_addr_v6: [libc::int32_t; 4], pub ut_addr_v6: [libc::int32_t; 4],
pub __unused: [libc::c_char; 20], pub __unused: [libc::c_char; 20],