Skip to content

Commit 79e2049

Browse files
ShadowApexpastaq
authored andcommitted
fix(Device Hiding): add 'hide_from_root' option to allow moving devnode during hide logic
1 parent 0b629aa commit 79e2049

File tree

3 files changed

+116
-50
lines changed

3 files changed

+116
-50
lines changed

docs/usage.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,17 @@ mapping:
259259
button: West
260260
```
261261

262+
### Environment Variables
263+
264+
Some aspects of InputPlumber can be controlled with environment variables. This
265+
is the current list of environment variables that are used:
266+
267+
| Variable | Default | Description |
268+
| -------- | ------- | ----------- |
269+
| `LOG_LEVEL` | info | Log level to use. Can be one of: 'error', 'warn', 'info', 'debug', 'trace' |
270+
| `HIDE_DEVICES_FROM_ROOT` | 0 | Try to hide the device from elevated processes by moving the device node to `/dev/inputplumber/sources`. |
271+
272+
262273
### Troubleshooting
263274

264275
Find the InputPlumber version:

src/input/composite_device/mod.rs

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ use crate::{
4040
},
4141
target::TargetDeviceTypeId,
4242
},
43-
udev::{hide_device, unhide_device},
43+
udev::{hide_device, unhide_device, HideFlag},
4444
};
4545

4646
use self::{client::CompositeDeviceClient, command::CompositeCommand};
@@ -123,7 +123,7 @@ pub struct CompositeDevice {
123123
source_devices_discovered: Vec<SourceDevice>,
124124
/// Source devices that should be hidden before they are started. This
125125
/// is a list of devnode paths to hide (e.g. ["/dev/input/event10", "/dev/hidraw1"])
126-
source_devices_to_hide: Vec<String>,
126+
source_devices_to_hide: Vec<(String, &'static [HideFlag])>,
127127
/// HashSet of source devices that are blocked from passing their input events to target
128128
/// events.
129129
source_devices_blocked: HashSet<String>,
@@ -650,9 +650,9 @@ impl CompositeDevice {
650650
/// consume.
651651
async fn run_source_devices(&mut self) -> Result<(), Box<dyn Error>> {
652652
// Hide the device if specified
653-
for source_path in self.source_devices_to_hide.drain(..) {
653+
for (source_path, flags) in self.source_devices_to_hide.drain(..) {
654654
log::debug!("Hiding device: {}", source_path);
655-
if let Err(e) = hide_device(source_path.as_str()).await {
655+
if let Err(e) = hide_device(source_path.as_str(), flags).await {
656656
log::warn!("Failed to hide device '{source_path}': {e:?}");
657657
}
658658
log::debug!("Finished hiding device: {source_path}");
@@ -1713,7 +1713,23 @@ impl CompositeDevice {
17131713
!should_passthru && subsystem.as_str() != "iio" && subsystem.as_str() != "tty";
17141714
if should_hide {
17151715
let source_path = device.devnode();
1716-
self.source_devices_to_hide.push(source_path);
1716+
// The device should be hidden if either the udev device has
1717+
// the appropriate property set OR if inputplumber was run
1718+
// with the appropriate environment variable.
1719+
let hide_from_root = if let Ok(value) = std::env::var("HIDE_DEVICES_FROM_ROOT")
1720+
{
1721+
value.as_str() == "1" || value.as_str() == "true"
1722+
} else if let Some(value) = device.get_property("HIDE_DEVICES_FROM_ROOT") {
1723+
value.as_str() == "1" || value.as_str() == "true"
1724+
} else {
1725+
false
1726+
};
1727+
let flags: &[HideFlag] = if hide_from_root {
1728+
&[HideFlag::ChangePermissions, HideFlag::MoveSourceDevice]
1729+
} else {
1730+
&[HideFlag::ChangePermissions]
1731+
};
1732+
self.source_devices_to_hide.push((source_path, flags));
17171733
}
17181734

17191735
match subsystem.as_str() {

src/udev/mod.rs

Lines changed: 84 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,11 @@ pub mod device_test;
66

77
pub mod device;
88

9-
use std::{error::Error, fs, path::Path};
9+
use std::{
10+
error::Error,
11+
fs,
12+
path::{Path, PathBuf},
13+
};
1014

1115
use tokio::process::Command;
1216
use udev::Enumerator;
@@ -17,8 +21,15 @@ const RULE_HIDE_DEVICE_EARLY_PRIORITY: &str = "50";
1721
const RULE_HIDE_DEVICE_LATE_PRIORITY: &str = "96";
1822
const RULES_PREFIX: &str = "/run/udev/rules.d";
1923

24+
/// HideFlags can be used to change the behavior of how devices are hidden.
25+
#[derive(Debug, PartialEq, Eq)]
26+
pub enum HideFlag {
27+
ChangePermissions,
28+
MoveSourceDevice,
29+
}
30+
2031
/// Hide the given input device from regular users.
21-
pub async fn hide_device(path: &str) -> Result<(), Box<dyn Error>> {
32+
pub async fn hide_device(path: &str, flags: &[HideFlag]) -> Result<(), Box<dyn Error>> {
2233
// Get the device to hide
2334
let device = get_device(path.to_string()).await?;
2435
let name = device.name.clone();
@@ -30,34 +41,64 @@ pub async fn hide_device(path: &str) -> Result<(), Box<dyn Error>> {
3041
return Err("Unable to create match rule for device".into());
3142
};
3243

33-
// Create the directory to move devnodes to
34-
tokio::fs::create_dir_all("/dev/inputplumber/sources").await?;
35-
36-
// Find the chmod command to use for hiding
37-
let chmod_cmd = if Path::new("/bin/chmod").exists() {
38-
"/bin/chmod".to_string()
39-
} else if Path::new("/usr/bin/chmod").exists() {
40-
"/usr/bin/chmod".to_string()
41-
} else {
42-
let output = Command::new("which").arg("chmod").output().await?;
43-
if !output.status.success() {
44-
return Err("Unable to determine chmod command location".into());
45-
}
46-
str::from_utf8(output.stdout.as_slice())?.trim().to_string()
47-
};
44+
// Create the udev rule content to update permissions on the source node.
45+
let mut chmod_early_rule = String::new();
46+
let mut chmod_late_rule = String::new();
47+
if flags.contains(&HideFlag::ChangePermissions) {
48+
// Find the chmod command to use for hiding
49+
let chmod_cmd = if Path::new("/bin/chmod").exists() {
50+
"/bin/chmod".to_string()
51+
} else if Path::new("/usr/bin/chmod").exists() {
52+
"/usr/bin/chmod".to_string()
53+
} else {
54+
let output = Command::new("which").arg("chmod").output().await?;
55+
if !output.status.success() {
56+
return Err("Unable to determine chmod command location".into());
57+
}
58+
str::from_utf8(output.stdout.as_slice())?.trim().to_string()
59+
};
4860

49-
// Find the mv command to use for hiding
50-
let mv_cmd = if Path::new("/bin/mv").exists() {
51-
"/bin/mv".to_string()
52-
} else if Path::new("/usr/bin/mv").exists() {
53-
"/usr/bin/mv".to_string()
54-
} else {
55-
let output = Command::new("which").arg("mv").output().await?;
56-
if !output.status.success() {
57-
return Err("Unable to determine mv command location".into());
58-
}
59-
str::from_utf8(output.stdout.as_slice())?.trim().to_string()
60-
};
61+
// Build the rule content
62+
chmod_early_rule = format!(
63+
r#"KERNEL=="js[0-9]*|event[0-9]*", SUBSYSTEM=="{subsystem}", MODE:="0000", GROUP:="root", RUN+="{chmod_cmd} 000 /dev/input/%k", SYMLINK+="inputplumber/by-hidden/%k"
64+
KERNEL=="hidraw[0-9]*", SUBSYSTEM=="{subsystem}", MODE:="0000", GROUP:="root", RUN+="{chmod_cmd} 000 /dev/%k", SYMLINK+="inputplumber/by-hidden/%k"
65+
"#
66+
);
67+
chmod_late_rule = format!(
68+
r#"KERNEL=="js[0-9]*|event[0-9]*", SUBSYSTEM=="{subsystem}", MODE="000", GROUP="root", TAG-="uaccess", RUN+="{chmod_cmd} 000 /dev/input/%k"
69+
KERNEL=="hidraw[0-9]*", SUBSYSTEM=="{subsystem}", MODE="000", GROUP="root", TAG-="uaccess", RUN+="{chmod_cmd} 000 /dev/%k"
70+
"#
71+
);
72+
}
73+
74+
// Create the udev rule content to move the device node
75+
let mut mv_early_rule = String::new();
76+
let mut mv_late_rule = String::new();
77+
if flags.contains(&HideFlag::MoveSourceDevice) {
78+
// Create the directory to move devnodes to
79+
tokio::fs::create_dir_all("/dev/inputplumber/sources").await?;
80+
81+
// Find the mv command to use for hiding
82+
let mv_cmd = if Path::new("/bin/mv").exists() {
83+
"/bin/mv".to_string()
84+
} else if Path::new("/usr/bin/mv").exists() {
85+
"/usr/bin/mv".to_string()
86+
} else {
87+
let output = Command::new("which").arg("mv").output().await?;
88+
if !output.status.success() {
89+
return Err("Unable to determine mv command location".into());
90+
}
91+
str::from_utf8(output.stdout.as_slice())?.trim().to_string()
92+
};
93+
94+
// Build the rule content
95+
mv_early_rule = format!(
96+
r#"KERNEL=="js[0-9]*|event[0-9]*", SUBSYSTEM=="{subsystem}", RUN+="{mv_cmd} /dev/input/%k /dev/inputplumber/sources/%k"
97+
KERNEL=="hidraw[0-9]*", SUBSYSTEM=="{subsystem}", RUN+="{mv_cmd} /dev/%k /dev/inputplumber/sources/%k"
98+
"#
99+
);
100+
mv_late_rule = mv_early_rule.clone();
101+
}
61102

62103
// Create an early udev rule to hide the device
63104
let rule = format!(
@@ -66,10 +107,8 @@ pub async fn hide_device(path: &str) -> Result<(), Box<dyn Error>> {
66107
{match_rule}, GOTO="inputplumber_valid"
67108
GOTO="inputplumber_end"
68109
LABEL="inputplumber_valid"
69-
KERNEL=="js[0-9]*|event[0-9]*", SUBSYSTEM=="{subsystem}", MODE:="0000", GROUP:="root", RUN+="{chmod_cmd} 000 /dev/input/%k", SYMLINK+="inputplumber/by-hidden/%k"
70-
KERNEL=="hidraw[0-9]*", SUBSYSTEM=="{subsystem}", MODE:="0000", GROUP:="root", RUN+="{chmod_cmd} 000 /dev/%k", SYMLINK+="inputplumber/by-hidden/%k"
71-
KERNEL=="js[0-9]*|event[0-9]*", SUBSYSTEM=="{subsystem}", RUN+="{mv_cmd} /dev/input/%k /dev/inputplumber/sources/%k"
72-
KERNEL=="hidraw[0-9]*", SUBSYSTEM=="{subsystem}", RUN+="{mv_cmd} /dev/%k /dev/inputplumber/sources/%k"
110+
{chmod_early_rule}
111+
{mv_early_rule}
73112
LABEL="inputplumber_end"
74113
"#
75114
);
@@ -87,10 +126,8 @@ LABEL="inputplumber_end"
87126
{match_rule}, GOTO="inputplumber_valid"
88127
GOTO="inputplumber_end"
89128
LABEL="inputplumber_valid"
90-
KERNEL=="js[0-9]*|event[0-9]*", SUBSYSTEM=="{subsystem}", MODE="000", GROUP="root", TAG-="uaccess", RUN+="{chmod_cmd} 000 /dev/input/%k"
91-
KERNEL=="hidraw[0-9]*", SUBSYSTEM=="{subsystem}", MODE="000", GROUP="root", TAG-="uaccess", RUN+="{chmod_cmd} 000 /dev/%k"
92-
KERNEL=="js[0-9]*|event[0-9]*", SUBSYSTEM=="{subsystem}", RUN+="{mv_cmd} /dev/input/%k /dev/inputplumber/sources/%k"
93-
KERNEL=="hidraw[0-9]*", SUBSYSTEM=="{subsystem}", RUN+="{mv_cmd} /dev/%k /dev/inputplumber/sources/%k"
129+
{chmod_late_rule}
130+
{mv_late_rule}
94131
LABEL="inputplumber_end"
95132
"#
96133
);
@@ -126,14 +163,16 @@ pub async fn unhide_device(path: String) -> Result<(), Box<dyn Error>> {
126163

127164
// Move the device back
128165
let src_path = format!("/dev/inputplumber/sources/{name}");
129-
let dst_path = if name.starts_with("event") || name.starts_with("js") {
130-
format!("/dev/input/{name}")
131-
} else {
132-
format!("/dev/{name}")
133-
};
134-
log::debug!("Restoring device node path '{src_path}' to '{dst_path}'");
135-
if let Err(e) = fs::rename(&src_path, &dst_path) {
136-
log::warn!("Failed to move device node from {src_path} to {dst_path}: {e}");
166+
if PathBuf::from(&src_path).exists() {
167+
let dst_path = if name.starts_with("event") || name.starts_with("js") {
168+
format!("/dev/input/{name}")
169+
} else {
170+
format!("/dev/{name}")
171+
};
172+
log::debug!("Restoring device node path '{src_path}' to '{dst_path}'");
173+
if let Err(e) = fs::rename(&src_path, &dst_path) {
174+
log::warn!("Failed to move device node from {src_path} to {dst_path}: {e}");
175+
}
137176
}
138177

139178
// Reload udev

0 commit comments

Comments
 (0)