Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

df: switch from u64 to u128 to handle fs with large inodes nr #6071

Merged
merged 3 commits into from
Mar 18, 2024
Merged
Changes from 1 commit
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
197 changes: 168 additions & 29 deletions src/uu/df/src/table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,14 @@
use std::fmt;
use std::ops::AddAssign;

type InodesIntT = u128;
type MaybeInodesT = Option<InodesIntT>;
Copy link
Member

Choose a reason for hiding this comment

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

I appreciate the idea here, but I think it obfuscates the exact data type too much. Id rather just see Option<u128> everywhere than MaybeInodesT because then I actually know what I'm dealing with.


/// A row in the filesystem usage data table.
///
/// A row comprises several pieces of information, including the
/// filesystem device, the mountpoint, the number of bytes used, etc.
#[derive(Clone)]

Check warning on line 28 in src/uu/df/src/table.rs

View check run for this annotation

Codecov / codecov/patch

src/uu/df/src/table.rs#L28

Added line #L28 was not covered by tests
pub(crate) struct Row {
/// The filename given on the command-line, if given.
file: Option<String>,
Expand Down Expand Up @@ -58,13 +62,13 @@
bytes_capacity: Option<f64>,

/// Total number of inodes in the filesystem.
inodes: u64,
inodes: MaybeInodesT,

Check warning on line 65 in src/uu/df/src/table.rs

View check run for this annotation

Codecov / codecov/patch

src/uu/df/src/table.rs#L65

Added line #L65 was not covered by tests

/// Number of used inodes.
inodes_used: u64,
inodes_used: MaybeInodesT,

Check warning on line 68 in src/uu/df/src/table.rs

View check run for this annotation

Codecov / codecov/patch

src/uu/df/src/table.rs#L68

Added line #L68 was not covered by tests

/// Number of free inodes.
inodes_free: u64,
inodes_free: MaybeInodesT,

Check warning on line 71 in src/uu/df/src/table.rs

View check run for this annotation

Codecov / codecov/patch

src/uu/df/src/table.rs#L71

Added line #L71 was not covered by tests

/// Percentage of inodes that are used, given as a float between 0 and 1.
///
Expand All @@ -85,14 +89,36 @@
bytes_usage: None,
#[cfg(target_os = "macos")]
bytes_capacity: None,
inodes: 0,
inodes_used: 0,
inodes_free: 0,
inodes: Some(0),
inodes_used: Some(0),
inodes_free: Some(0),
inodes_usage: None,
}
}
}

fn checked_accumulation_op(accumulator: &MaybeInodesT, summand: &MaybeInodesT) -> MaybeInodesT {
let a = (*accumulator)?;
if let Some(s) = *summand {
if s > InodesIntT::MAX / 2 {
eprintln!("invalid inodes number ({s}) - filesystem will be ignored");
*accumulator

Check warning on line 105 in src/uu/df/src/table.rs

View check run for this annotation

Codecov / codecov/patch

src/uu/df/src/table.rs#L104-L105

Added lines #L104 - L105 were not covered by tests
} else {
a.checked_add(s)
}
} else {
*accumulator

Check warning on line 110 in src/uu/df/src/table.rs

View check run for this annotation

Codecov / codecov/patch

src/uu/df/src/table.rs#L110

Added line #L110 was not covered by tests
}
}

fn calc_inode_usage(inodes: MaybeInodesT, inodes_used: MaybeInodesT) -> Option<f64> {
if inodes? == 0 {
None

Check warning on line 116 in src/uu/df/src/table.rs

View check run for this annotation

Codecov / codecov/patch

src/uu/df/src/table.rs#L116

Added line #L116 was not covered by tests
} else {
Some(inodes_used? as f64 / inodes? as f64)
}
}

