1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-28 11:37:44 +00:00

Merge pull request #1169 from bootandy/du

Fix Du mac/inodes
This commit is contained in:
Alex Lyon 2018-03-28 10:33:06 -07:00 committed by GitHub
commit 87d317e22f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 157 additions and 6 deletions

View file

@ -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");
}
}
}

View file

@ -0,0 +1 @@
hello

Binary file not shown.

View file

@ -0,0 +1 @@
hello

1
tests/fixtures/du/words.txt vendored Normal file
View file

@ -0,0 +1 @@
hi

127
tests/test_du.rs Normal file
View 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");
}

View file

@ -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;