cargo fmt

This commit is contained in:
Johannes Leupolz 2025-12-10 22:46:52 +00:00
parent b3d0a0225d
commit f0bff128a6
22 changed files with 429 additions and 347 deletions

View file

@ -4,7 +4,7 @@
// Author: Johannes Leupolz <dev@leupolz.eu>
//
// 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,
);
}
generate_fuse_bindings("cuse_lowlevel.h", &fuse3_lib, cuse_binding_filter);
}

View file

@ -4,7 +4,7 @@
// Author: Johannes Leupolz <dev@leupolz.eu>
//
// 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,
};
}
}

View file

@ -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<String> = 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);

View file

@ -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<String> = 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));

View file

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

View file

@ -3,7 +3,7 @@
// Author: Johannes Leupolz <dev@leupolz.eu>
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<VuInputDevice>
pub input_device: Option<VuInputDevice>,
}
#[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<Arc<Mutex<VuInputState>>, String> {
pub fn get_vuinput_state(fh: &VuFileHandle) -> Result<Arc<Mutex<VuInputState>>, 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<Arc<Mutex<VuInputState>>, String> {
pub fn remove_vuinput_state(fh: &VuFileHandle) -> Result<Arc<Mutex<VuInputState>>, 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<RwLock<HashMap<VuFileHandle, Arc<Mutex<VuInputState>>>>> = OnceLock::new();
pub static VUINPUT_STATE: OnceLock<RwLock<HashMap<VuFileHandle, Arc<Mutex<VuInputState>>>>> =
OnceLock::new();
// For log limiting. Idea: Move to log_limit crate
pub static DEDUP_LAST_ERROR: OnceLock<Mutex<Option<(u64,VuError)>>> = OnceLock::new();
pub static DEDUP_LAST_ERROR: OnceLock<Mutex<Option<(u64, VuError)>>> = OnceLock::new();

View file

@ -2,17 +2,17 @@
//
// Author: Johannes Leupolz <dev@leupolz.eu>
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<String> {
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))
}

View file

@ -2,18 +2,18 @@
//
// Author: Johannes Leupolz <dev@leupolz.eu>
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<AtomicU64> = 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);
}
}
}
}

View file

@ -2,20 +2,21 @@
//
// Author: Johannes Leupolz <dev@leupolz.eu>
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);
}

View file

@ -2,17 +2,16 @@
//
// Author: Johannes Leupolz <dev@leupolz.eu>
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::<libc::uinput_user_dev>());
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::<input_event_compat>();
let normal_size= std::mem::size_of::<libc::input_event>();
let compat_size = std::mem::size_of::<input_event_compat>();
let normal_size = std::mem::size_of::<libc::input_event>();
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
}
}

View file

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

View file

@ -38,7 +38,7 @@ use crate::job_engine::job::Dispatcher;
pub mod closure_job;
pub mod job;
pub static JOB_DISPATCHER: OnceLock<Mutex<Dispatcher>>= OnceLock::new();
pub static JOB_DISPATCHER: OnceLock<Mutex<Dispatcher>> = OnceLock::new();
#[cfg(test)]
mod tests;
mod tests;

View file

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

View file

@ -2,22 +2,36 @@
//
// Author: Johannes Leupolz <dev@leupolz.eu>
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<State>,Condvar)>,
sync_state: Arc<(Mutex<State>, 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<HashMap<String,String>> = None;
let mut netlink_data: Option<HashMap<String, String>> = None;
let mut runtime_data: Option<String> = 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);
}
}

View file

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

View file

@ -3,8 +3,8 @@
// Author: Johannes Leupolz <dev@leupolz.eu>
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;

View file

@ -129,12 +129,10 @@ impl EventStore {
pub static EVENT_STORE: OnceLock<Arc<Mutex<EventStore>>> = 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<AtomicBool>) {
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();

View file

@ -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<OwnedFd, String> {
.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<String, String>) {
pub fn send_udev_monitor_message_with_properties(properties: HashMap<String, String>) {
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<u8> = Vec::new();
for (key,value) in properties.iter() {
debug!("Sending udev message over netlink for {}", device_name);
let mut payload: Vec<u8> = 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:
*/
*/

View file

@ -2,23 +2,31 @@
//
// Author: Johannes Leupolz <dev@leupolz.eu>
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<State>,Condvar)>,
sync_state: Arc<(Mutex<State>, 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);
}
}

View file

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

View file

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

View file

@ -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<Namespaces>= OnceLock::new();
pub static SELF_NAMESPACES: OnceLock<Namespaces> = 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<u64>,
pub time: Option<u64>,
pub time_for_children: Option<u64>,
}
/// Returns true if the process with `pid` is a 32-bit (compat) process. None, if unsure.
pub fn is_compat_process(pid: Pid) -> Option<bool> {
match pid {
Pid::Pid(pid) => {
const EI_CLASS: usize = 4;
@ -75,8 +76,7 @@ pub fn is_compat_process(pid: Pid) -> Option<bool> {
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<Pid> {
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::<i32>().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<dyn Fn()>) -> nix::Result<nix::unistd::Pid> {
pub fn run_in_net_and_mnt_namespace(
ns: &RequestingProcess,
func: Box<dyn Fn()>,
) -> nix::Result<nix::unistd::Pid> {
//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<dyn Fn()>)
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<dyn Fn()>)
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<dyn Fn()>)
}
pub async fn await_process(pid: Pid) -> io::Result<i32> {
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<i32> {
// 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(())
})
}
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(())
})
}