Initial commit
This commit is contained in:
16
src/canary.rs
Normal file
16
src/canary.rs
Normal file
@@ -0,0 +1,16 @@
|
||||
/// The canary thread needs to ensure that it's not disturbed by signals,
|
||||
/// in order to get an accurate reading of scheduler starvation. This function
|
||||
/// will disable all signals for the current thread.
|
||||
fn disable_signals() {}
|
||||
|
||||
// The canary thread polls in a loop and measures the time between each time it
|
||||
// gets rescheduled. If the time between two reschedules is too long, the canary
|
||||
// thread will report this to the main thread so it can start reducing the realtime
|
||||
// privileges of the supervised processes.
|
||||
//
|
||||
// Or is it the watchdog thread that does this?
|
||||
fn canary_thread() {}
|
||||
|
||||
fn watchdog_thread() {}
|
||||
|
||||
// CancellationToken
|
||||
145
src/cli.rs
Normal file
145
src/cli.rs
Normal file
@@ -0,0 +1,145 @@
|
||||
use clap::Parser;
|
||||
|
||||
use crate::nix::{SchedPolicy, SchedType};
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
pub struct Args {
|
||||
/// Log to STDERR in addition to journald
|
||||
#[arg(long)]
|
||||
pub stderr: bool,
|
||||
|
||||
/// Run daemon as user
|
||||
#[arg(long, default_value = "rtkit", value_name = "USER")]
|
||||
pub user_name: String,
|
||||
|
||||
/// Choose scheduling policy
|
||||
#[arg(long, default_value_t = SchedulingPolicy::Rr)]
|
||||
pub scheduling_policy: SchedulingPolicy,
|
||||
|
||||
/// Realtime priority for the daemon
|
||||
#[arg(long, default_value = "0", value_name = "[0..99]")]
|
||||
pub our_realtime_priority: u32,
|
||||
|
||||
/// Nice level for the daemon
|
||||
#[arg(long, default_value = "0", value_name = "[-20..19]")]
|
||||
pub our_nice_level: i32,
|
||||
|
||||
/// Max realtime priority for clients
|
||||
#[arg(long, default_value = "99", value_name = "[0..99]")]
|
||||
pub max_realtime_priority: u32,
|
||||
|
||||
/// Min nice level for clients
|
||||
#[arg(long, default_value = "-20", value_name = "[-20..19]")]
|
||||
pub min_nice_level: i32,
|
||||
|
||||
/// Require clients to have set RLIMIT_RTTIME not greater than this
|
||||
#[arg(long, value_name = "USEC")]
|
||||
pub rttime_usec_max: Option<u64>,
|
||||
|
||||
/// How many users this daemon will serve at max at the same time
|
||||
#[arg(long)]
|
||||
pub users_max: Option<u32>,
|
||||
|
||||
/// How many processes this daemon will serve at max per user at the same time
|
||||
#[arg(long)]
|
||||
pub processes_per_user_max: Option<u32>,
|
||||
|
||||
/// How many threads this daemon will serve at max per user at the same time
|
||||
#[arg(long)]
|
||||
pub threads_per_user_max: Option<u32>,
|
||||
|
||||
/// Enforce requests limits in this time
|
||||
#[arg(long, value_name = "SEC")]
|
||||
pub actions_burst_sec: Option<u32>,
|
||||
|
||||
/// Allow this many requests per burst
|
||||
#[arg(long)]
|
||||
pub actions_per_burst_max: Option<u32>,
|
||||
|
||||
/// Canary cheep interval
|
||||
#[arg(long, default_value = "5000", value_name = "MILLISEC")]
|
||||
pub canary_cheep_msec: u32,
|
||||
|
||||
/// Watchdog action delay
|
||||
#[arg(long, default_value = "10000", value_name = "MILLISEC")]
|
||||
pub canary_watchdog_msec: u32,
|
||||
|
||||
/// When the canary dies demote unknown processes too?
|
||||
#[arg(long)]
|
||||
pub canary_demote_unknown: bool,
|
||||
|
||||
/// When the canary dies demote root processes too?
|
||||
#[arg(long)]
|
||||
pub canary_demote_root: bool,
|
||||
|
||||
/// How long to refuse further requests after the canary died
|
||||
#[arg(long, default_value = "300", value_name = "SEC")]
|
||||
pub canary_refuse_sec: u32,
|
||||
|
||||
/// Don't run a canary-based RT watchdog
|
||||
#[arg(long)]
|
||||
pub no_canary: bool,
|
||||
|
||||
/// Don't drop privileges
|
||||
#[arg(long)]
|
||||
pub no_drop_privileges: bool,
|
||||
|
||||
/// Don't chroot
|
||||
#[arg(long)]
|
||||
pub no_chroot: bool,
|
||||
|
||||
/// Don't limit daemon's resources
|
||||
#[arg(long)]
|
||||
pub no_limit_resources: bool,
|
||||
}
|
||||
|
||||
impl Args {
|
||||
pub fn proc_dir(&self) -> &str {
|
||||
if self.no_chroot {
|
||||
"/proc"
|
||||
} else {
|
||||
"/"
|
||||
}
|
||||
}
|
||||
|
||||
pub fn scheduling_policy(&self) -> SchedPolicy {
|
||||
let sched_type: SchedType = self.scheduling_policy.into();
|
||||
SchedPolicy::new(sched_type)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
pub enum SchedulingPolicy {
|
||||
Fifo,
|
||||
Rr,
|
||||
}
|
||||
|
||||
impl std::str::FromStr for SchedulingPolicy {
|
||||
type Err = String;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"FIFO" | "fifo" => Ok(Self::Fifo),
|
||||
"RR" | "rr" => Ok(Self::Rr),
|
||||
_ => Err(format!("unknown scheduling policy: {}", s)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for SchedulingPolicy {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Fifo => write!(f, "FIFO"),
|
||||
Self::Rr => write!(f, "RR"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<SchedType> for SchedulingPolicy {
|
||||
fn into(self) -> SchedType {
|
||||
match self {
|
||||
Self::Fifo => SchedType::SCHED_FIFO,
|
||||
Self::Rr => SchedType::SCHED_RR,
|
||||
}
|
||||
}
|
||||
}
|
||||
135
src/core.rs
Normal file
135
src/core.rs
Normal file
@@ -0,0 +1,135 @@
|
||||
use nix::unistd::{Pid, Uid};
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
use crate::{
|
||||
cli::Args, nix::{sched_get_priority_max, sched_get_priority_min}, self_drop_realtime, self_set_realtime
|
||||
};
|
||||
|
||||
struct User {
|
||||
uid: Uid,
|
||||
timestamp: u64,
|
||||
n_actions: u32,
|
||||
processes: Vec<Process>,
|
||||
}
|
||||
|
||||
struct Process {
|
||||
pid: Pid,
|
||||
starttime: u64,
|
||||
threads: Vec<Thread>,
|
||||
}
|
||||
|
||||
struct Thread {
|
||||
tid: Pid,
|
||||
starttime: u64,
|
||||
}
|
||||
|
||||
struct State {
|
||||
// TODO:
|
||||
}
|
||||
|
||||
impl State {
|
||||
fn make_thread_realtime_with_pid(
|
||||
&mut self,
|
||||
args: &Args,
|
||||
user: User,
|
||||
pid: Pid,
|
||||
tid: Pid,
|
||||
priority: u32,
|
||||
) {
|
||||
if sched_get_priority_min(args.scheduling_policy())
|
||||
.is_ok_and(|min| priority < u32::try_from(min).unwrap())
|
||||
|| sched_get_priority_max(args.scheduling_policy())
|
||||
.is_ok_and(|max| priority > u32::try_from(max).unwrap())
|
||||
{
|
||||
todo!("bail EINVAL")
|
||||
}
|
||||
|
||||
// We always want to be able to get a higher RT priority than the client
|
||||
if priority >= args.our_realtime_priority
|
||||
|| priority > args.max_realtime_priority
|
||||
{
|
||||
todo!("bail EPERM")
|
||||
}
|
||||
|
||||
// Make sure users don't flood us with requests
|
||||
if !verify_burst(&user) {
|
||||
todo!("bail EBUSY")
|
||||
}
|
||||
|
||||
// TODO: implement
|
||||
|
||||
self_drop_realtime(args.our_nice_level);
|
||||
}
|
||||
|
||||
fn make_thread_high_priority_with_pid(&mut self) {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn reset_known(&mut self) {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn reset_all(&mut self) {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn exit(&mut self) {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
// Polkit
|
||||
|
||||
// Unix process lookup
|
||||
|
||||
fn read_start_time(pid: Pid, tid: Pid) -> u64 {
|
||||
unimplemented!("Read /proc and get the start time of the process/thread")
|
||||
}
|
||||
|
||||
fn verify_burst(user: &User) -> bool {
|
||||
unimplemented!("Check if the user has issued more than ACTIONS_PER_BURST_MAX in this time")
|
||||
}
|
||||
|
||||
// TODO: make into impls
|
||||
|
||||
// fn find_user(users: &[User], uid: Uid) -> Option<&User> {
|
||||
// unimplemented!("Find the user with the given UID")
|
||||
// }
|
||||
|
||||
// fn find_process(user: &User, pid: Pid) -> Option<&Process> {
|
||||
// unimplemented!("Find the process with the given PID")
|
||||
// }
|
||||
|
||||
// fn find_thread(process: &Process, tid: Pid) -> Option<&Thread> {
|
||||
// unimplemented!("Find the thread with the given TID")
|
||||
// }
|
||||
|
||||
// fn thread_gc(process: &mut Process) {
|
||||
// unimplemented!("Garbage collect threads")
|
||||
// }
|
||||
|
||||
// fn process_gc(user: &mut User) {
|
||||
// unimplemented!("Garbage collect processes")
|
||||
// }
|
||||
|
||||
// fn user_gc(users: &mut Vec<User>) {
|
||||
// unimplemented!("Garbage collect users")
|
||||
// }
|
||||
|
||||
/* This checks if a thread still matters to us, i.e. if its
|
||||
* PID still refers to the same thread and if it is still high
|
||||
* priority or real time */
|
||||
fn thread_relevant(pid: Pid, tid: Pid) -> bool {
|
||||
unimplemented!("Check if the thread is still relevant")
|
||||
}
|
||||
|
||||
/* Verifies that RLIMIT_RTTIME is set for the specified process */
|
||||
fn verify_process_rttime(process: &Process) -> Result<()> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
// Ensure that the user owns the process
|
||||
fn verify_process_user(process: &Process, user: &User) -> Result<()> {
|
||||
unimplemented!()
|
||||
}
|
||||
10
src/dbus.rs
Normal file
10
src/dbus.rs
Normal file
@@ -0,0 +1,10 @@
|
||||
use anyhow::Result;
|
||||
|
||||
const RTKIT_DBUS_SERVICE_NAME: &str = "org.freedesktop.RealtimeKit1";
|
||||
const RTKIT_DBUS_OBJECT_PATH: &str = "/org/freedesktop/RealtimeKit1";
|
||||
|
||||
// pub fn setup_dbus() -> Result<> {
|
||||
|
||||
// }
|
||||
|
||||
// pub fn dbus_handler()
|
||||
47
src/main.rs
Normal file
47
src/main.rs
Normal file
@@ -0,0 +1,47 @@
|
||||
mod canary;
|
||||
mod cli;
|
||||
mod core;
|
||||
mod dbus;
|
||||
mod privileges;
|
||||
// mod sched;
|
||||
mod nix;
|
||||
|
||||
use cli::Args;
|
||||
|
||||
use clap::Parser;
|
||||
|
||||
use privileges::*;
|
||||
|
||||
use anyhow::{bail, Result, Context};
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let args = Args::parse();
|
||||
|
||||
if !self_is_root() {
|
||||
bail!("rtkit must be run as root");
|
||||
}
|
||||
|
||||
// TODO: initialize logging
|
||||
|
||||
self_raise_timer_slack(&args).context("failed to raise our own timer slack")?;
|
||||
|
||||
self_drop_realtime(args.our_nice_level).context("failed to drop our own realtime scheduler")?;
|
||||
|
||||
// setup_dbus();
|
||||
|
||||
self_drop_privileges(&args).context("failed to drop our own privileges")?;
|
||||
|
||||
if !args.no_limit_resources {
|
||||
set_resource_limits(&args).context("failed to set resource limits")?;
|
||||
}
|
||||
|
||||
// let canary_handle = start_canary();
|
||||
|
||||
// let watchdog_handle = start_watchdog();
|
||||
|
||||
self_reduce_umask();
|
||||
|
||||
// start_dbus();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
10
src/nix.rs
Normal file
10
src/nix.rs
Normal file
@@ -0,0 +1,10 @@
|
||||
mod macros;
|
||||
mod priority;
|
||||
mod sched;
|
||||
|
||||
pub use sched::{
|
||||
sched_get_priority_max, sched_get_priority_min, sched_getscheduler, sched_setscheduler,
|
||||
SchedFlags, SchedParam, SchedPolicy, SchedType,
|
||||
};
|
||||
|
||||
pub use priority::{getpriority, setpriority, PriorityKind};
|
||||
322
src/nix/macros.rs
Normal file
322
src/nix/macros.rs
Normal file
@@ -0,0 +1,322 @@
|
||||
/// Borrowed from https://github.com/nix-rust/nix/blob/master/src/macros.rs
|
||||
|
||||
/// The `libc_bitflags!` macro helps with a common use case of defining a public bitflags type
|
||||
/// with values from the libc crate. It is used the same way as the `bitflags!` macro, except
|
||||
/// that only the name of the flag value has to be given.
|
||||
///
|
||||
/// The `libc` crate must be in scope with the name `libc`.
|
||||
///
|
||||
/// # Example
|
||||
/// ```ignore
|
||||
/// libc_bitflags!{
|
||||
/// pub struct ProtFlags: libc::c_int {
|
||||
/// PROT_NONE;
|
||||
/// PROT_READ;
|
||||
/// /// PROT_WRITE enables write protect
|
||||
/// PROT_WRITE;
|
||||
/// PROT_EXEC;
|
||||
/// #[cfg(linux_android)]
|
||||
/// PROT_GROWSDOWN;
|
||||
/// #[cfg(linux_android)]
|
||||
/// PROT_GROWSUP;
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Example with casting, due to a mistake in libc. In this example, the
|
||||
/// various flags have different types, so we cast the broken ones to the right
|
||||
/// type.
|
||||
///
|
||||
/// ```ignore
|
||||
/// libc_bitflags!{
|
||||
/// pub struct SaFlags: libc::c_ulong {
|
||||
/// SA_NOCLDSTOP as libc::c_ulong;
|
||||
/// SA_NOCLDWAIT;
|
||||
/// SA_NODEFER as libc::c_ulong;
|
||||
/// SA_ONSTACK;
|
||||
/// SA_RESETHAND as libc::c_ulong;
|
||||
/// SA_RESTART as libc::c_ulong;
|
||||
/// SA_SIGINFO;
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
macro_rules! libc_bitflags {
|
||||
(
|
||||
$(#[$outer:meta])*
|
||||
pub struct $BitFlags:ident: $T:ty {
|
||||
$(
|
||||
$(#[$inner:ident $($args:tt)*])*
|
||||
$Flag:ident $(as $cast:ty)*;
|
||||
)+
|
||||
}
|
||||
) => {
|
||||
::bitflags::bitflags! {
|
||||
#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
||||
#[repr(transparent)]
|
||||
$(#[$outer])*
|
||||
pub struct $BitFlags: $T {
|
||||
$(
|
||||
$(#[$inner $($args)*])*
|
||||
const $Flag = libc::$Flag $(as $cast)*;
|
||||
)+
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// The `libc_enum!` macro helps with a common use case of defining an enum exclusively using
|
||||
/// values from the `libc` crate. This macro supports both `pub` and private `enum`s.
|
||||
///
|
||||
/// The `libc` crate must be in scope with the name `libc`.
|
||||
///
|
||||
/// # Example
|
||||
/// ```ignore
|
||||
/// libc_enum!{
|
||||
/// pub enum ProtFlags {
|
||||
/// PROT_NONE,
|
||||
/// PROT_READ,
|
||||
/// PROT_WRITE,
|
||||
/// PROT_EXEC,
|
||||
/// #[cfg(linux_android)]
|
||||
/// PROT_GROWSDOWN,
|
||||
/// #[cfg(linux_android)]
|
||||
/// PROT_GROWSUP,
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
// Some targets don't use all rules.
|
||||
#[allow(unused_macro_rules)]
|
||||
macro_rules! libc_enum {
|
||||
// Exit rule.
|
||||
(@make_enum
|
||||
name: $BitFlags:ident,
|
||||
{
|
||||
$v:vis
|
||||
attrs: [$($attrs:tt)*],
|
||||
entries: [$($entries:tt)*],
|
||||
}
|
||||
) => {
|
||||
$($attrs)*
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
||||
$v enum $BitFlags {
|
||||
$($entries)*
|
||||
}
|
||||
};
|
||||
|
||||
// Exit rule including TryFrom
|
||||
(@make_enum
|
||||
name: $BitFlags:ident,
|
||||
{
|
||||
$v:vis
|
||||
attrs: [$($attrs:tt)*],
|
||||
entries: [$($entries:tt)*],
|
||||
from_type: $repr:path,
|
||||
try_froms: [$($try_froms:tt)*]
|
||||
}
|
||||
) => {
|
||||
$($attrs)*
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
||||
$v enum $BitFlags {
|
||||
$($entries)*
|
||||
}
|
||||
impl ::std::convert::TryFrom<$repr> for $BitFlags {
|
||||
type Error = nix::Error;
|
||||
#[allow(unused_doc_comments)]
|
||||
#[allow(deprecated)]
|
||||
#[allow(unused_attributes)]
|
||||
fn try_from(x: $repr) -> nix::Result<Self> {
|
||||
match x {
|
||||
$($try_froms)*
|
||||
_ => Err(nix::Error::EINVAL)
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Done accumulating.
|
||||
(@accumulate_entries
|
||||
name: $BitFlags:ident,
|
||||
{
|
||||
$v:vis
|
||||
attrs: $attrs:tt,
|
||||
},
|
||||
$entries:tt,
|
||||
$try_froms:tt;
|
||||
) => {
|
||||
libc_enum! {
|
||||
@make_enum
|
||||
name: $BitFlags,
|
||||
{
|
||||
$v
|
||||
attrs: $attrs,
|
||||
entries: $entries,
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Done accumulating and want TryFrom
|
||||
(@accumulate_entries
|
||||
name: $BitFlags:ident,
|
||||
{
|
||||
$v:vis
|
||||
attrs: $attrs:tt,
|
||||
from_type: $repr:path,
|
||||
},
|
||||
$entries:tt,
|
||||
$try_froms:tt;
|
||||
) => {
|
||||
libc_enum! {
|
||||
@make_enum
|
||||
name: $BitFlags,
|
||||
{
|
||||
$v
|
||||
attrs: $attrs,
|
||||
entries: $entries,
|
||||
from_type: $repr,
|
||||
try_froms: $try_froms
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Munch an attr.
|
||||
(@accumulate_entries
|
||||
name: $BitFlags:ident,
|
||||
$prefix:tt,
|
||||
[$($entries:tt)*],
|
||||
[$($try_froms:tt)*];
|
||||
#[$attr:meta] $($tail:tt)*
|
||||
) => {
|
||||
libc_enum! {
|
||||
@accumulate_entries
|
||||
name: $BitFlags,
|
||||
$prefix,
|
||||
[
|
||||
$($entries)*
|
||||
#[$attr]
|
||||
],
|
||||
[
|
||||
$($try_froms)*
|
||||
#[$attr]
|
||||
];
|
||||
$($tail)*
|
||||
}
|
||||
};
|
||||
|
||||
// Munch last ident if not followed by a comma.
|
||||
(@accumulate_entries
|
||||
name: $BitFlags:ident,
|
||||
$prefix:tt,
|
||||
[$($entries:tt)*],
|
||||
[$($try_froms:tt)*];
|
||||
$entry:ident
|
||||
) => {
|
||||
libc_enum! {
|
||||
@accumulate_entries
|
||||
name: $BitFlags,
|
||||
$prefix,
|
||||
[
|
||||
$($entries)*
|
||||
$entry = libc::$entry,
|
||||
],
|
||||
[
|
||||
$($try_froms)*
|
||||
libc::$entry => Ok($BitFlags::$entry),
|
||||
];
|
||||
}
|
||||
};
|
||||
|
||||
// Munch an ident; covers terminating comma case.
|
||||
(@accumulate_entries
|
||||
name: $BitFlags:ident,
|
||||
$prefix:tt,
|
||||
[$($entries:tt)*],
|
||||
[$($try_froms:tt)*];
|
||||
$entry:ident,
|
||||
$($tail:tt)*
|
||||
) => {
|
||||
libc_enum! {
|
||||
@accumulate_entries
|
||||
name: $BitFlags,
|
||||
$prefix,
|
||||
[
|
||||
$($entries)*
|
||||
$entry = libc::$entry,
|
||||
],
|
||||
[
|
||||
$($try_froms)*
|
||||
libc::$entry => Ok($BitFlags::$entry),
|
||||
];
|
||||
$($tail)*
|
||||
}
|
||||
};
|
||||
|
||||
// Munch an ident and cast it to the given type; covers terminating comma.
|
||||
(@accumulate_entries
|
||||
name: $BitFlags:ident,
|
||||
$prefix:tt,
|
||||
[$($entries:tt)*],
|
||||
[$($try_froms:tt)*];
|
||||
$entry:ident as $ty:ty,
|
||||
$($tail:tt)*
|
||||
) => {
|
||||
libc_enum! {
|
||||
@accumulate_entries
|
||||
name: $BitFlags,
|
||||
$prefix,
|
||||
[
|
||||
$($entries)*
|
||||
$entry = libc::$entry as $ty,
|
||||
],
|
||||
[
|
||||
$($try_froms)*
|
||||
libc::$entry as $ty => Ok($BitFlags::$entry),
|
||||
];
|
||||
$($tail)*
|
||||
}
|
||||
};
|
||||
|
||||
// Entry rule.
|
||||
(
|
||||
$(#[$attr:meta])*
|
||||
$v:vis enum $BitFlags:ident {
|
||||
$($vals:tt)*
|
||||
}
|
||||
) => {
|
||||
libc_enum! {
|
||||
@accumulate_entries
|
||||
name: $BitFlags,
|
||||
{
|
||||
$v
|
||||
attrs: [$(#[$attr])*],
|
||||
},
|
||||
[],
|
||||
[];
|
||||
$($vals)*
|
||||
}
|
||||
};
|
||||
|
||||
// Entry rule including TryFrom
|
||||
(
|
||||
$(#[$attr:meta])*
|
||||
$v:vis enum $BitFlags:ident {
|
||||
$($vals:tt)*
|
||||
}
|
||||
impl TryFrom<$repr:path>
|
||||
) => {
|
||||
libc_enum! {
|
||||
@accumulate_entries
|
||||
name: $BitFlags,
|
||||
{
|
||||
$v
|
||||
attrs: [$(#[$attr])*],
|
||||
from_type: $repr,
|
||||
},
|
||||
[],
|
||||
[];
|
||||
$($vals)*
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) use libc_bitflags;
|
||||
pub(crate) use libc_enum;
|
||||
86
src/nix/priority.rs
Normal file
86
src/nix/priority.rs
Normal file
@@ -0,0 +1,86 @@
|
||||
use nix::{
|
||||
errno::Errno,
|
||||
libc::{self, __priority_which_t, id_t},
|
||||
unistd::{Gid, Pid, Uid},
|
||||
Result,
|
||||
};
|
||||
|
||||
use super::macros::*;
|
||||
|
||||
libc_enum! {
|
||||
#[repr(u32)]
|
||||
#[non_exhaustive]
|
||||
enum WhichPriority {
|
||||
PRIO_PROCESS,
|
||||
PRIO_PGRP,
|
||||
PRIO_USER,
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
pub enum PriorityKind {
|
||||
Process(Option<Pid>),
|
||||
Group(Option<Gid>),
|
||||
User(Option<Uid>),
|
||||
}
|
||||
|
||||
impl From<Pid> for PriorityKind {
|
||||
fn from(pid: Pid) -> Self {
|
||||
PriorityKind::Process(Some(pid))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Gid> for PriorityKind {
|
||||
fn from(gid: Gid) -> Self {
|
||||
PriorityKind::Group(Some(gid))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Uid> for PriorityKind {
|
||||
fn from(uid: Uid) -> Self {
|
||||
PriorityKind::User(Some(uid))
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<WhichPriority> for PriorityKind {
|
||||
fn into(self) -> WhichPriority {
|
||||
match self {
|
||||
PriorityKind::Process(_) => WhichPriority::PRIO_PROCESS,
|
||||
PriorityKind::Group(_) => WhichPriority::PRIO_PGRP,
|
||||
PriorityKind::User(_) => WhichPriority::PRIO_USER,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<u32> for PriorityKind {
|
||||
fn into(self) -> u32 {
|
||||
match self {
|
||||
// TODO: Deal with this unwrap
|
||||
PriorityKind::Process(pid) => pid
|
||||
.map(|p| u32::try_from(i32::from(p)).unwrap())
|
||||
.unwrap_or(0),
|
||||
PriorityKind::Group(gid) => gid.map(|g| g.into()).unwrap_or(0),
|
||||
PriorityKind::User(uid) => uid.map(|u| u.into()).unwrap_or(0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn into_priority_args(priority: PriorityKind) -> (__priority_which_t, id_t) {
|
||||
let _which = WhichPriority::from(priority.into());
|
||||
let which = __priority_which_t::from(_which as u32);
|
||||
|
||||
let who: id_t = priority.into();
|
||||
(which, who)
|
||||
}
|
||||
|
||||
pub fn getpriority(priority: PriorityKind) -> Result<i32> {
|
||||
let (which, who) = into_priority_args(priority);
|
||||
let res = unsafe { libc::getpriority(which, who) };
|
||||
Errno::result(res)
|
||||
}
|
||||
|
||||
pub fn setpriority(priority: PriorityKind, prio: i32) -> Result<i32> {
|
||||
let (which, who) = into_priority_args(priority);
|
||||
let result = unsafe { libc::setpriority(which, who, prio) };
|
||||
Errno::result(result)
|
||||
}
|
||||
183
src/nix/sched.rs
Normal file
183
src/nix/sched.rs
Normal file
@@ -0,0 +1,183 @@
|
||||
/// Kindly borrowed from https://github.com/nix-rust/nix/pull/1435
|
||||
use nix::{
|
||||
errno::Errno,
|
||||
libc::{self, c_int},
|
||||
unistd::Pid,
|
||||
Result,
|
||||
};
|
||||
|
||||
use super::macros::*;
|
||||
|
||||
libc_enum! {
|
||||
#[repr(i32)]
|
||||
#[non_exhaustive]
|
||||
pub enum SchedType {
|
||||
#[cfg(target_os = "android")]
|
||||
SCHED_NORMAL,
|
||||
#[cfg(any(target_os = "dragonfly", target_os = "freebsd", target_os = "fuchsia", target_os = "illumos", target_os = "linux", target_os = "netbsd"))]
|
||||
SCHED_OTHER,
|
||||
#[cfg(any(target_os = "android", target_os = "dragonfly", target_os = "freebsd", target_os = "fuchsia", target_os = "illumos", target_os = "linux", target_os = "netbsd"))]
|
||||
SCHED_FIFO,
|
||||
#[cfg(any(target_os = "android", target_os = "dragonfly", target_os = "freebsd", target_os = "fuchsia", target_os = "illumos", target_os = "linux", target_os = "netbsd"))]
|
||||
SCHED_RR,
|
||||
#[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
|
||||
SCHED_BATCH,
|
||||
#[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
|
||||
SCHED_IDLE,
|
||||
#[cfg(target_os = "android")]
|
||||
SCHED_DEADLINE,
|
||||
#[cfg(target_os = "illumos")]
|
||||
SCHED_SYS,
|
||||
#[cfg(target_os = "illumos")]
|
||||
SCHED_IA,
|
||||
#[cfg(target_os = "illumos")]
|
||||
SCHED_FSS,
|
||||
#[cfg(target_os = "illumos")]
|
||||
SCHED_FX,
|
||||
}
|
||||
|
||||
impl TryFrom<i32>
|
||||
}
|
||||
|
||||
libc_bitflags! {
|
||||
pub struct SchedFlags: c_int {
|
||||
#[cfg(target_os = "linux")]
|
||||
SCHED_RESET_ON_FORK;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
pub struct SchedPolicy {
|
||||
pub sched_type: SchedType,
|
||||
#[cfg(target_os = "linux")]
|
||||
pub sched_flags: SchedFlags,
|
||||
}
|
||||
|
||||
impl SchedPolicy {
|
||||
pub fn new(sched_type: SchedType) -> Self {
|
||||
SchedPolicy::_with_flags(sched_type, SchedFlags::empty())
|
||||
}
|
||||
|
||||
pub fn with_flags(sched_type: SchedType, sched_flags: SchedFlags) -> Self {
|
||||
SchedPolicy::_with_flags(sched_type, sched_flags)
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub const fn bits(&self) -> i32 {
|
||||
self.sched_type as i32 | self.sched_flags.bits()
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
pub const fn bits(&self) -> i32 {
|
||||
self.sched_type as i32
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub fn from_bits(bits: i32) -> Option<Self> {
|
||||
let type_bits = bits & !SchedFlags::all().bits();
|
||||
let sched_type = SchedType::try_from(type_bits).ok()?;
|
||||
let flag_bits = bits & SchedFlags::all().bits();
|
||||
let sched_flags = SchedFlags::from_bits(flag_bits)?;
|
||||
Some(SchedPolicy::with_flags(sched_type, sched_flags))
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
pub fn from_bits(bits: i32) -> Option<Self> {
|
||||
let type_bits = bits & !SchedFlags::all().bits();
|
||||
let sched_type = SchedType::try_from(type_bits).ok()?;
|
||||
Some(SchedPolicy::new(sched_type))
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
#[inline]
|
||||
fn _with_flags(sched_type: SchedType, sched_flags: SchedFlags) -> Self {
|
||||
SchedPolicy {
|
||||
sched_type,
|
||||
sched_flags,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
#[inline]
|
||||
fn _with_flags(sched_type: SchedType, _sched_flags: SchedFlags) -> Self {
|
||||
SchedPolicy { sched_type }
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(transparent)]
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
pub struct SchedParam(libc::sched_param);
|
||||
|
||||
impl SchedParam {
|
||||
pub fn new(priority: i32) -> Self {
|
||||
let sched_param = unsafe {
|
||||
// Illumos's sched_param has a private padding field.
|
||||
let mut param = std::mem::MaybeUninit::<libc::sched_param>::zeroed();
|
||||
let p = param.as_mut_ptr();
|
||||
(*p).sched_priority = priority;
|
||||
param.assume_init()
|
||||
};
|
||||
SchedParam(sched_param)
|
||||
}
|
||||
|
||||
pub const fn priority(&self) -> i32 {
|
||||
self.0.sched_priority
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for SchedParam {
|
||||
fn default() -> Self {
|
||||
SchedParam::new(0)
|
||||
}
|
||||
}
|
||||
/// Get minimum priority value for policy
|
||||
///
|
||||
/// See also [`sched_get_priority_min(2)`](https://pubs.opengroup.org/onlinepubs/9699919799/functions/sched_get_priority_min.html)
|
||||
pub fn sched_get_priority_min(policy: SchedPolicy) -> Result<i32> {
|
||||
let res = unsafe { libc::sched_get_priority_min(policy.bits()) };
|
||||
|
||||
Errno::result(res)
|
||||
}
|
||||
|
||||
/// Get maximum priority value for policy
|
||||
///
|
||||
/// See also [`sched_get_priority_max(2)`](https://pubs.opengroup.org/onlinepubs/9699919799/functions/sched_get_priority_max.html)
|
||||
pub fn sched_get_priority_max(policy: SchedPolicy) -> Result<i32> {
|
||||
let res = unsafe { libc::sched_get_priority_max(policy.bits()) };
|
||||
|
||||
Errno::result(res)
|
||||
}
|
||||
|
||||
/// Set thread's scheduling policy and parameters
|
||||
///
|
||||
/// `pid` is the thread ID to update.
|
||||
/// If `pid` is None or zero, then the policy and parameters for the calling thread are set.
|
||||
///
|
||||
/// See also [`sched_setscheduler(2)`](https://pubs.opengroup.org/onlinepubs/9699919799/functions/sched_setscheduler.html)
|
||||
pub fn sched_setscheduler(
|
||||
pid: Option<Pid>,
|
||||
policy: SchedPolicy,
|
||||
sched_param: SchedParam,
|
||||
) -> Result<()> {
|
||||
let res = unsafe {
|
||||
libc::sched_setscheduler(
|
||||
pid.unwrap_or_else(|| Pid::from_raw(0)).into(),
|
||||
policy.bits(),
|
||||
&sched_param.0,
|
||||
)
|
||||
};
|
||||
|
||||
Errno::result(res).map(drop)
|
||||
}
|
||||
|
||||
/// Get thread's scheduling policy and parameters
|
||||
///
|
||||
/// `pid` is the thread ID to check.
|
||||
/// If `pid` is None or zero, then the policy and parameters for the calling thread are retrieved.
|
||||
///
|
||||
/// See also [`sched_getscheduler(2)`](https://pubs.opengroup.org/onlinepubs/9699919799/functions/sched_getscheduler.html)
|
||||
pub fn sched_getscheduler(pid: Option<Pid>) -> Result<SchedPolicy> {
|
||||
let res = unsafe { libc::sched_getscheduler(pid.unwrap_or_else(|| Pid::from_raw(0)).into()) };
|
||||
|
||||
Errno::result(res).and(SchedPolicy::from_bits(res).ok_or(Errno::EINVAL))
|
||||
}
|
||||
90
src/privileges.rs
Normal file
90
src/privileges.rs
Normal file
@@ -0,0 +1,90 @@
|
||||
use std::{env::set_current_dir, os::unix::fs::chroot};
|
||||
|
||||
use anyhow::Result;
|
||||
use nix::{
|
||||
sys::{
|
||||
prctl,
|
||||
resource::{getrlimit, setrlimit, Resource},
|
||||
stat::{umask, Mode},
|
||||
},
|
||||
unistd::{getuid, User},
|
||||
};
|
||||
|
||||
use crate::{cli::Args, nix::*};
|
||||
|
||||
pub fn self_is_root() -> bool {
|
||||
getuid().is_root()
|
||||
}
|
||||
|
||||
pub fn self_raise_timer_slack(args: &Args) -> Result<()> {
|
||||
let slack_ns = u64::from((args.canary_watchdog_msec - args.canary_cheep_msec) / 2) * 1_000_000;
|
||||
prctl::set_timerslack(slack_ns)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn self_drop_privileges(args: &Args) -> Result<()> {
|
||||
let user = if !args.no_drop_privileges {
|
||||
Some(User::from_name(&args.user_name)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if !args.no_chroot {
|
||||
chroot("/proc")?;
|
||||
set_current_dir("/")?;
|
||||
}
|
||||
|
||||
if let Some(user) = user { }
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_resource_limits(args: &Args) -> Result<()> {
|
||||
let limits: [(Resource, nix::libc::rlim_t); 8] = [
|
||||
(Resource::RLIMIT_FSIZE, 0),
|
||||
(Resource::RLIMIT_MEMLOCK, 0),
|
||||
(Resource::RLIMIT_MSGQUEUE, 0),
|
||||
(Resource::RLIMIT_NICE, 20),
|
||||
(Resource::RLIMIT_NOFILE, 50),
|
||||
(Resource::RLIMIT_NPROC, 3),
|
||||
(Resource::RLIMIT_RTPRIO, 0),
|
||||
(Resource::RLIMIT_RTTIME, args.rttime_usec_max.unwrap_or(0)),
|
||||
];
|
||||
|
||||
for (resource, value) in limits.into_iter() {
|
||||
let (_, rlim_max) = getrlimit(resource)?;
|
||||
if rlim_max < value {
|
||||
continue;
|
||||
}
|
||||
setrlimit(resource, value, value)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn self_set_realtime(args: &Args, priority: i32) -> Result<()> {
|
||||
let policy = SchedPolicy::with_flags(
|
||||
args.scheduling_policy.into(),
|
||||
SchedFlags::SCHED_RESET_ON_FORK,
|
||||
);
|
||||
let param = SchedParam::new(priority);
|
||||
|
||||
sched_setscheduler(None, policy, param)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn self_drop_realtime(nice_level: i32) -> Result<()> {
|
||||
let param = SchedParam::default();
|
||||
let policy = SchedPolicy::new(SchedType::SCHED_OTHER);
|
||||
|
||||
sched_setscheduler(None, policy, param)?;
|
||||
|
||||
setpriority(PriorityKind::Process(None), nice_level)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn self_reduce_umask() {
|
||||
umask(Mode::from_bits_truncate(0o777));
|
||||
}
|
||||
1
src/util.rs
Normal file
1
src/util.rs
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
Reference in New Issue
Block a user