diff --git a/src/du/du.rs b/src/du/du.rs index e87308952..aa79d2663 100644 --- a/src/du/du.rs +++ b/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> { +fn du( + mut my_stat: Stat, + options: &Options, + depth: usize, + inodes: &mut HashSet, +) -> Box> { let mut stats = vec![]; let mut futures = vec![]; @@ -95,8 +107,12 @@ fn du(mut my_stat: Stat, options: &Options, depth: usize) -> Box 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) -> 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 = 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"); + } } } diff --git a/tests/fixtures/du/subdir/deeper/words.txt b/tests/fixtures/du/subdir/deeper/words.txt new file mode 100644 index 000000000..ce0136250 --- /dev/null +++ b/tests/fixtures/du/subdir/deeper/words.txt @@ -0,0 +1 @@ +hello diff --git a/tests/fixtures/du/subdir/links/subwords.txt b/tests/fixtures/du/subdir/links/subwords.txt new file mode 100644 index 000000000..e7f3c2d40 Binary files /dev/null and b/tests/fixtures/du/subdir/links/subwords.txt differ diff --git a/tests/fixtures/du/subdir/links/subwords2.txt b/tests/fixtures/du/subdir/links/subwords2.txt new file mode 100644 index 000000000..ce0136250 --- /dev/null +++ b/tests/fixtures/du/subdir/links/subwords2.txt @@ -0,0 +1 @@ +hello diff --git a/tests/fixtures/du/words.txt b/tests/fixtures/du/words.txt new file mode 100644 index 000000000..45b983be3 --- /dev/null +++ b/tests/fixtures/du/words.txt @@ -0,0 +1 @@ +hi diff --git a/tests/test_du.rs b/tests/test_du.rs new file mode 100644 index 000000000..7ade82dd7 --- /dev/null +++ b/tests/test_du.rs @@ -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"); +} diff --git a/tests/tests.rs b/tests/tests.rs index cb164a91c..b1362dd0a 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -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;