From bc8415c9dbce3083d706eaa492c0352d8e95728d Mon Sep 17 00:00:00 2001 From: Syukron Rifail M Date: Thu, 10 Jun 2021 22:01:28 +0700 Subject: [PATCH] du: add --dereference --- src/uu/du/src/du.rs | 61 +++++++------- tests/by-util/test_du.rs | 80 ++++++++++++++----- .../subdir/deeper/deeper_dir/deeper_words.txt | 1 + 3 files changed, 95 insertions(+), 47 deletions(-) create mode 100644 tests/fixtures/du/subdir/deeper/deeper_dir/deeper_words.txt diff --git a/src/uu/du/src/du.rs b/src/uu/du/src/du.rs index e466b8afe..e4bac2e18 100644 --- a/src/uu/du/src/du.rs +++ b/src/uu/du/src/du.rs @@ -25,7 +25,6 @@ use std::os::unix::fs::MetadataExt; use std::os::windows::fs::MetadataExt; #[cfg(windows)] use std::os::windows::io::AsRawHandle; -#[cfg(windows)] use std::path::Path; use std::path::PathBuf; use std::str::FromStr; @@ -62,6 +61,7 @@ mod options { pub const TIME: &str = "time"; pub const TIME_STYLE: &str = "time-style"; pub const ONE_FILE_SYSTEM: &str = "one-file-system"; + pub const DEREFERENCE: &str = "dereference"; pub const FILE: &str = "FILE"; } @@ -87,6 +87,7 @@ struct Options { total: bool, separate_dirs: bool, one_file_system: bool, + dereference: bool, } #[derive(PartialEq, Eq, Hash, Clone, Copy)] @@ -107,8 +108,12 @@ struct Stat { } impl Stat { - fn new(path: PathBuf) -> Result { - let metadata = fs::symlink_metadata(&path)?; + fn new(path: PathBuf, options: &Options) -> Result { + let metadata = if options.dereference { + fs::metadata(&path)? + } else { + fs::symlink_metadata(&path)? + }; #[cfg(not(windows))] let file_info = FileInfo { @@ -279,8 +284,14 @@ fn du( for f in read { match f { - Ok(entry) => match Stat::new(entry.path()) { + Ok(entry) => match Stat::new(entry.path(), options) { Ok(this_stat) => { + if let Some(inode) = this_stat.inode { + if inodes.contains(&inode) { + continue; + } + inodes.insert(inode); + } if this_stat.is_dir { if options.one_file_system { if let (Some(this_inode), Some(my_inode)) = @@ -293,12 +304,6 @@ fn du( } futures.push(du(this_stat, options, depth + 1, inodes)); } else { - if let Some(inode) = this_stat.inode { - if inodes.contains(&inode) { - continue; - } - inodes.insert(inode); - } my_stat.size += this_stat.size; my_stat.blocks += this_stat.blocks; if options.all { @@ -308,18 +313,13 @@ fn du( } Err(error) => match error.kind() { ErrorKind::PermissionDenied => { - let description = format!( - "cannot access '{}'", - entry - .path() - .as_os_str() - .to_str() - .unwrap_or("") - ); + let description = format!("cannot access '{}'", entry.path().display()); let error_message = "Permission denied"; show_error_custom_description!(description, "{}", error_message) } - _ => show_error!("{}", error), + _ => { + show_error!("cannot access '{}': {}", entry.path().display(), error) + } }, }, Err(error) => show_error!("{}", error), @@ -327,7 +327,7 @@ fn du( } } - stats.extend(futures.into_iter().flatten().rev().filter(|stat| { + stats.extend(futures.into_iter().flatten().filter(|stat| { if !options.separate_dirs && stat.path.parent().unwrap() == my_stat.path { my_stat.size += stat.size; my_stat.blocks += stat.blocks; @@ -466,12 +466,12 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .long("count-links") .help("count sizes many times if hard linked") ) - // .arg( - // Arg::with_name("dereference") - // .short("L") - // .long("dereference") - // .help("dereference all symbolic links") - // ) + .arg( + Arg::with_name(options::DEREFERENCE) + .short("L") + .long(options::DEREFERENCE) + .help("dereference all symbolic links") + ) // .arg( // Arg::with_name("no-dereference") // .short("P") @@ -588,12 +588,13 @@ pub fn uumain(args: impl uucore::Args) -> i32 { total: matches.is_present(options::TOTAL), separate_dirs: matches.is_present(options::SEPARATE_DIRS), one_file_system: matches.is_present(options::ONE_FILE_SYSTEM), + dereference: matches.is_present(options::DEREFERENCE), }; let files = match matches.value_of(options::FILE) { Some(_) => matches.values_of(options::FILE).unwrap().collect(), None => { - vec!["./"] // TODO: gnu `du` doesn't use trailing "/" here + vec!["."] } }; @@ -655,10 +656,12 @@ Try '{} --help' for more information.", let mut grand_total = 0; for path_string in files { let path = PathBuf::from(&path_string); - match Stat::new(path) { + match Stat::new(path, &options) { Ok(stat) => { let mut inodes: HashSet = HashSet::new(); - + if let Some(inode) = stat.inode { + inodes.insert(inode); + } let iter = du(stat, &options, 0, &mut inodes); let (_, len) = iter.size_hint(); let len = len.unwrap(); diff --git a/tests/by-util/test_du.rs b/tests/by-util/test_du.rs index 93875ae51..ffe449880 100644 --- a/tests/by-util/test_du.rs +++ b/tests/by-util/test_du.rs @@ -8,7 +8,9 @@ use crate::common::util::*; const SUB_DIR: &str = "subdir/deeper"; +const SUB_DEEPER_DIR: &str = "subdir/deeper/deeper_dir"; const SUB_DIR_LINKS: &str = "subdir/links"; +const SUB_DIR_LINKS_DEEPER_SYM_DIR: &str = "subdir/links/deeper_dir"; const SUB_FILE: &str = "subdir/links/subwords.txt"; const SUB_LINK: &str = "subdir/links/sublink.txt"; @@ -21,7 +23,7 @@ fn _du_basics(s: &str) { let answer = "32\t./subdir 8\t./subdir/deeper 24\t./subdir/links -40\t./ +40\t. "; assert_eq!(s, answer); } @@ -30,7 +32,7 @@ fn _du_basics(s: &str) { let answer = "28\t./subdir 8\t./subdir/deeper 16\t./subdir/links -36\t./ +36\t. "; assert_eq!(s, answer); } @@ -54,15 +56,15 @@ fn test_du_basics_subdir() { #[cfg(target_vendor = "apple")] fn _du_basics_subdir(s: &str) { - assert_eq!(s, "4\tsubdir/deeper\n"); + assert_eq!(s, "4\tsubdir/deeper/deeper_dir\n8\tsubdir/deeper\n"); } #[cfg(target_os = "windows")] fn _du_basics_subdir(s: &str) { - assert_eq!(s, "0\tsubdir/deeper\n"); + assert_eq!(s, "0\tsubdir/deeper\\deeper_dir\n0\tsubdir/deeper\n"); } #[cfg(target_os = "freebsd")] fn _du_basics_subdir(s: &str) { - assert_eq!(s, "8\tsubdir/deeper\n"); + assert_eq!(s, "8\tsubdir/deeper/deeper_dir\n16\tsubdir/deeper\n"); } #[cfg(all( not(target_vendor = "apple"), @@ -210,12 +212,7 @@ fn test_du_d_flag() { { let result_reference = scene.cmd("du").arg("-d1").run(); if result_reference.succeeded() { - assert_eq!( - // TODO: gnu `du` doesn't use trailing "/" here - // result.stdout_str(), result_reference.stdout_str() - result.stdout_str().trim_end_matches("/\n"), - result_reference.stdout_str().trim_end_matches('\n') - ); + assert_eq!(result.stdout_str(), result_reference.stdout_str()); return; } } @@ -224,15 +221,15 @@ fn test_du_d_flag() { #[cfg(target_vendor = "apple")] fn _du_d_flag(s: &str) { - assert_eq!(s, "16\t./subdir\n20\t./\n"); + assert_eq!(s, "20\t./subdir\n24\t.\n"); } #[cfg(target_os = "windows")] fn _du_d_flag(s: &str) { - assert_eq!(s, "8\t./subdir\n8\t./\n"); + assert_eq!(s, "8\t.\\subdir\n8\t.\n"); } #[cfg(target_os = "freebsd")] fn _du_d_flag(s: &str) { - assert_eq!(s, "28\t./subdir\n36\t./\n"); + assert_eq!(s, "36\t./subdir\n44\t.\n"); } #[cfg(all( not(target_vendor = "apple"), @@ -242,9 +239,56 @@ fn _du_d_flag(s: &str) { fn _du_d_flag(s: &str) { // MS-WSL linux has altered expected output if !uucore::os::is_wsl_1() { - assert_eq!(s, "28\t./subdir\n36\t./\n"); + assert_eq!(s, "28\t./subdir\n36\t.\n"); } else { - assert_eq!(s, "8\t./subdir\n8\t./\n"); + assert_eq!(s, "8\t./subdir\n8\t.\n"); + } +} + +#[test] +fn test_du_dereference() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + at.symlink_dir(SUB_DEEPER_DIR, SUB_DIR_LINKS_DEEPER_SYM_DIR); + + let result = scene.ucmd().arg("-L").arg(SUB_DIR_LINKS).succeeds(); + + #[cfg(target_os = "linux")] + { + let result_reference = scene.cmd("du").arg("-L").arg(SUB_DIR_LINKS).run(); + if result_reference.succeeded() { + assert_eq!(result.stdout_str(), result_reference.stdout_str()); + return; + } + } + + _du_dereference(result.stdout_str()); +} + +#[cfg(target_vendor = "apple")] +fn _du_dereference(s: &str) { + assert_eq!(s, "4\tsubdir/links/deeper_dir\n16\tsubdir/links\n"); +} +#[cfg(target_os = "windows")] +fn _du_dereference(s: &str) { + assert_eq!(s, "0\tsubdir/links\\deeper_dir\n8\tsubdir/links\n"); +} +#[cfg(target_os = "freebsd")] +fn _du_dereference(s: &str) { + assert_eq!(s, "8\tsubdir/links/deeper_dir\n24\tsubdir/links\n"); +} +#[cfg(all( + not(target_vendor = "apple"), + not(target_os = "windows"), + not(target_os = "freebsd") +))] +fn _du_dereference(s: &str) { + // MS-WSL linux has altered expected output + if !uucore::os::is_wsl_1() { + assert_eq!(s, "8\tsubdir/links/deeper_dir\n24\tsubdir/links\n"); + } else { + assert_eq!(s, "0\tsubdir/links/deeper_dir\n8\tsubdir/links\n"); } } @@ -366,12 +410,12 @@ fn test_du_threshold() { .arg(format!("--threshold={}", threshold)) .succeeds() .stdout_contains("links") - .stdout_does_not_contain("deeper"); + .stdout_does_not_contain("deeper_dir"); scene .ucmd() .arg(format!("--threshold=-{}", threshold)) .succeeds() .stdout_does_not_contain("links") - .stdout_contains("deeper"); + .stdout_contains("deeper_dir"); } diff --git a/tests/fixtures/du/subdir/deeper/deeper_dir/deeper_words.txt b/tests/fixtures/du/subdir/deeper/deeper_dir/deeper_words.txt new file mode 100644 index 000000000..a04238969 --- /dev/null +++ b/tests/fixtures/du/subdir/deeper/deeper_dir/deeper_words.txt @@ -0,0 +1 @@ +hello world!