mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-27 19:17:43 +00:00
commit
461a4e72b0
9 changed files with 1614 additions and 0 deletions
|
@ -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" }
|
||||||
|
|
2
Makefile
2
Makefile
|
@ -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
18
src/stat/Cargo.toml
Normal 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
430
src/stat/fsext.rs
Normal 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
5
src/stat/main.rs
Normal 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
988
src/stat/stat.rs
Normal 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
70
src/stat/test_stat.rs
Normal 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
98
tests/test_stat.rs
Normal 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()
|
||||||
|
}
|
|
@ -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;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue