From 007a704cd957803454b32bb808940dab4e7baa8e Mon Sep 17 00:00:00 2001 From: wtcpython <1762993226@qq.com> Date: Thu, 30 Jul 2026 20:08:35 +0800 Subject: [PATCH] du: preserve suffix-only block size units --- src/uu/du/src/du.rs | 54 ++++++++++++++++++++++++++++------------ tests/by-util/test_du.rs | 30 +++++++++++++++++++++- 2 files changed, 67 insertions(+), 17 deletions(-) diff --git a/src/uu/du/src/du.rs b/src/uu/du/src/du.rs index f30127ef7b0..a7f56730d20 100644 --- a/src/uu/du/src/du.rs +++ b/src/uu/du/src/du.rs @@ -113,7 +113,7 @@ enum Deref { enum SizeFormat { HumanDecimal, HumanBinary, - BlockSize(u64), + BlockSize(u64, Option), } #[derive(PartialEq, Eq, Hash, Clone, Copy)] @@ -301,19 +301,37 @@ fn get_file_info(path: &Path, _metadata: &Metadata) -> Option { result } -fn read_block_size(s: Option<&str>) -> UResult { +fn read_block_size(s: Option<&str>) -> UResult<(u64, Option)> { + let vars = ["DU_BLOCK_SIZE", "BLOCK_SIZE", "BLOCKSIZE"]; if let Some(s) = s { parse_size_u64(s) + .map(|block_size| (block_size, suffix_from_parsed_block_size(s))) .map_err(|e| USimpleError::new(1, format_error_message(&e, s, options::BLOCK_SIZE))) - } else if let Some(bytes) = - parse_block_size::block_size_from_env(&["DU_BLOCK_SIZE", "BLOCK_SIZE", "BLOCKSIZE"]).found() - { - Ok(bytes) + } else if let Some(block_size) = parse_block_size::block_size_from_env(&vars).found() { + let suffix = vars + .into_iter() + .find_map(|var| env::var(var).ok()) + .and_then(|value| suffix_from_parsed_block_size(&value)); + Ok((block_size, suffix)) } else { - Ok(parse_block_size::default_block_size()) + Ok((parse_block_size::default_block_size(), None)) } } +fn suffix_from_parsed_block_size(s: &str) -> Option { + let mut chars = s.chars(); + let unit = chars.next()?.to_ascii_uppercase(); + if !unit.is_ascii_alphabetic() || unit == 'B' { + return None; + } + + Some(match chars.as_str() { + "" | "D" => unit.to_string(), + "B" if unit == 'K' => "kB".to_string(), + suffix => format!("{unit}{suffix}"), + }) +} + #[cfg(all(unix, not(target_os = "redox")))] fn time_from_raw_stat( entry_stat: &uucore::safe_traversal::Metadata, @@ -885,7 +903,7 @@ impl StatPrinter { } fn convert_size(&self, size: u64) -> String { - match self.size_format { + match &self.size_format { SizeFormat::HumanDecimal => uucore::format::human::human_readable( size, uucore::format::human::SizeFormat::Decimal, @@ -894,12 +912,16 @@ impl StatPrinter { size, uucore::format::human::SizeFormat::Binary, ), - SizeFormat::BlockSize(block_size) => { + SizeFormat::BlockSize(block_size, suffix) => { if self.inodes { // we ignore block size (-B) with --inodes size.to_string() } else { - size.div_ceil(block_size).to_string() + let blocks = size.div_ceil(*block_size); + match suffix { + Some(suffix) => format!("{blocks}{suffix}"), + None => blocks.to_string(), + } } } } @@ -994,27 +1016,27 @@ fn get_size_format_flag_arg_index_if_present(matches: &ArgMatches, arg: &str) -> fn parse_block_size_arg_or_default_fallback(matches: &ArgMatches) -> UResult { let block_size_str = matches.get_one::(options::BLOCK_SIZE); - let block_size = read_block_size(block_size_str.map(AsRef::as_ref))?; + let (block_size, suffix) = read_block_size(block_size_str.map(AsRef::as_ref))?; if block_size == 0 { return Err(std::io::Error::other(translate!("du-error-invalid-block-size-argument", "option" => options::BLOCK_SIZE, "value" => block_size_str.map_or("???BUG", |v| v).quote())) .into()); } - Ok(SizeFormat::BlockSize(block_size)) + Ok(SizeFormat::BlockSize(block_size, suffix)) } fn parse_size_format(matches: &ArgMatches) -> UResult { let block_size_value_or_default_fallback = parse_block_size_arg_or_default_fallback(matches)?; let candidates = [ ( - SizeFormat::BlockSize(1), + SizeFormat::BlockSize(1, None), get_size_format_flag_arg_index_if_present(matches, options::BYTES), ), ( - SizeFormat::BlockSize(1024), + SizeFormat::BlockSize(1024, None), get_size_format_flag_arg_index_if_present(matches, options::BLOCK_SIZE_1K), ), ( - SizeFormat::BlockSize(1024 * 1024), + SizeFormat::BlockSize(1024 * 1024, None), get_size_format_flag_arg_index_if_present(matches, options::BLOCK_SIZE_1M), ), ( @@ -1601,7 +1623,7 @@ mod test_du { fn test_read_block_size() { let test_data = [Some("1024".to_string()), Some("K".to_string()), None]; for it in &test_data { - assert!(matches!(read_block_size(it.as_deref()), Ok(1024))); + assert!(matches!(read_block_size(it.as_deref()), Ok((1024, _)))); } } } diff --git a/tests/by-util/test_du.rs b/tests/by-util/test_du.rs index 81450b57f21..72fc62bd3f6 100644 --- a/tests/by-util/test_du.rs +++ b/tests/by-util/test_du.rs @@ -302,6 +302,34 @@ fn test_du_env_block_size_hierarchy() { assert_eq!(expected, result2); } +#[test] +fn test_du_suffix_only_block_size() { + let ts = TestScenario::new(util_name!()); + let file = "file"; + ts.fixtures.write(file, "x"); + + for (arg, env, expected) in [ + (Some("-BM"), None, "1M"), + (Some("-B1M"), None, "1"), + (None, Some(("DU_BLOCK_SIZE", "M")), "1M"), + (None, Some(("BLOCK_SIZE", "KB")), "1kB"), + (None, Some(("BLOCKSIZE", "MiB")), "1MiB"), + (None, Some(("DU_BLOCK_SIZE", "1M")), "1"), + ] { + let mut cmd = ts.ucmd(); + if let Some(arg) = arg { + cmd.arg(arg); + } + if let Some((var, value)) = env { + cmd.env(var, value); + } + cmd.arg("--apparent-size") + .arg(file) + .succeeds() + .stdout_only(format!("{expected}\t{file}\n")); + } +} + #[test] fn test_du_binary_block_size() { let ts = TestScenario::new(util_name!()); @@ -2104,7 +2132,7 @@ fn test_du_inodes_blocksize_ineffective() { let at = &ts.fixtures; let fpath = at.plus("test.txt"); at.touch(fpath); - for method in ["-B3", "--block-size=3"] { + for method in ["-B3", "--block-size=3", "-BM"] { // No warning ts.ucmd() .arg(method)