diff --git a/src/uu/fmt/src/fmt.rs b/src/uu/fmt/src/fmt.rs index 46f9d547f..4919fb435 100644 --- a/src/uu/fmt/src/fmt.rs +++ b/src/uu/fmt/src/fmt.rs @@ -6,8 +6,10 @@ // spell-checker:ignore (ToDO) PSKIP linebreak ostream parasplit tabwidth xanti xprefix use clap::{Arg, ArgAction, ArgMatches, Command}; +use std::ffi::OsString; use std::fs::File; use std::io::{BufReader, BufWriter, Read, Stdout, Write, stdin, stdout}; +use std::path::Path; use uucore::display::Quotable; use uucore::error::{FromIo, UResult, USimpleError}; use uucore::translate; @@ -205,27 +207,27 @@ impl FmtOptions { /// /// A `UResult<()>` indicating success or failure. fn process_file( - file_name: &str, + file_name: &OsString, fmt_opts: &FmtOptions, ostream: &mut BufWriter, ) -> UResult<()> { - let mut fp = BufReader::new(match file_name { - "-" => Box::new(stdin()) as Box, - _ => { - let f = File::open(file_name).map_err_context( - || translate!("fmt-error-cannot-open-for-reading", "file" => file_name.quote()), - )?; - if f.metadata() - .map_err_context( - || translate!("fmt-error-cannot-get-metadata", "file" => file_name.quote()), - )? - .is_dir() - { - return Err(FmtError::ReadError.into()); - } - - Box::new(f) as Box + let mut fp = BufReader::new(if file_name == "-" { + Box::new(stdin()) as Box + } else { + let path = Path::new(file_name); + let f = File::open(path).map_err_context( + || translate!("fmt-error-cannot-open-for-reading", "file" => path.quote()), + )?; + if f.metadata() + .map_err_context( + || translate!("fmt-error-cannot-get-metadata", "file" => path.quote()), + )? + .is_dir() + { + return Err(FmtError::ReadError.into()); } + + Box::new(f) as Box }); let p_stream = ParagraphStream::new(fmt_opts, &mut fp); @@ -258,23 +260,24 @@ fn process_file( /// # Returns /// A `UResult<()>` with the file names, or an error if one of the file names could not be parsed /// (e.g., it is given as a negative number not in the first argument and not after a -- -fn extract_files(matches: &ArgMatches) -> UResult> { +fn extract_files(matches: &ArgMatches) -> UResult> { let in_first_pos = matches .index_of(options::FILES_OR_WIDTH) .is_some_and(|x| x == 1); let is_neg = |s: &str| s.parse::().is_ok_and(|w| w < 0); - let files: UResult> = matches - .get_many::(options::FILES_OR_WIDTH) + let files: UResult> = matches + .get_many::(options::FILES_OR_WIDTH) .into_iter() .flatten() .enumerate() .filter_map(|(i, x)| { - if is_neg(x) { + let x_str = x.to_string_lossy(); + if is_neg(&x_str) { if in_first_pos && i == 0 { None } else { - let first_num = x + let first_num = x_str .chars() .nth(1) .expect("a negative number should be at least two characters long"); @@ -287,7 +290,7 @@ fn extract_files(matches: &ArgMatches) -> UResult> { .collect(); if files.as_ref().is_ok_and(|f| f.is_empty()) { - Ok(vec!["-".into()]) + Ok(vec![OsString::from("-")]) } else { files } @@ -304,8 +307,11 @@ fn extract_width(matches: &ArgMatches) -> UResult> { } if let Some(1) = matches.index_of(options::FILES_OR_WIDTH) { - let width_arg = matches.get_one::(options::FILES_OR_WIDTH).unwrap(); - if let Some(num) = width_arg.strip_prefix('-') { + let width_arg = matches + .get_one::(options::FILES_OR_WIDTH) + .unwrap(); + let width_str = width_arg.to_string_lossy(); + if let Some(num) = width_str.strip_prefix('-') { Ok(num.parse::().ok()) } else { // will be treated as a file name @@ -456,6 +462,7 @@ pub fn uu_app() -> Command { .action(ArgAction::Append) .value_name("FILES") .value_hint(clap::ValueHint::FilePath) + .value_parser(clap::value_parser!(OsString)) .allow_negative_numbers(true), ) } diff --git a/tests/by-util/test_fmt.rs b/tests/by-util/test_fmt.rs index 54d827f83..abf2e132c 100644 --- a/tests/by-util/test_fmt.rs +++ b/tests/by-util/test_fmt.rs @@ -4,7 +4,8 @@ // file that was distributed with this source code. // spell-checker:ignore plass samp - +#[cfg(target_os = "linux")] +use std::os::unix::ffi::OsStringExt; use uutests::new_ucmd; #[test] @@ -374,3 +375,16 @@ fn test_fmt_knuth_plass_line_breaking() { .succeeds() .stdout_is(expected); } + +#[test] +#[cfg(target_os = "linux")] +fn test_fmt_non_utf8_paths() { + use uutests::at_and_ucmd; + + let (at, mut ucmd) = at_and_ucmd!(); + let filename = std::ffi::OsString::from_vec(vec![0xFF, 0xFE]); + + std::fs::write(at.plus(&filename), b"hello world this is a test").unwrap(); + + ucmd.arg(&filename).succeeds(); +}