tty: Re-evaluate ignored nodes on udev add events and session resume (#3651)

* tty: Re-evaluate ignored nodes on udev add events

When a DRM device is removed and rescanned (e.g. during a dGPU
suspend/resume cycle), the kernel may assign it a new device ID.
Re-evaluating the config paths specifically on UdevEvent::Added
ensures that symlinks like /dev/dri/by-path/... are resolved to
their new underlying IDs, preventing niri from accidentally
opening an ignored hotplugged device.

This check is restricted to device additions to avoid unnecessary
filesystem I/O on the hot path during bursts of other udev events.

* fixes

---------

Co-authored-by: Ivan Molodetskikh <yalterz@gmail.com>
This commit is contained in:
jan Lemata
2026-04-19 14:20:56 +05:30
committed by GitHub
parent d09fa2709c
commit 04c422e43f
+23 -29
View File
@@ -97,9 +97,6 @@ pub struct Tty {
dmabuf_global: Option<DmabufGlobal>, dmabuf_global: Option<DmabufGlobal>,
// The output config had changed, but the session is paused, so we need to update it on resume. // The output config had changed, but the session is paused, so we need to update it on resume.
update_output_config_on_resume: bool, update_output_config_on_resume: bool,
// The ignored nodes have changed, but the session is paused, so we need to update it on
// resume.
update_ignored_nodes_on_resume: bool,
// Whether the debug tinting is enabled. // Whether the debug tinting is enabled.
debug_tint: bool, debug_tint: bool,
ipc_outputs: Arc<Mutex<IpcOutputMap>>, ipc_outputs: Arc<Mutex<IpcOutputMap>>,
@@ -495,11 +492,6 @@ impl Tty {
} }
info!("using as the render node: {node_path}"); info!("using as the render node: {node_path}");
let mut ignored_nodes = ignored_nodes_from_config(&config.borrow());
if ignored_nodes.remove(&primary_node) || ignored_nodes.remove(&primary_render_node) {
warn!("ignoring the primary node or render node is not allowed");
}
Ok(Self { Ok(Self {
config, config,
session, session,
@@ -508,11 +500,10 @@ impl Tty {
gpu_manager, gpu_manager,
primary_node, primary_node,
primary_render_node, primary_render_node,
ignored_nodes, ignored_nodes: HashSet::new(),
devices: HashMap::new(), devices: HashMap::new(),
dmabuf_global: None, dmabuf_global: None,
update_output_config_on_resume: false, update_output_config_on_resume: false,
update_ignored_nodes_on_resume: false,
debug_tint: false, debug_tint: false,
ipc_outputs: Arc::new(Mutex::new(HashMap::new())), ipc_outputs: Arc::new(Mutex::new(HashMap::new())),
}) })
@@ -527,6 +518,9 @@ impl Tty {
return; return;
} }
// Initialize the ignored nodes.
self.ignored_nodes = self.compute_ignored_nodes();
let udev = self.udev_dispatcher.clone(); let udev = self.udev_dispatcher.clone();
let udev = udev.as_source_ref(); let udev = udev.as_source_ref();
@@ -566,6 +560,10 @@ impl Tty {
return; return;
} }
// Recompute ignored nodes to resolve symlinks (like /dev/dri/by-path/...) to their
// new underlying device IDs.
self.ignored_nodes = self.compute_ignored_nodes();
if let Err(err) = self.device_added(device_id, &path, niri) { if let Err(err) = self.device_added(device_id, &path, niri) {
warn!("error adding device: {err:?}"); warn!("error adding device: {err:?}");
} }
@@ -613,16 +611,9 @@ impl Tty {
warn!("error resuming libinput"); warn!("error resuming libinput");
} }
if self.update_ignored_nodes_on_resume { // While the session was suspended, GPUs could have been added, so
self.update_ignored_nodes_on_resume = false; // /dev/dri/by-path/... symlinks need to be re-resolved.
let mut ignored_nodes = ignored_nodes_from_config(&self.config.borrow()); self.ignored_nodes = self.compute_ignored_nodes();
if ignored_nodes.remove(&self.primary_node)
|| ignored_nodes.remove(&self.primary_render_node)
{
warn!("ignoring the primary node or render node is not allowed");
}
self.ignored_nodes = ignored_nodes;
}
let mut device_list = self let mut device_list = self
.udev_dispatcher .udev_dispatcher
@@ -2292,22 +2283,25 @@ impl Tty {
} }
} }
pub fn update_ignored_nodes_config(&mut self, niri: &mut Niri) { fn compute_ignored_nodes(&self) -> HashSet<DrmNode> {
let _span = tracy_client::span!("Tty::update_ignored_nodes_config");
// If we're inactive, we can't do anything, so just set a flag for later.
if !self.session.is_active() {
self.update_ignored_nodes_on_resume = true;
return;
}
let mut ignored_nodes = ignored_nodes_from_config(&self.config.borrow()); let mut ignored_nodes = ignored_nodes_from_config(&self.config.borrow());
if ignored_nodes.remove(&self.primary_node) if ignored_nodes.remove(&self.primary_node)
|| ignored_nodes.remove(&self.primary_render_node) || ignored_nodes.remove(&self.primary_render_node)
{ {
warn!("ignoring the primary node or render node is not allowed"); warn!("ignoring the primary node or render node is not allowed");
} }
ignored_nodes
}
pub fn update_ignored_nodes_config(&mut self, niri: &mut Niri) {
let _span = tracy_client::span!("Tty::update_ignored_nodes_config");
// If we're inactive, we can't do anything, but we'll recompute in ActivateSession.
if !self.session.is_active() {
return;
}
let ignored_nodes = self.compute_ignored_nodes();
if ignored_nodes == self.ignored_nodes { if ignored_nodes == self.ignored_nodes {
return; return;
} }