compositor: set scheduler to SCHED_RR before dropping CAP_SYS_NICE
This commit is contained in:
parent
af6e868a78
commit
7a623006e2
8 changed files with 202 additions and 12 deletions
|
|
@ -234,7 +234,9 @@ impl ValueEnum for &'static Format {
|
|||
|
||||
pub fn main() {
|
||||
let cli = Jay::parse();
|
||||
drop_all_pr_caps();
|
||||
if not_matches!(cli.command, Cmd::Run(_)) {
|
||||
drop_all_pr_caps();
|
||||
}
|
||||
match cli.command {
|
||||
Cmd::Run(a) => start_compositor(cli.global, a),
|
||||
Cmd::GenerateCompletion(g) => generate::main(g),
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ use {
|
|||
logger::Logger,
|
||||
output_schedule::OutputSchedule,
|
||||
portal::{self, PortalStartup},
|
||||
pr_caps::pr_caps,
|
||||
scale::Scale,
|
||||
sighand::{self, SighandError},
|
||||
state::{ConnectorData, IdleState, ScreenlockState, State, XWaylandState},
|
||||
|
|
@ -51,9 +52,17 @@ use {
|
|||
},
|
||||
user_session::import_environment,
|
||||
utils::{
|
||||
clone3::ensure_reaper, clonecell::CloneCell, errorfmt::ErrorFmt, fdcloser::FdCloser,
|
||||
numcell::NumCell, oserror::OsError, queue::AsyncQueue, refcounted::RefCounted,
|
||||
run_toplevel::RunToplevel, tri::Try,
|
||||
clone3::ensure_reaper,
|
||||
clonecell::CloneCell,
|
||||
errorfmt::ErrorFmt,
|
||||
fdcloser::FdCloser,
|
||||
nice::{did_elevate_scheduler, elevate_scheduler},
|
||||
numcell::NumCell,
|
||||
oserror::OsError,
|
||||
queue::AsyncQueue,
|
||||
refcounted::RefCounted,
|
||||
run_toplevel::RunToplevel,
|
||||
tri::Try,
|
||||
},
|
||||
version::VERSION,
|
||||
video::drm::wait_for_sync_obj::WaitForSyncObj,
|
||||
|
|
@ -72,6 +81,11 @@ pub const MAX_EXTENTS: i32 = (1 << 22) - 1;
|
|||
pub fn start_compositor(global: GlobalArgs, args: RunArgs) {
|
||||
sighand::reset_all();
|
||||
let reaper_pid = ensure_reaper();
|
||||
let caps = pr_caps().into_comp();
|
||||
if caps.has_nice() {
|
||||
elevate_scheduler();
|
||||
}
|
||||
drop(caps);
|
||||
let forker = create_forker(reaper_pid);
|
||||
let portal = portal::run_from_compositor(global.log_level.into());
|
||||
enable_profiler();
|
||||
|
|
@ -146,6 +160,9 @@ fn start_compositor2(
|
|||
) -> Result<(), CompositorError> {
|
||||
log::info!("pid = {}", uapi::getpid());
|
||||
log::info!("version = {VERSION}");
|
||||
if did_elevate_scheduler() {
|
||||
log::info!("Running with elevated scheduler: SCHED_RR");
|
||||
}
|
||||
init_fd_limit();
|
||||
leaks::init();
|
||||
clientmem::init()?;
|
||||
|
|
@ -680,7 +697,7 @@ fn create_dummy_output(state: &Rc<State>) {
|
|||
state.dummy_output.set(Some(dummy_output));
|
||||
}
|
||||
|
||||
fn config_dir() -> Option<String> {
|
||||
pub fn config_dir() -> Option<String> {
|
||||
if let Ok(xdg) = env::var("XDG_CONFIG_HOME") {
|
||||
Some(format!("{}/jay", xdg))
|
||||
} else if let Ok(home) = env::var("HOME") {
|
||||
|
|
|
|||
|
|
@ -10,8 +10,13 @@ use {
|
|||
state::State,
|
||||
tree::ToplevelData,
|
||||
utils::{
|
||||
clonecell::CloneCell, numcell::NumCell, ptr_ext::PtrExt,
|
||||
toplevel_identifier::ToplevelIdentifier, unlink_on_drop::UnlinkOnDrop, xrd::xrd,
|
||||
clonecell::CloneCell,
|
||||
nice::{JAY_NO_REALTIME, dont_allow_config_so},
|
||||
numcell::NumCell,
|
||||
ptr_ext::PtrExt,
|
||||
toplevel_identifier::ToplevelIdentifier,
|
||||
unlink_on_drop::UnlinkOnDrop,
|
||||
xrd::xrd,
|
||||
},
|
||||
},
|
||||
bincode::Options,
|
||||
|
|
@ -26,7 +31,7 @@ use {
|
|||
window::{self, TileState},
|
||||
},
|
||||
libloading::Library,
|
||||
std::{cell::Cell, io, mem, ptr, rc::Rc},
|
||||
std::{cell::Cell, io, mem, path::Path, ptr, rc::Rc},
|
||||
thiserror::Error,
|
||||
};
|
||||
|
||||
|
|
@ -42,6 +47,8 @@ pub enum ConfigError {
|
|||
CopyConfigFile(#[source] io::Error),
|
||||
#[error("XDG_RUNTIME_DIR is not set")]
|
||||
XrdNotSet,
|
||||
#[error("Custom config.so is not permitted")]
|
||||
NotPermitted,
|
||||
}
|
||||
|
||||
pub struct ConfigProxy {
|
||||
|
|
@ -280,11 +287,24 @@ impl ConfigProxy {
|
|||
}
|
||||
|
||||
pub fn from_config_dir(state: &Rc<State>) -> Result<Self, ConfigError> {
|
||||
if dont_allow_config_so() {
|
||||
if have_config_so(state.config_dir.as_deref()) {
|
||||
log::warn!("Not loading config.so because");
|
||||
log::warn!(" 1. Jay was started with CAP_SYS_NICE");
|
||||
log::warn!(" 2. Jay was not started with {}=1", JAY_NO_REALTIME);
|
||||
log::warn!(" 3. The scheduler was elevated to SCHED_RR");
|
||||
log::warn!(
|
||||
" 4. Jay was not compiled with {}=1",
|
||||
jay_allow_realtime_config_so!(),
|
||||
);
|
||||
}
|
||||
return Err(ConfigError::NotPermitted);
|
||||
}
|
||||
let dir = match state.config_dir.as_deref() {
|
||||
Some(d) => d,
|
||||
_ => return Err(ConfigError::ConfigDirNotSet),
|
||||
};
|
||||
let file = format!("{}/config.so", dir);
|
||||
let file = format!("{}/{CONFIG_SO}", dir);
|
||||
unsafe { Self::from_file(&file, state) }
|
||||
}
|
||||
|
||||
|
|
@ -355,3 +375,15 @@ pub struct InvokedShortcut {
|
|||
pub effective_mods: Modifiers,
|
||||
pub sym: KeySym,
|
||||
}
|
||||
|
||||
const CONFIG_SO: &str = "config.so";
|
||||
|
||||
pub fn have_config_so(config_dir: Option<&str>) -> bool {
|
||||
let Some(dir) = config_dir else {
|
||||
return false;
|
||||
};
|
||||
let mut dir = dir.to_owned();
|
||||
dir.push_str("/");
|
||||
dir.push_str(CONFIG_SO);
|
||||
Path::new(&dir).exists()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -760,3 +760,15 @@ macro_rules! client_wire_scale_to_logical {
|
|||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! not_matches {
|
||||
($($tt:tt)*) => {
|
||||
!matches!($($tt)*)
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! jay_allow_realtime_config_so {
|
||||
() => {
|
||||
"JAY_ALLOW_REALTIME_CONFIG_SO"
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,17 +1,52 @@
|
|||
use {
|
||||
crate::{
|
||||
pr_caps::sys::{
|
||||
_LINUX_CAPABILITY_U32S_3, _LINUX_CAPABILITY_VERSION_3, cap_user_data_t,
|
||||
_LINUX_CAPABILITY_U32S_3, _LINUX_CAPABILITY_VERSION_3, CAP_SYS_NICE, cap_user_data_t,
|
||||
cap_user_header_t,
|
||||
},
|
||||
utils::{errorfmt::ErrorFmt, oserror::OsError},
|
||||
utils::{bitflags::BitflagsExt, errorfmt::ErrorFmt, oserror::OsError},
|
||||
},
|
||||
uapi::{
|
||||
c::{SYS_capset, syscall},
|
||||
c::{SYS_capget, SYS_capset, syscall},
|
||||
map_err,
|
||||
},
|
||||
};
|
||||
|
||||
pub struct PrCaps {
|
||||
effective: u64,
|
||||
permitted: u64,
|
||||
inheritable: u64,
|
||||
}
|
||||
|
||||
pub struct PrCompCaps {
|
||||
caps: PrCaps,
|
||||
}
|
||||
|
||||
pub fn pr_caps() -> PrCaps {
|
||||
let mut hdr = cap_user_header_t {
|
||||
version: _LINUX_CAPABILITY_VERSION_3,
|
||||
pid: 0,
|
||||
};
|
||||
let mut caps = [cap_user_data_t::default(); _LINUX_CAPABILITY_U32S_3];
|
||||
let ret = unsafe { syscall(SYS_capget, &mut hdr, &mut caps) };
|
||||
if let Err(e) = map_err!(ret) {
|
||||
eprintln!(
|
||||
"Could not get process capabilities: {}",
|
||||
ErrorFmt(OsError(e.0))
|
||||
);
|
||||
return PrCaps {
|
||||
effective: 0,
|
||||
permitted: 0,
|
||||
inheritable: 0,
|
||||
};
|
||||
}
|
||||
PrCaps {
|
||||
effective: caps[0].effective as u64 | ((caps[1].effective as u64) << 32),
|
||||
permitted: caps[0].permitted as u64 | ((caps[1].permitted as u64) << 32),
|
||||
inheritable: caps[0].inheritable as u64 | ((caps[1].inheritable as u64) << 32),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn drop_all_pr_caps() {
|
||||
let mut hdr = cap_user_header_t {
|
||||
version: _LINUX_CAPABILITY_VERSION_3,
|
||||
|
|
@ -27,6 +62,55 @@ pub fn drop_all_pr_caps() {
|
|||
}
|
||||
}
|
||||
|
||||
impl PrCaps {
|
||||
pub fn into_comp(mut self) -> PrCompCaps {
|
||||
let mut caps = 0;
|
||||
macro_rules! add_cap {
|
||||
($name:ident) => {
|
||||
if self.permitted.contains(1 << $name) {
|
||||
caps |= 1 << $name;
|
||||
}
|
||||
};
|
||||
}
|
||||
add_cap!(CAP_SYS_NICE);
|
||||
let mut hdr = cap_user_header_t {
|
||||
version: _LINUX_CAPABILITY_VERSION_3,
|
||||
pid: 0,
|
||||
};
|
||||
let caps_hi = (caps >> 32) as u32;
|
||||
let caps_lo = caps as u32;
|
||||
let mut data = [cap_user_data_t::default(); _LINUX_CAPABILITY_U32S_3];
|
||||
data[0].effective = caps_lo;
|
||||
data[1].effective = caps_hi;
|
||||
data[0].permitted = caps_lo;
|
||||
data[1].permitted = caps_hi;
|
||||
let ret = unsafe { syscall(SYS_capset, &mut hdr, &data) };
|
||||
if let Err(e) = map_err!(ret) {
|
||||
eprintln!(
|
||||
"Could not get set compositor capabilities: {}",
|
||||
ErrorFmt(OsError(e.0))
|
||||
);
|
||||
return PrCompCaps { caps: self };
|
||||
}
|
||||
self.effective = caps;
|
||||
self.permitted = caps;
|
||||
self.inheritable = 0;
|
||||
PrCompCaps { caps: self }
|
||||
}
|
||||
}
|
||||
|
||||
impl PrCompCaps {
|
||||
pub fn has_nice(&self) -> bool {
|
||||
self.caps.effective.contains(1 << CAP_SYS_NICE)
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for PrCaps {
|
||||
fn drop(&mut self) {
|
||||
drop_all_pr_caps();
|
||||
}
|
||||
}
|
||||
|
||||
mod sys {
|
||||
#![allow(dead_code)]
|
||||
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ pub mod line_logger;
|
|||
pub mod linkedlist;
|
||||
pub mod log_on_drop;
|
||||
pub mod mmap;
|
||||
pub mod nice;
|
||||
pub mod nonblock;
|
||||
pub mod num_cpus;
|
||||
pub mod numcell;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
use {
|
||||
crate::{
|
||||
forker::ForkerError,
|
||||
pr_caps::drop_all_pr_caps,
|
||||
utils::{errorfmt::ErrorFmt, on_drop::OnDrop, process_name::set_process_name},
|
||||
},
|
||||
std::{env, mem::MaybeUninit, process, slice, str::FromStr},
|
||||
|
|
@ -161,6 +162,7 @@ pub fn ensure_reaper() -> c::pid_t {
|
|||
set_deathsig();
|
||||
return reaper_pid;
|
||||
};
|
||||
drop_all_pr_caps();
|
||||
set_process_name("jay reaper");
|
||||
while let Ok((pid, status)) = uapi::wait() {
|
||||
if pid == main_process_id {
|
||||
|
|
|
|||
40
src/utils/nice.rs
Normal file
40
src/utils/nice.rs
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
use {
|
||||
crate::{compositor::config_dir, config::have_config_so},
|
||||
c::sched_setscheduler,
|
||||
std::{
|
||||
env, mem,
|
||||
sync::atomic::{AtomicBool, Ordering::Relaxed},
|
||||
},
|
||||
uapi::c::{self, SCHED_RESET_ON_FORK, SCHED_RR, sched_param},
|
||||
};
|
||||
|
||||
static DID_ELEVATE_SCHEDULER: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
pub const JAY_NO_REALTIME: &str = "JAY_NO_REALTIME";
|
||||
|
||||
pub fn elevate_scheduler() {
|
||||
if env::var(JAY_NO_REALTIME).as_deref().unwrap_or_default() == "1" {
|
||||
return;
|
||||
}
|
||||
if have_config_so(config_dir().as_deref()) && dont_allow_realtime_config_so() {
|
||||
return;
|
||||
}
|
||||
let mut param = unsafe { mem::zeroed::<sched_param>() };
|
||||
param.sched_priority = 1;
|
||||
let res = unsafe { sched_setscheduler(0, SCHED_RR | SCHED_RESET_ON_FORK, ¶m) };
|
||||
if res == 0 {
|
||||
DID_ELEVATE_SCHEDULER.store(true, Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn did_elevate_scheduler() -> bool {
|
||||
DID_ELEVATE_SCHEDULER.load(Relaxed)
|
||||
}
|
||||
|
||||
fn dont_allow_realtime_config_so() -> bool {
|
||||
option_env!(jay_allow_realtime_config_so!()).unwrap_or_default() != "1"
|
||||
}
|
||||
|
||||
pub fn dont_allow_config_so() -> bool {
|
||||
did_elevate_scheduler() && dont_allow_realtime_config_so()
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue