Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions src/uu/du/src/block_size.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// This file is part of the uutils coreutils package.
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.

use uucore::parser::parse_size::{ParseSizeError, parse_size_non_zero_u64};

#[derive(Clone)]
pub(super) struct BlockSize {
pub(super) bytes: u64,
suffix: String,
}

impl BlockSize {
pub(super) fn new(bytes: u64) -> Self {
Self {
bytes,
suffix: String::new(),
}
}

pub(super) fn parse(value: &str) -> Result<Self, ParseSizeError> {
let size = value;
let bytes = parse_size_non_zero_u64(size)?;
// An omitted multiplier requests a unit label; an explicit multiplier
// requests just the count, even when that multiplier is one.
let mut suffix = if size.starts_with(|c: char| c.is_ascii_digit()) {
String::new()
} else {
size.to_owned()
};
if let Some(first) = suffix.get_mut(..1) {
first.make_ascii_uppercase();
}
if suffix == "KB" {
"kB".clone_into(&mut suffix);
} else if suffix.ends_with('D') {
suffix.pop();
}
Ok(Self { bytes, suffix })
}

pub(super) fn format(&self, size: u64) -> String {
let count = size.div_ceil(self.bytes);
let mut number = count.to_string();
number.push_str(&self.suffix);
number
}
}
63 changes: 29 additions & 34 deletions src/uu/du/src/du.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ use uucore::line_ending::LineEnding;
use uucore::safe_traversal::{DirFd, SymlinkBehavior};
use uucore::translate;

use block_size::BlockSize;
use uucore::parser::parse_block_size;
use uucore::parser::parse_glob;
use uucore::parser::parse_size::{ParseSizeError, parse_size_u64};
Expand All @@ -47,6 +48,8 @@ use windows_sys::Win32::Storage::FileSystem::{
FileStandardInfo, GetFileInformationByHandleEx,
};

mod block_size;

mod options {
pub const HELP: &str = "help";
pub const NULL: &str = "0";
Expand Down Expand Up @@ -114,7 +117,7 @@ enum Deref {
enum SizeFormat {
HumanDecimal,
HumanBinary,
BlockSize(u64),
BlockSize(BlockSize),
}

#[derive(PartialEq, Eq, Hash, Clone, Copy)]
Expand Down Expand Up @@ -302,35 +305,27 @@ fn get_file_info(path: &Path, _metadata: &Metadata) -> Option<FileInfo> {
result
}

fn read_block_size(s: Option<&str>, diag_args: Option<&[OsString]>) -> UResult<u64> {
fn read_block_size(s: Option<&str>, diag_args: Option<&[OsString]>) -> UResult<BlockSize> {
if let Some(s) = s {
parse_size_u64(s)
.and_then(|bytes| {
// A block size of zero is rejected here rather than by the
// caller, so that it goes through the caret path like every
// other bad SIZE.
if bytes == 0 {
Err(ParseSizeError::ParseFailure(s.to_string()))
} else {
Ok(bytes)
}
})
.map_err(|e| {
let message = format_error_message(&e, s, options::BLOCK_SIZE);
e.size_value_error(
diag_args,
&OptionValue::new(s, 'B', options::BLOCK_SIZE),
0,
&message,
USimpleError::new(1, message.clone()),
)
})
} else if let Some(bytes) =
parse_block_size::block_size_from_env(&["DU_BLOCK_SIZE", "BLOCK_SIZE", "BLOCKSIZE"]).found()
{
Ok(bytes)
BlockSize::parse(s).map_err(|e| {
let message = format_error_message(&e, s, options::BLOCK_SIZE);
e.size_value_error(
diag_args,
&OptionValue::new(s, 'B', options::BLOCK_SIZE),
0,
&message,
USimpleError::new(1, message.clone()),
)
})
} else {
Ok(parse_block_size::default_block_size())
// The first set variable wins, even if its value is invalid.
let from_env = ["DU_BLOCK_SIZE", "BLOCK_SIZE", "BLOCKSIZE"]
.into_iter()
.find_map(|name| env::var(name).ok());
Ok(from_env
.as_deref()
.and_then(|value| BlockSize::parse(value).ok())
.unwrap_or_else(|| BlockSize::new(parse_block_size::default_block_size())))
}
}

Expand Down Expand Up @@ -899,7 +894,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,
Expand All @@ -913,7 +908,7 @@ impl StatPrinter {
// we ignore block size (-B) with --inodes
size.to_string()
} else {
size.div_ceil(block_size).to_string()
block_size.format(size)
}
}
}
Expand Down Expand Up @@ -1017,15 +1012,15 @@ fn parse_size_format(matches: &ArgMatches, diag_args: Option<&[OsString]>) -> UR
)?);
let candidates = [
(
SizeFormat::BlockSize(1),
SizeFormat::BlockSize(BlockSize::new(1)),
get_size_format_flag_arg_index_if_present(matches, options::BYTES),
),
(
SizeFormat::BlockSize(1024),
SizeFormat::BlockSize(BlockSize::new(1024)),
get_size_format_flag_arg_index_if_present(matches, options::BLOCK_SIZE_1K),
),
(
SizeFormat::BlockSize(1024 * 1024),
SizeFormat::BlockSize(BlockSize::new(1024 * 1024)),
get_size_format_flag_arg_index_if_present(matches, options::BLOCK_SIZE_1M),
),
(
Expand Down Expand Up @@ -1633,7 +1628,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(), None), Ok(1024)));
assert_eq!(read_block_size(it.as_deref(), None).unwrap().bytes, 1024);
}
}
}
90 changes: 90 additions & 0 deletions tests/by-util/test_du.rs
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,96 @@ fn test_du_env_block_size_hierarchy() {
assert_eq!(expected, result2);
}

