Skip to content

df: the --total row sums per-filesystem rounded-up block counts — wrong totals in release, attempt to multiply with overflow with -h (exit 134) #14459

Description

@leeewee

df --total accumulates the total row's block count as Σ ceil(bytesᵢ / block_size) — summing each filesystem's rounded-up block count instead of rounding the summed bytes once. With a large --block-size every non-empty filesystem rounds up to one block, so the total degenerates into roughly a count of filesystems. The -h path then multiplies that inflated count back by the block size, which overflows. Normal rows take neither path, which is why every non-total row matches GNU exactly and only the total row is wrong.

Steps to reproduce

  • default release build (shipped):
    the total row reports wrong numbers — with -B 10000000000000000000 GNU reports a total of 1 block where uutils reports 29 (or 12E with -h).
$ df       --total -h -B 10000000000000000000 -a | tail -1
total            12E  8.9E   15E  44% -                     # uutils
$ /usr/bin/df --total -h -B 10000000000000000000 -a | tail -1
total                    1     1         1  43% -            # GNU

$ df       --total -B 10000000000000000000 -a | tail -1     # without -h
total                   29    25         9  44% -            # uutils
$ /usr/bin/df --total -B 10000000000000000000 -a | tail -1
total                    1     1         1  43% -            # GNU
  • overflow-checks build:
    adding -h multiplies that inflated count back by the block size and the product overflows u64attempt to multiply with overflow at
    df/src/table.rs:312:17, exit 134.
$ df --total -h -B 10000000000000000000 -a
thread 'main' panicked at src/uu/df/src/table.rs:312:17:
attempt to multiply with overflow
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Aborted (core dumped)
$ echo $?
134

Root cause

1. scaled sums per-row ceilings, not the ceiling of the sum.

impl BytesCell {
fn new(bytes: u64, block_size: &BlockSize) -> Self {
Self {
bytes,
scaled: {
let BlockSize::Bytes(d) = block_size;
(bytes as f64 / *d as f64).ceil() as u64
},
}
}
}
impl Add for BytesCell {
type Output = Self;
fn add(self, rhs: Self) -> Self {
Self {
bytes: self.bytes + rhs.bytes,
scaled: self.scaled + rhs.scaled,
}
}
}

2. The -h branch converts blocks back to bytes with an unguarded multiply.

fn scaled_bytes(&self, bytes_column: &BytesCell) -> Cell {
let size = bytes_column.scaled;
let s = if let Some(h) = self.options.human_readable {
let size = if self.is_total_row {
let BlockSize::Bytes(d) = self.options.block_size;
d * size
} else {
bytes_column.bytes
};
to_magnitude_and_suffix(size.into(), SuffixType::HumanReadable(h), true)

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions