1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-27 19:17:43 +00:00

Merge pull request #893 from knight42/stat

Implement stat
This commit is contained in:
Heather 2016-06-07 16:35:01 +04:00
commit 461a4e72b0
9 changed files with 1614 additions and 0 deletions

View file

@ -21,6 +21,7 @@ unix = [
"nice", "nice",
"nohup", "nohup",
"pathchk", "pathchk",
"stat",
"stdbuf", "stdbuf",
"timeout", "timeout",
"touch", "touch",
@ -153,6 +154,7 @@ shuf = { optional=true, path="src/shuf" }
sleep = { optional=true, path="src/sleep" } sleep = { optional=true, path="src/sleep" }
sort = { optional=true, path="src/sort" } sort = { optional=true, path="src/sort" }
split = { optional=true, path="src/split" } split = { optional=true, path="src/split" }
stat = { optional=true, path="src/stat" }
stdbuf = { optional=true, path="src/stdbuf" } stdbuf = { optional=true, path="src/stdbuf" }
sum = { optional=true, path="src/sum" } sum = { optional=true, path="src/sum" }
sync = { optional=true, path="src/sync" } sync = { optional=true, path="src/sync" }

View file

@ -113,6 +113,7 @@ UNIX_PROGS := \
nice \ nice \
nohup \ nohup \
pathchk \ pathchk \
stat \
stdbuf \ stdbuf \
timeout \ timeout \
touch \ touch \
@ -168,6 +169,7 @@ TEST_PROGS := \
seq \ seq \
sort \ sort \
split \ split \
stat \
stdbuf \ stdbuf \
sum \ sum \
tac \ tac \

18
src/stat/Cargo.toml Normal file
View file

@ -0,0 +1,18 @@
[package]
name = "stat"
version = "0.0.1"
authors = []
[lib]
name = "uu_stat"
path = "stat.rs"
[dependencies]
getopts = "*"
libc = "^0.2"
time = "*"
uucore = { path="../uucore" }
[[bin]]
name = "stat"
path = "main.rs"

430
src/stat/fsext.rs Normal file
View file

@ -0,0 +1,430 @@
// 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 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};
#[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));
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 {
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: mode_t) -> 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 mode_t) {
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 mode_t) {
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 mode_t) {
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};
use std::error::Error;
use std::io::Error as IOError;
#[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;
}
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<P: AsRef<Path>>(path: P) -> Result<Sstatfs, String>
where Vec<u8>: From<P>
{
match CString::new(path) {
Ok(p) => {
let mut buffer: Sstatfs = unsafe { mem::zeroed() };
unsafe {
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()))
}
}
}
}
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 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));
}
}

5
src/stat/main.rs Normal file
View file

@ -0,0 +1,5 @@
extern crate uu_stat;
fn main() {
std::process::exit(uu_stat::uumain(std::env::args().collect()));
}

988
src/stat/stat.rs Normal file
View file

@ -0,0 +1,988 @@
#![crate_name = "uu_stat"]
// 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 getopts;
use getopts::Options;
#[macro_use]
mod fsext;
use fsext::*;
#[macro_use]
extern crate uucore;
use std::{fs, iter, cmp};
use std::fs::File;
use std::io::{Write, BufReader, BufRead};
use std::borrow::Cow;
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 {
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");
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 = 1 << 5;
#[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,
},
}
pub trait ScanUtil {
fn scan_num<F>(&self) -> Option<(F, usize)> where F: std::str::FromStr;
fn scan_char(&self, radix: u32) -> Option<(char, usize)>;
}
impl ScanUtil for str {
fn scan_num<F>(&self) -> Option<(F, usize)>
where F: std::str::FromStr
{
let mut chars = self.chars();
let mut i = 0;
match chars.next() {
Some('-') | Some('+') | Some('0'...'9') => i += 1,
_ => return None,
}
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
}
}
fn scan_char(&self, radix: u32) -> Option<(char, usize)> {
let count = match radix {
8 => 3_usize,
16 => 2,
_ => return None,
};
let mut chars = self.chars().enumerate();
let mut res = 0_u32;
let mut offset = 0_usize;
while let Some((i, c)) = chars.next() {
if i >= count {
break;
}
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 offset > 0 {
Some((res as u8 as char, offset))
} else {
None
}
}
}
pub struct Stater {
follow: bool,
showfs: bool,
from_user: bool,
files: Vec<String>,
mount_list: Vec<String>,
default_tokens: Vec<Token>,
default_dev_tokens: Vec<Token>,
}
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<str> = 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<str> = 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<str> = 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<str> = extend_digits!(arg, min_digits);
print_adjusted!(extended,
left_align,
should_alter,
prefix,
width,
padding_char);
}
_ => unreachable!(),
}
}
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<Vec<Token>, String> {
let mut tokens = Vec::new();
let bound = fmtstr.len();
let chars = fmtstr.chars().collect::<Vec<char>>();
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::<usize>() {
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::<i32>() {
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' 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' => {
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')),
'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<Stater, String> {
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 {
try!(Stater::generate_tokens(&fmtstr, use_printf))
};
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())
})
.collect::<Vec<String>>();
// Reverse sort. The longer comes first.
mount_list.sort_by(|a, b| b.cmp(a));
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,
mount_list: mount_list,
})
}
fn find_mount_point<P: AsRef<Path>>(&self, p: P) -> Option<String> {
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());
}
}
None
}
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 {
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
};
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 {
// access rights in octal
'a' => {
arg = format!("{:o}", 0o7777 & meta.mode());
otype = OutputType::UnsignedOct;
}
// access rights in human readable form
'A' => {
arg = pretty_access(meta.mode() as mode_t);
otype = OutputType::Str;
}
// number of blocks allocated (see %B)
'b' => {
arg = format!("{}", meta.blocks());
otype = OutputType::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' => {
// the size in bytes of each block reported by %b
arg = format!("{}", 512);
otype = OutputType::Unsigned;
}
// device number in decimal
'd' => {
arg = format!("{}", meta.dev());
otype = OutputType::Unsigned;
}
// device number in hex
'D' => {
arg = format!("{:x}", meta.dev());
otype = OutputType::UnsignedHex;
}
// raw mode in hex
'f' => {
arg = format!("{:x}", meta.mode());
otype = OutputType::UnsignedHex;
}
// file type
'F' => {
arg = pretty_filetype(meta.mode() as mode_t, meta.len()).to_owned();
otype = OutputType::Str;
}
// group ID of owner
'g' => {
arg = format!("{}", meta.gid());
otype = OutputType::Unsigned;
}
// group name of owner
'G' => {
arg = get_grp_name(meta.gid());
otype = OutputType::Str;
}
// number of hard links
'h' => {
arg = format!("{}", meta.nlink());
otype = OutputType::Unsigned;
}
// inode number
'i' => {
arg = format!("{}", meta.ino());
otype = OutputType::Unsigned;
}
// mount point
'm' => {
arg = self.find_mount_point(file).unwrap();
otype = OutputType::Str;
}
// file name
'n' => {
arg = file.to_owned();
otype = OutputType::Str;
}
// 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());
} else {
arg = format!("`{}'", file);
}
otype = OutputType::Str;
}
// optimal I/O transfer size hint
'o' => {
arg = format!("{}", meta.blksize());
otype = OutputType::Unsigned;
}
// total size, in bytes
's' => {
arg = format!("{}", meta.len());
otype = OutputType::Integer;
}
// major device type in hex, for character/block device special
// files
't' => {
arg = format!("{:x}", meta.rdev() >> 8);
otype = OutputType::UnsignedHex;
}
// minor device type in hex, for character/block device special
// files
'T' => {
arg = format!("{:x}", meta.rdev() & 0xff);
otype = OutputType::UnsignedHex;
}
// user ID of owner
'u' => {
arg = format!("{}", meta.uid());
otype = OutputType::Unsigned;
}
// user name of owner
'U' => {
arg = get_usr_name(meta.uid());
otype = OutputType::Str;
}
// 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();
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();
otype = OutputType::Integer;
}
// time of last access, human-readable
'x' => {
arg = pretty_time(meta.atime(), meta.atime_nsec());
otype = OutputType::Str;
}
// time of last access, seconds since Epoch
'X' => {
arg = format!("{}", meta.atime());
otype = OutputType::Integer;
}
// time of last data modification, human-readable
'y' => {
arg = pretty_time(meta.mtime(), meta.mtime_nsec());
otype = OutputType::Str;
}
// time of last data modification, seconds since Epoch
'Y' => {
arg = format!("{}", meta.mtime());
otype = OutputType::Str;
}
// time of last status change, human-readable
'z' => {
arg = pretty_time(meta.ctime(), meta.ctime_nsec());
otype = OutputType::Str;
}
// time of last status change, seconds since Epoch
'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 1;
}
}
} else {
match statfs(file) {
Ok(meta) => {
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 {
// free blocks available to non-superuser
'a' => {
arg = format!("{}", meta.avail_blocks());
otype = OutputType::Integer;
}
// total data blocks in file system
'b' => {
arg = format!("{}", meta.total_blocks());
otype = OutputType::Integer;
}
// total file nodes in file system
'c' => {
arg = format!("{}", meta.total_fnodes());
otype = OutputType::Unsigned;
}
// free file nodes in file system
'd' => {
arg = format!("{}", meta.free_fnodes());
otype = OutputType::Integer;
}
// free blocks in file system
'f' => {
arg = format!("{}", meta.free_blocks());
otype = OutputType::Integer;
}
// file system ID in hex
'i' => {
arg = format!("{:x}", meta.fsid());
otype = OutputType::UnsignedHex;
}
// maximum length of filenames
'l' => {
arg = format!("{}", meta.namelen());
otype = OutputType::Unsigned;
}
// file name
'n' => {
arg = file.to_owned();
otype = OutputType::Str;
}
// block size (for faster transfers)
's' => {
arg = format!("{}", meta.iosize());
otype = OutputType::Unsigned;
}
// fundamental block size (for block counts)
'S' => {
arg = format!("{}", meta.blksize());
otype = OutputType::Unsigned;
}
// file system type in hex
't' => {
arg = format!("{:x}", meta.fs_type());
otype = OutputType::UnsignedHex;
}
// file system type in human readable form
'T' => {
arg = pretty_fstype(meta.fs_type()).into_owned();
otype = OutputType::Str;
}
_ => {
arg = "?".to_owned();
otype = OutputType::Unknown;
}
}
print_it(&arg, otype, flag, width, precision);
}
}
}
}
Err(e) => {
show_info!("cannot read file system information for '{}': {}", file, e);
return 1;
}
}
}
0
}
// taken from coreutils/src/stat.c
fn default_fmt(showfs: bool, terse: bool, dev: bool) -> String {
// SELinux related format is *ignored*
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<String>) -> 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) {
Ok(stater) => stater.exec(),
Err(e) => {
show_info!("{}", e);
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
}

70
src/stat/test_stat.rs Normal file
View file

@ -0,0 +1,70 @@
pub use super::*;
#[test]
fn test_scanutil() {
assert_eq!(Some((-5, 2)), "-5zxc".scan_num::<i32>());
assert_eq!(Some((51, 2)), "51zxc".scan_num::<u32>());
assert_eq!(Some((192, 4)), "+192zxc".scan_num::<i32>());
assert_eq!(None, "z192zxc".scan_num::<i32>());
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());
}
}

98
tests/test_stat.rs Normal file
View file

@ -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()
}

View file

@ -21,6 +21,7 @@ mod sieve;
#[cfg(unix)] mod test_stdbuf; #[cfg(unix)] mod test_stdbuf;
#[cfg(unix)] mod test_touch; #[cfg(unix)] mod test_touch;
#[cfg(unix)] mod test_unlink; #[cfg(unix)] mod test_unlink;
#[cfg(unix)] mod test_stat;
mod test_base64; mod test_base64;
mod test_basename; mod test_basename;