Skip to content

Commit 2989f3e

Browse files
committed
fix(Profile): use file descriptor instead of profile path
BREAKING CHANGE: The LoadProfilePath method has been replaced with LoadProfile.
1 parent 7a54be8 commit 2989f3e

File tree

12 files changed

+145
-92
lines changed

12 files changed

+145
-92
lines changed

rootfs/usr/share/polkit-1/actions/org.shadowblip.InputPlumber.policy

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -113,9 +113,9 @@
113113
</defaults>
114114
</action>
115115

116-
<action id="org.shadowblip.Input.CompositeDevice.LoadProfilePath">
117-
<description>Load the device profile from the given path</description>
118-
<message>Authorization required to load device profile from a specified path.</message>
116+
<action id="org.shadowblip.Input.CompositeDevice.LoadProfile">
117+
<description>Load the device profile from the given file descriptor</description>
118+
<message>Authorization required to load device profile.</message>
119119
<defaults>
120120
<allow_any>no</allow_any>
121121
<allow_inactive>auth_admin</allow_inactive>

src/cli/device.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
use std::error::Error;
22
use std::fmt::Display;
3+
use std::fs::File;
4+
use std::os::fd::OwnedFd;
35
use std::path::PathBuf;
46

57
use clap::builder::PossibleValuesParser;
@@ -232,7 +234,9 @@ pub async fn handle_device(
232234
.unwrap_or_default()
233235
.to_string_lossy()
234236
.to_string();
235-
if let Err(e) = device.load_profile_path(abs_path).await {
237+
let file = File::open(abs_path)?;
238+
let fd = OwnedFd::from(file);
239+
if let Err(e) = device.load_profile(fd.into()).await {
236240
return Err(format!("Failed to load input profile {path}: {e:?}").into());
237241
}
238242
println!("Successfully loaded profile: {path}");

src/cli/ui/menu/device_test_menu.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::{error::Error, time::Duration};
1+
use std::{error::Error, fs::File, os::fd::OwnedFd, time::Duration};
22

33
use futures::StreamExt;
44
use packed_struct::PackedStruct;
@@ -120,7 +120,9 @@ impl DeviceTestMenu {
120120
let profile_dir = get_profiles_path();
121121
let profile_path = profile_dir.join("debug.yaml");
122122
let profile_path = profile_path.to_string_lossy().to_string();
123-
device.load_profile_path(profile_path).await?;
123+
let profile = File::open(profile_path)?;
124+
let profile_fd = OwnedFd::from(profile);
125+
device.load_profile(profile_fd.into()).await?;
124126
}
125127

126128
// Create channels to listen for input reports
@@ -181,7 +183,9 @@ impl DeviceTestMenu {
181183

182184
// Restore the profile
183185
if let Some(profile_path) = profile_path {
184-
let _ = device.load_profile_path(profile_path).await;
186+
let profile = File::open(profile_path).unwrap();
187+
let profile_fd = OwnedFd::from(profile);
188+
let _ = device.load_profile(profile_fd.into()).await;
185189
} else if let Some(profile) = profile {
186190
let _ = device.load_profile_from_yaml(profile).await;
187191
}

src/config/capability_map.rs

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
pub mod evdev;
22
pub mod hidraw;
33

4-
use std::{collections::HashMap, io::Read, path::Path};
4+
use std::{collections::HashMap, fs::File, io::Read, path::Path};
55

66
use evdev::EvdevConfig;
77
use hidraw::HidrawConfig;
@@ -26,9 +26,8 @@ pub fn load_capability_mappings() -> HashMap<String, CapabilityMapConfig> {
2626
for file in files {
2727
// Try to load the capability map
2828
log::trace!("Found file: {}", file.display());
29-
let mapping = CapabilityMapConfig::from_yaml_file(file.display().to_string());
30-
let map = match mapping {
31-
Ok(map) => map,
29+
let map = match CapabilityMapConfig::from_yaml_path(&file) {
30+
Ok(mapping) => mapping,
3231
Err(e) => {
3332
log::warn!("Failed to parse capability mapping: {e}",);
3433
continue;
@@ -61,13 +60,17 @@ impl CapabilityMapConfig {
6160
Ok(config)
6261
}
6362

64-
/// Load a [CapabilityMapConfig] from the given YAML file
65-
pub fn from_yaml_file<P>(path: P) -> Result<Self, LoadError>
63+
/// Load a [CapabilityMapConfig] from the given YAML file path
64+
pub fn from_yaml_path<P>(path: P) -> Result<Self, LoadError>
6665
where
6766
P: AsRef<Path>,
6867
{
6968
let file = std::fs::File::open(path)?;
69+
Self::from_yaml_file(file)
70+
}
7071

72+
/// Load a [CapabilityMapConfig] from the given YAML file
73+
pub fn from_yaml_file(file: File) -> Result<Self, LoadError> {
7174
// Read up to a defined maximum size to prevent denial of service
7275
const MAX_SIZE: usize = 512 * 1024;
7376
let mut reader = file.take(MAX_SIZE as u64);

src/config/config_test.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ async fn check_autostart_rules() -> Result<(), Box<dyn Error>> {
3232

3333
// Load the config file
3434
let path = entry.path();
35-
let Ok(config) = CompositeDeviceConfig::from_yaml_file(path.display().to_string()) else {
35+
let Ok(config) = CompositeDeviceConfig::from_yaml_path(&path) else {
3636
continue;
3737
};
3838

src/config/mod.rs

Lines changed: 60 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,17 @@ pub mod capability_map;
33
pub mod config_test;
44
pub mod path;
55

6-
use std::io::{self, Read};
6+
use std::{
7+
fs::File,
8+
io::{self, Read},
9+
os::fd::OwnedFd,
10+
path::Path,
11+
};
712

813
use ::procfs::CpuInfo;
914
use capability_map::CapabilityConfig;
1015
use glob_match::glob_match;
16+
use nix::fcntl::{fcntl, FcntlArg, OFlag};
1117

1218
use serde::{Deserialize, Serialize};
1319
use thiserror::Error;
@@ -30,6 +36,8 @@ pub enum LoadError {
3036
DeserializeError(#[from] serde_yaml::Error),
3137
#[error("Config too large, reached maximum size of {0} bytes")]
3238
MaximumSizeReached(usize),
39+
#[error("Operation failed: {0}")]
40+
Os(#[from] nix::errno::Errno),
3341
}
3442

3543
#[derive(Debug, Deserialize, Serialize, Clone)]
@@ -46,16 +54,30 @@ pub struct DeviceProfile {
4654
}
4755

4856
impl DeviceProfile {
49-
/// Load a [CapabilityProfile] from the given YAML string
57+
/// Load a [DeviceProfile] from the given YAML string
5058
pub fn from_yaml(content: String) -> Result<DeviceProfile, LoadError> {
5159
let device: DeviceProfile = serde_yaml::from_str(content.as_str())?;
5260
Ok(device)
5361
}
5462

55-
/// Load a [CapabilityProfile] from the given YAML file
56-
pub fn from_yaml_file(path: String) -> Result<DeviceProfile, LoadError> {
57-
let file = std::fs::File::open(path)?;
63+
/// Load a [DeviceProfile] from the given YAML file descriptor
64+
pub fn from_yaml_fd(fd: OwnedFd) -> Result<Self, LoadError> {
65+
validate_fd_flags(&fd)?;
66+
let file = File::from(fd);
67+
Self::from_yaml_file(file)
68+
}
69+
70+
/// Load a [DeviceProfile] from the given YAML file path
71+
pub fn from_yaml_path<P>(path: P) -> Result<Self, LoadError>
72+
where
73+
P: AsRef<Path>,
74+
{
75+
let file = File::open(path)?;
76+
Self::from_yaml_file(file)
77+
}
5878

79+
/// Load a [DeviceProfile] from the given YAML file
80+
pub fn from_yaml_file(file: File) -> Result<DeviceProfile, LoadError> {
5981
// Read up to a defined maximum size to prevent denial of service
6082
const MAX_SIZE: usize = 512 * 1024;
6183
let mut reader = file.take(MAX_SIZE as u64);
@@ -466,16 +488,30 @@ pub struct CompositeDeviceConfig {
466488
}
467489

468490
impl CompositeDeviceConfig {
469-
/// Load a [CompositeDevice] from the given YAML string
470-
pub fn from_yaml(content: String) -> Result<CompositeDeviceConfig, LoadError> {
491+
/// Load a [CompositeDeviceConfig] from the given YAML string
492+
pub fn from_yaml(content: String) -> Result<Self, LoadError> {
471493
let device: CompositeDeviceConfig = serde_yaml::from_str(content.as_str())?;
472494
Ok(device)
473495
}
474496

475-
/// Load a [CompositeDevice] from the given YAML file
476-
pub fn from_yaml_file(path: String) -> Result<CompositeDeviceConfig, LoadError> {
477-
let file = std::fs::File::open(path)?;
497+
/// Load a [CompositeDeviceConfig] from the given YAML file descriptor
498+
pub fn from_yaml_fd(fd: OwnedFd) -> Result<Self, LoadError> {
499+
validate_fd_flags(&fd)?;
500+
let file = File::from(fd);
501+
Self::from_yaml_file(file)
502+
}
478503

504+
/// Load a [CompositeDeviceConfig] from the given YAML file path
505+
pub fn from_yaml_path<P>(path: P) -> Result<Self, LoadError>
506+
where
507+
P: AsRef<Path>,
508+
{
509+
let file = File::open(path)?;
510+
Self::from_yaml_file(file)
511+
}
512+
513+
/// Load a [CompositeDeviceConfig] from the given YAML file
514+
pub fn from_yaml_file(file: File) -> Result<Self, LoadError> {
479515
// Read up to a defined maximum size to prevent denial of service
480516
const MAX_SIZE: usize = 512 * 1024;
481517
let mut reader = file.take(MAX_SIZE as u64);
@@ -987,3 +1023,17 @@ impl CompositeDeviceConfig {
9871023
Some(matches)
9881024
}
9891025
}
1026+
1027+
/// Ensures the given file descriptor has valid flags set
1028+
fn validate_fd_flags(fd: &OwnedFd) -> Result<(), LoadError> {
1029+
// Validate the flags for the fd
1030+
let flags = fcntl(&fd, FcntlArg::F_GETFL)?;
1031+
let Some(flags) = OFlag::from_bits(flags) else {
1032+
return Err(LoadError::Os(nix::errno::Errno::EIO));
1033+
};
1034+
if flags.contains(OFlag::O_PATH) {
1035+
return Err(LoadError::Os(nix::errno::Errno::EIO));
1036+
}
1037+
1038+
Ok(())
1039+
}

src/dbus/interface/composite_device.rs

Lines changed: 27 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
use std::collections::HashMap;
2-
use std::{collections::HashSet, str::FromStr};
2+
use std::os::fd::AsRawFd;
3+
use std::str::FromStr;
34

5+
use tokio::fs;
6+
use zbus::zvariant::OwnedFd;
47
use zbus::{
58
fdo,
69
message::Header,
@@ -13,7 +16,7 @@ use crate::{
1316
config::DeviceProfile,
1417
dbus::polkit::check_polkit,
1518
input::{
16-
capability::{Capability, Gamepad, Mouse},
19+
capability::Capability,
1720
composite_device::{client::CompositeDeviceClient, InterceptMode},
1821
event::{native::NativeEvent, value::InputValue},
1922
},
@@ -149,21 +152,33 @@ impl CompositeDeviceInterface {
149152
Ok(data)
150153
}
151154

152-
/// Load the device profile from the given path
153-
async fn load_profile_path(
155+
/// Load the device profile
156+
async fn load_profile(
154157
&self,
155-
path: String,
158+
profile: OwnedFd,
156159
#[zbus(connection)] conn: &Connection,
157160
#[zbus(header)] hdr: Header<'_>,
158161
) -> fdo::Result<()> {
159162
check_polkit(
160163
conn,
161164
Some(hdr),
162-
"org.shadowblip.Input.CompositeDevice.LoadProfilePath",
165+
"org.shadowblip.Input.CompositeDevice.LoadProfile",
163166
)
164167
.await?;
168+
169+
// Try to lookup the path from the file descriptor
170+
let fd = std::os::fd::OwnedFd::from(profile);
171+
let raw_fd = fd.as_raw_fd();
172+
let proc_path = format!("/proc/self/fd/{raw_fd}");
173+
let path = fs::read_link(proc_path).await.ok();
174+
log::debug!("Loading profile: {path:?}");
175+
176+
// Load the profile
177+
let profile =
178+
DeviceProfile::from_yaml_fd(fd).map_err(|e| fdo::Error::Failed(e.to_string()))?;
179+
165180
self.composite_device
166-
.load_profile_path(path)
181+
.load_profile(profile, path)
167182
.await
168183
.map_err(|e| fdo::Error::Failed(e.to_string()))
169184
}
@@ -462,30 +477,12 @@ impl CompositeDeviceInterface {
462477
.get_target_capabilities()
463478
.await
464479
.map_err(|e| fdo::Error::Failed(e.to_string()))?;
480+
let capability_strings = capabilities
481+
.into_iter()
482+
.map(|cap| cap.to_capability_string())
483+
.collect();
465484

466-
let mut capability_strings = HashSet::new();
467-
for cap in capabilities {
468-
let str = match cap {
469-
Capability::Gamepad(gamepad) => match gamepad {
470-
Gamepad::Button(button) => format!("Gamepad:Button:{}", button),
471-
Gamepad::Axis(axis) => format!("Gamepad:Axis:{}", axis),
472-
Gamepad::Trigger(trigger) => format!("Gamepad:Trigger:{}", trigger),
473-
Gamepad::Accelerometer => "Gamepad:Accelerometer".to_string(),
474-
Gamepad::Gyro => "Gamepad:Gyro".to_string(),
475-
Gamepad::Dial(dial) => format!("Gamepad:Dial:{dial}"),
476-
},
477-
Capability::Mouse(mouse) => match mouse {
478-
Mouse::Motion => "Mouse:Motion".to_string(),
479-
Mouse::Button(button) => format!("Mouse:Button:{}", button),
480-
},
481-
Capability::Keyboard(key) => format!("Keyboard:{}", key),
482-
Capability::DBus(action) => format!("DBus:{}", action.as_str()),
483-
_ => cap.to_string(),
484-
};
485-
capability_strings.insert(str);
486-
}
487-
488-
Ok(capability_strings.into_iter().collect())
485+
Ok(capability_strings)
489486
}
490487

491488
/// List of source devices that this composite device is processing inputs for

src/dbus/interface/manager.rs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use std::time::Duration;
22

33
use tokio::sync::mpsc;
4-
use zbus::{fdo, message::Header, Connection};
4+
use zbus::{fdo, message::Header, zvariant::OwnedFd, Connection};
55
use zbus_macros::interface;
66

77
use crate::{
@@ -156,11 +156,10 @@ impl ManagerInterface {
156156
Ok(supported.iter().map(|id| id.to_string()).collect())
157157
}
158158

159-
/// Create a composite device using the given composite device config. The
160-
/// path should be the absolute path to a composite device configuration file.
159+
/// Create a composite device using the given composite device config.
161160
async fn create_composite_device(
162161
&self,
163-
config_path: String,
162+
config: OwnedFd,
164163
#[zbus(connection)] conn: &Connection,
165164
#[zbus(header)] hdr: Header<'_>,
166165
) -> fdo::Result<String> {
@@ -170,7 +169,7 @@ impl ManagerInterface {
170169
"org.shadowblip.InputPlumber.CreateCompositeDevice",
171170
)
172171
.await?;
173-
let device = CompositeDeviceConfig::from_yaml_file(config_path).map_err(|e| match e {
172+
let device = CompositeDeviceConfig::from_yaml_fd(config.into()).map_err(|e| match e {
174173
LoadError::IoError(error) => fdo::Error::Failed(error.to_string()),
175174
LoadError::MaximumSizeReached(error) => fdo::Error::Failed(error.to_string()),
176175
LoadError::DeserializeError(_) => {

src/input/composite_device/client.rs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
use std::collections::{HashMap, HashSet};
2+
use std::path::PathBuf;
23
use std::time::Duration;
34
use thiserror::Error;
45
use tokio::sync::mpsc::error::SendTimeoutError;
56
use tokio::sync::mpsc::Receiver;
67
use tokio::sync::mpsc::{channel, error::SendError, Sender};
78

8-
use crate::config::CompositeDeviceConfig;
9+
use crate::config::{CompositeDeviceConfig, DeviceProfile};
910
use crate::input::event::native::NativeEvent;
1011
use crate::input::info::DeviceInfo;
1112
use crate::input::output_capability::OutputCapability;
@@ -340,11 +341,15 @@ impl CompositeDeviceClient {
340341
Err(ClientError::ChannelClosed)
341342
}
342343

343-
/// Load the device profile from the given path
344-
pub async fn load_profile_path(&self, path: String) -> Result<(), ClientError> {
344+
/// Load the device profile
345+
pub async fn load_profile(
346+
&self,
347+
profile: DeviceProfile,
348+
path: Option<PathBuf>,
349+
) -> Result<(), ClientError> {
345350
let (tx, rx) = channel(1);
346351
self.tx
347-
.send(CompositeCommand::LoadProfilePath(path, tx))
352+
.send(CompositeCommand::LoadProfile(profile, path, tx))
348353
.await?;
349354
if let Some(result) = Self::recv(rx).await {
350355
return match result {

0 commit comments

Comments
 (0)