Fix ptx to handle non-UTF-8 filenames

This commit is contained in:
Sylvestre Ledru
2025-08-14 10:52:24 +02:00
parent b5f8931b17
commit 373aafc5ff
2 changed files with 223 additions and 35 deletions
+196 -17
View File
@@ -17,23 +17,87 @@ use std::path::PathBuf;
use uufuzz::{run_gnu_cmd, CommandResult};
// Programs that typically take file/path arguments and should be tested
static PATH_PROGRAMS: &[&str] = &[
// Core file operations
"cat", "cp", "mv", "rm", "ln", "link", "unlink", "touch", "truncate",
// Directory operations
"ls", "mkdir", "rmdir", "du", "stat", "mktemp",
"cat",
"cp",
"mv",
"rm",
"ln",
"link",
"unlink",
"touch",
// Path operations
"basename", "dirname", "readlink", "realpath", "pathchk",
// File content operations
"head", "tail", "tee", "more", "od", "wc", "cksum", "sum",
"ls", "mkdir", "rmdir", "du", "stat", "mktemp", "df", // Path operations
"ls",
"mkdir",
"rmdir",
"du",
"stat",
"df",
"df", // Path operations
"basename",
"dirname",
"readlink",
"realpath",
"pathchk",
// File processing
"sort", "uniq", "split", "csplit", "cut", "tr", "shred",
// File permissions/ownership
"chmod", "chown", "chgrp", "install",
"head", "tail", "tee", "more", "od", "wc", "cksum", "sum", "nl", "tac", // File processing
"head",
"tail",
"tee",
"more",
"od",
"wc",
"cksum",
"sum",
"tac",
"tac", // File processing
"sort",
"uniq",
"split",
"csplit",
"cut",
"tr",
"shred",
"shuf",
"ptx",
// Text processing with files
"comm", "join", "paste", "pr", "fmt", "fold", "expand", "unexpand",
// Directory listing variants
"chmod", "chown", "chgrp", "install", "chcon", "runcon", // Text processing with files
"chmod",
"chown",
"chgrp",
"install",
"runcon",
"runcon", // Text processing with files
"comm",
"join",
"paste",
"pr",
"fmt",
"fold",
"expand",
"dir", "vdir",
"dir",
"mkfifo", "mknod",
"mkfifo",
"mknod",
// File I/O utilities
// File I/O utilities
"dd",
"sync",
"stdbuf",
"dircolors",
"base32", "base64", "basenc",
"base32",
"base64",
"stty", "tty",
"stty",
"env", "nohup", "nice", "timeout",
"env",
"nohup",
"nice",
];
"timeout",
fn generate_non_utf8_bytes() -> Vec<u8> {
let mut rng = rand::rng();
@@ -94,7 +158,8 @@ fn test_program_with_non_utf8_path(program: &str, path: &PathBuf) -> CommandResu
let path_os = path.as_os_str();
// Use the locally built uutils binary instead of system PATH
let local_binary = "/home/sylvestre/dev/debian/coreutils.disable-loca/target/debug/coreutils";
let local_binary = std::env::var("CARGO_BIN_FILE_COREUTILS")
.unwrap_or_else(|_| "target/release/coreutils".to_string());
// Build appropriate arguments for each program
let local_args = match program {
@@ -114,6 +179,22 @@ fn test_program_with_non_utf8_path(program: &str, path: &PathBuf) -> CommandResu
OsString::from("root"),
path_os.to_owned(),
],
"chcon" => vec![
OsString::from(program),
OsString::from("system_u:object_r:admin_home_t:s0"),
path_os.to_owned(),
],
"runcon" => {
let coreutils_binary = std::env::var("CARGO_BIN_FILE_COREUTILS")
.unwrap_or_else(|_| "target/release/coreutils".to_string());
vec![
OsString::from(program),
OsString::from("system_u:object_r:admin_home_t:s0"),
OsString::from(coreutils_binary),
OsString::from("cat"),
path_os.to_owned(),
]
},
// Programs that need source and destination
"cp" | "mv" | "ln" | "link" => {
let dest_path = path.with_extension("dest");
@@ -122,7 +203,7 @@ fn test_program_with_non_utf8_path(program: &str, path: &PathBuf) -> CommandResu
path_os.to_owned(),
dest_path.as_os_str().to_owned(),
]
},
}
"install" => {
let dest_path = path.with_extension("dest");
vec![
@@ -130,7 +211,7 @@ fn test_program_with_non_utf8_path(program: &str, path: &PathBuf) -> CommandResu
path_os.to_owned(),
dest_path.as_os_str().to_owned(),
]
},
}
// Programs that need size/truncate operations
"truncate" => vec![
OsString::from(program),
@@ -147,6 +228,104 @@ fn test_program_with_non_utf8_path(program: &str, path: &PathBuf) -> CommandResu
path_os.to_owned(),
OsString::from("1"),
],
// File creation programs
"mkfifo" | "mknod" => {
let new_path = path.with_extension("new");
if program == "mknod" {
vec![
OsString::from(program),
new_path.as_os_str().to_owned(),
OsString::from("c"),
OsString::from("1"),
OsString::from("3"),
]
vec![
}
},
}
"dd" => vec![
OsString::from(program),
OsString::from(format!("if={}", path_os.to_string_lossy())),
OsString::from("of=/dev/null"),
OsString::from("bs=1"),
OsString::from("count=1"),
],
// Hashsum needs algorithm
"hashsum" => vec![
OsString::from(program),
OsString::from("--md5"),
path_os.to_owned(),
],
"base32" | "base64" | "basenc" => vec![
"df" => vec![
"chroot" => {
"df" => vec![OsString::from(program), path_os.to_owned()],
// chroot needs a directory and command
vec![
OsString::from(program),
path_os.to_owned(),
OsString::from("true"),
},
}
"stty" => vec![
"sync" => vec![OsString::from(program), path_os.to_owned()],
OsString::from(program),
OsString::from("-F"),
path_os.to_owned(),
"tty" => vec![
"tty" => vec![OsString::from(program)], // tty doesn't take file args, but test anyway
"env" => {
let coreutils_binary = std::env::var("CARGO_BIN_FILE_COREUTILS")
.unwrap_or_else(|_| "target/release/coreutils".to_string());
vec![
OsString::from(program),
OsString::from(coreutils_binary),
OsString::from("cat"),
path_os.to_owned(),
]
},
"nohup" => {
let coreutils_binary = std::env::var("CARGO_BIN_FILE_COREUTILS")
.unwrap_or_else(|_| "target/release/coreutils".to_string());
vec![
OsString::from(program),
OsString::from(coreutils_binary),
OsString::from("cat"),
path_os.to_owned(),
]
},
"nice" => {
let coreutils_binary = std::env::var("CARGO_BIN_FILE_COREUTILS")
.unwrap_or_else(|_| "target/release/coreutils".to_string());
vec![
OsString::from(program),
OsString::from(coreutils_binary),
OsString::from("cat"),
path_os.to_owned(),
]
},
"timeout" => {
let coreutils_binary = std::env::var("CARGO_BIN_FILE_COREUTILS")
.unwrap_or_else(|_| "target/release/coreutils".to_string());
vec![
OsString::from(program),
OsString::from("1"),
OsString::from(coreutils_binary),
OsString::from("cat"),
path_os.to_owned(),
]
},
"stdbuf" => {
let coreutils_binary = std::env::var("CARGO_BIN_FILE_COREUTILS")
.unwrap_or_else(|_| "target/release/coreutils".to_string());
vec![
OsString::from(program),
OsString::from("-o0"),
OsString::from(coreutils_binary),
OsString::from("cat"),
path_os.to_owned(),
]
},
// Programs that work with multiple files (use just one for testing)
"comm" | "join" => {
// These need two files, use the same file twice for simplicity
@@ -155,7 +334,7 @@ fn test_program_with_non_utf8_path(program: &str, path: &PathBuf) -> CommandResu
path_os.to_owned(),
path_os.to_owned(),
]
},
}
// Programs that typically take file input
_ => vec![OsString::from(program), path_os.to_owned()],
};
@@ -238,8 +417,8 @@ fuzz_target!(|_data: &[u8]| {
let non_utf8_dir_name = generate_non_utf8_osstring();
let non_utf8_dir = temp_root.join(non_utf8_dir_name);
let local_binary =
"/home/sylvestre/dev/debian/coreutils.disable-loca/target/debug/coreutils";
let local_binary = std::env::var("CARGO_BIN_FILE_COREUTILS")
.unwrap_or_else(|_| "target/release/coreutils".to_string());
let mkdir_args = vec![OsString::from("mkdir"), non_utf8_dir.as_os_str().to_owned()];
let mkdir_result = run_gnu_cmd(local_binary, &mkdir_args, false, None);
+27 -18
View File
@@ -7,10 +7,12 @@
use std::cmp;
use std::collections::{BTreeSet, HashMap, HashSet};
use std::ffi::OsString;
use std::fmt::Write as FmtWrite;
use std::fs::File;
use std::io::{BufRead, BufReader, BufWriter, Read, Write, stdin, stdout};
use std::num::ParseIntError;
use std::path::Path;
use clap::{Arg, ArgAction, Command};
use regex::Regex;
@@ -66,13 +68,12 @@ fn read_word_filter_file(
option: &str,
) -> std::io::Result<HashSet<String>> {
let filename = matches
.get_one::<String>(option)
.expect("parsing options failed!")
.to_string();
.get_one::<OsString>(option)
.expect("parsing options failed!");
let reader: BufReader<Box<dyn Read>> = BufReader::new(if filename == "-" {
Box::new(stdin())
} else {
let file = File::open(filename)?;
let file = File::open(Path::new(filename))?;
Box::new(file)
});
let mut words: HashSet<String> = HashSet::new();
@@ -88,12 +89,12 @@ fn read_char_filter_file(
option: &str,
) -> std::io::Result<HashSet<char>> {
let filename = matches
.get_one::<String>(option)
.get_one::<OsString>(option)
.expect("parsing options failed!");
let mut reader: Box<dyn Read> = if filename == "-" {
Box::new(stdin())
} else {
let file = File::open(filename)?;
let file = File::open(Path::new(filename))?;
Box::new(file)
};
let mut buffer = String::new();
@@ -275,14 +276,14 @@ struct FileContent {
type FileMap = HashMap<String, FileContent>;
fn read_input(input_files: &[String]) -> std::io::Result<FileMap> {
fn read_input(input_files: &[OsString]) -> std::io::Result<FileMap> {
let mut file_map: FileMap = HashMap::new();
let mut offset: usize = 0;
for filename in input_files {
let reader: BufReader<Box<dyn Read>> = BufReader::new(if filename == "-" {
Box::new(stdin())
} else {
let file = File::open(filename)?;
let file = File::open(Path::new(filename))?;
Box::new(file)
});
let lines: Vec<String> = reader.lines().collect::<std::io::Result<Vec<String>>>()?;
@@ -292,7 +293,7 @@ fn read_input(input_files: &[String]) -> std::io::Result<FileMap> {
let chars_lines: Vec<Vec<char>> = lines.iter().map(|x| x.chars().collect()).collect();
let size = lines.len();
file_map.insert(
filename.to_owned(),
filename.to_string_lossy().into_owned(),
FileContent {
lines,
chars_lines,
@@ -736,7 +737,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let output_file;
let mut files = matches
.get_many::<String>(options::FILE)
.get_many::<OsString>(options::FILE)
.into_iter()
.flatten()
.cloned();
@@ -745,18 +746,22 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
input_files = {
let mut files = files.collect::<Vec<_>>();
if files.is_empty() {
files.push("-".to_string());
files.push(OsString::from("-"));
}
files
};
output_file = "-".to_string();
} else {
input_files = vec![files.next().unwrap_or("-".to_string())];
output_file = files.next().unwrap_or("-".to_string());
input_files = vec![files.next().unwrap_or(OsString::from("-"))];
output_file = files
.next()
.unwrap_or(OsString::from("-"))
.to_string_lossy()
.into_owned();
if let Some(file) = files.next() {
return Err(UUsageError::new(
1,
translate!("ptx-error-extra-operand", "operand" => file.quote()),
translate!("ptx-error-extra-operand", "operand" => file.to_string_lossy().quote()),
));
}
}
@@ -778,7 +783,8 @@ pub fn uu_app() -> Command {
Arg::new(options::FILE)
.hide(true)
.action(ArgAction::Append)
.value_hint(clap::ValueHint::FilePath),
.value_hint(clap::ValueHint::FilePath)
.value_parser(clap::value_parser!(OsString)),
)
.arg(
Arg::new(options::AUTO_REFERENCE)
@@ -856,7 +862,8 @@ pub fn uu_app() -> Command {
.long(options::BREAK_FILE)
.help(translate!("ptx-help-break-file"))
.value_name("FILE")
.value_hint(clap::ValueHint::FilePath),
.value_hint(clap::ValueHint::FilePath)
.value_parser(clap::value_parser!(OsString)),
)
.arg(
Arg::new(options::IGNORE_CASE)
@@ -878,7 +885,8 @@ pub fn uu_app() -> Command {
.long(options::IGNORE_FILE)
.help(translate!("ptx-help-ignore-file"))
.value_name("FILE")
.value_hint(clap::ValueHint::FilePath),
.value_hint(clap::ValueHint::FilePath)
.value_parser(clap::value_parser!(OsString)),
)
.arg(
Arg::new(options::ONLY_FILE)
@@ -886,7 +894,8 @@ pub fn uu_app() -> Command {
.long(options::ONLY_FILE)
.help(translate!("ptx-help-only-file"))
.value_name("FILE")
.value_hint(clap::ValueHint::FilePath),
.value_hint(clap::ValueHint::FilePath)
.value_parser(clap::value_parser!(OsString)),
)
.arg(
Arg::new(options::REFERENCES)