mirror of
https://github.com/uutils/coreutils.git
synced 2026-06-10 15:48:22 -07:00
Fix truncate to handle non-UTF-8 filenames
This commit is contained in:
@@ -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;
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user