From 38bdf37fcd4aaccae46c5809ef80bb98a1400c13 Mon Sep 17 00:00:00 2001 From: Tuomas Tynkkynen Date: Sat, 20 Sep 2025 04:50:13 +0300 Subject: [PATCH] setpgid: Add tool --- Cargo.lock | 11 ++++ Cargo.toml | 2 + src/uu/setpgid/Cargo.toml | 17 ++++++ src/uu/setpgid/setpgid.md | 7 +++ src/uu/setpgid/src/main.rs | 1 + src/uu/setpgid/src/setpgid.rs | 99 +++++++++++++++++++++++++++++++++++ tests/by-util/test_setpgid.rs | 37 +++++++++++++ tests/tests.rs | 4 ++ 8 files changed, 178 insertions(+) create mode 100644 src/uu/setpgid/Cargo.toml create mode 100644 src/uu/setpgid/setpgid.md create mode 100644 src/uu/setpgid/src/main.rs create mode 100644 src/uu/setpgid/src/setpgid.rs create mode 100644 tests/by-util/test_setpgid.rs diff --git a/Cargo.lock b/Cargo.lock index 48d160a..a185c66 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1331,6 +1331,7 @@ dependencies = [ "uu_nologin", "uu_renice", "uu_rev", + "uu_setpgid", "uu_setsid", "uu_uuidgen", "uucore", @@ -1500,6 +1501,16 @@ dependencies = [ "uucore", ] +[[package]] +name = "uu_setpgid" +version = "0.0.1" +dependencies = [ + "clap", + "libc", + "nix", + "uucore", +] + [[package]] name = "uu_setsid" version = "0.0.1" diff --git a/Cargo.toml b/Cargo.toml index 8f7db3d..e195a02 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,6 +42,7 @@ feat_common_core = [ "nologin", "renice", "rev", + "setpgid", "setsid", "uuidgen", ] @@ -106,6 +107,7 @@ mountpoint = { optional = true, version = "0.0.1", package = "uu_mountpoint", pa nologin = { optional = true, version = "0.0.1", package = "uu_nologin", path = "src/uu/nologin" } renice = { optional = true, version = "0.0.1", package = "uu_renice", path = "src/uu/renice" } rev = { optional = true, version = "0.0.1", package = "uu_rev", path = "src/uu/rev" } +setpgid = { optional = true, version = "0.0.1", package = "uu_setpgid", path = "src/uu/setpgid" } setsid = { optional = true, version = "0.0.1", package = "uu_setsid", path ="src/uu/setsid" } uuidgen = { optional = true, version = "0.0.1", package = "uu_uuidgen", path ="src/uu/uuidgen" } diff --git a/src/uu/setpgid/Cargo.toml b/src/uu/setpgid/Cargo.toml new file mode 100644 index 0000000..cf1eb7f --- /dev/null +++ b/src/uu/setpgid/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "uu_setpgid" +version = "0.0.1" +edition = "2021" + +[lib] +path = "src/setpgid.rs" + +[[bin]] +name = "setpgid" +path = "src/main.rs" + +[dependencies] +uucore = { workspace = true } +clap = { workspace = true } +libc = { workspace = true } +nix = { workspace = true, features = ["process"] } diff --git a/src/uu/setpgid/setpgid.md b/src/uu/setpgid/setpgid.md new file mode 100644 index 0000000..fd44756 --- /dev/null +++ b/src/uu/setpgid/setpgid.md @@ -0,0 +1,7 @@ +# setpgid + +``` +setpgid [options] [...] +``` + +Run a program in a new process group diff --git a/src/uu/setpgid/src/main.rs b/src/uu/setpgid/src/main.rs new file mode 100644 index 0000000..d81013b --- /dev/null +++ b/src/uu/setpgid/src/main.rs @@ -0,0 +1 @@ +uucore::bin!(uu_setpgid); diff --git a/src/uu/setpgid/src/setpgid.rs b/src/uu/setpgid/src/setpgid.rs new file mode 100644 index 0000000..262e40a --- /dev/null +++ b/src/uu/setpgid/src/setpgid.rs @@ -0,0 +1,99 @@ +// This file is part of the uutils util-linux package. +// +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. + +use clap::{crate_version, Arg, ArgAction, Command as ClapCommand}; +use uucore::{ + error::{UResult, USimpleError}, + format_usage, help_about, help_usage, +}; + +const ABOUT: &str = help_about!("setpgid.md"); +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::fs::File; + use std::os::unix::io::AsRawFd; + + let matches = uu_app().try_get_matches_from(args)?; + + let remaining_args: Vec = matches + .get_many::("args") + .unwrap() + .cloned() + .collect(); + + if unsafe { libc::setpgid(0, 0) } != 0 { + return Err(USimpleError::new( + 1, + format!( + "failed to create new process group: {}", + std::io::Error::last_os_error() + ), + )); + } + + if matches.get_flag("foreground") { + if let Ok(tty_file) = File::open("/dev/tty") { + unsafe { + libc::tcsetpgrp(tty_file.as_raw_fd(), libc::getpgrp()); + } + } + // According to strace open("/dev/tty") failure is ignored. + } + + let program = &remaining_args[0]; + 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 mut argv = vec![program_cstr.clone()]; + for arg in program_args { + argv.push(CString::new(arg.as_str()).unwrap()); + } + + let Err(e) = nix::unistd::execvp(&program_cstr, &argv); + Err(USimpleError::new( + 1, + format!("failed to execute '{}': {}", program, e), + )) +} + +#[cfg(not(target_family = "unix"))] +#[uucore::main] +pub fn uumain(args: impl uucore::Args) -> UResult<()> { + let _matches: clap::ArgMatches = uu_app().try_get_matches_from(args)?; + + Err(USimpleError::new( + 1, + "`setpgid` is unavailable on non-UNIX-like platforms.", + )) +} + +pub fn uu_app() -> ClapCommand { + ClapCommand::new(uucore::util_name()) + .version(crate_version!()) + .about(ABOUT) + .override_usage(format_usage(USAGE)) + .infer_long_args(true) + .arg( + Arg::new("foreground") + .short('f') + .long("foreground") + .help("Make a foreground process group") + .action(ArgAction::SetTrue), + ) + .arg( + Arg::new("args") + .hide_short_help(true) + .hide_long_help(true) + .required(true) + .action(ArgAction::Append) + .num_args(1..) + .trailing_var_arg(true), + ) +} diff --git a/tests/by-util/test_setpgid.rs b/tests/by-util/test_setpgid.rs new file mode 100644 index 0000000..88fc9a2 --- /dev/null +++ b/tests/by-util/test_setpgid.rs @@ -0,0 +1,37 @@ +// This file is part of the uutils util-linux package. +// +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. + +use regex::Regex; +use uutests::new_ucmd; + +#[test] +#[cfg(target_family = "unix")] +fn test_nonexistent_program() { + new_ucmd!() + .arg("does_not_exist") + .fails() + .stderr_contains("failed to execute"); +} + +#[test] +#[cfg(target_os = "linux")] +fn test_pgid_changed() { + let our_pgid = unsafe { libc::getpgid(0) }; + // Gets pgid of the 'cut' process from /proc + new_ucmd!() + .args(&["cut", "-d", " ", "-f", "5", "/proc/self/stat"]) + .succeeds() + .stdout_does_not_match(&Regex::new(&format!("^{}$", our_pgid)).unwrap()); +} + +#[test] +#[cfg(target_family = "unix")] +fn test_flag_after_command() { + new_ucmd!() + .arg("echo") + .arg("-f") + .succeeds() + .stdout_is("-f\n"); +} diff --git a/tests/tests.rs b/tests/tests.rs index 4871156..2aa4324 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -55,6 +55,10 @@ mod test_renice; #[path = "by-util/test_rev.rs"] mod test_rev; +#[cfg(feature = "setpgid")] +#[path = "by-util/test_setpgid.rs"] +mod test_setpgid; + #[cfg(feature = "setsid")] #[path = "by-util/test_setsid.rs"] mod test_setsid;