From 3202c18c5411d1aee0951f86ba57e34c46036737 Mon Sep 17 00:00:00 2001 From: Knight Date: Tue, 31 May 2016 13:21:55 +0800 Subject: [PATCH 01/12] stat: add Cargo.toml --- src/stat/Cargo.toml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 src/stat/Cargo.toml diff --git a/src/stat/Cargo.toml b/src/stat/Cargo.toml new file mode 100644 index 000000000..158f88f1f --- /dev/null +++ b/src/stat/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "stat" +version = "0.0.1" +authors = [] + +[lib] +name = "uu_stat" +path = "stat.rs" + +[dependencies] +getopts = "*" +libc = "*" +time = "*" +users = "*" +uucore = { path="../uucore" } + +[[bin]] +name = "stat" +path = "main.rs" From 0926cd43ac7ad35646b915b2086fbf7799ac5af7 Mon Sep 17 00:00:00 2001 From: Knight Date: Tue, 31 May 2016 13:22:19 +0800 Subject: [PATCH 02/12] stat: add fsext.rs Mainly includes the pretty-* functions, turning the file types, file system types and time into human-readable form. --- src/stat/fsext.rs | 359 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 359 insertions(+) create mode 100644 src/stat/fsext.rs diff --git a/src/stat/fsext.rs b/src/stat/fsext.rs new file mode 100644 index 000000000..d28c1f32a --- /dev/null +++ b/src/stat/fsext.rs @@ -0,0 +1,359 @@ +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}; + +#[macro_export] +macro_rules! has { + ($mode:expr, $perm:expr) => ( + ($mode & $perm != 0) + ) +} + +pub fn pretty_time(sec: i64, nsec: i64) -> String { + let tm = time::at(Timespec::new(sec, nsec as i32)); + time::strftime("%Y-%m-%d %H:%M:%S.%f %z", &tm).unwrap() +} + +pub fn pretty_filetype<'a>(mode: u32, size: u64) -> &'a str { + match mode & S_IFMT { + S_IFREG => { + if size != 0 { + "regular file" + } else { + "regular empty file" + } + } + S_IFDIR => "directory", + S_IFLNK => "symbolic link", + S_IFCHR => "character special file", + S_IFBLK => "block special file", + S_IFIFO => "fifo", + S_IFSOCK => "socket", + // TODO: Other file types + // See coreutils/gnulib/lib/file-type.c + _ => "weird file", + } +} + +pub fn pretty_access(mode: u32) -> String { + let mut result = String::with_capacity(10); + result.push(match mode & S_IFMT { + S_IFDIR => 'd', + S_IFCHR => 'c', + S_IFBLK => 'b', + S_IFREG => '-', + S_IFIFO => 'p', + S_IFLNK => 'l', + S_IFSOCK => 's', + // TODO: Other file types + // See coreutils/gnulib/lib/filemode.c + _ => '?', + }); + + result.push(if has!(mode, S_IRUSR) { + 'r' + } else { + '-' + }); + result.push(if has!(mode, S_IWUSR) { + 'w' + } else { + '-' + }); + result.push(if has!(mode, S_ISUID as u32) { + if has!(mode, S_IXUSR) { + 's' + } else { + 'S' + } + } else if has!(mode, S_IXUSR) { + 'x' + } else { + '-' + }); + + result.push(if has!(mode, S_IRGRP) { + 'r' + } else { + '-' + }); + result.push(if has!(mode, S_IWGRP) { + 'w' + } else { + '-' + }); + result.push(if has!(mode, S_ISGID as u32) { + if has!(mode, S_IXGRP) { + 's' + } else { + 'S' + } + } else if has!(mode, S_IXGRP) { + 'x' + } else { + '-' + }); + + result.push(if has!(mode, S_IROTH) { + 'r' + } else { + '-' + }); + result.push(if has!(mode, S_IWOTH) { + 'w' + } else { + '-' + }); + result.push(if has!(mode, S_ISVTX as u32) { + if has!(mode, S_IXOTH) { + 't' + } else { + 'T' + } + } else if has!(mode, S_IXOTH) { + 'x' + } else { + '-' + }); + + result +} + +use std::mem::{self, transmute}; +use std::path::Path; +use std::borrow::Cow; +use std::ffi::CString; +use std::convert::{AsRef, From}; + +pub struct Statfs { + pub f_type: i64, + pub f_bsize: i64, + pub f_blocks: u64, + pub f_bfree: u64, + pub f_bavail: u64, + pub f_files: u64, + pub f_ffree: u64, + pub f_namelen: i64, + pub f_frsize: i64, + pub f_fsid: u64, +} + +pub fn statfs>(path: P) -> Result + where Vec: From

