mirror of
https://github.com/uutils/coreutils
synced 2024-07-21 09:54:42 +00:00
commit
da0de488e6
|
@ -24,6 +24,7 @@ unix = [
|
|||
"nice",
|
||||
"nohup",
|
||||
"pathchk",
|
||||
"pinky",
|
||||
"stat",
|
||||
"stdbuf",
|
||||
"timeout",
|
||||
|
@ -145,6 +146,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" }
|
||||
|
|
2
Makefile
2
Makefile
|
@ -116,6 +116,7 @@ UNIX_PROGS := \
|
|||
nice \
|
||||
nohup \
|
||||
pathchk \
|
||||
pinky \
|
||||
stat \
|
||||
stdbuf \
|
||||
timeout \
|
||||
|
@ -164,6 +165,7 @@ TEST_PROGS := \
|
|||
od \
|
||||
paste \
|
||||
pathchk \
|
||||
pinky \
|
||||
printf \
|
||||
ptx \
|
||||
pwd \
|
||||
|
|
|
@ -149,7 +149,6 @@ To do
|
|||
|
||||
- chcon
|
||||
- chgrp
|
||||
- chown
|
||||
- copy
|
||||
- cp (not much done)
|
||||
- csplit
|
||||
|
@ -163,7 +162,6 @@ To do
|
|||
- mv (almost done, one more option)
|
||||
- numfmt
|
||||
- od (in progress, needs lots of work)
|
||||
- pinky
|
||||
- pr
|
||||
- printf
|
||||
- remove
|
||||
|
|
18
src/pinky/Cargo.toml
Normal file
18
src/pinky/Cargo.toml
Normal file
|
@ -0,0 +1,18 @@
|
|||
[package]
|
||||
name = "pinky"
|
||||
version = "0.0.1"
|
||||
authors = []
|
||||
|
||||
[lib]
|
||||
name = "uu_pinky"
|
||||
path = "pinky.rs"
|
||||
|
||||
[dependencies]
|
||||
getopts = "*"
|
||||
time = "*"
|
||||
libc = "^0.2"
|
||||
uucore = { path="../uucore" }
|
||||
|
||||
[[bin]]
|
||||
name = "pinky"
|
||||
path = "main.rs"
|
5
src/pinky/main.rs
Normal file
5
src/pinky/main.rs
Normal file
|
@ -0,0 +1,5 @@
|
|||
extern crate uu_pinky;
|
||||
|
||||
fn main() {
|
||||
std::process::exit(uu_pinky::uumain(std::env::args().collect()));
|
||||
}
|
477
src/pinky/pinky.rs
Normal file
477
src/pinky/pinky.rs
Normal file
|
@ -0,0 +1,477 @@
|
|||
#![crate_name = "uu_pinky"]
|
||||
|
||||
// This file is part of the uutils coreutils package.
|
||||
//
|
||||
// (c) Jian Zeng <anonymousknight96@gmail.com>
|
||||
//
|
||||
// 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, S_IWGRP};
|
||||
|
||||
extern crate time;
|
||||
|
||||
use std::io::prelude::*;
|
||||
use std::io::BufReader;
|
||||
use std::io::Result as IOResult;
|
||||
|
||||
use std::fs::File;
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
|
||||
use std::ptr;
|
||||
use std::ffi::{CStr, CString, 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");
|
||||
|
||||
const BUFSIZE: usize = 1024;
|
||||
|
||||
pub fn uumain(args: Vec<String>) -> 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,
|
||||
utmpx::DEFAULT_FILE);
|
||||
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 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;
|
||||
include_where = false;
|
||||
}
|
||||
if matches.opt_present("q") {
|
||||
include_fullname = false;
|
||||
include_idle = false;
|
||||
include_where = false;
|
||||
}
|
||||
|
||||
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,
|
||||
include_where: include_where,
|
||||
names: matches.free,
|
||||
};
|
||||
|
||||
if do_short_format {
|
||||
if let Err(e) = pk.short_pinky() {
|
||||
disp_err!("{}", e);
|
||||
1
|
||||
} else {
|
||||
0
|
||||
}
|
||||
} else {
|
||||
pk.long_pinky()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
struct Pinky {
|
||||
include_idle: bool,
|
||||
include_heading: bool,
|
||||
include_fullname: bool,
|
||||
include_project: bool,
|
||||
include_plan: bool,
|
||||
include_where: bool,
|
||||
include_home_and_shell: bool,
|
||||
names: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub 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,
|
||||
}
|
||||
|
||||
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(CString::new(u).unwrap().as_ptr())
|
||||
};
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
pub 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
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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 as i32));
|
||||
time::strftime("%Y-%m-%d %H:%M", &tm).unwrap()
|
||||
}
|
||||
|
||||
const AI_CANONNAME: libc::c_int = 0x2;
|
||||
|
||||
fn canon_host(host: &str) -> Option<String> {
|
||||
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");
|
||||
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 as u32) != 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}", "?????");
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
let ut_host = String::from_chars(ut.ut_host.as_ref().as_ptr());
|
||||
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!("");
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
for ut in 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.names {
|
||||
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: Read>(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);
|
||||
}
|
||||
}
|
50
src/pinky/utmp.rs
Normal file
50
src/pinky/utmp.rs
Normal file
|
@ -0,0 +1,50 @@
|
|||
// This file is part of the uutils coreutils package.
|
||||
//
|
||||
// (c) Jian Zeng <anonymousknight96@gmail.com>
|
||||
//
|
||||
// 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;
|
||||
|
||||
use std::ptr;
|
||||
|
||||
#[cfg(unix)]
|
||||
extern "C" {
|
||||
fn getutxent() -> *const c_utmp;
|
||||
fn setutxent();
|
||||
fn endutxent();
|
||||
}
|
||||
|
||||
pub struct UtmpIter;
|
||||
|
||||
impl UtmpIter {
|
||||
fn new() -> Self {
|
||||
unsafe {
|
||||
setutxent();
|
||||
}
|
||||
UtmpIter
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for UtmpIter {
|
||||
type Item = c_utmp;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
unsafe {
|
||||
let line = getutxent();
|
||||
|
||||
if line.is_null() {
|
||||
endutxent();
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(ptr::read(line))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_utmps() -> UtmpIter {
|
||||
UtmpIter::new()
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
80
tests/test_pinky.rs
Normal file
80
tests/test_pinky.rs
Normal file
|
@ -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()
|
||||
}
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -30,6 +30,7 @@ unix_only! {
|
|||
"install", test_install;
|
||||
"mv", test_mv;
|
||||
"pathchk", test_pathchk;
|
||||
"pinky", test_pinky;
|
||||
"stdbuf", test_stdbuf;
|
||||
"touch", test_touch;
|
||||
"unlink", test_unlink;
|
||||
|
|
Loading…
Reference in a new issue