#[test]
fn test_du_block_size_unit_display() {
use std::fmt::Write;

let ts = TestScenario::new(util_name!());
// Sparse files exercise rounding without depending on filesystem allocation.
let files = ["empty", "below", "exact", "above", "large"];
for (unit, bytes, suffix) in [
("K", 1024_u64, "K"),
("k", 1024, "K"),
("KiB", 1024, "KiB"),
("kiB", 1024, "KiB"),
("M", 1_048_576, "M"),
("m", 1_048_576, "M"),
("MiB", 1_048_576, "MiB"),
("miB", 1_048_576, "MiB"),
("kB", 1000, "kB"),
("KB", 1000, "kB"),
("MB", 1_000_000, "MB"),
("mB", 1_000_000, "MB"),
("kD", 1000, "K"),
] {
let large_blocks = if bytes <= 1024 { 12_345 } else { 12 };
let lengths = [0, bytes - 1, bytes, bytes + 1, bytes * large_blocks + 17];
for (file, len) in files.iter().zip(lengths) {
std::fs::File::create(ts.fixtures.plus(file))
.unwrap()
.set_len(len)
.unwrap();
}
for prefix in ["", "1"] {
let value = format!("{prefix}{unit}");
let display_suffix = if prefix.is_empty() { suffix } else { "" };
let mut expected = String::new();
for (file, count) in files.iter().zip([0, 1, 1, 2, large_blocks + 1]) {
writeln!(expected, "{count}{display_suffix}\t{file}").unwrap();
}
for source in [
"-B",
"--block-size=",
"DU_BLOCK_SIZE",
"BLOCK_SIZE",
"BLOCKSIZE",
] {
let mut cmd = ts.ucmd();
cmd.arg("-A").args(&files);
if source.starts_with('-') {
cmd.arg(format!("{source}{value}"));
} else {
cmd.env(source, &value);
}
cmd.succeeds().stdout_only(&expected);
}
}
}
}

#[test]
fn test_du_block_size_display_overrides() {
let ts = TestScenario::new(util_name!());
std::fs::File::create(ts.fixtures.plus("data"))
.unwrap()
.set_len(4097)
.unwrap();
for (args, expected) in [
(vec!["-k"], "5\tdata\n"),
(vec!["-m"], "1\tdata\n"),
(vec!["-b"], "4097\tdata\n"),
(vec!["-h"], "4.1K\tdata\n"),
(vec!["--block-size=1KiB"], "5\tdata\n"),
(vec!["--block-size=2KiB"], "3\tdata\n"),
(vec!["-BKiB", "-k"], "5\tdata\n"),
(vec!["-k", "-BKiB"], "5KiB\tdata\n"),
(vec!["-c", "-BKiB"], "5KiB\tdata\n5KiB\ttotal\n"),
] {
ts.ucmd()
.env("DU_BLOCK_SIZE", "KiB")
.args(&["-A", "data"])
.args(&args)
.succeeds()
.stdout_only(expected);
}
ts.ucmd()
.env("DU_BLOCK_SIZE", "invalid")
.env("BLOCK_SIZE", "KiB")
.args(&["-A", "data"])
.succeeds()
.stdout_only("5\tdata\n");
}

#[test]
fn test_du_binary_block_size() {
let ts = TestScenario::new(util_name!());
Expand Down
Loading