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:
parent
7fb3eef5e5
commit
3ed49033b7
4 changed files with 263 additions and 34 deletions
|
@ -9,6 +9,7 @@ path = "pinky.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
getopts = "*"
|
getopts = "*"
|
||||||
|
time = "*"
|
||||||
libc = "^0.2"
|
libc = "^0.2"
|
||||||
uucore = { path="../uucore" }
|
uucore = { path="../uucore" }
|
||||||
|
|
||||||
|
|
|
@ -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,34 +193,38 @@ struct Passwd {
|
||||||
pw_shell: String,
|
pw_shell: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn getpw(u: &str) -> Option<Passwd> {
|
trait FromChars {
|
||||||
let pw = unsafe { getpwnam(u.as_ptr() as *const i8) };
|
fn from_chars(*const c_char) -> Self;
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl FromChars for String {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn cstr2string(ptr: *const c_char) -> String {
|
fn from_chars(ptr: *const c_char) -> Self {
|
||||||
if ptr.is_null() {
|
if ptr.is_null() {
|
||||||
println!("null ptr");
|
|
||||||
return "".to_owned();
|
return "".to_owned();
|
||||||
}
|
}
|
||||||
let s = unsafe { CStr::from_ptr(ptr) };
|
let s = unsafe { CStr::from_ptr(ptr) };
|
||||||
s.to_string_lossy().into_owned()
|
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 {
|
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
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;
|
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],
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue