#![allow(non_snake_case)]
#![allow(non_upper_case_globals)]
#![allow(dead_code)]
use core::any::Any;
use std::sync::atomic::{AtomicBool, Ordering};
use crate::ported::functionbar::FunctionBar_newEnterEsc;
use crate::ported::listitem::ListItem;
use crate::ported::object::{Object, Object_isA};
use crate::ported::panel::{Panel, Panel_add, Panel_new, Panel_setHeader, Panel_setSelected};
use crate::ported::process::{Process, Process_class};
#[cfg(target_os = "linux")]
use crate::ported::listitem::ListItem_new;
#[cfg(target_os = "linux")]
use crate::ported::process::Process_getPid;
const SCHED_OTHER: i32 = 0;
const SCHED_FIFO: i32 = 1;
const SCHED_RR: i32 = 2;
const SCHED_BATCH: i32 = 3;
const SCHED_IDLE: i32 = 5;
const SCHED_DEADLINE: i32 = 6;
const SCHED_RESET_ON_FORK: i32 = 0x4000_0000;
struct SchedulingPolicy {
name: Option<&'static str>,
id: i32,
prioritySupport: bool,
}
static policies: [SchedulingPolicy; 6] = [
SchedulingPolicy {
name: Some("Other"),
id: SCHED_OTHER,
prioritySupport: false,
},
SchedulingPolicy {
name: Some("FiFo"),
id: SCHED_FIFO,
prioritySupport: true,
},
SchedulingPolicy {
name: Some("RoundRobin"),
id: SCHED_RR,
prioritySupport: true,
},
SchedulingPolicy {
name: Some("Batch"),
id: SCHED_BATCH,
prioritySupport: false,
},
SchedulingPolicy {
name: None,
id: 0,
prioritySupport: false,
},
SchedulingPolicy {
name: Some("Idle"),
id: SCHED_IDLE,
prioritySupport: false,
},
];
static reset_on_fork: AtomicBool = AtomicBool::new(false);
pub struct SchedulingArg {
pub policy: i32,
pub priority: i32,
}
pub fn Scheduling_newPolicyPanel(preSelectedPolicy: i32) -> Panel {
let mut this = Panel_new(
0,
0,
0,
0,
Some(FunctionBar_newEnterEsc("Select ", "Cancel ")),
);
Panel_setHeader(&mut this, "New policy:");
let rof = reset_on_fork.load(Ordering::Relaxed);
Panel_add(
&mut this,
Box::new(ListItem {
value: if rof {
"Reset on fork: on".to_string()
} else {
"Reset on fork: off".to_string()
},
key: -1,
moving: false,
}),
);
for i in 0..policies.len() {
let name = match policies[i].name {
Some(n) => n,
None => continue,
};
Panel_add(
&mut this,
Box::new(ListItem {
value: name.to_string(),
key: policies[i].id,
moving: false,
}),
);
if policies[i].id == preSelectedPolicy {
Panel_setSelected(&mut this, i as i32);
}
}
this
}
pub fn Scheduling_togglePolicyPanelResetOnFork(schedPanel: &mut Panel) {
let rof = !reset_on_fork.load(Ordering::Relaxed);
reset_on_fork.store(rof, Ordering::Relaxed);
let item = (schedPanel.items[0].object_mut() as &mut dyn Any)
.downcast_mut::<ListItem>()
.expect("Scheduling_togglePolicyPanelResetOnFork: panel row 0 is not a ListItem");
item.value = if rof {
"Reset on fork: on".to_string()
} else {
"Reset on fork: off".to_string()
};
}
pub fn Scheduling_newPriorityPanel(policy: i32, preSelectedPriority: i32) -> Option<Panel> {
if policy < 0 || policy as usize >= policies.len() || policies[policy as usize].name.is_none() {
return None;
}
if !policies[policy as usize].prioritySupport {
return None;
}
#[cfg(target_os = "linux")]
{
let min = unsafe { libc::sched_get_priority_min(policy) };
if min < 0 {
return None;
}
let max = unsafe { libc::sched_get_priority_max(policy) };
if max < 0 {
return None;
}
let mut this = Panel_new(
0,
0,
0,
0,
Some(FunctionBar_newEnterEsc("Select ", "Cancel ")),
);
Panel_setHeader(&mut this, "Priority:");
for i in min..=max {
let buf = format!("{}", i);
Panel_add(&mut this, Box::new(ListItem_new(&buf, i)));
if i == preSelectedPriority {
Panel_setSelected(&mut this, i);
}
}
Some(this)
}
#[cfg(not(target_os = "linux"))]
{
let _ = preSelectedPriority;
None
}
}
pub fn Scheduling_setPolicy(p: &Process, arg: &SchedulingArg) -> bool {
let sarg = arg;
let policy = sarg.policy;
debug_assert!(policy >= 0);
debug_assert!((policy as usize) < policies.len());
debug_assert!(policies[policy as usize].name.is_some());
#[cfg(target_os = "linux")]
{
let param = libc::sched_param {
sched_priority: if policies[policy as usize].prioritySupport {
sarg.priority
} else {
0
},
};
let mut policy = policy;
if reset_on_fork.load(Ordering::Relaxed) {
policy &= SCHED_RESET_ON_FORK;
}
let r = unsafe { libc::sched_setscheduler(Process_getPid(p), policy, ¶m) };
r != -1
}
#[cfg(not(target_os = "linux"))]
{
let _ = p;
false
}
}
pub fn Scheduling_rowSetPolicy(row: &dyn Object, arg: &SchedulingArg) -> bool {
debug_assert!(Object_isA(Some(row), &Process_class));
let any: &dyn Any = row;
let p = any
.downcast_ref::<Process>()
.expect("Scheduling_rowSetPolicy: row is not a Process");
Scheduling_setPolicy(p, arg)
}
pub fn Scheduling_formatPolicy(policy: i32) -> &'static str {
let policy = policy & !SCHED_RESET_ON_FORK;
match policy {
SCHED_OTHER => "OTHER",
SCHED_FIFO => "FIFO",
SCHED_RR => "RR",
SCHED_BATCH => "BATCH",
SCHED_IDLE => "IDLE",
SCHED_DEADLINE => "EDF",
_ => "???",
}
}
pub fn Scheduling_readProcessPolicy(proc_: &mut Process) {
#[cfg(target_os = "linux")]
{
proc_.scheduling_policy = unsafe { libc::sched_getscheduler(Process_getPid(proc_)) };
}
#[cfg(not(target_os = "linux"))]
{
let _ = proc_;
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Mutex;
static GLOBAL_LOCK: Mutex<()> = Mutex::new(());
fn row_value(p: &Panel, i: usize) -> &str {
let any: &dyn Any = p.items[i].object();
&any.downcast_ref::<ListItem>().unwrap().value
}
#[test]
fn maps_each_known_policy() {
assert_eq!(Scheduling_formatPolicy(SCHED_OTHER), "OTHER");
assert_eq!(Scheduling_formatPolicy(SCHED_FIFO), "FIFO");
assert_eq!(Scheduling_formatPolicy(SCHED_RR), "RR");
assert_eq!(Scheduling_formatPolicy(SCHED_BATCH), "BATCH");
assert_eq!(Scheduling_formatPolicy(SCHED_IDLE), "IDLE");
assert_eq!(Scheduling_formatPolicy(SCHED_DEADLINE), "EDF");
}
#[test]
fn unknown_policy_is_question_marks() {
assert_eq!(Scheduling_formatPolicy(4), "???");
assert_eq!(Scheduling_formatPolicy(7), "???");
assert_eq!(Scheduling_formatPolicy(-1), "???");
}
#[test]
fn reset_on_fork_bit_is_stripped_before_lookup() {
assert_eq!(
Scheduling_formatPolicy(SCHED_FIFO | SCHED_RESET_ON_FORK),
"FIFO"
);
assert_eq!(
Scheduling_formatPolicy(SCHED_OTHER | SCHED_RESET_ON_FORK),
"OTHER"
);
assert_eq!(Scheduling_formatPolicy(SCHED_RESET_ON_FORK), "OTHER");
}
#[test]
fn new_policy_panel_builds_reset_row_then_named_policies() {
let _guard = GLOBAL_LOCK.lock().unwrap();
reset_on_fork.store(false, Ordering::Relaxed);
let p = Scheduling_newPolicyPanel(SCHED_RR);
assert_eq!(p.items.len(), 6);
assert_eq!(row_value(&p, 0), "Reset on fork: off");
assert_eq!(row_value(&p, 1), "Other");
assert_eq!(row_value(&p, 2), "FiFo");
assert_eq!(row_value(&p, 3), "RoundRobin");
assert_eq!(row_value(&p, 4), "Batch");
assert_eq!(row_value(&p, 5), "Idle");
assert_eq!(p.selected, 2);
}
#[test]
fn new_policy_panel_reflects_reset_on_fork_on() {
let _guard = GLOBAL_LOCK.lock().unwrap();
reset_on_fork.store(true, Ordering::Relaxed);
let p = Scheduling_newPolicyPanel(SCHED_OTHER);
assert_eq!(row_value(&p, 0), "Reset on fork: on");
assert_eq!(p.selected, 0);
reset_on_fork.store(false, Ordering::Relaxed);
}
#[test]
fn toggle_flips_global_and_row_zero() {
let _guard = GLOBAL_LOCK.lock().unwrap();
reset_on_fork.store(false, Ordering::Relaxed);
let mut p = Scheduling_newPolicyPanel(SCHED_OTHER);
assert_eq!(row_value(&p, 0), "Reset on fork: off");
Scheduling_togglePolicyPanelResetOnFork(&mut p);
assert!(reset_on_fork.load(Ordering::Relaxed));
assert_eq!(row_value(&p, 0), "Reset on fork: on");
Scheduling_togglePolicyPanelResetOnFork(&mut p);
assert!(!reset_on_fork.load(Ordering::Relaxed));
assert_eq!(row_value(&p, 0), "Reset on fork: off");
}
#[test]
fn priority_panel_rejects_out_of_range_policy() {
assert!(Scheduling_newPriorityPanel(-1, 50).is_none());
assert!(Scheduling_newPriorityPanel(6, 50).is_none());
assert!(Scheduling_newPriorityPanel(100, 50).is_none());
}
#[test]
fn priority_panel_rejects_unnamed_hole() {
assert!(policies[4].name.is_none());
assert!(Scheduling_newPriorityPanel(4, 50).is_none());
}
#[test]
fn priority_panel_rejects_policies_without_priority_support() {
assert!(Scheduling_newPriorityPanel(SCHED_OTHER, 50).is_none());
assert!(Scheduling_newPriorityPanel(SCHED_BATCH, 50).is_none());
assert!(Scheduling_newPriorityPanel(SCHED_IDLE, 50).is_none());
}
#[test]
fn row_set_policy_accepts_a_process() {
let p = Process::default();
let arg = SchedulingArg {
policy: SCHED_OTHER,
priority: 0,
};
let obj: &dyn Object = &p;
let r = Scheduling_rowSetPolicy(obj, &arg);
#[cfg(not(target_os = "linux"))]
assert!(!r);
#[cfg(target_os = "linux")]
let _ = r;
}
#[test]
#[should_panic]
fn row_set_policy_rejects_non_process() {
let item = ListItem {
value: String::new(),
key: 0,
moving: false,
};
let arg = SchedulingArg {
policy: SCHED_OTHER,
priority: 0,
};
let obj: &dyn Object = &item;
let _ = Scheduling_rowSetPolicy(obj, &arg);
}
#[cfg(target_os = "linux")]
#[test]
fn priority_panel_builds_for_fifo_on_linux() {
let min = unsafe { libc::sched_get_priority_min(SCHED_FIFO) };
let max = unsafe { libc::sched_get_priority_max(SCHED_FIFO) };
assert!(min >= 0 && max >= min);
let pre = min; let panel = Scheduling_newPriorityPanel(SCHED_FIFO, pre)
.expect("FIFO priority panel must be Some on Linux");
let expected_rows = (max - min + 1) as usize;
assert_eq!(panel.items.len(), expected_rows);
assert_eq!(row_value(&panel, 0), format!("{}", min));
assert_eq!(panel.selected, min);
}
#[cfg(target_os = "linux")]
#[test]
fn read_process_policy_reads_own_scheduler_on_linux() {
let pid = unsafe { libc::getpid() };
let mut proc_ = Process::default();
proc_.super_.id = pid;
Scheduling_readProcessPolicy(&mut proc_);
let direct = unsafe { libc::sched_getscheduler(pid) };
assert!(direct >= 0);
assert_eq!(proc_.scheduling_policy, direct);
}
#[cfg(target_os = "linux")]
#[test]
fn set_policy_to_other_succeeds_on_linux() {
let pid = unsafe { libc::getpid() };
let mut proc_ = Process::default();
proc_.super_.id = pid;
let arg = SchedulingArg {
policy: SCHED_OTHER,
priority: 0,
};
assert!(Scheduling_setPolicy(&proc_, &arg));
}
}