@@ -18,10 +18,14 @@ use uucore::fsext::{FsUsage, MountInfo};
18
18
use std:: fmt;
19
19
use std:: ops:: AddAssign ;
20
20
21
+ type InodesIntT = u128 ;
22
+ type MaybeInodesT = Option < InodesIntT > ;
23
+
21
24
/// A row in the filesystem usage data table.
22
25
///
23
26
/// A row comprises several pieces of information, including the
24
27
/// filesystem device, the mountpoint, the number of bytes used, etc.
28
+ #[ derive( Clone ) ]
25
29
pub ( crate ) struct Row {
26
30
/// The filename given on the command-line, if given.
27
31
file : Option < String > ,
@@ -58,13 +62,13 @@ pub(crate) struct Row {
58
62
bytes_capacity : Option < f64 > ,
59
63
60
64
/// Total number of inodes in the filesystem.
61
- inodes : u64 ,
65
+ inodes : MaybeInodesT ,
62
66
63
67
/// Number of used inodes.
64
- inodes_used : u64 ,
68
+ inodes_used : MaybeInodesT ,
65
69
66
70
/// Number of free inodes.
67
- inodes_free : u64 ,
71
+ inodes_free : MaybeInodesT ,
68
72
69
73
/// Percentage of inodes that are used, given as a float between 0 and 1.
70
74
///
@@ -85,14 +89,36 @@ impl Row {
85
89
bytes_usage : None ,
86
90
#[ cfg( target_os = "macos" ) ]
87
91
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 ) ,
91
95
inodes_usage : None ,
92
96
}
93
97
}
94
98
}
95
99
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
+
96
122
impl AddAssign for Row {
97
123
/// Sum the numeric values of two rows.
98
124
///
@@ -102,8 +128,8 @@ impl AddAssign for Row {
102
128
let bytes = self . bytes + rhs. bytes ;
103
129
let bytes_used = self . bytes_used + rhs. bytes_used ;
104
130
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 ) ;
107
133
* self = Self {
108
134
file : None ,
109
135
fs_device : "total" . into ( ) ,
@@ -125,12 +151,8 @@ impl AddAssign for Row {
125
151
bytes_capacity : None ,
126
152
inodes,
127
153
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) ,
134
156
}
135
157
}
136
158
}
@@ -178,9 +200,9 @@ impl From<Filesystem> for Row {
178
200
} else {
179
201
Some ( bavail as f64 / ( ( bused + bavail) as f64 ) )
180
202
} ,
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 ) ,
184
206
inodes_usage : if files == 0 {
185
207
None
186
208
} else {
@@ -235,11 +257,15 @@ impl<'a> RowFormatter<'a> {
235
257
/// Get a string giving the scaled version of the input number.
236
258
///
237
259
/// 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
+ }
241
267
} else {
242
- size . to_string ( )
268
+ "int_overflow" . into ( )
243
269
}
244
270
}
245
271
@@ -505,9 +531,9 @@ mod tests {
505
531
#[ cfg( target_os = "macos" ) ]
506
532
bytes_capacity : Some ( 0.5 ) ,
507
533
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 ) ,
511
537
inodes_usage : Some ( 0.2 ) ,
512
538
}
513
539
}
@@ -698,9 +724,9 @@ mod tests {
698
724
fs_device : "my_device" . to_string ( ) ,
699
725
fs_mount : "my_mount" . to_string ( ) ,
700
726
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 ) ,
704
730
inodes_usage : Some ( 0.2 ) ,
705
731
706
732
..Default :: default ( )
@@ -721,7 +747,7 @@ mod tests {
721
747
} ;
722
748
let row = Row {
723
749
bytes : 100 ,
724
- inodes : 10 ,
750
+ inodes : Some ( 10 ) ,
725
751
..Default :: default ( )
726
752
} ;
727
753
let fmt = RowFormatter :: new ( & row, & options, false ) ;
@@ -846,6 +872,118 @@ mod tests {
846
872
847
873
let row = Row :: from ( d) ;
848
874
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 ) ;
850
988
}
851
989
}
0 commit comments