@@ -38,40 +38,50 @@ impl TruncateMode {
3838 /// reduce by is greater than `fsize`, then this function returns
3939 /// 0 (since it cannot return a negative number).
4040 ///
41+ /// # Returns
42+ ///
43+ /// `None` if rounding by 0, else the target size.
44+ ///
4145 /// # Examples
4246 ///
4347 /// Extending a file of 10 bytes by 5 bytes:
4448 ///
4549 /// ```rust,ignore
4650 /// let mode = TruncateMode::Extend(5);
4751 /// let fsize = 10;
48- /// assert_eq!(mode.to_size(fsize), 15 );
52+ /// assert_eq!(mode.to_size(fsize), Some(15) );
4953 /// ```
5054 ///
5155 /// Reducing a file by more than its size results in 0:
5256 ///
5357 /// ```rust,ignore
5458 /// let mode = TruncateMode::Reduce(5);
5559 /// let fsize = 3;
56- /// assert_eq!(mode.to_size(fsize), 0);
60+ /// assert_eq!(mode.to_size(fsize), Some(0));
61+ /// ```
62+ ///
63+ /// Rounding a file by 0:
64+ ///
65+ /// ```rust,ignore
66+ /// let mode = TruncateMode::RoundDown(0);
67+ /// let fsize = 17;
68+ /// assert_eq!(mode.to_size(fsize), None);
5769 /// ```
58- fn to_size ( & self , fsize : u64 ) -> u64 {
70+ fn to_size ( & self , fsize : u64 ) -> Option < u64 > {
5971 match self {
60- Self :: Absolute ( size) => * size,
61- Self :: Extend ( size) => fsize + size,
62- Self :: Reduce ( size) => {
63- if * size > fsize {
64- 0
65- } else {
66- fsize - size
67- }
68- }
69- Self :: AtMost ( size) => fsize. min ( * size) ,
70- Self :: AtLeast ( size) => fsize. max ( * size) ,
71- Self :: RoundDown ( size) => fsize - fsize % size,
72- Self :: RoundUp ( size) => fsize + fsize % size,
72+ Self :: Absolute ( size) => Some ( * size) ,
73+ Self :: Extend ( size) => Some ( fsize + size) ,
74+ Self :: Reduce ( size) => Some ( fsize. saturating_sub ( * size) ) ,
75+ Self :: AtMost ( size) => Some ( fsize. min ( * size) ) ,
76+ Self :: AtLeast ( size) => Some ( fsize. max ( * size) ) ,
77+ Self :: RoundDown ( size) => fsize. checked_rem ( * size) . map ( |remainder| fsize - remainder) ,
78+ Self :: RoundUp ( size) => fsize. checked_rem ( * size) . map ( |remainder| fsize + remainder) ,
7379 }
7480 }
81+
82+ fn is_absolute ( & self ) -> bool {
83+ matches ! ( self , Self :: Absolute ( _) )
84+ }
7585}
7686
7787pub mod options {
@@ -170,18 +180,9 @@ pub fn uu_app() -> Command {
170180///
171181/// If the file could not be opened, or there was a problem setting the
172182/// size of the file.
173- fn file_truncate ( filename : & OsString , create : bool , size : u64 ) -> UResult < ( ) > {
183+ fn do_file_truncate ( filename : & Path , create : bool , size : u64 ) -> UResult < ( ) > {
174184 let path = Path :: new ( filename) ;
175185
176- #[ cfg( unix) ]
177- if let Ok ( metadata) = metadata ( path) {
178- if metadata. file_type ( ) . is_fifo ( ) {
179- return Err ( USimpleError :: new (
180- 1 ,
181- translate ! ( "truncate-error-cannot-open-no-device" , "filename" => filename. to_string_lossy( ) . quote( ) ) ,
182- ) ) ;
183- }
184- }
185186 match OpenOptions :: new ( ) . write ( true ) . create ( create) . open ( path) {
186187 Ok ( file) => file. set_len ( size) ,
187188 Err ( e) if e. kind ( ) == ErrorKind :: NotFound && !create => Ok ( ( ) ) ,
@@ -192,181 +193,99 @@ fn file_truncate(filename: &OsString, create: bool, size: u64) -> UResult<()> {
192193 )
193194}
194195
195- /// Truncate files to a size relative to a given file.
196- ///
197- /// `rfilename` is the name of the reference file.
198- ///
199- /// `size_string` gives the size relative to the reference file to which
200- /// to set the target files. For example, "+3K" means "set each file to
201- /// be three kilobytes larger than the size of the reference file".
202- ///
203- /// If `create` is true, then each file will be created if it does not
204- /// already exist.
205- ///
206- /// # Errors
207- ///
208- /// If any file could not be opened, or there was a problem setting
209- /// the size of at least one file.
210- ///
211- /// If at least one file is a named pipe (also known as a fifo).
212- fn truncate_reference_and_size (
213- rfilename : & str ,
214- size_string : & str ,
215- filenames : & [ OsString ] ,
216- create : bool ,
196+ fn file_truncate (
197+ no_create : bool ,
198+ reference_size : Option < u64 > ,
199+ mode : & TruncateMode ,
200+ filename : & OsString ,
217201) -> UResult < ( ) > {
218- let mode = match parse_mode_and_size ( size_string) {
219- Err ( e) => {
220- return Err ( USimpleError :: new (
221- 1 ,
222- translate ! ( "truncate-error-invalid-number" , "error" => e) ,
223- ) ) ;
224- }
225- Ok ( TruncateMode :: Absolute ( _) ) => {
226- return Err ( USimpleError :: new (
227- 1 ,
228- translate ! ( "truncate-error-must-specify-relative-size" ) ,
229- ) ) ;
202+ let path = Path :: new ( filename) ;
203+
204+ // Get the length of the file.
205+ let file_size = match metadata ( path) {
206+ Ok ( metadata) => {
207+ // A pipe has no length. Do this check here to avoid duplicate `stat()` syscall.
208+ #[ cfg( unix) ]
209+ if metadata. file_type ( ) . is_fifo ( ) {
210+ return Err ( USimpleError :: new (
211+ 1 ,
212+ translate ! ( "truncate-error-cannot-open-no-device" , "filename" => filename. to_string_lossy( ) . quote( ) ) ,
213+ ) ) ;
214+ }
215+ metadata. len ( )
230216 }
231- Ok ( m ) => m ,
217+ Err ( _ ) => 0 ,
232218 } ;
233219
234- if let TruncateMode :: RoundDown ( 0 ) | TruncateMode :: RoundUp ( 0 ) = mode {
220+ // The reference size can be either:
221+ //
222+ // 1. The size of a given file
223+ // 2. The size of the file to be truncated if no reference has been provided.
224+ let actual_reference_size = reference_size. unwrap_or ( file_size) ;
225+
226+ let Some ( truncate_size) = mode. to_size ( actual_reference_size) else {
235227 return Err ( USimpleError :: new (
236228 1 ,
237229 translate ! ( "truncate-error-division-by-zero" ) ,
238230 ) ) ;
239- }
240-
241- let metadata = metadata ( rfilename) . map_err ( |e| match e. kind ( ) {
242- ErrorKind :: NotFound => USimpleError :: new (
243- 1 ,
244- translate ! ( "truncate-error-cannot-stat-no-such-file" , "filename" => rfilename. quote( ) ) ,
245- ) ,
246- _ => e. map_err_context ( String :: new) ,
247- } ) ?;
248-
249- let fsize = metadata. len ( ) ;
250- let tsize = mode. to_size ( fsize) ;
251-
252- for filename in filenames {
253- file_truncate ( filename, create, tsize) ?;
254- }
231+ } ;
255232
256- Ok ( ( ) )
233+ do_file_truncate ( path , !no_create , truncate_size )
257234}
258235
259- /// Truncate files to match the size of a given reference file.
260- ///
261- /// `rfilename` is the name of the reference file.
262- ///
263- /// If `create` is true, then each file will be created if it does not
264- /// already exist.
265- ///
266- /// # Errors
267- ///
268- /// If any file could not be opened, or there was a problem setting
269- /// the size of at least one file.
270- ///
271- /// If at least one file is a named pipe (also known as a fifo).
272- fn truncate_reference_file_only (
273- rfilename : & str ,
236+ fn truncate (
237+ no_create : bool ,
238+ _: bool ,
239+ reference : Option < String > ,
240+ size : Option < String > ,
274241 filenames : & [ OsString ] ,
275- create : bool ,
276242) -> UResult < ( ) > {
277- let metadata = metadata ( rfilename) . map_err ( |e| match e. kind ( ) {
278- ErrorKind :: NotFound => USimpleError :: new (
279- 1 ,
280- translate ! ( "truncate-error-cannot-stat-no-such-file" , "filename" => rfilename. quote( ) ) ,
281- ) ,
282- _ => e. map_err_context ( String :: new) ,
283- } ) ?;
284-
285- let tsize = metadata. len ( ) ;
286-
287- for filename in filenames {
288- file_truncate ( filename, create, tsize) ?;
289- }
243+ let reference_size = match reference {
244+ Some ( reference_path) => {
245+ let reference_metadata = metadata ( & reference_path) . map_err ( |error| match error. kind ( ) {
246+ ErrorKind :: NotFound => USimpleError :: new (
247+ 1 ,
248+ translate ! ( "truncate-error-cannot-stat-no-such-file" , "filename" => reference_path. quote( ) ) ,
249+ ) ,
250+ _ => error. map_err_context ( String :: new) ,
251+ } ) ?;
252+
253+ Some ( reference_metadata. len ( ) )
254+ }
255+ None => None ,
256+ } ;
290257
291- Ok ( ( ) )
292- }
258+ let size_string = size. as_deref ( ) ;
293259
294- /// Truncate files to a specified size.
295- ///
296- /// `size_string` gives either an absolute size or a relative size. A
297- /// relative size adjusts the size of each file relative to its current
298- /// size. For example, "3K" means "set each file to be three kilobytes"
299- /// whereas "+3K" means "set each file to be three kilobytes larger than
300- /// its current size".
301- ///
302- /// If `create` is true, then each file will be created if it does not
303- /// already exist.
304- ///
305- /// # Errors
306- ///
307- /// If any file could not be opened, or there was a problem setting
308- /// the size of at least one file.
309- ///
310- /// If at least one file is a named pipe (also known as a fifo).
311- fn truncate_size_only ( size_string : & str , filenames : & [ OsString ] , create : bool ) -> UResult < ( ) > {
312- let mode = parse_mode_and_size ( size_string) . map_err ( |e| {
313- USimpleError :: new ( 1 , translate ! ( "truncate-error-invalid-number" , "error" => e) )
314- } ) ?;
260+ // Omitting the mode is equivalent to extending a file by 0 bytes.
261+ let mode = match size_string {
262+ Some ( string) => match parse_mode_and_size ( string) {
263+ Err ( error) => {
264+ return Err ( USimpleError :: new (
265+ 1 ,
266+ translate ! ( "truncate-error-invalid-number" , "error" => error) ,
267+ ) ) ;
268+ }
269+ Ok ( mode) => mode,
270+ } ,
271+ None => TruncateMode :: Extend ( 0 ) ,
272+ } ;
315273
316- if let TruncateMode :: RoundDown ( 0 ) | TruncateMode :: RoundUp ( 0 ) = mode {
274+ // If a reference file has been given, the truncate mode cannot be absolute.
275+ if reference_size. is_some ( ) && mode. is_absolute ( ) {
317276 return Err ( USimpleError :: new (
318277 1 ,
319- translate ! ( "truncate-error-division-by-zero " ) ,
278+ translate ! ( "truncate-error-must-specify-relative-size " ) ,
320279 ) ) ;
321280 }
322281
323282 for filename in filenames {
324- let path = Path :: new ( filename) ;
325- let fsize = match metadata ( path) {
326- Ok ( m) => {
327- #[ cfg( unix) ]
328- if m. file_type ( ) . is_fifo ( ) {
329- return Err ( USimpleError :: new (
330- 1 ,
331- translate ! ( "truncate-error-cannot-open-no-device" , "filename" => filename. to_string_lossy( ) . quote( ) ) ,
332- ) ) ;
333- }
334- m. len ( )
335- }
336- Err ( _) => 0 ,
337- } ;
338- let tsize = mode. to_size ( fsize) ;
339- // TODO: Fix duplicate call to stat
340- file_truncate ( filename, create, tsize) ?;
283+ file_truncate ( no_create, reference_size, & mode, filename) ?;
341284 }
342285
343286 Ok ( ( ) )
344287}
345288
346- fn truncate (
347- no_create : bool ,
348- _: bool ,
349- reference : Option < String > ,
350- size : Option < String > ,
351- filenames : & [ OsString ] ,
352- ) -> UResult < ( ) > {
353- let create = !no_create;
354-
355- // There are four possibilities
356- // - reference file given and size given,
357- // - reference file given but no size given,
358- // - no reference file given but size given,
359- // - no reference file given and no size given,
360- match ( reference, size) {
361- ( Some ( rfilename) , Some ( size_string) ) => {
362- truncate_reference_and_size ( & rfilename, & size_string, filenames, create)
363- }
364- ( Some ( rfilename) , None ) => truncate_reference_file_only ( & rfilename, filenames, create) ,
365- ( None , Some ( size_string) ) => truncate_size_only ( & size_string, filenames, create) ,
366- ( None , None ) => unreachable ! ( ) , // this case cannot happen anymore because it's handled by clap
367- }
368- }
369-
370289/// Decide whether a character is one of the size modifiers, like '+' or '<'.
371290fn is_modifier ( c : char ) -> bool {
372291 c == '+' || c == '-' || c == '<' || c == '>' || c == '/' || c == '%'
@@ -432,8 +351,11 @@ mod tests {
432351
433352 #[ test]
434353 fn test_to_size ( ) {
435- assert_eq ! ( TruncateMode :: Extend ( 5 ) . to_size( 10 ) , 15 ) ;
436- assert_eq ! ( TruncateMode :: Reduce ( 5 ) . to_size( 10 ) , 5 ) ;
437- assert_eq ! ( TruncateMode :: Reduce ( 5 ) . to_size( 3 ) , 0 ) ;
354+ assert_eq ! ( TruncateMode :: Extend ( 5 ) . to_size( 10 ) , Some ( 15 ) ) ;
355+ assert_eq ! ( TruncateMode :: Reduce ( 5 ) . to_size( 10 ) , Some ( 5 ) ) ;
356+ assert_eq ! ( TruncateMode :: Reduce ( 5 ) . to_size( 3 ) , Some ( 0 ) ) ;
357+ assert_eq ! ( TruncateMode :: RoundDown ( 4 ) . to_size( 13 ) , Some ( 12 ) ) ;
358+ assert_eq ! ( TruncateMode :: RoundUp ( 8 ) . to_size( 16 ) , Some ( 16 ) ) ;
359+ assert_eq ! ( TruncateMode :: RoundDown ( 0 ) . to_size( 123 ) , None ) ;
438360 }
439361}
0 commit comments