Skip to content

truncate: eliminate duplicate stat() syscall#9527

Merged
sylvestre merged 3 commits intouutils:mainfrom
Jean-Christian-Cirstea:truncate
Dec 19, 2025
Merged

truncate: eliminate duplicate stat() syscall#9527
sylvestre merged 3 commits intouutils:mainfrom
Jean-Christian-Cirstea:truncate

Conversation

@Jean-Christian-Cirstea
Copy link
Contributor

@Jean-Christian-Cirstea Jean-Christian-Cirstea commented Nov 30, 2025

When no reference file is given, truncate performs two stat() syscalls:

  1. The first one to retrieve the size of the file.
  2. The second one to check if the file if a pipe.

This commit fixes this issue by restructuring the code. The main change resides in the elimination of the initial match performed at the top level to select between different scenarios. Instead, the new implementation:

  1. Check if a reference file has been given. If so, retrieve its size.
  2. Determine the given mode. If no mode has been given, TruncateMode::Extend(0) is implied.
  3. Retrieve the size of the file, while checking if it is a pipe. This is the key point of this PR.
  4. Determine the truncate size based on the reference size and the mode. If no reference file has been provided, use the size of the file to be resized as a reference size.
  5. Finally, perform ftruncate() to resize the file.

Comment on lines +77 to +78
Self::RoundDown(size) => fsize.checked_rem(*size).map(|remainder| fsize - remainder),
Self::RoundUp(size) => fsize.checked_rem(*size).map(|remainder| fsize + remainder),
Copy link
Contributor Author

Choose a reason for hiding this comment

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

As previously stated, calling this method when rounding to 0 resulted in a panic. Following the guideline of avoiding panicking code, use checked_rem() method.

Copy link
Contributor

Choose a reason for hiding this comment

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

The remainder calculation seems to be wrong for RoundUp - should be (fsize + size - 1) / size * size to properly round up
no?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The formula is indeed wrong. Should I fix it in this PR or in a separate PR?

Copy link
Contributor

Choose a reason for hiding this comment

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

this pr

Copy link
Contributor Author

Choose a reason for hiding this comment

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

checked_next_multiple_of() depuis la bibliothèque standard est maintenant utilisé pour calculer le prochain multiple de size. Un test unitaire a également été ajouté.

Copy link
Contributor

Choose a reason for hiding this comment

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

please don't use french in the comment ;)

Comment on lines +87 to +89
fn is_absolute(&self) -> bool {
matches!(self, Self::Absolute(_))
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Tiny helper function to determine if self is absolute. Useful when checking if an absolute mode was provided with a reference file.

Comment on lines -176 to -184
#[cfg(unix)]
if let Ok(metadata) = metadata(path) {
if metadata.file_type().is_fifo() {
return Err(USimpleError::new(
1,
translate!("truncate-error-cannot-open-no-device", "filename" => filename.to_string_lossy().quote()),
));
}
}
Copy link
Contributor Author

@Jean-Christian-Cirstea Jean-Christian-Cirstea Nov 30, 2025

Choose a reason for hiding this comment

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

This check was moved to line 212.

Comment on lines +209 to 221
let file_size = match metadata(path) {
Ok(metadata) => {
#[cfg(unix)]
if metadata.file_type().is_fifo() {
return Err(USimpleError::new(
1,
translate!("truncate-error-cannot-open-no-device", "filename" => filename.to_string_lossy().quote()),
));
}
metadata.len()
}
Ok(m) => m,
Err(_) => 0,
};
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Retrieve the size of the file, while checking if it is a pipe. One single stat() syscall required.

Copy link
Contributor

Choose a reason for hiding this comment

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

maybe add this as a comment in the code?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added comments.

Comment on lines +263 to +275
// Omitting the mode is equivalent to extending a file by 0 bytes.
let mode = match size_string {
Some(string) => match parse_mode_and_size(string) {
Err(error) => {
return Err(USimpleError::new(
1,
translate!("truncate-error-invalid-number", "error" => error),
));
}
Ok(mode) => mode,
},
None => TruncateMode::Extend(0),
};
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Specifying no mode implies TruncateMode::Extend(0).

Copy link
Contributor

Choose a reason for hiding this comment

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

same, better in the code as comment :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There is a comment. Look carefully at line 263. Est-ce que j'aurais dû l'écrire en français pour qu'il soit plus clair ? :)

Copy link
Contributor

Choose a reason for hiding this comment

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

Oups pardon :)

Comment on lines -62 to -68
Self::Reduce(size) => {
if *size > fsize {
0
} else {
fsize - size
}
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The standard library provides saturating_sub(). No need to implement its logic by ourselves.

@github-actions
Copy link

GNU testsuite comparison:

Skip an intermittent issue tests/tail/overlay-headers (fails in this run but passes in the 'main' branch)

Signed-off-by: Jean-Christian CÎRSTEA <jean-christian.cirstea@tuta.com>
1. The documentation for parse_mode_and_size() has been updated.
2. Added documentation for TruncateMode::is_absolute().

Signed-off-by: Jean-Christian CÎRSTEA <jean-christian.cirstea@tuta.com>
@github-actions
Copy link

GNU testsuite comparison:

Skip an intermittent issue tests/tail/overlay-headers (fails in this run but passes in the 'main' branch)
Skipping an intermittent issue tests/misc/tee (passes in this run but fails in the 'main' branch)

Signed-off-by: Jean-Christian CÎRSTEA <jean-christian.cirstea@tuta.com>
@github-actions
Copy link

github-actions bot commented Dec 6, 2025

GNU testsuite comparison:

Skip an intermittent issue tests/tail/overlay-headers (fails in this run but passes in the 'main' branch)
Congrats! The gnu test tests/tty/tty-eof is no longer failing!

@Jean-Christian-Cirstea
Copy link
Contributor Author

@sylvestre Any outstanding issues with this PR?

@sylvestre sylvestre merged commit fe97933 into uutils:main Dec 19, 2025
127 checks passed
CrazyRoka pushed a commit to CrazyRoka/coreutils that referenced this pull request Dec 28, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants