Skip to content

ls, df: preserve suffix-only block size units - #14390

Open
Socialpranker wants to merge 2 commits into
uutils:mainfrom
Socialpranker:feat/ls-df-blocksize-suffix
Open

ls, df: preserve suffix-only block size units#14390
Socialpranker wants to merge 2 commits into
uutils:mainfrom
Socialpranker:feat/ls-df-blocksize-suffix

Conversation

@Socialpranker

Copy link
Copy Markdown
Contributor

ls -l --block-size=K and df --block-size=K printed a bare number where
GNU echoes the unit back next to it:

$ ls -l --block-size=K file     # GNU
-rw-r--r-- 1 user user 1K Sep  5 08:00 file
$ ls -l --block-size=K file     # uutils, before
-rw-r--r-- 1 user user 1 Sep  5 08:00 file

GNU's rule: a suffix-only spec (no leading digits — K, k, KB,
KiB, M, ...) echoes the unit next to the scaled number. A spec with a
leading number (--block-size=1K, --block-size=1024, --block-size=2K)
does not — it only sets the divisor. The same distinction applies to
LS_BLOCK_SIZE, BLOCK_SIZE, BLOCKSIZE, and DF_BLOCK_SIZE.

This was already flagged as a known gap in #14330's description ("The 27
cases that still differ are all suffix-only block sizes... a separate
defect in a different place — the same one #13655 is fixing for du") and
is what #13655 fixes for du. This PR fixes the same defect for ls and
df.

The fix

uucore::parser::parse_block_size gains suffix_from_parsed_block_size
(extracts the GNU-canonicalized unit from a suffix-only spec, None
otherwise) and block_size_from_env_with_suffix (like
block_size_from_env, but also returns the suffix from whichever variable
resolved the size). Both are shared by ls and df; du already carries
its own copy of this logic in #13655, which touches only du.rs and
test_du.rs and does not conflict with this change.

df's BlockSize::Bytes gains a second field for the optional suffix,
threaded through read_block_size and echoed in table.rs wherever a
scaled, non-human-readable value is printed.

ls needed a bit more care: BLOCKSIZE affects only the allocation
column (-s, and the total line), not the -l file-size column, so a
single suffix field would leak the unit onto the wrong column when
BLOCKSIZE alone was set. Config now carries the file-size and
allocation suffixes separately (file_size_block_size_suffix,
block_size_suffix), matching the existing split between
file_size_block_size and block_size. -k clears the allocation
suffix along with the allocation block size, but leaves a file-size
suffix from the environment alone, matching GNU.

Neither -h/--human-readable nor --si are affected — they do their
own dynamic scaling and print their own units.

How the GNU behavior was established

By running the installed GNU coreutils 9.11 binary (via Homebrew, gls/
gdf) as a black box and diffing its output against uutils — the rules
above are read off those transcripts. I did not read GNU coreutils
source.

Testing

  • New tests in tests/by-util/test_ls.rs (test_ls_suffix_only_block_size,
    ..._allocation, ..._env_block_size,
    test_ls_suffix_only_blocksize_env_does_not_reach_file_size,
    ..._with_kibibyte_flag, ..._not_used_with_human_readable) and
    tests/by-util/test_df.rs (test_df_suffix_only_block_size,
    ..._env_block_size, ..._not_used_with_human_readable,
    ..._ignored_in_posix_portability_mode), plus unit tests for the new
    uucore helpers.
  • cargo test --no-default-features --features "feat_os_unix ls" --test tests -- block_size:
    46 passed (ls, df, du, dd, truncate, sort block-size suites,
    none regressed) + 6 more under a test_ls_suffix_only filter (name
    substring mismatch in the first run, confirmed separately): all green.
  • cargo test --no-default-features --features df --test tests -- block_size:
    20 passed.
  • cargo fmt --all --check: clean.
  • cargo clippy -p uu_ls -p uu_df -p uucore --all-targets -- -D warnings:
    clean (run per-crate; --all-targets across all three at once hits an
    unrelated, pre-existing uucore::entries test compile error from
    feature unification on this workspace/toolchain — reproduces identically
    on unpatched main).
  • cargo check --target x86_64-pc-windows-gnu: clean.
  • Differential A/B against GNU coreutils 9.11 (Homebrew gls/gdf), 12
    block-size specs (K k KB KiB M m MB G 1K 1024 2K 1MB) x ls -l, plus
    -s, env vars (LS_BLOCK_SIZE, BLOCK_SIZE, BLOCKSIZE,
    DF_BLOCK_SIZE), and the file/allocation-column split: byte-identical
    to GNU in every case. One pre-existing, unrelated mismatch found in the
    process (ls -s --block-size=M under-rounds a partial block) is the
    defect ls: round block counts up when scaling to a block size #14330 already fixes; this PR does not touch ls.rs or that
    code path.

