mirror of
https://github.com/uutils/coreutils.git
synced 2025-12-23 08:47:37 +00:00
Merge pull request #8457 from sylvestre/no-utf8
Some checks are pending
CICD / MinRustV (push) Waiting to run
CICD / Style/cargo-deny (push) Waiting to run
CICD / Build (push) Blocked by required conditions
CICD / Style/deps (push) Waiting to run
CICD / Documentation/warnings (push) Waiting to run
CICD / Test all features separately (push) Blocked by required conditions
CICD / Dependencies (push) Waiting to run
CICD / Build/Makefile (push) Blocked by required conditions
CICD / Build/stable (push) Blocked by required conditions
CICD / Build/nightly (push) Blocked by required conditions
CICD / Binary sizes (push) Blocked by required conditions
CICD / Tests/BusyBox test suite (push) Blocked by required conditions
CICD / Tests/Toybox test suite (push) Blocked by required conditions
CICD / Code Coverage (push) Waiting to run
CICD / Separate Builds (push) Waiting to run
CICD / Build/SELinux (push) Blocked by required conditions
GnuTests / Run GNU tests (native) (push) Waiting to run
GnuTests / Run GNU tests (SELinux) (push) Waiting to run
GnuTests / Aggregate GNU test results (push) Blocked by required conditions
Android / Test builds (push) Waiting to run
Code Quality / Style/format (push) Waiting to run
Code Quality / Style/lint (push) Waiting to run
Code Quality / Style/spelling (push) Waiting to run
Code Quality / Style/toml (push) Waiting to run
Code Quality / Style/Python (push) Waiting to run
Code Quality / Pre-commit hooks (push) Waiting to run
FreeBSD / Style and Lint (push) Waiting to run
FreeBSD / Tests (push) Waiting to run
L10n (Localization) / L10n/Build and Test (push) Waiting to run
L10n (Localization) / L10n/Fluent Syntax Check (push) Waiting to run
L10n (Localization) / L10n/Clap Error Localization Test (push) Waiting to run
L10n (Localization) / L10n/French Integration Test (push) Waiting to run
L10n (Localization) / L10n/Multi-call Binary Install Test (push) Waiting to run
L10n (Localization) / L10n/Installation Test (Make & Cargo) (push) Waiting to run
L10n (Localization) / L10n/Locale Support Verification (push) Waiting to run
L10n (Localization) / L10n/Locale Embedding Regression Test (push) Waiting to run
WSL2 / Test (push) Waiting to run
Some checks are pending
CICD / MinRustV (push) Waiting to run
CICD / Style/cargo-deny (push) Waiting to run
CICD / Build (push) Blocked by required conditions
CICD / Style/deps (push) Waiting to run
CICD / Documentation/warnings (push) Waiting to run
CICD / Test all features separately (push) Blocked by required conditions
CICD / Dependencies (push) Waiting to run
CICD / Build/Makefile (push) Blocked by required conditions
CICD / Build/stable (push) Blocked by required conditions
CICD / Build/nightly (push) Blocked by required conditions
CICD / Binary sizes (push) Blocked by required conditions
CICD / Tests/BusyBox test suite (push) Blocked by required conditions
CICD / Tests/Toybox test suite (push) Blocked by required conditions
CICD / Code Coverage (push) Waiting to run
CICD / Separate Builds (push) Waiting to run
CICD / Build/SELinux (push) Blocked by required conditions
GnuTests / Run GNU tests (native) (push) Waiting to run
GnuTests / Run GNU tests (SELinux) (push) Waiting to run
GnuTests / Aggregate GNU test results (push) Blocked by required conditions
Android / Test builds (push) Waiting to run
Code Quality / Style/format (push) Waiting to run
Code Quality / Style/lint (push) Waiting to run
Code Quality / Style/spelling (push) Waiting to run
Code Quality / Style/toml (push) Waiting to run
Code Quality / Style/Python (push) Waiting to run
Code Quality / Pre-commit hooks (push) Waiting to run
FreeBSD / Style and Lint (push) Waiting to run
FreeBSD / Tests (push) Waiting to run
L10n (Localization) / L10n/Build and Test (push) Waiting to run
L10n (Localization) / L10n/Fluent Syntax Check (push) Waiting to run
L10n (Localization) / L10n/Clap Error Localization Test (push) Waiting to run
L10n (Localization) / L10n/French Integration Test (push) Waiting to run
L10n (Localization) / L10n/Multi-call Binary Install Test (push) Waiting to run
L10n (Localization) / L10n/Installation Test (Make & Cargo) (push) Waiting to run
L10n (Localization) / L10n/Locale Support Verification (push) Waiting to run
L10n (Localization) / L10n/Locale Embedding Regression Test (push) Waiting to run
WSL2 / Test (push) Waiting to run
Allow non-utf8 paths name as input for most of the programs
This commit is contained in:
commit
5ba99ae564
75 changed files with 1898 additions and 484 deletions
1
.github/workflows/fuzzing.yml
vendored
1
.github/workflows/fuzzing.yml
vendored
|
|
@ -62,6 +62,7 @@ jobs:
|
|||
- { name: fuzz_parse_size, should_pass: true }
|
||||
- { name: fuzz_parse_time, should_pass: true }
|
||||
- { name: fuzz_seq_parse_number, should_pass: true }
|
||||
- { name: fuzz_non_utf8_paths, should_pass: true }
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
|
|
|
|||
|
|
@ -138,3 +138,9 @@ name = "fuzz_cksum"
|
|||
path = "fuzz_targets/fuzz_cksum.rs"
|
||||
test = false
|
||||
doc = false
|
||||
|
||||
[[bin]]
|
||||
name = "fuzz_non_utf8_paths"
|
||||
path = "fuzz_targets/fuzz_non_utf8_paths.rs"
|
||||
test = false
|
||||
doc = false
|
||||
|
|
|
|||
442
fuzz/fuzz_targets/fuzz_non_utf8_paths.rs
Normal file
442
fuzz/fuzz_targets/fuzz_non_utf8_paths.rs
Normal file
|
|
@ -0,0 +1,442 @@
|
|||
// This file is part of the uutils coreutils package.
|
||||
//
|
||||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
|
||||
// spell-checker:ignore osstring
|
||||
|
||||
#![no_main]
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
use rand::Rng;
|
||||
use rand::prelude::IndexedRandom;
|
||||
use std::collections::HashSet;
|
||||
use std::env::temp_dir;
|
||||
use std::ffi::{OsStr, OsString};
|
||||
use std::fs;
|
||||
use std::os::unix::ffi::{OsStrExt, OsStringExt};
|
||||
use std::path::PathBuf;
|
||||
|
||||
use uufuzz::{CommandResult, run_gnu_cmd};
|
||||
// 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",
|
||||
// Path operations
|
||||
"ls",
|
||||
"mkdir",
|
||||
"rmdir",
|
||||
"du",
|
||||
"stat",
|
||||
"mktemp",
|
||||
"df",
|
||||
"basename",
|
||||
"dirname",
|
||||
"readlink",
|
||||
"realpath",
|
||||
"pathchk",
|
||||
"chroot",
|
||||
// File processing
|
||||
"head",
|
||||
"tail",
|
||||
"tee",
|
||||
"more",
|
||||
"od",
|
||||
"wc",
|
||||
"cksum",
|
||||
"sum",
|
||||
"nl",
|
||||
"tac",
|
||||
"sort",
|
||||
"uniq",
|
||||
"split",
|
||||
"csplit",
|
||||
"cut",
|
||||
"tr",
|
||||
"shred",
|
||||
"shuf",
|
||||
"ptx",
|
||||
"tsort",
|
||||
// Text processing with files
|
||||
"chmod",
|
||||
"chown",
|
||||
"chgrp",
|
||||
"install",
|
||||
"chcon",
|
||||
"runcon",
|
||||
"comm",
|
||||
"join",
|
||||
"paste",
|
||||
"pr",
|
||||
"fmt",
|
||||
"fold",
|
||||
"expand",
|
||||
"unexpand",
|
||||
"dir",
|
||||
"vdir",
|
||||
"mkfifo",
|
||||
"mknod",
|
||||
"hashsum",
|
||||
// File I/O utilities
|
||||
"dd",
|
||||
"sync",
|
||||
"stdbuf",
|
||||
"dircolors",
|
||||
// Encoding/decoding utilities
|
||||
"base32",
|
||||
"base64",
|
||||
"basenc",
|
||||
"stty",
|
||||
"tty",
|
||||
"env",
|
||||
"nohup",
|
||||
"nice",
|
||||
"timeout",
|
||||
];
|
||||
|
||||
fn generate_non_utf8_bytes() -> Vec<u8> {
|
||||
let mut rng = rand::rng();
|
||||
let mut bytes = Vec::new();
|
||||
|
||||
// Start with some valid UTF-8 to make it look like a reasonable path
|
||||
bytes.extend_from_slice(b"test_");
|
||||
|
||||
// Add some invalid UTF-8 sequences
|
||||
match rng.random_range(0..4) {
|
||||
0 => bytes.extend_from_slice(&[0xFF, 0xFE]), // Invalid UTF-8
|
||||
1 => bytes.extend_from_slice(&[0xC0, 0x80]), // Overlong encoding
|
||||
2 => bytes.extend_from_slice(&[0xED, 0xA0, 0x80]), // UTF-16 surrogate
|
||||
_ => bytes.extend_from_slice(&[0xF4, 0x90, 0x80, 0x80]), // Beyond Unicode range
|
||||
}
|
||||
|
||||
bytes
|
||||
}
|
||||
|
||||
fn generate_non_utf8_osstring() -> OsString {
|
||||
OsString::from_vec(generate_non_utf8_bytes())
|
||||
}
|
||||
|
||||
fn setup_test_files() -> Result<(PathBuf, Vec<PathBuf>), std::io::Error> {
|
||||
let mut rng = rand::rng();
|
||||
let temp_root = temp_dir().join(format!("utf8_test_{}", rng.random::<u64>()));
|
||||
fs::create_dir_all(&temp_root)?;
|
||||
|
||||
let mut test_files = Vec::new();
|
||||
|
||||
// Create some files with non-UTF-8 names
|
||||
for i in 0..3 {
|
||||
let mut path_bytes = temp_root.as_os_str().as_bytes().to_vec();
|
||||
path_bytes.push(b'/');
|
||||
|
||||
if i == 0 {
|
||||
// One normal UTF-8 file for comparison
|
||||
path_bytes.extend_from_slice(b"normal_file.txt");
|
||||
} else {
|
||||
// Files with invalid UTF-8 names
|
||||
path_bytes.extend_from_slice(&generate_non_utf8_bytes());
|
||||
}
|
||||
|
||||
let file_path = PathBuf::from(OsStr::from_bytes(&path_bytes));
|
||||
|
||||
// Try to create the file - this may fail on some filesystems
|
||||
if let Ok(mut file) = fs::File::create(&file_path) {
|
||||
use std::io::Write;
|
||||
let _ = write!(file, "test content for file {}\n", i);
|
||||
test_files.push(file_path);
|
||||
}
|
||||
}
|
||||
|
||||
Ok((temp_root, test_files))
|
||||
}
|
||||
|
||||
fn test_program_with_non_utf8_path(program: &str, path: &PathBuf) -> CommandResult {
|
||||
let path_os = path.as_os_str();
|
||||
|
||||
// Use the locally built uutils binary instead of system PATH
|
||||
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 {
|
||||
// Programs that need mode/permissions
|
||||
"chmod" => vec![
|
||||
OsString::from(program),
|
||||
OsString::from("644"),
|
||||
path_os.to_owned(),
|
||||
],
|
||||
"chown" => vec![
|
||||
OsString::from(program),
|
||||
OsString::from("root:root"),
|
||||
path_os.to_owned(),
|
||||
],
|
||||
"chgrp" => vec![
|
||||
OsString::from(program),
|
||||
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");
|
||||
vec![
|
||||
OsString::from(program),
|
||||
path_os.to_owned(),
|
||||
dest_path.as_os_str().to_owned(),
|
||||
]
|
||||
}
|
||||
"install" => {
|
||||
let dest_path = path.with_extension("dest");
|
||||
vec![
|
||||
OsString::from(program),
|
||||
path_os.to_owned(),
|
||||
dest_path.as_os_str().to_owned(),
|
||||
]
|
||||
}
|
||||
// Programs that need size/truncate operations
|
||||
"truncate" => vec![
|
||||
OsString::from(program),
|
||||
OsString::from("--size=0"),
|
||||
path_os.to_owned(),
|
||||
],
|
||||
"split" => vec![
|
||||
OsString::from(program),
|
||||
path_os.to_owned(),
|
||||
OsString::from("split_prefix_"),
|
||||
],
|
||||
"csplit" => vec![
|
||||
OsString::from(program),
|
||||
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"),
|
||||
]
|
||||
} else {
|
||||
vec![OsString::from(program), new_path.as_os_str().to_owned()]
|
||||
}
|
||||
}
|
||||
"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(),
|
||||
],
|
||||
// Encoding/decoding programs
|
||||
"base32" | "base64" | "basenc" => vec![OsString::from(program), path_os.to_owned()],
|
||||
"df" => vec![OsString::from(program), path_os.to_owned()],
|
||||
"chroot" => {
|
||||
// chroot needs a directory and command
|
||||
vec![
|
||||
OsString::from(program),
|
||||
path_os.to_owned(),
|
||||
OsString::from("true"),
|
||||
]
|
||||
}
|
||||
"sync" => vec![OsString::from(program), path_os.to_owned()],
|
||||
"stty" => vec![
|
||||
OsString::from(program),
|
||||
OsString::from("-F"),
|
||||
path_os.to_owned(),
|
||||
],
|
||||
"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
|
||||
vec![
|
||||
OsString::from(program),
|
||||
path_os.to_owned(),
|
||||
path_os.to_owned(),
|
||||
]
|
||||
}
|
||||
// Programs that typically take file input
|
||||
_ => vec![OsString::from(program), path_os.to_owned()],
|
||||
};
|
||||
|
||||
// Try to run the local uutils version
|
||||
match run_gnu_cmd(&local_binary, &local_args, false, None) {
|
||||
Ok(result) => result,
|
||||
Err(error_result) => {
|
||||
// Local command failed, return the error
|
||||
error_result
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn cleanup_test_files(temp_root: &PathBuf) {
|
||||
let _ = fs::remove_dir_all(temp_root);
|
||||
}
|
||||
|
||||
fn check_for_utf8_error_and_panic(result: &CommandResult, program: &str, path: &PathBuf) {
|
||||
let stderr_lower = result.stderr.to_lowercase();
|
||||
let is_utf8_error = stderr_lower.contains("invalid utf-8")
|
||||
|| stderr_lower.contains("not valid unicode")
|
||||
|| stderr_lower.contains("invalid utf8")
|
||||
|| stderr_lower.contains("utf-8 error");
|
||||
|
||||
if is_utf8_error {
|
||||
println!(
|
||||
"UTF-8 conversion error detected in {}: {}",
|
||||
program, result.stderr
|
||||
);
|
||||
println!("Path: {:?}", path);
|
||||
println!("Exit code: {}", result.exit_code);
|
||||
panic!(
|
||||
"FUZZER FAILURE: {} failed with UTF-8 error on non-UTF-8 path: {:?}",
|
||||
program, path
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fuzz_target!(|_data: &[u8]| {
|
||||
let mut rng = rand::rng();
|
||||
|
||||
// Set up test environment
|
||||
let (temp_root, test_files) = match setup_test_files() {
|
||||
Ok(files) => files,
|
||||
Err(_) => return, // Skip if we can't set up test files
|
||||
};
|
||||
|
||||
// Pick multiple random programs to test in each iteration
|
||||
let num_programs_to_test = rng.random_range(1..=3); // Test 1-3 programs per iteration
|
||||
let mut tested_programs = HashSet::new();
|
||||
|
||||
let mut programs_tested = Vec::<String>::new();
|
||||
|
||||
for _ in 0..num_programs_to_test {
|
||||
// Pick a random program that we haven't tested yet in this iteration
|
||||
let available_programs: Vec<_> = PATH_PROGRAMS
|
||||
.iter()
|
||||
.filter(|p| !tested_programs.contains(*p))
|
||||
.collect();
|
||||
|
||||
if available_programs.is_empty() {
|
||||
break;
|
||||
}
|
||||
|
||||
let program = available_programs.choose(&mut rng).unwrap();
|
||||
tested_programs.insert(*program);
|
||||
programs_tested.push(program.to_string());
|
||||
|
||||
// Test with one random file that has non-UTF-8 names (not all files to speed up)
|
||||
if let Some(test_file) = test_files.choose(&mut rng) {
|
||||
let result = test_program_with_non_utf8_path(program, test_file);
|
||||
|
||||
// Check if the program handled the non-UTF-8 path gracefully
|
||||
check_for_utf8_error_and_panic(&result, program, test_file);
|
||||
}
|
||||
|
||||
// Special cases for programs that need additional testing
|
||||
if **program == "mkdir" || **program == "mktemp" {
|
||||
let non_utf8_dir_name = generate_non_utf8_osstring();
|
||||
let non_utf8_dir = temp_root.join(non_utf8_dir_name);
|
||||
|
||||
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);
|
||||
match mkdir_result {
|
||||
Ok(result) => {
|
||||
check_for_utf8_error_and_panic(&result, "mkdir", &non_utf8_dir);
|
||||
}
|
||||
Err(error) => {
|
||||
check_for_utf8_error_and_panic(&error, "mkdir", &non_utf8_dir);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
println!("Tested programs: {}", programs_tested.join(", "));
|
||||
|
||||
// Clean up
|
||||
cleanup_test_files(&temp_root);
|
||||
});
|
||||
|
|
@ -6,6 +6,7 @@
|
|||
// spell-checker:ignore hexupper lsbf msbf unpadded nopad aGVsbG8sIHdvcmxkIQ
|
||||
|
||||
use clap::{Arg, ArgAction, Command};
|
||||
use std::ffi::OsString;
|
||||
use std::fs::File;
|
||||
use std::io::{self, ErrorKind, Read, Seek, SeekFrom};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
|
@ -44,14 +45,14 @@ pub mod options {
|
|||
|
||||
impl Config {
|
||||
pub fn from(options: &clap::ArgMatches) -> UResult<Self> {
|
||||
let to_read = match options.get_many::<String>(options::FILE) {
|
||||
let to_read = match options.get_many::<OsString>(options::FILE) {
|
||||
Some(mut values) => {
|
||||
let name = values.next().unwrap();
|
||||
|
||||
if let Some(extra_op) = values.next() {
|
||||
return Err(UUsageError::new(
|
||||
BASE_CMD_PARSE_ERROR,
|
||||
translate!("base-common-extra-operand", "operand" => extra_op.quote()),
|
||||
translate!("base-common-extra-operand", "operand" => extra_op.to_string_lossy().quote()),
|
||||
));
|
||||
}
|
||||
|
||||
|
|
@ -143,6 +144,7 @@ pub fn base_app(about: &'static str, usage: &str) -> Command {
|
|||
Arg::new(options::FILE)
|
||||
.index(1)
|
||||
.action(ArgAction::Append)
|
||||
.value_parser(clap::value_parser!(OsString))
|
||||
.value_hint(clap::ValueHint::FilePath),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ mod platform;
|
|||
use crate::platform::is_unsafe_overwrite;
|
||||
use clap::{Arg, ArgAction, Command};
|
||||
use memchr::memchr2;
|
||||
use std::ffi::OsString;
|
||||
use std::fs::{File, metadata};
|
||||
use std::io::{self, BufWriter, ErrorKind, IsTerminal, Read, Write};
|
||||
/// Unix domain socket support
|
||||
|
|
@ -267,9 +268,9 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
|||
.any(|v| matches.get_flag(v));
|
||||
|
||||
let squeeze_blank = matches.get_flag(options::SQUEEZE_BLANK);
|
||||
let files: Vec<String> = match matches.get_many::<String>(options::FILE) {
|
||||
let files: Vec<OsString> = match matches.get_many::<OsString>(options::FILE) {
|
||||
Some(v) => v.cloned().collect(),
|
||||
None => vec!["-".to_owned()],
|
||||
None => vec![OsString::from("-")],
|
||||
};
|
||||
|
||||
let options = OutputOptions {
|
||||
|
|
@ -294,6 +295,7 @@ pub fn uu_app() -> Command {
|
|||
Arg::new(options::FILE)
|
||||
.hide(true)
|
||||
.action(ArgAction::Append)
|
||||
.value_parser(clap::value_parser!(OsString))
|
||||
.value_hint(clap::ValueHint::FilePath),
|
||||
)
|
||||
.arg(
|
||||
|
|
@ -379,7 +381,7 @@ fn cat_handle<R: FdReadable>(
|
|||
}
|
||||
}
|
||||
|
||||
fn cat_path(path: &str, options: &OutputOptions, state: &mut OutputState) -> CatResult<()> {
|
||||
fn cat_path(path: &OsString, options: &OutputOptions, state: &mut OutputState) -> CatResult<()> {
|
||||
match get_input_type(path)? {
|
||||
InputType::StdIn => {
|
||||
let stdin = io::stdin();
|
||||
|
|
@ -417,7 +419,7 @@ fn cat_path(path: &str, options: &OutputOptions, state: &mut OutputState) -> Cat
|
|||
}
|
||||
}
|
||||
|
||||
fn cat_files(files: &[String], options: &OutputOptions) -> UResult<()> {
|
||||
fn cat_files(files: &[OsString], options: &OutputOptions) -> UResult<()> {
|
||||
let mut state = OutputState {
|
||||
line_number: LineNumber::new(),
|
||||
at_line_start: true,
|
||||
|
|
@ -452,7 +454,7 @@ fn cat_files(files: &[String], options: &OutputOptions) -> UResult<()> {
|
|||
/// # Arguments
|
||||
///
|
||||
/// * `path` - Path on a file system to classify metadata
|
||||
fn get_input_type(path: &str) -> CatResult<InputType> {
|
||||
fn get_input_type(path: &OsString) -> CatResult<InputType> {
|
||||
if path == "-" {
|
||||
return Ok(InputType::StdIn);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
// spell-checker:ignore (ToDO) COMFOLLOW Chowner RFILE RFILE's derefer dgid nonblank nonprint nonprinting
|
||||
|
||||
use uucore::display::Quotable;
|
||||
pub use uucore::entries;
|
||||
use uucore::entries;
|
||||
use uucore::error::{FromIo, UResult, USimpleError};
|
||||
use uucore::format_usage;
|
||||
use uucore::perms::{GidUidOwnerFilter, IfFrom, chown_base, options};
|
||||
|
|
@ -37,15 +37,16 @@ fn parse_gid_from_str(group: &str) -> Result<u32, String> {
|
|||
|
||||
fn get_dest_gid(matches: &ArgMatches) -> UResult<(Option<u32>, String)> {
|
||||
let mut raw_group = String::new();
|
||||
let dest_gid = if let Some(file) = matches.get_one::<String>(options::REFERENCE) {
|
||||
fs::metadata(file)
|
||||
let dest_gid = if let Some(file) = matches.get_one::<std::ffi::OsString>(options::REFERENCE) {
|
||||
let path = std::path::Path::new(file);
|
||||
fs::metadata(path)
|
||||
.map(|meta| {
|
||||
let gid = meta.gid();
|
||||
raw_group = entries::gid2grp(gid).unwrap_or_else(|_| gid.to_string());
|
||||
Some(gid)
|
||||
})
|
||||
.map_err_context(
|
||||
|| translate!("chgrp-error-failed-to-get-attributes", "file" => file.quote()),
|
||||
|| translate!("chgrp-error-failed-to-get-attributes", "file" => path.quote()),
|
||||
)?
|
||||
} else {
|
||||
let group = matches
|
||||
|
|
@ -153,6 +154,7 @@ pub fn uu_app() -> Command {
|
|||
.long(options::REFERENCE)
|
||||
.value_name("RFILE")
|
||||
.value_hint(clap::ValueHint::FilePath)
|
||||
.value_parser(clap::value_parser!(std::ffi::OsString))
|
||||
.help(translate!("chgrp-help-reference")),
|
||||
)
|
||||
.arg(
|
||||
|
|
|
|||
|
|
@ -119,11 +119,11 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
|||
let quiet = matches.get_flag(options::QUIET);
|
||||
let verbose = matches.get_flag(options::VERBOSE);
|
||||
let preserve_root = matches.get_flag(options::PRESERVE_ROOT);
|
||||
let fmode = match matches.get_one::<String>(options::REFERENCE) {
|
||||
let fmode = match matches.get_one::<OsString>(options::REFERENCE) {
|
||||
Some(fref) => match fs::metadata(fref) {
|
||||
Ok(meta) => Some(meta.mode() & 0o7777),
|
||||
Err(_) => {
|
||||
return Err(ChmodError::CannotStat(fref.to_string()).into());
|
||||
return Err(ChmodError::CannotStat(fref.to_string_lossy().to_string()).into());
|
||||
}
|
||||
},
|
||||
None => None,
|
||||
|
|
@ -135,16 +135,15 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
|||
} else {
|
||||
modes.unwrap().to_string() // modes is required
|
||||
};
|
||||
// FIXME: enable non-utf8 paths
|
||||
let mut files: Vec<String> = matches
|
||||
.get_many::<String>(options::FILE)
|
||||
.map(|v| v.map(ToString::to_string).collect())
|
||||
let mut files: Vec<OsString> = matches
|
||||
.get_many::<OsString>(options::FILE)
|
||||
.map(|v| v.cloned().collect())
|
||||
.unwrap_or_default();
|
||||
let cmode = if fmode.is_some() {
|
||||
// "--reference" and MODE are mutually exclusive
|
||||
// if "--reference" was used MODE needs to be interpreted as another FILE
|
||||
// it wasn't possible to implement this behavior directly with clap
|
||||
files.push(cmode);
|
||||
files.push(OsString::from(cmode));
|
||||
None
|
||||
} else {
|
||||
Some(cmode)
|
||||
|
|
@ -236,6 +235,7 @@ pub fn uu_app() -> Command {
|
|||
Arg::new(options::REFERENCE)
|
||||
.long("reference")
|
||||
.value_hint(clap::ValueHint::FilePath)
|
||||
.value_parser(clap::value_parser!(OsString))
|
||||
.help(translate!("chmod-help-reference")),
|
||||
)
|
||||
.arg(
|
||||
|
|
@ -248,7 +248,8 @@ pub fn uu_app() -> Command {
|
|||
Arg::new(options::FILE)
|
||||
.required_unless_present(options::MODE)
|
||||
.action(ArgAction::Append)
|
||||
.value_hint(clap::ValueHint::AnyPath),
|
||||
.value_hint(clap::ValueHint::AnyPath)
|
||||
.value_parser(clap::value_parser!(OsString)),
|
||||
)
|
||||
// Add common arguments with chgrp, chown & chmod
|
||||
.args(uucore::perms::common_args())
|
||||
|
|
@ -267,11 +268,10 @@ struct Chmoder {
|
|||
}
|
||||
|
||||
impl Chmoder {
|
||||
fn chmod(&self, files: &[String]) -> UResult<()> {
|
||||
fn chmod(&self, files: &[OsString]) -> UResult<()> {
|
||||
let mut r = Ok(());
|
||||
|
||||
for filename in files {
|
||||
let filename = &filename[..];
|
||||
let file = Path::new(filename);
|
||||
if !file.exists() {
|
||||
if file.is_symlink() {
|
||||
|
|
@ -285,18 +285,22 @@ impl Chmoder {
|
|||
}
|
||||
|
||||
if !self.quiet {
|
||||
show!(ChmodError::DanglingSymlink(filename.to_string()));
|
||||
show!(ChmodError::DanglingSymlink(
|
||||
filename.to_string_lossy().to_string()
|
||||
));
|
||||
set_exit_code(1);
|
||||
}
|
||||
|
||||
if self.verbose {
|
||||
println!(
|
||||
"{}",
|
||||
translate!("chmod-verbose-failed-dangling", "file" => filename.quote())
|
||||
translate!("chmod-verbose-failed-dangling", "file" => filename.to_string_lossy().quote())
|
||||
);
|
||||
}
|
||||
} else if !self.quiet {
|
||||
show!(ChmodError::NoSuchFile(filename.to_string()));
|
||||
show!(ChmodError::NoSuchFile(
|
||||
filename.to_string_lossy().to_string()
|
||||
));
|
||||
}
|
||||
// GNU exits with exit code 1 even if -q or --quiet are passed
|
||||
// So we set the exit code, because it hasn't been set yet if `self.quiet` is true.
|
||||
|
|
@ -308,8 +312,8 @@ impl Chmoder {
|
|||
// should not change the permissions in this case
|
||||
continue;
|
||||
}
|
||||
if self.recursive && self.preserve_root && filename == "/" {
|
||||
return Err(ChmodError::PreserveRoot(filename.to_string()).into());
|
||||
if self.recursive && self.preserve_root && file == Path::new("/") {
|
||||
return Err(ChmodError::PreserveRoot("/".to_string()).into());
|
||||
}
|
||||
if self.recursive {
|
||||
r = self.walk_dir_with_context(file, true);
|
||||
|
|
|
|||
|
|
@ -6,8 +6,10 @@
|
|||
// spell-checker:ignore (ToDO) delim mkdelim pairable
|
||||
|
||||
use std::cmp::Ordering;
|
||||
use std::ffi::OsString;
|
||||
use std::fs::{File, metadata};
|
||||
use std::io::{self, BufRead, BufReader, Read, Stdin, stdin};
|
||||
use std::path::Path;
|
||||
use uucore::LocalizedCommand;
|
||||
use uucore::error::{FromIo, UResult, USimpleError};
|
||||
use uucore::format_usage;
|
||||
|
|
@ -115,7 +117,7 @@ impl OrderChecker {
|
|||
}
|
||||
|
||||
// Check if two files are identical by comparing their contents
|
||||
pub fn are_files_identical(path1: &str, path2: &str) -> io::Result<bool> {
|
||||
pub fn are_files_identical(path1: &Path, path2: &Path) -> io::Result<bool> {
|
||||
// First compare file sizes
|
||||
let metadata1 = metadata(path1)?;
|
||||
let metadata2 = metadata(path2)?;
|
||||
|
|
@ -174,11 +176,11 @@ fn comm(a: &mut LineReader, b: &mut LineReader, delim: &str, opts: &ArgMatches)
|
|||
let should_check_order = !no_check_order
|
||||
&& (check_order
|
||||
|| if let (Some(file1), Some(file2)) = (
|
||||
opts.get_one::<String>(options::FILE_1),
|
||||
opts.get_one::<String>(options::FILE_2),
|
||||
opts.get_one::<OsString>(options::FILE_1),
|
||||
opts.get_one::<OsString>(options::FILE_2),
|
||||
) {
|
||||
!(paths_refer_to_same_file(file1, file2, true)
|
||||
|| are_files_identical(file1, file2).unwrap_or(false))
|
||||
!(paths_refer_to_same_file(file1.as_os_str(), file2.as_os_str(), true)
|
||||
|| are_files_identical(Path::new(file1), Path::new(file2)).unwrap_or(false))
|
||||
} else {
|
||||
true
|
||||
});
|
||||
|
|
@ -264,7 +266,7 @@ fn comm(a: &mut LineReader, b: &mut LineReader, delim: &str, opts: &ArgMatches)
|
|||
}
|
||||
}
|
||||
|
||||
fn open_file(name: &str, line_ending: LineEnding) -> io::Result<LineReader> {
|
||||
fn open_file(name: &OsString, line_ending: LineEnding) -> io::Result<LineReader> {
|
||||
if name == "-" {
|
||||
Ok(LineReader::new(Input::Stdin(stdin()), line_ending))
|
||||
} else {
|
||||
|
|
@ -283,10 +285,12 @@ fn open_file(name: &str, line_ending: LineEnding) -> io::Result<LineReader> {
|
|||
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||
let matches = uu_app().get_matches_from_localized(args);
|
||||
let line_ending = LineEnding::from_zero_flag(matches.get_flag(options::ZERO_TERMINATED));
|
||||
let filename1 = matches.get_one::<String>(options::FILE_1).unwrap();
|
||||
let filename2 = matches.get_one::<String>(options::FILE_2).unwrap();
|
||||
let mut f1 = open_file(filename1, line_ending).map_err_context(|| filename1.to_string())?;
|
||||
let mut f2 = open_file(filename2, line_ending).map_err_context(|| filename2.to_string())?;
|
||||
let filename1 = matches.get_one::<OsString>(options::FILE_1).unwrap();
|
||||
let filename2 = matches.get_one::<OsString>(options::FILE_2).unwrap();
|
||||
let mut f1 = open_file(filename1, line_ending)
|
||||
.map_err_context(|| filename1.to_string_lossy().to_string())?;
|
||||
let mut f2 = open_file(filename2, line_ending)
|
||||
.map_err_context(|| filename2.to_string_lossy().to_string())?;
|
||||
|
||||
// Due to default_value(), there must be at least one value here, thus unwrap() must not panic.
|
||||
let all_delimiters = matches
|
||||
|
|
@ -360,12 +364,14 @@ pub fn uu_app() -> Command {
|
|||
.arg(
|
||||
Arg::new(options::FILE_1)
|
||||
.required(true)
|
||||
.value_hint(clap::ValueHint::FilePath),
|
||||
.value_hint(clap::ValueHint::FilePath)
|
||||
.value_parser(clap::value_parser!(OsString)),
|
||||
)
|
||||
.arg(
|
||||
Arg::new(options::FILE_2)
|
||||
.required(true)
|
||||
.value_hint(clap::ValueHint::FilePath),
|
||||
.value_hint(clap::ValueHint::FilePath)
|
||||
.value_parser(clap::value_parser!(OsString)),
|
||||
)
|
||||
.arg(
|
||||
Arg::new(options::TOTAL)
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
#![allow(rustdoc::private_intra_doc_links)]
|
||||
|
||||
use std::cmp::Ordering;
|
||||
use std::ffi::OsString;
|
||||
use std::io::{self, BufReader, ErrorKind};
|
||||
use std::{
|
||||
fs::{File, remove_file},
|
||||
|
|
@ -608,7 +609,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
|||
let matches = uu_app().get_matches_from_localized(args);
|
||||
|
||||
// get the file to split
|
||||
let file_name = matches.get_one::<String>(options::FILE).unwrap();
|
||||
let file_name = matches.get_one::<OsString>(options::FILE).unwrap();
|
||||
|
||||
// get the patterns to split on
|
||||
let patterns: Vec<String> = matches
|
||||
|
|
@ -689,7 +690,8 @@ pub fn uu_app() -> Command {
|
|||
Arg::new(options::FILE)
|
||||
.hide(true)
|
||||
.required(true)
|
||||
.value_hint(clap::ValueHint::FilePath),
|
||||
.value_hint(clap::ValueHint::FilePath)
|
||||
.value_parser(clap::value_parser!(OsString)),
|
||||
)
|
||||
.arg(
|
||||
Arg::new(options::PATTERN)
|
||||
|
|
|
|||
|
|
@ -343,11 +343,11 @@ fn cut_fields<R: Read, W: Write>(
|
|||
}
|
||||
}
|
||||
|
||||
fn cut_files(mut filenames: Vec<String>, mode: &Mode) {
|
||||
fn cut_files(mut filenames: Vec<OsString>, mode: &Mode) {
|
||||
let mut stdin_read = false;
|
||||
|
||||
if filenames.is_empty() {
|
||||
filenames.push("-".to_owned());
|
||||
filenames.push(OsString::from("-"));
|
||||
}
|
||||
|
||||
let mut out: Box<dyn Write> = if stdout().is_terminal() {
|
||||
|
|
@ -370,12 +370,12 @@ fn cut_files(mut filenames: Vec<String>, mode: &Mode) {
|
|||
|
||||
stdin_read = true;
|
||||
} else {
|
||||
let path = Path::new(&filename[..]);
|
||||
let path = Path::new(filename);
|
||||
|
||||
if path.is_dir() {
|
||||
show_error!(
|
||||
"{}: {}",
|
||||
filename.maybe_quote(),
|
||||
filename.to_string_lossy().maybe_quote(),
|
||||
translate!("cut-error-is-directory")
|
||||
);
|
||||
set_exit_code(1);
|
||||
|
|
@ -384,7 +384,7 @@ fn cut_files(mut filenames: Vec<String>, mode: &Mode) {
|
|||
|
||||
show_if_err!(
|
||||
File::open(path)
|
||||
.map_err_context(|| filename.maybe_quote().to_string())
|
||||
.map_err_context(|| filename.to_string_lossy().to_string())
|
||||
.and_then(|file| {
|
||||
match &mode {
|
||||
Mode::Bytes(ranges, opts) | Mode::Characters(ranges, opts) => {
|
||||
|
|
@ -577,8 +577,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
|||
},
|
||||
};
|
||||
|
||||
let files: Vec<String> = matches
|
||||
.get_many::<String>(options::FILE)
|
||||
let files: Vec<OsString> = matches
|
||||
.get_many::<OsString>(options::FILE)
|
||||
.unwrap_or_default()
|
||||
.cloned()
|
||||
.collect();
|
||||
|
|
@ -681,6 +681,7 @@ 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)),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
use std::borrow::Borrow;
|
||||
use std::env;
|
||||
use std::ffi::OsString;
|
||||
use std::fmt::Write as _;
|
||||
use std::fs::File;
|
||||
use std::io::{BufRead, BufReader};
|
||||
|
|
@ -124,7 +125,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
|||
let matches = uu_app().get_matches_from_localized(args);
|
||||
|
||||
let files = matches
|
||||
.get_many::<String>(options::FILE)
|
||||
.get_many::<OsString>(options::FILE)
|
||||
.map_or(vec![], |file_values| file_values.collect());
|
||||
|
||||
// clap provides .conflicts_with / .conflicts_with_all, but we want to
|
||||
|
|
@ -149,7 +150,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
|||
if !files.is_empty() {
|
||||
return Err(UUsageError::new(
|
||||
1,
|
||||
translate!("dircolors-error-extra-operand-print-database", "operand" => files[0].quote()),
|
||||
translate!("dircolors-error-extra-operand-print-database", "operand" => files[0].to_string_lossy().quote()),
|
||||
));
|
||||
}
|
||||
|
||||
|
|
@ -198,14 +199,18 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
|||
} else if files.len() > 1 {
|
||||
return Err(UUsageError::new(
|
||||
1,
|
||||
translate!("dircolors-error-extra-operand", "operand" => files[1].quote()),
|
||||
translate!("dircolors-error-extra-operand", "operand" => files[1].to_string_lossy().quote()),
|
||||
));
|
||||
} else if files[0].eq("-") {
|
||||
} else if files[0] == "-" {
|
||||
let fin = BufReader::new(std::io::stdin());
|
||||
// For example, for echo "owt 40;33"|dircolors -b -
|
||||
result = parse(fin.lines().map_while(Result::ok), &out_format, files[0]);
|
||||
result = parse(
|
||||
fin.lines().map_while(Result::ok),
|
||||
&out_format,
|
||||
&files[0].to_string_lossy(),
|
||||
);
|
||||
} else {
|
||||
let path = Path::new(files[0]);
|
||||
let path = Path::new(&files[0]);
|
||||
if path.is_dir() {
|
||||
return Err(USimpleError::new(
|
||||
2,
|
||||
|
|
@ -280,6 +285,7 @@ pub fn uu_app() -> Command {
|
|||
Arg::new(options::FILE)
|
||||
.hide(true)
|
||||
.value_hint(clap::ValueHint::FilePath)
|
||||
.value_parser(clap::value_parser!(OsString))
|
||||
.action(ArgAction::Append),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
// file that was distributed with this source code.
|
||||
|
||||
use clap::{Arg, ArgAction, Command};
|
||||
use std::ffi::OsString;
|
||||
use std::path::Path;
|
||||
use uucore::LocalizedCommand;
|
||||
use uucore::display::print_verbatim;
|
||||
|
|
@ -26,8 +27,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
|||
|
||||
let line_ending = LineEnding::from_zero_flag(matches.get_flag(options::ZERO));
|
||||
|
||||
let dirnames: Vec<String> = matches
|
||||
.get_many::<String>(options::DIR)
|
||||
let dirnames: Vec<OsString> = matches
|
||||
.get_many::<OsString>(options::DIR)
|
||||
.unwrap_or_default()
|
||||
.cloned()
|
||||
.collect();
|
||||
|
|
@ -47,7 +48,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
|||
}
|
||||
}
|
||||
None => {
|
||||
if p.is_absolute() || path == "/" {
|
||||
if p.is_absolute() || path.as_os_str() == "/" {
|
||||
print!("/");
|
||||
} else {
|
||||
print!(".");
|
||||
|
|
@ -79,6 +80,7 @@ pub fn uu_app() -> Command {
|
|||
Arg::new(options::DIR)
|
||||
.hide(true)
|
||||
.action(ArgAction::Append)
|
||||
.value_hint(clap::ValueHint::AnyPath),
|
||||
.value_hint(clap::ValueHint::AnyPath)
|
||||
.value_parser(clap::value_parser!(OsString)),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@ use clap::{Arg, ArgAction, ArgMatches, Command, builder::PossibleValue};
|
|||
use glob::Pattern;
|
||||
use std::collections::HashSet;
|
||||
use std::env;
|
||||
use std::ffi::OsStr;
|
||||
use std::ffi::OsString;
|
||||
use std::fs::Metadata;
|
||||
use std::fs::{self, DirEntry, File};
|
||||
use std::io::{BufRead, BufReader, stdout};
|
||||
|
|
@ -530,7 +532,7 @@ impl StatPrinter {
|
|||
}
|
||||
|
||||
/// Read file paths from the specified file, separated by null characters
|
||||
fn read_files_from(file_name: &str) -> Result<Vec<PathBuf>, std::io::Error> {
|
||||
fn read_files_from(file_name: &OsStr) -> Result<Vec<PathBuf>, std::io::Error> {
|
||||
let reader: Box<dyn BufRead> = if file_name == "-" {
|
||||
// Read from standard input
|
||||
Box::new(BufReader::new(std::io::stdin()))
|
||||
|
|
@ -539,7 +541,7 @@ fn read_files_from(file_name: &str) -> Result<Vec<PathBuf>, std::io::Error> {
|
|||
let path = PathBuf::from(file_name);
|
||||
if path.is_dir() {
|
||||
return Err(std::io::Error::other(
|
||||
translate!("du-error-read-error-is-directory", "file" => file_name),
|
||||
translate!("du-error-read-error-is-directory", "file" => file_name.to_string_lossy()),
|
||||
));
|
||||
}
|
||||
|
||||
|
|
@ -548,7 +550,7 @@ fn read_files_from(file_name: &str) -> Result<Vec<PathBuf>, std::io::Error> {
|
|||
Ok(file) => Box::new(BufReader::new(file)),
|
||||
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
|
||||
return Err(std::io::Error::other(
|
||||
translate!("du-error-cannot-open-for-reading", "file" => file_name),
|
||||
translate!("du-error-cannot-open-for-reading", "file" => file_name.to_string_lossy()),
|
||||
));
|
||||
}
|
||||
Err(e) => return Err(e),
|
||||
|
|
@ -564,11 +566,11 @@ fn read_files_from(file_name: &str) -> Result<Vec<PathBuf>, std::io::Error> {
|
|||
let line_number = i + 1;
|
||||
show_error!(
|
||||
"{}",
|
||||
translate!("du-error-invalid-zero-length-file-name", "file" => file_name, "line" => line_number)
|
||||
translate!("du-error-invalid-zero-length-file-name", "file" => file_name.to_string_lossy(), "line" => line_number)
|
||||
);
|
||||
set_exit_code(1);
|
||||
} else {
|
||||
let p = PathBuf::from(String::from_utf8_lossy(&path).to_string());
|
||||
let p = PathBuf::from(&*uucore::os_str_from_bytes(&path).unwrap());
|
||||
if !paths.contains(&p) {
|
||||
paths.push(p);
|
||||
}
|
||||
|
|
@ -594,13 +596,14 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
|||
summarize,
|
||||
)?;
|
||||
|
||||
let files = if let Some(file_from) = matches.get_one::<String>(options::FILES0_FROM) {
|
||||
if file_from == "-" && matches.get_one::<String>(options::FILE).is_some() {
|
||||
let files = if let Some(file_from) = matches.get_one::<OsString>(options::FILES0_FROM) {
|
||||
if file_from == "-" && matches.get_one::<OsString>(options::FILE).is_some() {
|
||||
return Err(std::io::Error::other(
|
||||
translate!("du-error-extra-operand-with-files0-from",
|
||||
"file" => matches
|
||||
.get_one::<String>(options::FILE)
|
||||
.get_one::<OsString>(options::FILE)
|
||||
.unwrap()
|
||||
.to_string_lossy()
|
||||
.quote()
|
||||
),
|
||||
)
|
||||
|
|
@ -608,7 +611,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
|||
}
|
||||
|
||||
read_files_from(file_from)?
|
||||
} else if let Some(files) = matches.get_many::<String>(options::FILE) {
|
||||
} else if let Some(files) = matches.get_many::<OsString>(options::FILE) {
|
||||
let files = files.map(PathBuf::from);
|
||||
if count_links {
|
||||
files.collect()
|
||||
|
|
@ -984,6 +987,7 @@ pub fn uu_app() -> Command {
|
|||
.long("files0-from")
|
||||
.value_name("FILE")
|
||||
.value_hint(clap::ValueHint::FilePath)
|
||||
.value_parser(clap::value_parser!(OsString))
|
||||
.help(translate!("du-help-files0-from"))
|
||||
.action(ArgAction::Append),
|
||||
)
|
||||
|
|
@ -1010,6 +1014,7 @@ pub fn uu_app() -> Command {
|
|||
Arg::new(options::FILE)
|
||||
.hide(true)
|
||||
.value_hint(clap::ValueHint::AnyPath)
|
||||
.value_parser(clap::value_parser!(OsString))
|
||||
.action(ArgAction::Append),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -170,7 +170,7 @@ fn tabstops_parse(s: &str) -> Result<(RemainingMode, Vec<usize>), ParseError> {
|
|||
}
|
||||
|
||||
struct Options {
|
||||
files: Vec<String>,
|
||||
files: Vec<OsString>,
|
||||
tabstops: Vec<usize>,
|
||||
tspaces: String,
|
||||
iflag: bool,
|
||||
|
|
@ -204,9 +204,9 @@ impl Options {
|
|||
.unwrap(); // length of tabstops is guaranteed >= 1
|
||||
let tspaces = " ".repeat(nspaces);
|
||||
|
||||
let files: Vec<String> = match matches.get_many::<String>(options::FILES) {
|
||||
Some(s) => s.map(|v| v.to_string()).collect(),
|
||||
None => vec!["-".to_owned()],
|
||||
let files: Vec<OsString> = match matches.get_many::<OsString>(options::FILES) {
|
||||
Some(s) => s.cloned().collect(),
|
||||
None => vec![OsString::from("-")],
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
|
|
@ -283,16 +283,18 @@ pub fn uu_app() -> Command {
|
|||
Arg::new(options::FILES)
|
||||
.action(ArgAction::Append)
|
||||
.hide(true)
|
||||
.value_hint(clap::ValueHint::FilePath),
|
||||
.value_hint(clap::ValueHint::FilePath)
|
||||
.value_parser(clap::value_parser!(OsString)),
|
||||
)
|
||||
}
|
||||
|
||||
fn open(path: &str) -> UResult<BufReader<Box<dyn Read + 'static>>> {
|
||||
fn open(path: &OsString) -> UResult<BufReader<Box<dyn Read + 'static>>> {
|
||||
let file_buf;
|
||||
if path == "-" {
|
||||
Ok(BufReader::new(Box::new(stdin()) as Box<dyn Read>))
|
||||
} else {
|
||||
file_buf = File::open(path).map_err_context(|| path.to_string())?;
|
||||
let path_ref = Path::new(path);
|
||||
file_buf = File::open(path_ref).map_err_context(|| path.to_string_lossy().to_string())?;
|
||||
Ok(BufReader::new(Box::new(file_buf) as Box<dyn Read>))
|
||||
}
|
||||
}
|
||||
|
|
@ -446,7 +448,7 @@ fn expand(options: &Options) -> UResult<()> {
|
|||
if Path::new(file).is_dir() {
|
||||
show_error!(
|
||||
"{}",
|
||||
translate!("expand-error-is-directory", "file" => file)
|
||||
translate!("expand-error-is-directory", "file" => file.to_string_lossy())
|
||||
);
|
||||
set_exit_code(1);
|
||||
continue;
|
||||
|
|
|
|||
|
|
@ -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<Stdout>,
|
||||
) -> UResult<()> {
|
||||
let mut fp = BufReader::new(match file_name {
|
||||
"-" => Box::new(stdin()) as Box<dyn Read + 'static>,
|
||||
_ => {
|
||||
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<dyn Read + 'static>
|
||||
let mut fp = BufReader::new(if file_name == "-" {
|
||||
Box::new(stdin()) as Box<dyn Read + 'static>
|
||||
} 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<dyn Read + 'static>
|
||||
});
|
||||
|
||||
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<Vec<String>> {
|
||||
fn extract_files(matches: &ArgMatches) -> UResult<Vec<OsString>> {
|
||||
let in_first_pos = matches
|
||||
.index_of(options::FILES_OR_WIDTH)
|
||||
.is_some_and(|x| x == 1);
|
||||
let is_neg = |s: &str| s.parse::<isize>().is_ok_and(|w| w < 0);
|
||||
|
||||
let files: UResult<Vec<String>> = matches
|
||||
.get_many::<String>(options::FILES_OR_WIDTH)
|
||||
let files: UResult<Vec<OsString>> = matches
|
||||
.get_many::<OsString>(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<Vec<String>> {
|
|||
.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<Option<usize>> {
|
|||
}
|
||||
|
||||
if let Some(1) = matches.index_of(options::FILES_OR_WIDTH) {
|
||||
let width_arg = matches.get_one::<String>(options::FILES_OR_WIDTH).unwrap();
|
||||
if let Some(num) = width_arg.strip_prefix('-') {
|
||||
let width_arg = matches
|
||||
.get_one::<OsString>(options::FILES_OR_WIDTH)
|
||||
.unwrap();
|
||||
let width_str = width_arg.to_string_lossy();
|
||||
if let Some(num) = width_str.strip_prefix('-') {
|
||||
Ok(num.parse::<usize>().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),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ enum HeadError {
|
|||
ParseError(String),
|
||||
|
||||
#[error("{}", translate!("head-error-bad-encoding"))]
|
||||
#[allow(dead_code)]
|
||||
BadEncoding,
|
||||
|
||||
#[error("{}", translate!("head-error-num-too-large"))]
|
||||
|
|
@ -129,6 +130,7 @@ pub fn uu_app() -> Command {
|
|||
.arg(
|
||||
Arg::new(options::FILES_NAME)
|
||||
.action(ArgAction::Append)
|
||||
.value_parser(clap::value_parser!(OsString))
|
||||
.value_hint(clap::ValueHint::FilePath),
|
||||
)
|
||||
}
|
||||
|
|
@ -178,15 +180,22 @@ fn arg_iterate<'a>(
|
|||
let first = args.next().unwrap();
|
||||
if let Some(second) = args.next() {
|
||||
if let Some(s) = second.to_str() {
|
||||
match parse::parse_obsolete(s) {
|
||||
Some(Ok(iter)) => Ok(Box::new(vec![first].into_iter().chain(iter).chain(args))),
|
||||
Some(Err(parse::ParseError)) => Err(HeadError::ParseError(
|
||||
translate!("head-error-bad-argument-format", "arg" => s.quote()),
|
||||
)),
|
||||
None => Ok(Box::new(vec![first, second].into_iter().chain(args))),
|
||||
if let Some(v) = parse::parse_obsolete(s) {
|
||||
match v {
|
||||
Ok(iter) => Ok(Box::new(vec![first].into_iter().chain(iter).chain(args))),
|
||||
Err(parse::ParseError) => Err(HeadError::ParseError(
|
||||
translate!("head-error-bad-argument-format", "arg" => s.quote()),
|
||||
)),
|
||||
}
|
||||
} else {
|
||||
// The second argument contains non-UTF-8 sequences, so it can't be an obsolete option
|
||||
// like "-5". Treat it as a regular file argument.
|
||||
Ok(Box::new(vec![first, second].into_iter().chain(args)))
|
||||
}
|
||||
} else {
|
||||
Err(HeadError::BadEncoding)
|
||||
// The second argument contains non-UTF-8 sequences, so it can't be an obsolete option
|
||||
// like "-5". Treat it as a regular file argument.
|
||||
Ok(Box::new(vec![first, second].into_iter().chain(args)))
|
||||
}
|
||||
} else {
|
||||
Ok(Box::new(vec![first].into_iter()))
|
||||
|
|
@ -200,7 +209,7 @@ struct HeadOptions {
|
|||
pub line_ending: LineEnding,
|
||||
pub presume_input_pipe: bool,
|
||||
pub mode: Mode,
|
||||
pub files: Vec<String>,
|
||||
pub files: Vec<OsString>,
|
||||
}
|
||||
|
||||
impl HeadOptions {
|
||||
|
|
@ -215,9 +224,9 @@ impl HeadOptions {
|
|||
|
||||
options.mode = Mode::from(matches)?;
|
||||
|
||||
options.files = match matches.get_many::<String>(options::FILES_NAME) {
|
||||
options.files = match matches.get_many::<OsString>(options::FILES_NAME) {
|
||||
Some(v) => v.cloned().collect(),
|
||||
None => vec!["-".to_owned()],
|
||||
None => vec![OsString::from("-")],
|
||||
};
|
||||
|
||||
Ok(options)
|
||||
|
|
@ -463,76 +472,74 @@ fn head_file(input: &mut File, options: &HeadOptions) -> io::Result<u64> {
|
|||
fn uu_head(options: &HeadOptions) -> UResult<()> {
|
||||
let mut first = true;
|
||||
for file in &options.files {
|
||||
let res = match file.as_str() {
|
||||
"-" => {
|
||||
if (options.files.len() > 1 && !options.quiet) || options.verbose {
|
||||
if !first {
|
||||
println!();
|
||||
}
|
||||
println!("{}", translate!("head-header-stdin"));
|
||||
let res = if file == "-" {
|
||||
if (options.files.len() > 1 && !options.quiet) || options.verbose {
|
||||
if !first {
|
||||
println!();
|
||||
}
|
||||
let stdin = io::stdin();
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
let stdin_raw_fd = stdin.as_raw_fd();
|
||||
let mut stdin_file = unsafe { File::from_raw_fd(stdin_raw_fd) };
|
||||
let current_pos = stdin_file.stream_position();
|
||||
if let Ok(current_pos) = current_pos {
|
||||
// We have a seekable file. Ensure we set the input stream to the
|
||||
// last byte read so that any tools that parse the remainder of
|
||||
// the stdin stream read from the correct place.
|
||||
|
||||
let bytes_read = head_file(&mut stdin_file, options)?;
|
||||
stdin_file.seek(SeekFrom::Start(current_pos + bytes_read))?;
|
||||
} else {
|
||||
let _bytes_read = head_file(&mut stdin_file, options)?;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
{
|
||||
let mut stdin = stdin.lock();
|
||||
|
||||
match options.mode {
|
||||
Mode::FirstBytes(n) => read_n_bytes(&mut stdin, n),
|
||||
Mode::AllButLastBytes(n) => read_but_last_n_bytes(&mut stdin, n),
|
||||
Mode::FirstLines(n) => {
|
||||
read_n_lines(&mut stdin, n, options.line_ending.into())
|
||||
}
|
||||
Mode::AllButLastLines(n) => {
|
||||
read_but_last_n_lines(&mut stdin, n, options.line_ending.into())
|
||||
}
|
||||
}?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
println!("{}", translate!("head-header-stdin"));
|
||||
}
|
||||
name => {
|
||||
let mut file = match File::open(name) {
|
||||
Ok(f) => f,
|
||||
Err(err) => {
|
||||
show!(err.map_err_context(
|
||||
|| translate!("head-error-cannot-open", "name" => name.quote())
|
||||
));
|
||||
continue;
|
||||
}
|
||||
};
|
||||
if (options.files.len() > 1 && !options.quiet) || options.verbose {
|
||||
if !first {
|
||||
println!();
|
||||
}
|
||||
println!("==> {name} <==");
|
||||
let stdin = io::stdin();
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
let stdin_raw_fd = stdin.as_raw_fd();
|
||||
let mut stdin_file = unsafe { File::from_raw_fd(stdin_raw_fd) };
|
||||
let current_pos = stdin_file.stream_position();
|
||||
if let Ok(current_pos) = current_pos {
|
||||
// We have a seekable file. Ensure we set the input stream to the
|
||||
// last byte read so that any tools that parse the remainder of
|
||||
// the stdin stream read from the correct place.
|
||||
|
||||
let bytes_read = head_file(&mut stdin_file, options)?;
|
||||
stdin_file.seek(SeekFrom::Start(current_pos + bytes_read))?;
|
||||
} else {
|
||||
let _bytes_read = head_file(&mut stdin_file, options)?;
|
||||
}
|
||||
head_file(&mut file, options)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
{
|
||||
let mut stdin = stdin.lock();
|
||||
|
||||
match options.mode {
|
||||
Mode::FirstBytes(n) => read_n_bytes(&mut stdin, n),
|
||||
Mode::AllButLastBytes(n) => read_but_last_n_bytes(&mut stdin, n),
|
||||
Mode::FirstLines(n) => read_n_lines(&mut stdin, n, options.line_ending.into()),
|
||||
Mode::AllButLastLines(n) => {
|
||||
read_but_last_n_lines(&mut stdin, n, options.line_ending.into())
|
||||
}
|
||||
}?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
} else {
|
||||
let mut file_handle = match File::open(file) {
|
||||
Ok(f) => f,
|
||||
Err(err) => {
|
||||
show!(err.map_err_context(
|
||||
|| translate!("head-error-cannot-open", "name" => file.to_string_lossy().quote())
|
||||
));
|
||||
continue;
|
||||
}
|
||||
};
|
||||
if (options.files.len() > 1 && !options.quiet) || options.verbose {
|
||||
if !first {
|
||||
println!();
|
||||
}
|
||||
match file.to_str() {
|
||||
Some(name) => println!("==> {name} <=="),
|
||||
None => println!("==> {} <==", file.to_string_lossy()),
|
||||
}
|
||||
}
|
||||
head_file(&mut file_handle, options)?;
|
||||
Ok(())
|
||||
};
|
||||
if let Err(e) = res {
|
||||
let name = if file.as_str() == "-" {
|
||||
"standard input"
|
||||
let name = if file == "-" {
|
||||
"standard input".to_string()
|
||||
} else {
|
||||
file
|
||||
file.to_string_lossy().into_owned()
|
||||
};
|
||||
return Err(HeadError::Io {
|
||||
name: name.to_string(),
|
||||
|
|
@ -675,7 +682,7 @@ mod tests {
|
|||
use std::os::unix::ffi::OsStringExt;
|
||||
let invalid = OsString::from_vec(vec![b'\x80', b'\x81']);
|
||||
// this arises from a conversion from OsString to &str
|
||||
assert!(arg_iterate(vec![OsString::from("head"), invalid].into_iter()).is_err());
|
||||
assert!(arg_iterate(vec![OsString::from("head"), invalid].into_iter()).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ mod mode;
|
|||
use clap::{Arg, ArgAction, ArgMatches, Command};
|
||||
use file_diff::diff;
|
||||
use filetime::{FileTime, set_file_times};
|
||||
use std::ffi::OsString;
|
||||
use std::fmt::Debug;
|
||||
use std::fs::File;
|
||||
use std::fs::{self, metadata};
|
||||
|
|
@ -168,9 +169,9 @@ static ARG_FILES: &str = "files";
|
|||
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||
let matches = uu_app().get_matches_from_localized(args);
|
||||
|
||||
let paths: Vec<String> = matches
|
||||
.get_many::<String>(ARG_FILES)
|
||||
.map(|v| v.map(ToString::to_string).collect())
|
||||
let paths: Vec<OsString> = matches
|
||||
.get_many::<OsString>(ARG_FILES)
|
||||
.map(|v| v.cloned().collect())
|
||||
.unwrap_or_default();
|
||||
|
||||
let behavior = behavior(&matches)?;
|
||||
|
|
@ -303,7 +304,8 @@ pub fn uu_app() -> Command {
|
|||
Arg::new(ARG_FILES)
|
||||
.action(ArgAction::Append)
|
||||
.num_args(1..)
|
||||
.value_hint(clap::ValueHint::AnyPath),
|
||||
.value_hint(clap::ValueHint::AnyPath)
|
||||
.value_parser(clap::value_parser!(OsString)),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -435,7 +437,7 @@ fn behavior(matches: &ArgMatches) -> UResult<Behavior> {
|
|||
///
|
||||
/// Returns a Result type with the Err variant containing the error message.
|
||||
///
|
||||
fn directory(paths: &[String], b: &Behavior) -> UResult<()> {
|
||||
fn directory(paths: &[OsString], b: &Behavior) -> UResult<()> {
|
||||
if paths.is_empty() {
|
||||
Err(InstallError::DirNeedsArg.into())
|
||||
} else {
|
||||
|
|
@ -518,7 +520,7 @@ fn is_potential_directory_path(path: &Path) -> bool {
|
|||
/// Returns a Result type with the Err variant containing the error message.
|
||||
///
|
||||
#[allow(clippy::cognitive_complexity)]
|
||||
fn standard(mut paths: Vec<String>, b: &Behavior) -> UResult<()> {
|
||||
fn standard(mut paths: Vec<OsString>, b: &Behavior) -> UResult<()> {
|
||||
// first check that paths contains at least one element
|
||||
if paths.is_empty() {
|
||||
return Err(UUsageError::new(
|
||||
|
|
@ -528,7 +530,7 @@ fn standard(mut paths: Vec<String>, b: &Behavior) -> UResult<()> {
|
|||
}
|
||||
if b.no_target_dir && paths.len() > 2 {
|
||||
return Err(InstallError::ExtraOperand(
|
||||
paths[2].clone(),
|
||||
paths[2].to_string_lossy().into_owned(),
|
||||
format_usage(&translate!("install-usage")),
|
||||
)
|
||||
.into());
|
||||
|
|
@ -544,7 +546,7 @@ fn standard(mut paths: Vec<String>, b: &Behavior) -> UResult<()> {
|
|||
if paths.is_empty() {
|
||||
return Err(UUsageError::new(
|
||||
1,
|
||||
translate!("install-error-missing-destination-operand", "path" => last_path.to_str().unwrap()),
|
||||
translate!("install-error-missing-destination-operand", "path" => last_path.to_string_lossy()),
|
||||
));
|
||||
}
|
||||
|
||||
|
|
@ -566,10 +568,18 @@ fn standard(mut paths: Vec<String>, b: &Behavior) -> UResult<()> {
|
|||
|
||||
if let Some(to_create) = to_create {
|
||||
// if the path ends in /, remove it
|
||||
let to_create = if to_create.to_string_lossy().ends_with('/') {
|
||||
Path::new(to_create.to_str().unwrap().trim_end_matches('/'))
|
||||
} else {
|
||||
to_create
|
||||
let to_create_owned;
|
||||
let to_create = match uucore::os_str_as_bytes(to_create.as_os_str()) {
|
||||
Ok(path_bytes) if path_bytes.ends_with(b"/") => {
|
||||
let mut trimmed_bytes = path_bytes;
|
||||
while trimmed_bytes.ends_with(b"/") {
|
||||
trimmed_bytes = &trimmed_bytes[..trimmed_bytes.len() - 1];
|
||||
}
|
||||
let trimmed_os_str = std::ffi::OsStr::from_bytes(trimmed_bytes);
|
||||
to_create_owned = PathBuf::from(trimmed_os_str);
|
||||
to_create_owned.as_path()
|
||||
}
|
||||
_ => to_create,
|
||||
};
|
||||
|
||||
if !to_create.exists() {
|
||||
|
|
@ -835,7 +845,7 @@ fn copy_file(from: &Path, to: &Path) -> UResult<()> {
|
|||
///
|
||||
fn strip_file(to: &Path, b: &Behavior) -> UResult<()> {
|
||||
// Check if the filename starts with a hyphen and adjust the path
|
||||
let to_str = to.as_os_str().to_str().unwrap_or_default();
|
||||
let to_str = to.to_string_lossy();
|
||||
let to = if to_str.starts_with('-') {
|
||||
let mut new_path = PathBuf::from(".");
|
||||
new_path.push(to);
|
||||
|
|
@ -1085,7 +1095,7 @@ fn need_copy(from: &Path, to: &Path, b: &Behavior) -> bool {
|
|||
}
|
||||
|
||||
// Check if the contents of the source and destination files differ.
|
||||
if !diff(from.to_str().unwrap(), to.to_str().unwrap()) {
|
||||
if !diff(&from.to_string_lossy(), &to.to_string_lossy()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -413,7 +413,7 @@ impl Line {
|
|||
|
||||
struct State<'a> {
|
||||
key: usize,
|
||||
file_name: &'a str,
|
||||
file_name: &'a OsString,
|
||||
file_num: FileNum,
|
||||
print_unpaired: bool,
|
||||
lines: Split<Box<dyn BufRead + 'a>>,
|
||||
|
|
@ -427,7 +427,7 @@ struct State<'a> {
|
|||
impl<'a> State<'a> {
|
||||
fn new(
|
||||
file_num: FileNum,
|
||||
name: &'a str,
|
||||
name: &'a OsString,
|
||||
stdin: &'a Stdin,
|
||||
key: usize,
|
||||
line_ending: LineEnding,
|
||||
|
|
@ -436,7 +436,8 @@ impl<'a> State<'a> {
|
|||
let file_buf = if name == "-" {
|
||||
Box::new(stdin.lock()) as Box<dyn BufRead>
|
||||
} else {
|
||||
let file = File::open(name).map_err_context(|| format!("{}", name.maybe_quote()))?;
|
||||
let file = File::open(name)
|
||||
.map_err_context(|| format!("{}", name.to_string_lossy().maybe_quote()))?;
|
||||
Box::new(BufReader::new(file)) as Box<dyn BufRead>
|
||||
};
|
||||
|
||||
|
|
@ -639,7 +640,7 @@ impl<'a> State<'a> {
|
|||
&& (input.check_order == CheckOrder::Enabled
|
||||
|| (self.has_unpaired && !self.has_failed))
|
||||
{
|
||||
let err_msg = translate!("join-error-not-sorted", "file" => self.file_name.maybe_quote(), "line_num" => self.line_num, "content" => String::from_utf8_lossy(&line.string));
|
||||
let err_msg = translate!("join-error-not-sorted", "file" => self.file_name.to_string_lossy().maybe_quote(), "line_num" => self.line_num, "content" => String::from_utf8_lossy(&line.string));
|
||||
// This is fatal if the check is enabled.
|
||||
if input.check_order == CheckOrder::Enabled {
|
||||
return Err(JoinError::UnorderedInput(err_msg));
|
||||
|
|
@ -826,8 +827,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
|||
|
||||
let settings = parse_settings(&matches)?;
|
||||
|
||||
let file1 = matches.get_one::<String>("file1").unwrap();
|
||||
let file2 = matches.get_one::<String>("file2").unwrap();
|
||||
let file1 = matches.get_one::<OsString>("file1").unwrap();
|
||||
let file2 = matches.get_one::<OsString>("file2").unwrap();
|
||||
|
||||
if file1 == "-" && file2 == "-" {
|
||||
return Err(USimpleError::new(
|
||||
|
|
@ -951,6 +952,7 @@ pub fn uu_app() -> Command {
|
|||
.required(true)
|
||||
.value_name("FILE1")
|
||||
.value_hint(clap::ValueHint::FilePath)
|
||||
.value_parser(clap::value_parser!(OsString))
|
||||
.hide(true),
|
||||
)
|
||||
.arg(
|
||||
|
|
@ -958,11 +960,17 @@ pub fn uu_app() -> Command {
|
|||
.required(true)
|
||||
.value_name("FILE2")
|
||||
.value_hint(clap::ValueHint::FilePath)
|
||||
.value_parser(clap::value_parser!(OsString))
|
||||
.hide(true),
|
||||
)
|
||||
}
|
||||
|
||||
fn exec<Sep: Separator>(file1: &str, file2: &str, settings: Settings, sep: Sep) -> UResult<()> {
|
||||
fn exec<Sep: Separator>(
|
||||
file1: &OsString,
|
||||
file2: &OsString,
|
||||
settings: Settings,
|
||||
sep: Sep,
|
||||
) -> UResult<()> {
|
||||
let stdin = stdin();
|
||||
|
||||
let mut state1 = State::new(
|
||||
|
|
|
|||
|
|
@ -30,11 +30,11 @@ use uucore::fs::{MissingHandling, ResolveMode, canonicalize};
|
|||
pub struct Settings {
|
||||
overwrite: OverwriteMode,
|
||||
backup: BackupMode,
|
||||
suffix: String,
|
||||
suffix: OsString,
|
||||
symbolic: bool,
|
||||
relative: bool,
|
||||
logical: bool,
|
||||
target_dir: Option<String>,
|
||||
target_dir: Option<PathBuf>,
|
||||
no_target_dir: bool,
|
||||
no_dereference: bool,
|
||||
verbose: bool,
|
||||
|
|
@ -61,7 +61,7 @@ enum LnError {
|
|||
#[error("{}", translate!("ln-error-missing-destination", "operand" => _0.quote()))]
|
||||
MissingDestination(PathBuf),
|
||||
|
||||
#[error("{}", translate!("ln-error-extra-operand", "operand" => format!("{_0:?}").trim_matches('"'), "program" => _1.clone()))]
|
||||
#[error("{}", translate!("ln-error-extra-operand", "operand" => _0.to_string_lossy(), "program" => _1.clone()))]
|
||||
ExtraOperand(OsString, String),
|
||||
}
|
||||
|
||||
|
|
@ -102,7 +102,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
|||
/* the list of files */
|
||||
|
||||
let paths: Vec<PathBuf> = matches
|
||||
.get_many::<String>(ARG_FILES)
|
||||
.get_many::<OsString>(ARG_FILES)
|
||||
.unwrap()
|
||||
.map(PathBuf::from)
|
||||
.collect();
|
||||
|
|
@ -126,13 +126,13 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
|||
let settings = Settings {
|
||||
overwrite: overwrite_mode,
|
||||
backup: backup_mode,
|
||||
suffix: backup_suffix,
|
||||
suffix: OsString::from(backup_suffix),
|
||||
symbolic,
|
||||
logical,
|
||||
relative: matches.get_flag(options::RELATIVE),
|
||||
target_dir: matches
|
||||
.get_one::<String>(options::TARGET_DIRECTORY)
|
||||
.map(String::from),
|
||||
.get_one::<OsString>(options::TARGET_DIRECTORY)
|
||||
.map(PathBuf::from),
|
||||
no_target_dir: matches.get_flag(options::NO_TARGET_DIRECTORY),
|
||||
no_dereference: matches.get_flag(options::NO_DEREFERENCE),
|
||||
verbose: matches.get_flag(options::VERBOSE),
|
||||
|
|
@ -210,6 +210,7 @@ pub fn uu_app() -> Command {
|
|||
.help(translate!("ln-help-target-directory"))
|
||||
.value_name("DIRECTORY")
|
||||
.value_hint(clap::ValueHint::DirPath)
|
||||
.value_parser(clap::value_parser!(OsString))
|
||||
.conflicts_with(options::NO_TARGET_DIRECTORY),
|
||||
)
|
||||
.arg(
|
||||
|
|
@ -238,6 +239,7 @@ pub fn uu_app() -> Command {
|
|||
Arg::new(ARG_FILES)
|
||||
.action(ArgAction::Append)
|
||||
.value_hint(clap::ValueHint::AnyPath)
|
||||
.value_parser(clap::value_parser!(OsString))
|
||||
.required(true)
|
||||
.num_args(1..),
|
||||
)
|
||||
|
|
@ -245,9 +247,9 @@ pub fn uu_app() -> Command {
|
|||
|
||||
fn exec(files: &[PathBuf], settings: &Settings) -> UResult<()> {
|
||||
// Handle cases where we create links in a directory first.
|
||||
if let Some(ref name) = settings.target_dir {
|
||||
if let Some(ref target_path) = settings.target_dir {
|
||||
// 4th form: a directory is specified by -t.
|
||||
return link_files_in_dir(files, &PathBuf::from(name), settings);
|
||||
return link_files_in_dir(files, target_path, settings);
|
||||
}
|
||||
if !settings.no_target_dir {
|
||||
if files.len() == 1 {
|
||||
|
|
@ -445,16 +447,16 @@ fn link(src: &Path, dst: &Path, settings: &Settings) -> UResult<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn simple_backup_path(path: &Path, suffix: &str) -> PathBuf {
|
||||
let mut p = path.as_os_str().to_str().unwrap().to_owned();
|
||||
p.push_str(suffix);
|
||||
PathBuf::from(p)
|
||||
fn simple_backup_path(path: &Path, suffix: &OsString) -> PathBuf {
|
||||
let mut file_name = path.file_name().unwrap_or_default().to_os_string();
|
||||
file_name.push(suffix);
|
||||
path.with_file_name(file_name)
|
||||
}
|
||||
|
||||
fn numbered_backup_path(path: &Path) -> PathBuf {
|
||||
let mut i: u64 = 1;
|
||||
loop {
|
||||
let new_path = simple_backup_path(path, &format!(".~{i}~"));
|
||||
let new_path = simple_backup_path(path, &OsString::from(format!(".~{i}~")));
|
||||
if !new_path.exists() {
|
||||
return new_path;
|
||||
}
|
||||
|
|
@ -462,8 +464,8 @@ fn numbered_backup_path(path: &Path) -> PathBuf {
|
|||
}
|
||||
}
|
||||
|
||||
fn existing_backup_path(path: &Path, suffix: &str) -> PathBuf {
|
||||
let test_path = simple_backup_path(path, ".~1~");
|
||||
fn existing_backup_path(path: &Path, suffix: &OsString) -> PathBuf {
|
||||
let test_path = simple_backup_path(path, &OsString::from(".~1~"));
|
||||
if test_path.exists() {
|
||||
return numbered_backup_path(path);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ use uucore::format_usage;
|
|||
use uucore::translate;
|
||||
|
||||
use std::env;
|
||||
use std::ffi::OsStr;
|
||||
use std::ffi::{OsStr, OsString};
|
||||
use std::io::ErrorKind;
|
||||
use std::iter;
|
||||
use std::path::{MAIN_SEPARATOR, Path, PathBuf};
|
||||
|
|
@ -105,7 +105,7 @@ pub struct Options {
|
|||
pub treat_as_template: bool,
|
||||
|
||||
/// The template to use for the name of the temporary file.
|
||||
pub template: String,
|
||||
pub template: OsString,
|
||||
}
|
||||
|
||||
impl Options {
|
||||
|
|
@ -123,12 +123,12 @@ impl Options {
|
|||
.ok()
|
||||
.map_or_else(env::temp_dir, PathBuf::from),
|
||||
});
|
||||
let (tmpdir, template) = match matches.get_one::<String>(ARG_TEMPLATE) {
|
||||
let (tmpdir, template) = match matches.get_one::<OsString>(ARG_TEMPLATE) {
|
||||
// If no template argument is given, `--tmpdir` is implied.
|
||||
None => {
|
||||
let tmpdir = Some(tmpdir.unwrap_or_else(env::temp_dir));
|
||||
let template = DEFAULT_TEMPLATE;
|
||||
(tmpdir, template.to_string())
|
||||
(tmpdir, OsString::from(template))
|
||||
}
|
||||
Some(template) => {
|
||||
let tmpdir = if env::var(TMPDIR_ENV_VAR).is_ok() && matches.get_flag(OPT_T) {
|
||||
|
|
@ -142,7 +142,7 @@ impl Options {
|
|||
} else {
|
||||
None
|
||||
};
|
||||
(tmpdir, template.to_string())
|
||||
(tmpdir, template.clone())
|
||||
}
|
||||
};
|
||||
Self {
|
||||
|
|
@ -200,23 +200,30 @@ fn find_last_contiguous_block_of_xs(s: &str) -> Option<(usize, usize)> {
|
|||
|
||||
impl Params {
|
||||
fn from(options: Options) -> Result<Self, MkTempError> {
|
||||
// Convert OsString template to string for processing
|
||||
let Some(template_str) = options.template.to_str() else {
|
||||
// For non-UTF-8 templates, return an error
|
||||
return Err(MkTempError::InvalidTemplate(
|
||||
options.template.to_string_lossy().into_owned(),
|
||||
));
|
||||
};
|
||||
|
||||
// The template argument must end in 'X' if a suffix option is given.
|
||||
if options.suffix.is_some() && !options.template.ends_with('X') {
|
||||
return Err(MkTempError::MustEndInX(options.template));
|
||||
if options.suffix.is_some() && !template_str.ends_with('X') {
|
||||
return Err(MkTempError::MustEndInX(template_str.to_string()));
|
||||
}
|
||||
|
||||
// Get the start and end indices of the randomized part of the template.
|
||||
//
|
||||
// For example, if the template is "abcXXXXyz", then `i` is 3 and `j` is 7.
|
||||
let Some((i, j)) = find_last_contiguous_block_of_xs(&options.template) else {
|
||||
let Some((i, j)) = find_last_contiguous_block_of_xs(template_str) else {
|
||||
let s = match options.suffix {
|
||||
// If a suffix is specified, the error message includes the template without the suffix.
|
||||
Some(_) => options
|
||||
.template
|
||||
Some(_) => template_str
|
||||
.chars()
|
||||
.take(options.template.len())
|
||||
.take(template_str.len())
|
||||
.collect::<String>(),
|
||||
None => options.template,
|
||||
None => template_str.to_string(),
|
||||
};
|
||||
return Err(MkTempError::TooFewXs(s));
|
||||
};
|
||||
|
|
@ -227,35 +234,36 @@ impl Params {
|
|||
// then `prefix` is "a/b/c/d".
|
||||
let tmpdir = options.tmpdir;
|
||||
let prefix_from_option = tmpdir.clone().unwrap_or_default();
|
||||
let prefix_from_template = &options.template[..i];
|
||||
let prefix = Path::new(&prefix_from_option)
|
||||
.join(prefix_from_template)
|
||||
.display()
|
||||
.to_string();
|
||||
let prefix_from_template = &template_str[..i];
|
||||
let prefix_path = Path::new(&prefix_from_option).join(prefix_from_template);
|
||||
if options.treat_as_template && prefix_from_template.contains(MAIN_SEPARATOR) {
|
||||
return Err(MkTempError::PrefixContainsDirSeparator(options.template));
|
||||
return Err(MkTempError::PrefixContainsDirSeparator(
|
||||
template_str.to_string(),
|
||||
));
|
||||
}
|
||||
if tmpdir.is_some() && Path::new(prefix_from_template).is_absolute() {
|
||||
return Err(MkTempError::InvalidTemplate(options.template));
|
||||
return Err(MkTempError::InvalidTemplate(template_str.to_string()));
|
||||
}
|
||||
|
||||
// Split the parent directory from the file part of the prefix.
|
||||
//
|
||||
// For example, if `prefix` is "a/b/c/d", then `directory` is
|
||||
// "a/b/c" is `prefix` gets reassigned to "d".
|
||||
let (directory, prefix) = if prefix.ends_with(MAIN_SEPARATOR) {
|
||||
(prefix, String::new())
|
||||
} else {
|
||||
let path = Path::new(&prefix);
|
||||
let directory = match path.parent() {
|
||||
None => String::new(),
|
||||
Some(d) => d.display().to_string(),
|
||||
};
|
||||
let prefix = match path.file_name() {
|
||||
None => String::new(),
|
||||
Some(f) => f.to_str().unwrap().to_string(),
|
||||
};
|
||||
(directory, prefix)
|
||||
// For example, if `prefix_path` is "a/b/c/d", then `directory` is
|
||||
// "a/b/c" and `prefix` gets reassigned to "d".
|
||||
let (directory, prefix) = {
|
||||
let prefix_str = prefix_path.to_string_lossy();
|
||||
if prefix_str.ends_with(MAIN_SEPARATOR) {
|
||||
(prefix_path, String::new())
|
||||
} else {
|
||||
let directory = match prefix_path.parent() {
|
||||
None => PathBuf::new(),
|
||||
Some(d) => d.to_path_buf(),
|
||||
};
|
||||
let prefix = match prefix_path.file_name() {
|
||||
None => String::new(),
|
||||
Some(f) => f.to_str().unwrap().to_string(),
|
||||
};
|
||||
(directory, prefix)
|
||||
}
|
||||
};
|
||||
|
||||
// Combine the suffix from the template with the suffix given as an option.
|
||||
|
|
@ -263,7 +271,7 @@ impl Params {
|
|||
// For example, if the suffix command-line argument is ".txt" and
|
||||
// the template is "XXXabc", then `suffix` is "abc.txt".
|
||||
let suffix_from_option = options.suffix.unwrap_or_default();
|
||||
let suffix_from_template = &options.template[j..];
|
||||
let suffix_from_template = &template_str[j..];
|
||||
let suffix = format!("{suffix_from_template}{suffix_from_option}");
|
||||
if suffix.contains(MAIN_SEPARATOR) {
|
||||
return Err(MkTempError::SuffixContainsDirSeparator(suffix));
|
||||
|
|
@ -276,7 +284,7 @@ impl Params {
|
|||
let num_rand_chars = j - i;
|
||||
|
||||
Ok(Self {
|
||||
directory: directory.into(),
|
||||
directory,
|
||||
prefix,
|
||||
num_rand_chars,
|
||||
suffix,
|
||||
|
|
@ -360,7 +368,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
|||
// If POSIXLY_CORRECT was set, template MUST be the last argument.
|
||||
if matches.contains_id(ARG_TEMPLATE) {
|
||||
// Template argument was provided, check if was the last one.
|
||||
if args.last().unwrap() != OsStr::new(&options.template) {
|
||||
if args.last().unwrap() != &options.template {
|
||||
return Err(Box::new(MkTempError::TooManyTemplates));
|
||||
}
|
||||
}
|
||||
|
|
@ -457,7 +465,11 @@ pub fn uu_app() -> Command {
|
|||
.help(translate!("mktemp-help-t"))
|
||||
.action(ArgAction::SetTrue),
|
||||
)
|
||||
.arg(Arg::new(ARG_TEMPLATE).num_args(..=1))
|
||||
.arg(
|
||||
Arg::new(ARG_TEMPLATE)
|
||||
.num_args(..=1)
|
||||
.value_parser(clap::value_parser!(OsString)),
|
||||
)
|
||||
}
|
||||
|
||||
fn dry_exec(tmpdir: &Path, prefix: &str, rand: usize, suffix: &str) -> UResult<PathBuf> {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
// file that was distributed with this source code.
|
||||
|
||||
use std::{
|
||||
ffi::OsString,
|
||||
fs::File,
|
||||
io::{BufRead, BufReader, Stdin, Stdout, Write, stdin, stdout},
|
||||
panic::set_hook,
|
||||
|
|
@ -154,12 +155,12 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
|||
}));
|
||||
let matches = uu_app().get_matches_from_localized(args);
|
||||
let mut options = Options::from(&matches);
|
||||
if let Some(files) = matches.get_many::<String>(options::FILES) {
|
||||
if let Some(files) = matches.get_many::<OsString>(options::FILES) {
|
||||
let length = files.len();
|
||||
|
||||
let mut files_iter = files.map(|s| s.as_str()).peekable();
|
||||
while let (Some(file), next_file) = (files_iter.next(), files_iter.peek()) {
|
||||
let file = Path::new(file);
|
||||
let mut files_iter = files.peekable();
|
||||
while let (Some(file_os), next_file) = (files_iter.next(), files_iter.peek()) {
|
||||
let file = Path::new(file_os);
|
||||
if file.is_dir() {
|
||||
show!(UUsageError::new(
|
||||
0,
|
||||
|
|
@ -188,11 +189,12 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
|||
}
|
||||
Ok(opened_file) => opened_file,
|
||||
};
|
||||
let next_file_str = next_file.map(|f| f.to_string_lossy().into_owned());
|
||||
more(
|
||||
InputType::File(BufReader::new(opened_file)),
|
||||
length > 1,
|
||||
file.to_str(),
|
||||
next_file.copied(),
|
||||
Some(&file.to_string_lossy()),
|
||||
next_file_str.as_deref(),
|
||||
&mut options,
|
||||
)?;
|
||||
}
|
||||
|
|
@ -311,7 +313,8 @@ pub fn uu_app() -> Command {
|
|||
.required(false)
|
||||
.action(ArgAction::Append)
|
||||
.help(translate!("more-help-files"))
|
||||
.value_hint(clap::ValueHint::FilePath),
|
||||
.value_hint(clap::ValueHint::FilePath)
|
||||
.value_parser(clap::value_parser!(OsString)),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
// file that was distributed with this source code.
|
||||
|
||||
use clap::{Arg, ArgAction, Command};
|
||||
use std::ffi::OsString;
|
||||
use std::fs::File;
|
||||
use std::io::{BufRead, BufReader, Read, stdin};
|
||||
use std::path::Path;
|
||||
|
|
@ -195,9 +196,9 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
|||
));
|
||||
}
|
||||
|
||||
let files: Vec<String> = match matches.get_many::<String>(options::FILE) {
|
||||
let files: Vec<OsString> = match matches.get_many::<OsString>(options::FILE) {
|
||||
Some(v) => v.cloned().collect(),
|
||||
None => vec!["-".to_owned()],
|
||||
None => vec![OsString::from("-")],
|
||||
};
|
||||
|
||||
let mut stats = Stats::new(settings.starting_line_number);
|
||||
|
|
@ -216,7 +217,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
|||
);
|
||||
set_exit_code(1);
|
||||
} else {
|
||||
let reader = File::open(path).map_err_context(|| file.to_string())?;
|
||||
let reader =
|
||||
File::open(path).map_err_context(|| file.to_string_lossy().to_string())?;
|
||||
let mut buffer = BufReader::new(reader);
|
||||
nl(&mut buffer, &mut stats, &settings)?;
|
||||
}
|
||||
|
|
@ -245,7 +247,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::BODY_NUMBERING)
|
||||
|
|
|
|||
|
|
@ -5,9 +5,11 @@
|
|||
|
||||
use clap::{Arg, ArgAction, Command};
|
||||
use std::cell::{OnceCell, RefCell};
|
||||
use std::ffi::OsString;
|
||||
use std::fs::File;
|
||||
use std::io::{BufRead, BufReader, Stdin, Write, stdin, stdout};
|
||||
use std::iter::Cycle;
|
||||
use std::path::Path;
|
||||
use std::rc::Rc;
|
||||
use std::slice::Iter;
|
||||
use uucore::LocalizedCommand;
|
||||
|
|
@ -30,7 +32,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
|||
let serial = matches.get_flag(options::SERIAL);
|
||||
let delimiters = matches.get_one::<String>(options::DELIMITER).unwrap();
|
||||
let files = matches
|
||||
.get_many::<String>(options::FILE)
|
||||
.get_many::<OsString>(options::FILE)
|
||||
.unwrap()
|
||||
.cloned()
|
||||
.collect();
|
||||
|
|
@ -67,7 +69,8 @@ pub fn uu_app() -> Command {
|
|||
.value_name("FILE")
|
||||
.action(ArgAction::Append)
|
||||
.default_value("-")
|
||||
.value_hint(clap::ValueHint::FilePath),
|
||||
.value_hint(clap::ValueHint::FilePath)
|
||||
.value_parser(clap::value_parser!(OsString)),
|
||||
)
|
||||
.arg(
|
||||
Arg::new(options::ZERO_TERMINATED)
|
||||
|
|
@ -80,7 +83,7 @@ pub fn uu_app() -> Command {
|
|||
|
||||
#[allow(clippy::cognitive_complexity)]
|
||||
fn paste(
|
||||
filenames: Vec<String>,
|
||||
filenames: Vec<OsString>,
|
||||
serial: bool,
|
||||
delimiters: &str,
|
||||
line_ending: LineEnding,
|
||||
|
|
@ -92,17 +95,16 @@ fn paste(
|
|||
let mut input_source_vec = Vec::with_capacity(filenames.len());
|
||||
|
||||
for filename in filenames {
|
||||
let input_source = match filename.as_str() {
|
||||
"-" => InputSource::StandardInput(
|
||||
let input_source = if filename == "-" {
|
||||
InputSource::StandardInput(
|
||||
stdin_once_cell
|
||||
.get_or_init(|| Rc::new(RefCell::new(stdin())))
|
||||
.clone(),
|
||||
),
|
||||
st => {
|
||||
let file = File::open(st)?;
|
||||
|
||||
InputSource::File(BufReader::new(file))
|
||||
}
|
||||
)
|
||||
} else {
|
||||
let path = Path::new(&filename);
|
||||
let file = File::open(path)?;
|
||||
InputSource::File(BufReader::new(file))
|
||||
};
|
||||
|
||||
input_source_vec.push(input_source);
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
// spell-checker:ignore (ToDO) lstat
|
||||
use clap::{Arg, ArgAction, Command};
|
||||
use std::ffi::OsString;
|
||||
use std::fs;
|
||||
use std::io::{ErrorKind, Write};
|
||||
use uucore::LocalizedCommand;
|
||||
|
|
@ -53,7 +54,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
|||
};
|
||||
|
||||
// take necessary actions
|
||||
let paths = matches.get_many::<String>(options::PATH);
|
||||
let paths = matches.get_many::<OsString>(options::PATH);
|
||||
if paths.is_none() {
|
||||
return Err(UUsageError::new(
|
||||
1,
|
||||
|
|
@ -65,8 +66,9 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
|||
// FIXME: TCS, seems inefficient and overly verbose (?)
|
||||
let mut res = true;
|
||||
for p in paths.unwrap() {
|
||||
let path_str = p.to_string_lossy();
|
||||
let mut path = Vec::new();
|
||||
for path_segment in p.split('/') {
|
||||
for path_segment in path_str.split('/') {
|
||||
path.push(path_segment.to_string());
|
||||
}
|
||||
res &= check_path(&mode, &path);
|
||||
|
|
@ -108,7 +110,8 @@ pub fn uu_app() -> Command {
|
|||
Arg::new(options::PATH)
|
||||
.hide(true)
|
||||
.action(ArgAction::Append)
|
||||
.value_hint(clap::ValueHint::AnyPath),
|
||||
.value_hint(clap::ValueHint::AnyPath)
|
||||
.value_parser(clap::value_parser!(OsString)),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,10 +7,12 @@
|
|||
|
||||
use std::cmp;
|
||||
use std::collections::{BTreeSet, HashMap, HashSet};
|
||||
use std::ffi::{OsStr, 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();
|
||||
|
|
@ -191,7 +192,7 @@ struct WordRef {
|
|||
local_line_nr: usize,
|
||||
position: usize,
|
||||
position_end: usize,
|
||||
filename: String,
|
||||
filename: OsString,
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
|
|
@ -273,16 +274,16 @@ struct FileContent {
|
|||
offset: usize,
|
||||
}
|
||||
|
||||
type FileMap = HashMap<String, FileContent>;
|
||||
type FileMap = HashMap<OsString, 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.clone(),
|
||||
FileContent {
|
||||
lines,
|
||||
chars_lines,
|
||||
|
|
@ -646,21 +647,22 @@ fn write_traditional_output(
|
|||
config: &Config,
|
||||
file_map: &FileMap,
|
||||
words: &BTreeSet<WordRef>,
|
||||
output_filename: &str,
|
||||
output_filename: &OsStr,
|
||||
) -> UResult<()> {
|
||||
let mut writer: BufWriter<Box<dyn Write>> = BufWriter::new(if output_filename == "-" {
|
||||
Box::new(stdout())
|
||||
} else {
|
||||
let file = File::create(output_filename)
|
||||
.map_err_context(|| output_filename.maybe_quote().to_string())?;
|
||||
Box::new(file)
|
||||
});
|
||||
let mut writer: BufWriter<Box<dyn Write>> =
|
||||
BufWriter::new(if output_filename == OsStr::new("-") {
|
||||
Box::new(stdout())
|
||||
} else {
|
||||
let file = File::create(output_filename)
|
||||
.map_err_context(|| output_filename.to_string_lossy().quote().to_string())?;
|
||||
Box::new(file)
|
||||
});
|
||||
|
||||
let context_reg = Regex::new(&config.context_regex).unwrap();
|
||||
|
||||
for word_ref in words {
|
||||
let file_map_value: &FileContent = file_map
|
||||
.get(&(word_ref.filename))
|
||||
.get(&word_ref.filename)
|
||||
.expect("Missing file in file map");
|
||||
let FileContent {
|
||||
ref lines,
|
||||
|
|
@ -733,10 +735,10 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
|||
let config = get_config(&matches)?;
|
||||
|
||||
let input_files;
|
||||
let output_file;
|
||||
let output_file: OsString;
|
||||
|
||||
let mut files = matches
|
||||
.get_many::<String>(options::FILE)
|
||||
.get_many::<OsString>(options::FILE)
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.cloned();
|
||||
|
|
@ -745,18 +747,18 @@ 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();
|
||||
output_file = OsString::from("-");
|
||||
} 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("-"));
|
||||
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 +780,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 +859,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 +882,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 +891,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)
|
||||
|
|
|
|||
|
|
@ -6,11 +6,11 @@
|
|||
// spell-checker:ignore (ToDO) errno
|
||||
|
||||
use clap::{Arg, ArgAction, Command};
|
||||
use std::ffi::OsString;
|
||||
use std::fs;
|
||||
use std::io::{Write, stdout};
|
||||
use std::path::{Path, PathBuf};
|
||||
use uucore::LocalizedCommand;
|
||||
use uucore::display::Quotable;
|
||||
use uucore::error::{FromIo, UResult, USimpleError, UUsageError};
|
||||
use uucore::fs::{MissingHandling, ResolveMode, canonicalize};
|
||||
use uucore::line_ending::LineEnding;
|
||||
|
|
@ -54,9 +54,9 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
|||
MissingHandling::Normal
|
||||
};
|
||||
|
||||
let files: Vec<String> = matches
|
||||
.get_many::<String>(ARG_FILES)
|
||||
.map(|v| v.map(ToString::to_string).collect())
|
||||
let files: Vec<PathBuf> = matches
|
||||
.get_many::<OsString>(ARG_FILES)
|
||||
.map(|v| v.map(PathBuf::from).collect())
|
||||
.unwrap_or_default();
|
||||
|
||||
if files.is_empty() {
|
||||
|
|
@ -77,12 +77,11 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
|||
Some(LineEnding::from_zero_flag(use_zero))
|
||||
};
|
||||
|
||||
for f in &files {
|
||||
let p = PathBuf::from(f);
|
||||
for p in &files {
|
||||
let path_result = if res_mode == ResolveMode::None {
|
||||
fs::read_link(&p)
|
||||
fs::read_link(p)
|
||||
} else {
|
||||
canonicalize(&p, can_mode, res_mode)
|
||||
canonicalize(p, can_mode, res_mode)
|
||||
};
|
||||
|
||||
match path_result {
|
||||
|
|
@ -93,7 +92,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
|||
return if verbose {
|
||||
Err(USimpleError::new(
|
||||
1,
|
||||
err.map_err_context(move || f.maybe_quote().to_string())
|
||||
err.map_err_context(move || p.to_string_lossy().to_string())
|
||||
.to_string(),
|
||||
))
|
||||
} else {
|
||||
|
|
@ -171,13 +170,13 @@ pub fn uu_app() -> Command {
|
|||
.arg(
|
||||
Arg::new(ARG_FILES)
|
||||
.action(ArgAction::Append)
|
||||
.value_parser(clap::value_parser!(OsString))
|
||||
.value_hint(clap::ValueHint::AnyPath),
|
||||
)
|
||||
}
|
||||
|
||||
fn show(path: &Path, line_ending: Option<LineEnding>) -> std::io::Result<()> {
|
||||
let path = path.to_str().unwrap();
|
||||
print!("{path}");
|
||||
uucore::display::print_verbatim(path)?;
|
||||
if let Some(line_ending) = line_ending {
|
||||
print!("{line_ending}");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,3 +11,6 @@ realpath-help-canonicalize-existing = canonicalize by following every symlink in
|
|||
realpath-help-canonicalize-missing = canonicalize by following every symlink in every component of the given name recursively, without requirements on components existence
|
||||
realpath-help-relative-to = print the resolved path relative to DIR
|
||||
realpath-help-relative-base = print absolute paths unless paths below DIR
|
||||
|
||||
# Error messages
|
||||
realpath-invalid-empty-operand = invalid operand: empty string
|
||||
|
|
|
|||
|
|
@ -11,3 +11,6 @@ realpath-help-canonicalize-existing = canonicaliser en suivant récursivement ch
|
|||
realpath-help-canonicalize-missing = canonicaliser en suivant récursivement chaque lien symbolique dans chaque composant du nom donné, sans exigences sur l'existence des composants
|
||||
realpath-help-relative-to = afficher le chemin résolu relativement à RÉP
|
||||
realpath-help-relative-base = afficher les chemins absolus sauf pour les chemins sous RÉP
|
||||
|
||||
# Messages d'erreur
|
||||
realpath-invalid-empty-operand = opérande invalide : chaîne vide
|
||||
|
|
|
|||
|
|
@ -5,8 +5,12 @@
|
|||
|
||||
// spell-checker:ignore (ToDO) retcode
|
||||
|
||||
use clap::{Arg, ArgAction, ArgMatches, Command, builder::NonEmptyStringValueParser};
|
||||
use clap::{
|
||||
Arg, ArgAction, ArgMatches, Command,
|
||||
builder::{TypedValueParser, ValueParserFactory},
|
||||
};
|
||||
use std::{
|
||||
ffi::{OsStr, OsString},
|
||||
io::{Write, stdout},
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
|
@ -33,6 +37,39 @@ const OPT_RELATIVE_BASE: &str = "relative-base";
|
|||
|
||||
const ARG_FILES: &str = "files";
|
||||
|
||||
/// Custom parser that validates `OsString` is not empty
|
||||
#[derive(Clone, Debug)]
|
||||
struct NonEmptyOsStringParser;
|
||||
|
||||
impl TypedValueParser for NonEmptyOsStringParser {
|
||||
type Value = OsString;
|
||||
|
||||
fn parse_ref(
|
||||
&self,
|
||||
_cmd: &Command,
|
||||
_arg: Option<&Arg>,
|
||||
value: &OsStr,
|
||||
) -> Result<Self::Value, clap::Error> {
|
||||
if value.is_empty() {
|
||||
let mut err = clap::Error::new(clap::error::ErrorKind::ValueValidation);
|
||||
err.insert(
|
||||
clap::error::ContextKind::Custom,
|
||||
clap::error::ContextValue::String(translate!("realpath-invalid-empty-operand")),
|
||||
);
|
||||
return Err(err);
|
||||
}
|
||||
Ok(value.to_os_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl ValueParserFactory for NonEmptyOsStringParser {
|
||||
type Parser = Self;
|
||||
|
||||
fn value_parser() -> Self::Parser {
|
||||
Self
|
||||
}
|
||||
}
|
||||
|
||||
#[uucore::main]
|
||||
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||
let matches = uu_app().try_get_matches_from(args).with_exit_code(1)?;
|
||||
|
|
@ -40,7 +77,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
|||
/* the list of files */
|
||||
|
||||
let paths: Vec<PathBuf> = matches
|
||||
.get_many::<String>(ARG_FILES)
|
||||
.get_many::<OsString>(ARG_FILES)
|
||||
.unwrap()
|
||||
.map(PathBuf::from)
|
||||
.collect();
|
||||
|
|
@ -145,21 +182,21 @@ pub fn uu_app() -> Command {
|
|||
Arg::new(OPT_RELATIVE_TO)
|
||||
.long(OPT_RELATIVE_TO)
|
||||
.value_name("DIR")
|
||||
.value_parser(NonEmptyStringValueParser::new())
|
||||
.value_parser(NonEmptyOsStringParser)
|
||||
.help(translate!("realpath-help-relative-to")),
|
||||
)
|
||||
.arg(
|
||||
Arg::new(OPT_RELATIVE_BASE)
|
||||
.long(OPT_RELATIVE_BASE)
|
||||
.value_name("DIR")
|
||||
.value_parser(NonEmptyStringValueParser::new())
|
||||
.value_parser(NonEmptyOsStringParser)
|
||||
.help(translate!("realpath-help-relative-base")),
|
||||
)
|
||||
.arg(
|
||||
Arg::new(ARG_FILES)
|
||||
.action(ArgAction::Append)
|
||||
.required(true)
|
||||
.value_parser(NonEmptyStringValueParser::new())
|
||||
.value_parser(NonEmptyOsStringParser)
|
||||
.value_hint(clap::ValueHint::AnyPath),
|
||||
)
|
||||
}
|
||||
|
|
@ -174,10 +211,10 @@ fn prepare_relative_options(
|
|||
resolve_mode: ResolveMode,
|
||||
) -> UResult<(Option<PathBuf>, Option<PathBuf>)> {
|
||||
let relative_to = matches
|
||||
.get_one::<String>(OPT_RELATIVE_TO)
|
||||
.get_one::<OsString>(OPT_RELATIVE_TO)
|
||||
.map(PathBuf::from);
|
||||
let relative_base = matches
|
||||
.get_one::<String>(OPT_RELATIVE_BASE)
|
||||
.get_one::<OsString>(OPT_RELATIVE_BASE)
|
||||
.map(PathBuf::from);
|
||||
let relative_to = canonicalize_relative_option(relative_to, can_mode, resolve_mode)?;
|
||||
let relative_base = canonicalize_relative_option(relative_base, can_mode, resolve_mode)?;
|
||||
|
|
|
|||
|
|
@ -31,17 +31,17 @@ enum RmError {
|
|||
#[error("{}", translate!("rm-error-missing-operand", "util_name" => uucore::execution_phrase()))]
|
||||
MissingOperand,
|
||||
#[error("{}", translate!("rm-error-cannot-remove-no-such-file", "file" => _0.quote()))]
|
||||
CannotRemoveNoSuchFile(String),
|
||||
CannotRemoveNoSuchFile(OsString),
|
||||
#[error("{}", translate!("rm-error-cannot-remove-permission-denied", "file" => _0.quote()))]
|
||||
CannotRemovePermissionDenied(String),
|
||||
CannotRemovePermissionDenied(OsString),
|
||||
#[error("{}", translate!("rm-error-cannot-remove-is-directory", "file" => _0.quote()))]
|
||||
CannotRemoveIsDirectory(String),
|
||||
CannotRemoveIsDirectory(OsString),
|
||||
#[error("{}", translate!("rm-error-dangerous-recursive-operation"))]
|
||||
DangerousRecursiveOperation,
|
||||
#[error("{}", translate!("rm-error-use-no-preserve-root"))]
|
||||
UseNoPreserveRoot,
|
||||
#[error("{}", translate!("rm-error-refusing-to-remove-directory", "path" => _0))]
|
||||
RefusingToRemoveDirectory(String),
|
||||
#[error("{}", translate!("rm-error-refusing-to-remove-directory", "path" => _0.to_string_lossy()))]
|
||||
RefusingToRemoveDirectory(OsString),
|
||||
}
|
||||
|
||||
impl UError for RmError {}
|
||||
|
|
@ -366,7 +366,7 @@ pub fn remove(files: &[&OsStr], options: &Options) -> bool {
|
|||
} else {
|
||||
show_error!(
|
||||
"{}",
|
||||
RmError::CannotRemoveNoSuchFile(filename.to_string_lossy().to_string())
|
||||
RmError::CannotRemoveNoSuchFile(filename.to_os_string())
|
||||
);
|
||||
true
|
||||
}
|
||||
|
|
@ -542,7 +542,7 @@ fn handle_dir(path: &Path, options: &Options) -> bool {
|
|||
if path_is_current_or_parent_directory(path) {
|
||||
show_error!(
|
||||
"{}",
|
||||
RmError::RefusingToRemoveDirectory(path.display().to_string())
|
||||
RmError::RefusingToRemoveDirectory(path.as_os_str().to_os_string())
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
|
@ -559,7 +559,7 @@ fn handle_dir(path: &Path, options: &Options) -> bool {
|
|||
} else {
|
||||
show_error!(
|
||||
"{}",
|
||||
RmError::CannotRemoveIsDirectory(path.to_string_lossy().to_string())
|
||||
RmError::CannotRemoveIsDirectory(path.as_os_str().to_os_string())
|
||||
);
|
||||
had_err = true;
|
||||
}
|
||||
|
|
@ -580,7 +580,7 @@ fn remove_dir(path: &Path, options: &Options) -> bool {
|
|||
if !options.dir && !options.recursive {
|
||||
show_error!(
|
||||
"{}",
|
||||
RmError::CannotRemoveIsDirectory(path.to_string_lossy().to_string())
|
||||
RmError::CannotRemoveIsDirectory(path.as_os_str().to_os_string())
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
|
@ -621,7 +621,7 @@ fn remove_file(path: &Path, options: &Options) -> bool {
|
|||
// GNU compatibility (rm/fail-eacces.sh)
|
||||
show_error!(
|
||||
"{}",
|
||||
RmError::CannotRemovePermissionDenied(path.to_string_lossy().to_string())
|
||||
RmError::CannotRemovePermissionDenied(path.as_os_str().to_os_string())
|
||||
);
|
||||
} else {
|
||||
show_error!("cannot remove {}: {e}", path.quote());
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ use clap::{Arg, ArgAction, Command};
|
|||
#[cfg(unix)]
|
||||
use libc::S_IWUSR;
|
||||
use rand::{Rng, SeedableRng, rngs::StdRng, seq::SliceRandom};
|
||||
use std::ffi::OsString;
|
||||
use std::fs::{self, File, OpenOptions};
|
||||
use std::io::{self, Read, Seek, Write};
|
||||
#[cfg(unix)]
|
||||
|
|
@ -297,7 +298,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
|||
let zero = matches.get_flag(options::ZERO);
|
||||
let verbose = matches.get_flag(options::VERBOSE);
|
||||
|
||||
for path_str in matches.get_many::<String>(options::FILE).unwrap() {
|
||||
for path_str in matches.get_many::<OsString>(options::FILE).unwrap() {
|
||||
show_if_err!(wipe_file(
|
||||
path_str,
|
||||
iterations,
|
||||
|
|
@ -396,7 +397,8 @@ pub fn uu_app() -> Command {
|
|||
.arg(
|
||||
Arg::new(options::FILE)
|
||||
.action(ArgAction::Append)
|
||||
.value_hint(clap::ValueHint::FilePath),
|
||||
.value_hint(clap::ValueHint::FilePath)
|
||||
.value_parser(clap::value_parser!(OsString)),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -428,7 +430,7 @@ fn pass_name(pass_type: &PassType) -> String {
|
|||
#[allow(clippy::too_many_arguments)]
|
||||
#[allow(clippy::cognitive_complexity)]
|
||||
fn wipe_file(
|
||||
path_str: &str,
|
||||
path_str: &OsString,
|
||||
n_passes: usize,
|
||||
remove_method: RemoveMethod,
|
||||
size: Option<u64>,
|
||||
|
|
@ -605,7 +607,7 @@ fn do_pass(
|
|||
/// Repeatedly renames the file with strings of decreasing length (most likely all 0s)
|
||||
/// Return the path of the file after its last renaming or None in case of an error
|
||||
fn wipe_name(orig_path: &Path, verbose: bool, remove_method: RemoveMethod) -> Option<PathBuf> {
|
||||
let file_name_len = orig_path.file_name().unwrap().to_str().unwrap().len();
|
||||
let file_name_len = orig_path.file_name().unwrap().len();
|
||||
|
||||
let mut last_path = PathBuf::from(orig_path);
|
||||
|
||||
|
|
@ -657,7 +659,7 @@ fn wipe_name(orig_path: &Path, verbose: bool, remove_method: RemoveMethod) -> Op
|
|||
|
||||
fn do_remove(
|
||||
path: &Path,
|
||||
orig_filename: &str,
|
||||
orig_filename: &OsString,
|
||||
verbose: bool,
|
||||
remove_method: RemoveMethod,
|
||||
) -> Result<(), io::Error> {
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ use crate::{
|
|||
OPT_NUMERIC_SUFFIXES_SHORT, OPT_SUFFIX_LENGTH,
|
||||
};
|
||||
use clap::ArgMatches;
|
||||
use std::ffi::{OsStr, OsString};
|
||||
use std::path::is_separator;
|
||||
use thiserror::Error;
|
||||
use uucore::display::Quotable;
|
||||
|
|
@ -76,7 +77,7 @@ pub struct Suffix {
|
|||
length: usize,
|
||||
start: usize,
|
||||
auto_widening: bool,
|
||||
additional: String,
|
||||
additional: OsString,
|
||||
}
|
||||
|
||||
/// An error when parsing suffix parameters from command-line arguments.
|
||||
|
|
@ -219,11 +220,13 @@ impl Suffix {
|
|||
}
|
||||
|
||||
let additional = matches
|
||||
.get_one::<String>(OPT_ADDITIONAL_SUFFIX)
|
||||
.get_one::<OsString>(OPT_ADDITIONAL_SUFFIX)
|
||||
.unwrap()
|
||||
.to_string();
|
||||
if additional.chars().any(is_separator) {
|
||||
return Err(SuffixError::ContainsSeparator(additional));
|
||||
.clone();
|
||||
if additional.to_string_lossy().chars().any(is_separator) {
|
||||
return Err(SuffixError::ContainsSeparator(
|
||||
additional.to_string_lossy().to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let result = Self {
|
||||
|
|
@ -300,14 +303,14 @@ impl Suffix {
|
|||
/// assert_eq!(it.next().unwrap(), "chunk_02.txt");
|
||||
/// ```
|
||||
pub struct FilenameIterator<'a> {
|
||||
prefix: &'a str,
|
||||
additional_suffix: &'a str,
|
||||
prefix: &'a OsStr,
|
||||
additional_suffix: &'a OsStr,
|
||||
number: Number,
|
||||
first_iteration: bool,
|
||||
}
|
||||
|
||||
impl<'a> FilenameIterator<'a> {
|
||||
pub fn new(prefix: &'a str, suffix: &'a Suffix) -> UResult<Self> {
|
||||
pub fn new(prefix: &'a OsStr, suffix: &'a Suffix) -> UResult<Self> {
|
||||
let radix = suffix.stype.radix();
|
||||
let number = if suffix.auto_widening {
|
||||
Number::DynamicWidth(DynamicWidthNumber::new(radix, suffix.start))
|
||||
|
|
@ -321,7 +324,7 @@ impl<'a> FilenameIterator<'a> {
|
|||
})?,
|
||||
)
|
||||
};
|
||||
let additional_suffix = suffix.additional.as_str();
|
||||
let additional_suffix = &suffix.additional;
|
||||
|
||||
Ok(FilenameIterator {
|
||||
prefix,
|
||||
|
|
@ -345,7 +348,9 @@ impl Iterator for FilenameIterator<'_> {
|
|||
// struct parameters unchanged.
|
||||
Some(format!(
|
||||
"{}{}{}",
|
||||
self.prefix, self.number, self.additional_suffix
|
||||
self.prefix.to_string_lossy(),
|
||||
self.number,
|
||||
self.additional_suffix.to_string_lossy()
|
||||
))
|
||||
}
|
||||
}
|
||||
|
|
@ -364,14 +369,14 @@ mod tests {
|
|||
length: 2,
|
||||
start: 0,
|
||||
auto_widening: false,
|
||||
additional: ".txt".to_string(),
|
||||
additional: ".txt".into(),
|
||||
};
|
||||
let mut it = FilenameIterator::new("chunk_", &suffix).unwrap();
|
||||
let mut it = FilenameIterator::new(std::ffi::OsStr::new("chunk_"), &suffix).unwrap();
|
||||
assert_eq!(it.next().unwrap(), "chunk_aa.txt");
|
||||
assert_eq!(it.next().unwrap(), "chunk_ab.txt");
|
||||
assert_eq!(it.next().unwrap(), "chunk_ac.txt");
|
||||
|
||||
let mut it = FilenameIterator::new("chunk_", &suffix).unwrap();
|
||||
let mut it = FilenameIterator::new(std::ffi::OsStr::new("chunk_"), &suffix).unwrap();
|
||||
assert_eq!(it.nth(26 * 26 - 1).unwrap(), "chunk_zz.txt");
|
||||
assert_eq!(it.next(), None);
|
||||
}
|
||||
|
|
@ -383,14 +388,14 @@ mod tests {
|
|||
length: 2,
|
||||
start: 0,
|
||||
auto_widening: false,
|
||||
additional: ".txt".to_string(),
|
||||
additional: ".txt".into(),
|
||||
};
|
||||
let mut it = FilenameIterator::new("chunk_", &suffix).unwrap();
|
||||
let mut it = FilenameIterator::new(std::ffi::OsStr::new("chunk_"), &suffix).unwrap();
|
||||
assert_eq!(it.next().unwrap(), "chunk_00.txt");
|
||||
assert_eq!(it.next().unwrap(), "chunk_01.txt");
|
||||
assert_eq!(it.next().unwrap(), "chunk_02.txt");
|
||||
|
||||
let mut it = FilenameIterator::new("chunk_", &suffix).unwrap();
|
||||
let mut it = FilenameIterator::new(std::ffi::OsStr::new("chunk_"), &suffix).unwrap();
|
||||
assert_eq!(it.nth(10 * 10 - 1).unwrap(), "chunk_99.txt");
|
||||
assert_eq!(it.next(), None);
|
||||
}
|
||||
|
|
@ -402,14 +407,14 @@ mod tests {
|
|||
length: 2,
|
||||
start: 0,
|
||||
auto_widening: true,
|
||||
additional: ".txt".to_string(),
|
||||
additional: ".txt".into(),
|
||||
};
|
||||
let mut it = FilenameIterator::new("chunk_", &suffix).unwrap();
|
||||
let mut it = FilenameIterator::new(std::ffi::OsStr::new("chunk_"), &suffix).unwrap();
|
||||
assert_eq!(it.next().unwrap(), "chunk_aa.txt");
|
||||
assert_eq!(it.next().unwrap(), "chunk_ab.txt");
|
||||
assert_eq!(it.next().unwrap(), "chunk_ac.txt");
|
||||
|
||||
let mut it = FilenameIterator::new("chunk_", &suffix).unwrap();
|
||||
let mut it = FilenameIterator::new(std::ffi::OsStr::new("chunk_"), &suffix).unwrap();
|
||||
assert_eq!(it.nth(26 * 25 - 1).unwrap(), "chunk_yz.txt");
|
||||
assert_eq!(it.next().unwrap(), "chunk_zaaa.txt");
|
||||
assert_eq!(it.next().unwrap(), "chunk_zaab.txt");
|
||||
|
|
@ -422,14 +427,14 @@ mod tests {
|
|||
length: 2,
|
||||
start: 0,
|
||||
auto_widening: true,
|
||||
additional: ".txt".to_string(),
|
||||
additional: ".txt".into(),
|
||||
};
|
||||
let mut it = FilenameIterator::new("chunk_", &suffix).unwrap();
|
||||
let mut it = FilenameIterator::new(std::ffi::OsStr::new("chunk_"), &suffix).unwrap();
|
||||
assert_eq!(it.next().unwrap(), "chunk_00.txt");
|
||||
assert_eq!(it.next().unwrap(), "chunk_01.txt");
|
||||
assert_eq!(it.next().unwrap(), "chunk_02.txt");
|
||||
|
||||
let mut it = FilenameIterator::new("chunk_", &suffix).unwrap();
|
||||
let mut it = FilenameIterator::new(std::ffi::OsStr::new("chunk_"), &suffix).unwrap();
|
||||
assert_eq!(it.nth(10 * 9 - 1).unwrap(), "chunk_89.txt");
|
||||
assert_eq!(it.next().unwrap(), "chunk_9000.txt");
|
||||
assert_eq!(it.next().unwrap(), "chunk_9001.txt");
|
||||
|
|
@ -442,9 +447,9 @@ mod tests {
|
|||
length: 2,
|
||||
start: 5,
|
||||
auto_widening: true,
|
||||
additional: ".txt".to_string(),
|
||||
additional: ".txt".into(),
|
||||
};
|
||||
let mut it = FilenameIterator::new("chunk_", &suffix).unwrap();
|
||||
let mut it = FilenameIterator::new(std::ffi::OsStr::new("chunk_"), &suffix).unwrap();
|
||||
assert_eq!(it.next().unwrap(), "chunk_05.txt");
|
||||
assert_eq!(it.next().unwrap(), "chunk_06.txt");
|
||||
assert_eq!(it.next().unwrap(), "chunk_07.txt");
|
||||
|
|
@ -457,9 +462,9 @@ mod tests {
|
|||
length: 2,
|
||||
start: 9,
|
||||
auto_widening: true,
|
||||
additional: ".txt".to_string(),
|
||||
additional: ".txt".into(),
|
||||
};
|
||||
let mut it = FilenameIterator::new("chunk_", &suffix).unwrap();
|
||||
let mut it = FilenameIterator::new(std::ffi::OsStr::new("chunk_"), &suffix).unwrap();
|
||||
assert_eq!(it.next().unwrap(), "chunk_09.txt");
|
||||
assert_eq!(it.next().unwrap(), "chunk_0a.txt");
|
||||
assert_eq!(it.next().unwrap(), "chunk_0b.txt");
|
||||
|
|
@ -472,9 +477,9 @@ mod tests {
|
|||
length: 3,
|
||||
start: 999,
|
||||
auto_widening: false,
|
||||
additional: ".txt".to_string(),
|
||||
additional: ".txt".into(),
|
||||
};
|
||||
let mut it = FilenameIterator::new("chunk_", &suffix).unwrap();
|
||||
let mut it = FilenameIterator::new(std::ffi::OsStr::new("chunk_"), &suffix).unwrap();
|
||||
assert_eq!(it.next().unwrap(), "chunk_999.txt");
|
||||
assert!(it.next().is_none());
|
||||
|
||||
|
|
@ -483,9 +488,9 @@ mod tests {
|
|||
length: 3,
|
||||
start: 1000,
|
||||
auto_widening: false,
|
||||
additional: ".txt".to_string(),
|
||||
additional: ".txt".into(),
|
||||
};
|
||||
let it = FilenameIterator::new("chunk_", &suffix);
|
||||
let it = FilenameIterator::new(std::ffi::OsStr::new("chunk_"), &suffix);
|
||||
assert!(it.is_err());
|
||||
|
||||
let suffix = Suffix {
|
||||
|
|
@ -493,9 +498,9 @@ mod tests {
|
|||
length: 3,
|
||||
start: 0xfff,
|
||||
auto_widening: false,
|
||||
additional: ".txt".to_string(),
|
||||
additional: ".txt".into(),
|
||||
};
|
||||
let mut it = FilenameIterator::new("chunk_", &suffix).unwrap();
|
||||
let mut it = FilenameIterator::new(std::ffi::OsStr::new("chunk_"), &suffix).unwrap();
|
||||
assert_eq!(it.next().unwrap(), "chunk_fff.txt");
|
||||
assert!(it.next().is_none());
|
||||
|
||||
|
|
@ -504,9 +509,9 @@ mod tests {
|
|||
length: 3,
|
||||
start: 0x1000,
|
||||
auto_widening: false,
|
||||
additional: ".txt".to_string(),
|
||||
additional: ".txt".into(),
|
||||
};
|
||||
let it = FilenameIterator::new("chunk_", &suffix);
|
||||
let it = FilenameIterator::new(std::ffi::OsStr::new("chunk_"), &suffix);
|
||||
assert!(it.is_err());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
use std::env;
|
||||
use std::ffi::OsStr;
|
||||
use std::io::Write;
|
||||
use std::io::{BufWriter, Error, Result};
|
||||
use std::path::Path;
|
||||
|
|
@ -163,12 +164,12 @@ pub fn instantiate_current_writer(
|
|||
}
|
||||
}
|
||||
|
||||
pub fn paths_refer_to_same_file(p1: &str, p2: &str) -> bool {
|
||||
pub fn paths_refer_to_same_file(p1: &OsStr, p2: &OsStr) -> bool {
|
||||
// We have to take symlinks and relative paths into account.
|
||||
let p1 = if p1 == "-" {
|
||||
FileInformation::from_file(&std::io::stdin())
|
||||
} else {
|
||||
FileInformation::from_path(Path::new(&p1), true)
|
||||
FileInformation::from_path(Path::new(p1), true)
|
||||
};
|
||||
fs::infos_refer_to_same_file(p1, FileInformation::from_path(Path::new(p2), true))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
//
|
||||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
use std::ffi::OsStr;
|
||||
use std::io::Write;
|
||||
use std::io::{BufWriter, Error, Result};
|
||||
use std::path::Path;
|
||||
|
|
@ -39,7 +40,7 @@ pub fn instantiate_current_writer(
|
|||
Ok(BufWriter::new(Box::new(file) as Box<dyn Write>))
|
||||
}
|
||||
|
||||
pub fn paths_refer_to_same_file(p1: &str, p2: &str) -> bool {
|
||||
pub fn paths_refer_to_same_file(p1: &OsStr, p2: &OsStr) -> bool {
|
||||
// Windows doesn't support many of the unix ways of paths being equals
|
||||
fs::paths_refer_to_same_file(Path::new(p1), Path::new(p2), true)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -274,6 +274,7 @@ pub fn uu_app() -> Command {
|
|||
.allow_hyphen_values(true)
|
||||
.value_name("SUFFIX")
|
||||
.default_value("")
|
||||
.value_parser(clap::value_parser!(OsString))
|
||||
.help(translate!("split-help-additional-suffix")),
|
||||
)
|
||||
.arg(
|
||||
|
|
@ -375,9 +376,14 @@ pub fn uu_app() -> Command {
|
|||
.arg(
|
||||
Arg::new(ARG_INPUT)
|
||||
.default_value("-")
|
||||
.value_hint(ValueHint::FilePath),
|
||||
.value_hint(ValueHint::FilePath)
|
||||
.value_parser(clap::value_parser!(OsString)),
|
||||
)
|
||||
.arg(
|
||||
Arg::new(ARG_PREFIX)
|
||||
.default_value("x")
|
||||
.value_parser(clap::value_parser!(OsString)),
|
||||
)
|
||||
.arg(Arg::new(ARG_PREFIX).default_value("x"))
|
||||
}
|
||||
|
||||
/// Parameters that control how a file gets split.
|
||||
|
|
@ -385,9 +391,9 @@ pub fn uu_app() -> Command {
|
|||
/// You can convert an [`ArgMatches`] instance into a [`Settings`]
|
||||
/// instance by calling [`Settings::from`].
|
||||
struct Settings {
|
||||
prefix: String,
|
||||
prefix: OsString,
|
||||
suffix: Suffix,
|
||||
input: String,
|
||||
input: OsString,
|
||||
/// When supplied, a shell command to output to instead of xaa, xab …
|
||||
filter: Option<String>,
|
||||
strategy: Strategy,
|
||||
|
|
@ -489,9 +495,9 @@ impl Settings {
|
|||
};
|
||||
|
||||
let result = Self {
|
||||
prefix: matches.get_one::<String>(ARG_PREFIX).unwrap().clone(),
|
||||
prefix: matches.get_one::<OsString>(ARG_PREFIX).unwrap().clone(),
|
||||
suffix,
|
||||
input: matches.get_one::<String>(ARG_INPUT).unwrap().clone(),
|
||||
input: matches.get_one::<OsString>(ARG_INPUT).unwrap().clone(),
|
||||
filter: matches.get_one::<String>(OPT_FILTER).cloned(),
|
||||
strategy,
|
||||
verbose: matches.value_source(OPT_VERBOSE) == Some(ValueSource::CommandLine),
|
||||
|
|
@ -529,7 +535,7 @@ impl Settings {
|
|||
filename: &str,
|
||||
is_new: bool,
|
||||
) -> io::Result<BufWriter<Box<dyn Write>>> {
|
||||
if platform::paths_refer_to_same_file(&self.input, filename) {
|
||||
if platform::paths_refer_to_same_file(&self.input, filename.as_ref()) {
|
||||
return Err(io::Error::other(
|
||||
translate!("split-error-would-overwrite-input", "file" => filename.quote()),
|
||||
));
|
||||
|
|
@ -598,7 +604,7 @@ fn custom_write_all<T: Write>(
|
|||
///
|
||||
/// Note: The `buf` might end up with either partial or entire input content.
|
||||
fn get_input_size<R>(
|
||||
input: &String,
|
||||
input: &OsString,
|
||||
reader: &mut R,
|
||||
buf: &mut Vec<u8>,
|
||||
io_blksize: Option<u64>,
|
||||
|
|
@ -633,12 +639,12 @@ where
|
|||
// STDIN stream that did not fit all content into a buffer
|
||||
// Most likely continuous/infinite input stream
|
||||
Err(io::Error::other(
|
||||
translate!("split-error-cannot-determine-input-size", "input" => input),
|
||||
translate!("split-error-cannot-determine-input-size", "input" => input.to_string_lossy()),
|
||||
))
|
||||
} else {
|
||||
// Could be that file size is larger than set read limit
|
||||
// Get the file size from filesystem metadata
|
||||
let metadata = metadata(input)?;
|
||||
let metadata = metadata(Path::new(input))?;
|
||||
let metadata_size = metadata.len();
|
||||
if num_bytes <= metadata_size {
|
||||
Ok(metadata_size)
|
||||
|
|
@ -658,7 +664,7 @@ where
|
|||
// TODO It might be possible to do more here
|
||||
// to address all possible file types and edge cases
|
||||
Err(io::Error::other(
|
||||
translate!("split-error-cannot-determine-file-size", "input" => input),
|
||||
translate!("split-error-cannot-determine-file-size", "input" => input.to_string_lossy()),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
|
@ -1167,7 +1173,7 @@ where
|
|||
Err(error) => {
|
||||
return Err(USimpleError::new(
|
||||
1,
|
||||
translate!("split-error-cannot-read-from-input", "input" => settings.input.clone(), "error" => error),
|
||||
translate!("split-error-cannot-read-from-input", "input" => settings.input.to_string_lossy(), "error" => error),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
@ -1529,7 +1535,7 @@ fn split(settings: &Settings) -> UResult<()> {
|
|||
Box::new(stdin()) as Box<dyn Read>
|
||||
} else {
|
||||
let r = File::open(Path::new(&settings.input)).map_err_context(
|
||||
|| translate!("split-error-cannot-open-for-reading", "file" => settings.input.quote()),
|
||||
|| translate!("split-error-cannot-open-for-reading", "file" => settings.input.to_string_lossy().quote()),
|
||||
)?;
|
||||
Box::new(r) as Box<dyn Read>
|
||||
};
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
// spell-checker:ignore (ToDO) tempdir dyld dylib optgrps libstdbuf
|
||||
|
||||
use clap::{Arg, ArgAction, ArgMatches, Command};
|
||||
use std::ffi::OsString;
|
||||
use std::os::unix::process::ExitStatusExt;
|
||||
use std::path::PathBuf;
|
||||
use std::process;
|
||||
|
|
@ -183,9 +184,9 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
|||
let options =
|
||||
ProgramOptions::try_from(&matches).map_err(|e| UUsageError::new(125, e.to_string()))?;
|
||||
|
||||
let mut command_values = matches.get_many::<String>(options::COMMAND).unwrap();
|
||||
let mut command_values = matches.get_many::<OsString>(options::COMMAND).unwrap();
|
||||
let mut command = process::Command::new(command_values.next().unwrap());
|
||||
let command_params: Vec<&str> = command_values.map(|s| s.as_ref()).collect();
|
||||
let command_params: Vec<&OsString> = command_values.collect();
|
||||
|
||||
let tmp_dir = tempdir().unwrap();
|
||||
let (preload_env, libstdbuf) = get_preload_env(&tmp_dir)?;
|
||||
|
|
@ -269,6 +270,7 @@ pub fn uu_app() -> Command {
|
|||
.action(ArgAction::Append)
|
||||
.hide(true)
|
||||
.required(true)
|
||||
.value_hint(clap::ValueHint::CommandName),
|
||||
.value_hint(clap::ValueHint::CommandName)
|
||||
.value_parser(clap::value_parser!(OsString)),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
// spell-checker:ignore (ToDO) sysv
|
||||
|
||||
use clap::{Arg, ArgAction, Command};
|
||||
use std::ffi::OsString;
|
||||
use std::fs::File;
|
||||
use std::io::{ErrorKind, Read, Write, stdin, stdout};
|
||||
use std::path::Path;
|
||||
|
|
@ -67,27 +68,26 @@ fn sysv_sum(mut reader: impl Read) -> std::io::Result<(usize, u16)> {
|
|||
Ok((blocks_read, ret as u16))
|
||||
}
|
||||
|
||||
fn open(name: &str) -> UResult<Box<dyn Read>> {
|
||||
match name {
|
||||
"-" => Ok(Box::new(stdin()) as Box<dyn Read>),
|
||||
_ => {
|
||||
let path = &Path::new(name);
|
||||
if path.is_dir() {
|
||||
return Err(USimpleError::new(
|
||||
2,
|
||||
translate!("sum-error-is-directory", "name" => name.maybe_quote()),
|
||||
));
|
||||
}
|
||||
// Silent the warning as we want to the error message
|
||||
if path.metadata().is_err() {
|
||||
return Err(USimpleError::new(
|
||||
2,
|
||||
translate!("sum-error-no-such-file-or-directory", "name" => name.maybe_quote()),
|
||||
));
|
||||
}
|
||||
let f = File::open(path).map_err_context(String::new)?;
|
||||
Ok(Box::new(f) as Box<dyn Read>)
|
||||
fn open(name: &OsString) -> UResult<Box<dyn Read>> {
|
||||
if name == "-" {
|
||||
Ok(Box::new(stdin()) as Box<dyn Read>)
|
||||
} else {
|
||||
let path = Path::new(name);
|
||||
if path.is_dir() {
|
||||
return Err(USimpleError::new(
|
||||
2,
|
||||
translate!("sum-error-is-directory", "name" => name.to_string_lossy().maybe_quote()),
|
||||
));
|
||||
}
|
||||
// Silent the warning as we want to the error message
|
||||
if path.metadata().is_err() {
|
||||
return Err(USimpleError::new(
|
||||
2,
|
||||
translate!("sum-error-no-such-file-or-directory", "name" => name.to_string_lossy().maybe_quote()),
|
||||
));
|
||||
}
|
||||
let f = File::open(path).map_err_context(String::new)?;
|
||||
Ok(Box::new(f) as Box<dyn Read>)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -101,9 +101,9 @@ mod options {
|
|||
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||
let matches = uu_app().get_matches_from_localized(args);
|
||||
|
||||
let files: Vec<String> = match matches.get_many::<String>(options::FILE) {
|
||||
let files: Vec<OsString> = match matches.get_many::<OsString>(options::FILE) {
|
||||
Some(v) => v.cloned().collect(),
|
||||
None => vec!["-".to_owned()],
|
||||
None => vec![OsString::from("-")],
|
||||
};
|
||||
|
||||
let sysv = matches.get_flag(options::SYSTEM_V_COMPATIBLE);
|
||||
|
|
@ -127,7 +127,11 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
|||
|
||||
let mut stdout = stdout().lock();
|
||||
if print_names {
|
||||
writeln!(stdout, "{sum:0width$} {blocks:width$} {file}")?;
|
||||
writeln!(
|
||||
stdout,
|
||||
"{sum:0width$} {blocks:width$} {}",
|
||||
file.to_string_lossy()
|
||||
)?;
|
||||
} else {
|
||||
writeln!(stdout, "{sum:0width$} {blocks:width$}")?;
|
||||
}
|
||||
|
|
@ -146,7 +150,8 @@ pub fn uu_app() -> Command {
|
|||
Arg::new(options::FILE)
|
||||
.action(ArgAction::Append)
|
||||
.hide(true)
|
||||
.value_hint(clap::ValueHint::FilePath),
|
||||
.value_hint(clap::ValueHint::FilePath)
|
||||
.value_parser(clap::value_parser!(OsString)),
|
||||
)
|
||||
.arg(
|
||||
Arg::new(options::BSD_COMPATIBLE)
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
// file that was distributed with this source code.
|
||||
//! Errors returned by tac during processing of a file.
|
||||
|
||||
use std::ffi::OsString;
|
||||
use thiserror::Error;
|
||||
use uucore::display::Quotable;
|
||||
use uucore::error::UError;
|
||||
|
|
@ -16,16 +17,16 @@ pub enum TacError {
|
|||
InvalidRegex(regex::Error),
|
||||
/// An argument to tac is invalid.
|
||||
#[error("{}", translate!("tac-error-invalid-argument", "argument" => .0.maybe_quote()))]
|
||||
InvalidArgument(String),
|
||||
InvalidArgument(OsString),
|
||||
/// The specified file is not found on the filesystem.
|
||||
#[error("{}", translate!("tac-error-file-not-found", "filename" => .0.quote()))]
|
||||
FileNotFound(String),
|
||||
FileNotFound(OsString),
|
||||
/// An error reading the contents of a file or stdin.
|
||||
///
|
||||
/// The parameters are the name of the file and the underlying
|
||||
/// [`std::io::Error`] that caused this error.
|
||||
#[error("{}", translate!("tac-error-read-error", "filename" => .0.clone(), "error" => .1))]
|
||||
ReadError(String, std::io::Error),
|
||||
#[error("{}", translate!("tac-error-read-error", "filename" => .0.quote(), "error" => .1))]
|
||||
ReadError(OsString, std::io::Error),
|
||||
/// An error writing the (reversed) contents of a file or stdin.
|
||||
///
|
||||
/// The parameter is the underlying [`std::io::Error`] that caused
|
||||
|
|
|
|||
|
|
@ -9,12 +9,12 @@ mod error;
|
|||
use clap::{Arg, ArgAction, Command};
|
||||
use memchr::memmem;
|
||||
use memmap2::Mmap;
|
||||
use std::ffi::OsString;
|
||||
use std::io::{BufWriter, Read, Write, stdin, stdout};
|
||||
use std::{
|
||||
fs::{File, read},
|
||||
path::Path,
|
||||
};
|
||||
use uucore::display::Quotable;
|
||||
use uucore::error::UError;
|
||||
use uucore::error::UResult;
|
||||
use uucore::{format_usage, show};
|
||||
|
|
@ -46,9 +46,9 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
|||
raw_separator
|
||||
};
|
||||
|
||||
let files: Vec<&str> = match matches.get_many::<String>(options::FILE) {
|
||||
Some(v) => v.map(|s| s.as_str()).collect(),
|
||||
None => vec!["-"],
|
||||
let files: Vec<OsString> = match matches.get_many::<OsString>(options::FILE) {
|
||||
Some(v) => v.cloned().collect(),
|
||||
None => vec![OsString::from("-")],
|
||||
};
|
||||
|
||||
tac(&files, before, regex, separator)
|
||||
|
|
@ -86,6 +86,7 @@ pub fn uu_app() -> Command {
|
|||
Arg::new(options::FILE)
|
||||
.hide(true)
|
||||
.action(ArgAction::Append)
|
||||
.value_parser(clap::value_parser!(OsString))
|
||||
.value_hint(clap::ValueHint::FilePath),
|
||||
)
|
||||
}
|
||||
|
|
@ -221,7 +222,7 @@ fn buffer_tac(data: &[u8], before: bool, separator: &str) -> std::io::Result<()>
|
|||
}
|
||||
|
||||
#[allow(clippy::cognitive_complexity)]
|
||||
fn tac(filenames: &[&str], before: bool, regex: bool, separator: &str) -> UResult<()> {
|
||||
fn tac(filenames: &[OsString], before: bool, regex: bool, separator: &str) -> UResult<()> {
|
||||
// Compile the regular expression pattern if it is provided.
|
||||
let maybe_pattern = if regex {
|
||||
match regex::bytes::Regex::new(separator) {
|
||||
|
|
@ -232,7 +233,7 @@ fn tac(filenames: &[&str], before: bool, regex: bool, separator: &str) -> UResul
|
|||
None
|
||||
};
|
||||
|
||||
for &filename in filenames {
|
||||
for filename in filenames {
|
||||
let mmap;
|
||||
let buf;
|
||||
|
||||
|
|
@ -243,7 +244,7 @@ fn tac(filenames: &[&str], before: bool, regex: bool, separator: &str) -> UResul
|
|||
} else {
|
||||
let mut buf1 = Vec::new();
|
||||
if let Err(e) = stdin().read_to_end(&mut buf1) {
|
||||
let e: Box<dyn UError> = TacError::ReadError("stdin".to_string(), e).into();
|
||||
let e: Box<dyn UError> = TacError::ReadError(OsString::from("stdin"), e).into();
|
||||
show!(e);
|
||||
continue;
|
||||
}
|
||||
|
|
@ -253,13 +254,13 @@ fn tac(filenames: &[&str], before: bool, regex: bool, separator: &str) -> UResul
|
|||
} else {
|
||||
let path = Path::new(filename);
|
||||
if path.is_dir() {
|
||||
let e: Box<dyn UError> = TacError::InvalidArgument(String::from(filename)).into();
|
||||
let e: Box<dyn UError> = TacError::InvalidArgument(filename.clone()).into();
|
||||
show!(e);
|
||||
continue;
|
||||
}
|
||||
|
||||
if path.metadata().is_err() {
|
||||
let e: Box<dyn UError> = TacError::FileNotFound(String::from(filename)).into();
|
||||
let e: Box<dyn UError> = TacError::FileNotFound(filename.clone()).into();
|
||||
show!(e);
|
||||
continue;
|
||||
}
|
||||
|
|
@ -274,8 +275,7 @@ fn tac(filenames: &[&str], before: bool, regex: bool, separator: &str) -> UResul
|
|||
&buf
|
||||
}
|
||||
Err(e) => {
|
||||
let s = format!("{}", filename.quote());
|
||||
let e: Box<dyn UError> = TacError::ReadError(s.to_string(), e).into();
|
||||
let e: Box<dyn UError> = TacError::ReadError(filename.clone(), e).into();
|
||||
show!(e);
|
||||
continue;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
// cSpell:ignore POLLERR POLLRDBAND pfds revents
|
||||
|
||||
use clap::{Arg, ArgAction, Command, builder::PossibleValue};
|
||||
use std::ffi::OsString;
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::{Error, ErrorKind, Read, Result, Write, stdin, stdout};
|
||||
use std::path::PathBuf;
|
||||
|
|
@ -34,7 +35,7 @@ struct Options {
|
|||
append: bool,
|
||||
ignore_interrupts: bool,
|
||||
ignore_pipe_errors: bool,
|
||||
files: Vec<String>,
|
||||
files: Vec<OsString>,
|
||||
output_error: Option<OutputErrorMode>,
|
||||
}
|
||||
|
||||
|
|
@ -77,8 +78,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
|||
};
|
||||
|
||||
let files = matches
|
||||
.get_many::<String>(options::FILE)
|
||||
.map(|v| v.map(ToString::to_string).collect())
|
||||
.get_many::<OsString>(options::FILE)
|
||||
.map(|v| v.cloned().collect())
|
||||
.unwrap_or_default();
|
||||
|
||||
let options = Options {
|
||||
|
|
@ -127,7 +128,8 @@ pub fn uu_app() -> Command {
|
|||
.arg(
|
||||
Arg::new(options::FILE)
|
||||
.action(ArgAction::Append)
|
||||
.value_hint(clap::ValueHint::FilePath),
|
||||
.value_hint(clap::ValueHint::FilePath)
|
||||
.value_parser(clap::value_parser!(OsString)),
|
||||
)
|
||||
.arg(
|
||||
Arg::new(options::IGNORE_PIPE_ERRORS)
|
||||
|
|
@ -252,7 +254,7 @@ fn copy(mut input: impl Read, mut output: impl Write) -> Result<usize> {
|
|||
/// If that error should lead to program termination, this function returns Some(Err()),
|
||||
/// otherwise it returns None.
|
||||
fn open(
|
||||
name: &str,
|
||||
name: &OsString,
|
||||
append: bool,
|
||||
output_error: Option<&OutputErrorMode>,
|
||||
) -> Option<Result<NamedWriter>> {
|
||||
|
|
@ -266,10 +268,10 @@ fn open(
|
|||
match mode.write(true).create(true).open(path.as_path()) {
|
||||
Ok(file) => Some(Ok(NamedWriter {
|
||||
inner: Box::new(file),
|
||||
name: name.to_owned(),
|
||||
name: name.to_string_lossy().to_string(),
|
||||
})),
|
||||
Err(f) => {
|
||||
show_error!("{}: {f}", name.maybe_quote());
|
||||
show_error!("{}: {f}", name.to_string_lossy().maybe_quote());
|
||||
match output_error {
|
||||
Some(OutputErrorMode::Exit | OutputErrorMode::ExitNoPipe) => Some(Err(f)),
|
||||
_ => None,
|
||||
|
|
|
|||
|
|
@ -157,20 +157,20 @@ fn is_first_filename_timestamp(
|
|||
reference: Option<&OsString>,
|
||||
date: Option<&str>,
|
||||
timestamp: Option<&str>,
|
||||
files: &[&String],
|
||||
files: &[&OsString],
|
||||
) -> bool {
|
||||
if timestamp.is_none()
|
||||
timestamp.is_none()
|
||||
&& reference.is_none()
|
||||
&& date.is_none()
|
||||
&& files.len() >= 2
|
||||
// env check is last as the slowest op
|
||||
&& matches!(std::env::var("_POSIX2_VERSION").as_deref(), Ok("199209"))
|
||||
{
|
||||
let s = files[0];
|
||||
all_digits(s) && (s.len() == 8 || (s.len() == 10 && (69..=99).contains(&get_year(s))))
|
||||
} else {
|
||||
false
|
||||
}
|
||||
&& files[0].to_str().is_some_and(is_timestamp)
|
||||
}
|
||||
|
||||
// Check if string is a valid POSIX timestamp (8 digits or 10 digits with valid year range)
|
||||
fn is_timestamp(s: &str) -> bool {
|
||||
all_digits(s) && (s.len() == 8 || (s.len() == 10 && (69..=99).contains(&get_year(s))))
|
||||
}
|
||||
|
||||
/// Cycle the last two characters to the beginning of the string.
|
||||
|
|
@ -189,8 +189,8 @@ fn shr2(s: &str) -> String {
|
|||
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||
let matches = uu_app().get_matches_from_localized(args);
|
||||
|
||||
let mut filenames: Vec<&String> = matches
|
||||
.get_many::<String>(ARG_FILES)
|
||||
let mut filenames: Vec<&OsString> = matches
|
||||
.get_many::<OsString>(ARG_FILES)
|
||||
.ok_or_else(|| {
|
||||
USimpleError::new(
|
||||
1,
|
||||
|
|
@ -211,10 +211,11 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
|||
.map(|t| t.to_owned());
|
||||
|
||||
if is_first_filename_timestamp(reference, date.as_deref(), timestamp.as_deref(), &filenames) {
|
||||
timestamp = if filenames[0].len() == 10 {
|
||||
Some(shr2(filenames[0]))
|
||||
let first_file = filenames[0].to_str().unwrap();
|
||||
timestamp = if first_file.len() == 10 {
|
||||
Some(shr2(first_file))
|
||||
} else {
|
||||
Some(filenames[0].to_string())
|
||||
Some(first_file.to_string())
|
||||
};
|
||||
filenames = filenames[1..].to_vec();
|
||||
}
|
||||
|
|
@ -338,6 +339,7 @@ pub fn uu_app() -> Command {
|
|||
Arg::new(ARG_FILES)
|
||||
.action(ArgAction::Append)
|
||||
.num_args(1..)
|
||||
.value_parser(clap::value_parser!(OsString))
|
||||
.value_hint(clap::ValueHint::AnyPath),
|
||||
)
|
||||
.group(
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
//spell-checker:ignore TAOCP indegree
|
||||
use clap::{Arg, Command};
|
||||
use std::collections::{HashMap, HashSet, VecDeque};
|
||||
use std::ffi::OsString;
|
||||
use std::path::Path;
|
||||
use thiserror::Error;
|
||||
use uucore::display::Quotable;
|
||||
|
|
@ -47,26 +48,26 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
|||
let matches = uu_app().get_matches_from_localized(args);
|
||||
|
||||
let input = matches
|
||||
.get_one::<String>(options::FILE)
|
||||
.get_one::<OsString>(options::FILE)
|
||||
.expect("Value is required by clap");
|
||||
|
||||
let data = if input == "-" {
|
||||
let stdin = std::io::stdin();
|
||||
std::io::read_to_string(stdin)?
|
||||
} else {
|
||||
let path = Path::new(&input);
|
||||
let path = Path::new(input);
|
||||
if path.is_dir() {
|
||||
return Err(TsortError::IsDir(input.to_string()).into());
|
||||
return Err(TsortError::IsDir(input.to_string_lossy().to_string()).into());
|
||||
}
|
||||
std::fs::read_to_string(path)?
|
||||
};
|
||||
|
||||
// Create the directed graph from pairs of tokens in the input data.
|
||||
let mut g = Graph::new(input.clone());
|
||||
let mut g = Graph::new(input.to_string_lossy().to_string());
|
||||
for ab in data.split_whitespace().collect::<Vec<&str>>().chunks(2) {
|
||||
match ab {
|
||||
[a, b] => g.add_edge(a, b),
|
||||
_ => return Err(TsortError::NumTokensOdd(input.to_string()).into()),
|
||||
_ => return Err(TsortError::NumTokensOdd(input.to_string_lossy().to_string()).into()),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -85,6 +86,7 @@ pub fn uu_app() -> Command {
|
|||
Arg::new(options::FILE)
|
||||
.default_value("-")
|
||||
.hide(true)
|
||||
.value_parser(clap::value_parser!(OsString))
|
||||
.value_hint(clap::ValueHint::FilePath),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ use clap::{Arg, ArgMatches, Command};
|
|||
|
||||
use libc::{gid_t, uid_t};
|
||||
use options::traverse;
|
||||
use std::ffi::OsString;
|
||||
use walkdir::WalkDir;
|
||||
|
||||
use std::ffi::CString;
|
||||
|
|
@ -193,7 +194,7 @@ pub struct ChownExecutor {
|
|||
pub traverse_symlinks: TraverseSymlinks,
|
||||
pub verbosity: Verbosity,
|
||||
pub filter: IfFrom,
|
||||
pub files: Vec<String>,
|
||||
pub files: Vec<OsString>,
|
||||
pub recursive: bool,
|
||||
pub preserve_root: bool,
|
||||
pub dereference: bool,
|
||||
|
|
@ -597,13 +598,14 @@ pub fn chown_base(
|
|||
.value_hint(clap::ValueHint::FilePath)
|
||||
.action(clap::ArgAction::Append)
|
||||
.required(true)
|
||||
.num_args(1..),
|
||||
.num_args(1..)
|
||||
.value_parser(clap::value_parser!(std::ffi::OsString)),
|
||||
);
|
||||
let matches = command.try_get_matches_from(args)?;
|
||||
|
||||
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();
|
||||
|
||||
let preserve_root = matches.get_flag(options::preserve_root::PRESERVE);
|
||||
|
|
|
|||
|
|
@ -2,9 +2,25 @@
|
|||
//
|
||||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
#[cfg(target_os = "linux")]
|
||||
use uutests::at_and_ucmd;
|
||||
use uutests::new_ucmd;
|
||||
use uutests::util::TestScenario;
|
||||
|
||||
#[test]
|
||||
#[cfg(target_os = "linux")]
|
||||
fn test_base64_non_utf8_paths() {
|
||||
use std::os::unix::ffi::OsStringExt;
|
||||
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").unwrap();
|
||||
|
||||
ucmd.arg(&filename)
|
||||
.succeeds()
|
||||
.stdout_is("aGVsbG8gd29ybGQ=\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_encode() {
|
||||
let input = "hello, world!";
|
||||
|
|
|
|||
|
|
@ -747,6 +747,30 @@ fn test_write_fast_read_error() {
|
|||
ucmd.arg("foo").fails().stderr_contains("Permission denied");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_os = "linux")]
|
||||
fn test_cat_non_utf8_paths() {
|
||||
use std::ffi::OsStr;
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
|
||||
let scene = TestScenario::new(util_name!());
|
||||
let at = &scene.fixtures;
|
||||
|
||||
// Create a test file with non-UTF-8 bytes in the name
|
||||
let non_utf8_bytes = b"test_\xFF\xFE.txt";
|
||||
let non_utf8_name = OsStr::from_bytes(non_utf8_bytes);
|
||||
|
||||
// Create the actual file with some content
|
||||
std::fs::write(at.plus(non_utf8_name), "Hello, non-UTF-8 world!\n").unwrap();
|
||||
|
||||
// Test that cat handles non-UTF-8 file names without crashing
|
||||
let result = scene.ucmd().arg(non_utf8_name).succeeds();
|
||||
|
||||
// The result should contain the file content
|
||||
let output = result.stdout_str_lossy();
|
||||
assert_eq!(output, "Hello, non-UTF-8 world!\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_os = "linux")]
|
||||
fn test_appending_same_input_output() {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@
|
|||
// file that was distributed with this source code.
|
||||
// spell-checker:ignore (words) nosuchgroup groupname
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
use std::os::unix::ffi::OsStringExt;
|
||||
use uucore::process::getegid;
|
||||
use uutests::{at_and_ucmd, new_ucmd};
|
||||
#[cfg(not(target_vendor = "apple"))]
|
||||
|
|
@ -599,3 +601,17 @@ fn test_numeric_group_formats() {
|
|||
let final_gid = at.plus("test_file").metadata().unwrap().gid();
|
||||
assert_eq!(final_gid, first_group.as_raw());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_os = "linux")]
|
||||
fn test_chgrp_non_utf8_paths() {
|
||||
let (at, mut ucmd) = at_and_ucmd!();
|
||||
|
||||
let filename = std::ffi::OsString::from_vec(vec![0xFF, 0xFE]);
|
||||
std::fs::write(at.plus(&filename), b"test content").unwrap();
|
||||
|
||||
// Get current user's primary group
|
||||
let current_gid = getegid();
|
||||
|
||||
ucmd.arg(current_gid.to_string()).arg(&filename).succeeds();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1183,3 +1183,88 @@ fn test_chmod_recursive_symlink_combinations() {
|
|||
0o100_600
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_os = "linux")]
|
||||
fn test_chmod_non_utf8_paths() {
|
||||
use std::ffi::OsStr;
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
|
||||
let scene = TestScenario::new(util_name!());
|
||||
let at = &scene.fixtures;
|
||||
|
||||
// Create a file with non-UTF-8 name
|
||||
// Using bytes that form an invalid UTF-8 sequence
|
||||
let non_utf8_bytes = b"test_\xFF\xFE.txt";
|
||||
let non_utf8_name = OsStr::from_bytes(non_utf8_bytes);
|
||||
|
||||
// Create the file using OpenOptions with the non-UTF-8 name
|
||||
OpenOptions::new()
|
||||
.mode(0o644)
|
||||
.create(true)
|
||||
.write(true)
|
||||
.truncate(true)
|
||||
.open(at.plus(non_utf8_name))
|
||||
.unwrap();
|
||||
|
||||
// Verify initial permissions
|
||||
let initial_perms = metadata(at.plus(non_utf8_name))
|
||||
.unwrap()
|
||||
.permissions()
|
||||
.mode();
|
||||
assert_eq!(initial_perms & 0o777, 0o644);
|
||||
|
||||
// Test chmod with the non-UTF-8 filename
|
||||
scene
|
||||
.ucmd()
|
||||
.arg("755")
|
||||
.arg(non_utf8_name)
|
||||
.succeeds()
|
||||
.no_stderr();
|
||||
|
||||
// Verify permissions were changed
|
||||
let new_perms = metadata(at.plus(non_utf8_name))
|
||||
.unwrap()
|
||||
.permissions()
|
||||
.mode();
|
||||
assert_eq!(new_perms & 0o777, 0o755);
|
||||
|
||||
// Test with multiple non-UTF-8 files
|
||||
let non_utf8_bytes2 = b"file_\xC0\x80.dat";
|
||||
let non_utf8_name2 = OsStr::from_bytes(non_utf8_bytes2);
|
||||
|
||||
OpenOptions::new()
|
||||
.mode(0o666)
|
||||
.create(true)
|
||||
.write(true)
|
||||
.truncate(true)
|
||||
.open(at.plus(non_utf8_name2))
|
||||
.unwrap();
|
||||
|
||||
// Change permissions on both files at once
|
||||
scene
|
||||
.ucmd()
|
||||
.arg("644")
|
||||
.arg(non_utf8_name)
|
||||
.arg(non_utf8_name2)
|
||||
.succeeds()
|
||||
.no_stderr();
|
||||
|
||||
// Verify both files have the new permissions
|
||||
assert_eq!(
|
||||
metadata(at.plus(non_utf8_name))
|
||||
.unwrap()
|
||||
.permissions()
|
||||
.mode()
|
||||
& 0o777,
|
||||
0o644
|
||||
);
|
||||
assert_eq!(
|
||||
metadata(at.plus(non_utf8_name2))
|
||||
.unwrap()
|
||||
.permissions()
|
||||
.mode()
|
||||
& 0o777,
|
||||
0o644
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1501,3 +1501,15 @@ fn test_stdin_no_trailing_newline() {
|
|||
.succeeds()
|
||||
.stdout_only("2\n5\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_os = "linux")]
|
||||
fn test_csplit_non_utf8_paths() {
|
||||
use std::os::unix::ffi::OsStringExt;
|
||||
let (at, mut ucmd) = at_and_ucmd!();
|
||||
|
||||
let filename = std::ffi::OsString::from_vec(vec![0xFF, 0xFE]);
|
||||
std::fs::write(at.plus(&filename), b"line1\nline2\nline3\nline4\nline5\n").unwrap();
|
||||
|
||||
ucmd.arg(&filename).arg("3").succeeds();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -385,3 +385,28 @@ fn test_failed_write_is_reported() {
|
|||
.fails()
|
||||
.stderr_is("cut: write error: No space left on device\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_os = "linux")]
|
||||
fn test_cut_non_utf8_paths() {
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
use uutests::util::TestScenario;
|
||||
use uutests::util_name;
|
||||
|
||||
let ts = TestScenario::new(util_name!());
|
||||
let test_dir = ts.fixtures.subdir.as_path();
|
||||
|
||||
// Create file directly with non-UTF-8 name
|
||||
let file_name = std::ffi::OsStr::from_bytes(b"test_\xFF\xFE.txt");
|
||||
let mut file = File::create(test_dir.join(file_name)).unwrap();
|
||||
file.write_all(b"a\tb\tc\n1\t2\t3\n").unwrap();
|
||||
|
||||
// Test that cut can handle non-UTF-8 filenames
|
||||
ts.ucmd()
|
||||
.arg("-f1,3")
|
||||
.arg(file_name)
|
||||
.succeeds()
|
||||
.stdout_only("a\tc\n1\t3\n");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,10 +3,28 @@
|
|||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
// spell-checker:ignore overridable colorterm
|
||||
#[cfg(target_os = "linux")]
|
||||
use uutests::at_and_ucmd;
|
||||
use uutests::new_ucmd;
|
||||
|
||||
use dircolors::{OutputFmt, StrUtils, guess_syntax};
|
||||
|
||||
#[test]
|
||||
#[cfg(target_os = "linux")]
|
||||
fn test_dircolors_non_utf8_paths() {
|
||||
use std::os::unix::ffi::OsStringExt;
|
||||
let (at, mut ucmd) = at_and_ucmd!();
|
||||
|
||||
let filename = std::ffi::OsString::from_vec(vec![0xFF, 0xFE]);
|
||||
std::fs::write(at.plus(&filename), b"NORMAL 00\n*.txt 32\n").unwrap();
|
||||
|
||||
ucmd.env("SHELL", "bash")
|
||||
.arg(&filename)
|
||||
.succeeds()
|
||||
.stdout_contains("LS_COLORS=")
|
||||
.stdout_contains("*.txt=32");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_invalid_arg() {
|
||||
new_ucmd!().arg("--definitely-invalid").fails_with_code(1);
|
||||
|
|
|
|||
|
|
@ -64,3 +64,23 @@ fn test_pwd() {
|
|||
fn test_empty() {
|
||||
new_ucmd!().arg("").succeeds().stdout_is(".\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn test_dirname_non_utf8_paths() {
|
||||
use std::ffi::OsStr;
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
|
||||
// Create a test file with non-UTF-8 bytes in the name
|
||||
let non_utf8_bytes = b"test_\xFF\xFE/file.txt";
|
||||
let non_utf8_name = OsStr::from_bytes(non_utf8_bytes);
|
||||
|
||||
// Test that dirname handles non-UTF-8 paths without crashing
|
||||
let result = new_ucmd!().arg(non_utf8_name).succeeds();
|
||||
|
||||
// Just verify it didn't crash and produced some output
|
||||
// The exact output format may vary due to lossy conversion
|
||||
let output = result.stdout_str_lossy();
|
||||
assert!(!output.is_empty());
|
||||
assert!(output.contains("test_"));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -426,3 +426,19 @@ fn test_nonexisting_file() {
|
|||
.stderr_contains("expand: nonexistent: No such file or directory")
|
||||
.stdout_contains_line("// !note: file contains significant whitespace");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_os = "linux")]
|
||||
fn test_expand_non_utf8_paths() {
|
||||
use std::os::unix::ffi::OsStringExt;
|
||||
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\tworld\ntest\tline\n").unwrap();
|
||||
|
||||
ucmd.arg(&filename)
|
||||
.succeeds()
|
||||
.stdout_is("hello world\ntest line\n");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -858,3 +858,27 @@ fn test_write_to_dev_full() {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_os = "linux")]
|
||||
fn test_head_non_utf8_paths() {
|
||||
use std::ffi::OsStr;
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
|
||||
let scene = TestScenario::new(util_name!());
|
||||
let at = &scene.fixtures;
|
||||
|
||||
// Create a test file with non-UTF-8 bytes in the name
|
||||
let non_utf8_bytes = b"test_\xFF\xFE.txt";
|
||||
let non_utf8_name = OsStr::from_bytes(non_utf8_bytes);
|
||||
|
||||
std::fs::write(at.plus(non_utf8_name), "line1\nline2\nline3\n").unwrap();
|
||||
|
||||
let result = scene.ucmd().arg(non_utf8_name).succeeds();
|
||||
|
||||
let output = result.stdout_str_lossy();
|
||||
assert!(output.contains("line1"));
|
||||
assert!(output.contains("line2"));
|
||||
assert!(output.contains("line3"));
|
||||
}
|
||||
// Test that head handles non-UTF-8 file names without crashing
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@
|
|||
#[cfg(not(target_os = "openbsd"))]
|
||||
use filetime::FileTime;
|
||||
use std::fs;
|
||||
#[cfg(target_os = "linux")]
|
||||
use std::os::unix::ffi::OsStringExt;
|
||||
use std::os::unix::fs::{MetadataExt, PermissionsExt};
|
||||
#[cfg(not(windows))]
|
||||
use std::process::Command;
|
||||
|
|
@ -2366,3 +2368,26 @@ fn test_install_compare_with_mode_bits() {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_os = "linux")]
|
||||
fn test_install_non_utf8_paths() {
|
||||
let (at, mut ucmd) = at_and_ucmd!();
|
||||
let source_filename = std::ffi::OsString::from_vec(vec![0xFF, 0xFE]);
|
||||
let dest_dir = "target_dir";
|
||||
|
||||
std::fs::write(at.plus(&source_filename), b"test content").unwrap();
|
||||
at.mkdir(dest_dir);
|
||||
|
||||
ucmd.arg(&source_filename).arg(dest_dir).succeeds();
|
||||
|
||||
// Test with trailing slash and directory creation (-D flag)
|
||||
let (at, mut ucmd) = at_and_ucmd!();
|
||||
let source_file = "source.txt";
|
||||
let mut target_path = std::ffi::OsString::from_vec(vec![0xFF, 0xFE, b'd', b'i', b'r']);
|
||||
target_path.push("/target.txt");
|
||||
|
||||
at.touch(source_file);
|
||||
|
||||
ucmd.arg("-D").arg(source_file).arg(&target_path).succeeds();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -533,3 +533,36 @@ fn test_full() {
|
|||
.fails()
|
||||
.stderr_contains("No space left on device");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_os = "linux")]
|
||||
fn test_join_non_utf8_paths() {
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
|
||||
let ts = TestScenario::new(util_name!());
|
||||
let test_dir = ts.fixtures.subdir.as_path();
|
||||
|
||||
// Create files directly with non-UTF-8 names
|
||||
let file1_bytes = b"test_\xFF\xFE_1.txt";
|
||||
let file2_bytes = b"test_\xFF\xFE_2.txt";
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
let file1_name = std::ffi::OsStr::from_bytes(file1_bytes);
|
||||
let file2_name = std::ffi::OsStr::from_bytes(file2_bytes);
|
||||
|
||||
let mut file1 = File::create(test_dir.join(file1_name)).unwrap();
|
||||
file1.write_all(b"a 1\n").unwrap();
|
||||
|
||||
let mut file2 = File::create(test_dir.join(file2_name)).unwrap();
|
||||
file2.write_all(b"a 2\n").unwrap();
|
||||
|
||||
ts.ucmd()
|
||||
.arg(file1_name)
|
||||
.arg(file2_name)
|
||||
.succeeds()
|
||||
.stdout_only("a 1 2\n");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -843,3 +843,48 @@ fn test_ln_seen_file() {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_os = "linux")]
|
||||
fn test_ln_non_utf8_paths() {
|
||||
use std::ffi::OsStr;
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
|
||||
let scene = TestScenario::new(util_name!());
|
||||
let at = &scene.fixtures;
|
||||
|
||||
// Create a test file with non-UTF-8 bytes in the name
|
||||
let non_utf8_bytes = b"test_\xFF\xFE.txt";
|
||||
let non_utf8_name = OsStr::from_bytes(non_utf8_bytes);
|
||||
let non_utf8_link_bytes = b"link_\xFF\xFE.txt";
|
||||
let non_utf8_link_name = OsStr::from_bytes(non_utf8_link_bytes);
|
||||
|
||||
// Create the actual file
|
||||
at.touch(non_utf8_name);
|
||||
|
||||
// Test creating a hard link with non-UTF-8 file names
|
||||
scene
|
||||
.ucmd()
|
||||
.arg(non_utf8_name)
|
||||
.arg(non_utf8_link_name)
|
||||
.succeeds();
|
||||
|
||||
// Both files should exist
|
||||
assert!(at.file_exists(non_utf8_name));
|
||||
assert!(at.file_exists(non_utf8_link_name));
|
||||
|
||||
// Test creating a symbolic link with non-UTF-8 file names
|
||||
let symlink_bytes = b"symlink_\xFF\xFE.txt";
|
||||
let symlink_name = OsStr::from_bytes(symlink_bytes);
|
||||
|
||||
scene
|
||||
.ucmd()
|
||||
.args(&["-s"])
|
||||
.arg(non_utf8_name)
|
||||
.arg(symlink_name)
|
||||
.succeeds();
|
||||
|
||||
// Check if symlink was created successfully
|
||||
let symlink_path = at.plus(symlink_name);
|
||||
assert!(symlink_path.is_symlink());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -997,3 +997,66 @@ fn test_missing_short_tmpdir_flag() {
|
|||
.no_stdout()
|
||||
.stderr_contains("a value is required for '-p <DIR>' but none was supplied");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_os = "linux")]
|
||||
fn test_non_utf8_template() {
|
||||
use std::ffi::OsStr;
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
|
||||
let ts = TestScenario::new(util_name!());
|
||||
|
||||
// Test that mktemp gracefully handles non-UTF-8 templates with an error instead of panicking
|
||||
let template = OsStr::from_bytes(b"test_\xFF\xFE_XXXXXX");
|
||||
|
||||
ts.ucmd().arg(template).fails().stderr_contains("invalid");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_os = "linux")]
|
||||
fn test_non_utf8_tmpdir_path() {
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
let (at, mut ucmd) = at_and_ucmd!();
|
||||
|
||||
// Create a directory with non-UTF8 bytes
|
||||
let dir_name = std::ffi::OsStr::from_bytes(b"test_dir_\xFF\xFE");
|
||||
std::fs::create_dir(at.plus(dir_name)).unwrap();
|
||||
|
||||
// Test that mktemp can handle non-UTF8 directory paths with -p option
|
||||
ucmd.arg("-p").arg(at.plus(dir_name)).succeeds();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_os = "linux")]
|
||||
fn test_non_utf8_tmpdir_long_option() {
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
let (at, mut ucmd) = at_and_ucmd!();
|
||||
|
||||
// Create a directory with non-UTF8 bytes
|
||||
let dir_name = std::ffi::OsStr::from_bytes(b"test_dir_\xFF\xFE");
|
||||
std::fs::create_dir(at.plus(dir_name)).unwrap();
|
||||
|
||||
// Test that mktemp can handle non-UTF8 directory paths with --tmpdir option
|
||||
// Note: Due to test framework limitations with non-UTF8 arguments and --tmpdir= syntax,
|
||||
// we'll test a more limited scenario that still validates non-UTF8 path handling
|
||||
ucmd.arg("-p")
|
||||
.arg(at.plus(dir_name))
|
||||
.arg("tmpXXXXXX")
|
||||
.succeeds();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_os = "linux")]
|
||||
fn test_non_utf8_tmpdir_directory_creation() {
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
let (at, mut ucmd) = at_and_ucmd!();
|
||||
|
||||
// Create a directory with non-UTF8 bytes
|
||||
let dir_name = std::ffi::OsStr::from_bytes(b"test_dir_\xFF\xFE");
|
||||
std::fs::create_dir(at.plus(dir_name)).unwrap();
|
||||
|
||||
// Test directory creation (-d flag) with non-UTF8 directory paths
|
||||
// We can't easily verify the exact output path because of UTF8 conversion issues,
|
||||
// but we can verify the command succeeds
|
||||
ucmd.arg("-d").arg("-p").arg(at.plus(dir_name)).succeeds();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -126,3 +126,21 @@ fn test_invalid_file_perms() {
|
|||
.stderr_contains("permission denied");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_os = "linux")]
|
||||
fn test_more_non_utf8_paths() {
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
if std::io::stdout().is_terminal() {
|
||||
let (at, mut ucmd) = at_and_ucmd!();
|
||||
let file_name = std::ffi::OsStr::from_bytes(b"test_\xFF\xFE.txt");
|
||||
// Create test file with normal name first
|
||||
at.write(
|
||||
&file_name.to_string_lossy(),
|
||||
"test content for non-UTF-8 file",
|
||||
);
|
||||
|
||||
// Test that more can handle non-UTF-8 filenames without crashing
|
||||
ucmd.arg(file_name).succeeds();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,22 @@ use uutests::new_ucmd;
|
|||
use uutests::util::TestScenario;
|
||||
use uutests::util_name;
|
||||
|
||||
#[test]
|
||||
#[cfg(target_os = "linux")]
|
||||
fn test_nl_non_utf8_paths() {
|
||||
use std::os::unix::ffi::OsStringExt;
|
||||
let (at, mut ucmd) = at_and_ucmd!();
|
||||
|
||||
let filename = std::ffi::OsString::from_vec(vec![0xFF, 0xFE]);
|
||||
std::fs::write(at.plus(&filename), b"line 1\nline 2\nline 3\n").unwrap();
|
||||
|
||||
ucmd.arg(&filename)
|
||||
.succeeds()
|
||||
.stdout_contains("1\t")
|
||||
.stdout_contains("2\t")
|
||||
.stdout_contains("3\t");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_invalid_arg() {
|
||||
new_ucmd!().arg("--definitely-invalid").fails_with_code(1);
|
||||
|
|
|
|||
|
|
@ -4,7 +4,8 @@
|
|||
// file that was distributed with this source code.
|
||||
|
||||
// spell-checker:ignore bsdutils toybox
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
use std::os::unix::ffi::OsStringExt;
|
||||
use uutests::at_and_ucmd;
|
||||
use uutests::new_ucmd;
|
||||
|
||||
|
|
@ -252,6 +253,7 @@ FIRST!SECOND@THIRD#FOURTH!ABCDEFG
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn test_non_utf8_input() {
|
||||
// 0xC0 is not valid UTF-8
|
||||
const INPUT: &[u8] = b"Non-UTF-8 test: \xC0\x00\xC0.\n";
|
||||
|
|
@ -375,3 +377,20 @@ fn test_data() {
|
|||
.stdout_is(example.out);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_os = "linux")]
|
||||
fn test_paste_non_utf8_paths() {
|
||||
let (at, mut ucmd) = at_and_ucmd!();
|
||||
|
||||
let filename1 = std::ffi::OsString::from_vec(vec![0xFF, 0xFE]);
|
||||
let filename2 = std::ffi::OsString::from_vec(vec![0xF0, 0x90]);
|
||||
|
||||
std::fs::write(at.plus(&filename1), b"line1\nline2\n").unwrap();
|
||||
std::fs::write(at.plus(&filename2), b"col1\ncol2\n").unwrap();
|
||||
|
||||
ucmd.arg(&filename1)
|
||||
.arg(&filename2)
|
||||
.succeeds()
|
||||
.stdout_is("line1\tcol1\nline2\tcol2\n");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
//
|
||||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
#[cfg(target_os = "linux")]
|
||||
use std::os::unix::ffi::OsStringExt;
|
||||
use uutests::new_ucmd;
|
||||
|
||||
#[test]
|
||||
|
|
@ -164,3 +166,10 @@ fn test_posix_all() {
|
|||
// fail on empty path
|
||||
new_ucmd!().args(&["-p", "-P", ""]).fails().no_stdout();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_os = "linux")]
|
||||
fn test_pathchk_non_utf8_paths() {
|
||||
let filename = std::ffi::OsString::from_vec(vec![0xFF, 0xFE]);
|
||||
new_ucmd!().arg(&filename).succeeds();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -373,3 +373,25 @@ fn test_delimiters() {
|
|||
.stderr_contains("ignoring --no-newline with multiple arguments")
|
||||
.stdout_is("/a\n/a\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_os = "linux")]
|
||||
fn test_readlink_non_utf8_paths() {
|
||||
use std::ffi::OsStr;
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
|
||||
let scene = TestScenario::new(util_name!());
|
||||
let at = &scene.fixtures;
|
||||
let file_name = "target_file";
|
||||
at.touch(file_name);
|
||||
let non_utf8_bytes = b"symlink_\xFF\xFE";
|
||||
let non_utf8_name = OsStr::from_bytes(non_utf8_bytes);
|
||||
|
||||
std::os::unix::fs::symlink(at.plus_as_string(file_name), at.plus(non_utf8_name)).unwrap();
|
||||
|
||||
// Test that readlink handles non-UTF-8 symlink names without crashing
|
||||
let result = scene.ucmd().arg(non_utf8_name).succeeds();
|
||||
|
||||
let output = result.stdout_str_lossy();
|
||||
assert!(output.contains(file_name));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -464,3 +464,44 @@ fn test_realpath_trailing_slash() {
|
|||
fn test_realpath_empty() {
|
||||
new_ucmd!().fails_with_code(1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_os = "linux")]
|
||||
fn test_realpath_non_utf8_paths() {
|
||||
use std::ffi::OsStr;
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
|
||||
let scene = TestScenario::new(util_name!());
|
||||
let at = &scene.fixtures;
|
||||
|
||||
// Create a test file with non-UTF-8 bytes in the name
|
||||
let non_utf8_bytes = b"test_\xFF\xFE.txt";
|
||||
let non_utf8_name = OsStr::from_bytes(non_utf8_bytes);
|
||||
|
||||
at.touch(non_utf8_name);
|
||||
let result = scene.ucmd().arg(non_utf8_name).succeeds();
|
||||
|
||||
let output = result.stdout_str_lossy();
|
||||
assert!(output.contains("test_"));
|
||||
assert!(output.contains(".txt"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_realpath_empty_string() {
|
||||
// Test that empty string arguments are rejected with exit code 1
|
||||
new_ucmd!().arg("").fails().code_is(1);
|
||||
|
||||
// Test that empty --relative-base is rejected
|
||||
new_ucmd!()
|
||||
.arg("--relative-base=")
|
||||
.arg("--relative-to=.")
|
||||
.arg(".")
|
||||
.fails()
|
||||
.code_is(1);
|
||||
|
||||
new_ucmd!()
|
||||
.arg("--relative-to=")
|
||||
.arg(".")
|
||||
.fails()
|
||||
.code_is(1);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1037,3 +1037,38 @@ fn test_inaccessible_dir_recursive() {
|
|||
assert!(!at.dir_exists("a/unreadable"));
|
||||
assert!(!at.dir_exists("a"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_os = "linux")]
|
||||
fn test_rm_non_utf8_paths() {
|
||||
use std::ffi::OsStr;
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
|
||||
let scene = TestScenario::new(util_name!());
|
||||
let at = &scene.fixtures;
|
||||
|
||||
// Create a test file with non-UTF-8 bytes in the name
|
||||
let non_utf8_bytes = b"test_\xFF\xFE.txt";
|
||||
let non_utf8_name = OsStr::from_bytes(non_utf8_bytes);
|
||||
|
||||
// Create the actual file
|
||||
at.touch(non_utf8_name);
|
||||
assert!(at.file_exists(non_utf8_name));
|
||||
|
||||
// Test that rm handles non-UTF-8 file names without crashing
|
||||
scene.ucmd().arg(non_utf8_name).succeeds();
|
||||
|
||||
// The file should be removed
|
||||
assert!(!at.file_exists(non_utf8_name));
|
||||
|
||||
// Test with directory
|
||||
let non_utf8_dir_bytes = b"test_dir_\xFF\xFE";
|
||||
let non_utf8_dir_name = OsStr::from_bytes(non_utf8_dir_bytes);
|
||||
|
||||
at.mkdir(non_utf8_dir_name);
|
||||
assert!(at.dir_exists(non_utf8_dir_name));
|
||||
|
||||
scene.ucmd().args(&["-r"]).arg(non_utf8_dir_name).succeeds();
|
||||
|
||||
assert!(!at.dir_exists(non_utf8_dir_name));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -316,3 +316,17 @@ fn test_shred_rename_exhaustion() {
|
|||
|
||||
assert!(!at.file_exists("test"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_os = "linux")]
|
||||
fn test_shred_non_utf8_paths() {
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
let ts = TestScenario::new(util_name!());
|
||||
let at = &ts.fixtures;
|
||||
|
||||
let file_name = std::ffi::OsStr::from_bytes(b"test_\xFF\xFE.txt");
|
||||
std::fs::write(at.plus(file_name), "test content").unwrap();
|
||||
|
||||
// Test that shred can handle non-UTF-8 filenames
|
||||
ts.ucmd().arg(file_name).succeeds();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ use regex::Regex;
|
|||
use rlimit::Resource;
|
||||
#[cfg(not(windows))]
|
||||
use std::env;
|
||||
#[cfg(target_os = "linux")]
|
||||
use std::os::unix::ffi::OsStringExt;
|
||||
use std::path::Path;
|
||||
use std::{
|
||||
fs::{File, read_dir},
|
||||
|
|
@ -1709,7 +1711,7 @@ fn test_split_invalid_input() {
|
|||
/// Test if there are invalid (non UTF-8) in the arguments - unix
|
||||
/// clap is expected to fail/panic
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
#[cfg(target_os = "linux")]
|
||||
fn test_split_non_utf8_argument_unix() {
|
||||
use std::ffi::OsStr;
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
|
|
@ -1724,9 +1726,7 @@ fn test_split_non_utf8_argument_unix() {
|
|||
let opt_value = [0x66, 0x6f, 0x80, 0x6f];
|
||||
let opt_value = OsStr::from_bytes(&opt_value[..]);
|
||||
let name = OsStr::from_bytes(name.as_bytes());
|
||||
ucmd.args(&[opt, opt_value, name])
|
||||
.fails()
|
||||
.stderr_contains("error: invalid UTF-8 was detected in one or more arguments");
|
||||
ucmd.args(&[opt, opt_value, name]).succeeds();
|
||||
}
|
||||
|
||||
/// Test if there are invalid (non UTF-8) in the arguments - windows
|
||||
|
|
@ -1747,9 +1747,7 @@ fn test_split_non_utf8_argument_windows() {
|
|||
let opt_value = [0x0066, 0x006f, 0xD800, 0x006f];
|
||||
let opt_value = OsString::from_wide(&opt_value[..]);
|
||||
let name = OsString::from(name);
|
||||
ucmd.args(&[opt, opt_value, name])
|
||||
.fails()
|
||||
.stderr_contains("error: invalid UTF-8 was detected in one or more arguments");
|
||||
ucmd.args(&[opt, opt_value, name]).succeeds();
|
||||
}
|
||||
|
||||
// Test '--separator' / '-t' option following GNU tests example
|
||||
|
|
@ -2006,3 +2004,77 @@ fn test_long_lines() {
|
|||
assert_eq!(at.read("xac").len(), 131_072);
|
||||
assert!(!at.plus("xad").exists());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_os = "linux")]
|
||||
fn test_split_non_utf8_paths() {
|
||||
let (at, mut ucmd) = at_and_ucmd!();
|
||||
|
||||
let filename = std::ffi::OsString::from_vec(vec![0xFF, 0xFE]);
|
||||
std::fs::write(at.plus(&filename), b"line1\nline2\nline3\nline4\nline5\n").unwrap();
|
||||
|
||||
ucmd.arg(&filename).succeeds();
|
||||
|
||||
// Check that at least one split file was created
|
||||
assert!(at.plus("xaa").exists());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_os = "linux")]
|
||||
fn test_split_non_utf8_prefix() {
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
let (at, mut ucmd) = at_and_ucmd!();
|
||||
|
||||
at.write("input.txt", "line1\nline2\nline3\nline4\n");
|
||||
|
||||
let prefix = std::ffi::OsStr::from_bytes(b"\xFF\xFE");
|
||||
ucmd.arg("input.txt").arg(prefix).succeeds();
|
||||
|
||||
// Check that split files were created (functionality works)
|
||||
// The actual filename may be converted due to lossy conversion, but the command should succeed
|
||||
let entries: Vec<_> = std::fs::read_dir(at.as_string()).unwrap().collect();
|
||||
let split_files = entries
|
||||
.iter()
|
||||
.filter_map(|e| e.as_ref().ok())
|
||||
.filter(|entry| {
|
||||
let name = entry.file_name();
|
||||
let name_str = name.to_string_lossy();
|
||||
name_str.starts_with("<EFBFBD>") || name_str.len() > 2 // split files should exist
|
||||
})
|
||||
.count();
|
||||
assert!(
|
||||
split_files > 0,
|
||||
"Expected at least one split file to be created"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_os = "linux")]
|
||||
fn test_split_non_utf8_additional_suffix() {
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
let (at, mut ucmd) = at_and_ucmd!();
|
||||
|
||||
at.write("input.txt", "line1\nline2\nline3\nline4\n");
|
||||
|
||||
let suffix = std::ffi::OsStr::from_bytes(b"\xFF\xFE");
|
||||
ucmd.args(&["input.txt", "--additional-suffix"])
|
||||
.arg(suffix)
|
||||
.succeeds();
|
||||
|
||||
// Check that split files were created (functionality works)
|
||||
// The actual filename may be converted due to lossy conversion, but the command should succeed
|
||||
let entries: Vec<_> = std::fs::read_dir(at.as_string()).unwrap().collect();
|
||||
let split_files = entries
|
||||
.iter()
|
||||
.filter_map(|e| e.as_ref().ok())
|
||||
.filter(|entry| {
|
||||
let name = entry.file_name();
|
||||
let name_str = name.to_string_lossy();
|
||||
name_str.ends_with("<EFBFBD>") || name_str.starts_with('x') // split files should exist
|
||||
})
|
||||
.count();
|
||||
assert!(
|
||||
split_files > 0,
|
||||
"Expected at least one split file to be created"
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@
|
|||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
// spell-checker:ignore dyld dylib setvbuf
|
||||
#[cfg(target_os = "linux")]
|
||||
use uutests::at_and_ucmd;
|
||||
use uutests::new_ucmd;
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
use uutests::util::TestScenario;
|
||||
|
|
@ -216,3 +218,21 @@ fn test_libstdbuf_preload() {
|
|||
"uutils echo should not show architecture mismatch"
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
#[cfg(not(target_env = "musl"))]
|
||||
#[test]
|
||||
fn test_stdbuf_non_utf8_paths() {
|
||||
use std::os::unix::ffi::OsStringExt;
|
||||
|
||||
let (at, mut ucmd) = at_and_ucmd!();
|
||||
|
||||
let filename = std::ffi::OsString::from_vec(vec![0xFF, 0xFE]);
|
||||
std::fs::write(at.plus(&filename), b"test content for stdbuf\n").unwrap();
|
||||
|
||||
ucmd.arg("-o0")
|
||||
.arg("cat")
|
||||
.arg(&filename)
|
||||
.succeeds()
|
||||
.stdout_is("test content for stdbuf\n");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
//
|
||||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
#[cfg(target_os = "linux")]
|
||||
use std::os::unix::ffi::OsStringExt;
|
||||
use uutests::at_and_ucmd;
|
||||
use uutests::new_ucmd;
|
||||
|
||||
|
|
@ -80,3 +82,14 @@ fn test_invalid_metadata() {
|
|||
.fails()
|
||||
.stderr_is("sum: b: No such file or directory\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_os = "linux")]
|
||||
fn test_sum_non_utf8_paths() {
|
||||
let (at, mut ucmd) = at_and_ucmd!();
|
||||
|
||||
let filename = std::ffi::OsString::from_vec(vec![0xFF, 0xFE]);
|
||||
std::fs::write(at.plus(&filename), b"test content").unwrap();
|
||||
|
||||
ucmd.arg(&filename).succeeds();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,10 +3,26 @@
|
|||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
// spell-checker:ignore axxbxx bxxaxx axxx axxxx xxaxx xxax xxxxa axyz zyax zyxa
|
||||
#[cfg(target_os = "linux")]
|
||||
use uutests::at_and_ucmd;
|
||||
use uutests::new_ucmd;
|
||||
use uutests::util::TestScenario;
|
||||
use uutests::util_name;
|
||||
|
||||
#[test]
|
||||
#[cfg(target_os = "linux")]
|
||||
fn test_tac_non_utf8_paths() {
|
||||
use std::os::unix::ffi::OsStringExt;
|
||||
let (at, mut ucmd) = at_and_ucmd!();
|
||||
|
||||
let filename = std::ffi::OsString::from_vec(vec![0xFF, 0xFE]);
|
||||
std::fs::write(at.plus(&filename), b"line1\nline2\nline3\n").unwrap();
|
||||
|
||||
ucmd.arg(&filename)
|
||||
.succeeds()
|
||||
.stdout_is("line3\nline2\nline1\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_invalid_arg() {
|
||||
new_ucmd!().arg("--definitely-invalid").fails_with_code(1);
|
||||
|
|
|
|||
|
|
@ -1013,3 +1013,19 @@ fn test_touch_f_option() {
|
|||
assert!(at.file_exists(file));
|
||||
at.remove(file);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_os = "linux")]
|
||||
fn test_touch_non_utf8_paths() {
|
||||
use std::ffi::OsStr;
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
|
||||
let scene = TestScenario::new(util_name!());
|
||||
let at = &scene.fixtures;
|
||||
|
||||
let non_utf8_bytes = b"test_\xFF\xFE.txt";
|
||||
let non_utf8_name = OsStr::from_bytes(non_utf8_bytes);
|
||||
|
||||
scene.ucmd().arg(non_utf8_name).succeeds().no_output();
|
||||
assert!(std::fs::metadata(at.plus(non_utf8_name)).is_ok());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -420,3 +420,16 @@ 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::os::unix::ffi::OsStrExt;
|
||||
let ts = TestScenario::new(util_name!());
|
||||
let at = &ts.fixtures;
|
||||
let file_name = std::ffi::OsStr::from_bytes(b"test_\xFF\xFE.txt");
|
||||
at.write(&file_name.to_string_lossy(), "test content");
|
||||
|
||||
// Test that truncate can handle non-UTF-8 filenames
|
||||
ts.ucmd().arg("-s").arg("10").arg(file_name).succeeds();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,18 @@
|
|||
use uutests::at_and_ucmd;
|
||||
use uutests::new_ucmd;
|
||||
|
||||
#[test]
|
||||
#[cfg(target_os = "linux")]
|
||||
fn test_tsort_non_utf8_paths() {
|
||||
use std::os::unix::ffi::OsStringExt;
|
||||
let (at, mut ucmd) = at_and_ucmd!();
|
||||
|
||||
let filename = std::ffi::OsString::from_vec(vec![0xFF, 0xFE]);
|
||||
std::fs::write(at.plus(&filename), b"a b\nb c\n").unwrap();
|
||||
|
||||
ucmd.arg(&filename).succeeds().stdout_is("a\nb\nc\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_invalid_arg() {
|
||||
new_ucmd!().arg("--definitely-invalid").fails_with_code(1);
|
||||
|
|
|
|||
|
|
@ -75,3 +75,23 @@ fn test_unlink_symlink() {
|
|||
assert!(at.file_exists("foo"));
|
||||
assert!(!at.file_exists("bar"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_os = "linux")]
|
||||
fn test_unlink_non_utf8_paths() {
|
||||
use std::ffi::OsStr;
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
|
||||
let (at, mut ucmd) = at_and_ucmd!();
|
||||
// Create a test file with non-UTF-8 bytes in the name
|
||||
let non_utf8_bytes = b"test_\xFF\xFE.txt";
|
||||
let non_utf8_name = OsStr::from_bytes(non_utf8_bytes);
|
||||
|
||||
at.touch(non_utf8_name);
|
||||
assert!(at.file_exists(non_utf8_name));
|
||||
|
||||
// Test that unlink handles non-UTF-8 file names without crashing
|
||||
ucmd.arg(non_utf8_name).succeeds();
|
||||
|
||||
assert!(!at.file_exists(non_utf8_name));
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue