mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-28 11:37:44 +00:00
commit
87d317e22f
7 changed files with 157 additions and 6 deletions
32
src/du/du.rs
32
src/du/du.rs
|
@ -19,6 +19,7 @@ use std::iter;
|
|||
use std::io::{stderr, Result, Write};
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
use std::path::PathBuf;
|
||||
use std::collections::HashSet;
|
||||
use time::Timespec;
|
||||
|
||||
const NAME: &'static str = "du";
|
||||
|
@ -48,6 +49,7 @@ struct Stat {
|
|||
size: u64,
|
||||
blocks: u64,
|
||||
nlink: u64,
|
||||
inode: u64,
|
||||
created: u64,
|
||||
accessed: u64,
|
||||
modified: u64,
|
||||
|
@ -62,6 +64,7 @@ impl Stat {
|
|||
size: metadata.len(),
|
||||
blocks: metadata.blocks() as u64,
|
||||
nlink: metadata.nlink() as u64,
|
||||
inode: metadata.ino() as u64,
|
||||
created: metadata.mtime() as u64,
|
||||
accessed: metadata.atime() as u64,
|
||||
modified: metadata.mtime() as u64,
|
||||
|
@ -69,9 +72,18 @@ impl Stat {
|
|||
}
|
||||
}
|
||||
|
||||
fn get_default_blocks() -> u64 {
|
||||
1024
|
||||
}
|
||||
|
||||
// this takes `my_stat` to avoid having to stat files multiple times.
|
||||
// XXX: this should use the impl Trait return type when it is stabilized
|
||||
fn du(mut my_stat: Stat, options: &Options, depth: usize) -> Box<DoubleEndedIterator<Item = Stat>> {
|
||||
fn du(
|
||||
mut my_stat: Stat,
|
||||
options: &Options,
|
||||
depth: usize,
|
||||
inodes: &mut HashSet<u64>,
|
||||
) -> Box<DoubleEndedIterator<Item = Stat>> {
|
||||
let mut stats = vec![];
|
||||
let mut futures = vec![];
|
||||
|
||||
|
@ -95,8 +107,12 @@ fn du(mut my_stat: Stat, options: &Options, depth: usize) -> Box<DoubleEndedIter
|
|||
Ok(entry) => match Stat::new(entry.path()) {
|
||||
Ok(this_stat) => {
|
||||
if this_stat.is_dir {
|
||||
futures.push(du(this_stat, options, depth + 1));
|
||||
futures.push(du(this_stat, options, depth + 1, inodes));
|
||||
} else {
|
||||
if inodes.contains(&this_stat.inode) {
|
||||
continue;
|
||||
}
|
||||
inodes.insert(this_stat.inode);
|
||||
my_stat.size += this_stat.size;
|
||||
my_stat.blocks += this_stat.blocks;
|
||||
if options.all {
|
||||
|
@ -285,7 +301,7 @@ pub fn uumain(args: Vec<String>) -> i32 {
|
|||
};
|
||||
number * multiple
|
||||
}
|
||||
None => 1024,
|
||||
None => get_default_blocks(),
|
||||
};
|
||||
|
||||
let convert_size = |size: u64| -> String {
|
||||
|
@ -332,10 +348,12 @@ Try '{} --help' for more information.",
|
|||
|
||||
let mut grand_total = 0;
|
||||
for path_str in strs.into_iter() {
|
||||
let path = PathBuf::from(path_str);
|
||||
let path = PathBuf::from(&path_str);
|
||||
match Stat::new(path) {
|
||||
Ok(stat) => {
|
||||
let iter = du(stat, &options, 0).into_iter();
|
||||
let mut inodes: HashSet<u64> = HashSet::new();
|
||||
|
||||
let iter = du(stat, &options, 0, &mut inodes).into_iter();
|
||||
let (_, len) = iter.size_hint();
|
||||
let len = len.unwrap();
|
||||
for (index, stat) in iter.enumerate() {
|
||||
|
@ -398,7 +416,9 @@ Try '{} --help' for more information.",
|
|||
}
|
||||
}
|
||||
}
|
||||
Err(error) => show_error!("{}", error),
|
||||
Err(_) => {
|
||||
show_error!("{}: {}", path_str, "No such file or directory");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
1
tests/fixtures/du/subdir/deeper/words.txt
vendored
Normal file
1
tests/fixtures/du/subdir/deeper/words.txt
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
hello
|
BIN
tests/fixtures/du/subdir/links/subwords.txt
vendored
Normal file
BIN
tests/fixtures/du/subdir/links/subwords.txt
vendored
Normal file
Binary file not shown.
1
tests/fixtures/du/subdir/links/subwords2.txt
vendored
Normal file
1
tests/fixtures/du/subdir/links/subwords2.txt
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
hello
|
1
tests/fixtures/du/words.txt
vendored
Normal file
1
tests/fixtures/du/words.txt
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
hi
|
127
tests/test_du.rs
Normal file
127
tests/test_du.rs
Normal file
|
@ -0,0 +1,127 @@
|
|||
use common::util::*;
|
||||
|
||||
const SUB_DIR: &str = "subdir/deeper";
|
||||
const SUB_DIR_LINKS: &str = "subdir/links";
|
||||
const SUB_FILE: &str = "subdir/links/subwords.txt";
|
||||
const SUB_LINK: &str = "subdir/links/sublink.txt";
|
||||
|
||||
#[test]
|
||||
fn test_du_basics() {
|
||||
let (_at, mut ucmd) = at_and_ucmd!();
|
||||
let result = ucmd.run();
|
||||
assert!(result.success);
|
||||
assert_eq!(result.stderr, "");
|
||||
}
|
||||
#[cfg(target_os = "macos")]
|
||||
fn _du_basics(s: String) {
|
||||
let answer = "32\t./subdir
|
||||
8\t./subdir/deeper
|
||||
24\t./subdir/links
|
||||
40\t./
|
||||
";
|
||||
assert_eq!(s, answer);
|
||||
}
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
fn _du_basics(s: String) {
|
||||
let answer = "28\t./subdir
|
||||
8\t./subdir/deeper
|
||||
16\t./subdir/links
|
||||
36\t./
|
||||
";
|
||||
assert_eq!(s, answer);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_du_basics_subdir() {
|
||||
let (_at, mut ucmd) = at_and_ucmd!();
|
||||
|
||||
let result = ucmd.arg(SUB_DIR).run();
|
||||
assert!(result.success);
|
||||
assert_eq!(result.stderr, "");
|
||||
_du_basics_subdir(result.stdout);
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
fn _du_basics_subdir(s: String) {
|
||||
assert_eq!(s, "4\tsubdir/deeper\n");
|
||||
}
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
fn _du_basics_subdir(s: String) {
|
||||
assert_eq!(s, "8\tsubdir/deeper\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_du_basics_bad_name() {
|
||||
let (_at, mut ucmd) = at_and_ucmd!();
|
||||
|
||||
let result = ucmd.arg("bad_name").run();
|
||||
assert_eq!(result.stdout, "");
|
||||
assert_eq!(
|
||||
result.stderr,
|
||||
"du: error: bad_name: No such file or directory\n"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_du_soft_link() {
|
||||
let ts = TestScenario::new("du");
|
||||
|
||||
let link = ts.cmd("ln").arg("-s").arg(SUB_FILE).arg(SUB_LINK).run();
|
||||
assert!(link.success);
|
||||
|
||||
let result = ts.ucmd().arg(SUB_DIR_LINKS).run();
|
||||
assert!(result.success);
|
||||
assert_eq!(result.stderr, "");
|
||||
_du_soft_link(result.stdout);
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
fn _du_soft_link(s: String) {
|
||||
assert_eq!(s, "16\tsubdir/links\n");
|
||||
}
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
fn _du_soft_link(s: String) {
|
||||
assert_eq!(s, "16\tsubdir/links\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_du_hard_link() {
|
||||
let ts = TestScenario::new("du");
|
||||
|
||||
let link = ts.cmd("ln").arg(SUB_FILE).arg(SUB_LINK).run();
|
||||
assert!(link.success);
|
||||
|
||||
let result = ts.ucmd().arg(SUB_DIR_LINKS).run();
|
||||
assert!(result.success);
|
||||
assert_eq!(result.stderr, "");
|
||||
// We do not double count hard links as the inodes are identical
|
||||
_du_hard_link(result.stdout);
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
fn _du_hard_link(s: String) {
|
||||
assert_eq!(s, "12\tsubdir/links\n")
|
||||
}
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
fn _du_hard_link(s: String) {
|
||||
assert_eq!(s, "16\tsubdir/links\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_du_d_flag() {
|
||||
let ts = TestScenario::new("du");
|
||||
|
||||
let result = ts.ucmd().arg("-d").arg("1").run();
|
||||
assert!(result.success);
|
||||
assert_eq!(result.stderr, "");
|
||||
_du_d_flag(result.stdout);
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
fn _du_d_flag(s: String) {
|
||||
assert_eq!(s, "16\t./subdir\n20\t./\n");
|
||||
}
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
fn _du_d_flag(s: String) {
|
||||
assert_eq!(s, "28\t./subdir\n36\t./\n");
|
||||
}
|
|
@ -52,6 +52,7 @@ generic! {
|
|||
"cut", test_cut;
|
||||
"dircolors", test_dircolors;
|
||||
"dirname", test_dirname;
|
||||
"du", test_du;
|
||||
"echo", test_echo;
|
||||
"env", test_env;
|
||||
"expr", test_expr;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue