Add --container-runtime. Currently the behavior should be the same, but in the future, there should be individual behavior for for example incus or helpful infos during the startup. Also add --container-name and --strategy-file, which are currently unused

This commit is contained in:
Johannes Leupolz 2026-04-14 19:35:25 +00:00
parent 1cbd0540ab
commit 86c4e1c712
10 changed files with 485 additions and 149 deletions

View file

@ -37,7 +37,7 @@ jobs:
fuse3 \
libclang-dev
# debian packages, if packages are not downloaded via cargo
sudo apt install -y librust-bindgen-dev librust-nix-dev librust-libc-dev librust-time-dev librust-log-dev librust-env-logger-dev librust-libudev-dev librust-regex-dev librust-async-channel-dev librust-futures-dev librust-async-io-dev librust-anyhow-dev librust-clap-dev librust-base64-dev librust-smallvec-dev
sudo apt install -y librust-bindgen-dev librust-nix-dev librust-libc-dev librust-time-dev librust-log-dev librust-env-logger-dev librust-libudev-dev librust-regex-dev librust-async-channel-dev librust-futures-dev librust-async-io-dev librust-anyhow-dev librust-clap-dev librust-base64-dev librust-smallvec-dev librust-async-trait-dev
- name: Show versions (debug)
run: |

1
debian/control vendored
View file

@ -27,6 +27,7 @@ Build-Depends: debhelper (>= 12),
librust-clap-dev,
librust-base64-dev,
librust-smallvec-dev,
librust-async-trait-dev,
pkg-config,
build-essential
Standards-Version: 4.6.0

View file

@ -31,10 +31,11 @@ serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
base64 = "0.22"
smallvec = "1.15.1"
async-trait = "0.1.89"
[features]
requires-privileges = []
requires-rootless = []
requires-uinput = []
requires-bwrap = []
requires-podman = []
requires-podman = []

View file

@ -0,0 +1,255 @@
// SPDX-License-Identifier: MIT
//
// Author: Johannes Leupolz <dev@leupolz.eu>
use async_trait::async_trait;
use crate::{
actions::action::Action,
global_config,
input_realizer::{input_device, runtime_data},
process_tools::{self, Pid, RequestingProcess},
};
pub static PLACEMENT_IN_CONTAINER: GenericPlacementInContainer = GenericPlacementInContainer {};
pub static PLACEMENT_ON_HOST: GenericPlacementOnHost = GenericPlacementOnHost {};
pub static SEND_NETLINK_ONLY: GenericSendNetlinkMessageOnly = GenericSendNetlinkMessageOnly {};
#[async_trait]
pub trait InjectionStrategy {
/// Create the device node.
async fn mknod_device_node(
&self,
requesting_process: &RequestingProcess,
devname: &str,
major: u64,
minor: u64,
) -> anyhow::Result<()>;
/// Remove device.
async fn remove_device_node(
&self,
requesting_process: &RequestingProcess,
devname: &str,
major: u64,
minor: u64,
) -> anyhow::Result<()>;
/// Write udev metadata.
async fn write_udev_runtime_data(
&self,
requesting_process: &RequestingProcess,
runtime_data: &str,
major: u64,
minor: u64,
) -> anyhow::Result<()>;
/// Emit netlink message.
/// emit_netlink_message is the same for all container engines, we add
/// a method to enable more flexibility in the future and also allow tests.
// async fn emit_netlink_message(&self, netlink_message: HashMap<String, String>,) -> anyhow::Result<()>;
/// Remove runtime data.
async fn remove_udev_runtime_data(
&self,
requesting_process: &RequestingProcess,
major: u64,
minor: u64,
) -> anyhow::Result<()>;
}
pub struct GenericPlacementInContainer {}
pub struct GenericPlacementOnHost {}
pub struct GenericSendNetlinkMessageOnly {}
#[async_trait]
impl InjectionStrategy for GenericPlacementInContainer {
async fn mknod_device_node(
&self,
requesting_process: &RequestingProcess,
devname: &str,
major: u64,
minor: u64,
) -> anyhow::Result<()> {
let mknod_device_action = Action::MknodDevice {
path: format!("/dev/input/{}", &devname),
major: major,
minor: minor,
};
let child_pid = process_tools::start_action(mknod_device_action, &requesting_process)
.expect("subprocess should work");
let _exit_info = process_tools::await_process(Pid::Pid(child_pid))
.await
.unwrap();
Ok(())
}
async fn remove_device_node(
&self,
requesting_process: &RequestingProcess,
devname: &str,
major: u64,
minor: u64,
) -> anyhow::Result<()> {
let dev_path = format!("/dev/input/{}", devname);
let remove_device_action = Action::RemoveDevice {
path: dev_path,
major: major,
minor: minor,
};
let child_pid_1 = process_tools::start_action(remove_device_action, &requesting_process)
.expect("subprocess should work");
let _exit_info = process_tools::await_process(Pid::Pid(child_pid_1)).await;
Ok(())
}
async fn write_udev_runtime_data(
&self,
requesting_process: &RequestingProcess,
runtime_data: &str,
major: u64,
minor: u64,
) -> anyhow::Result<()> {
let write_udev_runtime_data = Action::WriteUdevRuntimeData {
runtime_data: Some(runtime_data.to_string()),
major: major,
minor: minor,
};
let child_pid = process_tools::start_action(write_udev_runtime_data, &requesting_process)
.expect("subprocess should work");
let _exit_info = process_tools::await_process(Pid::Pid(child_pid))
.await
.unwrap();
Ok(())
}
async fn remove_udev_runtime_data(
&self,
requesting_process: &RequestingProcess,
major: u64,
minor: u64,
) -> anyhow::Result<()> {
let write_udev_runtime_data_action = Action::WriteUdevRuntimeData {
runtime_data: None,
major: major,
minor: minor,
};
let child_pid_2 =
process_tools::start_action(write_udev_runtime_data_action, &requesting_process)
.expect("subprocess should work");
let _exit_info = process_tools::await_process(Pid::Pid(child_pid_2)).await;
Ok(())
}
}
#[async_trait]
impl InjectionStrategy for GenericPlacementOnHost {
async fn mknod_device_node(
&self,
_requesting_process: &RequestingProcess,
devname: &str,
major: u64,
minor: u64,
) -> anyhow::Result<()> {
let path_prefix = format!("/run/vuinputd/{}", global_config::get_vudevname());
let path = format!("{}/dev-input/{}", path_prefix, devname);
input_device::ensure_input_device(path.clone(), major, minor)
.expect(&format!("VUI-DEV-001: could not create {}", &path));
//TODO: somewhat costly
Ok(())
}
async fn remove_device_node(
&self,
_requesting_process: &RequestingProcess,
devname: &str,
major: u64,
minor: u64,
) -> anyhow::Result<()> {
let path_prefix = format!("/run/vuinputd/{}", global_config::get_vudevname());
let devnode = format!("{}/dev-input/{}", path_prefix, devname);
input_device::remove_input_device(devnode.clone(), major, minor).expect(&format!(
"VUI-DEV-003: could not remove device node {}",
&devnode
));
Ok(())
}
async fn write_udev_runtime_data(
&self,
_requesting_process: &RequestingProcess,
runtime_data: &str,
major: u64,
minor: u64,
) -> anyhow::Result<()> {
let path_prefix = format!("/run/vuinputd/{}", global_config::get_vudevname());
runtime_data::write_udev_data(&path_prefix, &runtime_data, major.into(), minor.into())
.expect(&format!(
"VUI-UDEV-002: could not write into {}",
&path_prefix
)); //TODO: somewhat costly
Ok(())
}
async fn remove_udev_runtime_data(
&self,
_requesting_process: &RequestingProcess,
major: u64,
minor: u64,
) -> anyhow::Result<()> {
let path_prefix = format!("/run/vuinputd/{}", global_config::get_vudevname());
runtime_data::delete_udev_data(&path_prefix, major, minor).expect(&format!(
"VUI-UDEV-003: could not remove udev data from {}",
&path_prefix
));
Ok(())
}
}
#[async_trait]
impl InjectionStrategy for GenericSendNetlinkMessageOnly {
async fn mknod_device_node(
&self,
_requesting_process: &RequestingProcess,
_devname: &str,
_major: u64,
_minor: u64,
) -> anyhow::Result<()> {
Ok(())
}
async fn remove_device_node(
&self,
_requesting_process: &RequestingProcess,
_devname: &str,
_major: u64,
_minor: u64,
) -> anyhow::Result<()> {
Ok(())
}
async fn write_udev_runtime_data(
&self,
_requesting_process: &RequestingProcess,
_runtime_data: &str,
_major: u64,
_minor: u64,
) -> anyhow::Result<()> {
Ok(())
}
async fn remove_udev_runtime_data(
&self,
_requesting_process: &RequestingProcess,
_major: u64,
_minor: u64,
) -> anyhow::Result<()> {
Ok(())
}
}