impl AddAssign for Row {
/// Sum the numeric values of two rows.
///
Expand All @@ -102,8 +128,8 @@
let bytes = self.bytes + rhs.bytes;
let bytes_used = self.bytes_used + rhs.bytes_used;
let bytes_avail = self.bytes_avail + rhs.bytes_avail;
let inodes = self.inodes + rhs.inodes;
let inodes_used = self.inodes_used + rhs.inodes_used;
let inodes = checked_accumulation_op(&self.inodes, &rhs.inodes);
let inodes_used = checked_accumulation_op(&self.inodes_used, &rhs.inodes_used);
*self = Self {
file: None,
fs_device: "total".into(),
Expand All @@ -125,12 +151,8 @@
bytes_capacity: None,
inodes,
inodes_used,
inodes_free: self.inodes_free + rhs.inodes_free,
inodes_usage: if inodes == 0 {
None
} else {
Some(inodes_used as f64 / inodes as f64)
},
inodes_free: checked_accumulation_op(&self.inodes_free, &rhs.inodes_free),
inodes_usage: calc_inode_usage(inodes, inodes_used),
}
}
}
Expand Down Expand Up @@ -178,9 +200,9 @@
} else {
Some(bavail as f64 / ((bused + bavail) as f64))
},
inodes: files,
inodes_used: fused,
inodes_free: ffree,
inodes: Some(files as InodesIntT),
inodes_used: Some(fused as InodesIntT),
inodes_free: Some(ffree as InodesIntT),
inodes_usage: if files == 0 {
None
} else {
Expand Down Expand Up @@ -235,11 +257,15 @@
/// Get a string giving the scaled version of the input number.
///
/// The scaling factor is defined in the `options` field.
fn scaled_inodes(&self, size: u64) -> String {
if let Some(h) = self.options.human_readable {
to_magnitude_and_suffix(size.into(), SuffixType::HumanReadable(h))
fn scaled_inodes(&self, maybe_size: MaybeInodesT) -> String {
if let Some(size) = maybe_size {
if let Some(h) = self.options.human_readable {
to_magnitude_and_suffix(size, SuffixType::HumanReadable(h))
} else {
size.to_string()
}
} else {
size.to_string()
"int_overflow".into()

Check warning on line 268 in src/uu/df/src/table.rs

View check run for this annotation

Codecov / codecov/patch

src/uu/df/src/table.rs#L268

Added line #L268 was not covered by tests
}
}

Expand Down Expand Up @@ -505,9 +531,9 @@
#[cfg(target_os = "macos")]
bytes_capacity: Some(0.5),

inodes: 10,
inodes_used: 2,
inodes_free: 8,
inodes: Some(10),
inodes_used: Some(2),
inodes_free: Some(8),
inodes_usage: Some(0.2),
}
}
Expand Down Expand Up @@ -698,9 +724,9 @@
fs_device: "my_device".to_string(),
fs_mount: "my_mount".to_string(),

inodes: 10,
inodes_used: 2,
inodes_free: 8,
inodes: Some(10),
inodes_used: Some(2),
inodes_free: Some(8),
inodes_usage: Some(0.2),

..Default::default()
Expand All @@ -721,7 +747,7 @@
};
let row = Row {
bytes: 100,
inodes: 10,
inodes: Some(10),
..Default::default()
};
let fmt = RowFormatter::new(&row, &options, false);
Expand Down Expand Up @@ -846,6 +872,119 @@

let row = Row::from(d);

assert_eq!(row.inodes_used, 0);
assert_eq!(row.inodes_used, Some(0));
}

#[test]
fn test_row_accumulation_u64_overflow() {
let total = u64::MAX as super::InodesIntT;
let used1 = 3000 as super::InodesIntT;
let used2 = 50000 as super::InodesIntT;

let mut row1 = Row {
inodes: Some(total),
inodes_used: Some(used1),
inodes_free: Some(total - used1),
..Default::default()
};

let row2 = Row {
inodes: Some(total),
inodes_used: Some(used2),
inodes_free: Some(total - used2),
..Default::default()
};

row1 += row2;

assert_eq!(row1.inodes, Some(total * 2));
assert_eq!(row1.inodes_used, Some(used1 + used2));
assert_eq!(row1.inodes_free, Some(total * 2 - used1 - used2));
}

#[test]
fn test_row_accumulation_close_to_u128_overflow() {
let total = u128::MAX as super::InodesIntT / 2 - 1;
let used1 = total - 50000;
let used2 = total - 100000;

let mut row1 = Row {
inodes: Some(total),
inodes_used: Some(used1),
inodes_free: Some(total - used1),
..Default::default()
};

let row2 = Row {
inodes: Some(total),
inodes_used: Some(used2),
inodes_free: Some(total - used2),
..Default::default()
};

row1 += row2;

assert_eq!(row1.inodes, Some(total * 2));
assert_eq!(row1.inodes_used, Some(used1 + used2));
assert_eq!(row1.inodes_free, Some(total * 2 - used1 - used2));
}

#[test]
#[allow(clippy::cognitive_complexity)]
fn test_row_accumulation_and_usage_close_over_u128_overflow() {
let total = u128::MAX as super::InodesIntT / 2 - 1;
let used1 = total / 2;
let free1 = total - used1;
let used2 = total / 2 - 10;
let free2 = total - used2;

let mut row1 = Row {
inodes: Some(total),
inodes_used: Some(used1),
inodes_free: Some(free1),
..Default::default()
};

let row2 = Row {
inodes: Some(total),
inodes_used: Some(used2),
inodes_free: Some(free2),
..Default::default()
};

row1 += row2.clone();

assert_eq!(row1.inodes, Some(total * 2));
assert_eq!(row1.inodes_used, Some(used1 + used2));
assert_eq!(row1.inodes_free, Some(free1 + free2));
assert_eq!(row1.inodes_usage, Some(0.5));

row1 += row2.clone();

assert_eq!(row1.inodes, None); // total * 3
assert_eq!(row1.inodes_used, Some(used1 + used2 * 2));
assert_eq!(row1.inodes_free, Some(free1 + free2 * 2));
assert_eq!(row1.inodes_usage, None);

row1 += row2.clone();

assert_eq!(row1.inodes, None); // total * 4
assert_eq!(row1.inodes_used, Some(used1 + used2 * 3)); // used * 4
assert_eq!(row1.inodes_free, None); // free * 4
assert_eq!(row1.inodes_usage, None);

row1 += row2.clone();

assert_eq!(row1.inodes, None); // total * 5
assert_eq!(row1.inodes_used, None); // used * 5
assert_eq!(row1.inodes_free, None); // free * 5
assert_eq!(row1.inodes_usage, None);

row1 += row2;

assert_eq!(row1.inodes, None); // total * 6
assert_eq!(row1.inodes_used, None); // used * 6
assert_eq!(row1.inodes_free, None); // free * 6
assert_eq!(row1.inodes_usage, None);
}
}
Loading