Fix truncate to handle non-UTF-8 filenames

This commit is contained in:
Sylvestre Ledru
2025-08-14 10:52:24 +02:00
parent b54d999cc6
commit 693bdd7748
2 changed files with 42 additions and 15 deletions
+18 -15
View File
@@ -5,6 +5,7 @@
// spell-checker:ignore (ToDO) RFILE refsize rfilename fsize tsize
use clap::{Arg, ArgAction, Command};
use std::ffi::OsString;
use std::fs::{OpenOptions, metadata};
use std::io::ErrorKind;
#[cfg(unix)]
@@ -94,9 +95,9 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
}
})?;
let files: Vec<String> = matches
.get_many::<String>(options::ARG_FILES)
.map(|v| v.map(ToString::to_string).collect())
let files: Vec<OsString> = matches
.get_many::<OsString>(options::ARG_FILES)
.map(|v| v.cloned().collect())
.unwrap_or_default();
if files.is_empty() {
@@ -158,7 +159,8 @@ pub fn uu_app() -> Command {
.value_name("FILE")
.action(ArgAction::Append)
.required(true)
.value_hint(clap::ValueHint::FilePath),
.value_hint(clap::ValueHint::FilePath)
.value_parser(clap::value_parser!(OsString)),
)
}
@@ -174,18 +176,18 @@ pub fn uu_app() -> Command {
///
/// If the file could not be opened, or there was a problem setting the
/// size of the file.
fn file_truncate(filename: &str, create: bool, size: u64) -> UResult<()> {
fn file_truncate(filename: &OsString, create: bool, size: u64) -> UResult<()> {
let path = Path::new(filename);
#[cfg(unix)]
if let Ok(metadata) = metadata(filename) {
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.quote()),
translate!("truncate-error-cannot-open-no-device", "filename" => filename.to_string_lossy().quote()),
));
}
}
let path = Path::new(filename);
match OpenOptions::new().write(true).create(create).open(path) {
Ok(file) => file.set_len(size),
Err(e) if e.kind() == ErrorKind::NotFound && !create => Ok(()),
@@ -216,7 +218,7 @@ fn file_truncate(filename: &str, create: bool, size: u64) -> UResult<()> {
fn truncate_reference_and_size(
rfilename: &str,
size_string: &str,
filenames: &[String],
filenames: &[OsString],
create: bool,
) -> UResult<()> {
let mode = match parse_mode_and_size(size_string) {
@@ -275,7 +277,7 @@ fn truncate_reference_and_size(
/// If at least one file is a named pipe (also known as a fifo).
fn truncate_reference_file_only(
rfilename: &str,
filenames: &[String],
filenames: &[OsString],
create: bool,
) -> UResult<()> {
let metadata = metadata(rfilename).map_err(|e| match e.kind() {
@@ -312,7 +314,7 @@ fn truncate_reference_file_only(
/// the size of at least one file.
///
/// If at least one file is a named pipe (also known as a fifo).
fn truncate_size_only(size_string: &str, filenames: &[String], create: bool) -> UResult<()> {
fn truncate_size_only(size_string: &str, filenames: &[OsString], create: bool) -> UResult<()> {
let mode = parse_mode_and_size(size_string).map_err(|e| {
USimpleError::new(1, translate!("truncate-error-invalid-number", "error" => e))
})?;
@@ -325,13 +327,14 @@ fn truncate_size_only(size_string: &str, filenames: &[String], create: bool) ->
}
for filename in filenames {
let fsize = match metadata(filename) {
let path = Path::new(filename);
let fsize = match metadata(path) {
Ok(m) => {
#[cfg(unix)]
if m.file_type().is_fifo() {
return Err(USimpleError::new(
1,
translate!("truncate-error-cannot-open-no-device", "filename" => filename.quote()),
translate!("truncate-error-cannot-open-no-device", "filename" => filename.to_string_lossy().quote()),
));
}
m.len()
@@ -351,7 +354,7 @@ fn truncate(
_: bool,
reference: Option<String>,
size: Option<String>,
filenames: &[String],
filenames: &[OsString],
) -> UResult<()> {
let create = !no_create;
+24
View File
@@ -420,3 +420,27 @@ fn test_fifo_error_reference_and_size() {
.no_stdout()
.stderr_contains("cannot open 'fifo' for writing: No such device or address");
}
#[test]
#[cfg(target_os = "linux")]
fn test_truncate_non_utf8_paths() {
use std::fs;
let ts = TestScenario::new(util_name!());
let at = &ts.fixtures;
// Create test file with normal name first
at.write("temp.txt", "test content");
// Rename to non-UTF-8 name
#[cfg(unix)]
{
use std::os::unix::ffi::OsStrExt;
let file_name = std::ffi::OsStr::from_bytes(b"test_\xFF\xFE.txt");
fs::rename(at.subdir.join("temp.txt"), at.subdir.join(file_name)).unwrap();
// Test that truncate can handle non-UTF-8 filenames
ts.ucmd().arg("-s").arg("10").arg(file_name).succeeds();
}
}