1
0
Fork 0
forked from wry/wry

Merge pull request #465 from mahkoh/jorth/cap-sys-nice

all: add support for CAP_SYS_NICE
This commit is contained in:
mahkoh 2025-05-11 20:52:23 +02:00 committed by GitHub
commit 7beaad9041
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 585 additions and 51 deletions

7
Cargo.lock generated
View file

@ -611,6 +611,7 @@ dependencies = [
"num-derive",
"num-traits",
"once_cell",
"opera",
"parking_lot",
"pin-project",
"png",
@ -859,6 +860,12 @@ version = "1.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
[[package]]
name = "opera"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5444847837ad2026bf6addabcf0c3c25bf3e9f92e435dedb555f4a6a0180ded1"
[[package]]
name = "option-ext"
version = "0.2.0"

View file

@ -65,6 +65,7 @@ kbvm = "0.1.4"
tiny-skia = { version = "0.11.4", default-features = false, features = ["std"] }
regex = "1.11.1"
cfg-if = "1.0.0"
opera = "1.0.1"
[build-dependencies]
repc = "0.1.1"

View file

@ -52,6 +52,33 @@ cargo build --release
The binary is then available under `./target/release/jay`.
## Running with CAP_SYS_NICE
Jay supports being started with CAP_SYS_NICE capabilities. For example, such
capabilities can be added to the binary via
```shell
~# setcap cap_sys_nice=p jay
```
If CAP_SYS_NICE is available, Jay will, by default, elevate its scheduler to
SCHED_RR and create Vulkan queues with the highest available priority. This can
improve responsiveness if the CPU or GPU are under high load.
If Jay is started with the environment variable `JAY_NO_REALTIME=1` or a
`config.so` exists, then Jay will not elevate its scheduler but will still
create elevated Vulkan queues.
Jay will drop all capabilities almost immediately after being started. Before
that, it will spawn a dedicated thread that retains the CAP_SYS_NICE capability
to create elevated Vulkan queues later.
If Jay has elevated its scheduler to SCHED_RR, then it will refuse to load
`config.so` configurations. Otherwise unprivileged applications would be able
to run arbitrary code with SCHED_RR by crafting a dedicated `config.so`. This
behavior can be overridden by compiling Jay with
`JAY_ALLOW_REALTIME_CONFIG_SO=1`.
# Setup
## Configuration

View file

@ -31,6 +31,9 @@
[window-and-client-rules.md](./docs/window-and-client-rules.md).
- Add client and tree CLI subcommands to inspect clients and windows, primarily
to facilitate the writing of window and client rules.
- Jay now supports being started with CAP_SYS_NICE capabilities to improve
responsiveness under high system load. This is described in detail in
[setup.md](docs/setup.md).
# 1.10.0 (2025-04-22)

View file

@ -28,6 +28,7 @@ use {
compositor::start_compositor,
format::{Format, ref_formats},
portal,
pr_caps::drop_all_pr_caps,
},
::log::Level,
clap::{Args, Parser, Subcommand, ValueEnum, ValueHint, builder::PossibleValue},
@ -233,6 +234,9 @@ impl ValueEnum for &'static Format {
pub fn main() {
let cli = Jay::parse();
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),

View file

@ -39,6 +39,7 @@ use {
logger::Logger,
output_schedule::OutputSchedule,
portal::{self, PortalStartup},
pr_caps::{PrCapsThread, 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,14 @@ 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();
let caps_thread = if caps.has_nice() {
elevate_scheduler();
Some(caps.into_thread())
} else {
drop(caps);
None
};
let forker = create_forker(reaper_pid);
let portal = portal::run_from_compositor(global.log_level.into());
enable_profiler();
@ -83,7 +100,14 @@ pub fn start_compositor(global: GlobalArgs, args: RunArgs) {
None
}
};
let res = start_compositor2(Some(forker), portal, Some(logger.clone()), args, None);
let res = start_compositor2(
Some(forker),
portal,
Some(logger.clone()),
args,
None,
caps_thread,
);
leaks::log_leaked();
if let Err(e) = res {
let e = ErrorFmt(e);
@ -97,7 +121,7 @@ pub fn start_compositor(global: GlobalArgs, args: RunArgs) {
#[cfg(feature = "it")]
pub fn start_compositor_for_test(future: TestFuture) -> Result<(), CompositorError> {
let res = start_compositor2(None, None, None, RunArgs::default(), Some(future));
let res = start_compositor2(None, None, None, RunArgs::default(), Some(future), None);
leaks::log_leaked();
res
}
@ -143,9 +167,13 @@ fn start_compositor2(
logger: Option<Arc<Logger>>,
run_args: RunArgs,
test_future: Option<TestFuture>,
caps_thread: Option<PrCapsThread>,
) -> 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()?;
@ -304,6 +332,7 @@ fn start_compositor2(
show_pin_icon: Cell::new(false),
cl_matcher_manager: ClMatcherManager::new(&crit_ids),
tl_matcher_manager: TlMatcherManager::new(&crit_ids),
caps_thread,
});
state.tracker.register(ClientId::from_raw(0));
create_dummy_output(&state);
@ -680,7 +709,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") {

View file

@ -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()
}

View file

@ -4,6 +4,7 @@ use {
async_engine::AsyncEngine,
gfx_api::{GfxContext, GfxError},
io_uring::IoUring,
pr_caps::PrCapsThread,
utils::errorfmt::ErrorFmt,
video::drm::Drm,
},
@ -19,12 +20,13 @@ pub fn create_gfx_context(
ring: &Rc<IoUring>,
drm: &Drm,
api: GfxApi,
caps_thread: Option<&PrCapsThread>,
) -> Result<Rc<dyn GfxContext>, GfxError> {
let mut apis = [GfxApi::OpenGl, GfxApi::Vulkan];
apis.sort_by_key(|&a| if a == api { -1 } else { a as i32 });
let mut last_err = None;
for api in apis {
let res = create_gfx_context_(eng, ring, drm, api);
let res = create_gfx_context_(eng, ring, drm, api, caps_thread);
match res {
Ok(_) => return res,
Err(e) => {
@ -41,10 +43,11 @@ fn create_gfx_context_(
ring: &Rc<IoUring>,
drm: &Drm,
api: GfxApi,
caps_thread: Option<&PrCapsThread>,
) -> Result<Rc<dyn GfxContext>, GfxError> {
match api {
GfxApi::OpenGl => gl::create_gfx_context(drm),
GfxApi::Vulkan => vulkan::create_graphics_context(eng, ring, drm),
GfxApi::Vulkan => vulkan::create_graphics_context(eng, ring, drm, caps_thread),
_ => unreachable!(),
}
}

View file

@ -35,8 +35,9 @@ use {
image::VulkanImageMemory, instance::VulkanInstance, renderer::VulkanRenderer,
},
io_uring::IoUring,
pr_caps::PrCapsThread,
rect::Rect,
utils::oserror::OsError,
utils::{errorfmt::ErrorFmt, oserror::OsError},
video::{
dmabuf::DmaBuf,
drm::{Drm, DrmError, sync_obj::SyncObjCtx},
@ -224,16 +225,27 @@ pub fn create_graphics_context(
eng: &Rc<AsyncEngine>,
ring: &Rc<IoUring>,
drm: &Drm,
caps_thread: Option<&PrCapsThread>,
) -> Result<Rc<dyn GfxContext>, GfxError> {
let instance = VulkanInstance::new(Level::Info, *VULKAN_VALIDATION)?;
let device = instance.create_device(drm)?;
let device = 'device: {
if let Some(t) = caps_thread {
match unsafe { t.run(|| instance.create_device(drm, true)) } {
Ok(d) => break 'device d,
Err(e) => {
log::warn!("Could not create high-priority device: {}", ErrorFmt(e));
}
}
}
instance.create_device(drm, false)?
};
let renderer = device.create_renderer(eng, ring)?;
Ok(Rc::new(Context(renderer)))
}
pub fn create_vulkan_allocator(drm: &Drm) -> Result<Rc<dyn Allocator>, AllocatorError> {
let instance = VulkanInstance::new(Level::Debug, *VULKAN_VALIDATION)?;
let device = instance.create_device(drm)?;
let device = instance.create_device(drm, false)?;
let allocator = device.create_bo_allocator(drm)?;
Ok(Rc::new(allocator))
}

View file

@ -24,12 +24,12 @@ use {
physical_device_drm, queue_family_foreign,
},
khr::{
driver_properties, external_fence_fd, external_memory_fd, external_semaphore_fd,
self, driver_properties, external_fence_fd, external_memory_fd, external_semaphore_fd,
push_descriptor,
},
vk::{
self, DeviceCreateInfo, DeviceQueueCreateInfo, DeviceSize, DriverId,
ExternalSemaphoreFeatureFlags, ExternalSemaphoreHandleTypeFlags,
self, DeviceCreateInfo, DeviceQueueCreateInfo, DeviceQueueGlobalPriorityCreateInfoKHR,
DeviceSize, DriverId, ExternalSemaphoreFeatureFlags, ExternalSemaphoreHandleTypeFlags,
ExternalSemaphoreProperties, MAX_MEMORY_TYPES, MemoryPropertyFlags, MemoryType,
PhysicalDevice, PhysicalDeviceBufferDeviceAddressFeatures,
PhysicalDeviceDescriptorBufferFeaturesEXT, PhysicalDeviceDescriptorBufferPropertiesEXT,
@ -39,7 +39,7 @@ use {
PhysicalDeviceProperties2, PhysicalDeviceSynchronization2Features,
PhysicalDeviceTimelineSemaphoreFeatures,
PhysicalDeviceUniformBufferStandardLayoutFeatures, PhysicalDeviceVulkan12Properties,
Queue, QueueFlags,
Queue, QueueFamilyProperties2, QueueFlags, QueueGlobalPriorityKHR,
},
},
isnt::std_1::collections::IsntHashMap2Ext,
@ -50,6 +50,7 @@ use {
sync::Arc,
},
uapi::Ustr,
vk::QueueFamilyGlobalPriorityPropertiesKHR,
};
pub struct VulkanDevice {
@ -210,20 +211,42 @@ impl VulkanInstance {
fn find_queues(
&self,
phy_dev: PhysicalDevice,
) -> Result<(u32, Option<(u32, u32, u32)>), VulkanError> {
let props = unsafe {
) -> Result<
(
u32,
Option<(u32, u32, u32)>,
QueueGlobalPriorityKHR,
QueueGlobalPriorityKHR,
),
VulkanError,
> {
let len = unsafe {
self.instance
.get_physical_device_queue_family_properties(phy_dev)
.get_physical_device_queue_family_properties2_len(phy_dev)
};
let mut priority_props = vec![QueueFamilyGlobalPriorityPropertiesKHR::default(); len];
let mut props: Vec<_> = priority_props
.iter_mut()
.map(|p| QueueFamilyProperties2::default().push_next(p))
.collect();
unsafe {
self.instance
.get_physical_device_queue_family_properties2(phy_dev, &mut props[..])
}
let gfx_queue = props
.iter()
.position(|p| p.queue_flags.contains(QueueFlags::GRAPHICS))
.position(|p| {
p.queue_family_properties
.queue_flags
.contains(QueueFlags::GRAPHICS)
})
.ok_or(VulkanError::NoGraphicsQueue)?;
let transfer_queue = 'transfer: {
let mut transfer_only = None;
let mut compute_only = None;
let mut separate_gfx = None;
for (idx, props) in props.iter().enumerate() {
let props = &props.queue_family_properties;
if idx == gfx_queue {
continue;
}
@ -244,7 +267,7 @@ impl VulkanInstance {
if let Some(idx) = transfer_only.or(compute_only).or(separate_gfx) {
break 'transfer Some(idx);
}
if props[gfx_queue].queue_count > 1 {
if props[gfx_queue].queue_family_properties.queue_count > 1 {
break 'transfer Some(gfx_queue);
}
None
@ -252,13 +275,27 @@ impl VulkanInstance {
let mut width_mask = 0;
let mut height_mask = 0;
if let Some(idx) = transfer_queue {
let g = &props[idx].min_image_transfer_granularity;
let g = &props[idx]
.queue_family_properties
.min_image_transfer_granularity;
width_mask = g.width.wrapping_sub(1);
height_mask = g.height.wrapping_sub(1);
}
let get_priority = |idx: usize| {
let props = &priority_props[idx];
if props.priority_count > 0 {
props.priorities[props.priority_count as usize - 1]
} else {
QueueGlobalPriorityKHR::MEDIUM
}
};
Ok((
gfx_queue as _,
transfer_queue.map(|v| (v as _, width_mask, height_mask)),
get_priority(gfx_queue),
transfer_queue
.map(get_priority)
.unwrap_or(QueueGlobalPriorityKHR::MEDIUM),
))
}
@ -275,7 +312,11 @@ impl VulkanInstance {
.contains(ExternalSemaphoreFeatureFlags::IMPORTABLE)
}
pub fn create_device(self: &Rc<Self>, drm: &Drm) -> Result<Rc<VulkanDevice>, VulkanError> {
pub fn create_device(
self: &Rc<Self>,
drm: &Drm,
mut high_priority: bool,
) -> Result<Rc<VulkanDevice>, VulkanError> {
let render_node = drm
.get_render_node()
.map_err(VulkanError::FetchRenderNode)?
@ -293,7 +334,17 @@ impl VulkanInstance {
if !supports_descriptor_buffer {
log::warn!("Vulkan device does not support descriptor buffers");
}
let (graphics_queue_family_idx, transfer_queue_family) = self.find_queues(phy_dev)?;
let supports_queue_priority = extensions.contains_key(khr::global_priority::NAME);
if !supports_queue_priority && high_priority {
high_priority = false;
log::warn!("Vulkan device does not support queue priorities");
}
let (
graphics_queue_family_idx,
transfer_queue_family,
max_graphics_priority,
max_transfer_priority,
) = self.find_queues(phy_dev)?;
let mut distinct_transfer_queue_family_idx = None;
let mut transfer_granularity_mask = (0, 0);
if let Some((idx, width_mask, height_mask)) = transfer_queue_family {
@ -325,18 +376,31 @@ impl VulkanInstance {
let mut uniform_buffer_standard_layout_features =
PhysicalDeviceUniformBufferStandardLayoutFeatures::default()
.uniform_buffer_standard_layout(true);
let mut gfx_queue_device_queue_global_priority_create_info =
DeviceQueueGlobalPriorityCreateInfoKHR::default()
.global_priority(max_graphics_priority);
let mut trn_queue_device_queue_global_priority_create_info =
DeviceQueueGlobalPriorityCreateInfoKHR::default()
.global_priority(max_transfer_priority);
let mut queue_create_infos = ArrayVec::<_, 2>::new();
queue_create_infos.push(
DeviceQueueCreateInfo::default()
.queue_family_index(graphics_queue_family_idx)
.queue_priorities(&[1.0]),
);
let queue_create_info = |idx, priority_info| {
let mut info = DeviceQueueCreateInfo::default()
.queue_family_index(idx)
.queue_priorities(&[1.0]);
if high_priority {
info = info.push_next(priority_info);
}
info
};
queue_create_infos.push(queue_create_info(
graphics_queue_family_idx,
&mut gfx_queue_device_queue_global_priority_create_info,
));
if let Some((tq, _, _)) = transfer_queue_family {
queue_create_infos.push(
DeviceQueueCreateInfo::default()
.queue_family_index(tq)
.queue_priorities(&[1.0]),
);
queue_create_infos.push(queue_create_info(
tq,
&mut trn_queue_device_queue_global_priority_create_info,
));
}
let mut device_create_info = DeviceCreateInfo::default()
.push_next(&mut semaphore_features)
@ -433,6 +497,11 @@ impl VulkanInstance {
};
unsafe { device.get_device_queue(family_idx, queue_idx) }
});
if high_priority {
log::info!(
"Created queues with priorities {max_graphics_priority:?}/{max_transfer_priority:?}",
);
}
Ok(Rc::new(VulkanDevice {
physical_device: phy_dev,
render_node,

View file

@ -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"
};
}

View file

@ -88,6 +88,7 @@ mod output_schedule;
mod pango;
mod pipewire;
mod portal;
mod pr_caps;
mod rect;
mod renderer;
mod scale;

View file

@ -185,17 +185,22 @@ impl UsrJayRenderCtxOwner for PortalDisplay {
}
}
if render_ctx.is_none() {
let ctx =
match create_gfx_context(&self.state.eng, &self.state.ring, &drm, GfxApi::OpenGl) {
Ok(c) => c,
Err(e) => {
log::error!(
"Could not create render context from drm device: {}",
ErrorFmt(e)
);
return;
}
};
let ctx = match create_gfx_context(
&self.state.eng,
&self.state.ring,
&drm,
GfxApi::OpenGl,
None,
) {
Ok(c) => c,
Err(e) => {
log::error!(
"Could not create render context from drm device: {}",
ErrorFmt(e)
);
return;
}
};
let ctx = Rc::new(PortalRenderCtx {
_dev_id: dev_id,
ctx,

275
src/pr_caps.rs Normal file
View file

@ -0,0 +1,275 @@
use {
crate::{
pr_caps::sys::{
_LINUX_CAPABILITY_U32S_3, _LINUX_CAPABILITY_VERSION_3, CAP_SYS_NICE, cap_user_data_t,
cap_user_header_t,
},
utils::{bitflags::BitflagsExt, errorfmt::ErrorFmt, oserror::OsError},
},
opera::PhantomNotSend,
parking_lot::{Condvar, Mutex},
std::{
mem,
sync::Arc,
thread::{self, JoinHandle},
},
uapi::{
c::{SYS_capget, SYS_capset, syscall},
map_err,
},
};
pub struct PrCaps {
effective: u64,
permitted: u64,
inheritable: u64,
}
pub struct PrCompCaps {
caps: PrCaps,
}
pub struct PrCapsThread {
thread: Option<JoinHandle<()>>,
data: Arc<ThreadData>,
_no_send: PhantomNotSend,
}
#[derive(Default)]
struct ThreadData {
cond: Condvar,
mutex: Mutex<MutData>,
}
#[derive(Default)]
struct MutData {
exit: bool,
fun: Option<Box<dyn FnOnce() + Send>>,
}
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,
pid: 0,
};
let caps = [cap_user_data_t::default(); _LINUX_CAPABILITY_U32S_3];
let ret = unsafe { syscall(SYS_capset, &mut hdr, &caps) };
if let Err(e) = map_err!(ret) {
eprintln!(
"Could not get drop capabilities: {}",
ErrorFmt(OsError(e.0))
);
}
}
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)
}
pub fn into_thread(self) -> PrCapsThread {
let data = Arc::new(ThreadData::default());
let data2 = data.clone();
let jh = thread::Builder::new()
.name("SYS_nice thread".to_string())
.spawn(move || {
let data2 = data2;
let mut lock = data2.mutex.lock();
loop {
if lock.exit {
return;
}
if let Some(f) = lock.fun.take() {
f();
}
data2.cond.wait(&mut lock);
}
})
.expect("Could not spawn SYS_nice thread");
PrCapsThread {
thread: Some(jh),
data,
_no_send: Default::default(),
}
}
}
impl PrCapsThread {
pub unsafe fn run<T, F>(&self, f: F) -> T
where
F: FnOnce() -> T,
{
struct AssertSend<T>(T);
unsafe impl<T> Send for AssertSend<T> {}
struct Data<T> {
cond: Condvar,
mutex: Mutex<Option<AssertSend<T>>>,
}
let data = Arc::new(Data {
cond: Default::default(),
mutex: Default::default(),
});
let data2 = data.clone();
let f = AssertSend(f);
let fun = Box::new(move || {
let f = f;
let t = f.0();
*data2.mutex.lock() = Some(AssertSend(t));
data2.cond.notify_all();
});
let fun = unsafe {
mem::transmute::<Box<dyn FnOnce() + Send + '_>, Box<dyn FnOnce() + Send>>(fun)
};
self.data.mutex.lock().fun = Some(fun);
self.data.cond.notify_all();
let mut lock = data.mutex.lock();
loop {
if let Some(t) = lock.take() {
return t.0;
}
data.cond.wait(&mut lock);
}
}
}
impl Drop for PrCaps {
fn drop(&mut self) {
drop_all_pr_caps();
}
}
impl Drop for PrCapsThread {
fn drop(&mut self) {
self.data.mutex.lock().exit = true;
self.data.cond.notify_all();
let _ = self.thread.take().unwrap().join();
}
}
mod sys {
#![allow(dead_code)]
use uapi::c::pid_t;
pub const _LINUX_CAPABILITY_VERSION_3: u32 = 0x20080522;
pub const _LINUX_CAPABILITY_U32S_3: usize = 2;
#[repr(C)]
#[derive(Copy, Clone, Debug)]
pub struct cap_user_header_t {
pub version: u32,
pub pid: pid_t,
}
#[repr(C)]
#[derive(Copy, Clone, Debug, Default)]
pub struct cap_user_data_t {
pub effective: u32,
pub permitted: u32,
pub inheritable: u32,
}
pub const CAP_CHOWN: u32 = 0;
pub const CAP_DAC_OVERRIDE: u32 = 1;
pub const CAP_DAC_READ_SEARCH: u32 = 2;
pub const CAP_FOWNER: u32 = 3;
pub const CAP_FSETID: u32 = 4;
pub const CAP_KILL: u32 = 5;
pub const CAP_SETGID: u32 = 6;
pub const CAP_SETUID: u32 = 7;
pub const CAP_SETPCAP: u32 = 8;
pub const CAP_LINUX_IMMUTABLE: u32 = 9;
pub const CAP_NET_BIND_SERVICE: u32 = 10;
pub const CAP_NET_BROADCAST: u32 = 11;
pub const CAP_NET_ADMIN: u32 = 12;
pub const CAP_NET_RAW: u32 = 13;
pub const CAP_IPC_LOCK: u32 = 14;
pub const CAP_IPC_OWNER: u32 = 15;
pub const CAP_SYS_MODULE: u32 = 16;
pub const CAP_SYS_RAWIO: u32 = 17;
pub const CAP_SYS_CHROOT: u32 = 18;
pub const CAP_SYS_PTRACE: u32 = 19;
pub const CAP_SYS_PACCT: u32 = 20;
pub const CAP_SYS_ADMIN: u32 = 21;
pub const CAP_SYS_BOOT: u32 = 22;
pub const CAP_SYS_NICE: u32 = 23;
pub const CAP_SYS_RESOURCE: u32 = 24;
pub const CAP_SYS_TIME: u32 = 25;
pub const CAP_SYS_TTY_CONFIG: u32 = 26;
pub const CAP_MKNOD: u32 = 27;
pub const CAP_LEASE: u32 = 28;
pub const CAP_AUDIT_WRITE: u32 = 29;
pub const CAP_AUDIT_CONTROL: u32 = 30;
pub const CAP_SETFCAP: u32 = 31;
pub const CAP_MAC_OVERRIDE: u32 = 32;
pub const CAP_MAC_ADMIN: u32 = 33;
pub const CAP_SYSLOG: u32 = 34;
pub const CAP_WAKE_ALARM: u32 = 35;
pub const CAP_BLOCK_SUSPEND: u32 = 36;
pub const CAP_AUDIT_READ: u32 = 37;
pub const CAP_PERFMON: u32 = 38;
pub const CAP_BPF: u32 = 39;
pub const CAP_CHECKPOINT_RESTORE: u32 = 40;
}

View file

@ -74,6 +74,7 @@ use {
keyboard::KeyboardStateIds,
leaks::Tracker,
logger::Logger,
pr_caps::PrCapsThread,
rect::{Rect, Region},
renderer::Renderer,
scale::Scale,
@ -246,6 +247,7 @@ pub struct State {
pub show_pin_icon: Cell<bool>,
pub cl_matcher_manager: ClMatcherManager,
pub tl_matcher_manager: TlMatcherManager,
pub caps_thread: Option<PrCapsThread>,
}
// impl Drop for State {
@ -446,6 +448,7 @@ impl State {
&self.ring,
drm,
api.unwrap_or(self.default_gfx_api.get()),
self.caps_thread.as_ref(),
)
}

View file

@ -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;

View file

@ -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},
@ -135,6 +136,7 @@ pub fn ensure_reaper() -> c::pid_t {
if let Ok(id) = env::var(REAPER_VAR) {
if let Ok(id) = c::pid_t::from_str(&id) {
if uapi::getppid() == id {
set_deathsig();
return id;
}
}
@ -157,8 +159,10 @@ pub fn ensure_reaper() -> c::pid_t {
unsafe {
env::set_var(REAPER_VAR, reaper_pid.to_string());
}
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 {
@ -167,3 +171,9 @@ pub fn ensure_reaper() -> c::pid_t {
}
process::exit(1);
}
fn set_deathsig() {
unsafe {
c::prctl(c::PR_SET_PDEATHSIG, c::SIGKILL as c::c_ulong);
}
}

40
src/utils/nice.rs Normal file
View 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, &param) };
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()
}

View file

@ -1529,7 +1529,7 @@
"tile",
"quit",
"reload-config-toml",
"reload-config-to",
"reload-config-so",
"consume",
"forward",
"none",

View file

@ -3428,7 +3428,7 @@ The string should have one of the following values:
Reload the `config.toml`.
- `reload-config-to`:
- `reload-config-so`:
Reload the `config.so`.

View file

@ -790,7 +790,7 @@ SimpleActionName:
description: Terminate the compositor.
- value: reload-config-toml
description: Reload the `config.toml`.
- value: reload-config-to
- value: reload-config-so
description: Reload the `config.so`.
- value: consume
description: |