+{ + use std::error::Error; + match CString::new(path) { + Ok(p) => { + let mut buffer = unsafe { mem::zeroed() }; + unsafe { + match self::libc::statfs(p.as_ptr(), &mut buffer) { + 0 => { + let fsid: u64; + if cfg!(unix) { + // Linux, SunOS, HP-UX, 4.4BSD, FreeBSD have a system call statfs() that returns + // a struct statfs, containing a fsid_t f_fsid, where fsid_t is defined + // as struct { int val[2]; } + let f_fsid: &[u32; 2] = transmute(&buffer.f_fsid); + fsid = (f_fsid[0] as u64) << 32_u64 | f_fsid[1] as u64; + } else { + // Solaris, Irix and POSIX have a system call statvfs(2) that returns a + // struct statvfs, containing an unsigned long f_fsid + fsid = 0; + } + Ok(Statfs { + f_type: buffer.f_type as i64, + f_bsize: buffer.f_bsize as i64, + f_blocks: buffer.f_blocks as u64, + f_bfree: buffer.f_bfree as u64, + f_bavail: buffer.f_bavail as u64, + f_files: buffer.f_files as u64, + f_ffree: buffer.f_ffree as u64, + f_fsid: fsid, + f_namelen: buffer.f_namelen as i64, + f_frsize: buffer.f_frsize as i64, + }) + } + // TODO: Return explicit error message + _ => Err("Unknown error".to_owned()), + } + } + } + Err(e) => Err(e.description().to_owned()), + } +} + +pub fn pretty_fstype<'a>(fstype: i64) -> Cow<'a, str> { + match fstype { + 0x61636673 => "acfs".into(), + 0xADF5 => "adfs".into(), + 0xADFF => "affs".into(), + 0x5346414F => "afs".into(), + 0x09041934 => "anon-inode FS".into(), + 0x61756673 => "aufs".into(), + 0x0187 => "autofs".into(), + 0x42465331 => "befs".into(), + 0x62646576 => "bdevfs".into(), + 0x1BADFACE => "bfs".into(), + 0xCAFE4A11 => "bpf_fs".into(), + 0x42494E4D => "binfmt_misc".into(), + 0x9123683E => "btrfs".into(), + 0x73727279 => "btrfs_test".into(), + 0x00C36400 => "ceph".into(), + 0x0027E0EB => "cgroupfs".into(), + 0xFF534D42 => "cifs".into(), + 0x73757245 => "coda".into(), + 0x012FF7B7 => "coh".into(), + 0x62656570 => "configfs".into(), + 0x28CD3D45 => "cramfs".into(), + 0x453DCD28 => "cramfs-wend".into(), + 0x64626720 => "debugfs".into(), + 0x1373 => "devfs".into(), + 0x1CD1 => "devpts".into(), + 0xF15F => "ecryptfs".into(), + 0xDE5E81E4 => "efivarfs".into(), + 0x00414A53 => "efs".into(), + 0x5DF5 => "exofs".into(), + 0x137D => "ext".into(), + 0xEF53 => "ext2/ext3".into(), + 0xEF51 => "ext2".into(), + 0xF2F52010 => "f2fs".into(), + 0x4006 => "fat".into(), + 0x19830326 => "fhgfs".into(), + 0x65735546 => "fuseblk".into(), + 0x65735543 => "fusectl".into(), + 0x0BAD1DEA => "futexfs".into(), + 0x01161970 => "gfs/gfs2".into(), + 0x47504653 => "gpfs".into(), + 0x4244 => "hfs".into(), + 0x482B => "hfs+".into(), + 0x4858 => "hfsx".into(), + 0x00C0FFEE => "hostfs".into(), + 0xF995E849 => "hpfs".into(), + 0x958458F6 => "hugetlbfs".into(), + 0x11307854 => "inodefs".into(), + 0x013111A8 => "ibrix".into(), + 0x2BAD1DEA => "inotifyfs".into(), + 0x9660 => "isofs".into(), + 0x4004 => "isofs".into(), + 0x4000 => "isofs".into(), + 0x07C0 => "jffs".into(), + 0x72B6 => "jffs2".into(), + 0x3153464A => "jfs".into(), + 0x6B414653 => "k-afs".into(), + 0xC97E8168 => "logfs".into(), + 0x0BD00BD0 => "lustre".into(), + 0x5346314D => "m1fs".into(), + 0x137F => "minix".into(), + 0x138F => "minix (30 char.)".into(), + 0x2468 => "minix v2".into(), + 0x2478 => "minix v2 (30 char.)".into(), + 0x4D5A => "minix3".into(), + 0x19800202 => "mqueue".into(), + 0x4D44 => "msdos".into(), + 0x564C => "novell".into(), + 0x6969 => "nfs".into(), + 0x6E667364 => "nfsd".into(), + 0x3434 => "nilfs".into(), + 0x6E736673 => "nsfs".into(), + 0x5346544E => "ntfs".into(), + 0x9FA1 => "openprom".into(), + 0x7461636F => "ocfs2".into(), + 0x794C7630 => "overlayfs".into(), + 0xAAD7AAEA => "panfs".into(), + 0x50495045 => "pipefs".into(), + 0x7C7C6673 => "prl_fs".into(), + 0x9FA0 => "proc".into(), + 0x6165676C => "pstorefs".into(), + 0x002F => "qnx4".into(), + 0x68191122 => "qnx6".into(), + 0x858458F6 => "ramfs".into(), + 0x52654973 => "reiserfs".into(), + 0x7275 => "romfs".into(), + 0x67596969 => "rpc_pipefs".into(), + 0x73636673 => "securityfs".into(), + 0xF97CFF8C => "selinux".into(), + 0x43415D53 => "smackfs".into(), + 0x517B => "smb".into(), + 0xFE534D42 => "smb2".into(), + 0xBEEFDEAD => "snfs".into(), + 0x534F434B => "sockfs".into(), + 0x73717368 => "squashfs".into(), + 0x62656572 => "sysfs".into(), + 0x012FF7B6 => "sysv2".into(), + 0x012FF7B5 => "sysv4".into(), + 0x01021994 => "tmpfs".into(), + 0x74726163 => "tracefs".into(), + 0x24051905 => "ubifs".into(), + 0x15013346 => "udf".into(), + 0x00011954 => "ufs".into(), + 0x54190100 => "ufs".into(), + 0x9FA2 => "usbdevfs".into(), + 0x01021997 => "v9fs".into(), + 0xBACBACBC => "vmhgfs".into(), + 0xA501FCF5 => "vxfs".into(), + 0x565A4653 => "vzfs".into(), + 0x53464846 => "wslfs".into(), + 0xABBA1974 => "xenfs".into(), + 0x012FF7B4 => "xenix".into(), + 0x58465342 => "xfs".into(), + 0x012FD16D => "xia".into(), + 0x2FC12FC1 => "zfs".into(), + other => format!("UNKNOWN ({:#x})", other).into(), + } +} + +#[cfg(test)] +mod tests { + #[allow(unused_imports)] + 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 u32 | 0o655)); + assert_eq!("brwsr-xr-x", + pretty_access(S_IFBLK | S_ISUID as u32 | 0o755)); + + assert_eq!("prw---sr--", + pretty_access(S_IFIFO | S_ISGID as u32 | 0o614)); + assert_eq!("prw---Sr--", + pretty_access(S_IFIFO | S_ISGID as u32 | 0o604)); + + assert_eq!("c---r-xr-t", + pretty_access(S_IFCHR | S_ISVTX as u32 | 0o055)); + assert_eq!("c---r-xr-T", + pretty_access(S_IFCHR | S_ISVTX as u32 | 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)); + } +} From 676f00fea3bc53dad380ad85a5b76a1426612b85 Mon Sep 17 00:00:00 2001 From: Knight Date: Tue, 31 May 2016 13:27:45 +0800 Subject: [PATCH 03/12] stat: add main.rs --- src/stat/main.rs | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 src/stat/main.rs diff --git a/src/stat/main.rs b/src/stat/main.rs new file mode 100644 index 000000000..7a290254b --- /dev/null +++ b/src/stat/main.rs @@ -0,0 +1,5 @@ +extern crate uu_stat; + +fn main() { + std::process::exit(uu_stat::uumain(std::env::args().collect())); +} From 03ce99b455894bbfdc36ff88b8af9a1189ddedb3 Mon Sep 17 00:00:00 2001 From: Knight Date: Wed, 1 Jun 2016 01:24:13 +0800 Subject: [PATCH 04/12] stat: fsext.rs: remove useless brackets --- src/stat/fsext.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/stat/fsext.rs b/src/stat/fsext.rs index d28c1f32a..3802b63bb 100644 --- a/src/stat/fsext.rs +++ b/src/stat/fsext.rs @@ -9,7 +9,7 @@ pub use self::libc::{S_IFMT, S_IFDIR, S_IFCHR, S_IFBLK, S_IFREG, S_IFIFO, S_IFLN #[macro_export] macro_rules! has { ($mode:expr, $perm:expr) => ( - ($mode & $perm != 0) + $mode & $perm != 0 ) } From de4a95accbbc84d4f055b781cab2bb5e7e39d954 Mon Sep 17 00:00:00 2001 From: Knight Date: Wed, 1 Jun 2016 09:44:10 +0800 Subject: [PATCH 05/12] stat: almost done TODO: * print escaped char * find mount point * more test cases --- src/stat/stat.rs | 901 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 901 insertions(+) create mode 100644 src/stat/stat.rs diff --git a/src/stat/stat.rs b/src/stat/stat.rs new file mode 100644 index 000000000..9c90da125 --- /dev/null +++ b/src/stat/stat.rs @@ -0,0 +1,901 @@ +#![crate_name = "uu_stat"] + +// This file is part of the uutils coreutils package. +// +// (c) Jian Zeng +// +// For the full copyright and license information, please view the LICENSE file +// that was distributed with this source code. +// + +extern crate users; + +extern crate getopts; +use getopts::Options; + +#[macro_use] +mod fsext; +use fsext::*; + +#[macro_use] +extern crate uucore; + +use std::{fs, iter, cmp}; +use std::io::Write; +use std::borrow::Cow; +// use std::error::Error; +use std::os::unix::fs::{FileTypeExt, MetadataExt}; + +#[cfg(test)] +mod test_stat; + +macro_rules! check_bound { + ($str: ident, $bound:expr, $beg: expr, $end: expr) => ( + if $end >= $bound { + return Err(format!("‘{}’: invalid directive", &$str[$beg..$end])); + } + + ) +} +macro_rules! fill_string { + ($str: ident, $c: expr, $cnt: expr) => ( + iter::repeat($c).take($cnt).map(|c| $str.push(c)).all(|_| true) + ) +} + +macro_rules! extend_digits { + ($str: ident, $min: expr) => ( + if $min > $str.len() { + let mut pad = String::with_capacity($min); + fill_string!(pad, '0', $min - $str.len()); + pad.push_str($str); + pad.into() + } else { + $str.into() + } + ) +} + +macro_rules! pad_and_print { + ($result: ident, $str: ident, $left: expr, $width: expr, $padding: expr) => ( + if $str.len() < $width { + if $left { + $result.push_str($str.as_ref()); + fill_string!($result, $padding, $width - $str.len()); + } else { + fill_string!($result, $padding, $width - $str.len()); + $result.push_str($str.as_ref()); + } + } else { + $result.push_str($str.as_ref()); + } + print!("{}", $result); + ) +} +macro_rules! print_adjusted { + ($str: ident, $left: expr, $width: expr, $padding: expr) => ( + let field_width = cmp::max($width, $str.len()); + let mut result = String::with_capacity(field_width); + pad_and_print!(result, $str, $left, field_width, $padding); + ); + ($str: ident, $left: expr, $need_prefix: expr, $prefix: expr, $width: expr, $padding: expr) => ( + let mut field_width = cmp::max($width, $str.len()); + let mut result = String::with_capacity(field_width + $prefix.len()); + if $need_prefix { + result.push_str($prefix); + field_width -= $prefix.len(); + } + pad_and_print!(result, $str, $left, field_width, $padding); + ) +} + +static NAME: &'static str = "stat"; +static VERSION: &'static str = env!("CARGO_PKG_VERSION"); + +pub const F_ALTER: u8 = 0b1; +pub const F_ZERO: u8 = 0b10; +pub const F_LEFT: u8 = 0b100; +pub const F_SPACE: u8 = 0b1000; +pub const F_SIGN: u8 = 0b10000; +// unused at present +pub const F_GROUP: u8 = 0b100000; + +#[derive(Debug, PartialEq)] +pub enum OutputType { + Str, + Integer, + Unsigned, + UnsignedHex, + UnsignedOct, + Unknown, +} + +#[derive(Debug, PartialEq)] +pub enum Token { + Char(char), + Directive { + flag: u8, + width: usize, + precision: i32, + format: char, + }, +} + +trait ScanNum { + /// Return (F, offset) + fn scan_num(&self) -> Option<(F, usize)> where F: std::str::FromStr; +} + +impl ScanNum for str { + fn scan_num(&self) -> Option<(F, usize)> + where F: std::str::FromStr + { + let mut chars = self.chars(); + let mut i = 0; + while let Some(c) = chars.next() { + match c { + '-' | '+' | '0'...'9' => i += 1, + _ => break, + } + } + if i > 0 { + F::from_str(&self[..i]).ok().map(|x| (x, i)) + } else { + None + } + } +} + +pub struct Stater { + follow: bool, + showfs: bool, + from_user: bool, + files: Vec, + default_tokens: Vec, + default_dev_tokens: Vec, +} + +fn print_it(arg: &str, otype: OutputType, flag: u8, width: usize, precision: i32) { + + // If the precision is given as just '.', the precision is taken to be zero. + // A negative precision is taken as if the precision were omitted. + // This gives the minimum number of digits to appear for d, i, o, u, x, and X conversions, + // the maximum number of characters to be printed from a string for s and S conversions. + + // # + // The value should be converted to an "alternate form". + // For o conversions, the first character of the output string is made zero (by prefixing a 0 if it was not zero already). + // For x and X conversions, a nonzero result has the string "0x" (or "0X" for X conversions) prepended to it. + + // 0 + // The value should be zero padded. + // For d, i, o, u, x, X, a, A, e, E, f, F, g, and G conversions, the converted value is padded on the left with zeros rather than blanks. + // If the 0 and - flags both appear, the 0 flag is ignored. + // If a precision is given with a numeric conversion (d, i, o, u, x, and X), the 0 flag is ignored. + // For other conversions, the behavior is undefined. + + // - + // The converted value is to be left adjusted on the field boundary. (The default is right justification.) + // The converted value is padded on the right with blanks, rather than on the left with blanks or zeros. + // A - overrides a 0 if both are given. + + // ' ' (a space) + // A blank should be left before a positive number (or empty string) produced by a signed conversion. + + // + + // A sign (+ or -) should always be placed before a number produced by a signed conversion. + // By default, a sign is used only for negative numbers. + // A + overrides a space if both are used. + + if otype == OutputType::Unknown { + return print!("?"); + } + + let left_align = has!(flag, F_LEFT); + let padding_char = if has!(flag, F_ZERO) && !left_align && precision == -1 { + '0' + } else { + ' ' + }; + + let has_sign = has!(flag, F_SIGN) || has!(flag, F_SPACE); + + let should_alter = has!(flag, F_ALTER); + let prefix = match otype { + OutputType::UnsignedOct => "0", + OutputType::UnsignedHex => "0x", + OutputType::Integer => { + if has!(flag, F_SIGN) { + "+" + } else { + " " + } + } + _ => "", + }; + + match otype { + OutputType::Str => { + let limit = cmp::min(precision, arg.len() as i32); + let s: &str = if limit >= 0 { + &arg[..limit as usize] + } else { + arg + }; + print_adjusted!(s, left_align, width, ' '); + } + OutputType::Integer => { + let min_digits = cmp::max(precision, arg.len() as i32) as usize; + let extended: Cow = extend_digits!(arg, min_digits); + print_adjusted!(extended, left_align, has_sign, prefix, width, padding_char); + } + OutputType::Unsigned => { + let min_digits = cmp::max(precision, arg.len() as i32) as usize; + let extended: Cow = extend_digits!(arg, min_digits); + print_adjusted!(extended, left_align, width, padding_char); + } + OutputType::UnsignedOct => { + let min_digits = cmp::max(precision, arg.len() as i32) as usize; + let extended: Cow = extend_digits!(arg, min_digits); + print_adjusted!(extended, + left_align, + should_alter, + prefix, + width, + padding_char); + } + OutputType::UnsignedHex => { + let min_digits = cmp::max(precision, arg.len() as i32) as usize; + let extended: Cow = extend_digits!(arg, min_digits); + print_adjusted!(extended, + left_align, + should_alter, + prefix, + width, + padding_char); + } + _ => unreachable!(), + } +} + +impl Stater { + pub fn generate_tokens(fmtstr: &str, use_printf: bool) -> Result, String> { + + let mut tokens = Vec::new(); + let bound = fmtstr.len(); + let chars = fmtstr.chars().collect::>(); + + let mut i = 0_usize; + while i < bound { + + match chars[i] { + '%' => { + let old = i; + + i += 1; + if i >= bound { + tokens.push(Token::Char('%')); + continue; + } + if chars[i] == '%' { + tokens.push(Token::Char('%')); + i += 1; + continue; + } + + let mut flag: u8 = 0; + + while i < bound { + match chars[i] { + '#' => flag |= F_ALTER, + '0' => flag |= F_ZERO, + '-' => flag |= F_LEFT, + ' ' => flag |= F_SPACE, + '+' => flag |= F_SIGN, + //'\'' => flag |= F_GROUP, + '\'' => unimplemented!(), + 'I' => unimplemented!(), + _ => break, + } + i += 1; + } + check_bound!(fmtstr, bound, old, i); + + let mut width = 0_usize; + let mut precision = -1_i32; + let mut j = i; + + match fmtstr[j..].scan_num::() { + Some((field_width, offset)) => { + width = field_width; + j += offset; + } + None => (), + } + check_bound!(fmtstr, bound, old, j); + + if chars[j] == '.' { + j += 1; + check_bound!(fmtstr, bound, old, j); + + match fmtstr[j..].scan_num::() { + Some((prec, offset)) => { + if prec >= 0 { + precision = prec; + } + j += offset; + } + None => precision = 0, + } + check_bound!(fmtstr, bound, old, j); + } + + i = j; + tokens.push(Token::Directive { + width: width, + flag: flag, + precision: precision, + format: chars[i], + }) + + } + '\\' => { + if !use_printf { + tokens.push(Token::Char('\\')); + } else { + i += 1; + if i >= bound { + show_warning!("backslash at end of format"); + tokens.push(Token::Char('\\')); + continue; + } + match chars[i] { + 'x' => { + // TODO: parse character + } + '0'...'7' => { + // TODO: parse character + } + '"' => tokens.push(Token::Char('"')), + '\\' => tokens.push(Token::Char('\\')), + 'a' => tokens.push(Token::Char('\x07')), + 'b' => tokens.push(Token::Char('\x08')), + 'e' => tokens.push(Token::Char('\x1B')), + 'f' => tokens.push(Token::Char('\x0c')), + 'n' => tokens.push(Token::Char('\n')), + 'r' => tokens.push(Token::Char('\r')), + 'v' => tokens.push(Token::Char('\x0b')), + c => { + show_warning!("unrecognized escape '\\{}'", c); + tokens.push(Token::Char(c)); + } + } + } + } + + c => tokens.push(Token::Char(c)), + } + i += 1; + } + if !use_printf && !fmtstr.ends_with('\n') { + tokens.push(Token::Char('\n')); + } + Ok(tokens) + } + + fn new(matches: getopts::Matches) -> Result { + let fmtstr = if matches.opt_present("printf") { + matches.opt_str("printf").expect("Invalid format string") + } else { + matches.opt_str("format").unwrap_or("".to_owned()) + }; + + let use_printf = matches.opt_present("printf"); + let terse = matches.opt_present("terse"); + let showfs = matches.opt_present("file-system"); + + let default_tokens = if fmtstr.is_empty() { + Stater::generate_tokens(&Stater::default_fmt(showfs, terse, false), use_printf).unwrap() + } else { + match Stater::generate_tokens(&fmtstr, use_printf) { + Ok(ts) => ts, + Err(e) => return Err(e), + } + }; + let default_dev_tokens = Stater::generate_tokens(&Stater::default_fmt(showfs, terse, true), + use_printf) + .unwrap(); + + Ok(Stater { + follow: matches.opt_present("dereference"), + showfs: showfs, + from_user: !fmtstr.is_empty(), + files: matches.free, + default_tokens: default_tokens, + default_dev_tokens: default_dev_tokens, + }) + } + + fn exec(&self) -> i32 { + for f in &self.files { + self.do_stat(f.as_str()); + } + 0 + } + + fn do_stat(&self, file: &str) { + + #[inline] + fn get_grp_name(gid: u32) -> String { + if let Some(g) = users::get_group_by_gid(gid) { + g.name().to_owned() + } else { + "UNKNOWN".to_owned() + } + } + + #[inline] + fn get_usr_name(uid: u32) -> String { + if let Some(g) = users::get_user_by_uid(uid) { + g.name().to_owned() + } else { + "UNKNOWN".to_owned() + } + } + + if !self.showfs { + let result = if self.follow { + fs::metadata(file) + } else { + fs::symlink_metadata(file) + }; + match result { + Ok(meta) => { + let ftype = meta.file_type(); + let tokens = if self.from_user || + !(ftype.is_char_device() || ftype.is_block_device()) { + &self.default_tokens + } else { + &self.default_dev_tokens + }; + let is_symlink = ftype.is_symlink(); + + for t in tokens.into_iter() { + match t { + &Token::Char(c) => print!("{}", c), + &Token::Directive { flag, width, precision, format } => { + + let arg: String; + let otype: OutputType; + + match format { + // unsigned oct + 'a' => { + arg = format!("{:o}", 0o7777 & meta.mode()); + otype = OutputType::UnsignedOct; + } + // string + 'A' => { + arg = pretty_access(meta.mode()); + otype = OutputType::Str; + } + // unsigned + 'b' => { + arg = format!("{}", meta.blocks()); + otype = OutputType::Unsigned; + } + + // unsigned + // FIXME: blocksize differs on various platform + // See coreutils/gnulib/lib/stat-size.h ST_NBLOCKSIZE + 'B' => { + // the size in bytes of each block reported by %b + arg = format!("{}", 512); + otype = OutputType::Unsigned; + } + + // unsigned + 'd' => { + arg = format!("{}", meta.dev()); + otype = OutputType::Unsigned; + } + // unsigned hex + 'D' => { + arg = format!("{:x}", meta.dev()); + otype = OutputType::UnsignedHex; + } + // unsigned hex + 'f' => { + arg = format!("{:x}", meta.mode()); + otype = OutputType::UnsignedHex; + } + // string + 'F' => { + arg = pretty_filetype(meta.mode(), meta.len()).to_owned(); + otype = OutputType::Str; + } + // unsigned + 'g' => { + arg = format!("{}", meta.gid()); + otype = OutputType::Unsigned; + } + // string + 'G' => { + arg = get_grp_name(meta.gid()); + otype = OutputType::Str; + } + // unsigned + 'h' => { + arg = format!("{}", meta.nlink()); + otype = OutputType::Unsigned; + } + // unsigned + 'i' => { + arg = format!("{}", meta.ino()); + otype = OutputType::Unsigned; + } + + // string + // FIXME: + 'm' => { + // mount point + arg = "/".to_owned(); + otype = OutputType::Str; + } + + // string + 'n' => { + arg = file.to_owned(); + otype = OutputType::Str; + } + // string + 'N' => { + if is_symlink { + arg = format!("'{}' -> '{}'", + file, + fs::read_link(file) + .expect("Invalid symlink") + .to_string_lossy()); + } else { + arg = format!("'{}'", file); + } + otype = OutputType::Str; + } + // unsigned + 'o' => { + arg = format!("{}", meta.blksize()); + otype = OutputType::Unsigned; + } + // int + 's' => { + arg = format!("{}", meta.len()); + otype = OutputType::Integer; + } + // unsigned hex + 't' => { + arg = format!("{:x}", meta.rdev() >> 8); + otype = OutputType::UnsignedHex; + } + // unsigned hex + 'T' => { + arg = format!("{:x}", meta.rdev() & 0xff); + otype = OutputType::UnsignedHex; + } + // unsigned + 'u' => { + arg = format!("{}", meta.uid()); + otype = OutputType::Unsigned; + } + // string + 'U' => { + arg = get_usr_name(meta.uid()); + otype = OutputType::Str; + } + + // string + // FIXME: + 'w' => { + // time of file birth, human-readable; - if unknown + arg = "-".to_owned(); + otype = OutputType::Str; + } + + // int + // FIXME: + 'W' => { + // time of file birth, seconds since Epoch; 0 if unknown + arg = format!("{}", 0); + otype = OutputType::Integer; + } + + // string + 'x' => { + arg = pretty_time(meta.atime(), meta.atime_nsec()); + otype = OutputType::Str; + } + // int + 'X' => { + arg = format!("{}", meta.atime()); + otype = OutputType::Integer; + } + // string + 'y' => { + arg = pretty_time(meta.mtime(), meta.mtime_nsec()); + otype = OutputType::Str; + } + // int + 'Y' => { + arg = format!("{}", meta.mtime()); + otype = OutputType::Str; + } + // string + 'z' => { + arg = pretty_time(meta.ctime(), meta.ctime_nsec()); + otype = OutputType::Str; + } + // int + 'Z' => { + arg = format!("{}", meta.ctime()); + otype = OutputType::Integer; + } + + _ => { + arg = "?".to_owned(); + otype = OutputType::Unknown; + } + } + print_it(&arg, otype, flag, width, precision); + } + } + } + } + Err(e) => { + show_info!("cannot stat '{}': {}", file, e); + return; + } + } + } else { + match statfs(file) { + Ok(data) => { + let tokens = &self.default_tokens; + + for t in tokens.into_iter() { + match t { + &Token::Char(c) => print!("{}", c), + &Token::Directive { flag, width, precision, format } => { + + let arg: String; + let otype: OutputType; + match format { + // int + 'a' => { + arg = format!("{}", data.f_bavail); + otype = OutputType::Integer; + } + // int + 'b' => { + arg = format!("{}", data.f_blocks); + otype = OutputType::Integer; + } + // unsigned + 'c' => { + arg = format!("{}", data.f_files); + otype = OutputType::Unsigned; + } + // int + 'd' => { + arg = format!("{}", data.f_ffree); + otype = OutputType::Integer; + } + // int + 'f' => { + arg = format!("{}", data.f_bfree); + otype = OutputType::Integer; + } + // hex unsigned + 'i' => { + arg = format!("{:x}", data.f_fsid); + otype = OutputType::UnsignedHex; + } + // unsigned + 'l' => { + arg = format!("{}", data.f_namelen); + otype = OutputType::Unsigned; + } + // string + 'n' => { + arg = file.to_owned(); + otype = OutputType::Str; + } + // unsigned + 's' => { + arg = format!("{}", data.f_bsize); + otype = OutputType::Unsigned; + } + // unsigned + 'S' => { + arg = format!("{}", data.f_frsize); + otype = OutputType::Unsigned; + } + // hex unsigned + 't' => { + arg = format!("{:x}", data.f_type); + otype = OutputType::UnsignedHex; + } + // string + 'T' => { + arg = pretty_fstype(data.f_type).into_owned(); + otype = OutputType::Str; + } + _ => { + arg = "?".to_owned(); + otype = OutputType::Unknown; + } + } + + print_it(&arg, otype, flag, width, precision); + } + } + } + } + Err(e) => { + show_info!("cannot stat '{}': {}", file, e); + return; + } + } + } + } + + // taken from coreutils/src/stat.c + fn default_fmt(showfs: bool, terse: bool, dev: bool) -> String { + + // SELinux related format is *ignored* + + // 36 is taken randomly + let mut fmtstr = String::with_capacity(36); + if showfs { + if terse { + fmtstr.push_str("%n %i %l %t %s %S %b %f %a %c %d\n"); + } else { + fmtstr.push_str(" File: \"%n\"\n ID: %-8i Namelen: %-7l Type: %T\nBlock \ + size: %-10s Fundamental block size: %S\nBlocks: Total: %-10b \ + Free: %-10f Available: %a\nInodes: Total: %-10c Free: %d\n"); + } + } else if terse { + fmtstr.push_str("%n %s %b %f %u %g %D %i %h %t %T %X %Y %Z %W %o\n"); + } else { + fmtstr.push_str(" File: %N\n Size: %-10s\tBlocks: %-10b IO Block: %-6o %F\n"); + if dev { + fmtstr.push_str("Device: %Dh/%dd\tInode: %-10i Links: %-5h Device type: %t,%T\n"); + } else { + fmtstr.push_str("Device: %Dh/%dd\tInode: %-10i Links: %h\n"); + } + fmtstr.push_str("Access: (%04a/%10.10A) Uid: (%5u/%8U) Gid: (%5g/%8G)\n"); + fmtstr.push_str("Access: %x\nModify: %y\nChange: %z\n Birth: %w\n"); + } + fmtstr + } +} + +pub fn uumain(args: Vec) -> i32 { + let mut opts = Options::new(); + + opts.optflag("h", "help", "display this help and exit"); + opts.optflag("", "version", "output version information and exit"); + + opts.optflag("L", "dereference", "follow links"); + opts.optflag("f", + "file-system", + "display file system status instead of file status"); + opts.optflag("t", "terse", "print the information in terse form"); + + // Omit the unused description as they are too long + opts.optopt("c", "format", "", "FORMAT"); + opts.optopt("", "printf", "", "FORMAT"); + + + let matches = match opts.parse(&args[1..]) { + Ok(m) => m, + Err(f) => { + disp_err!("{}", f); + return 1; + } + }; + + if matches.opt_present("help") { + return help(); + } else if matches.opt_present("version") { + return version(); + } + + if matches.free.is_empty() { + disp_err!("missing operand"); + return 1; + } + + match Stater::new(matches) { + // FIXME: Handle error + Ok(stater) => stater.exec(), + Err(e) => { + show_info!("{}", e); + return 1; + } + } +} + +fn version() -> i32 { + println!("{} {}", NAME, VERSION); + 0 +} + +fn help() -> i32 { + let msg = format!(r#"Usage: {} [OPTION]... FILE... +Display file or file system status. + +Mandatory arguments to long options are mandatory for short options too. + -L, --dereference follow links + -f, --file-system display file system status instead of file status + -c --format=FORMAT use the specified FORMAT instead of the default; + output a newline after each use of FORMAT + --printf=FORMAT like --format, but interpret backslash escapes, + and do not output a mandatory trailing newline; + if you want a newline, include \n in FORMAT + -t, --terse print the information in terse form + --help display this help and exit + --version output version information and exit + +The valid format sequences for files (without --file-system): + + %a access rights in octal (note '#' and '0' printf flags) + %A access rights in human readable form + %b number of blocks allocated (see %B) + %B the size in bytes of each block reported by %b + %C SELinux security context string + %d device number in decimal + %D device number in hex + %f raw mode in hex + %F file type + %g group ID of owner + %G group name of owner + %h number of hard links + %i inode number + %m mount point + %n file name + %N quoted file name with dereference if symbolic link + %o optimal I/O transfer size hint + %s total size, in bytes + %t major device type in hex, for character/block device special files + %T minor device type in hex, for character/block device special files + %u user ID of owner + %U user name of owner + %w time of file birth, human-readable; - if unknown + %W time of file birth, seconds since Epoch; 0 if unknown + %x time of last access, human-readable + %X time of last access, seconds since Epoch + %y time of last data modification, human-readable + %Y time of last data modification, seconds since Epoch + %z time of last status change, human-readable + %Z time of last status change, seconds since Epoch + +Valid format sequences for file systems: + + %a free blocks available to non-superuser + %b total data blocks in file system + %c total file nodes in file system + %d free file nodes in file system + %f free blocks in file system + %i file system ID in hex + %l maximum length of filenames + %n file name + %s block size (for faster transfers) + %S fundamental block size (for block counts) + %t file system type in hex + %T file system type in human readable form + +NOTE: your shell may have its own version of stat, which usually supersedes +the version described here. Please refer to your shell's documentation +for details about the options it supports."#, + NAME); + println!("{}", msg); + 0 +} From e1251185105a7ba9e209b7801c7746ec00123737 Mon Sep 17 00:00:00 2001 From: Knight Date: Wed, 1 Jun 2016 14:47:37 +0800 Subject: [PATCH 06/12] stat: find mount point --- src/stat/stat.rs | 71 ++++++++++++++++++++++++++++++++---------------- 1 file changed, 48 insertions(+), 23 deletions(-) diff --git a/src/stat/stat.rs b/src/stat/stat.rs index 9c90da125..15e1366af 100644 --- a/src/stat/stat.rs +++ b/src/stat/stat.rs @@ -21,10 +21,12 @@ use fsext::*; extern crate uucore; use std::{fs, iter, cmp}; -use std::io::Write; +use std::fs::File; +use std::io::{Write, BufReader, BufRead}; use std::borrow::Cow; -// use std::error::Error; use std::os::unix::fs::{FileTypeExt, MetadataExt}; +use std::path::Path; +use std::convert::AsRef; #[cfg(test)] mod test_stat; @@ -92,13 +94,14 @@ macro_rules! print_adjusted { static NAME: &'static str = "stat"; static VERSION: &'static str = env!("CARGO_PKG_VERSION"); -pub const F_ALTER: u8 = 0b1; -pub const F_ZERO: u8 = 0b10; -pub const F_LEFT: u8 = 0b100; -pub const F_SPACE: u8 = 0b1000; -pub const F_SIGN: u8 = 0b10000; +const MOUNT_INFO: &'static str = "/etc/mtab"; +pub const F_ALTER: u8 = 1; +pub const F_ZERO: u8 = 1 << 1; +pub const F_LEFT: u8 = 1 << 2; +pub const F_SPACE: u8 = 1 << 3; +pub const F_SIGN: u8 = 1 << 4; // unused at present -pub const F_GROUP: u8 = 0b100000; +pub const F_GROUP: u8 = 1 << 5; #[derive(Debug, PartialEq)] pub enum OutputType { @@ -151,6 +154,7 @@ pub struct Stater { showfs: bool, from_user: bool, files: Vec, + mount_list: Vec, default_tokens: Vec, default_dev_tokens: Vec, } @@ -292,7 +296,7 @@ impl Stater { '-' => flag |= F_LEFT, ' ' => flag |= F_SPACE, '+' => flag |= F_SIGN, - //'\'' => flag |= F_GROUP, + // '\'' => flag |= F_GROUP, '\'' => unimplemented!(), 'I' => unimplemented!(), _ => break, @@ -406,6 +410,15 @@ impl Stater { 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()) + }) + .collect::>(); + mount_list.sort_by(|a, b| b.cmp(a)); + Ok(Stater { follow: matches.opt_present("dereference"), showfs: showfs, @@ -413,17 +426,32 @@ impl Stater { files: matches.free, default_tokens: default_tokens, default_dev_tokens: default_dev_tokens, + mount_list: mount_list, }) } - fn exec(&self) -> i32 { - for f in &self.files { - self.do_stat(f.as_str()); + fn find_mount_point>(&self, p: P) -> Option { + let path = match p.as_ref().canonicalize() { + Ok(s) => s, + Err(_) => return None, + }; + for root in (&self.mount_list).into_iter() { + if path.starts_with(root) { + return Some(root.clone()); + } } - 0 + None } - fn do_stat(&self, file: &str) { + fn exec(&self) -> i32 { + let mut ret = 0; + for f in &self.files { + ret |= self.do_stat(f.as_str()); + } + ret + } + + fn do_stat(&self, file: &str) -> i32 { #[inline] fn get_grp_name(gid: u32) -> String { @@ -535,11 +563,9 @@ impl Stater { otype = OutputType::Unsigned; } - // string - // FIXME: + // mount point 'm' => { - // mount point - arg = "/".to_owned(); + arg = self.find_mount_point(file).unwrap(); otype = OutputType::Str; } @@ -651,7 +677,7 @@ impl Stater { } Err(e) => { show_info!("cannot stat '{}': {}", file, e); - return; + return 1; } } } else { @@ -740,10 +766,11 @@ impl Stater { } Err(e) => { show_info!("cannot stat '{}': {}", file, e); - return; + return 1; } } } + 0 } // taken from coreutils/src/stat.c @@ -751,7 +778,6 @@ impl Stater { // SELinux related format is *ignored* - // 36 is taken randomly let mut fmtstr = String::with_capacity(36); if showfs { if terse { @@ -814,11 +840,10 @@ pub fn uumain(args: Vec) -> i32 { } match Stater::new(matches) { - // FIXME: Handle error Ok(stater) => stater.exec(), Err(e) => { show_info!("{}", e); - return 1; + 1 } } } From fd652bc285ee3450ea9391cd7197ade52a90fe12 Mon Sep 17 00:00:00 2001 From: Knight Date: Wed, 1 Jun 2016 17:40:16 +0800 Subject: [PATCH 07/12] stat: TODO: more tests --- src/stat/fsext.rs | 11 ++++- src/stat/stat.rs | 105 ++++++++++++++++++++++++++++++++++++---------- 2 files changed, 93 insertions(+), 23 deletions(-) diff --git a/src/stat/fsext.rs b/src/stat/fsext.rs index 3802b63bb..b0d1d248b 100644 --- a/src/stat/fsext.rs +++ b/src/stat/fsext.rs @@ -1,3 +1,11 @@ +// This file is part of the uutils coreutils package. +// +// (c) Jian Zeng +// +// For the full copyright and license information, please view the LICENSE file +// that was distributed with this source code. +// + extern crate libc; extern crate time; @@ -307,8 +315,7 @@ pub fn pretty_fstype<'a>(fstype: i64) -> Cow<'a, str> { } #[cfg(test)] -mod tests { - #[allow(unused_imports)] +mod test_fsext { use super::*; #[test] diff --git a/src/stat/stat.rs b/src/stat/stat.rs index 15e1366af..808291841 100644 --- a/src/stat/stat.rs +++ b/src/stat/stat.rs @@ -75,12 +75,12 @@ macro_rules! pad_and_print { ) } macro_rules! print_adjusted { - ($str: ident, $left: expr, $width: expr, $padding: expr) => ( + ($str: ident, $left: expr, $width: expr, $padding: expr) => ({ let field_width = cmp::max($width, $str.len()); let mut result = String::with_capacity(field_width); pad_and_print!(result, $str, $left, field_width, $padding); - ); - ($str: ident, $left: expr, $need_prefix: expr, $prefix: expr, $width: expr, $padding: expr) => ( + }); + ($str: ident, $left: expr, $need_prefix: expr, $prefix: expr, $width: expr, $padding: expr) => ({ let mut field_width = cmp::max($width, $str.len()); let mut result = String::with_capacity(field_width + $prefix.len()); if $need_prefix { @@ -88,7 +88,7 @@ macro_rules! print_adjusted { field_width -= $prefix.len(); } pad_and_print!(result, $str, $left, field_width, $padding); - ) + }) } static NAME: &'static str = "stat"; @@ -124,20 +124,28 @@ pub enum Token { }, } -trait ScanNum { - /// Return (F, offset) +pub trait ScanUtil { fn scan_num(&self) -> Option<(F, usize)> where F: std::str::FromStr; + fn scan_char(&self, radix: u32) -> Option<(char, usize)>; } -impl ScanNum for str { +impl ScanUtil for str { fn scan_num(&self) -> Option<(F, usize)> where F: std::str::FromStr { let mut chars = self.chars(); let mut i = 0; + if let Some(c) = chars.next() { + match c { + '-' | '+' | '0' ... '9' => i += 1, + _ => return None, + } + } else { + return None; + } while let Some(c) = chars.next() { match c { - '-' | '+' | '0'...'9' => i += 1, + '0'...'9' => i += 1, _ => break, } } @@ -147,6 +155,40 @@ impl ScanNum for str { None } } + + fn scan_char(&self, radix: u32) -> Option<(char, usize)> { + let mut chars = self.chars(); + let mut i = 0; + let count = match radix { + 8 => 3_usize, + 16 => 2, + _ => return None, + }; + let mut res = 0_u32; + while i < count { + if let Some(c) = chars.next() { + match c.to_digit(radix) { + Some(digit) => { + let tmp = res * radix + digit; + if tmp < 256 { + res = tmp; + } else { + break; + } + } + None => break, + } + } else { + break; + } + i += 1; + } + if i > 0 { + Some((res as u8 as char, i)) + } else { + None + } + } } pub struct Stater { @@ -354,21 +396,29 @@ impl Stater { continue; } match chars[i] { - 'x' => { - // TODO: parse character + 'x' if i + 1 < bound => { + if let Some((c, offset)) = fmtstr[i + 1..].scan_char(16) { + tokens.push(Token::Char(c)); + i += offset; + } else { + show_warning!("unrecognized escape '\\x'"); + tokens.push(Token::Char('x')); + } } '0'...'7' => { - // TODO: parse character + let (c, offset) = fmtstr[i..].scan_char(8).unwrap(); + tokens.push(Token::Char(c)); + i += offset - 1; } '"' => tokens.push(Token::Char('"')), '\\' => tokens.push(Token::Char('\\')), 'a' => tokens.push(Token::Char('\x07')), 'b' => tokens.push(Token::Char('\x08')), 'e' => tokens.push(Token::Char('\x1B')), - 'f' => tokens.push(Token::Char('\x0c')), + 'f' => tokens.push(Token::Char('\x0C')), 'n' => tokens.push(Token::Char('\n')), 'r' => tokens.push(Token::Char('\r')), - 'v' => tokens.push(Token::Char('\x0b')), + 'v' => tokens.push(Token::Char('\x0B')), c => { show_warning!("unrecognized escape '\\{}'", c); tokens.push(Token::Char(c)); @@ -401,10 +451,7 @@ impl Stater { let default_tokens = if fmtstr.is_empty() { Stater::generate_tokens(&Stater::default_fmt(showfs, terse, false), use_printf).unwrap() } else { - match Stater::generate_tokens(&fmtstr, use_printf) { - Ok(ts) => ts, - Err(e) => return Err(e), - } + try!(Stater::generate_tokens(&fmtstr, use_printf)) }; let default_dev_tokens = Stater::generate_tokens(&Stater::default_fmt(showfs, terse, true), use_printf) @@ -417,6 +464,7 @@ impl Stater { 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)); Ok(Stater { @@ -619,18 +667,33 @@ impl Stater { } // string - // FIXME: 'w' => { // time of file birth, human-readable; - if unknown - arg = "-".to_owned(); + arg = if let Some(elapsed) = meta.created() + .ok() + .map(|t| { + t.elapsed().unwrap() + }) { + pretty_time(elapsed.as_secs() as i64, + elapsed.subsec_nanos() as i64) + } else { + "-".to_owned() + }; otype = OutputType::Str; } // int - // FIXME: 'W' => { // time of file birth, seconds since Epoch; 0 if unknown - arg = format!("{}", 0); + arg = if let Some(elapsed) = meta.created() + .ok() + .map(|t| { + t.elapsed().unwrap() + }) { + format!("{}", elapsed.as_secs()) + } else { + "0".to_owned() + }; otype = OutputType::Integer; } From ab17a5e5449f8dd732be2af23239b781ac8700a4 Mon Sep 17 00:00:00 2001 From: Knight Date: Wed, 1 Jun 2016 20:20:32 +0800 Subject: [PATCH 08/12] stat: Add entries --- Cargo.toml | 2 ++ Makefile | 2 ++ tests/tests.rs | 1 + 3 files changed, 5 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index 23e63faa8..e17c40bd2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,6 +21,7 @@ unix = [ "nice", "nohup", "pathchk", + "stat", "stdbuf", "timeout", "touch", @@ -153,6 +154,7 @@ shuf = { optional=true, path="src/shuf" } sleep = { optional=true, path="src/sleep" } sort = { optional=true, path="src/sort" } split = { optional=true, path="src/split" } +stat = { optional=true, path="src/stat" } stdbuf = { optional=true, path="src/stdbuf" } sum = { optional=true, path="src/sum" } sync = { optional=true, path="src/sync" } diff --git a/Makefile b/Makefile index 20dd47815..e7abc7007 100644 --- a/Makefile +++ b/Makefile @@ -113,6 +113,7 @@ UNIX_PROGS := \ nice \ nohup \ pathchk \ + stat \ stdbuf \ timeout \ touch \ @@ -168,6 +169,7 @@ TEST_PROGS := \ seq \ sort \ split \ + stat \ stdbuf \ sum \ tac \ diff --git a/tests/tests.rs b/tests/tests.rs index 044ba6ffa..1f07567f5 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -21,6 +21,7 @@ mod sieve; #[cfg(unix)] mod test_stdbuf; #[cfg(unix)] mod test_touch; #[cfg(unix)] mod test_unlink; +#[cfg(unix)] mod test_stat; mod test_base64; mod test_basename; From 48968f3d8a9fc286961c04577a24d82298c61dbe Mon Sep 17 00:00:00 2001 From: Knight Date: Sat, 4 Jun 2016 13:27:32 +0800 Subject: [PATCH 09/12] stat: Add tests --- src/stat/test_stat.rs | 70 +++++++++++++++++++++++++++++++ tests/test_stat.rs | 98 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 168 insertions(+) create mode 100644 src/stat/test_stat.rs create mode 100644 tests/test_stat.rs diff --git a/src/stat/test_stat.rs b/src/stat/test_stat.rs new file mode 100644 index 000000000..816ad680a --- /dev/null +++ b/src/stat/test_stat.rs @@ -0,0 +1,70 @@ +pub use super::*; + +#[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)); +} + +#[cfg(test)] +mod test_generate_tokens { + use super::*; + + #[test] + fn test_normal_format() { + let s = "%10.2ac%-5.w\n"; + let expected = vec![Token::Directive { + flag: 0, + width: 10, + precision: 2, + format: 'a', + }, + Token::Char('c'), + Token::Directive { + flag: F_LEFT, + width: 5, + precision: 0, + format: 'w', + }, + Token::Char('\n')]; + assert_eq!(&expected, &Stater::generate_tokens(s, false).unwrap()); + } + + #[test] + fn test_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()); + } +} diff --git a/tests/test_stat.rs b/tests/test_stat.rs new file mode 100644 index 000000000..baf924528 --- /dev/null +++ b/tests/test_stat.rs @@ -0,0 +1,98 @@ +use common::util::*; + +static UTIL_NAME: &'static str = "stat"; + +use std::process::Command; + +#[test] +fn test_invalid_option() { + let (_, mut ucmd) = testing(UTIL_NAME); + ucmd.arg("-w").arg("-q").arg("/"); + ucmd.fails(); +} + +#[test] +#[cfg(target_os = "linux")] +fn test_terse_fs_format() { + let (_, mut ucmd) = testing(UTIL_NAME); + let args = ["-f", "-t", "/proc"]; + ucmd.args(&args); + assert_eq!(ucmd.run().stdout, expected_result(&args)); +} + +#[test] +#[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"]; + ucmd.args(&args); + assert_eq!(ucmd.run().stdout, "/dev/shm 0 0x1021994 tmpfs\n"); +} + +#[test] +#[cfg(target_os = "linux")] +fn test_terse_normal_format() { + let (_, mut ucmd) = testing(UTIL_NAME); + let args = ["-t", "/"]; + ucmd.args(&args); + assert_eq!(ucmd.run().stdout, expected_result(&args)); +} + +#[test] +#[cfg(target_os = "linux")] +fn test_normal_format() { + let (_, mut ucmd) = testing(UTIL_NAME); + let args = ["/boot"]; + ucmd.args(&args); + assert_eq!(ucmd.run().stdout, expected_result(&args)); +} + +#[test] +#[cfg(target_os = "linux")] +fn test_follow_symlink() { + let (_, mut ucmd) = testing(UTIL_NAME); + let args = ["-L", "/dev/cdrom"]; + ucmd.args(&args); + assert_eq!(ucmd.run().stdout, expected_result(&args)); +} + +#[test] +#[cfg(target_os = "linux")] +fn test_symlink() { + let (_, mut ucmd) = testing(UTIL_NAME); + let args = ["/dev/cdrom"]; + ucmd.args(&args); + assert_eq!(ucmd.run().stdout, expected_result(&args)); +} + +#[test] +#[cfg(target_os = "linux")] +fn test_char() { + let (_, mut ucmd) = testing(UTIL_NAME); + let args = ["/dev/zero"]; + ucmd.args(&args); + assert_eq!(ucmd.run().stdout, expected_result(&args)); +} + +#[test] +#[cfg(target_os = "linux")] +fn test_multi_files() { + let (_, mut ucmd) = testing(UTIL_NAME); + let args = ["/dev", "/usr/lib", "/etc/fstab", "/var"]; + ucmd.args(&args); + assert_eq!(ucmd.run().stdout, expected_result(&args)); +} + +#[test] +#[cfg(target_os = "linux")] +fn test_printf() { + let (_, mut ucmd) = testing(UTIL_NAME); + let args = ["--printf=123%-# 15q\\r\\\"\\\\\\a\\b\\e\\f\\v%+020.23m\\x12\\167\\132\\112\\n", "/"]; + ucmd.args(&args); + assert_eq!(ucmd.run().stdout, "123?\r\"\\\x07\x08\x1B\x0C\x0B /\x12wZJ\n"); +} + +fn expected_result(args: &[&str]) -> String { + let output = Command::new(UTIL_NAME).args(args).output().unwrap(); + String::from_utf8_lossy(&output.stdout).into_owned() +} From 580667295cebc0d21d6d5d3fb0a87f4cbf97ba95 Mon Sep 17 00:00:00 2001 From: Knight Date: Sun, 5 Jun 2016 11:30:46 +0800 Subject: [PATCH 10/12] stat: use struct libc::statfs now --- src/stat/fsext.rs | 149 ++++++++++++++++++++++++++++++++-------------- 1 file changed, 104 insertions(+), 45 deletions(-) diff --git a/src/stat/fsext.rs b/src/stat/fsext.rs index b0d1d248b..d382f6b95 100644 --- a/src/stat/fsext.rs +++ b/src/stat/fsext.rs @@ -12,7 +12,7 @@ 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}; + S_IXGRP, S_IROTH, S_IWOTH, S_IXOTH, mode_t, c_int, strerror}; #[macro_export] macro_rules! has { @@ -26,7 +26,7 @@ pub fn pretty_time(sec: i64, nsec: i64) -> String { time::strftime("%Y-%m-%d %H:%M:%S.%f %z", &tm).unwrap() } -pub fn pretty_filetype<'a>(mode: u32, size: u64) -> &'a str { +pub fn pretty_filetype<'a>(mode: mode_t, size: u64) -> &'a str { match mode & S_IFMT { S_IFREG => { if size != 0 { @@ -47,7 +47,7 @@ pub fn pretty_filetype<'a>(mode: u32, size: u64) -> &'a str { } } -pub fn pretty_access(mode: u32) -> String { +pub fn pretty_access(mode: mode_t) -> String { let mut result = String::with_capacity(10); result.push(match mode & S_IFMT { S_IFDIR => 'd', @@ -136,57 +136,116 @@ use std::path::Path; use std::borrow::Cow; use std::ffi::CString; use std::convert::{AsRef, From}; +use std::error::Error; +use std::io::Error as IOError; -pub struct Statfs { - pub f_type: i64, - pub f_bsize: i64, - pub f_blocks: u64, - pub f_bfree: u64, - pub f_bavail: u64, - pub f_files: u64, - pub f_ffree: u64, - pub f_namelen: i64, - pub f_frsize: i64, - pub f_fsid: u64, +#[cfg(any(target_os = "linux", target_os = "macos", target_os = "android"))] +use self::libc::statfs as Sstatfs; +// #[cfg(any(target_os = "openbsd", target_os = "netbsd", target_os = "openbsd", target_os = "bitrig", target_os = "dragonfly"))] +// use self::libc::statvfs as Sstatfs; + +#[cfg(any(target_os = "linux", target_os = "macos", target_os = "android"))] +use self::libc::statfs as statfs_fn; +// #[cfg(any(target_os = "openbsd", target_os = "netbsd", target_os = "openbsd", target_os = "bitrig", target_os = "dragonfly"))] +// use self::libc::statvfs as statfs_fn; + +pub trait FsMeta { + fn fs_type(&self) -> i64; + fn iosize(&self) -> i64; + fn blksize(&self) -> i64; + fn total_blocks(&self) -> u64; + fn free_blocks(&self) -> u64; + fn avail_blocks(&self) -> u64; + fn total_fnodes(&self) -> u64; + fn free_fnodes(&self) -> u64; + fn fsid(&self) -> u64; + fn namelen(&self) -> i64; } -pub fn statfs>(path: P) -> Result +impl FsMeta for Sstatfs { + fn blksize(&self) -> i64 { + self.f_bsize as i64 + } + fn total_blocks(&self) -> u64 { + self.f_blocks as u64 + } + fn free_blocks(&self) -> u64 { + self.f_bfree as u64 + } + fn avail_blocks(&self) -> u64 { + self.f_bavail as u64 + } + fn total_fnodes(&self) -> u64 { + self.f_files as u64 + } + fn free_fnodes(&self) -> u64 { + self.f_ffree as u64 + } + fn fs_type(&self) -> i64 { + self.f_type as i64 + } + + #[cfg(target_os = "linux")] + fn iosize(&self) -> i64 { + self.f_frsize as i64 + } + #[cfg(target_os = "macos")] + fn iosize(&self) -> i64 { + self.f_iosize as i64 + } + // FIXME: + #[cfg(not(any(target_os = "macos", target_os = "linux")))] + fn iosize(&self) -> i64 { + 0 + } + + // Linux, SunOS, HP-UX, 4.4BSD, FreeBSD have a system call statfs() that returns + // a struct statfs, containing a fsid_t f_fsid, where fsid_t is defined + // as struct { int val[2]; } + // + // Solaris, Irix and POSIX have a system call statvfs(2) that returns a + // struct statvfs, containing an unsigned long f_fsid + #[cfg(any(target_os = "macos", target_os = "linux"))] + fn fsid(&self) -> u64 { + let f_fsid: &[u32; 2] = unsafe { transmute(&self.f_fsid) }; + (f_fsid[0] as u64) << 32 | f_fsid[1] as u64 + } + // FIXME: + #[cfg(not(any(target_os = "macos", target_os = "linux")))] + fn fsid(&self) -> u64 { + 0 + } + + #[cfg(target_os = "linux")] + fn namelen(&self) -> i64 { + self.f_namelen as i64 + } + #[cfg(target_os = "macos")] + fn namelen(&self) -> i64 { + 1024 + } + // FIXME: + #[cfg(not(any(target_os = "macos", target_os = "linux")))] + fn namelen(&self) -> u64 { + 0 + } +} + +pub fn statfs>(path: P) -> Result where Vec: From

{ - use std::error::Error; match CString::new(path) { Ok(p) => { - let mut buffer = unsafe { mem::zeroed() }; + let mut buffer: Sstatfs = unsafe { mem::zeroed() }; unsafe { - match self::libc::statfs(p.as_ptr(), &mut buffer) { - 0 => { - let fsid: u64; - if cfg!(unix) { - // Linux, SunOS, HP-UX, 4.4BSD, FreeBSD have a system call statfs() that returns - // a struct statfs, containing a fsid_t f_fsid, where fsid_t is defined - // as struct { int val[2]; } - let f_fsid: &[u32; 2] = transmute(&buffer.f_fsid); - fsid = (f_fsid[0] as u64) << 32_u64 | f_fsid[1] as u64; - } else { - // Solaris, Irix and POSIX have a system call statvfs(2) that returns a - // struct statvfs, containing an unsigned long f_fsid - fsid = 0; - } - Ok(Statfs { - f_type: buffer.f_type as i64, - f_bsize: buffer.f_bsize as i64, - f_blocks: buffer.f_blocks as u64, - f_bfree: buffer.f_bfree as u64, - f_bavail: buffer.f_bavail as u64, - f_files: buffer.f_files as u64, - f_ffree: buffer.f_ffree as u64, - f_fsid: fsid, - f_namelen: buffer.f_namelen as i64, - f_frsize: buffer.f_frsize as i64, - }) + match statfs_fn(p.as_ptr(), &mut buffer) { + 0 => Ok(buffer), + _ => { + 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())) } - // TODO: Return explicit error message - _ => Err("Unknown error".to_owned()), } } } From ad3c984afd3d359470ab737fd251d24085275e91 Mon Sep 17 00:00:00 2001 From: Knight Date: Sun, 5 Jun 2016 11:33:58 +0800 Subject: [PATCH 11/12] stat: get rid of `crate users` --- src/stat/Cargo.toml | 1 - src/stat/stat.rs | 227 ++++++++++++++++++++++---------------------- 2 files changed, 111 insertions(+), 117 deletions(-) diff --git a/src/stat/Cargo.toml b/src/stat/Cargo.toml index 158f88f1f..f2d6f4c72 100644 --- a/src/stat/Cargo.toml +++ b/src/stat/Cargo.toml @@ -11,7 +11,6 @@ path = "stat.rs" getopts = "*" libc = "*" time = "*" -users = "*" uucore = { path="../uucore" } [[bin]] diff --git a/src/stat/stat.rs b/src/stat/stat.rs index 808291841..2515c82c3 100644 --- a/src/stat/stat.rs +++ b/src/stat/stat.rs @@ -8,8 +8,6 @@ // that was distributed with this source code. // -extern crate users; - extern crate getopts; use getopts::Options; @@ -44,7 +42,6 @@ macro_rules! fill_string { iter::repeat($c).take($cnt).map(|c| $str.push(c)).all(|_| true) ) } - macro_rules! extend_digits { ($str: ident, $min: expr) => ( if $min > $str.len() { @@ -57,7 +54,6 @@ macro_rules! extend_digits { } ) } - macro_rules! pad_and_print { ($result: ident, $str: ident, $left: expr, $width: expr, $padding: expr) => ( if $str.len() < $width { @@ -135,13 +131,9 @@ impl ScanUtil for str { { let mut chars = self.chars(); let mut i = 0; - if let Some(c) = chars.next() { - match c { - '-' | '+' | '0' ... '9' => i += 1, - _ => return None, - } - } else { - return None; + match chars.next() { + Some('-') | Some('+') | Some('0'...'9') => i += 1, + _ => return None, } while let Some(c) = chars.next() { match c { @@ -157,34 +149,33 @@ impl ScanUtil for str { } fn scan_char(&self, radix: u32) -> Option<(char, usize)> { - let mut chars = self.chars(); - let mut i = 0; let count = match radix { 8 => 3_usize, 16 => 2, _ => return None, }; + let mut chars = self.chars().enumerate(); let mut res = 0_u32; - while i < count { - if let Some(c) = chars.next() { - match c.to_digit(radix) { - Some(digit) => { - let tmp = res * radix + digit; - if tmp < 256 { - res = tmp; - } else { - break; - } - } - None => break, - } - } else { + let mut offset = 0_usize; + while let Some((i, c)) = chars.next() { + if i >= count { break; } - i += 1; + match c.to_digit(radix) { + Some(digit) => { + let tmp = res * radix + digit; + if tmp < 256 { + res = tmp; + } else { + break; + } + } + None => break, + } + offset = i + 1; } - if i > 0 { - Some((res as u8 as char, i)) + if offset > 0 { + Some((res as u8 as char, offset)) } else { None } @@ -304,6 +295,31 @@ fn print_it(arg: &str, otype: OutputType, flag: u8, width: usize, precision: i32 } } + +use std::ptr; +use std::ffi::CStr; +use uucore::c_types::{getpwuid, getgrgid}; +fn get_grp_name(gid: u32) -> String { + let p = unsafe { getgrgid(gid) }; + if !p.is_null() { + unsafe { CStr::from_ptr(ptr::read(p).gr_name).to_string_lossy().into_owned() } + } else { + "UNKNOWN".to_owned() + } +} +fn get_usr_name(uid: u32) -> String { + let p = unsafe { getpwuid(uid) }; + if !p.is_null() { + unsafe { + CStr::from_ptr(ptr::read(p).pw_name) + .to_string_lossy() + .into_owned() + } + } else { + "UNKNOWN".to_owned() + } +} + impl Stater { pub fn generate_tokens(fmtstr: &str, use_printf: bool) -> Result, String> { @@ -501,24 +517,6 @@ impl Stater { fn do_stat(&self, file: &str) -> i32 { - #[inline] - fn get_grp_name(gid: u32) -> String { - if let Some(g) = users::get_group_by_gid(gid) { - g.name().to_owned() - } else { - "UNKNOWN".to_owned() - } - } - - #[inline] - fn get_usr_name(uid: u32) -> String { - if let Some(g) = users::get_user_by_uid(uid) { - g.name().to_owned() - } else { - "UNKNOWN".to_owned() - } - } - if !self.showfs { let result = if self.follow { fs::metadata(file) @@ -534,7 +532,6 @@ impl Stater { } else { &self.default_dev_tokens }; - let is_symlink = ftype.is_symlink(); for t in tokens.into_iter() { match t { @@ -545,23 +542,23 @@ impl Stater { let otype: OutputType; match format { - // unsigned oct + // access rights in octal 'a' => { arg = format!("{:o}", 0o7777 & meta.mode()); otype = OutputType::UnsignedOct; } - // string + // access rights in human readable form 'A' => { - arg = pretty_access(meta.mode()); + arg = pretty_access(meta.mode() as mode_t); otype = OutputType::Str; } - // unsigned + // number of blocks allocated (see %B) 'b' => { arg = format!("{}", meta.blocks()); otype = OutputType::Unsigned; } - // unsigned + // the size in bytes of each block reported by %b // FIXME: blocksize differs on various platform // See coreutils/gnulib/lib/stat-size.h ST_NBLOCKSIZE 'B' => { @@ -570,42 +567,42 @@ impl Stater { otype = OutputType::Unsigned; } - // unsigned + // device number in decimal 'd' => { arg = format!("{}", meta.dev()); otype = OutputType::Unsigned; } - // unsigned hex + // device number in hex 'D' => { arg = format!("{:x}", meta.dev()); otype = OutputType::UnsignedHex; } - // unsigned hex + // raw mode in hex 'f' => { arg = format!("{:x}", meta.mode()); otype = OutputType::UnsignedHex; } - // string + // file type 'F' => { arg = pretty_filetype(meta.mode(), meta.len()).to_owned(); otype = OutputType::Str; } - // unsigned + // group ID of owner 'g' => { arg = format!("{}", meta.gid()); otype = OutputType::Unsigned; } - // string + // group name of owner 'G' => { arg = get_grp_name(meta.gid()); otype = OutputType::Str; } - // unsigned + // number of hard links 'h' => { arg = format!("{}", meta.nlink()); otype = OutputType::Unsigned; } - // unsigned + // inode number 'i' => { arg = format!("{}", meta.ino()); otype = OutputType::Unsigned; @@ -617,14 +614,14 @@ impl Stater { otype = OutputType::Str; } - // string + // file name 'n' => { arg = file.to_owned(); otype = OutputType::Str; } - // string + // quoted file name with dereference if symbolic link 'N' => { - if is_symlink { + if ftype.is_symlink() { arg = format!("'{}' -> '{}'", file, fs::read_link(file) @@ -635,45 +632,45 @@ impl Stater { } otype = OutputType::Str; } - // unsigned + // optimal I/O transfer size hint 'o' => { arg = format!("{}", meta.blksize()); otype = OutputType::Unsigned; } - // int + // total size, in bytes 's' => { arg = format!("{}", meta.len()); otype = OutputType::Integer; } - // unsigned hex + // major device type in hex, for character/block device special + // files 't' => { arg = format!("{:x}", meta.rdev() >> 8); otype = OutputType::UnsignedHex; } - // unsigned hex + // minor device type in hex, for character/block device special + // files 'T' => { arg = format!("{:x}", meta.rdev() & 0xff); otype = OutputType::UnsignedHex; } - // unsigned + // user ID of owner 'u' => { arg = format!("{}", meta.uid()); otype = OutputType::Unsigned; } - // string + // user name of owner 'U' => { arg = get_usr_name(meta.uid()); otype = OutputType::Str; } - // string + // time of file birth, human-readable; - if unknown 'w' => { - // time of file birth, human-readable; - if unknown - arg = if let Some(elapsed) = meta.created() - .ok() - .map(|t| { - t.elapsed().unwrap() - }) { + 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 { @@ -682,14 +679,12 @@ impl Stater { otype = OutputType::Str; } - // int + // time of file birth, seconds since Epoch; 0 if unknown 'W' => { - // time of file birth, seconds since Epoch; 0 if unknown - arg = if let Some(elapsed) = meta.created() - .ok() - .map(|t| { - t.elapsed().unwrap() - }) { + arg = if let Ok(elapsed) = meta.created() + .map(|t| { + t.elapsed().unwrap() + }) { format!("{}", elapsed.as_secs()) } else { "0".to_owned() @@ -697,32 +692,32 @@ impl Stater { otype = OutputType::Integer; } - // string + // time of last access, human-readable 'x' => { arg = pretty_time(meta.atime(), meta.atime_nsec()); otype = OutputType::Str; } - // int + // time of last access, seconds since Epoch 'X' => { arg = format!("{}", meta.atime()); otype = OutputType::Integer; } - // string + // time of last data modification, human-readable 'y' => { arg = pretty_time(meta.mtime(), meta.mtime_nsec()); otype = OutputType::Str; } - // int + // time of last data modification, seconds since Epoch 'Y' => { arg = format!("{}", meta.mtime()); otype = OutputType::Str; } - // string + // time of last status change, human-readable 'z' => { arg = pretty_time(meta.ctime(), meta.ctime_nsec()); otype = OutputType::Str; } - // int + // time of last status change, seconds since Epoch 'Z' => { arg = format!("{}", meta.ctime()); otype = OutputType::Integer; @@ -745,7 +740,7 @@ impl Stater { } } else { match statfs(file) { - Ok(data) => { + Ok(meta) => { let tokens = &self.default_tokens; for t in tokens.into_iter() { @@ -756,64 +751,64 @@ impl Stater { let arg: String; let otype: OutputType; match format { - // int + // free blocks available to non-superuser 'a' => { - arg = format!("{}", data.f_bavail); + arg = format!("{}", meta.avail_blocks()); otype = OutputType::Integer; } - // int + // total data blocks in file system 'b' => { - arg = format!("{}", data.f_blocks); + arg = format!("{}", meta.total_blocks()); otype = OutputType::Integer; } - // unsigned + // total file nodes in file system 'c' => { - arg = format!("{}", data.f_files); + arg = format!("{}", meta.total_fnodes()); otype = OutputType::Unsigned; } - // int + // free file nodes in file system 'd' => { - arg = format!("{}", data.f_ffree); + arg = format!("{}", meta.free_fnodes()); otype = OutputType::Integer; } - // int + // free blocks in file system 'f' => { - arg = format!("{}", data.f_bfree); + arg = format!("{}", meta.free_blocks()); otype = OutputType::Integer; } - // hex unsigned + // file system ID in hex 'i' => { - arg = format!("{:x}", data.f_fsid); + arg = format!("{:x}", meta.fsid()); otype = OutputType::UnsignedHex; } - // unsigned + // maximum length of filenames 'l' => { - arg = format!("{}", data.f_namelen); + arg = format!("{}", meta.namelen()); otype = OutputType::Unsigned; } - // string + // file name 'n' => { arg = file.to_owned(); otype = OutputType::Str; } - // unsigned + // block size (for faster transfers) 's' => { - arg = format!("{}", data.f_bsize); + arg = format!("{}", meta.iosize()); otype = OutputType::Unsigned; } - // unsigned + // fundamental block size (for block counts) 'S' => { - arg = format!("{}", data.f_frsize); + arg = format!("{}", meta.blksize()); otype = OutputType::Unsigned; } - // hex unsigned + // file system type in hex 't' => { - arg = format!("{:x}", data.f_type); + arg = format!("{:x}", meta.fs_type()); otype = OutputType::UnsignedHex; } - // string + // file system type in human readable form 'T' => { - arg = pretty_fstype(data.f_type).into_owned(); + arg = pretty_fstype(meta.fs_type()).into_owned(); otype = OutputType::Str; } _ => { @@ -828,7 +823,7 @@ impl Stater { } } Err(e) => { - show_info!("cannot stat '{}': {}", file, e); + show_info!("cannot read file system information for '{}': {}", file, e); return 1; } } From 5a0dd670038bef63fb5040ec9ba4c7c630004ffa Mon Sep 17 00:00:00 2001 From: Knight Date: Sun, 5 Jun 2016 11:49:49 +0800 Subject: [PATCH 12/12] stat: make ci happy 1. force ci to use libc@0.2 2. dont use unstable api Metadata::created 3. change quote style 4. pass metadata.mode() as mode_t --- src/stat/Cargo.toml | 2 +- src/stat/fsext.rs | 25 +++++++++++++++---------- src/stat/stat.rs | 44 ++++++++++++++++++++++++-------------------- 3 files changed, 40 insertions(+), 31 deletions(-) diff --git a/src/stat/Cargo.toml b/src/stat/Cargo.toml index f2d6f4c72..da3788528 100644 --- a/src/stat/Cargo.toml +++ b/src/stat/Cargo.toml @@ -9,7 +9,7 @@ path = "stat.rs" [dependencies] getopts = "*" -libc = "*" +libc = "^0.2" time = "*" uucore = { path="../uucore" } diff --git a/src/stat/fsext.rs b/src/stat/fsext.rs index d382f6b95..a169ed084 100644 --- a/src/stat/fsext.rs +++ b/src/stat/fsext.rs @@ -23,7 +23,12 @@ macro_rules! has { pub fn pretty_time(sec: i64, nsec: i64) -> String { let tm = time::at(Timespec::new(sec, nsec as i32)); - time::strftime("%Y-%m-%d %H:%M:%S.%f %z", &tm).unwrap() + let res = time::strftime("%Y-%m-%d %H:%M:%S.%f %z", &tm).unwrap(); + if res.ends_with(" -0000") { + res.replace(" -0000", " +0000") + } else { + res + } } pub fn pretty_filetype<'a>(mode: mode_t, size: u64) -> &'a str { @@ -72,7 +77,7 @@ pub fn pretty_access(mode: mode_t) -> String { } else { '-' }); - result.push(if has!(mode, S_ISUID as u32) { + result.push(if has!(mode, S_ISUID as mode_t) { if has!(mode, S_IXUSR) { 's' } else { @@ -94,7 +99,7 @@ pub fn pretty_access(mode: mode_t) -> String { } else { '-' }); - result.push(if has!(mode, S_ISGID as u32) { + result.push(if has!(mode, S_ISGID as mode_t) { if has!(mode, S_IXGRP) { 's' } else { @@ -116,7 +121,7 @@ pub fn pretty_access(mode: mode_t) -> String { } else { '-' }); - result.push(if has!(mode, S_ISVTX as u32) { + result.push(if has!(mode, S_ISVTX as mode_t) { if has!(mode, S_IXOTH) { 't' } else { @@ -386,19 +391,19 @@ mod test_fsext { assert_eq!("?rw-r-xr-x", pretty_access(0o655)); assert_eq!("brwSr-xr-x", - pretty_access(S_IFBLK | S_ISUID as u32 | 0o655)); + pretty_access(S_IFBLK | S_ISUID as mode_t | 0o655)); assert_eq!("brwsr-xr-x", - pretty_access(S_IFBLK | S_ISUID as u32 | 0o755)); + pretty_access(S_IFBLK | S_ISUID as mode_t | 0o755)); assert_eq!("prw---sr--", - pretty_access(S_IFIFO | S_ISGID as u32 | 0o614)); + pretty_access(S_IFIFO | S_ISGID as mode_t | 0o614)); assert_eq!("prw---Sr--", - pretty_access(S_IFIFO | S_ISGID as u32 | 0o604)); + pretty_access(S_IFIFO | S_ISGID as mode_t | 0o604)); assert_eq!("c---r-xr-t", - pretty_access(S_IFCHR | S_ISVTX as u32 | 0o055)); + pretty_access(S_IFCHR | S_ISVTX as mode_t | 0o055)); assert_eq!("c---r-xr-T", - pretty_access(S_IFCHR | S_ISVTX as u32 | 0o054)); + pretty_access(S_IFCHR | S_ISVTX as mode_t | 0o054)); } #[test] diff --git a/src/stat/stat.rs b/src/stat/stat.rs index 2515c82c3..58152a080 100644 --- a/src/stat/stat.rs +++ b/src/stat/stat.rs @@ -584,7 +584,7 @@ impl Stater { } // file type 'F' => { - arg = pretty_filetype(meta.mode(), meta.len()).to_owned(); + arg = pretty_filetype(meta.mode() as mode_t, meta.len()).to_owned(); otype = OutputType::Str; } // group ID of owner @@ -622,13 +622,13 @@ impl Stater { // quoted file name with dereference if symbolic link 'N' => { if ftype.is_symlink() { - arg = format!("'{}' -> '{}'", + arg = format!("`{}' -> `{}'", file, fs::read_link(file) .expect("Invalid symlink") .to_string_lossy()); } else { - arg = format!("'{}'", file); + arg = format!("`{}'", file); } otype = OutputType::Str; } @@ -667,28 +667,32 @@ impl Stater { // time of file birth, human-readable; - if unknown 'w' => { - 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() - }; + // 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(); otype = OutputType::Str; } // time of file birth, seconds since Epoch; 0 if unknown 'W' => { - arg = if let Ok(elapsed) = meta.created() - .map(|t| { - t.elapsed().unwrap() - }) { - format!("{}", elapsed.as_secs()) - } else { - "0".to_owned() - }; + // 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(); otype = OutputType::Integer; }