Implementation of the remove-device-step for --placement on-host. Not tested, yet

This commit is contained in:
Johannes Leupolz 2026-01-20 21:26:18 +00:00
parent 350a80644a
commit fd55bffddb
13 changed files with 125 additions and 33 deletions

View file

@ -24,6 +24,8 @@ Error messages may change over time; error codes do not.
|------|------|--------|
| VUI-UDEV-001 | udev | udev control socket not reachable |
| VUI-UDEV-002 | udev | could not write into /run/vuinputd/... |
| VUI-UDEV-003 | udev | could not remove udev data from ... |
| VUI-DEV-001 | ddev | could not remove device node ... |
---

View file

@ -26,10 +26,12 @@ fn handle_action(action: Action) -> anyhow::Result<()> {
major,
minor,
} => {
runtime_data::ensure_udev_structure("/run",true)?;
runtime_data::ensure_udev_structure()?;
match runtime_data {
Some(data) => runtime_data::write_udev_data("/run",&data, major.into(), minor.into())?,
None => runtime_data::delete_udev_data("/run",major.into(), minor.into())?,
Some(data) => {
runtime_data::write_udev_data("/run", &data, major.into(), minor.into())?
}
None => runtime_data::delete_udev_data("/run", major.into(), minor.into())?,
}
Ok(())
}

View file

@ -15,6 +15,7 @@ pub struct VuInputDevice {
pub major: u64,
pub minor: u64,
pub syspath: String,
pub devname: String,
pub devnode: String,
}

View file

@ -172,6 +172,7 @@ pub unsafe extern "C" fn vuinput_ioctl(
major: major,
minor: minor,
syspath: sysname.clone(),
devname: devname.clone(),
devnode: devnode.clone(),
});
@ -231,7 +232,7 @@ pub unsafe extern "C" fn vuinput_ioctl(
let input_device = input_device.unwrap();
let remove_job = RemoveDeviceJob::new(
vuinput_state.requesting_process.clone(),
input_device.devnode.clone(),
input_device.devname.clone(),
input_device.syspath.clone(),
input_device.major,
input_device.minor,

View file

@ -3,7 +3,7 @@
// Author: Johannes Leupolz <dev@leupolz.eu>
use crate::job_engine::JOB_DISPATCHER;
use crate::jobs::remove_device_job::{self, RemoveDeviceJob};
use crate::jobs::remove_device_job::RemoveDeviceJob;
use crate::process_tools::SELF_NAMESPACES;
use crate::{cuse_device::*, jobs};
use ::cuse_lowlevel::*;
@ -34,7 +34,7 @@ pub unsafe extern "C" fn vuinput_release(
let input_device = input_device.unwrap();
let remove_job = RemoveDeviceJob::new(
vuinput_state.requesting_process.clone(),
input_device.devnode.clone(),
input_device.devname.clone(),
input_device.syspath.clone(),
input_device.major,
input_device.minor,

View file

@ -2,14 +2,14 @@
//
// Author: Johannes Leupolz <dev@leupolz.eu>
use clap::{Parser, ValueEnum};
use clap::ValueEnum;
use std::sync::OnceLock;
#[derive(Debug)]
pub struct GlobalConfig {
pub policy: DevicePolicy,
pub placement: Placement,
pub devname: String,
pub vudevname: String,
}
// The actual static variable. It starts empty and is set once in main().
@ -50,7 +50,7 @@ pub fn initialize_global_config(
.set(GlobalConfig {
policy: device_policy.clone(),
placement: placement.clone(),
devname: devname.clone().unwrap_or("vuinput".to_string()),
vudevname: devname.clone().unwrap_or("vuinput".to_string()),
})
.is_err()
{
@ -67,6 +67,6 @@ pub fn get_placement<'a>() -> &'a Placement {
&CONFIG.get().unwrap().placement
}
pub fn get_devname<'a>() -> &'a String {
&CONFIG.get().unwrap().devname
pub fn get_vudevname<'a>() -> &'a String {
&CONFIG.get().unwrap().vudevname
}

View file

@ -0,0 +1,80 @@
// SPDX-License-Identifier: MIT
//
// Author: Johannes Leupolz <dev@leupolz.eu>
use std::io::{self, BufRead};
use std::{
fs::{self, File},
path::Path,
};
/// Ensure required dev-input, udev directories and files exist
pub fn ensure_host_fs_structure(path_prefix: &str) -> io::Result<()> {
let _ = check_if_path_allows_char_devs(&path_prefix);
let dev_input_dir = format!("{}/dev-input", path_prefix);
let dev_input_dir = Path::new(&dev_input_dir);
// Create directory like `mkdir -p`
if !dev_input_dir.exists() {
fs::create_dir_all(dev_input_dir)?;
}
// Note that this structure _must_ exist, before a service using libinput is run.
let data_dir = format!("{}/udev/data", path_prefix);
let data_dir = Path::new(&data_dir);
// Create directory like `mkdir -p`
if !data_dir.exists() {
fs::create_dir_all(data_dir)?;
}
let control_file = format!("{}/udev/control", path_prefix);
let control_file = Path::new(&control_file);
// Ensure /run/udev/control exists, create empty if not
if !control_file.exists() {
File::create(control_file)?;
}
Ok(())
}
/// simple heuristic that checks whether path_prefix allows the hosting of character devices
/// This heuristic is not 100%, but a simple indicator
pub fn check_if_path_allows_char_devs(path: &str) -> io::Result<()> {
let file = File::open("/proc/self/mountinfo")?;
let reader = io::BufReader::new(file);
for line in reader.lines() {
let line = line?;
let (left, _) = match line.split_once(" - ") {
Some(v) => v,
None => continue,
};
let fields: Vec<&str> = left.split_whitespace().collect();
// mount point is field 5
let mount_point = fields.get(4).copied().unwrap_or("");
// mount options are field 6
let options = fields.get(5).copied().unwrap_or("");
if mount_point.contains(path) {
if options.split(',').any(|o| o == "nodev") {
log::warn!(
"mount {} is present but mounted with nodev; device nodes will not work",
path
);
} else {
log::info!("mount {} is present and allows device nodes", path);
}
return Ok(());
}
}
log::warn!(
"expected mount {} not found; user likely forgot to mount tmpfs with dev-option on it",
path
);
Ok(())
}

View file

@ -2,6 +2,7 @@
//
// Author: Johannes Leupolz <dev@leupolz.eu>
pub mod host_fs;
pub mod input_device;
pub mod netlink_message;
pub mod runtime_data;

View file

@ -9,12 +9,12 @@ use std::path::Path;
use log::{info, warn};
/// Ensure required udev directories and files exist
pub fn ensure_udev_structure(path_prefix: &str, warn_if_nonexistent: bool) -> io::Result<()> {
pub fn ensure_udev_structure() -> io::Result<()> {
// Note that this structure _must_ exist, before a service using libinput is run. The time of device creation might be too late.
let data_dir = format!("{}/udev/data", path_prefix);
let data_dir = format!("/run/udev/data");
let data_dir = Path::new(&data_dir);
let control_file = format!("{}/udev/control", path_prefix);
let control_file = format!("/run/udev/control");
let control_file = Path::new(&control_file);
// Create directory like `mkdir -p`

View file

@ -14,7 +14,7 @@ use async_io::Timer;
use log::debug;
use crate::{
actions::{self, action::Action},
actions::action::Action,
global_config::{self, get_placement, Placement},
input_realizer::runtime_data,
job_engine::job::{Job, JobTarget},
@ -161,7 +161,7 @@ impl EmitUdevEventJob {
let _exit_info = await_process(Pid::Pid(child_pid)).await.unwrap();
}
Placement::OnHost => {
let path_prefix = format!("/run/vuinputd/{}", global_config::get_devname());
let path_prefix = format!("/run/vuinputd/{}", global_config::get_vudevname());
runtime_data::write_udev_data(
&path_prefix,
&runtime_data,

View file

@ -16,8 +16,6 @@ use crate::{
process_tools::{self, await_process, Pid, RequestingProcess},
};
use crate::input_realizer::runtime_data::write_udev_data;
#[derive(Clone, Debug, Copy, PartialOrd, PartialEq)]
pub enum State {
Initialized,
@ -112,11 +110,8 @@ impl MknodDeviceJob {
let _exit_info = await_process(Pid::Pid(child_pid)).await.unwrap();
}
Placement::OnHost => {
let path = format!(
"/run/vuinputd/{}/dev-input/{}",
global_config::get_devname(),
self.devname
);
let path_prefix = format!("/run/vuinputd/{}", global_config::get_vudevname());
let path = format!("{}/dev-input/{}", path_prefix, self.devname);
input_device::ensure_input_device(path.clone(), self.major, self.minor)
.expect(&format!("VUI-DEV-001: could not create {}", &path));
//TODO: somewhat costly

View file

@ -30,7 +30,7 @@ pub enum State {
pub struct RemoveDeviceJob {
requesting_process: RequestingProcess,
target: JobTarget,
dev_path: String,
dev_name: String,
sys_path: String,
major: u64,
minor: u64,
@ -40,7 +40,7 @@ pub struct RemoveDeviceJob {
impl RemoveDeviceJob {
pub fn new(
requesting_process: RequestingProcess,
dev_path: String,
dev_name: String,
sys_path: String,
major: u64,
minor: u64,
@ -48,7 +48,7 @@ impl RemoveDeviceJob {
Self {
requesting_process: requesting_process.clone(),
target: JobTarget::Container(requesting_process),
dev_path: dev_path,
dev_name: dev_name,
sys_path: sys_path,
major: major,
minor: minor,
@ -122,14 +122,14 @@ impl RemoveDeviceJob {
let netlink_data = netlink_event.add_data;
let mut netlink_data = netlink_data.unwrap().clone();
let dev_path = self.dev_path.clone();
let _ = netlink_data.insert("ACTION".to_string(), "remove".to_string());
match global_config::get_placement() {
Placement::InContainer => {
let dev_path = format!("/dev/input/{}", &self.dev_name);
let remove_device_action = Action::RemoveDevice {
path: dev_path.clone(),
path: dev_path,
major: self.major,
minor: self.minor,
};
@ -154,9 +154,17 @@ impl RemoveDeviceJob {
let _exit_info = await_process(Pid::Pid(child_pid_2)).await;
}
Placement::OnHost => {
todo!();
//input_device::remove_input_device(path, major.into(), minor.into())?;
//runtime_data::delete_udev_data("/run",major.into(), minor.into())?;
let path_prefix = format!("/run/vuinputd/{}", global_config::get_vudevname());
let devnode = format!("{}/dev-input/{}", path_prefix, self.dev_name);
input_device::remove_input_device(devnode.clone(), self.major, self.minor).expect(
&format!("VUI-DEV-003: could not remove device node {}", &devnode),
);
runtime_data::delete_udev_data(&path_prefix, self.major, self.minor).expect(
&format!(
"VUI-UDEV-003: could not remove udev data from {}",
&path_prefix
),
);
}
Placement::None => {}
}

View file

@ -33,6 +33,7 @@ use crate::cuse_device::state::{initialize_dedup_last_error, initialize_vuinput_
use crate::cuse_device::vuinput_make_cuse_ops;
use crate::cuse_device::vuinput_open::VUINPUT_COUNTER;
use crate::global_config::{DevicePolicy, Placement};
use crate::input_realizer::host_fs;
use crate::jobs::monitor_udev_job::MonitorBackgroundLoop;
pub mod process_tools;
@ -219,8 +220,9 @@ fn main() -> std::io::Result<()> {
None => "vuinput",
Some(devname) => devname,
};
if args.placement==Placement::OnHost {
todo!("ensure structure and writablity of dev-input")
if args.placement == Placement::OnHost {
let path_prefix = format!("/run/vuinputd/{}", global_config::get_vudevname());
let _ = host_fs::ensure_host_fs_structure(&path_prefix);
}
let vuinput_devicename = CString::new(format!("DEVNAME={}", vuinput_devicename)).unwrap();