View file

@ -0,0 +1,78 @@
// SPDX-License-Identifier: MIT
//
// Author: Johannes Leupolz <dev@leupolz.eu>
use crate::{
container_runtime::injection_strategy::{
GenericPlacementInContainer, GenericPlacementOnHost, GenericSendNetlinkMessageOnly,
InjectionStrategy, PLACEMENT_IN_CONTAINER, PLACEMENT_ON_HOST, SEND_NETLINK_ONLY,
},
global_config::get_vudevname,
};
pub mod injection_strategy;
/// Container runtime used for name resolution and lifecycle events
#[derive(Debug, Clone, clap::ValueEnum, Default, PartialEq, Eq)]
pub enum ContainerRuntime {
#[default]
/// Probe for installed runtimes (default). Currently just falls back to "generic placement in container"
Auto,
/// Generic linux namespaces. This technique uses nsenter and tries to create files and devices directly in the filesystem inside the container.
GenericPlacementInContainer,
/// Generic linux namespaces. This technique creates files and devices directly in the filesystem of the host. It is the job of the user to bind mount those devices to make them available in the container.
GenericPlacementOnHost,
/// Generic linux namespaces. This technique just sends the netlink message. Works if the user bind mounds the whole /dev/input and /var/run/udev-folder
GenericSendNetlinkMessageOnly,
/// Incus (incus info / incus list). Not implemented, yet.
Incus,
/// Docker (docker inspect / Docker socket). This currently falls back to GenericPlacementInContainer.
Docker,
/// Podman (podman inspect / Podman socket). This currently falls back to GenericPlacementOnHost
Podman,
/// systemd-nspawn via machinectl. This currently falls back to GenericPlacementInContainer.
Nspawn,
/// bubblewrap. This currently falls back to GenericPlacementOnHost
Bubblewrap,
/// Custom engine, please define a --strategie-file
CustomEngine,
}
impl ContainerRuntime {
fn uses_run_folder(&self) -> bool {
match self {
ContainerRuntime::Auto => false,
ContainerRuntime::GenericPlacementInContainer => false,
ContainerRuntime::GenericPlacementOnHost => true,
ContainerRuntime::GenericSendNetlinkMessageOnly => false,
ContainerRuntime::Incus => false,
ContainerRuntime::Docker => false,
ContainerRuntime::Podman => false,
ContainerRuntime::Nspawn => false,
ContainerRuntime::Bubblewrap => true,
ContainerRuntime::CustomEngine => false,
}
}
pub fn initialize(&self) {
if self.uses_run_folder() {
let path_prefix = format!("/run/vuinputd/{}", get_vudevname());
let _ = crate::input_realizer::host_fs::ensure_host_fs_structure(&path_prefix);
}
}
pub fn injection_strategy(&self) -> &'static dyn InjectionStrategy {
match self {
ContainerRuntime::Auto => &PLACEMENT_IN_CONTAINER,
ContainerRuntime::GenericPlacementInContainer => &PLACEMENT_IN_CONTAINER,
ContainerRuntime::GenericPlacementOnHost => &PLACEMENT_ON_HOST,
ContainerRuntime::GenericSendNetlinkMessageOnly => &SEND_NETLINK_ONLY,
ContainerRuntime::Incus => todo!("not implemented yet"),
ContainerRuntime::Docker => &PLACEMENT_IN_CONTAINER,
ContainerRuntime::Podman => &PLACEMENT_IN_CONTAINER,
ContainerRuntime::Nspawn => &PLACEMENT_IN_CONTAINER,
ContainerRuntime::Bubblewrap => &PLACEMENT_ON_HOST,
ContainerRuntime::CustomEngine => todo!("not implemented yet"),
}
}
}

View file

