From 53bc1ff153687ad51414359c594ef3a55c8f83f2 Mon Sep 17 00:00:00 2001 From: Johannes Leupolz Date: Mon, 27 Oct 2025 22:25:31 +0000 Subject: [PATCH] Improved error handling; also implemented some cleanup --- .../src/container/inject_in_container_job.rs | 13 +++++-- vuinputd/src/container/mknod_input_device.rs | 37 ++++++++++++++----- .../container/remove_from_container_job.rs | 21 +++++++++-- vuinputd/src/container/runtime_data.rs | 8 ++++ vuinputd/src/jobs/job.rs | 3 ++ vuinputd/src/main.rs | 31 ++-------------- vuinputd/src/namespace.rs | 8 +++- 7 files changed, 77 insertions(+), 44 deletions(-) diff --git a/vuinputd/src/container/inject_in_container_job.rs b/vuinputd/src/container/inject_in_container_job.rs index 061351e..8783462 100644 --- a/vuinputd/src/container/inject_in_container_job.rs +++ b/vuinputd/src/container/inject_in_container_job.rs @@ -81,7 +81,7 @@ impl InjectInContainerJob { number_of_attempt+=1; // wait a maximum of 5 seconds == 50 attempts Timer::after(Duration::from_millis(100)).await; - } + } if (netlink_data.is_none() || runtime_data.is_none()) { if netlink_data.is_none() { debug!("Give up reading netlink data"); @@ -98,11 +98,16 @@ impl InjectInContainerJob { let runtime_data = runtime_data.unwrap(); let netlink_data = netlink_data.unwrap(); + let child_pid = run_in_net_and_mnt_namespace(self.namespaces, Box::new(move || { - - ensure_input_device(self.dev_path.clone(), self.major, self.minor).unwrap(); + + if let Err(e) = ensure_input_device(self.dev_path.clone(), self.major, self.minor) { + debug!("Error creating input device {}: {e}",self.dev_path.clone()); + }; ensure_udev_structure().unwrap(); - write_udev_data(runtime_data.as_str(), major, minor).unwrap(); + if let Err(e) = write_udev_data(runtime_data.as_str(), major, minor) { + debug!("Error writing udev data for device {}: {e}",self.dev_path.clone()); + }; send_udev_monitor_message_with_properties(netlink_data.clone()); })) diff --git a/vuinputd/src/container/mknod_input_device.rs b/vuinputd/src/container/mknod_input_device.rs index 0f0d6f8..9c39af5 100644 --- a/vuinputd/src/container/mknod_input_device.rs +++ b/vuinputd/src/container/mknod_input_device.rs @@ -10,6 +10,14 @@ use std::os::unix::fs::{MetadataExt, PermissionsExt}; use std::path::Path; pub fn ensure_input_device(dev_path: String, major: u64, minor: u64) -> Result<(), Box> { + + let input_dir = Path::new("/dev/input"); + // Create directory like `mkdir -p` + if !input_dir.exists() { + println!("Create /dev/input"); + fs::create_dir_all(input_dir)?; + } + let path = Path::new(&dev_path); let expected_dev = makedev(major, minor); let expected_mode = 0o666; @@ -64,14 +72,25 @@ pub fn ensure_input_device(dev_path: String, major: u64, minor: u64) -> Result<( Ok(()) } -/* -fn main() { - let name = "/dev/input/example0"; - let major = 13; // example values - let minor = 37; +pub fn remove_input_device(dev_path: String, major: u64, minor: u64) -> Result<(), Box> { + let path = Path::new(&dev_path); + let expected_dev = makedev(major, minor); - if let Err(e) = ensure_input_device(name, major, minor) { - eprintln!("Error: {}", e); + // --- Step 1: Ensure it is the correct device --- + if !path.exists() { + return Err("Device does not exist".into()); } -} - */ + match stat(path) { + Ok(st) => { + let is_char = (st.st_mode & libc::S_IFMT as u32) == libc::S_IFCHR as u32; + let dev_ok = st.st_rdev == expected_dev; + if !(is_char && dev_ok) { + return Err("Device that should be deleted has wrong major and minor".into()) + } + } + Err(x) => return Err("Could not execute stat on device file".into()) + } + + let _ = fs::remove_file(path); + Ok(()) +} \ No newline at end of file diff --git a/vuinputd/src/container/remove_from_container_job.rs b/vuinputd/src/container/remove_from_container_job.rs index b508fbf..d918a03 100644 --- a/vuinputd/src/container/remove_from_container_job.rs +++ b/vuinputd/src/container/remove_from_container_job.rs @@ -9,22 +9,28 @@ use async_pidfd::AsyncPidFd; use log::debug; use crate::{ - container::{mknod_input_device::ensure_input_device, netlink_message::send_udev_monitor_message_with_properties, runtime_data::{self, ensure_udev_structure, read_udev_data, write_udev_data}}, jobs::job::{Job, JobTarget}, monitor_udev::EVENT_STORE, namespace::{run_in_net_and_mnt_namespace, Namespaces} + container::{mknod_input_device::{ensure_input_device, remove_input_device}, netlink_message::send_udev_monitor_message_with_properties, runtime_data::{self, delete_udev_data, ensure_udev_structure, read_udev_data, write_udev_data}}, jobs::job::{Job, JobTarget}, monitor_udev::EVENT_STORE, namespace::{Namespaces, run_in_net_and_mnt_namespace} }; #[derive(Clone,Debug)] pub struct RemoveFromContainerJob { namespaces: Namespaces, target: JobTarget, + dev_path: String, sys_path: String, + major: u64, + minor: u64, } impl RemoveFromContainerJob { - pub fn new(namespaces: Namespaces,sys_path: String) -> Self { + pub fn new(namespaces: Namespaces,dev_path: String, sys_path: String, major: u64, minor: u64) -> Self { Self { namespaces: namespaces.clone(), target: JobTarget::Container(namespaces), + dev_path: dev_path, sys_path: sys_path, + major: major , + minor: minor, } } } @@ -49,7 +55,6 @@ impl Job for RemoveFromContainerJob { impl RemoveFromContainerJob { async fn remove_from_container(self) { - // TODO: Here is a race with inject in container. let netlink_event = match EVENT_STORE.get().unwrap().lock().unwrap().take(&self.sys_path) { Some(netlink_event) => netlink_event, None => { @@ -66,11 +71,21 @@ impl RemoveFromContainerJob { // define for capturing let mut netlink_data = netlink_data.unwrap().clone(); + let major = self.major; + let minor=self.minor; let _ = netlink_data.insert("ACTION".to_string(),"remove".to_string()); let child_pid = run_in_net_and_mnt_namespace(self.namespaces, Box::new(move || { + // TODO: we should keep the same order as event_execute_rules_on_remove in + // https://github.com/systemd/systemd/blob/main/src/udev/udev-event.c send_udev_monitor_message_with_properties(netlink_data.clone()); + if let Err(e) = delete_udev_data(major,minor) { + debug!("Error deleting udev data for {}:{}: {e}",major,minor); + } + if let Err(e) = remove_input_device(self.dev_path.clone(), self.major, self.minor) { + debug!("Error removing input device {}: {e}",self.dev_path.clone()); + }; })) .expect("subprocess should work"); diff --git a/vuinputd/src/container/runtime_data.rs b/vuinputd/src/container/runtime_data.rs index 3528f7f..94327d4 100644 --- a/vuinputd/src/container/runtime_data.rs +++ b/vuinputd/src/container/runtime_data.rs @@ -62,6 +62,14 @@ pub fn write_udev_data(content: &str, major: u64, minor: u64) -> io::Result<()> Ok(()) } +/// Delete udev data for a given major/minor number +/// - `major`, `minor` = device numbers +pub fn delete_udev_data(major: u64, minor: u64) -> io::Result<()> { + + let path = format!("/run/udev/data/c{}:{}", major, minor); + fs::remove_file(&path)?; + Ok(()) +} pub fn read_udev_data(major: u64, minor: u64) -> io::Result { let path = format!("/run/udev/data/c{}:{}", major, minor); diff --git a/vuinputd/src/jobs/job.rs b/vuinputd/src/jobs/job.rs index ba0ca8b..abedec6 100644 --- a/vuinputd/src/jobs/job.rs +++ b/vuinputd/src/jobs/job.rs @@ -6,6 +6,7 @@ use async_channel::{Receiver, Sender}; use futures::executor::{LocalPool, LocalSpawner}; use futures::future::RemoteHandle; use futures::task::LocalSpawnExt; +use log::debug; use std::sync::Mutex; use std::thread::{self, JoinHandle}; use std::{collections::HashMap, future::Future, pin::Pin, sync::Arc}; @@ -109,7 +110,9 @@ impl Dispatcher { pub fn close(&mut self) { self.tx = None; + debug!("Checking for running jobs before shutdown"); self.future_handles.lock().unwrap().clear(); + debug!("Pending jobs canceled"); } pub fn wait_until_finished(&mut self) { diff --git a/vuinputd/src/main.rs b/vuinputd/src/main.rs index 9176de4..e6199cb 100644 --- a/vuinputd/src/main.rs +++ b/vuinputd/src/main.rs @@ -13,36 +13,11 @@ // renaming // use in container // cancellation token -// device removal (udev netlink message) // distinguish between cleanup jobs that must not be cancelled and other jobs (especially background jobs) // naming: dev_path vs dev_node. I guess I mean the same. // Send warning, if udev monitor does not exist -// test scenario: -// vuinputd -// mouse-advanced -// ls -lh /dev/input/event9 -// cat /run/udev/data/c13:73 -// machinectl shell johannes@gamestreamingserver -// systemctl --user stop headless-labwc.service -// machinectl shell gamestreamingserver -// mkdir -p /run/udev/data/ -// touch /run/udev/control -// mknod /dev/input/event9 c 13 73 -// vim /run/udev/data/c13:73 -// machinectl shell johannes@gamestreamingserver -// systemctl --user start headless-labwc.service -// systemctl --user start wayvnc.service - - -//libinput backend -// seatd -//libinput-tools -// groups -// 777 for input9 -// char-input to systemd - use libc::O_CLOEXEC; use libc::{iovec, off_t, size_t, EBADRQC, EIO, ENOENT}; use libc::{uinput_abs_setup, uinput_ff_erase, uinput_ff_upload, uinput_setup}; @@ -287,7 +262,8 @@ unsafe extern "C" fn vuinput_release( // remove device in container, if the request was really from another namespace if ! SELF_NAMESPACES.get().unwrap().equal_mnt_and_net(&vuinput_state.ns_of_requestor) { - let remove_job=RemoveFromContainerJob::new(vuinput_state.ns_of_requestor.clone(),vuinput_state.input_device.as_ref().unwrap().syspath.clone()); + let input_device = vuinput_state.input_device.as_ref().unwrap(); + let remove_job=RemoveFromContainerJob::new(vuinput_state.ns_of_requestor.clone(),input_device.devnode.clone(),input_device.syspath.clone(),input_device.major,input_device.minor); JOB_DISPATCHER.get().unwrap().lock().unwrap().dispatch(Box::new(remove_job)); } @@ -468,7 +444,8 @@ unsafe extern "C" fn vuinput_ioctl( // Remove device in container, if the request was really from another namespace if ! SELF_NAMESPACES.get().unwrap().equal_mnt_and_net(&vuinput_state.ns_of_requestor) { - let remove_job=RemoveFromContainerJob::new(vuinput_state.ns_of_requestor.clone(),vuinput_state.input_device.as_ref().unwrap().syspath.clone()); + let input_device = vuinput_state.input_device.as_ref().unwrap(); + let remove_job=RemoveFromContainerJob::new(vuinput_state.ns_of_requestor.clone(),input_device.devnode.clone(),input_device.syspath.clone(),input_device.major,input_device.minor); JOB_DISPATCHER.get().unwrap().lock().unwrap().dispatch(Box::new(remove_job)); } diff --git a/vuinputd/src/namespace.rs b/vuinputd/src/namespace.rs index bbc510d..441a51f 100644 --- a/vuinputd/src/namespace.rs +++ b/vuinputd/src/namespace.rs @@ -2,13 +2,14 @@ // // Author: Johannes Leupolz +use log::debug; use nix::{ sched::{setns, CloneFlags}, unistd::{fork, ForkResult, Pid}, }; use std::{ fs::{self, File}, - os::fd::AsFd, + os::fd::AsFd, path::{self, Path}, }; #[derive(Debug, Default, Clone, Eq, PartialEq, Hash)] @@ -106,6 +107,11 @@ pub fn run_in_net_and_mnt_namespace(ns: Namespaces, func: Box) -> nix: } ForkResult::Child => { // enter namespace + let path: &Path = Path::new(ns.nspath.as_str()); + if !fs::exists(path).unwrap() { + debug!("the process whose namespaces we want to enter does not exist anymore!"); + std::process::exit(0); + } let net = File::open(ns.nspath.clone() + "/net").expect("net not found"); let mnt = File::open(ns.nspath.clone() + "/mnt").expect("mnt not found"); setns(net.as_fd(), CloneFlags::CLONE_NEWNET).expect("couldn't enter net");