Skip to content

Commit e0142d9

Browse files
authored
Merge pull request #549 from Bluemangoo/feature/top-tui-sg-action
top: impl `^u` `r` `k`
2 parents a876209 + 0b170d0 commit e0142d9

File tree

6 files changed

+229
-3
lines changed

6 files changed

+229
-3
lines changed

src/uu/top/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ license.workspace = true
1111
version.workspace = true
1212

1313
[dependencies]
14-
uucore = { workspace = true, features = ["utmpx", "uptime"] }
14+
uucore = { workspace = true, features = ["utmpx", "uptime", "signals"] }
1515
clap = { workspace = true }
1616
crossterm = { workspace = true }
1717
libc = { workspace = true }

src/uu/top/src/action.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// This file is part of the uutils procps package.
2+
//
3+
// For the full copyright and license information, please view the LICENSE
4+
// file that was distributed with this source code.
5+
6+
#[cfg(target_os = "linux")]
7+
pub(crate) fn renice(pid: u32, nice_value: i32) -> uucore::error::UResult<()> {
8+
use libc::{setpriority, PRIO_PROCESS};
9+
use uucore::error::USimpleError;
10+
11+
let result = unsafe { setpriority(PRIO_PROCESS, pid, nice_value) };
12+
if result == -1 {
13+
Err(USimpleError::new(0, "Permission Denied"))
14+
} else {
15+
Ok(())
16+
}
17+
}
18+
19+
#[cfg(unix)]
20+
pub(crate) fn kill_process(pid: u32, sig: usize) -> uucore::error::UResult<()> {
21+
use nix::sys::signal;
22+
use nix::sys::signal::Signal;
23+
use nix::unistd::Pid;
24+
use uucore::error::USimpleError;
25+
26+
signal::kill(Pid::from_raw(pid as i32), Signal::try_from(sig as i32)?)
27+
.map_err(|_| USimpleError::new(0, "Permission Denied"))
28+
}

src/uu/top/src/picker.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -432,6 +432,32 @@ fn mem(pid: u32, _stat: Stat) -> Box<dyn Column> {
432432
)
433433
}
434434

435+
#[cfg(target_os = "linux")]
436+
pub(crate) fn get_supplementary_groups(pid: u32) -> String {
437+
use sysinfo::{Gid, Groups};
438+
439+
let groups = Groups::new_with_refreshed_list();
440+
let path = PathBuf::from_str(&format!("/proc/{pid}/status")).unwrap();
441+
if let Ok(file) = File::open(path) {
442+
let content = read_to_string(file).unwrap();
443+
for line in content.lines() {
444+
if line.starts_with("Groups:") {
445+
let groups = line
446+
.split_whitespace()
447+
.skip(1)
448+
.filter_map(|s| Gid::from_str(s).ok())
449+
.filter_map(|gid| groups.iter().find(|g| g.id() == &gid))
450+
.map(|group| group.name())
451+
.collect::<Vec<_>>()
452+
.join(",");
453+
return groups;
454+
}
455+
}
456+
}
457+
String::new()
458+
}
459+
460+
#[cfg(target_os = "linux")]
435461
pub(crate) fn get_cgroup(pid: u32) -> String {
436462
let path = PathBuf::from_str(&format!("/proc/{pid}/cgroup")).unwrap();
437463
if let Ok(file) = File::open(path) {

src/uu/top/src/top.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ use uucore::{
2424
const ABOUT: &str = help_about!("top.md");
2525
const USAGE: &str = help_usage!("top.md");
2626

27+
mod action;
2728
mod field;
2829
mod header;
2930
mod picker;

src/uu/top/src/tui/input.rs

Lines changed: 170 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
// file that was distributed with this source code.
55

66
use crate::header::Header;
7-
use crate::picker::{get_cgroup, get_command};
7+
use crate::picker::get_command;
88
use crate::platform::get_numa_nodes;
99
use crate::tui::stat::{CpuValueMode, TuiStat};
1010
use crate::Filter::{EUser, User};
@@ -26,6 +26,14 @@ pub(crate) enum InputEvent {
2626
FilterEUser,
2727
WidthIncrement,
2828
Delay,
29+
#[cfg(target_os = "linux")]
30+
ReniceProc,
31+
#[cfg(target_os = "linux")]
32+
ReniceValue,
33+
#[cfg(unix)]
34+
KillProc,
35+
#[cfg(unix)]
36+
KillSignal,
2937
}
3038

3139
macro_rules! char {
@@ -98,6 +106,7 @@ pub fn handle_input(
98106
data.write().unwrap().1 = ProcList::new(settings, &tui_stat.read().unwrap());
99107
should_update.store(true, Ordering::Relaxed);
100108
}
109+
#[cfg(target_os = "linux")]
101110
Event::Key(KeyEvent {
102111
code: KeyCode::Char('g'),
103112
modifiers: KeyModifiers::CONTROL,
@@ -121,7 +130,7 @@ pub fn handle_input(
121130
pid,
122131
get_command(pid, false)
123132
);
124-
let content = get_cgroup(pid);
133+
let content = crate::picker::get_cgroup(pid);
125134
data.2 = Some(InfoBar { title, content });
126135
}
127136
should_update.store(true, Ordering::Relaxed);
@@ -164,6 +173,25 @@ pub fn handle_input(
164173
}
165174
should_update.store(true, Ordering::Relaxed);
166175
}
176+
#[cfg(unix)]
177+
char!('k') => {
178+
let data = data.read().unwrap();
179+
let mut tui_stat = tui_stat.write().unwrap();
180+
let mut nth = tui_stat.list_offset;
181+
if data.1.collected.is_empty() {
182+
return false;
183+
}
184+
if data.1.collected.len() <= nth {
185+
nth = data.1.collected.len() - 1;
186+
}
187+
let pid = data.1.collected[nth].0;
188+
tui_stat.input_value.clear();
189+
tui_stat.input_label = format!("PID to signal/kill [default pid = {}]", pid);
190+
tui_stat.selected_process = Some(pid);
191+
tui_stat.input_mode = InputMode::Input(InputEvent::KillProc);
192+
193+
should_update.store(true, Ordering::Relaxed);
194+
}
167195
char!('l') => {
168196
let mut stat = tui_stat.write().unwrap();
169197
stat.show_load_avg = !stat.show_load_avg;
@@ -194,11 +222,59 @@ pub fn handle_input(
194222
data.write().unwrap().1 = ProcList::new(settings, &tui_stat.read().unwrap());
195223
should_update.store(true, Ordering::Relaxed);
196224
}
225+
#[cfg(target_os = "linux")]
226+
char!('r') => {
227+
let data = data.read().unwrap();
228+
let mut tui_stat = tui_stat.write().unwrap();
229+
let mut nth = tui_stat.list_offset;
230+
if data.1.collected.is_empty() {
231+
return false;
232+
}
233+
if data.1.collected.len() <= nth {
234+
nth = data.1.collected.len() - 1;
235+
}
236+
let pid = data.1.collected[nth].0;
237+
tui_stat.input_value.clear();
238+
tui_stat.input_label = format!("PID to renice [default pid = {}]", pid);
239+
tui_stat.selected_process = Some(pid);
240+
tui_stat.input_mode = InputMode::Input(InputEvent::ReniceProc);
241+
242+
should_update.store(true, Ordering::Relaxed);
243+
}
197244
char!('t') => {
198245
let mut stat = tui_stat.write().unwrap();
199246
stat.cpu_graph_mode = stat.cpu_graph_mode.next();
200247
should_update.store(true, Ordering::Relaxed);
201248
}
249+
#[cfg(target_os = "linux")]
250+
Event::Key(KeyEvent {
251+
code: KeyCode::Char('u'),
252+
modifiers: KeyModifiers::CONTROL,
253+
..
254+
}) => {
255+
let mut data = data.write().unwrap();
256+
if data.2.is_some() {
257+
data.2 = None;
258+
} else {
259+
let tui_stat = tui_stat.read().unwrap();
260+
let mut nth = tui_stat.list_offset;
261+
if data.1.collected.is_empty() {
262+
return false;
263+
}
264+
if data.1.collected.len() <= nth {
265+
nth = data.1.collected.len() - 1;
266+
}
267+
let pid = data.1.collected[nth].0;
268+
let title = format!(
269+
"supplementary groups for pid {}, {}",
270+
pid,
271+
get_command(pid, false)
272+
);
273+
let content = crate::picker::get_supplementary_groups(pid);
274+
data.2 = Some(InfoBar { title, content });
275+
}
276+
should_update.store(true, Ordering::Relaxed);
277+
}
202278
char!('U') => {
203279
let mut stat = tui_stat.write().unwrap();
204280
stat.input_label = "Which user (blank for all) ".into();
@@ -509,5 +585,97 @@ fn handle_input_value(
509585
stat.reset_input();
510586
should_update.store(true, Ordering::Relaxed);
511587
}
588+
#[cfg(target_os = "linux")]
589+
InputEvent::ReniceProc => {
590+
let input_value = { tui_stat.read().unwrap().input_value.parse::<u32>() };
591+
let mut stat = tui_stat.write().unwrap();
592+
if let Ok(pid) = input_value {
593+
stat.selected_process = Some(pid);
594+
} else {
595+
let is_empty = stat.input_value.trim().is_empty();
596+
stat.reset_input();
597+
if !is_empty {
598+
stat.input_message = Some(" Unacceptable integer ".into());
599+
should_update.store(true, Ordering::Relaxed);
600+
return;
601+
}
602+
};
603+
stat.input_value.clear();
604+
stat.input_label = format!("Renice pid {} to value", stat.selected_process.unwrap());
605+
stat.input_mode = InputMode::Input(InputEvent::ReniceValue);
606+
should_update.store(true, Ordering::Relaxed);
607+
}
608+
#[cfg(target_os = "linux")]
609+
InputEvent::ReniceValue => {
610+
let mut stat = tui_stat.write().unwrap();
611+
let input_value = stat.input_value.parse::<i32>();
612+
stat.input_mode = InputMode::Command;
613+
stat.reset_input();
614+
if input_value.is_err() || input_value.as_ref().is_ok_and(|v| *v < -20 || *v > 19) {
615+
let is_empty = { tui_stat.read().unwrap().input_value.trim().is_empty() };
616+
let mut stat = tui_stat.write().unwrap();
617+
if !is_empty {
618+
stat.input_message = Some(" Unacceptable nice value ".into());
619+
}
620+
should_update.store(true, Ordering::Relaxed);
621+
return;
622+
}
623+
let input_value = input_value.unwrap();
624+
let pid = stat.selected_process.unwrap();
625+
if let Err(e) = crate::action::renice(pid, input_value) {
626+
stat.input_message = Some(format!(
627+
" Failed renice of PID {} to {}: {} ",
628+
pid, input_value, e
629+
));
630+
}
631+
should_update.store(true, Ordering::Relaxed);
632+
}
633+
#[cfg(unix)]
634+
InputEvent::KillProc => {
635+
let input_value = { tui_stat.read().unwrap().input_value.parse::<u32>() };
636+
let mut stat = tui_stat.write().unwrap();
637+
if let Ok(pid) = input_value {
638+
stat.selected_process = Some(pid);
639+
} else {
640+
let is_empty = stat.input_value.trim().is_empty();
641+
stat.reset_input();
642+
if !is_empty {
643+
stat.input_message = Some(" Unacceptable integer ".into());
644+
should_update.store(true, Ordering::Relaxed);
645+
return;
646+
}
647+
};
648+
stat.input_value.clear();
649+
stat.input_label = format!(
650+
"Send pid {} signal [15/sigterm]",
651+
stat.selected_process.unwrap()
652+
);
653+
stat.input_mode = InputMode::Input(InputEvent::KillSignal);
654+
should_update.store(true, Ordering::Relaxed);
655+
}
656+
#[cfg(unix)]
657+
InputEvent::KillSignal => {
658+
use uucore::signals::signal_by_name_or_value;
659+
let mut stat = tui_stat.write().unwrap();
660+
stat.input_mode = InputMode::Command;
661+
stat.reset_input();
662+
let signal = if stat.input_value.is_empty() {
663+
15
664+
} else if let Some(sig) = signal_by_name_or_value(&stat.input_value) {
665+
sig
666+
} else {
667+
stat.input_message = Some(" Unacceptable signal value".into());
668+
should_update.store(true, Ordering::Relaxed);
669+
return;
670+
};
671+
let pid = stat.selected_process.unwrap();
672+
if let Err(e) = crate::action::kill_process(pid, signal) {
673+
stat.input_message = Some(format!(
674+
" Failed signal pid {} with {}: {} ",
675+
pid, signal, e
676+
));
677+
}
678+
should_update.store(true, Ordering::Relaxed);
679+
}
512680
}
513681
}

src/uu/top/src/tui/stat.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ pub(crate) struct TuiStat {
1212
pub input_label: String,
1313
pub input_value: String,
1414
pub input_message: Option<String>, // Info or error
15+
#[allow(dead_code)]
16+
pub selected_process: Option<u32>,
1517

1618
pub show_load_avg: bool,
1719
pub cpu_graph_mode: CpuGraphMode,
@@ -51,6 +53,7 @@ impl TuiStat {
5153
input_label: String::new(),
5254
input_value: String::new(),
5355
input_message: None,
56+
selected_process: None,
5457

5558
show_load_avg: true,
5659
cpu_graph_mode: CpuGraphMode::default(),

0 commit comments

Comments
 (0)