From f0bff128a6090be495d0fab928495c4d16b53c67 Mon Sep 17 00:00:00 2001 From: Johannes Leupolz Date: Wed, 10 Dec 2025 22:46:52 +0000 Subject: [PATCH] cargo fmt --- cuse-lowlevel/build.rs | 24 ++-- cuse-lowlevel/src/lib.rs | 7 +- vuinput-examples/src/bin/mouse-advanced.rs | 15 +- vuinput-examples/src/bin/mouse-reuse.rs | 16 +-- vuinputd/src/cuse_device/mod.rs | 9 +- vuinputd/src/cuse_device/state.rs | 46 +++--- vuinputd/src/cuse_device/vuinput_ioctl.rs | 78 +++++++--- vuinputd/src/cuse_device/vuinput_open.rs | 16 +-- vuinputd/src/cuse_device/vuinput_release.rs | 36 +++-- vuinputd/src/cuse_device/vuinput_write.rs | 85 +++++------ vuinputd/src/job_engine/closure_job.rs | 6 +- vuinputd/src/job_engine/mod.rs | 4 +- vuinputd/src/job_engine/tests.rs | 9 +- vuinputd/src/jobs/inject_in_container_job.rs | 95 ++++++++----- vuinputd/src/jobs/mknod_input_device.rs | 11 +- vuinputd/src/jobs/mod.rs | 6 +- vuinputd/src/jobs/monitor_udev_job.rs | 12 +- vuinputd/src/jobs/netlink_message.rs | 47 +++--- .../src/jobs/remove_from_container_job.rs | 78 ++++++---- vuinputd/src/jobs/runtime_data.rs | 3 - vuinputd/src/main.rs | 39 +++-- vuinputd/src/process_tools/mod.rs | 134 +++++++++--------- 22 files changed, 429 insertions(+), 347 deletions(-) diff --git a/cuse-lowlevel/build.rs b/cuse-lowlevel/build.rs index 0c45b92..9813c1b 100644 --- a/cuse-lowlevel/build.rs +++ b/cuse-lowlevel/build.rs @@ -4,7 +4,7 @@ // Author: Johannes Leupolz // // This library is heavily baased on https://github.com/richard-w/libfuse-sys -// but adopted to only provide the low-level modules of fuse and cuse. +// but adopted to only provide the low-level modules of fuse and cuse. extern crate bindgen; extern crate pkg_config; @@ -15,7 +15,6 @@ use std::path::PathBuf; const FUSE_USE_VERSION: u32 = 314; //fuse version of ubuntu 24.04 - fn fuse_binding_filter(builder: bindgen::Builder) -> bindgen::Builder { let builder = builder // Whitelist "fuse_*" symbols and blocklist everything else @@ -103,18 +102,13 @@ fn main() { let mut pkgcfg = pkg_config::Config::new(); // Find libfuse - let fuse3_lib = pkgcfg.cargo_metadata(true).probe("fuse3").expect("Failed to find pkg-config module fuse3"); - + let fuse3_lib = pkgcfg + .cargo_metadata(true) + .probe("fuse3") + .expect("Failed to find pkg-config module fuse3"); + // Generate lowlevel bindings - generate_fuse_bindings( - "fuse_lowlevel.h", - &fuse3_lib, - fuse_binding_filter, - ); + generate_fuse_bindings("fuse_lowlevel.h", &fuse3_lib, fuse_binding_filter); // Generate lowlevel cuse bindings - generate_fuse_bindings( - "cuse_lowlevel.h", - &fuse3_lib, - cuse_binding_filter, - ); -} \ No newline at end of file + generate_fuse_bindings("cuse_lowlevel.h", &fuse3_lib, cuse_binding_filter); +} diff --git a/cuse-lowlevel/src/lib.rs b/cuse-lowlevel/src/lib.rs index 5a3b6c1..eb5296d 100644 --- a/cuse-lowlevel/src/lib.rs +++ b/cuse-lowlevel/src/lib.rs @@ -4,7 +4,7 @@ // Author: Johannes Leupolz // // This library is heavily baased on https://github.com/richard-w/libfuse-sys -// but adopted to only provide the low-level modules of fuse and cuse. +// but adopted to only provide the low-level modules of fuse and cuse. #![allow(non_snake_case)] #![allow(non_camel_case_types)] @@ -15,7 +15,6 @@ use libc::*; - pub mod fuse_lowlevel { use super::*; include!(concat!(env!("OUT_DIR"), "/fuse_lowlevel.rs")); @@ -24,8 +23,8 @@ pub mod fuse_lowlevel { pub mod cuse_lowlevel { use super::*; include!(concat!(env!("OUT_DIR"), "/cuse_lowlevel.rs")); - + use fuse_lowlevel::{ fuse_args, fuse_conn_info, fuse_file_info, fuse_pollhandle, fuse_req_t, fuse_session, }; -} \ No newline at end of file +} diff --git a/vuinput-examples/src/bin/mouse-advanced.rs b/vuinput-examples/src/bin/mouse-advanced.rs index 7269ada..e1c77e5 100644 --- a/vuinput-examples/src/bin/mouse-advanced.rs +++ b/vuinput-examples/src/bin/mouse-advanced.rs @@ -23,8 +23,7 @@ const REL_Y: i32 = 1; const SYN_REPORT: i32 = 0; const BUS_USB: u16 = 0x03; -/// - +/// fn emit(fd: c_int, ev_type: i32, code: i32, val: i32) -> io::Result<()> { // libc's input_event struct layout: @@ -62,11 +61,10 @@ fn main() -> io::Result<()> { // open device - matches: open("/dev/uinput-test", O_WRONLY | O_NONBLOCK); let args: Vec = std::env::args().collect(); - let device= - match args.len() { - 2 => args[1].clone(), - _ => "/dev/uinput".to_string() - }; + let device = match args.len() { + 2 => args[1].clone(), + _ => "/dev/uinput".to_string(), + }; let path = CString::new(device).unwrap(); let fd = unsafe { open(path.as_ptr(), O_WRONLY | O_NONBLOCK) }; @@ -97,19 +95,16 @@ fn main() -> io::Result<()> { std::process::exit(1); }); - ui_set_evbit(fd, EV_REL.try_into().unwrap()).unwrap_or_else(|e| { eprintln!("ui_set_evbit(EV_REL) failed: {:?}", e); std::process::exit(1); }); - ui_set_relbit(fd, REL_X.try_into().unwrap()).unwrap_or_else(|e| { eprintln!("ui_set_relbit(REL_X) failed: {:?}", e); std::process::exit(1); }); - ui_set_relbit(fd, REL_Y.try_into().unwrap()).unwrap_or_else(|e| { eprintln!("ui_set_relbit(REL_Y) failed: {:?}", e); std::process::exit(1); diff --git a/vuinput-examples/src/bin/mouse-reuse.rs b/vuinput-examples/src/bin/mouse-reuse.rs index 7b32db3..06c34a5 100644 --- a/vuinput-examples/src/bin/mouse-reuse.rs +++ b/vuinput-examples/src/bin/mouse-reuse.rs @@ -23,8 +23,7 @@ const REL_Y: i32 = 1; const SYN_REPORT: i32 = 0; const BUS_USB: u16 = 0x03; -/// - +/// fn emit(fd: c_int, ev_type: i32, code: i32, val: i32) -> io::Result<()> { // libc's input_event struct layout: @@ -62,11 +61,10 @@ fn main() -> io::Result<()> { // open device - matches: open("/dev/uinput-test", O_WRONLY | O_NONBLOCK); let args: Vec = std::env::args().collect(); - let device= - match args.len() { - 2 => args[1].clone(), - _ => "/dev/uinput".to_string() - }; + let device = match args.len() { + 2 => args[1].clone(), + _ => "/dev/uinput".to_string(), + }; let path = CString::new(device).unwrap(); let fd = unsafe { open(path.as_ptr(), O_WRONLY | O_NONBLOCK) }; @@ -97,19 +95,16 @@ fn main() -> io::Result<()> { std::process::exit(1); }); - ui_set_evbit(fd, EV_REL.try_into().unwrap()).unwrap_or_else(|e| { eprintln!("ui_set_evbit(EV_REL) failed: {:?}", e); std::process::exit(1); }); - ui_set_relbit(fd, REL_X.try_into().unwrap()).unwrap_or_else(|e| { eprintln!("ui_set_relbit(REL_X) failed: {:?}", e); std::process::exit(1); }); - ui_set_relbit(fd, REL_Y.try_into().unwrap()).unwrap_or_else(|e| { eprintln!("ui_set_relbit(REL_Y) failed: {:?}", e); std::process::exit(1); @@ -217,7 +212,6 @@ fn main() -> io::Result<()> { sleep(Duration::from_millis(300)); } - // Give userspace time to read events sleep(Duration::from_secs(5)); diff --git a/vuinputd/src/cuse_device/mod.rs b/vuinputd/src/cuse_device/mod.rs index b4e4d8e..5c5852e 100644 --- a/vuinputd/src/cuse_device/mod.rs +++ b/vuinputd/src/cuse_device/mod.rs @@ -4,20 +4,19 @@ pub mod state; pub mod vuinput_ioctl; -pub mod vuinput_write; -pub mod vuinput_release; pub mod vuinput_open; +pub mod vuinput_release; +pub mod vuinput_write; -use std::{fs, io}; +use std::io::ErrorKind; use std::os::unix::fs::{FileTypeExt, MetadataExt}; -use std::io::{ErrorKind}; +use std::{fs, io}; use ::cuse_lowlevel::*; use state::*; pub const BUS_USB: u16 = 0x03; - // Instance of cuse_lowlevel_ops with all stubs assigned. // Setting to None leads to e.g. "write error: Function not implemented". // You can find the implementations of the uinput default (open, release ,read, write, poll, diff --git a/vuinputd/src/cuse_device/state.rs b/vuinputd/src/cuse_device/state.rs index 7e8b503..d296a9d 100644 --- a/vuinputd/src/cuse_device/state.rs +++ b/vuinputd/src/cuse_device/state.rs @@ -3,7 +3,7 @@ // Author: Johannes Leupolz use std::collections::HashMap; -use std::fs::{File}; +use std::fs::File; use std::sync::{Arc, Mutex, OnceLock, RwLock}; use ::cuse_lowlevel::*; @@ -12,8 +12,8 @@ use crate::process_tools::RequestingProcess; #[derive(Debug)] pub struct VuInputDevice { - pub major : u64, - pub minor : u64, + pub major: u64, + pub minor: u64, pub syspath: String, pub devnode: String, } @@ -22,12 +22,12 @@ pub struct VuInputDevice { pub struct VuInputState { pub file: File, pub requesting_process: RequestingProcess, - pub input_device: Option + pub input_device: Option, } -#[derive(Debug,Eq, Hash, PartialEq, Clone)] +#[derive(Debug, Eq, Hash, PartialEq, Clone)] pub enum VuFileHandle { - Fh(u64) + Fh(u64), } impl VuFileHandle { @@ -39,15 +39,13 @@ impl VuFileHandle { impl std::fmt::Display for VuFileHandle { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - VuFileHandle::Fh(fh) => writeln!(f, "VuFileHandle({:?})",fh)?, + VuFileHandle::Fh(fh) => writeln!(f, "VuFileHandle({:?})", fh)?, } Ok(()) } } -pub fn get_vuinput_state( - fh:&VuFileHandle, -) -> Result>, String> { +pub fn get_vuinput_state(fh: &VuFileHandle) -> Result>, String> { let map = VUINPUT_STATE .get() .ok_or("global not initialized".to_string())?; @@ -58,11 +56,7 @@ pub fn get_vuinput_state( .ok_or("handle not opened".to_string()) } - -pub fn insert_vuinput_state( - fh:&VuFileHandle, - state: VuInputState, -) -> Result<(), String> { +pub fn insert_vuinput_state(fh: &VuFileHandle, state: VuInputState) -> Result<(), String> { let map = VUINPUT_STATE .get() .ok_or("global not initialized".to_string())?; @@ -79,9 +73,7 @@ pub fn insert_vuinput_state( Ok(()) } -pub fn remove_vuinput_state( - fh:&VuFileHandle, -) -> Result>, String> { +pub fn remove_vuinput_state(fh: &VuFileHandle) -> Result>, String> { let map = VUINPUT_STATE .get() .ok_or("global not initialized".to_string())?; @@ -91,22 +83,24 @@ pub fn remove_vuinput_state( } pub fn initialize_vuinput_state() { - VUINPUT_STATE.set(RwLock::new(HashMap::new())).expect("failed to initialize global state"); + VUINPUT_STATE + .set(RwLock::new(HashMap::new())) + .expect("failed to initialize global state"); } pub fn initialize_dedup_last_error() { - DEDUP_LAST_ERROR.set(Mutex::new(None)).expect("failed to initialize the log deduplication state"); + DEDUP_LAST_ERROR + .set(Mutex::new(None)) + .expect("failed to initialize the log deduplication state"); } - #[derive(Debug)] pub enum VuError { - WriteError + WriteError, } - -pub static VUINPUT_STATE: OnceLock>>>> = OnceLock::new(); +pub static VUINPUT_STATE: OnceLock>>>> = + OnceLock::new(); // For log limiting. Idea: Move to log_limit crate -pub static DEDUP_LAST_ERROR: OnceLock>> = OnceLock::new(); - +pub static DEDUP_LAST_ERROR: OnceLock>> = OnceLock::new(); diff --git a/vuinputd/src/cuse_device/vuinput_ioctl.rs b/vuinputd/src/cuse_device/vuinput_ioctl.rs index 9483c70..2ad991f 100644 --- a/vuinputd/src/cuse_device/vuinput_ioctl.rs +++ b/vuinputd/src/cuse_device/vuinput_ioctl.rs @@ -2,17 +2,17 @@ // // Author: Johannes Leupolz +use ::cuse_lowlevel::*; use libc::{iovec, size_t, EBADRQC}; use libc::{uinput_abs_setup, uinput_ff_erase, uinput_ff_upload, uinput_setup}; -use ::cuse_lowlevel::*; -use log::{debug}; -use std::ffi::{CStr}; +use log::debug; +use std::ffi::CStr; use std::io::Write; use std::os::fd::AsRawFd; use std::os::raw::{c_char, c_int, c_uint, c_void}; use uinput_ioctls::*; -use crate::cuse_device::{VuFileHandle, get_vuinput_state}; +use crate::cuse_device::{get_vuinput_state, VuFileHandle}; use crate::job_engine::JOB_DISPATCHER; use crate::jobs::inject_in_container_job::InjectInContainerJob; use crate::jobs::remove_from_container_job::RemoveFromContainerJob; @@ -46,7 +46,7 @@ pub unsafe extern "C" fn vuinput_ioctl( //UI_ABS_SETUP => UI_ABS_SETUP_WITHOUT_SIZE, _ => cmd_u64, }; - let vufh= VuFileHandle::from_fuse_file_info(_fi.as_ref().unwrap()); + let vufh = VuFileHandle::from_fuse_file_info(_fi.as_ref().unwrap()); let vuinput_state_mutex = get_vuinput_state(&vufh).unwrap(); let fh = &(*_fi).fh; let mut vuinput_state = vuinput_state_mutex.lock().unwrap(); @@ -166,21 +166,44 @@ pub unsafe extern "C" fn vuinput_ioctl( debug!("fh {}: syspath: {}", fh, sysname); let devnode = fetch_device_node(&sysname).unwrap(); debug!("fh {}: devnode: {}", fh, devnode); - let (major,minor) = fetch_major_minor(&devnode).unwrap(); - debug!("fh {}: major: {} minor: {} ", fh, major,minor); - vuinput_state.input_device = Some(VuInputDevice {major: major, minor: minor, syspath: sysname.clone(), devnode: devnode.clone() }); + let (major, minor) = fetch_major_minor(&devnode).unwrap(); + debug!("fh {}: major: {} minor: {} ", fh, major, minor); + vuinput_state.input_device = Some(VuInputDevice { + major: major, + minor: minor, + syspath: sysname.clone(), + devnode: devnode.clone(), + }); // Create device in container, if the request was really from another namespace - if ! SELF_NAMESPACES.get().unwrap().equal_mnt_and_net(&vuinput_state.requesting_process.namespaces) { - let inject_job=InjectInContainerJob::new(vuinput_state.requesting_process.clone(),devnode.clone(),sysname.clone(),major,minor); + if !SELF_NAMESPACES + .get() + .unwrap() + .equal_mnt_and_net(&vuinput_state.requesting_process.namespaces) + { + let inject_job = InjectInContainerJob::new( + vuinput_state.requesting_process.clone(), + devnode.clone(), + sysname.clone(), + major, + minor, + ); let awaiter = inject_job.get_awaiter_for_state(); - JOB_DISPATCHER.get().unwrap().lock().unwrap().dispatch(Box::new(inject_job)); + JOB_DISPATCHER + .get() + .unwrap() + .lock() + .unwrap() + .dispatch(Box::new(inject_job)); awaiter(&jobs::inject_in_container_job::State::Finished); - debug!("fh {}: injecting dev-nodes in container has been finished ", fh); + debug!( + "fh {}: injecting dev-nodes in container has been finished ", + fh + ); } // write a SYN-event (which is just zeros) just for validation - let syn_event : [u8; 24] = [0; 24]; + let syn_event: [u8; 24] = [0; 24]; vuinput_state.file.write_all(&syn_event).unwrap(); fuse_lowlevel::fuse_reply_ioctl(_req, 0, std::ptr::null(), 0); @@ -190,13 +213,32 @@ pub unsafe extern "C" fn vuinput_ioctl( let input_device = vuinput_state.input_device.take(); // Remove device in container, if the request was really from another namespace - if input_device.is_some() && ! SELF_NAMESPACES.get().unwrap().equal_mnt_and_net(&vuinput_state.requesting_process.namespaces) { + if input_device.is_some() + && !SELF_NAMESPACES + .get() + .unwrap() + .equal_mnt_and_net(&vuinput_state.requesting_process.namespaces) + { let input_device = input_device.unwrap(); - let remove_job=RemoveFromContainerJob::new(vuinput_state.requesting_process.clone(),input_device.devnode.clone(),input_device.syspath.clone(),input_device.major,input_device.minor); + let remove_job = RemoveFromContainerJob::new( + vuinput_state.requesting_process.clone(), + input_device.devnode.clone(), + input_device.syspath.clone(), + input_device.major, + input_device.minor, + ); let awaiter = remove_job.get_awaiter_for_state(); - JOB_DISPATCHER.get().unwrap().lock().unwrap().dispatch(Box::new(remove_job)); + JOB_DISPATCHER + .get() + .unwrap() + .lock() + .unwrap() + .dispatch(Box::new(remove_job)); awaiter(&jobs::remove_from_container_job::State::Finished); - debug!("fh {}: removing dev-nodes from container has been finished ", fh); + debug!( + "fh {}: removing dev-nodes from container has been finished ", + fh + ); } ui_dev_destroy(fd).unwrap(); @@ -364,7 +406,6 @@ pub unsafe extern "C" fn vuinput_ioctl( } } - pub fn fetch_device_node(path: &str) -> io::Result { for entry in fs::read_dir(path)? { let entry = entry?; // propagate per-entry errors @@ -396,4 +437,3 @@ pub fn fetch_major_minor(path: &str) -> io::Result<(u64, u64)> { Ok((major, minor)) } - diff --git a/vuinputd/src/cuse_device/vuinput_open.rs b/vuinputd/src/cuse_device/vuinput_open.rs index 9acfffd..757c854 100644 --- a/vuinputd/src/cuse_device/vuinput_open.rs +++ b/vuinputd/src/cuse_device/vuinput_open.rs @@ -2,18 +2,18 @@ // // Author: Johannes Leupolz -use std::sync::OnceLock; -use libc::{O_CLOEXEC}; -use libc::{ENOENT}; use ::cuse_lowlevel::*; +use libc::ENOENT; +use libc::O_CLOEXEC; use log::{debug, error}; -use std::fs::{OpenOptions}; -use std::os::unix::fs::{OpenOptionsExt}; +use std::fs::OpenOptions; +use std::os::unix::fs::OpenOptionsExt; use std::path::Path; use std::sync::atomic::{AtomicU64, Ordering}; +use std::sync::OnceLock; -use crate::process_tools::{Pid, get_requesting_process}; use crate::cuse_device::*; +use crate::process_tools::{get_requesting_process, Pid}; pub static VUINPUT_COUNTER: OnceLock = OnceLock::new(); @@ -47,7 +47,7 @@ pub unsafe extern "C" fn vuinput_open( VuInputState { file: v, requesting_process, - input_device: None + input_device: None, }, ) .unwrap(); @@ -58,4 +58,4 @@ pub unsafe extern "C" fn vuinput_open( fuse_lowlevel::fuse_reply_err(_req, ENOENT); } } -} \ No newline at end of file +} diff --git a/vuinputd/src/cuse_device/vuinput_release.rs b/vuinputd/src/cuse_device/vuinput_release.rs index 9454e5c..5994e2f 100644 --- a/vuinputd/src/cuse_device/vuinput_release.rs +++ b/vuinputd/src/cuse_device/vuinput_release.rs @@ -2,20 +2,21 @@ // // Author: Johannes Leupolz -use ::cuse_lowlevel::*; -use log::{debug}; -use std::sync::{Arc}; -use crate::{cuse_device::*, jobs}; use crate::job_engine::JOB_DISPATCHER; use crate::jobs::remove_from_container_job::RemoveFromContainerJob; use crate::process_tools::SELF_NAMESPACES; +use crate::{cuse_device::*, jobs}; +use ::cuse_lowlevel::*; +use log::debug; +use std::sync::Arc; pub unsafe extern "C" fn vuinput_release( _req: fuse_lowlevel::fuse_req_t, _fi: *mut fuse_lowlevel::fuse_file_info, ) { let fh = &(*_fi).fh; - let vuinput_state_mutex = remove_vuinput_state(&VuFileHandle::from_fuse_file_info(_fi.as_ref().unwrap())).unwrap(); + let vuinput_state_mutex = + remove_vuinput_state(&VuFileHandle::from_fuse_file_info(_fi.as_ref().unwrap())).unwrap(); let mut vuinput_state = vuinput_state_mutex.lock().unwrap(); let input_device = vuinput_state.input_device.take(); @@ -24,11 +25,27 @@ pub unsafe extern "C" fn vuinput_release( // Only do this in case it has not already been done by the ioctl UI_DEV_DESTROY // this here is relevant if the process was killed and didn't have the chance to send the // ioctl UI_DEV_DESTROY. - if input_device.is_some() && ! SELF_NAMESPACES.get().unwrap().equal_mnt_and_net(&vuinput_state.requesting_process.namespaces) { + if input_device.is_some() + && !SELF_NAMESPACES + .get() + .unwrap() + .equal_mnt_and_net(&vuinput_state.requesting_process.namespaces) + { let input_device = input_device.unwrap(); - let remove_job=RemoveFromContainerJob::new(vuinput_state.requesting_process.clone(),input_device.devnode.clone(),input_device.syspath.clone(),input_device.major,input_device.minor); + let remove_job = RemoveFromContainerJob::new( + vuinput_state.requesting_process.clone(), + input_device.devnode.clone(), + input_device.syspath.clone(), + input_device.major, + input_device.minor, + ); let awaiter = remove_job.get_awaiter_for_state(); - JOB_DISPATCHER.get().unwrap().lock().unwrap().dispatch(Box::new(remove_job)); + JOB_DISPATCHER + .get() + .unwrap() + .lock() + .unwrap() + .dispatch(Box::new(remove_job)); awaiter(&jobs::remove_from_container_job::State::Finished); } @@ -40,7 +57,7 @@ pub unsafe extern "C" fn vuinput_release( Arc::strong_count(&vuinput_state_mutex) ); drop(vuinput_state_mutex); // this also closes the file when no other references are open - // TODO: maybe also ensure that nothing is left in the containers + // TODO: maybe also ensure that nothing is left in the containers // Note: For CUSE, the kernel always issues RELEASE via fuse_sync_release(), // which forces a *synchronous* request (fuse_simple_request()). @@ -53,4 +70,3 @@ pub unsafe extern "C" fn vuinput_release( // `fuse_reply_err(req, 0)` is enough to wake the kernel and is safe here. fuse_lowlevel::fuse_reply_err(_req, 0); } - diff --git a/vuinputd/src/cuse_device/vuinput_write.rs b/vuinputd/src/cuse_device/vuinput_write.rs index 6665b24..671eb8b 100644 --- a/vuinputd/src/cuse_device/vuinput_write.rs +++ b/vuinputd/src/cuse_device/vuinput_write.rs @@ -2,17 +2,16 @@ // // Author: Johannes Leupolz +use crate::cuse_device::*; +use ::cuse_lowlevel::*; +use libc::{__s32, __u16, input_event}; use libc::{off_t, size_t, EIO}; use libc::{uinput_abs_setup, uinput_setup}; -use ::cuse_lowlevel::*; use log::{debug, trace}; use std::io::Write; use std::os::fd::AsRawFd; -use std::os::raw::{c_char}; +use std::os::raw::c_char; use uinput_ioctls::*; -use libc::{__s32, __u16, input_event}; -use crate::cuse_device::*; - // TODO: compat-mode+ ensure sizeof(struct input_event) pub unsafe extern "C" fn vuinput_write( @@ -27,14 +26,18 @@ pub unsafe extern "C" fn vuinput_write( "vuinput_write: offset needs to be 0 but is {}", _off ); - + let fh = &(*_fi).fh; let slice = std::slice::from_raw_parts(_buf as *const u8, _size); - let vuinput_state_mutex = get_vuinput_state(&VuFileHandle::from_fuse_file_info(_fi.as_ref().unwrap())).unwrap(); + let vuinput_state_mutex = + get_vuinput_state(&VuFileHandle::from_fuse_file_info(_fi.as_ref().unwrap())).unwrap(); let mut vuinput_state = vuinput_state_mutex.lock().unwrap(); - + if vuinput_state.input_device.is_none() { - debug!("{}: legacy device setup recognized! Ignore the data and use hardcoded values",fh); + debug!( + "{}: legacy device setup recognized! Ignore the data and use hardcoded values", + fh + ); assert!(_size == std::mem::size_of::()); let legacy_uinput_user_dev = _buf as *const libc::uinput_user_dev; @@ -45,8 +48,8 @@ pub unsafe extern "C" fn vuinput_write( usetup.id.vendor = 0x1209; usetup.id.product = 0x5020; usetup.id.version = (*legacy_uinput_user_dev).id.version; - usetup.ff_effects_max=(*legacy_uinput_user_dev).ff_effects_max; - usetup.name=(*legacy_uinput_user_dev).name; + usetup.ff_effects_max = (*legacy_uinput_user_dev).ff_effects_max; + usetup.name = (*legacy_uinput_user_dev).name; // Call IOCTLs to setup and create the device // Assuming your wrappers accept (fd, ptr_to_usetup) etc. @@ -56,10 +59,12 @@ pub unsafe extern "C" fn vuinput_write( ui_dev_setup(fd, usetup_ptr).unwrap(); // setup abs - for code in 0..libc::ABS_CNT{ - if (*legacy_uinput_user_dev).absmax[code] != 0 || (*legacy_uinput_user_dev).absmin[code] != 0 { + for code in 0..libc::ABS_CNT { + if (*legacy_uinput_user_dev).absmax[code] != 0 + || (*legacy_uinput_user_dev).absmin[code] != 0 + { let mut abs_setup: uinput_abs_setup = unsafe { std::mem::zeroed() }; - abs_setup.code=code.try_into().unwrap(); + abs_setup.code = code.try_into().unwrap(); abs_setup.absinfo.maximum = (*legacy_uinput_user_dev).absmax[code]; abs_setup.absinfo.minimum = (*legacy_uinput_user_dev).absmin[code]; abs_setup.absinfo.fuzz = (*legacy_uinput_user_dev).absfuzz[code]; @@ -77,50 +82,50 @@ pub unsafe extern "C" fn vuinput_write( let mut bytes = 0; let mut result = Result::Ok(0); - let compat_size= std::mem::size_of::(); - let normal_size= std::mem::size_of::(); + let compat_size = std::mem::size_of::(); + let normal_size = std::mem::size_of::(); let is_compat = vuinput_state.requesting_process.is_compat; // TODO: ARM: && !compat_uses_64bit_time() - + if !is_compat { while bytes + normal_size <= _size && result.is_ok() { result = vuinput_state.file.write(&slice[bytes..bytes + normal_size]); - bytes += normal_size; + bytes += normal_size; } } else { while bytes + compat_size <= _size && result.is_ok() { - let position= _buf.byte_add(bytes); + let position = _buf.byte_add(bytes); let compat = position as *const input_event_compat; let normal = map_to_64_bit(&*compat); - let normal_ptr=(&normal as *const libc::input_event) as *const u8; - let slice = std::slice::from_raw_parts(normal_ptr,normal_size); + let normal_ptr = (&normal as *const libc::input_event) as *const u8; + let slice = std::slice::from_raw_parts(normal_ptr, normal_size); result = vuinput_state.file.write(&slice); - bytes += compat_size; + bytes += compat_size; } }; - + match result { Ok(_) => { - trace!("wrote {} of {} bytes (compat {})", bytes,_size,is_compat); + trace!("wrote {} of {} bytes (compat {})", bytes, _size, is_compat); fuse_lowlevel::fuse_reply_write(_req, bytes); } Err(e) => { let mut last_error = DEDUP_LAST_ERROR.get().unwrap().lock().unwrap(); - + match *last_error { - Some((last_fh,VuError::WriteError)) if *fh == last_fh => {}, - _ => {debug!("fh {}: error writing to uinput: {e:?}",fh);} + Some((last_fh, VuError::WriteError)) if *fh == last_fh => {} + _ => { + debug!("fh {}: error writing to uinput: {e:?}", fh); + } } - - *last_error = Some((*fh,VuError::WriteError)); - + + *last_error = Some((*fh, VuError::WriteError)); + fuse_lowlevel::fuse_reply_err(_req, EIO); } } } - - #[repr(C)] pub struct input_event_compat { pub input_event_sec: u32, @@ -138,17 +143,17 @@ pub fn compat_uses_64bit_time() -> bool { match arch { "x86_64" => false, "ppc64" => false, // some setups still 32-bit time_t - _ => true, // arm64, riscv64, s390x all use 64-bit + _ => true, // arm64, riscv64, s390x all use 64-bit } } -pub fn map_to_64_bit(compat: &input_event_compat) -> input_event{ +pub fn map_to_64_bit(compat: &input_event_compat) -> input_event { let mut mapped: input_event = unsafe { std::mem::zeroed() }; - mapped.time.tv_sec=compat.input_event_sec.into(); - mapped.time.tv_usec=compat.input_event_usec.into(); - mapped.type_=compat.type_; - mapped.code=compat.code; - mapped.value=compat.value; + mapped.time.tv_sec = compat.input_event_sec.into(); + mapped.time.tv_usec = compat.input_event_usec.into(); + mapped.type_ = compat.type_; + mapped.code = compat.code; + mapped.value = compat.value; mapped -} \ No newline at end of file +} diff --git a/vuinputd/src/job_engine/closure_job.rs b/vuinputd/src/job_engine/closure_job.rs index 7ac1b8f..5e0fb65 100644 --- a/vuinputd/src/job_engine/closure_job.rs +++ b/vuinputd/src/job_engine/closure_job.rs @@ -7,7 +7,6 @@ use std::pin::Pin; use crate::job_engine::job::{Dispatcher, Job, JobTarget}; - pub struct ClosureJob { desc: String, execute_after_cancellation: bool, @@ -79,11 +78,10 @@ pub fn example() { // println!("Running container job for {:?}", target); // })); - // - // JOB_DISPATCHER.get().unwrap().lock().unwrap().dispatch(Box::new(ClosureJob::new("Monitor udev events", JobTarget::BackgroundLoop,false, + // + // JOB_DISPATCHER.get().unwrap().lock().unwrap().dispatch(Box::new(ClosureJob::new("Monitor udev events", JobTarget::BackgroundLoop,false, // Box::new(move |_target| Box::pin(monitor_udev::udev_monitor_loop(cancel_token.clone())))))); - // Allow loops to run briefly before dropping all senders -> graceful shutdown dispatcher.close(); dispatcher.wait_until_finished(); diff --git a/vuinputd/src/job_engine/mod.rs b/vuinputd/src/job_engine/mod.rs index 9ea9ce1..77f6dc1 100644 --- a/vuinputd/src/job_engine/mod.rs +++ b/vuinputd/src/job_engine/mod.rs @@ -38,7 +38,7 @@ use crate::job_engine::job::Dispatcher; pub mod closure_job; pub mod job; -pub static JOB_DISPATCHER: OnceLock>= OnceLock::new(); +pub static JOB_DISPATCHER: OnceLock> = OnceLock::new(); #[cfg(test)] -mod tests; \ No newline at end of file +mod tests; diff --git a/vuinputd/src/job_engine/tests.rs b/vuinputd/src/job_engine/tests.rs index 77276db..bcf7ecc 100644 --- a/vuinputd/src/job_engine/tests.rs +++ b/vuinputd/src/job_engine/tests.rs @@ -1,5 +1,3 @@ - - use crate::job_engine::closure_job::ClosureJob; use crate::job_engine::job::{Dispatcher, JobTarget}; @@ -32,11 +30,10 @@ fn test_job_ordering() { let c1 = c1.clone(); Box::pin(async move { *c1.lock().unwrap() = 5; - }) + }) }), ))); - // job 2: increment to 6 let c2 = c.clone(); dispatcher.dispatch(Box::new(ClosureJob::new( @@ -47,7 +44,7 @@ fn test_job_ordering() { let c2 = c2.clone(); Box::pin(async move { *c2.lock().unwrap() += 1; - }) + }) }), ))); @@ -365,4 +362,4 @@ fn test_cleanup_when_target_disappears() { // Should run normally assert!(buf.contains(&"alive-job".into())); } - */ \ No newline at end of file + */ diff --git a/vuinputd/src/jobs/inject_in_container_job.rs b/vuinputd/src/jobs/inject_in_container_job.rs index 4c0dd93..e78cad1 100644 --- a/vuinputd/src/jobs/inject_in_container_job.rs +++ b/vuinputd/src/jobs/inject_in_container_job.rs @@ -2,22 +2,36 @@ // // Author: Johannes Leupolz -use std::{collections::HashMap, future::Future, pin::Pin, sync::{Arc, Condvar, Mutex}, time::Duration}; +use std::{ + collections::HashMap, + future::Future, + pin::Pin, + sync::{Arc, Condvar, Mutex}, + time::Duration, +}; use async_io::Timer; use log::debug; -use crate::{job_engine::job::{Job, JobTarget}, jobs::{mknod_input_device::ensure_input_device, monitor_udev_job::EVENT_STORE, netlink_message::send_udev_monitor_message_with_properties, runtime_data::{ensure_udev_structure, read_udev_data, write_udev_data}}, process_tools::{Pid, RequestingProcess, await_process, run_in_net_and_mnt_namespace}}; +use crate::{ + job_engine::job::{Job, JobTarget}, + jobs::{ + mknod_input_device::ensure_input_device, + monitor_udev_job::EVENT_STORE, + netlink_message::send_udev_monitor_message_with_properties, + runtime_data::{ensure_udev_structure, read_udev_data, write_udev_data}, + }, + process_tools::{await_process, run_in_net_and_mnt_namespace, Pid, RequestingProcess}, +}; - -#[derive(Clone,Debug,Copy,PartialOrd,PartialEq)] +#[derive(Clone, Debug, Copy, PartialOrd, PartialEq)] pub enum State { Initialized, Started, Finished, } -#[derive(Clone,Debug)] +#[derive(Clone, Debug)] pub struct InjectInContainerJob { requesting_process: RequestingProcess, target: JobTarget, @@ -25,17 +39,23 @@ pub struct InjectInContainerJob { sys_path: String, major: u64, minor: u64, - sync_state: Arc<(Mutex,Condvar)>, + sync_state: Arc<(Mutex, Condvar)>, } impl InjectInContainerJob { - pub fn new(requesting_process: RequestingProcess,dev_path: String, sys_path: String, major: u64, minor: u64) -> Self { + pub fn new( + requesting_process: RequestingProcess, + dev_path: String, + sys_path: String, + major: u64, + minor: u64, + ) -> Self { Self { requesting_process: requesting_process.clone(), target: JobTarget::Container(requesting_process), dev_path: dev_path, sys_path: sys_path, - major: major , + major: major, minor: minor, sync_state: Arc::new((Mutex::new(State::Initialized), Condvar::new())), } @@ -52,10 +72,10 @@ impl InjectInContainerJob { pub fn get_awaiter_for_state(&self) -> impl FnOnce(&State) -> () { // pattern is described on https://doc.rust-lang.org/stable/std/sync/struct.Condvar.html let sync_state = self.sync_state.clone(); - let awaiter = move | state: &State| { + let awaiter = move |state: &State| { let (lock, cvar) = &*sync_state; let mut current_state = lock.lock().unwrap(); - while *current_state < *state { + while *current_state < *state { current_state = cvar.wait(current_state).unwrap(); } }; @@ -87,30 +107,33 @@ impl InjectInContainerJob { // Should be: Wait for the device to be created, the runtime data to be written and the // netlink message to be sent self.set_state(&State::Started); - let mut netlink_data: Option> = None; + let mut netlink_data: Option> = None; let mut runtime_data: Option = None; let mut number_of_attempt = 1; - while number_of_attempt<=50 && !(netlink_data.is_some() && runtime_data.is_some()) { - + while number_of_attempt <= 50 && !(netlink_data.is_some() && runtime_data.is_some()) { if netlink_data.is_none() { - - if let Some(netlink_event)=EVENT_STORE.get().unwrap().lock().unwrap().take(&self.sys_path) { + if let Some(netlink_event) = EVENT_STORE + .get() + .unwrap() + .lock() + .unwrap() + .take(&self.sys_path) + { if netlink_event.tombstone || netlink_event.remove_data.is_some() { debug!("do nothing, because the device has already been removed in the meantime"); return; } - netlink_data=netlink_event.add_data; + netlink_data = netlink_event.add_data; }; } if runtime_data.is_none() { - runtime_data = read_udev_data(self.major,self.minor).ok(); - + runtime_data = read_udev_data(self.major, self.minor).ok(); } - number_of_attempt+=1; + 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"); @@ -124,27 +147,29 @@ impl InjectInContainerJob { // define for capturing let major = self.major; - let minor=self.minor; + let minor = self.minor; let runtime_data = runtime_data.unwrap(); let netlink_data = netlink_data.unwrap(); let dev_path = self.dev_path.clone(); - - let child_pid = run_in_net_and_mnt_namespace(&self.requesting_process, Box::new(move || { - - if let Err(e) = ensure_input_device(dev_path.clone(), self.major, self.minor) { - debug!("Error creating input device {}: {e}",dev_path.clone()); - }; - ensure_udev_structure().unwrap(); - if let Err(e) = write_udev_data(runtime_data.as_str(), major, minor) { - debug!("Error writing udev data for device {}: {e}",dev_path.clone()); - }; - send_udev_monitor_message_with_properties(netlink_data.clone()); - - })) + let child_pid = run_in_net_and_mnt_namespace( + &self.requesting_process, + Box::new(move || { + if let Err(e) = ensure_input_device(dev_path.clone(), self.major, self.minor) { + debug!("Error creating input device {}: {e}", dev_path.clone()); + }; + ensure_udev_structure().unwrap(); + if let Err(e) = write_udev_data(runtime_data.as_str(), major, minor) { + debug!( + "Error writing udev data for device {}: {e}", + dev_path.clone() + ); + }; + send_udev_monitor_message_with_properties(netlink_data.clone()); + }), + ) .expect("subprocess should work"); let _exit_info = await_process(Pid::Pid(child_pid.as_raw())).await.unwrap(); self.set_state(&State::Finished); - } } diff --git a/vuinputd/src/jobs/mknod_input_device.rs b/vuinputd/src/jobs/mknod_input_device.rs index f2ae97b..02fc4bd 100644 --- a/vuinputd/src/jobs/mknod_input_device.rs +++ b/vuinputd/src/jobs/mknod_input_device.rs @@ -5,18 +5,17 @@ use nix::sys::stat::{makedev, mknod, stat, Mode, SFlag}; use std::error::Error; use std::fs; -use std::os::unix::fs::{PermissionsExt}; +use std::os::unix::fs::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; @@ -84,12 +83,12 @@ pub fn remove_input_device(dev_path: String, major: u64, minor: u64) -> Result<( 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()) + 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()) + 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/jobs/mod.rs b/vuinputd/src/jobs/mod.rs index cf229cf..1580916 100644 --- a/vuinputd/src/jobs/mod.rs +++ b/vuinputd/src/jobs/mod.rs @@ -3,8 +3,8 @@ // Author: Johannes Leupolz pub mod inject_in_container_job; -pub mod remove_from_container_job; -pub mod monitor_udev_job; pub mod mknod_input_device; +pub mod monitor_udev_job; +pub mod netlink_message; +pub mod remove_from_container_job; pub mod runtime_data; -pub mod netlink_message; \ No newline at end of file diff --git a/vuinputd/src/jobs/monitor_udev_job.rs b/vuinputd/src/jobs/monitor_udev_job.rs index 3697e07..fe6d4e9 100644 --- a/vuinputd/src/jobs/monitor_udev_job.rs +++ b/vuinputd/src/jobs/monitor_udev_job.rs @@ -129,12 +129,10 @@ impl EventStore { pub static EVENT_STORE: OnceLock>> = OnceLock::new(); -pub struct MonitorBackgroundLoop { -} +pub struct MonitorBackgroundLoop {} impl MonitorBackgroundLoop { pub fn new() -> Self { - Self { - } + Self {} } } @@ -208,16 +206,14 @@ pub async fn udev_monitor_loop(cancel_token: Arc) { let key = match key.as_str() { "ID_VUINPUT_KEYBOARD" => "ID_INPUT_KEYBOARD".to_string(), "ID_VUINPUT_MOUSE" => "ID_INPUT_MOUSE".to_string(), - _ => key + _ => key, }; let value: String = property.value().to_str().unwrap().to_string(); - if key!="ID_SEAT" { + if key != "ID_SEAT" { properties.insert(key, value); } } - - let value_of_devpath = properties.get("DEVPATH").unwrap(); diff --git a/vuinputd/src/jobs/netlink_message.rs b/vuinputd/src/jobs/netlink_message.rs index 8deb8de..2a1dc80 100644 --- a/vuinputd/src/jobs/netlink_message.rs +++ b/vuinputd/src/jobs/netlink_message.rs @@ -6,11 +6,11 @@ use std::collections::HashMap; use std::mem; use std::os::fd::{AsRawFd, OwnedFd}; -use std::io::{IoSlice}; +use std::io::IoSlice; use log::debug; use nix::sys::socket::{ - bind, sendmsg, socket, AddressFamily, MsgFlags, NetlinkAddr, SockFlag, SockProtocol, SockType + bind, sendmsg, socket, AddressFamily, MsgFlags, NetlinkAddr, SockFlag, SockProtocol, SockType, }; /// Netlink constants @@ -79,7 +79,7 @@ pub fn string_hash32(s: &str) -> u32 { match s { "input" => 3248653424, "" => 0, - _ => panic!("uncovered use case") + _ => panic!("uncovered use case"), } } @@ -95,12 +95,10 @@ fn open_netlink(groups: u32) -> Result { .map_err(|e| format!("Could not create netlink socket: {}", e))?; // pid 0 => the kernel takes care of assigning it. - let sockaddr=NetlinkAddr::new(0, groups); - let raw_fd= fd.as_raw_fd(); + let sockaddr = NetlinkAddr::new(0, groups); + let raw_fd = fd.as_raw_fd(); - bind(raw_fd, &sockaddr).map_err(|e| { - format!("Could not bind netlink socket: {}", e) - })?; + bind(raw_fd, &sockaddr).map_err(|e| format!("Could not bind netlink socket: {}", e))?; Ok(fd) } @@ -128,16 +126,19 @@ pub fn send_udev_monitor_message( let fd = open_netlink(groups)?; // prepare iovecs - let iov = [ - IoSlice::new(&header_bytes), - IoSlice::new(payload), - ]; + let iov = [IoSlice::new(&header_bytes), IoSlice::new(payload)]; // destination sockaddr (NULL nl_pid => kernel / multicast) let sockaddr = NetlinkAddr::new(0, groups); - let _rc = sendmsg(fd.as_raw_fd(), &iov, &[], MsgFlags::empty(), Some(&sockaddr)) - .map_err(|e| format!("Could not send message: {}", e)); + let _rc = sendmsg( + fd.as_raw_fd(), + &iov, + &[], + MsgFlags::empty(), + Some(&sockaddr), + ) + .map_err(|e| format!("Could not send message: {}", e)); debug!("udev message sent"); // ensure cleanup @@ -146,29 +147,27 @@ pub fn send_udev_monitor_message( Ok(()) } -pub fn send_udev_monitor_message_with_properties(properties:HashMap) { +pub fn send_udev_monitor_message_with_properties(properties: HashMap) { let device_name = match properties.get("DEVNAME") { Some(name) => name, - None => "unknown device" + None => "unknown device", }; - debug!("Sending udev message over netlink for {}",device_name); - let mut payload:Vec = Vec::new(); - for (key,value) in properties.iter() { + debug!("Sending udev message over netlink for {}", device_name); + let mut payload: Vec = Vec::new(); + for (key, value) in properties.iter() { payload.extend(key.as_bytes()); payload.extend("=".as_bytes()); payload.extend(value.as_bytes()); payload.push(0); } - - send_udev_monitor_message(&payload,Some("input"),None,UDEV_EVENT_MODE).unwrap(); + + send_udev_monitor_message(&payload, Some("input"), None, UDEV_EVENT_MODE).unwrap(); } // println!("{:02X?}", payload); // 746573743D76616C75650043555252454E545F544147533D3A736561745F7675696E7075743A00544147533D3A736561745F7675696E7075743A00444556504154483D2F646576696365732F7669727475616C2F696E7075742F696E7075743133382F6576656E74390049445F5655494E5055545F4D4F5553453D31004D494E4F523D37330049445F494E5055543D31002E494E5055545F434C4153533D6D6F757365005345514E554D3D3134393231002E484156455F485744425F50524F504552544945533D31004D414A4F523D313300414354494F4E3D6164640049445F53455249414C3D6E6F73657269616C004445564E414D453D2F6465762F696E7075742F6576656E743900555345435F494E495449414C495A45443D31373337373733353034373139320049445F5655494E5055543D310049445F534541543D736561745F7675696E7075740053554253595354454D3D696E70757400 // dGVzdD12YWx1ZQBDVVJSRU5UX1RBR1M9OnNlYXRfdnVpbnB1dDoAVEFHUz06c2VhdF92dWlucHV0OgBERVZQQVRIPS9kZXZpY2VzL3ZpcnR1YWwvaW5wdXQvaW5wdXQxMzgvZXZlbnQ5AElEX1ZVSU5QVVRfTU9VU0U9MQBNSU5PUj03MwBJRF9JTlBVVD0xAC5JTlBVVF9DTEFTUz1tb3VzZQBTRVFOVU09MTQ5MjEALkhBVkVfSFdEQl9QUk9QRVJUSUVTPTEATUFKT1I9MTMAQUNUSU9OPWFkZABJRF9TRVJJQUw9bm9zZXJpYWwAREVWTkFNRT0vZGV2L2lucHV0L2V2ZW50OQBVU0VDX0lOSVRJQUxJWkVEPTE3Mzc3NzM1MDQ3MTkyAElEX1ZVSU5QVVQ9MQBJRF9TRUFUPXNlYXRfdnVpbnB1dABTVUJTWVNURU09aW5wdXQA - - /* UDEV [16427452.069342] add /devices/virtual/input/input97 (input) ACTION=add @@ -211,4 +210,4 @@ TAGS=:seat_vuinput:power-switch: CURRENT_TAGS=:seat_vuinput:power-switch: -*/ \ No newline at end of file +*/ diff --git a/vuinputd/src/jobs/remove_from_container_job.rs b/vuinputd/src/jobs/remove_from_container_job.rs index 12b066d..e4b0077 100644 --- a/vuinputd/src/jobs/remove_from_container_job.rs +++ b/vuinputd/src/jobs/remove_from_container_job.rs @@ -2,23 +2,31 @@ // // Author: Johannes Leupolz -use std::{future::Future, pin::Pin, sync::{Arc, Condvar, Mutex}}; +use std::{ + future::Future, + pin::Pin, + sync::{Arc, Condvar, Mutex}, +}; use log::debug; -use crate::{job_engine::job::{Job, JobTarget}, jobs::{mknod_input_device::remove_input_device, monitor_udev_job::EVENT_STORE, netlink_message::send_udev_monitor_message_with_properties, runtime_data::{delete_udev_data}}, process_tools::{Pid, RequestingProcess, await_process, run_in_net_and_mnt_namespace}}; +use crate::{ + job_engine::job::{Job, JobTarget}, + jobs::{ + mknod_input_device::remove_input_device, monitor_udev_job::EVENT_STORE, + netlink_message::send_udev_monitor_message_with_properties, runtime_data::delete_udev_data, + }, + process_tools::{await_process, run_in_net_and_mnt_namespace, Pid, RequestingProcess}, +}; - - -#[derive(Clone,Debug,Copy,PartialOrd,PartialEq)] +#[derive(Clone, Debug, Copy, PartialOrd, PartialEq)] pub enum State { Initialized, Started, Finished, } - -#[derive(Clone,Debug)] +#[derive(Clone, Debug)] pub struct RemoveFromContainerJob { requesting_process: RequestingProcess, target: JobTarget, @@ -26,17 +34,23 @@ pub struct RemoveFromContainerJob { sys_path: String, major: u64, minor: u64, - sync_state: Arc<(Mutex,Condvar)>, + sync_state: Arc<(Mutex, Condvar)>, } impl RemoveFromContainerJob { - pub fn new(requesting_process: RequestingProcess,dev_path: String, sys_path: String, major: u64, minor: u64) -> Self { + pub fn new( + requesting_process: RequestingProcess, + dev_path: String, + sys_path: String, + major: u64, + minor: u64, + ) -> Self { Self { requesting_process: requesting_process.clone(), target: JobTarget::Container(requesting_process), dev_path: dev_path, sys_path: sys_path, - major: major , + major: major, minor: minor, sync_state: Arc::new((Mutex::new(State::Initialized), Condvar::new())), } @@ -52,7 +66,7 @@ impl RemoveFromContainerJob { pub fn get_awaiter_for_state(&self) -> impl FnOnce(&State) -> () { // pattern is described on https://doc.rust-lang.org/stable/std/sync/struct.Condvar.html let sync_state = self.sync_state.clone(); - let awaiter = move | state: &State| { + let awaiter = move |state: &State| { let (lock, cvar) = &*sync_state; let mut current_state = lock.lock().unwrap(); while *current_state < *state { @@ -61,7 +75,6 @@ impl RemoveFromContainerJob { }; awaiter } - } impl Job for RemoveFromContainerJob { @@ -86,7 +99,13 @@ impl RemoveFromContainerJob { async fn remove_from_container(self) { self.set_state(&State::Started); - let netlink_event = match EVENT_STORE.get().unwrap().lock().unwrap().take(&self.sys_path) { + let netlink_event = match EVENT_STORE + .get() + .unwrap() + .lock() + .unwrap() + .take(&self.sys_path) + { Some(netlink_event) => netlink_event, None => { debug!("do nothing, because the device has never been announced via netlink"); @@ -100,31 +119,32 @@ impl RemoveFromContainerJob { self.set_state(&State::Finished); return; } - let netlink_data=netlink_event.add_data; + let netlink_data = netlink_event.add_data; // define for capturing let mut netlink_data = netlink_data.unwrap().clone(); let major = self.major; - let minor=self.minor; + let minor = self.minor; let dev_path = self.dev_path.clone(); - let _ = netlink_data.insert("ACTION".to_string(),"remove".to_string()); - let child_pid = run_in_net_and_mnt_namespace(&self.requesting_process, 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(dev_path.clone(), self.major, self.minor) { - debug!("Error removing input device {}: {e}",dev_path.clone()); - }; + let _ = netlink_data.insert("ACTION".to_string(), "remove".to_string()); + let child_pid = run_in_net_and_mnt_namespace( + &self.requesting_process, + 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(dev_path.clone(), self.major, self.minor) { + debug!("Error removing input device {}: {e}", dev_path.clone()); + }; + }), + ) .expect("subprocess should work"); let _exit_info = await_process(Pid::Pid(child_pid.as_raw())).await; self.set_state(&State::Finished); - } } diff --git a/vuinputd/src/jobs/runtime_data.rs b/vuinputd/src/jobs/runtime_data.rs index f74b05a..8c5903e 100644 --- a/vuinputd/src/jobs/runtime_data.rs +++ b/vuinputd/src/jobs/runtime_data.rs @@ -9,7 +9,6 @@ use std::path::Path; /// Ensure required udev directories and files exist pub fn ensure_udev_structure() -> io::Result<()> { // TODO: this _must_ exist, before a service using libinput is run. The time of device creation might be too late - let data_dir = Path::new("/run/udev/data"); let control_file = Path::new("/run/udev/control"); @@ -53,7 +52,6 @@ pub fn write_udev_data(content: &str, major: u64, minor: u64) -> io::Result<()> cleaned.push_str(&line); cleaned.push('\n'); } - let path = format!("/run/udev/data/c{}:{}", major, minor); let mut file = File::create(&path)?; @@ -65,7 +63,6 @@ pub fn write_udev_data(content: &str, major: u64, minor: u64) -> io::Result<()> /// 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(()) diff --git a/vuinputd/src/main.rs b/vuinputd/src/main.rs index 42045a3..9c793fd 100644 --- a/vuinputd/src/main.rs +++ b/vuinputd/src/main.rs @@ -20,26 +20,25 @@ use ::cuse_lowlevel::*; use log::info; use std::ffi::CString; -use std::os::raw::{c_char}; -use std::sync::atomic::{AtomicU64}; -use std::sync::{Mutex}; +use std::os::raw::c_char; +use std::sync::atomic::AtomicU64; +use std::sync::Mutex; pub mod cuse_device; use crate::cuse_device::state::{initialize_dedup_last_error, initialize_vuinput_state}; +use crate::cuse_device::vuinput_make_cuse_ops; use crate::cuse_device::vuinput_open::VUINPUT_COUNTER; -use crate::cuse_device::{vuinput_make_cuse_ops}; use crate::jobs::monitor_udev_job::MonitorBackgroundLoop; pub mod process_tools; pub mod job_engine; -use crate::job_engine::{JOB_DISPATCHER, job::*}; +use crate::job_engine::{job::*, JOB_DISPATCHER}; use crate::process_tools::*; pub mod jobs; - fn main() -> std::io::Result<()> { env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("debug")).init(); @@ -48,11 +47,22 @@ fn main() -> std::io::Result<()> { let args: Vec = std::env::args().collect(); initialize_vuinput_state(); - VUINPUT_COUNTER.set(AtomicU64::new(3)).expect("failed to initialize the counter that provides the values of the CUSE file handles"); // 3, because 1 and 2 are usually STDOUT and STDERR - JOB_DISPATCHER.set(Mutex::new(Dispatcher::new())).expect("failed to initialize the job dispatcher"); - SELF_NAMESPACES.set(get_namespace(Pid::SelfPid)).expect("failed to retrieve the namespaces of the vuinputd process"); + VUINPUT_COUNTER.set(AtomicU64::new(3)).expect( + "failed to initialize the counter that provides the values of the CUSE file handles", + ); // 3, because 1 and 2 are usually STDOUT and STDERR + JOB_DISPATCHER + .set(Mutex::new(Dispatcher::new())) + .expect("failed to initialize the job dispatcher"); + SELF_NAMESPACES + .set(get_namespace(Pid::SelfPid)) + .expect("failed to retrieve the namespaces of the vuinputd process"); initialize_dedup_last_error(); - JOB_DISPATCHER.get().unwrap().lock().unwrap().dispatch(Box::new(MonitorBackgroundLoop::new())); + JOB_DISPATCHER + .get() + .unwrap() + .lock() + .unwrap() + .dispatch(Box::new(MonitorBackgroundLoop::new())); info!("Starting vuinputd"); @@ -62,7 +72,7 @@ fn main() -> std::io::Result<()> { let mut dev_info_argv: Vec<*const c_char> = vec![ vuinput_devicename.as_ptr(), // pointer to the C string - std::ptr::null(), // null terminator, often required by C APIs + std::ptr::null(), // null terminator, often required by C APIs ]; // setting dev_major and dev_minor to 0 leads to a dynamic assignment of the major and minor, very likely beginning with 234:0 @@ -104,7 +114,12 @@ fn main() -> std::io::Result<()> { } info!("Stopping vuinputd"); JOB_DISPATCHER.get().unwrap().lock().unwrap().close(); - JOB_DISPATCHER.get().unwrap().lock().unwrap().wait_until_finished(); + JOB_DISPATCHER + .get() + .unwrap() + .lock() + .unwrap() + .wait_until_finished(); Ok(()) } diff --git a/vuinputd/src/process_tools/mod.rs b/vuinputd/src/process_tools/mod.rs index 24952c0..6890344 100644 --- a/vuinputd/src/process_tools/mod.rs +++ b/vuinputd/src/process_tools/mod.rs @@ -9,14 +9,17 @@ use nix::{ unistd::{fork, ForkResult}, }; use std::{ - fs::{self, File}, io::Read, os::fd::{AsFd, FromRawFd, OwnedFd, RawFd}, path::{Path}, process, sync::OnceLock + fs::{self, File}, + io::Read, + os::fd::{AsFd, FromRawFd, OwnedFd, RawFd}, + path::Path, + process, + sync::OnceLock, }; use std::io; -pub static SELF_NAMESPACES: OnceLock= OnceLock::new(); - - +pub static SELF_NAMESPACES: OnceLock = OnceLock::new(); #[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)] pub enum Pid { @@ -28,7 +31,7 @@ impl Pid { pub fn path(&self) -> String { match self { Pid::SelfPid => "/proc/self".to_string(), - Pid::Pid(pid_no) => format!("/proc/{}",pid_no) + Pid::Pid(pid_no) => format!("/proc/{}", pid_no), } } } @@ -45,12 +48,10 @@ pub struct Namespaces { pub cgroup: Option, pub time: Option, pub time_for_children: Option, - } /// Returns true if the process with `pid` is a 32-bit (compat) process. None, if unsure. pub fn is_compat_process(pid: Pid) -> Option { - match pid { Pid::Pid(pid) => { const EI_CLASS: usize = 4; @@ -75,8 +76,7 @@ pub fn is_compat_process(pid: Pid) -> Option { Err(_) => None, } } - Pid::SelfPid => - unreachable!() + Pid::SelfPid => unreachable!(), } } @@ -112,12 +112,20 @@ impl std::fmt::Display for RequestingProcess { writeln!(f, " uts: {:?}", self.namespaces.uts)?; writeln!(f, " ipc: {:?}", self.namespaces.ipc)?; writeln!(f, " pid: {:?}", self.namespaces.pid)?; - writeln!(f, " pid_for_children: {:?}", self.namespaces.pid_for_children)?; + writeln!( + f, + " pid_for_children: {:?}", + self.namespaces.pid_for_children + )?; writeln!(f, " user: {:?}", self.namespaces.user)?; writeln!(f, " mnt: {:?}", self.namespaces.mnt)?; writeln!(f, " cgroup: {:?}", self.namespaces.cgroup)?; writeln!(f, " time: {:?}", self.namespaces.time)?; - writeln!(f, " time_for_children: {:?}", self.namespaces.time_for_children)?; + writeln!( + f, + " time_for_children: {:?}", + self.namespaces.time_for_children + )?; Ok(()) } } @@ -168,45 +176,42 @@ pub fn get_namespace(pid: Pid) -> Namespaces { } fn get_ppid(pid: Pid) -> Option { - let content = - match pid { - Pid::SelfPid => fs::read_to_string(format!("/proc/self/status")).ok()?, - Pid::Pid(pid) => fs::read_to_string(format!("/proc/{}/status", pid)).ok()? - }; - let ppid=content + let content = match pid { + Pid::SelfPid => fs::read_to_string(format!("/proc/self/status")).ok()?, + Pid::Pid(pid) => fs::read_to_string(format!("/proc/{}/status", pid)).ok()?, + }; + let ppid = content .lines() .find(|line| line.starts_with("PPid:")) .and_then(|line| line.split_whitespace().nth(1)) .and_then(|ppid| ppid.parse::().ok()); match ppid { None => None, - Some(ppid)=> Some(Pid::Pid(ppid)) + Some(ppid) => Some(Pid::Pid(ppid)), } } - - pub fn get_requesting_process(pid: Pid) -> RequestingProcess { - match pid { - Pid::Pid(_) => - { + Pid::Pid(_) => { let is_compat = match is_compat_process(pid) { Some(false) => { - debug!("identified process {} as 64 bit process",pid.path()); + debug!("identified process {} as 64 bit process", pid.path()); false - }, + } Some(true) => { - debug!("identified process {} as 32 bit process",pid.path()); + debug!("identified process {} as 32 bit process", pid.path()); true - }, + } None => { - debug!("could not identify bitness of process {}. Assume 64 bit process",pid.path()); + debug!( + "could not identify bitness of process {}. Assume 64 bit process", + pid.path() + ); false - }, + } }; - // go up the parent hierarchy until we find a parent with different namespaces let mut ppid = pid; let nsinodes = get_namespace(pid); @@ -214,19 +219,21 @@ pub fn get_requesting_process(pid: Pid) -> RequestingProcess { let candidate_ppid = get_ppid(ppid); match candidate_ppid { None => break, - Some(candidate_ppid) => - { + Some(candidate_ppid) => { let ppid_nsinodes = get_namespace(candidate_ppid); if nsinodes.equal_mnt_and_net(&ppid_nsinodes) { - ppid=candidate_ppid; + ppid = candidate_ppid; } else { break; } } - } } - debug!("identified process {} as root of process id {}",ppid.path(),pid.path()); + debug!( + "identified process {} as root of process id {}", + ppid.path(), + pid.path() + ); let nspath = format!("{}/ns", pid.path()); let nsroot = format!("{}/ns", ppid.path()); @@ -234,19 +241,21 @@ pub fn get_requesting_process(pid: Pid) -> RequestingProcess { nspath: nspath, nsroot: nsroot, namespaces: nsinodes, - is_compat: is_compat + is_compat: is_compat, } - }, - Pid::SelfPid => - { + } + Pid::SelfPid => { unreachable!(); - }, + } } } /// Runs a function inside the given network and mount namespaces. /// Returns the child PID so the caller can `waitpid` on it. -pub fn run_in_net_and_mnt_namespace(ns: &RequestingProcess, func: Box) -> nix::Result { +pub fn run_in_net_and_mnt_namespace( + ns: &RequestingProcess, + func: Box, +) -> nix::Result { //Note: The child process is created with a single thread—the one that called fork(). match unsafe { fork()? } { @@ -255,7 +264,7 @@ pub fn run_in_net_and_mnt_namespace(ns: &RequestingProcess, func: Box) Ok(child) } ForkResult::Child => { - debug!("Start new process {}",process::id()); + debug!("Start new process {}", process::id()); // enter namespace let path: &Path = Path::new(ns.nsroot.as_str()); debug!("Entering namespaces of process {}. We assume this is the root process of the container.",ns.nsroot.clone()); @@ -267,7 +276,7 @@ pub fn run_in_net_and_mnt_namespace(ns: &RequestingProcess, func: Box) let mnt = File::open(ns.nsroot.clone() + "/mnt").expect("mnt not found"); setns(net.as_fd(), CloneFlags::CLONE_NEWNET).expect("couldn't enter net"); setns(mnt.as_fd(), CloneFlags::CLONE_NEWNS).expect("couldn't enter mnt"); - + // execute your function func(); std::process::exit(0); @@ -276,15 +285,13 @@ pub fn run_in_net_and_mnt_namespace(ns: &RequestingProcess, func: Box) } pub async fn await_process(pid: Pid) -> io::Result { - match pid { - Pid::Pid(pid) => - { - unsafe { + Pid::Pid(pid) => { + unsafe { // Use pidfd_open() (libc) to get a real FD let pidfd = libc::syscall(libc::SYS_pidfd_open, pid, 0); if pidfd == -1 { - return Err(io::Error::last_os_error()) + return Err(io::Error::last_os_error()); } let owned_fd = OwnedFd::from_raw_fd(pidfd as RawFd); @@ -294,35 +301,28 @@ pub async fn await_process(pid: Pid) -> io::Result { // Retrieve the exit code using waitid() let mut si: libc::siginfo_t = std::mem::zeroed(); - let r = libc::waitid( - libc::P_PID, - pid as u32, - &mut si, - libc::WEXITED, - ); + let r = libc::waitid(libc::P_PID, pid as u32, &mut si, libc::WEXITED); if r != 0 { return Err(io::Error::last_os_error()); } Ok(si.si_status()) } - }, - Pid::SelfPid => - { + } + Pid::SelfPid => { unreachable!(); - }, + } } } - pub fn check_permissions() -> Result<(), std::io::Error> { let path = Path::new("/proc/self/status"); debug!("Capabilities of vuinputd process:"); - fs::read_to_string(path). - and_then(|status_file| { - status_file.lines() - .filter(|line| line.starts_with("Cap")) - .for_each(move |x| debug!("{}",x)); - Ok(()) - }) -} \ No newline at end of file + fs::read_to_string(path).and_then(|status_file| { + status_file + .lines() + .filter(|line| line.starts_with("Cap")) + .for_each(move |x| debug!("{}", x)); + Ok(()) + }) +}