From 0553e255a6215a9623ff20daa385149787f89c92 Mon Sep 17 00:00:00 2001 From: Sichen Date: Wed, 30 Sep 2026 21:51:41 +0000 Subject: [PATCH] setpgid: accept a command and arguments that are not valid UTF-8 Read the command and its arguments as OsString so they reach execvp() unchanged, as they do with util-linux setpgid. --- src/uu/setpgid/src/setpgid.rs | 15 +++++++++------ tests/by-util/test_setpgid.rs | 29 +++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 6 deletions(-) diff --git a/src/uu/setpgid/src/setpgid.rs b/src/uu/setpgid/src/setpgid.rs index 262e40a8..6abf8b09 100644 --- a/src/uu/setpgid/src/setpgid.rs +++ b/src/uu/setpgid/src/setpgid.rs @@ -3,6 +3,7 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. +use clap::builder::ValueParser; use clap::{crate_version, Arg, ArgAction, Command as ClapCommand}; use uucore::{ error::{UResult, USimpleError}, @@ -15,14 +16,15 @@ const USAGE: &str = help_usage!("setpgid.md"); #[cfg(target_family = "unix")] #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - use std::ffi::CString; + use std::ffi::{CString, OsString}; use std::fs::File; + use std::os::unix::ffi::OsStrExt; use std::os::unix::io::AsRawFd; let matches = uu_app().try_get_matches_from(args)?; - let remaining_args: Vec = matches - .get_many::("args") + let remaining_args: Vec = matches + .get_many::("args") .unwrap() .cloned() .collect(); @@ -50,16 +52,16 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let program_args = &remaining_args[1..]; // Command line arguments can't contain NUL bytes, so unwrap() is safe here. - let program_cstr = CString::new(program.as_str()).unwrap(); + let program_cstr = CString::new(program.as_bytes()).unwrap(); let mut argv = vec![program_cstr.clone()]; for arg in program_args { - argv.push(CString::new(arg.as_str()).unwrap()); + argv.push(CString::new(arg.as_bytes()).unwrap()); } let Err(e) = nix::unistd::execvp(&program_cstr, &argv); Err(USimpleError::new( 1, - format!("failed to execute '{}': {}", program, e), + format!("failed to execute '{}': {}", program.to_string_lossy(), e), )) } @@ -94,6 +96,7 @@ pub fn uu_app() -> ClapCommand { .required(true) .action(ArgAction::Append) .num_args(1..) + .value_parser(ValueParser::os_string()) .trailing_var_arg(true), ) } diff --git a/tests/by-util/test_setpgid.rs b/tests/by-util/test_setpgid.rs index 88fc9a2d..8dc0b3ef 100644 --- a/tests/by-util/test_setpgid.rs +++ b/tests/by-util/test_setpgid.rs @@ -35,3 +35,32 @@ fn test_flag_after_command() { .succeeds() .stdout_is("-f\n"); } + +#[test] +#[cfg(target_family = "unix")] +fn test_non_utf8_arguments() { + use std::ffi::OsStr; + use std::os::unix::ffi::OsStrExt; + + new_ucmd!() + .arg("echo") + .arg(OsStr::from_bytes(b"\xff")) + .succeeds() + .stdout_is_bytes(b"\xff\n"); +} + +#[test] +#[cfg(all(unix, not(target_os = "macos")))] +fn test_non_utf8_program_name() { + use std::ffi::OsStr; + use std::os::unix::ffi::OsStrExt; + use uutests::at_and_ucmd; + + let (at, mut ucmd) = at_and_ucmd!(); + let program = at.plus(OsStr::from_bytes(b"sh_\xff")); + std::os::unix::fs::symlink("/bin/sh", &program).unwrap(); + ucmd.arg(&program) + .args(&["-c", "echo ran"]) + .succeeds() + .stdout_is("ran\n"); +}