mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-29 03:57: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::io::{stderr, Result, Write};
|
||||||
use std::os::unix::fs::MetadataExt;
|
use std::os::unix::fs::MetadataExt;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
use std::collections::HashSet;
|
||||||
use time::Timespec;
|
use time::Timespec;
|
||||||
|
|
||||||
const NAME: &'static str = "du";
|
const NAME: &'static str = "du";
|
||||||
|
@ -48,6 +49,7 @@ struct Stat {
|
||||||
size: u64,
|
size: u64,
|
||||||
blocks: u64,
|
blocks: u64,
|
||||||
nlink: u64,
|
nlink: u64,
|
||||||
|
inode: u64,
|
||||||
created: u64,
|
created: u64,
|
||||||
accessed: u64,
|
accessed: u64,
|
||||||
modified: u64,
|
modified: u64,
|
||||||
|
@ -62,6 +64,7 @@ impl Stat {
|
||||||
size: metadata.len(),
|
size: metadata.len(),
|
||||||
blocks: metadata.blocks() as u64,
|
blocks: metadata.blocks() as u64,
|
||||||
nlink: metadata.nlink() as u64,
|
nlink: metadata.nlink() as u64,
|
||||||
|
inode: metadata.ino() as u64,
|
||||||
created: metadata.mtime() as u64,
|
created: metadata.mtime() as u64,
|
||||||
accessed: metadata.atime() as u64,
|
accessed: metadata.atime() as u64,
|
||||||
modified: metadata.mtime() 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.
|
// 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
|
// 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 stats = vec![];
|
||||||
let mut futures = 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(entry) => match Stat::new(entry.path()) {
|
||||||
Ok(this_stat) => {
|
Ok(this_stat) => {
|
||||||
if this_stat.is_dir {
|
if this_stat.is_dir {
|
||||||
futures.push(du(this_stat, options, depth + 1));
|
futures.push(du(this_stat, options, depth + 1, inodes));
|
||||||
} else {
|
} else {
|
||||||
|
if inodes.contains(&this_stat.inode) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
inodes.insert(this_stat.inode);
|
||||||
my_stat.size += this_stat.size;
|
my_stat.size += this_stat.size;
|
||||||
my_stat.blocks += this_stat.blocks;
|
my_stat.blocks += this_stat.blocks;
|
||||||
if options.all {
|
if options.all {
|
||||||
|
@ -285,7 +301,7 @@ pub fn uumain(args: Vec<String>) -> i32 {
|
||||||
};
|
};
|
||||||
number * multiple
|
number * multiple
|
||||||
}
|
}
|
||||||
None => 1024,
|
None => get_default_blocks(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let convert_size = |size: u64| -> String {
|
let convert_size = |size: u64| -> String {
|
||||||
|
@ -332,10 +348,12 @@ Try '{} --help' for more information.",
|
||||||
|
|
||||||
let mut grand_total = 0;
|
let mut grand_total = 0;
|
||||||
for path_str in strs.into_iter() {
|
for path_str in strs.into_iter() {
|
||||||
let path = PathBuf::from(path_str);
|
let path = PathBuf::from(&path_str);
|
||||||
match Stat::new(path) {
|
match Stat::new(path) {
|
||||||
Ok(stat) => {
|
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) = iter.size_hint();
|
||||||
let len = len.unwrap();
|
let len = len.unwrap();
|
||||||
for (index, stat) in iter.enumerate() {
|
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;
|
"cut", test_cut;
|
||||||
"dircolors", test_dircolors;
|
"dircolors", test_dircolors;
|
||||||
"dirname", test_dirname;
|
"dirname", test_dirname;
|
||||||
|
"du", test_du;
|
||||||
"echo", test_echo;
|
"echo", test_echo;
|
||||||
"env", test_env;
|
"env", test_env;
|
||||||
"expr", test_expr;
|
"expr", test_expr;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue