Skip to content

Commit 68679ea

Browse files
committed
switch from u64 to Optional<u128> to handle fs with large inodes nr
1 parent f89cfe2 commit 68679ea

File tree

1 file changed

+167
-29
lines changed

1 file changed

+167
-29
lines changed

src/uu/df/src/table.rs

Lines changed: 167 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,14 @@ use uucore::fsext::{FsUsage, MountInfo};
1818
use std::fmt;
1919
use std::ops::AddAssign;
2020

21+
type InodesIntT = u128;
22+
type MaybeInodesT = Option<InodesIntT>;
23+
2124
/// A row in the filesystem usage data table.
2225
///
2326
/// A row comprises several pieces of information, including the
2427
/// filesystem device, the mountpoint, the number of bytes used, etc.
28+
#[derive(Clone)]
2529
pub(crate) struct Row {
2630
/// The filename given on the command-line, if given.
2731
file: Option<String>,
@@ -58,13 +62,13 @@ pub(crate) struct Row {
5862
bytes_capacity: Option<f64>,
5963

6064
/// Total number of inodes in the filesystem.
61-
inodes: u64,
65+
inodes: MaybeInodesT,
6266

6367
/// Number of used inodes.
64-
inodes_used: u64,
68+
inodes_used: MaybeInodesT,
6569

6670
/// Number of free inodes.
67-
inodes_free: u64,
71+
inodes_free: MaybeInodesT,
6872

6973
/// Percentage of inodes that are used, given as a float between 0 and 1.
7074
///
@@ -85,14 +89,36 @@ impl Row {
8589
bytes_usage: None,
8690
#[cfg(target_os = "macos")]
8791
bytes_capacity: None,
88-
inodes: 0,
89-
inodes_used: 0,
90-
inodes_free: 0,
92+
inodes: Some(0),
93+
inodes_used: Some(0),
94+
inodes_free: Some(0),
9195
inodes_usage: None,
9296
}
9397
}
9498
}
9599

100+
fn checked_accumulation_op(accumulator: &MaybeInodesT, summand: &MaybeInodesT) -> MaybeInodesT {
101+
let a = (*accumulator)?;
102+
if let Some(s) = *summand {
103+
if s > InodesIntT::MAX / 2 {
104+
eprintln!("invalid inodes number ({s}) - filesystem will be ignored");
105+
*accumulator
106+
} else {
107+
a.checked_add(s)
108+
}
109+
} else {
110+
*accumulator
111+
}
112+
}
113+
114+
fn calc_inode_usage(inodes: MaybeInodesT, inodes_used: MaybeInodesT) -> Option<f64> {
115+
if inodes? == 0 {
116+
None
117+
} else {
118+
Some(inodes_used? as f64 / inodes? as f64)
119+
}
120+
}
121+
96122
impl AddAssign for Row {
97123
/// Sum the numeric values of two rows.
98124
///
@@ -102,8 +128,8 @@ impl AddAssign for Row {
102128
let bytes = self.bytes + rhs.bytes;
103129
let bytes_used = self.bytes_used + rhs.bytes_used;
104130
let bytes_avail = self.bytes_avail + rhs.bytes_avail;
105-
let inodes = self.inodes + rhs.inodes;
106-
let inodes_used = self.inodes_used + rhs.inodes_used;
131+
let inodes = checked_accumulation_op(&self.inodes, &rhs.inodes);
132+
let inodes_used = checked_accumulation_op(&self.inodes_used, &rhs.inodes_used);
107133
*self = Self {
108134
file: None,
109135
fs_device: "total".into(),
@@ -125,12 +151,8 @@ impl AddAssign for Row {
125151
bytes_capacity: None,
126152
inodes,
127153
inodes_used,
128-
inodes_free: self.inodes_free + rhs.inodes_free,
129-
inodes_usage: if inodes == 0 {
130-
None
131-
} else {
132-
Some(inodes_used as f64 / inodes as f64)
133-
},
154+
inodes_free: checked_accumulation_op(&self.inodes_free, &rhs.inodes_free),
155+
inodes_usage: calc_inode_usage(inodes, inodes_used),
134156
}
135157
}
136158
}
@@ -178,9 +200,9 @@ impl From<Filesystem> for Row {
178200
} else {
179201
Some(bavail as f64 / ((bused + bavail) as f64))
180202
},
181-
inodes: files,
182-
inodes_used: fused,
183-
inodes_free: ffree,
203+
inodes: Some(files as InodesIntT),
204+
inodes_used: Some(fused as InodesIntT),
205+
inodes_free: Some(ffree as InodesIntT),
184206
inodes_usage: if files == 0 {
185207
None
186208
} else {
@@ -235,11 +257,15 @@ impl<'a> RowFormatter<'a> {
235257
/// Get a string giving the scaled version of the input number.
236258
///
237259
/// The scaling factor is defined in the `options` field.
238-
fn scaled_inodes(&self, size: u64) -> String {
239-
if let Some(h) = self.options.human_readable {
240-
to_magnitude_and_suffix(size.into(), SuffixType::HumanReadable(h))
260+
fn scaled_inodes(&self, maybe_size: MaybeInodesT) -> String {
261+
if let Some(size) = maybe_size {
262+
if let Some(h) = self.options.human_readable {
263+
to_magnitude_and_suffix(size, SuffixType::HumanReadable(h))
264+
} else {
265+
size.to_string()
266+
}
241267
} else {
242-
size.to_string()
268+
"int_overflow".into()
243269
}
244270
}
245271

@@ -505,9 +531,9 @@ mod tests {
505531
#[cfg(target_os = "macos")]
506532
bytes_capacity: Some(0.5),
507533

508-
inodes: 10,
509-
inodes_used: 2,
510-
inodes_free: 8,
534+
inodes: Some(10),
535+
inodes_used: Some(2),
536+
inodes_free: Some(8),
511537
inodes_usage: Some(0.2),
512538
}
513539
}
@@ -698,9 +724,9 @@ mod tests {
698724
fs_device: "my_device".to_string(),
699725
fs_mount: "my_mount".to_string(),
700726

701-
inodes: 10,
702-
inodes_used: 2,
703-
inodes_free: 8,
727+
inodes: Some(10),
728+
inodes_used: Some(2),
729+
inodes_free: Some(8),
704730
inodes_usage: Some(0.2),
705731

706732
..Default::default()
@@ -721,7 +747,7 @@ mod tests {
721747
};
722748
let row = Row {
723749
bytes: 100,
724-
inodes: 10,
750+
inodes: Some(10),
725751
..Default::default()
726752
};
727753
let fmt = RowFormatter::new(&row, &options, false);
@@ -846,6 +872,118 @@ mod tests {
846872

847873
let row = Row::from(d);
848874

849-
assert_eq!(row.inodes_used, 0);
875+
assert_eq!(row.inodes_used, Some(0));
876+
}
877+
878+
#[test]
879+
fn test_row_accumulation_u64_overflow() {
880+
let total = u64::MAX as super::InodesIntT;
881+
let used1 = 3000 as super::InodesIntT;
882+
let used2 = 50000 as super::InodesIntT;
883+
884+
let mut row1 = Row {
885+
inodes: Some(total),
886+
inodes_used: Some(used1),
887+
inodes_free: Some(total - used1),
888+
..Default::default()
889+
};
890+
891+
let row2 = Row {
892+
inodes: Some(total),
893+
inodes_used: Some(used2),
894+
inodes_free: Some(total - used2),
895+
..Default::default()
896+
};
897+
898+
row1 += row2;
899+
900+
assert_eq!(row1.inodes, Some(total * 2));
901+
assert_eq!(row1.inodes_used, Some(used1 + used2));
902+
assert_eq!(row1.inodes_free, Some(total * 2 - used1 - used2));
903+
}
904+
905+
#[test]
906+
fn test_row_accumulation_close_to_u128_overflow() {
907+
let total = u128::MAX as super::InodesIntT / 2 - 1;
908+
let used1 = total - 50000;
909+
let used2 = total - 100000;
910+
911+
let mut row1 = Row {
912+
inodes: Some(total),
913+
inodes_used: Some(used1),
914+
inodes_free: Some(total - used1),
915+
..Default::default()
916+
};
917+
918+
let row2 = Row {
919+
inodes: Some(total),
920+
inodes_used: Some(used2),
921+
inodes_free: Some(total - used2),
922+
..Default::default()
923+
};
924+
925+
row1 += row2;
926+
927+
assert_eq!(row1.inodes, Some(total * 2));
928+
assert_eq!(row1.inodes_used, Some(used1 + used2));
929+
assert_eq!(row1.inodes_free, Some(total * 2 - used1 - used2));
930+
}
931+
932+
#[test]
933+
fn test_row_accumulation_and_usage_close_over_u128_overflow() {
934+
let total = u128::MAX as super::InodesIntT / 2 - 1;
935+
let used1 = total / 2;
936+
let free1 = total - used1;
937+
let used2 = total / 2 - 10;
938+
let free2 = total - used2;
939+
940+
let mut row1 = Row {
941+
inodes: Some(total),
942+
inodes_used: Some(used1),
943+
inodes_free: Some(free1),
944+
..Default::default()
945+
};
946+
947+
let row2 = Row {
948+
inodes: Some(total),
949+
inodes_used: Some(used2),
950+
inodes_free: Some(free2),
951+
..Default::default()
952+
};
953+
954+
row1 += row2.clone();
955+
956+
assert_eq!(row1.inodes, Some(total * 2));
957+
assert_eq!(row1.inodes_used, Some(used1 + used2));
958+
assert_eq!(row1.inodes_free, Some(free1 + free2));
959+
assert_eq!(row1.inodes_usage, Some(0.5));
960+
961+
row1 += row2.clone();
962+
963+
assert_eq!(row1.inodes, None); // total * 3
964+
assert_eq!(row1.inodes_used, Some(used1 + used2 * 2));
965+
assert_eq!(row1.inodes_free, Some(free1 + free2 * 2));
966+
assert_eq!(row1.inodes_usage, None);
967+
968+
row1 += row2.clone();
969+
970+
assert_eq!(row1.inodes, None); // total * 4
971+
assert_eq!(row1.inodes_used, Some(used1 + used2 * 3)); // used * 4
972+
assert_eq!(row1.inodes_free, None); // free * 4
973+
assert_eq!(row1.inodes_usage, None);
974+
975+
row1 += row2.clone();
976+
977+
assert_eq!(row1.inodes, None); // total * 5
978+
assert_eq!(row1.inodes_used, None); // used * 5
979+
assert_eq!(row1.inodes_free, None); // free * 5
980+
assert_eq!(row1.inodes_usage, None);
981+
982+
row1 += row2;
983+
984+
assert_eq!(row1.inodes, None); // total * 6
985+
assert_eq!(row1.inodes_used, None); // used * 6
986+
assert_eq!(row1.inodes_free, None); // free * 6
987+
assert_eq!(row1.inodes_usage, None);
850988
}
851989
}

0 commit comments

Comments
 (0)