Skip to content

Commit

Permalink
switch from u64 to Optional<u128> to handle fs with large inodes nr
Browse files Browse the repository at this point in the history
  • Loading branch information
cre4ture committed Mar 14, 2024
1 parent f89cfe2 commit eaab79c
Showing 1 changed file with 167 additions and 29 deletions.
196 changes: 167 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 uucore::fsext::{FsUsage, MountInfo};
use std::fmt;
use std::ops::AddAssign;

type InodesIntT = u128;
type MaybeInodesT = Option<InodesIntT>;

/// 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 @@ pub(crate) struct Row {
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 @@ impl Row {
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
} 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 @@ impl AddAssign for Row {
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 @@ impl AddAssign for Row {
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 @@ impl From<Filesystem> for Row {
} 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 @@ impl<'a> RowFormatter<'a> {
/// 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))

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

View check run for this annotation

Codecov / codecov/patch

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

Added line #L263 was not covered by tests
} 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 @@ mod tests {
#[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 @@ mod tests {
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 @@ mod tests {
};
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,118 @@ mod tests {

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]
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.clone();

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);
}
}

0 comments on commit eaab79c

Please sign in to comment.