mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-08-03 22:47:46 +00:00
pinky: implement short format
This commit is contained in:
parent
7fb3eef5e5
commit
3ed49033b7
4 changed files with 263 additions and 34 deletions
|
@ -9,6 +9,7 @@ path = "pinky.rs"
|
|||
|
||||
[dependencies]
|
||||
getopts = "*"
|
||||
time = "*"
|
||||
libc = "^0.2"
|
||||
uucore = { path="../uucore" }
|
||||
|
||||
|
|
|
@ -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<String>) -> 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<String>,
|
||||
names: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Passwd {
|
||||
pub struct Passwd {
|
||||
pw_name: String,
|
||||
pw_passwd: String,
|
||||
pw_uid: uid_t,
|
||||
|
@ -172,34 +193,38 @@ struct Passwd {
|
|||
pw_shell: String,
|
||||
}
|
||||
|
||||
fn getpw(u: &str) -> Option<Passwd> {
|
||||
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
|
||||
}
|
||||
trait FromChars {
|
||||
fn from_chars(*const c_char) -> Self;
|
||||
}
|
||||
|
||||
impl FromChars for String {
|
||||
#[inline]
|
||||
fn cstr2string(ptr: *const c_char) -> String {
|
||||
fn from_chars(ptr: *const c_char) -> Self {
|
||||
if ptr.is_null() {
|
||||
println!("null ptr");
|
||||
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) };
|
||||
if !pw.is_null() {
|
||||
let data = unsafe { ptr::read(pw) };
|
||||
Some(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: String::from_chars(data.pw_dir),
|
||||
pw_gecos: String::from_chars(data.pw_gecos),
|
||||
pw_shell: String::from_chars(data.pw_shell),
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
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()));
|
||||
|
|
58
src/pinky/utmp.rs
Normal file
58
src/pinky/utmp.rs
Normal 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)
|
||||
}
|
|
@ -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],
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue