Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion src/uu/nice/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
73 changes: 59 additions & 14 deletions src/uu/nice/src/nice.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -99,15 +99,54 @@ 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);

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}")))?;
Expand Down Expand Up @@ -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();
Expand All @@ -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 {
Expand Down
78 changes: 77 additions & 1 deletion tests/by-util/test_nice.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down Expand Up @@ -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();
}
Loading