@ -5,10 +5,12 @@
use clap::ValueEnum;
use std::sync::OnceLock;
use crate::container_runtime::ContainerRuntime;
#[derive(Debug)]
pub struct GlobalConfig {
pub policy: DevicePolicy,
pub placement: Placement,
pub container_runtime: ContainerRuntime,
pub vudevname: String,
pub device_owner: DeviceOwner,
}
@ -16,6 +18,16 @@ pub struct GlobalConfig {
// The actual static variable. It starts empty and is set once in main().
pub static CONFIG: OnceLock<GlobalConfig> = OnceLock::new();
/// Defines the operational scope of the vuinputd instance
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub enum Scope {
#[default]
/// Watch all running containers of the configured runtime and manage lifecycle.
Multi,
/// Bind to a single named container. The name is passed directly to the engine's CLI/API.
Single(String),
}
/// The device policy decides what events stay and what is filtered out.
#[derive(Debug, Copy, Clone, PartialEq, Eq, ValueEnum, Default)]
#[clap(rename_all = "kebab-case")] // This ensures StrictGamepad becomes "strict-gamepad"
@ -31,6 +43,8 @@ pub enum DevicePolicy {
StrictGamepad,
}
/// Where to create runtime artifacts (device nodes + udev data)
/// Deprecated, use --container-runtime instead. Currently just maps to
/// --container-runtime
#[derive(Debug, Clone, ValueEnum, Default, PartialEq, Eq)]
pub enum Placement {
#[default]
@ -66,14 +80,14 @@ impl DeviceOwner {
pub fn initialize_global_config(
device_policy: &DevicePolicy,
placement: &Placement,
container_runtime: &ContainerRuntime,
devname: &Option<String>,
device_owner: &DeviceOwner,
) {
if CONFIG
.set(GlobalConfig {
policy: device_policy.clone(),
placement: placement.clone(),
container_runtime: container_runtime.clone(),
vudevname: devname.clone().unwrap_or("vuinput".to_string()),
device_owner: device_owner.clone(),
})
@ -88,8 +102,8 @@ pub fn get_device_policy<'a>() -> &'a DevicePolicy {
&CONFIG.get().unwrap().policy
}
pub fn get_placement<'a>() -> &'a Placement {
&CONFIG.get().unwrap().placement
pub fn get_container_runtime<'a>() -> &'a ContainerRuntime {
&CONFIG.get().unwrap().container_runtime
}
pub fn get_vudevname<'a>() -> &'a String {

View file

@ -15,7 +15,7 @@ use log::debug;
use crate::{
actions::action::Action,
global_config::{self, get_placement, Placement},
global_config::get_container_runtime,
input_realizer::runtime_data,
job_engine::job::{Job, JobTarget},
jobs::monitor_udev_job::EVENT_STORE,
@ -146,35 +146,17 @@ impl EmitUdevEventJob {
let runtime_data = runtime_data.unwrap();
let netlink_data = netlink_data.unwrap();
match get_placement() {
Placement::InContainer => {
let write_udev_runtime_data = Action::WriteUdevRuntimeData {
runtime_data: Some(runtime_data),
major: self.major,
minor: self.minor,
};
let injector = get_container_runtime().injection_strategy();
let child_pid =
process_tools::start_action(write_udev_runtime_data, &self.requesting_process)
.expect("subprocess should work");
let _exit_info = await_process(Pid::Pid(child_pid)).await.unwrap();
}
Placement::OnHost => {
let path_prefix = format!("/run/vuinputd/{}", global_config::get_vudevname());
runtime_data::write_udev_data(
&path_prefix,
&runtime_data,
self.major.into(),
self.minor.into(),
)
.expect(&format!(
"VUI-UDEV-002: could not write into {}",
&path_prefix
)); //TODO: somewhat costly
}
Placement::None => {}
}
injector
.write_udev_runtime_data(
&self.requesting_process,
&runtime_data,
self.major,
self.minor,
)
.await
.unwrap();
// this is always in the container
let emit_netlink_message = Action::EmitNetlinkMessage {

View file

@ -10,7 +10,7 @@ use std::{
use crate::{
actions::action::Action,
global_config::{self, Placement},
global_config::{self, get_container_runtime, Placement},
input_realizer::input_device,
job_engine::job::{Job, JobTarget},
process_tools::{self, await_process, Pid, RequestingProcess},
@ -95,29 +95,17 @@ impl Job for MknodDeviceJob {
impl MknodDeviceJob {
async fn mknod_device(self) {
match global_config::get_placement() {
Placement::InContainer => {
let mknod_device_action = Action::MknodDevice {
path: format!("/dev/input/{}", &self.devname),
major: self.major,
minor: self.minor,
};
let injector = get_container_runtime().injection_strategy();
let child_pid =
process_tools::start_action(mknod_device_action, &self.requesting_process)
.expect("subprocess should work");
let _exit_info = await_process(Pid::Pid(child_pid)).await.unwrap();
}
Placement::OnHost => {
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
}
Placement::None => {}
}
injector
.mknod_device_node(
&self.requesting_process,
&self.devname,
self.major,
self.minor,
)
.await
.unwrap();
self.set_state(&State::Finished);
}

View file

@ -12,7 +12,7 @@ use log::debug;
use crate::{
actions::action::Action,
global_config::{self, Placement},
global_config::{self, get_container_runtime, Placement},
input_realizer::{input_device, runtime_data},
job_engine::job::{Job, JobTarget},
jobs::monitor_udev_job::EVENT_STORE,
@ -125,49 +125,22 @@ impl RemoveDeviceJob {
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,
major: self.major,
minor: self.minor,
};
let injector = get_container_runtime().injection_strategy();
let child_pid_1 =
process_tools::start_action(remove_device_action, &self.requesting_process)
.expect("subprocess should work");
injector
.remove_device_node(
&self.requesting_process,
&self.dev_name,
self.major,
self.minor,
)
.await
.unwrap();
let write_udev_runtime_data_action = Action::WriteUdevRuntimeData {
runtime_data: None,
major: self.major,
minor: self.minor,
};
let child_pid_2 = process_tools::start_action(
write_udev_runtime_data_action,
&self.requesting_process,
)
.expect("subprocess should work");
let _exit_info = await_process(Pid::Pid(child_pid_1)).await;
let _exit_info = await_process(Pid::Pid(child_pid_2)).await;
}
Placement::OnHost => {
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 => {}
}
injector
.remove_udev_runtime_data(&self.requesting_process, self.major, self.minor)
.await
.unwrap();
// this is always in the container
let emit_netlink_message = Action::EmitNetlinkMessage {

View file

@ -24,19 +24,20 @@ use base64::Engine as _;
use log::info;
use std::ffi::CString;
use std::os::raw::c_char;
use std::path::PathBuf;
use std::sync::atomic::AtomicU64;
use std::sync::Mutex;
pub mod cuse_device;
use crate::container_runtime::ContainerRuntime;
use crate::cuse_device::evdev_write_watcher::{
initialize_evdev_write_watcher, EVDEV_WRITE_WATCHER,
};
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::global_config::{DeviceOwner, DevicePolicy, Placement};
use crate::input_realizer::host_fs;
use crate::global_config::{DeviceOwner, DevicePolicy, Placement, Scope};
use crate::jobs::monitor_udev_job::MonitorBackgroundLoop;
pub mod process_tools;
@ -48,6 +49,7 @@ use crate::process_tools::*;
pub mod actions;
pub mod input_realizer;
pub mod container_runtime;
pub mod global_config;
pub mod jobs;
pub mod vt_tools;
@ -101,59 +103,100 @@ struct Args {
#[arg(long, value_enum, default_value_t)]
device_policy: DevicePolicy,
/// Placement of device nodes and udev data
#[arg(long, value_enum, default_value_t)]
pub placement: Placement,
/// [DEPRECATED] Placement of device nodes and udev data. Maps to --container-runtime. Will be removed in a future version.
#[arg(long, value_enum)]
pub placement: Option<Placement>,
/// Owner of the created devices
#[arg(long = "device-owner", value_enum, default_value_t)]
pub device_owner: DeviceOwner,
/// Container runtime used for name resolution and lifecycle events
#[arg(long, default_value_t = ContainerRuntime::Auto, value_enum)]
pub container_runtime: ContainerRuntime,
/// Path to a custom strategy configuration file (used when runtime is 'custom-engine')
#[arg(long)]
pub strategy_file: Option<PathBuf>,
/// Bind to a single named container. If omitted, the daemon watches all running containers (Multi mode).
#[arg(long, value_name = "CONTAINER_NAME")]
pub target_container: Option<String>,
}
fn validate_args(args: &Args) -> Result<(), String> {
let action: &Option<String> = match (&args.action, &args.action_base64) {
(None, None) => &None,
(None, Some(_)) => &args.action_base64,
(Some(_), None) => &args.action,
(Some(_), Some(_)) => {
return Err("--action and --action-base64 may not be used together".into());
}
};
// action might only occur with target-pid
match (
&args.major,
&args.minor,
&args.devname,
action,
&args.target_pid,
) {
(None, None, None, Some(_), _) => {}
(_, _, _, None, None) => {}
_ => {
return Err("--action or --action-base64 must not be used in combination with any other argument other than target-pid".into());
impl Args {
pub fn get_scope(&self) -> Scope {
match &self.target_container {
Some(name) => Scope::Single(name.clone()),
None => Scope::Multi,
}
}
// major/minor must appear together
match (&args.major, &args.minor) {
(Some(_), Some(_)) | (None, None) => {}
_ => {
return Err("--major and --minor must be specified together or not at all".into());
pub fn resolve_runtime(&self) -> ContainerRuntime {
if let Some(legacy_placement) = &self.placement {
return match legacy_placement {
Placement::InContainer => ContainerRuntime::GenericPlacementInContainer,
Placement::OnHost => ContainerRuntime::GenericPlacementOnHost,
Placement::None => ContainerRuntime::GenericSendNetlinkMessageOnly,
};
}
self.container_runtime.clone()
}
// devname length constraint
if let Some(devname) = &args.devname {
if devname.len() >= DEVNAME_MAX_LEN {
return Err(format!(
"--devname must be shorter than {} bytes",
DEVNAME_MAX_LEN
));
fn validate_args(&self) -> Result<(), String> {
if self.placement.is_some() && self.container_runtime != ContainerRuntime::Auto {
return Err(
"Conflict: --placement and --container-runtime cannot be used together. \
Please use only --container-runtime (the --placement flag is deprecated)."
.into(),
);
}
}
Ok(())
let action: &Option<String> = match (&self.action, &self.action_base64) {
(None, None) => &None,
(None, Some(_)) => &self.action_base64,
(Some(_), None) => &self.action,
(Some(_), Some(_)) => {
return Err("--action and --action-base64 may not be used together".into());
}
};
// action might only occur with target-pid
match (
self.major,
self.minor,
&self.devname,
action,
&self.target_pid,
) {
(None, None, None, Some(_), _) => {}
(_, _, _, None, None) => {}
_ => {
return Err("--action or --action-base64 must not be used in combination with any other argument other than target-pid".into());
}
}
// major/minor must appear together
match (self.major, self.minor) {
(Some(_), Some(_)) | (None, None) => {}
_ => {
return Err("--major and --minor must be specified together or not at all".into());
}
}
// devname length constraint
if let Some(devname) = &self.devname {
if devname.len() >= DEVNAME_MAX_LEN {
return Err(format!(
"--devname must be shorter than {} bytes",
DEVNAME_MAX_LEN
));
}
}
Ok(())
}
}
fn main() -> std::io::Result<()> {
@ -164,7 +207,7 @@ fn main() -> std::io::Result<()> {
.next()
.expect("Couldn't retrieve program name");
if let Err(e) = validate_args(&args) {
if let Err(e) = args.validate_args() {
eprintln!("Error: {e}");
std::process::exit(2);
}
@ -186,7 +229,8 @@ fn main() -> std::io::Result<()> {
if action.is_some() {
if let Some(target_pid) = args.target_pid {
process_tools::run_in_net_and_mnt_namespace(target_pid.as_str(),&args.device_owner).unwrap();
process_tools::run_in_net_and_mnt_namespace(target_pid.as_str(), &args.device_owner)
.unwrap();
}
let error_code = actions::handle_action::handle_cli_action(action.unwrap());
std::process::exit(error_code);
@ -200,9 +244,11 @@ fn main() -> std::io::Result<()> {
check_permissions().expect("failed to read the capabilities of the vuinputd process");
vt_tools::check_vt_status();
let container_runtime = args.resolve_runtime();
global_config::initialize_global_config(
&args.device_policy,
&args.placement,
&container_runtime,
&args.devname,
&args.device_owner,
);
@ -235,10 +281,8 @@ fn main() -> std::io::Result<()> {
None => "vuinput",
Some(devname) => devname,
};
if args.placement == Placement::OnHost {
let path_prefix = format!("/run/vuinputd/{}", global_config::get_vudevname());
let _ = host_fs::ensure_host_fs_structure(&path_prefix);
}
container_runtime.initialize();
let vuinput_devicename = CString::new(format!("DEVNAME={}", vuinput_devicename)).unwrap();