diff --git a/src/stat/fsext.rs b/src/stat/fsext.rs index a169ed084..eb81864cd 100644 --- a/src/stat/fsext.rs +++ b/src/stat/fsext.rs @@ -10,9 +10,9 @@ extern crate libc; extern crate time; use self::time::Timespec; -pub use self::libc::{S_IFMT, S_IFDIR, S_IFCHR, S_IFBLK, S_IFREG, S_IFIFO, S_IFLNK, S_IFSOCK, - S_ISUID, S_ISGID, S_ISVTX, S_IRUSR, S_IWUSR, S_IXUSR, S_IRGRP, S_IWGRP, - S_IXGRP, S_IROTH, S_IWOTH, S_IXOTH, mode_t, c_int, strerror}; +pub use self::libc::{S_IFMT, S_IFDIR, S_IFCHR, S_IFBLK, S_IFREG, S_IFIFO, S_IFLNK, S_IFSOCK, S_ISUID, S_ISGID, + S_ISVTX, S_IRUSR, S_IWUSR, S_IXUSR, S_IRGRP, S_IWGRP, S_IXGRP, S_IROTH, S_IWOTH, S_IXOTH, mode_t, + c_int, strerror}; #[macro_export] macro_rules! has { @@ -248,8 +248,8 @@ pub fn statfs>(path: P) -> Result _ => { let errno = IOError::last_os_error().raw_os_error().unwrap_or(0); Err(CString::from_raw(strerror(errno)) - .into_string() - .unwrap_or("Unknown Error".to_owned())) + .into_string() + .unwrap_or("Unknown Error".to_owned())) } } } @@ -377,54 +377,3 @@ pub fn pretty_fstype<'a>(fstype: i64) -> Cow<'a, str> { other => format!("UNKNOWN ({:#x})", other).into(), } } - -#[cfg(test)] -mod test_fsext { - use super::*; - - #[test] - fn test_access() { - assert_eq!("drwxr-xr-x", pretty_access(S_IFDIR | 0o755)); - assert_eq!("-rw-r--r--", pretty_access(S_IFREG | 0o644)); - assert_eq!("srw-r-----", pretty_access(S_IFSOCK | 0o640)); - assert_eq!("lrw-r-xr-x", pretty_access(S_IFLNK | 0o655)); - assert_eq!("?rw-r-xr-x", pretty_access(0o655)); - - assert_eq!("brwSr-xr-x", - pretty_access(S_IFBLK | S_ISUID as mode_t | 0o655)); - assert_eq!("brwsr-xr-x", - pretty_access(S_IFBLK | S_ISUID as mode_t | 0o755)); - - assert_eq!("prw---sr--", - pretty_access(S_IFIFO | S_ISGID as mode_t | 0o614)); - assert_eq!("prw---Sr--", - pretty_access(S_IFIFO | S_ISGID as mode_t | 0o604)); - - assert_eq!("c---r-xr-t", - pretty_access(S_IFCHR | S_ISVTX as mode_t | 0o055)); - assert_eq!("c---r-xr-T", - pretty_access(S_IFCHR | S_ISVTX as mode_t | 0o054)); - } - - #[test] - fn test_file_type() { - assert_eq!("block special file", pretty_filetype(S_IFBLK, 0)); - assert_eq!("character special file", pretty_filetype(S_IFCHR, 0)); - assert_eq!("regular file", pretty_filetype(S_IFREG, 1)); - assert_eq!("regular empty file", pretty_filetype(S_IFREG, 0)); - assert_eq!("weird file", pretty_filetype(0, 0)); - } - - #[test] - fn test_fs_type() { - assert_eq!("ext2/ext3", pretty_fstype(0xEF53)); - assert_eq!("tmpfs", pretty_fstype(0x01021994)); - assert_eq!("nfs", pretty_fstype(0x6969)); - assert_eq!("btrfs", pretty_fstype(0x9123683e)); - assert_eq!("xfs", pretty_fstype(0x58465342)); - assert_eq!("zfs", pretty_fstype(0x2FC12FC1)); - assert_eq!("ntfs", pretty_fstype(0x5346544e)); - assert_eq!("fat", pretty_fstype(0x4006)); - assert_eq!("UNKNOWN (0x1234)", pretty_fstype(0x1234)); - } -} diff --git a/src/stat/stat.rs b/src/stat/stat.rs index 58152a080..9c05030b1 100644 --- a/src/stat/stat.rs +++ b/src/stat/stat.rs @@ -13,7 +13,7 @@ use getopts::Options; #[macro_use] mod fsext; -use fsext::*; +pub use fsext::*; #[macro_use] extern crate uucore; @@ -26,9 +26,6 @@ use std::os::unix::fs::{FileTypeExt, MetadataExt}; use std::path::Path; use std::convert::AsRef; -#[cfg(test)] -mod test_stat; - macro_rules! check_bound { ($str: ident, $bound:expr, $beg: expr, $end: expr) => ( if $end >= $bound { @@ -43,7 +40,7 @@ macro_rules! fill_string { ) } macro_rules! extend_digits { - ($str: ident, $min: expr) => ( + ($str: expr, $min: expr) => ( if $min > $str.len() { let mut pad = String::with_capacity($min); fill_string!(pad, '0', $min - $str.len()); @@ -182,6 +179,23 @@ impl ScanUtil for str { } } +pub fn group_num<'a>(s: &'a str) -> Cow<'a, str> { + assert!(s.chars().all(char::is_numeric)); + if s.len() < 4 { + return s.into(); + } + let mut res = String::with_capacity((s.len() - 1) / 3); + let mut alone = (s.len() - 1) % 3 + 1; + res.push_str(&s[..alone]); + while alone != s.len() { + res.push(','); + res.push_str(&s[alone..alone + 3]); + alone += 3; + } + res.into() +} + + pub struct Stater { follow: bool, showfs: bool, @@ -262,13 +276,23 @@ fn print_it(arg: &str, otype: OutputType, flag: u8, width: usize, precision: i32 print_adjusted!(s, left_align, width, ' '); } OutputType::Integer => { + let arg = if has!(flag, F_GROUP) { + group_num(arg) + } else { + Cow::Borrowed(arg) + }; let min_digits = cmp::max(precision, arg.len() as i32) as usize; - let extended: Cow = extend_digits!(arg, min_digits); + let extended: Cow = extend_digits!(arg.as_ref(), min_digits); print_adjusted!(extended, left_align, has_sign, prefix, width, padding_char); } OutputType::Unsigned => { + let arg = if has!(flag, F_GROUP) { + group_num(arg) + } else { + Cow::Borrowed(arg) + }; let min_digits = cmp::max(precision, arg.len() as i32) as usize; - let extended: Cow = extend_digits!(arg, min_digits); + let extended: Cow = extend_digits!(arg.as_ref(), min_digits); print_adjusted!(extended, left_align, width, padding_char); } OutputType::UnsignedOct => { @@ -354,8 +378,7 @@ impl Stater { '-' => flag |= F_LEFT, ' ' => flag |= F_SPACE, '+' => flag |= F_SIGN, - // '\'' => flag |= F_GROUP, - '\'' => unimplemented!(), + '\'' => flag |= F_GROUP, 'I' => unimplemented!(), _ => break, } @@ -469,16 +492,13 @@ impl Stater { } else { try!(Stater::generate_tokens(&fmtstr, use_printf)) }; - let default_dev_tokens = Stater::generate_tokens(&Stater::default_fmt(showfs, terse, true), - use_printf) - .unwrap(); + let default_dev_tokens = Stater::generate_tokens(&Stater::default_fmt(showfs, terse, true), use_printf) + .unwrap(); let reader = BufReader::new(File::open(MOUNT_INFO).expect("Failed to read /etc/mtab")); let mut mount_list = reader.lines() .filter_map(|s| s.ok()) - .filter_map(|line| { - line.split_whitespace().nth(1).map(|s| s.to_owned()) - }) + .filter_map(|line| line.split_whitespace().nth(1).map(|s| s.to_owned())) .collect::>(); // Reverse sort. The longer comes first. mount_list.sort_by(|a, b| b.cmp(a)); @@ -526,8 +546,7 @@ impl Stater { match result { Ok(meta) => { let ftype = meta.file_type(); - let tokens = if self.from_user || - !(ftype.is_char_device() || ftype.is_block_device()) { + let tokens = if self.from_user || !(ftype.is_char_device() || ftype.is_block_device()) { &self.default_tokens } else { &self.default_dev_tokens @@ -622,11 +641,14 @@ impl Stater { // quoted file name with dereference if symbolic link 'N' => { if ftype.is_symlink() { - arg = format!("`{}' -> `{}'", - file, - fs::read_link(file) - .expect("Invalid symlink") - .to_string_lossy()); + let dst = match fs::read_link(file) { + Ok(path) => path, + Err(e) => { + println!("{}", e); + return 1; + } + }; + arg = format!("`{}' -> `{}'", file, dst.to_string_lossy()); } else { arg = format!("`{}'", file); } @@ -667,32 +689,29 @@ impl Stater { // time of file birth, human-readable; - if unknown 'w' => { - // Unstable. Commented - //arg = if let Ok(elapsed) = meta.created() - //.map(|t| { - //t.elapsed().unwrap() - //}) { - //pretty_time(elapsed.as_secs() as i64, - //elapsed.subsec_nanos() as i64) - //} else { - //"-".to_owned() - //}; - arg = "-".to_owned(); + arg = if cfg!(feature = "nightly") { + // Unstable + meta.created() + .map(|t| t.elapsed().unwrap()) + .map(|e| pretty_time(e.as_secs() as i64, e.subsec_nanos() as i64)) + .unwrap_or("-".to_owned()) + } else { + "-".to_owned() + }; otype = OutputType::Str; } // time of file birth, seconds since Epoch; 0 if unknown 'W' => { - // Unstable. Commented - //arg = if let Ok(elapsed) = meta.created() - //.map(|t| { - //t.elapsed().unwrap() - //}) { - //format!("{}", elapsed.as_secs()) - //} else { - //"0".to_owned() - //}; - arg = "0".to_owned(); + arg = if cfg!(feature = "nightly") { + // Unstable + meta.created() + .map(|t| t.elapsed().unwrap()) + .map(|e| format!("{}", e.as_secs())) + .unwrap_or("0".to_owned()) + } else { + "0".to_owned() + }; otype = OutputType::Integer; } diff --git a/tests/test_stat.rs b/tests/test_stat.rs index baf924528..37dfe68c9 100644 --- a/tests/test_stat.rs +++ b/tests/test_stat.rs @@ -1,8 +1,142 @@ use common::util::*; +extern crate uu_stat; +pub use self::uu_stat::*; + static UTIL_NAME: &'static str = "stat"; -use std::process::Command; +#[cfg(test)] +mod test_fsext { + use super::*; + + #[test] + fn test_access() { + assert_eq!("drwxr-xr-x", pretty_access(S_IFDIR | 0o755)); + assert_eq!("-rw-r--r--", pretty_access(S_IFREG | 0o644)); + assert_eq!("srw-r-----", pretty_access(S_IFSOCK | 0o640)); + assert_eq!("lrw-r-xr-x", pretty_access(S_IFLNK | 0o655)); + assert_eq!("?rw-r-xr-x", pretty_access(0o655)); + + assert_eq!("brwSr-xr-x", + pretty_access(S_IFBLK | S_ISUID as mode_t | 0o655)); + assert_eq!("brwsr-xr-x", + pretty_access(S_IFBLK | S_ISUID as mode_t | 0o755)); + + assert_eq!("prw---sr--", + pretty_access(S_IFIFO | S_ISGID as mode_t | 0o614)); + assert_eq!("prw---Sr--", + pretty_access(S_IFIFO | S_ISGID as mode_t | 0o604)); + + assert_eq!("c---r-xr-t", + pretty_access(S_IFCHR | S_ISVTX as mode_t | 0o055)); + assert_eq!("c---r-xr-T", + pretty_access(S_IFCHR | S_ISVTX as mode_t | 0o054)); + } + + #[test] + fn test_file_type() { + assert_eq!("block special file", pretty_filetype(S_IFBLK, 0)); + assert_eq!("character special file", pretty_filetype(S_IFCHR, 0)); + assert_eq!("regular file", pretty_filetype(S_IFREG, 1)); + assert_eq!("regular empty file", pretty_filetype(S_IFREG, 0)); + assert_eq!("weird file", pretty_filetype(0, 0)); + } + + #[test] + fn test_fs_type() { + assert_eq!("ext2/ext3", pretty_fstype(0xEF53)); + assert_eq!("tmpfs", pretty_fstype(0x01021994)); + assert_eq!("nfs", pretty_fstype(0x6969)); + assert_eq!("btrfs", pretty_fstype(0x9123683e)); + assert_eq!("xfs", pretty_fstype(0x58465342)); + assert_eq!("zfs", pretty_fstype(0x2FC12FC1)); + assert_eq!("ntfs", pretty_fstype(0x5346544e)); + assert_eq!("fat", pretty_fstype(0x4006)); + assert_eq!("UNKNOWN (0x1234)", pretty_fstype(0x1234)); + } +} + +#[test] +fn test_scanutil() { + assert_eq!(Some((-5, 2)), "-5zxc".scan_num::()); + assert_eq!(Some((51, 2)), "51zxc".scan_num::()); + assert_eq!(Some((192, 4)), "+192zxc".scan_num::()); + assert_eq!(None, "z192zxc".scan_num::()); + + assert_eq!(Some(('a', 3)), "141zxc".scan_char(8)); + assert_eq!(Some(('\n', 2)), "12qzxc".scan_char(8)); + assert_eq!(Some(('\r', 1)), "dqzxc".scan_char(16)); + assert_eq!(None, "z2qzxc".scan_char(8)); +} + +#[test] +fn test_groupnum() { + assert_eq!("12,379,821,234", group_num("12379821234")); + assert_eq!("21,234", group_num("21234")); + assert_eq!("821,234", group_num("821234")); + assert_eq!("1,821,234", group_num("1821234")); + assert_eq!("1,234", group_num("1234")); + assert_eq!("234", group_num("234")); + assert_eq!("24", group_num("24")); + assert_eq!("4", group_num("4")); + assert_eq!("", group_num("")); +} + +#[cfg(test)] +mod test_generate_tokens { + use super::*; + + #[test] + fn normal_format() { + let s = "%'010.2ac%-#5.w\n"; + let expected = vec![Token::Directive { + flag: F_GROUP | F_ZERO, + width: 10, + precision: 2, + format: 'a', + }, + Token::Char('c'), + Token::Directive { + flag: F_LEFT | F_ALTER, + width: 5, + precision: 0, + format: 'w', + }, + Token::Char('\n')]; + assert_eq!(&expected, &Stater::generate_tokens(s, false).unwrap()); + } + + #[test] + fn printf_format() { + let s = "%-# 15a\\r\\\"\\\\\\a\\b\\e\\f\\v%+020.-23w\\x12\\167\\132\\112\\n"; + let expected = vec![Token::Directive { + flag: F_LEFT | F_ALTER | F_SPACE, + width: 15, + precision: -1, + format: 'a', + }, + Token::Char('\r'), + Token::Char('"'), + Token::Char('\\'), + Token::Char('\x07'), + Token::Char('\x08'), + Token::Char('\x1B'), + Token::Char('\x0C'), + Token::Char('\x0B'), + Token::Directive { + flag: F_SIGN | F_ZERO, + width: 20, + precision: -1, + format: 'w', + }, + Token::Char('\x12'), + Token::Char('w'), + Token::Char('Z'), + Token::Char('J'), + Token::Char('\n')]; + assert_eq!(&expected, &Stater::generate_tokens(s, true).unwrap()); + } +} #[test] fn test_invalid_option() { @@ -11,6 +145,10 @@ fn test_invalid_option() { ucmd.fails(); } +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"; +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"; +const FS_FMTSTR: &'static str = "%a %b %c %d %f %i %l %n %s %S %t %T"; + #[test] #[cfg(target_os = "linux")] fn test_terse_fs_format() { @@ -24,9 +162,9 @@ fn test_terse_fs_format() { #[cfg(target_os = "linux")] fn test_fs_format() { let (_, mut ucmd) = testing(UTIL_NAME); - let args = ["-f", "--format=%n %i 0x%t %T", "/dev/shm"]; + let args = ["-f", "-c", FS_FMTSTR, "/dev/shm"]; ucmd.args(&args); - assert_eq!(ucmd.run().stdout, "/dev/shm 0 0x1021994 tmpfs\n"); + assert_eq!(ucmd.run().stdout, expected_result(&args)); } #[test] @@ -42,7 +180,7 @@ fn test_terse_normal_format() { #[cfg(target_os = "linux")] fn test_normal_format() { let (_, mut ucmd) = testing(UTIL_NAME); - let args = ["/boot"]; + let args = ["-c", NORMAL_FMTSTR, "/boot"]; ucmd.args(&args); assert_eq!(ucmd.run().stdout, expected_result(&args)); } @@ -51,7 +189,7 @@ fn test_normal_format() { #[cfg(target_os = "linux")] fn test_follow_symlink() { let (_, mut ucmd) = testing(UTIL_NAME); - let args = ["-L", "/dev/cdrom"]; + let args = ["-L", "-c", DEV_FMTSTR, "/dev/cdrom"]; ucmd.args(&args); assert_eq!(ucmd.run().stdout, expected_result(&args)); } @@ -60,7 +198,7 @@ fn test_follow_symlink() { #[cfg(target_os = "linux")] fn test_symlink() { let (_, mut ucmd) = testing(UTIL_NAME); - let args = ["/dev/cdrom"]; + let args = ["-c", DEV_FMTSTR, "/dev/cdrom"]; ucmd.args(&args); assert_eq!(ucmd.run().stdout, expected_result(&args)); } @@ -69,7 +207,7 @@ fn test_symlink() { #[cfg(target_os = "linux")] fn test_char() { let (_, mut ucmd) = testing(UTIL_NAME); - let args = ["/dev/zero"]; + let args = ["-c", DEV_FMTSTR, "/dev/zero"]; ucmd.args(&args); assert_eq!(ucmd.run().stdout, expected_result(&args)); } @@ -78,7 +216,7 @@ fn test_char() { #[cfg(target_os = "linux")] fn test_multi_files() { let (_, mut ucmd) = testing(UTIL_NAME); - let args = ["/dev", "/usr/lib", "/etc/fstab", "/var"]; + let args = ["-c", NORMAL_FMTSTR, "/dev", "/usr/lib", "/etc/fstab", "/var"]; ucmd.args(&args); assert_eq!(ucmd.run().stdout, expected_result(&args)); } @@ -93,6 +231,8 @@ fn test_printf() { } 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() }