Skip to content

Commit 6bde733

Browse files
committed
Start implementation of --placement on-host. Not complete and no automated tests, yet. #1
1 parent b4c6c32 commit 6bde733

File tree

13 files changed

+194
-100
lines changed

13 files changed

+194
-100
lines changed

docs/USAGE.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,8 +126,9 @@ on your container setup and isolation model.
126126
#### `--placement on-host`
127127

128128
* Device nodes and udev runtime data are created **on the host** under:
129-
* `/run/vuinputd/{devname}/dev`
130-
* `/run/vuinputd/{devname}/udev/data`
129+
* `/run/vuinputd/{devname}/dev-input`
130+
* `/run/vuinputd/{devname}/udev`
131+
* `/run/vuinputd/{devname}/dev-input` **must** have the mount option `dev`
131132
* The user is expected to **bind-mount these directories** into the container
132133
* Suitable for:
133134
* read-only containers

vuinputd/src/actions/action.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,18 @@ pub enum Action {
1616
minor: u64,
1717
},
1818

19-
#[serde(rename = "emit-udev-event")]
20-
EmitUdevEvent {
21-
netlink_message: HashMap<String, String>,
19+
#[serde(rename = "write-udev-runtime-data")]
20+
WriteUdevRuntimeData {
2221
runtime_data: Option<String>,
2322
major: u64,
2423
minor: u64,
2524
},
2625

26+
#[serde(rename = "emit-netlink-message")]
27+
EmitNetlinkMessage {
28+
netlink_message: HashMap<String, String>,
29+
},
30+
2731
#[serde(rename = "remove-device")]
2832
RemoveDevice {
2933
path: String,

vuinputd/src/actions/handle_action.rs

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,20 +21,22 @@ fn handle_action(action: Action) -> anyhow::Result<()> {
2121
input_device::ensure_input_device(path, major.into(), minor.into())?;
2222
Ok(())
2323
}
24-
Action::EmitUdevEvent {
25-
netlink_message,
24+
Action::WriteUdevRuntimeData {
2625
runtime_data,
2726
major,
2827
minor,
2928
} => {
30-
netlink_message::send_udev_monitor_message_with_properties(netlink_message);
31-
runtime_data::ensure_udev_structure()?;
29+
runtime_data::ensure_udev_structure("/run",true)?;
3230
match runtime_data {
33-
Some(data) => runtime_data::write_udev_data(&data, major.into(), minor.into())?,
34-
None => runtime_data::delete_udev_data(major.into(), minor.into())?,
31+
Some(data) => runtime_data::write_udev_data("/run",&data, major.into(), minor.into())?,
32+
None => runtime_data::delete_udev_data("/run",major.into(), minor.into())?,
3533
}
3634
Ok(())
3735
}
36+
Action::EmitNetlinkMessage { netlink_message } => {
37+
netlink_message::send_udev_monitor_message_with_properties(netlink_message);
38+
Ok(())
39+
}
3840
Action::RemoveDevice { path, major, minor } => {
3941
input_device::remove_input_device(path, major.into(), minor.into())?;
4042
Ok(())

vuinputd/src/actions/runtime_data.rs

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,13 @@ use std::path::Path;
99
use log::{info, warn};
1010

1111
/// Ensure required udev directories and files exist
12-
pub fn ensure_udev_structure() -> io::Result<()> {
12+
pub fn ensure_udev_structure(path_prefix: &str, warn_if_nonexistent: bool) -> io::Result<()> {
1313
// Note that this structure _must_ exist, before a service using libinput is run. The time of device creation might be too late.
1414

15-
let data_dir = Path::new("/run/udev/data");
16-
let control_file = Path::new("/run/udev/control");
15+
let data_dir = format!("{}/udev/data", path_prefix);
16+
let data_dir = Path::new(&data_dir);
17+
let control_file = format!("{}/udev/control", path_prefix);
18+
let control_file = Path::new(&control_file);
1719

1820
// Create directory like `mkdir -p`
1921
if !data_dir.exists() {
@@ -42,7 +44,7 @@ pub fn ensure_udev_structure() -> io::Result<()> {
4244
/// - remove all lines containing `seat_` references (G:, Q: lines)
4345
/// - replace ID_VUINPUT_* with ID_INPUT_*
4446
/// - write updated content to `/run/udev/data/c<major>:<minor>`
45-
pub fn write_udev_data(content: &str, major: u64, minor: u64) -> io::Result<()> {
47+
pub fn write_udev_data(path_prefix: &str, content: &str, major: u64, minor: u64) -> io::Result<()> {
4648
let mut cleaned = String::new();
4749

4850
for line in content.lines() {
@@ -60,7 +62,7 @@ pub fn write_udev_data(content: &str, major: u64, minor: u64) -> io::Result<()>
6062
cleaned.push('\n');
6163
}
6264

63-
let path = format!("/run/udev/data/c{}:{}", major, minor);
65+
let path = format!("{}/udev/data/c{}:{}", path_prefix, major, minor);
6466
let mut file = File::create(&path)?;
6567
file.write_all(cleaned.as_bytes())?;
6668

@@ -69,8 +71,8 @@ pub fn write_udev_data(content: &str, major: u64, minor: u64) -> io::Result<()>
6971

7072
/// Delete udev data for a given major/minor number
7173
/// - `major`, `minor` = device numbers
72-
pub fn delete_udev_data(major: u64, minor: u64) -> io::Result<()> {
73-
let path = format!("/run/udev/data/c{}:{}", major, minor);
74+
pub fn delete_udev_data(path_prefix: &str, major: u64, minor: u64) -> io::Result<()> {
75+
let path = format!("{}/udev/data/c{}:{}", path_prefix, major, minor);
7476
fs::remove_file(&path)?;
7577
Ok(())
7678
}

vuinputd/src/cuse_device/device_policy.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,10 +61,10 @@ pub fn is_allowed(keytracker: &mut KeyTracker, policy: &DevicePolicy, event: &in
6161
}
6262
}
6363

64-
fn is_allowed_in_mute_sysrq(keytracker: &mut KeyTracker, event: &input_event) -> bool {
64+
fn is_allowed_in_mute_sysrq(_keytracker: &mut KeyTracker, event: &input_event) -> bool {
6565
if event.type_ == EV_KEY && event.code == KEY_SYSRQ {
6666
return false;
67-
}
67+
}
6868
true
6969
}
7070

vuinputd/src/cuse_device/vuinput_ioctl.rs

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@ use uinput_ioctls::*;
1313

1414
use crate::cuse_device::{get_vuinput_state, VuFileHandle};
1515
use crate::job_engine::JOB_DISPATCHER;
16-
use crate::jobs::emit_udev_event_in_container_job::EmitUdevEventInContainerJob;
17-
use crate::jobs::mknod_device_in_container_job::MknodDeviceInContainerJob;
18-
use crate::jobs::remove_from_container_job::RemoveFromContainerJob;
16+
use crate::jobs::emit_udev_event_job::EmitUdevEventJob;
17+
use crate::jobs::mknod_device_job::MknodDeviceJob;
18+
use crate::jobs::remove_device_job::RemoveDeviceJob;
1919
use crate::process_tools::SELF_NAMESPACES;
2020
use crate::{cuse_device::*, jobs};
2121

@@ -164,7 +164,7 @@ pub unsafe extern "C" fn vuinput_ioctl(
164164
CStr::from_ptr(resultbuf.as_ptr()).to_string_lossy()
165165
);
166166
debug!("fh {}: syspath: {}", fh, sysname);
167-
let devnode = fetch_device_node(&sysname).unwrap();
167+
let (devname, devnode) = fetch_device_node(&sysname).unwrap();
168168
debug!("fh {}: devnode: {}", fh, devnode);
169169
let (major, minor) = fetch_major_minor(&devnode).unwrap();
170170
debug!("fh {}: major: {} minor: {} ", fh, major, minor);
@@ -181,9 +181,9 @@ pub unsafe extern "C" fn vuinput_ioctl(
181181
.unwrap()
182182
.equal_mnt_and_net(&vuinput_state.requesting_process.namespaces)
183183
{
184-
let mknod_job = MknodDeviceInContainerJob::new(
184+
let mknod_job = MknodDeviceJob::new(
185185
vuinput_state.requesting_process.clone(),
186-
devnode.clone(),
186+
devname.clone(),
187187
sysname.clone(),
188188
major,
189189
minor,
@@ -195,12 +195,12 @@ pub unsafe extern "C" fn vuinput_ioctl(
195195
.lock()
196196
.unwrap()
197197
.dispatch(Box::new(mknod_job));
198-
awaiter(&jobs::mknod_device_in_container_job::State::Finished);
198+
awaiter(&jobs::mknod_device_job::State::Finished);
199199
debug!("fh {}: mknod_device in container has been finished ", fh);
200200
fuse_lowlevel::fuse_reply_ioctl(_req, 0, std::ptr::null(), 0);
201201

202202
// we do not wait for the udev stuff
203-
let emit_udev_event_job = EmitUdevEventInContainerJob::new(
203+
let emit_udev_event_job = EmitUdevEventJob::new(
204204
vuinput_state.requesting_process.clone(),
205205
devnode.clone(),
206206
sysname.clone(),
@@ -229,7 +229,7 @@ pub unsafe extern "C" fn vuinput_ioctl(
229229
.equal_mnt_and_net(&vuinput_state.requesting_process.namespaces)
230230
{
231231
let input_device = input_device.unwrap();
232-
let remove_job = RemoveFromContainerJob::new(
232+
let remove_job = RemoveDeviceJob::new(
233233
vuinput_state.requesting_process.clone(),
234234
input_device.devnode.clone(),
235235
input_device.syspath.clone(),
@@ -243,7 +243,7 @@ pub unsafe extern "C" fn vuinput_ioctl(
243243
.lock()
244244
.unwrap()
245245
.dispatch(Box::new(remove_job));
246-
awaiter(&jobs::remove_from_container_job::State::Finished);
246+
awaiter(&jobs::remove_device_job::State::Finished);
247247
debug!(
248248
"fh {}: removing dev-nodes from container has been finished ",
249249
fh
@@ -415,12 +415,12 @@ pub unsafe extern "C" fn vuinput_ioctl(
415415
}
416416
}
417417

418-
pub fn fetch_device_node(path: &str) -> io::Result<String> {
418+
pub fn fetch_device_node(path: &str) -> io::Result<(String, String)> {
419419
for entry in fs::read_dir(path)? {
420420
let entry = entry?; // propagate per-entry errors
421421
if let Some(name) = entry.file_name().to_str() {
422422
if name.starts_with("event") {
423-
return Ok(format!("/dev/input/{}", name));
423+
return Ok((name.to_string(), format!("/dev/input/{}", name)));
424424
}
425425
}
426426
}

vuinputd/src/cuse_device/vuinput_release.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
// Author: Johannes Leupolz <dev@leupolz.eu>
44

55
use crate::job_engine::JOB_DISPATCHER;
6-
use crate::jobs::remove_from_container_job::RemoveFromContainerJob;
6+
use crate::jobs::remove_device_job::{self, RemoveDeviceJob};
77
use crate::process_tools::SELF_NAMESPACES;
88
use crate::{cuse_device::*, jobs};
99
use ::cuse_lowlevel::*;
@@ -32,7 +32,7 @@ pub unsafe extern "C" fn vuinput_release(
3232
.equal_mnt_and_net(&vuinput_state.requesting_process.namespaces)
3333
{
3434
let input_device = input_device.unwrap();
35-
let remove_job = RemoveFromContainerJob::new(
35+
let remove_job = RemoveDeviceJob::new(
3636
vuinput_state.requesting_process.clone(),
3737
input_device.devnode.clone(),
3838
input_device.syspath.clone(),
@@ -46,7 +46,7 @@ pub unsafe extern "C" fn vuinput_release(
4646
.lock()
4747
.unwrap()
4848
.dispatch(Box::new(remove_job));
49-
awaiter(&jobs::remove_from_container_job::State::Finished);
49+
awaiter(&jobs::remove_device_job::State::Finished);
5050
}
5151

5252
drop(vuinput_state);

vuinputd/src/global_config.rs

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use std::sync::OnceLock;
99
pub struct GlobalConfig {
1010
pub policy: DevicePolicy,
1111
pub placement: Placement,
12+
pub devname: String,
1213
}
1314

1415
// The actual static variable. It starts empty and is set once in main().
@@ -33,18 +34,23 @@ pub enum DevicePolicy {
3334
pub enum Placement {
3435
#[default]
3536
/// Create inside the container
36-
Inject,
37+
InContainer,
3738
/// Create on the host (user is expected to bind-mount)
38-
Host,
39-
/// Do not create any artifacts
39+
OnHost,
40+
/// Do not create any artifacts (netlink message in container is unaffected)
4041
None,
4142
}
4243

43-
pub fn initialize_global_config(device_policy: &DevicePolicy, placement: &Placement) {
44+
pub fn initialize_global_config(
45+
device_policy: &DevicePolicy,
46+
placement: &Placement,
47+
devname: &Option<String>,
48+
) {
4449
if CONFIG
4550
.set(GlobalConfig {
4651
policy: device_policy.clone(),
4752
placement: placement.clone(),
53+
devname: devname.clone().unwrap_or("vuinput".to_string()),
4854
})
4955
.is_err()
5056
{
@@ -60,3 +66,7 @@ pub fn get_device_policy<'a>() -> &'a DevicePolicy {
6066
pub fn get_placement<'a>() -> &'a Placement {
6167
&CONFIG.get().unwrap().placement
6268
}
69+
70+
pub fn get_devname<'a>() -> &'a String {
71+
&CONFIG.get().unwrap().devname
72+
}

vuinputd/src/jobs/emit_udev_event_in_container_job.rs renamed to vuinputd/src/jobs/emit_udev_event_job.rs

Lines changed: 48 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,12 @@ use async_io::Timer;
1414
use log::debug;
1515

1616
use crate::{
17-
actions::{action::Action, runtime_data::read_udev_data},
17+
actions::{
18+
self,
19+
action::Action,
20+
runtime_data::{self, read_udev_data},
21+
},
22+
global_config::{self, get_placement, Placement},
1823
job_engine::job::{Job, JobTarget},
1924
jobs::monitor_udev_job::EVENT_STORE,
2025
process_tools::{self, await_process, Pid, RequestingProcess},
@@ -28,7 +33,7 @@ pub enum State {
2833
}
2934

3035
#[derive(Clone, Debug)]
31-
pub struct EmitUdevEventInContainerJob {
36+
pub struct EmitUdevEventJob {
3237
requesting_process: RequestingProcess,
3338
target: JobTarget,
3439
dev_path: String,
@@ -38,7 +43,7 @@ pub struct EmitUdevEventInContainerJob {
3843
sync_state: Arc<(Mutex<State>, Condvar)>,
3944
}
4045

41-
impl EmitUdevEventInContainerJob {
46+
impl EmitUdevEventJob {
4247
pub fn new(
4348
requesting_process: RequestingProcess,
4449
dev_path: String,
@@ -79,7 +84,7 @@ impl EmitUdevEventInContainerJob {
7984
}
8085
}
8186

82-
impl Job for EmitUdevEventInContainerJob {
87+
impl Job for EmitUdevEventJob {
8388
fn desc(&self) -> &str {
8489
"emit udev event into container"
8590
}
@@ -88,17 +93,17 @@ impl Job for EmitUdevEventInContainerJob {
8893
false
8994
}
9095

91-
fn create_task(self: &EmitUdevEventInContainerJob) -> Pin<Box<dyn Future<Output = ()>>> {
92-
Box::pin(self.clone().inject_in_container())
96+
fn create_task(self: &EmitUdevEventJob) -> Pin<Box<dyn Future<Output = ()>>> {
97+
Box::pin(self.clone().emit_udev_event())
9398
}
9499

95100
fn job_target(&self) -> JobTarget {
96101
self.target.clone()
97102
}
98103
}
99104

100-
impl EmitUdevEventInContainerJob {
101-
async fn inject_in_container(self) {
105+
impl EmitUdevEventJob {
106+
async fn emit_udev_event(self) {
102107
// temporary hack that needs to be replaced. We try 50 times
103108
// Should be: Wait for the device to be created, the runtime data to be written and the
104109
// netlink message to be sent
@@ -144,18 +149,46 @@ impl EmitUdevEventInContainerJob {
144149
let runtime_data = runtime_data.unwrap();
145150
let netlink_data = netlink_data.unwrap();
146151

147-
let emit_udev_event_action = Action::EmitUdevEvent {
152+
match get_placement() {
153+
Placement::InContainer => {
154+
let write_udev_runtime_data = Action::WriteUdevRuntimeData {
155+
runtime_data: Some(runtime_data),
156+
major: self.major,
157+
minor: self.minor,
158+
};
159+
160+
let child_pid =
161+
process_tools::start_action(write_udev_runtime_data, &self.requesting_process)
162+
.expect("subprocess should work");
163+
164+
let _exit_info = await_process(Pid::Pid(child_pid)).await.unwrap();
165+
}
166+
Placement::OnHost => {
167+
let path_prefix = format!("/run/vuinputd/{}", global_config::get_devname());
168+
runtime_data::write_udev_data(
169+
&path_prefix,
170+
&runtime_data,
171+
self.major.into(),
172+
self.minor.into(),
173+
)
174+
.expect(&format!(
175+
"VUI-UDEV-002: could not write into {}",
176+
&path_prefix
177+
)); //TODO: somewhat costly
178+
}
179+
Placement::None => {}
180+
}
181+
182+
// this is always in the container
183+
let emit_netlink_message = Action::EmitNetlinkMessage {
148184
netlink_message: netlink_data.clone(),
149-
runtime_data: Some(runtime_data),
150-
major: self.major,
151-
minor: self.minor,
152185
};
153186

154-
let child_pid =
155-
process_tools::start_action(emit_udev_event_action, &self.requesting_process)
156-
.expect("subprocess should work");
187+
let child_pid = process_tools::start_action(emit_netlink_message, &self.requesting_process)
188+
.expect("subprocess should work");
157189

158190
let _exit_info = await_process(Pid::Pid(child_pid)).await.unwrap();
191+
159192
self.set_state(&State::Finished);
160193
}
161194
}

0 commit comments

Comments
 (0)