Improved error handling; also implemented some cleanup

This commit is contained in:
Johannes Leupolz 2025-10-27 22:25:31 +00:00
parent 6d096c2e62
commit 53bc1ff153
7 changed files with 77 additions and 44 deletions

View file

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

View file

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

View file

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

View file

@ -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<String> {
let path = format!("/run/udev/data/c{}:{}", major, minor);

View file

@ -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) {

View file

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

View file

@ -2,13 +2,14 @@
//
// Author: Johannes Leupolz <dev@leupolz.eu>
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<dyn Fn()>) -> 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");