Disclosure

Prepared with AI assistance (Claude Sonnet 5, via Claude Code), per the AI
policy in CONTRIBUTING.md. Every GNU behavior quoted above came from
running the installed binary, not from reading GPL source. All testing
was run locally.

GNU echoes the unit back next to the number when --block-size (or
LS_BLOCK_SIZE/BLOCK_SIZE/BLOCKSIZE/DF_BLOCK_SIZE) is given as a bare
unit (--block-size=K -> "1K"), but not when a numeric multiplier is
given (--block-size=1K or --block-size=1024 -> "1"). uutils printed a
bare number in both cases.

Adds suffix_from_parsed_block_size and block_size_from_env_with_suffix
to uucore's parse_block_size, shared by ls and df. df's BlockSize now
carries an optional display suffix. ls tracks the file-size and
allocation suffixes separately, since BLOCKSIZE only affects the
allocation column (-s) and must not leak into the -l size column.
@sylvestre

Copy link
Copy Markdown
Contributor

could you please make comment #0 a bit shorter ? nobody likes reading long llm output ;)

Comment thread src/uu/df/src/blocks.rs
Comment on lines +127 to +132
/// The default variant is `Bytes(1024, None)`.
///
/// The second field is the GNU-style display suffix (e.g. `"K"`) to echo
/// back next to a scaled number, set only when the block size was given as
/// a suffix-only spec (`--block-size=K`) rather than a numeric one
/// (`--block-size=1K`, `--block-size=1024`).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

make this comment shorter

Comment thread src/uu/ls/src/config.rs
fn resolve_block_sizes_from_env(opt_kb: bool) -> (u64, u64) {
match parse_block_size::block_size_from_env(&["LS_BLOCK_SIZE", "BLOCK_SIZE"]) {
parse_block_size::BlockSizeEnv::Found(size) => {
/// `BLOCKSIZE` only affects the allocation display (`-s`), not file sizes.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same, this comment is too long

BlockSizeEnv::NotSet
}

/// Extract the display suffix from a block-size spec, if the spec is

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i don't think we need 14 lines of comments
esp when they don't follow rustdoc format or provide an example

Comment thread src/uu/ls/src/config.rs
const DEFAULT_FILE_SIZE_BLOCK_SIZE: u64 = 1;

/// Resolve `(file_size_block_size, block_size)` from environment variables.
/// The block sizes `ls` displays with, plus the display suffixes that go

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please do a gh stack and move ls in the second commit of the stack

@github-actions

github-actions Bot commented Sep 5, 2026

Copy link
Copy Markdown

GNU testsuite comparison:

Skip an intermittent issue tests/date/date-locale-hour (fails in this run but passes in the 'main' branch)
Skipping an intermittent issue tests/cut/bounded-memory (passes in this run but fails in the 'main' branch)
Skipping an intermittent issue tests/rm/isatty (passes in this run but fails in the 'main' branch)
Note: The gnu test tests/basenc/bounded-memory is now being skipped but was previously passing.
Note: The gnu test tests/cut/cut-huge-range is now being skipped but was previously passing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants