diff --git a/src/uu/nice/Cargo.toml b/src/uu/nice/Cargo.toml index 627fc6996cd..36396f8c8a8 100644 --- a/src/uu/nice/Cargo.toml +++ b/src/uu/nice/Cargo.toml @@ -25,7 +25,10 @@ uucore = { workspace = true } rustix = { workspace = true, features = ["process"] } [target.'cfg(windows)'.dependencies] -windows-sys = { workspace = true, features = ["Win32_System_Threading"] } +windows-sys = { workspace = true, features = [ + "Win32_Foundation", + "Win32_System_Threading", +] } [lints] workspace = true diff --git a/src/uu/nice/src/nice.rs b/src/uu/nice/src/nice.rs index db986bda752..818211d1a7a 100644 --- a/src/uu/nice/src/nice.rs +++ b/src/uu/nice/src/nice.rs @@ -15,8 +15,8 @@ use std::os::windows::process::CommandExt as _; use std::process; #[cfg(windows)] use windows_sys::Win32::System::Threading::{ - ABOVE_NORMAL_PRIORITY_CLASS, BELOW_NORMAL_PRIORITY_CLASS, HIGH_PRIORITY_CLASS, - IDLE_PRIORITY_CLASS, NORMAL_PRIORITY_CLASS, REALTIME_PRIORITY_CLASS, + ABOVE_NORMAL_PRIORITY_CLASS, BELOW_NORMAL_PRIORITY_CLASS, GetCurrentProcess, GetPriorityClass, + HIGH_PRIORITY_CLASS, IDLE_PRIORITY_CLASS, NORMAL_PRIORITY_CLASS, REALTIME_PRIORITY_CLASS, }; use uucore::translate; @@ -99,6 +99,43 @@ fn standardize_nice_args(mut args: impl uucore::Args) -> impl uucore::Args { v.into_iter() } +/// Map a Windows priority class to the nice value Cygwin reports for it. +#[cfg(windows)] +fn niceness_from_priority_class(priority_class: u32) -> i32 { + match priority_class { + REALTIME_PRIORITY_CLASS => -20, + HIGH_PRIORITY_CLASS => -16, + ABOVE_NORMAL_PRIORITY_CLASS => -8, + BELOW_NORMAL_PRIORITY_CLASS => 8, + IDLE_PRIORITY_CLASS => 16, + // NORMAL, and the 0 that `GetPriorityClass` returns when it fails. + _ => 0, + } +} + +/// Map a nice value to a Windows priority class, like Cygwin's `setpriority`. +#[cfg(windows)] +fn priority_class_from_niceness(niceness: i32) -> u32 { + match niceness { + ..=-20 => REALTIME_PRIORITY_CLASS, + -19..=-13 => HIGH_PRIORITY_CLASS, + -12..=-5 => ABOVE_NORMAL_PRIORITY_CLASS, + -4..=3 => NORMAL_PRIORITY_CLASS, + 4..=11 => BELOW_NORMAL_PRIORITY_CLASS, + _ => IDLE_PRIORITY_CLASS, + } +} + +/// The priority class the current process runs at. +#[cfg(windows)] +fn get_current_priority_class() -> u32 { + // SAFETY: `GetCurrentProcess` always returns a valid pseudo-handle. + let process = unsafe { GetCurrentProcess() }; + + // SAFETY: `process` is a valid handle for querying the priority class. + unsafe { GetPriorityClass(process) } +} + #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { let args = standardize_nice_args(args); @@ -106,8 +143,10 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let matches = uucore::clap_localization::handle_clap_result_with_exit_code(uu_app(), args, 125)?; - #[cfg(not(unix))] - let current_niceness = 0i32; // todo: what we can do? + #[cfg(not(any(unix, windows)))] + let current_niceness = 0i32; + #[cfg(windows)] + let current_niceness = niceness_from_priority_class(get_current_priority_class()); #[cfg(unix)] let current_niceness = rustix::process::getpriority_process(None) .map_err(|e| uucore::error::USimpleError::new(125, format!("getpriority: {e}")))?; @@ -157,14 +196,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } #[cfg(windows)] - let priority_class = match new_niceness { - ..=-20 => REALTIME_PRIORITY_CLASS, - -19 => HIGH_PRIORITY_CLASS, - -18..=-1 => ABOVE_NORMAL_PRIORITY_CLASS, - 0 => NORMAL_PRIORITY_CLASS, - 1..=18 => BELOW_NORMAL_PRIORITY_CLASS, - 19.. => IDLE_PRIORITY_CLASS, - }; + let priority_class = priority_class_from_niceness(new_niceness); let cmd = cmd_iter.next().unwrap(); let args: Vec<&String> = cmd_iter.collect(); @@ -173,8 +205,21 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { #[cfg(unix)] let err = command.exec(); #[cfg(windows)] - let Err(err) = command.creation_flags(priority_class).spawn() else { - return Ok(()); + let err = match command.creation_flags(priority_class).spawn() { + Ok(mut child) => { + return match child.wait() { + Ok(status) => { + set_exit_code(status.code().unwrap_or(1)); + Ok(()) + } + Err(e) => { + show_error!("{cmd}: {e}"); + set_exit_code(126); + Ok(()) + } + }; + } + Err(err) => err, }; #[cfg(not(any(unix, windows)))] let Err(err) = command.status() else { diff --git a/tests/by-util/test_nice.rs b/tests/by-util/test_nice.rs index 1e1a69dca3c..36355c6ce9e 100644 --- a/tests/by-util/test_nice.rs +++ b/tests/by-util/test_nice.rs @@ -5,8 +5,10 @@ // spell-checker:ignore getpriority setpriority -#[cfg(unix)] +#[cfg(any(unix, windows))] use uutests::new_ucmd; +#[cfg(windows)] +use uutests::util::get_tests_binary; #[test] #[cfg(unix)] @@ -133,3 +135,77 @@ fn test_sign_middle() { //uu: "-2+4" is not a valid number: invalid digit found in string //gnu: invalid adjustment `-2+4' //Both message is fine + +/// The nice values Cygwin reports for the six Windows priority classes. +#[cfg(windows)] +const CYGWIN_NICENESS_VALUES: [i32; 6] = [-20, -16, -8, 0, 8, 16]; + +#[test] +#[cfg(windows)] +fn test_get_current_niceness_windows() { + let niceness: i32 = new_ucmd!() + .succeeds() + .stdout_str() + .trim() + .parse() + .expect("nice should print the current niceness"); + assert!( + CYGWIN_NICENESS_VALUES.contains(&niceness), + "{niceness} is not the niceness of a Windows priority class" + ); +} + +#[test] +#[cfg(windows)] +fn test_nice_reports_the_priority_class_it_set_windows() { + // `-n` adds to the current niceness, so read it first to ask for an + // absolute value regardless of the class the test runner is at. + let current: i32 = new_ucmd!() + .succeeds() + .stdout_str() + .trim() + .parse() + .expect("nice should print the current niceness"); + + // Both sides of every boundary of Cygwin's table, with the niceness it + // reports for that class. REALTIME is left out: it needs elevation. + for (niceness, reported) in [ + (-19, -16), + (-13, -16), + (-12, -8), + (-5, -8), + (-4, 0), + (3, 0), + (4, 8), + (11, 8), + (12, 16), + (19, 16), + ] { + new_ucmd!() + .args(&[ + "-n", + &(niceness - current).to_string(), + get_tests_binary(), + "nice", + ]) + .succeeds() + .stdout_is(format!("{reported}\n")); + } +} + +#[test] +#[cfg(windows)] +fn test_exit_status_of_command_windows() { + new_ucmd!() + .args(&["-n", "0", get_tests_binary(), "false"]) + .fails_with_code(1); +} + +#[test] +#[cfg(windows)] +fn test_missing_command_windows() { + new_ucmd!() + .args(&["-n", "0", "this-command-does-not-exist"]) + .fails_with_code(127) + .no_stdout(); +}