@@ -0,0 +1,2 @@
|
||||
/target
|
||||
/Cargo.lock
|
||||
@@ -0,0 +1,3 @@
|
||||
[workspace]
|
||||
members = ["cli", "daemon", "libcompat/udev", "libs/ipc", "libs/kobject-uevent", "libs/rule"]
|
||||
resolver = "3"
|
||||
@@ -0,0 +1,24 @@
|
||||
This is free and unencumbered software released into the public domain.
|
||||
|
||||
Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||
distribute this software, either in source code form or as a compiled
|
||||
binary, for any purpose, commercial or non-commercial, and by any
|
||||
means.
|
||||
|
||||
In jurisdictions that recognize copyright laws, the author or authors
|
||||
of this software dedicate any and all copyright interest in the
|
||||
software to the public domain. We make this dedication for the benefit
|
||||
of the public at large and to the detriment of our heirs and
|
||||
successors. We intend this dedication to be an overt act of
|
||||
relinquishment in perpetuity of all present and future rights to this
|
||||
software under copyright law.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
For more information, please refer to <https://unlicense.org/>
|
||||
@@ -0,0 +1,9 @@
|
||||
[package]
|
||||
name = "device"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1"
|
||||
clap = { version = "4", features = ["derive"] }
|
||||
ipc = { path = "../libs/ipc" }
|
||||
@@ -0,0 +1,18 @@
|
||||
mod trigger;
|
||||
|
||||
use clap::Parser;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
enum Cli {
|
||||
Trigger(trigger::Cli),
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let cli = Cli::parse();
|
||||
let result = match cli {
|
||||
Cli::Trigger(cli) => trigger::main(cli),
|
||||
};
|
||||
if let Err(err) = result {
|
||||
eprintln!("device: error: {err}");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
use clap::Parser;
|
||||
use ipc::client::Client;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Cli {
|
||||
#[arg(short, long, default_value = "all")]
|
||||
set: String,
|
||||
|
||||
#[arg(short, long, default_value = "change")]
|
||||
action: String,
|
||||
}
|
||||
|
||||
pub fn main(cli: Cli) -> anyhow::Result<()> {
|
||||
let mut client = Client::connect()?;
|
||||
client.trigger(cli.set, cli.action)?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
[package]
|
||||
name = "lxdeviced"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
ipc = { path = "../libs/ipc" }
|
||||
kobject-uevent = { path = "../libs/kobject-uevent" }
|
||||
libc = "0.2"
|
||||
nix = { version = "0.31", features = ["fs", "user"] }
|
||||
rule = { path = "../libs/rule" }
|
||||
rustc-hash = "2"
|
||||
serde = "1"
|
||||
serde_json = "1"
|
||||
tokio = { version = "1", features = ["rt", "rt-multi-thread", "macros", "fs", "signal", "sync"] }
|
||||
tracing = "0.1"
|
||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||
@@ -0,0 +1,209 @@
|
||||
use kobject_uevent::KobjectUevent;
|
||||
use rule::{engine::Engine, parser::When};
|
||||
use rustc_hash::FxHashMap;
|
||||
use std::{
|
||||
fs::Permissions,
|
||||
os::unix::fs::PermissionsExt,
|
||||
path::{Path, PathBuf},
|
||||
sync::{Arc, LazyLock, RwLock},
|
||||
};
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
static DEVICES: LazyLock<RwLock<FxHashMap<String, ArcDevice>>> =
|
||||
LazyLock::new(|| Default::default());
|
||||
|
||||
pub type ArcDevice = Arc<Device>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Device {
|
||||
lock: Mutex<()>,
|
||||
engine: Engine,
|
||||
}
|
||||
impl Device {
|
||||
pub fn new() -> Self {
|
||||
let engine = Engine::new();
|
||||
engine.set_property("DEVROOT".into(), dev_root().to_string_lossy().into());
|
||||
|
||||
Self {
|
||||
lock: Mutex::new(()),
|
||||
engine,
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_uevent(&self, ev: &KobjectUevent) {
|
||||
for (k, v) in ev.inner().iter() {
|
||||
self.engine.set_property(k.into(), v.into());
|
||||
}
|
||||
self.update_derived_properties();
|
||||
}
|
||||
|
||||
fn update_derived_properties(&self) {
|
||||
if let Some(devname) = self.engine.get_property("DEVNAME") {
|
||||
self.engine.set_property(
|
||||
"DEVNODE".into(),
|
||||
format!("{}/{devname}", dev_root().display()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async fn update_devnode_creds(&self) {
|
||||
let Some(devnode) = self.engine.get_property("DEVNODE") else {
|
||||
return;
|
||||
};
|
||||
let uid = crate::util::uid_by_string(self.engine.get_property("OWNER").as_deref());
|
||||
let gid = crate::util::gid_by_string(self.engine.get_property("GROUP").as_deref());
|
||||
let mode = crate::util::parse_mode(self.engine.get_property("MODE").as_deref());
|
||||
|
||||
if let Err(err) = crate::util::set_ownership(&devnode, uid, gid).await {
|
||||
tracing::warn!("{devnode}: failed to set ownership: {err}");
|
||||
}
|
||||
|
||||
if let Some(mode) = mode
|
||||
&& let Err(err) =
|
||||
tokio::fs::set_permissions(&devnode, Permissions::from_mode(mode as _)).await
|
||||
{
|
||||
tracing::warn!("{devnode}: failed to set permissions: {err}");
|
||||
}
|
||||
}
|
||||
|
||||
async fn update_symlinks(&self) {
|
||||
let Some(devnode) = self.engine.get_property("DEVNODE") else {
|
||||
return;
|
||||
};
|
||||
for i in self.engine.get_list("SYMLINKS") {
|
||||
if let Ok(x) = tokio::fs::read_link(&i).await
|
||||
&& x == Path::new(&devnode)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if let Some(parent) = Path::new(&i).parent() {
|
||||
_ = tokio::fs::create_dir_all(parent).await;
|
||||
}
|
||||
if let Err(err) = tokio::fs::symlink(&devnode, &i).await {
|
||||
tracing::warn!("{devnode}: failed to create symbolic link \"{i}\": {err}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn remove_symlinks(&self) {
|
||||
for i in self.engine.get_list("SYMLINKS") {
|
||||
if let Err(err) = tokio::fs::remove_file(&i).await {
|
||||
tracing::warn!("failed to remove symbolic link \"{i}\": {err}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn handle_uevent(ev: &KobjectUevent) {
|
||||
match ev.action() {
|
||||
Some("add") => add(ev).await,
|
||||
Some("remove") => remove(ev).await,
|
||||
Some("change") => change(ev).await,
|
||||
Some(other) => {
|
||||
tracing::warn!("Unrecognized uevent action \"{other}\", should we update lxdeviced?");
|
||||
}
|
||||
None => (),
|
||||
}
|
||||
}
|
||||
|
||||
async fn add(ev: &KobjectUevent) {
|
||||
assert_eq!(ev.action(), Some("add"));
|
||||
|
||||
let Some(devpath) = ev.devpath() else {
|
||||
return;
|
||||
};
|
||||
let device = Device::new();
|
||||
device.apply_uevent(&ev);
|
||||
device.engine.exec(&crate::rules::get()).await;
|
||||
|
||||
device.update_devnode_creds().await;
|
||||
device.update_symlinks().await;
|
||||
|
||||
DEVICES
|
||||
.write()
|
||||
.unwrap()
|
||||
.insert(devpath.into(), Arc::new(device));
|
||||
}
|
||||
|
||||
async fn remove(ev: &KobjectUevent) {
|
||||
assert_eq!(ev.action(), Some("remove"));
|
||||
|
||||
let Some(devpath) = ev.devpath() else {
|
||||
return;
|
||||
};
|
||||
let Some(device) = DEVICES.read().unwrap().get(devpath).cloned() else {
|
||||
return;
|
||||
};
|
||||
let _guard = device.lock.lock().await;
|
||||
device.apply_uevent(ev);
|
||||
device.engine.exec(&crate::rules::get()).await;
|
||||
|
||||
device.remove_symlinks().await;
|
||||
|
||||
DEVICES.write().unwrap().remove(devpath);
|
||||
}
|
||||
|
||||
async fn change(ev: &KobjectUevent) {
|
||||
assert_eq!(ev.action(), Some("change"));
|
||||
|
||||
let Some(devpath) = ev.devpath() else {
|
||||
return;
|
||||
};
|
||||
let Some(device) = DEVICES.read().unwrap().get(devpath).cloned() else {
|
||||
return;
|
||||
};
|
||||
let _guard = device.lock.lock().await;
|
||||
device.apply_uevent(ev);
|
||||
device.engine.exec(&crate::rules::get()).await;
|
||||
}
|
||||
|
||||
pub async fn update_devnode(ev: &KobjectUevent) {
|
||||
if std::env::var("LXDEVICED_HAS_DEVTMPFS").as_deref() == Ok("1") {
|
||||
return;
|
||||
}
|
||||
let Some(devname) = ev.devname() else {
|
||||
return;
|
||||
};
|
||||
let devnode = dev_root().join(devname);
|
||||
if ev.action() == Some("add") {
|
||||
if let Some(parent) = devnode.parent() {
|
||||
_ = tokio::fs::create_dir_all(&parent).await;
|
||||
}
|
||||
let Some(major) = ev.major() else {
|
||||
return;
|
||||
};
|
||||
let Some(minor) = ev.minor() else {
|
||||
return;
|
||||
};
|
||||
let mode = ev.devmode().unwrap_or(0o600);
|
||||
let dev = libc::makedev(major, minor);
|
||||
if ev.subsystem() == Some("block") {
|
||||
_ = crate::util::create_blockdev(devnode, dev, mode).await;
|
||||
} else {
|
||||
_ = crate::util::create_chardev(devnode, dev, mode).await;
|
||||
}
|
||||
} else if ev.action() == Some("remove") {
|
||||
_ = tokio::fs::remove_file(&devnode).await;
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn list_devices(when: When) -> Vec<String> {
|
||||
let mut ret = Vec::new();
|
||||
let keys: Vec<String> = DEVICES.read().unwrap().keys().map(Into::into).collect();
|
||||
for key in keys {
|
||||
let Some(device) = DEVICES.read().unwrap().get(&key).cloned() else {
|
||||
continue;
|
||||
};
|
||||
if device.engine.exec_condition(&when).unwrap_or_default() {
|
||||
ret.push(key);
|
||||
}
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
||||
fn dev_root() -> PathBuf {
|
||||
std::env::var("LXDEVICED_DEV_ROOT")
|
||||
.as_deref()
|
||||
.unwrap_or("/dev")
|
||||
.into()
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
use ipc::{
|
||||
repr::{Error, Request},
|
||||
server::{Connection, Listener},
|
||||
};
|
||||
use rule::parser::When;
|
||||
use serde::Serialize;
|
||||
use std::pin::Pin;
|
||||
|
||||
pub fn start_server() -> std::io::Result<()> {
|
||||
let listener = Listener::bind()?;
|
||||
tokio::spawn(server(listener));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn server(listener: Listener) {
|
||||
loop {
|
||||
let Ok(connection) = listener.accept().await else {
|
||||
continue;
|
||||
};
|
||||
tracing::debug!(
|
||||
"Established connection with {{ uid={}, gid={}, pid={} }}",
|
||||
connection.uid,
|
||||
connection.gid,
|
||||
connection.pid
|
||||
);
|
||||
tokio::spawn(handle_connection(connection));
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_connection(mut connection: Connection) -> std::io::Result<()> {
|
||||
loop {
|
||||
let request = connection.recv().await?;
|
||||
let handler = match request {
|
||||
Request::Trigger(set, action) => handler(trigger(set, action)),
|
||||
Request::ReloadRules => handler(reload_rules()),
|
||||
Request::ListDevices(when) => handler(list_devices(when)),
|
||||
};
|
||||
let reply = handler.await;
|
||||
connection.send(reply).await?;
|
||||
}
|
||||
}
|
||||
|
||||
async fn trigger(set: String, action: String) -> Result<(), Error> {
|
||||
if !crate::trigger::VALID_ACTIONS.contains(&action.as_str()) {
|
||||
return Err(Error::BadRequest);
|
||||
}
|
||||
crate::trigger::trigger(&set, &action).await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn reload_rules() -> Result<(), Error> {
|
||||
crate::rules::reload();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn list_devices(when: When) -> Result<Vec<String>, Error> {
|
||||
Ok(crate::device::list_devices(when).await)
|
||||
}
|
||||
|
||||
fn handler<R: Serialize>(
|
||||
fut: impl Future<Output = Result<R, Error>> + Send + 'static,
|
||||
) -> Pin<Box<dyn Future<Output = Result<serde_json::Value, Error>> + Send>> {
|
||||
Box::pin(async move {
|
||||
let reply = fut.await;
|
||||
reply.map(|x| serde_json::to_value(x).unwrap())
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
mod device;
|
||||
mod ipc;
|
||||
mod rules;
|
||||
mod trigger;
|
||||
mod uevent;
|
||||
mod util;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
if let Err(err) = ipc::start_server() {
|
||||
tracing::error!("Failed to start IPC server: {err}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
if let Err(err) = uevent::start_monitor() {
|
||||
tracing::error!("Failed to start KOBJECT_UEVENT monitor: {err}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
rules::reload();
|
||||
|
||||
_ = tokio::signal::ctrl_c().await;
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
use rule::engine::RuleSet;
|
||||
use std::{
|
||||
path::PathBuf,
|
||||
sync::{LazyLock, RwLock},
|
||||
};
|
||||
|
||||
static RULE_SET: LazyLock<RwLock<RuleSet>> = LazyLock::new(|| RwLock::new(RuleSet::empty()));
|
||||
|
||||
pub fn reload() {
|
||||
*RULE_SET.write().unwrap() = RuleSet::open_dirs(rule_dirs().iter());
|
||||
}
|
||||
|
||||
pub fn get() -> RuleSet {
|
||||
RULE_SET.read().unwrap().clone()
|
||||
}
|
||||
|
||||
pub fn rule_dirs() -> Vec<PathBuf> {
|
||||
let mut all = Vec::with_capacity(1);
|
||||
if let Some(path) = bundle_rule_dir() {
|
||||
all.push(path);
|
||||
}
|
||||
if let Ok(val) = std::env::var("LXDEVICED_EXTRA_RULE_DIRS") {
|
||||
for path in val.split(':') {
|
||||
all.push(path.into());
|
||||
}
|
||||
}
|
||||
all.push("/share/device/rules".into());
|
||||
all.push("/usr/share/device/rules".into());
|
||||
all.push("/etc/device/rules".into());
|
||||
all.push("/var/config/device/rules".into());
|
||||
all
|
||||
}
|
||||
|
||||
fn bundle_rule_dir() -> Option<PathBuf> {
|
||||
// current_exe: "/bundle/bin/lxdeviced"
|
||||
std::fs::canonicalize(std::env::current_exe().ok()?)
|
||||
.ok()?
|
||||
.parent()?
|
||||
.parent()?
|
||||
.join("share/device/rules")
|
||||
.into()
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
pub const VALID_ACTIONS: &[&str] = &["add", "remove", "change"];
|
||||
|
||||
pub async fn trigger(set: &str, action: &str) {
|
||||
assert!(VALID_ACTIONS.contains(&action));
|
||||
for i in set_to_dirs(set) {
|
||||
let path = &sysfs_root().join(i);
|
||||
if trigger_dir(path, action).await.is_err() {
|
||||
tracing::warn!(
|
||||
"\"{}\" is inaccessible. Check your system configuration.",
|
||||
path.display()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn trigger_dir(path: &Path, action: &str) -> std::io::Result<()> {
|
||||
let mut read_dir = tokio::fs::read_dir(path).await?;
|
||||
while let Ok(Some(dirent)) = read_dir.next_entry().await {
|
||||
if dirent.file_name().as_encoded_bytes() == b"uevent" {
|
||||
match tokio::fs::write(dirent.path(), action.as_bytes()).await {
|
||||
Ok(()) => {
|
||||
tracing::debug!(
|
||||
"Triggering uevent \"{action}\" for \"{}\".",
|
||||
dirent.path().display()
|
||||
);
|
||||
}
|
||||
Err(err) => {
|
||||
tracing::warn!(
|
||||
"Failed to trigger uevent \"{action}\" for \"{}\": {err}",
|
||||
dirent.path().display()
|
||||
);
|
||||
}
|
||||
};
|
||||
} else if dirent
|
||||
.file_type()
|
||||
.await
|
||||
.map(|x| x.is_dir())
|
||||
.unwrap_or_default()
|
||||
{
|
||||
_ = Box::pin(trigger_dir(&dirent.path(), action)).await;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_to_dirs(set: &str) -> Vec<PathBuf> {
|
||||
if set == "all" {
|
||||
vec![sysfs_root().into()]
|
||||
} else if set == "devices" {
|
||||
vec![sysfs_root().join("devices")]
|
||||
} else {
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
|
||||
fn sysfs_root() -> PathBuf {
|
||||
std::env::var("LXDEVICED_SYSFS_ROOT")
|
||||
.map(PathBuf::from)
|
||||
.unwrap_or_else(|_| "/sys".into())
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
use kobject_uevent::KobjectUeventSocket;
|
||||
|
||||
pub fn start_monitor() -> std::io::Result<()> {
|
||||
let socket = KobjectUeventSocket::connect(1)?;
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
let ev = match socket.recv().await {
|
||||
Ok(ev) => ev,
|
||||
Err(err) => {
|
||||
tracing::warn!("Failed to receive KOBJECT_UEVENT: {err}");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
tracing::debug!("Received KOBJECT_UEVENT: {ev:?}");
|
||||
crate::device::update_devnode(&ev).await;
|
||||
tokio::spawn(async move {
|
||||
crate::device::handle_uevent(&ev).await;
|
||||
});
|
||||
}
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
use nix::{
|
||||
sys::stat::{Mode, SFlag},
|
||||
unistd::{Group, User},
|
||||
};
|
||||
use std::{os::unix::fs::lchown, path::PathBuf};
|
||||
|
||||
pub async fn create_blockdev(path: impl Into<PathBuf>, dev: u64, mode: u16) -> std::io::Result<()> {
|
||||
create_nodefile(path, SFlag::S_IFBLK, dev, mode).await
|
||||
}
|
||||
|
||||
pub async fn create_chardev(path: impl Into<PathBuf>, dev: u64, mode: u16) -> std::io::Result<()> {
|
||||
create_nodefile(path, SFlag::S_IFCHR, dev, mode).await
|
||||
}
|
||||
|
||||
pub async fn set_ownership(
|
||||
path: impl Into<PathBuf>,
|
||||
uid: Option<u32>,
|
||||
gid: Option<u32>,
|
||||
) -> std::io::Result<()> {
|
||||
let path = path.into();
|
||||
tokio::task::spawn_blocking(move || lchown(&path, uid, gid))
|
||||
.await
|
||||
.unwrap()
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
pub fn uid_by_string(user: Option<&str>) -> Option<u32> {
|
||||
let mut uid = user.and_then(|x| {
|
||||
x.parse::<u32>().ok().or_else(|| {
|
||||
User::from_name(x)
|
||||
.ok()
|
||||
.flatten()
|
||||
.map(|user| user.uid.as_raw())
|
||||
})
|
||||
});
|
||||
if uid.is_none()
|
||||
&& let Some(user) = user
|
||||
{
|
||||
uid = Some(0);
|
||||
tracing::warn!("Unrecognized user \"{user}\", fallbacking to root",);
|
||||
}
|
||||
uid
|
||||
}
|
||||
|
||||
pub fn gid_by_string(group: Option<&str>) -> Option<u32> {
|
||||
let mut gid = group.and_then(|x| {
|
||||
x.parse::<u32>().ok().or_else(|| {
|
||||
Group::from_name(x)
|
||||
.ok()
|
||||
.flatten()
|
||||
.map(|group| group.gid.as_raw())
|
||||
})
|
||||
});
|
||||
if gid.is_none()
|
||||
&& let Some(group) = group
|
||||
{
|
||||
gid = Some(0);
|
||||
tracing::warn!("Unrecognized group \"{group}\", fallbacking to root",);
|
||||
}
|
||||
gid
|
||||
}
|
||||
|
||||
pub fn parse_mode(mode_raw: Option<&str>) -> Option<u16> {
|
||||
let mode = mode_raw
|
||||
.and_then(|x| x.strip_prefix("0o"))
|
||||
.and_then(|x| u16::from_str_radix(x, 8).ok());
|
||||
if mode.is_none()
|
||||
&& let Some(mode_raw) = mode_raw
|
||||
{
|
||||
tracing::warn!("Invalid mode \"{mode_raw}\", fallbacking to the default value");
|
||||
}
|
||||
mode
|
||||
}
|
||||
|
||||
async fn create_nodefile(
|
||||
path: impl Into<PathBuf>,
|
||||
sflag: SFlag,
|
||||
dev: u64,
|
||||
mode: u16,
|
||||
) -> std::io::Result<()> {
|
||||
let path = path.into();
|
||||
tokio::task::spawn_blocking(move || {
|
||||
nix::sys::stat::mknod(&path, sflag, Mode::from_bits_retain(mode as _), dev)
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
.map_err(Into::into)
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
[package]
|
||||
name = "udev"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
ipc = { path = "../../libs/ipc" }
|
||||
libc = "0.2"
|
||||
rustc-hash = "2"
|
||||
rule = { path = "../../libs/rule" }
|
||||
@@ -0,0 +1,124 @@
|
||||
#ifndef _LIBUDEV_H_
|
||||
#define _LIBUDEV_H_
|
||||
|
||||
#include <stddef.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
struct udev;
|
||||
struct udev* udev_ref(struct udev *p);
|
||||
struct udev* udev_unref(struct udev *udev);
|
||||
struct udev* udev_new(void);
|
||||
void* udev_get_userdata(struct udev *udev);
|
||||
void udev_set_userdata(struct udev *udev, void *userdata);
|
||||
|
||||
struct udev_list_entry;
|
||||
struct udev_list_entry* udev_list_entry_get_next(struct udev_list_entry *list_entry);
|
||||
struct udev_list_entry* udev_list_entry_get_by_name(struct udev_list_entry *list_entry, const char *name);
|
||||
const char* udev_list_entry_get_name(struct udev_list_entry *list_entry);
|
||||
const char* udev_list_entry_get_value(struct udev_list_entry *list_entry);
|
||||
#define udev_list_entry_foreach(list_entry, first_entry) \
|
||||
for (list_entry = first_entry; \
|
||||
list_entry; \
|
||||
list_entry = udev_list_entry_get_next(list_entry))
|
||||
|
||||
struct udev_device;
|
||||
struct udev_device* udev_device_ref(struct udev_device *p);
|
||||
struct udev_device* udev_device_unref(struct udev_device *p);
|
||||
struct udev* udev_device_get_udev(struct udev_device *udev_device);
|
||||
struct udev_device* udev_device_new_from_syspath(struct udev *udev, const char *syspath);
|
||||
struct udev_device* udev_device_new_from_devnum(struct udev *udev, char type, dev_t devnum);
|
||||
struct udev_device* udev_device_new_from_subsystem_sysname(struct udev *udev, const char *subsystem, const char *sysname);
|
||||
struct udev_device* udev_device_new_from_device_id(struct udev *udev, const char *id);
|
||||
struct udev_device* udev_device_new_from_environment(struct udev *udev);
|
||||
struct udev_device* udev_device_get_parent(struct udev_device *udev_device);
|
||||
struct udev_device* udev_device_get_parent_with_subsystem_devtype(
|
||||
struct udev_device *udev_device,
|
||||
const char *subsystem,
|
||||
const char *devtype);
|
||||
const char* udev_device_get_devpath(struct udev_device *udev_device);
|
||||
const char* udev_device_get_subsystem(struct udev_device *udev_device);
|
||||
const char* udev_device_get_devtype(struct udev_device *udev_device);
|
||||
const char* udev_device_get_syspath(struct udev_device *udev_device);
|
||||
const char* udev_device_get_sysname(struct udev_device *udev_device);
|
||||
const char* udev_device_get_sysnum(struct udev_device *udev_device);
|
||||
const char* udev_device_get_devnode(struct udev_device *udev_device);
|
||||
int udev_device_get_is_initialized(struct udev_device *udev_device);
|
||||
struct udev_list_entry* udev_device_get_devlinks_list_entry(struct udev_device *udev_device);
|
||||
struct udev_list_entry* udev_device_get_properties_list_entry(struct udev_device *udev_device);
|
||||
struct udev_list_entry* udev_device_get_tags_list_entry(struct udev_device *udev_device);
|
||||
struct udev_list_entry* udev_device_get_current_tags_list_entry(struct udev_device *udev_device);
|
||||
struct udev_list_entry* udev_device_get_sysattr_list_entry(struct udev_device *udev_device);
|
||||
const char* udev_device_get_property_value(struct udev_device *udev_device, const char *key);
|
||||
const char* udev_device_get_driver(struct udev_device *udev_device);
|
||||
dev_t udev_device_get_devnum(struct udev_device *udev_device);
|
||||
const char* udev_device_get_action(struct udev_device *udev_device);
|
||||
unsigned long long int udev_device_get_seqnum(struct udev_device *udev_device);
|
||||
unsigned long long int udev_device_get_usec_since_initialized(struct udev_device *udev_device);
|
||||
const char* udev_device_get_sysattr_value(struct udev_device *udev_device, const char *sysattr);
|
||||
int udev_device_set_sysattr_value(struct udev_device *udev_device, const char *sysattr, const char *value);
|
||||
int udev_device_has_tag(struct udev_device *udev_device, const char *tag);
|
||||
int udev_device_has_current_tag(struct udev_device *udev_device, const char *tag);
|
||||
|
||||
struct udev_monitor;
|
||||
struct udev_monitor* udev_monitor_ref(struct udev_monitor *p);
|
||||
struct udev_monitor* udev_monitor_unref(struct udev_monitor *p);
|
||||
struct udev* udev_monitor_get_udev(struct udev_monitor *udev_monitor);
|
||||
struct udev_monitor* udev_monitor_new_from_netlink(struct udev *udev, const char *name);
|
||||
int udev_monitor_enable_receiving(struct udev_monitor *udev_monitor);
|
||||
int udev_monitor_set_receive_buffer_size(struct udev_monitor *udev_monitor, int size);
|
||||
int udev_monitor_get_fd(struct udev_monitor *udev_monitor);
|
||||
struct udev_device* udev_monitor_receive_device(struct udev_monitor *udev_monitor);
|
||||
int udev_monitor_filter_add_match_subsystem_devtype(
|
||||
struct udev_monitor *udev_monitor,
|
||||
const char *subsystem,
|
||||
const char *devtype);
|
||||
int udev_monitor_filter_add_match_tag(struct udev_monitor *udev_monitor, const char *tag);
|
||||
int udev_monitor_filter_update(struct udev_monitor *udev_monitor);
|
||||
int udev_monitor_filter_remove(struct udev_monitor *udev_monitor);
|
||||
|
||||
struct udev_enumerate;
|
||||
struct udev_enumerate* udev_enumerate_ref(struct udev_enumerate *p);
|
||||
struct udev_enumerate* udev_enumerate_unref(struct udev_enumerate *p);
|
||||
struct udev* udev_enumerate_get_udev(struct udev_enumerate *udev_enumerate);
|
||||
struct udev_enumerate* udev_enumerate_new(struct udev *udev);
|
||||
int udev_enumerate_add_match_subsystem(struct udev_enumerate *udev_enumerate, const char *subsystem);
|
||||
int udev_enumerate_add_nomatch_subsystem(struct udev_enumerate *udev_enumerate, const char *subsystem);
|
||||
int udev_enumerate_add_match_sysattr(struct udev_enumerate *udev_enumerate, const char *sysattr, const char *value);
|
||||
int udev_enumerate_add_nomatch_sysattr(struct udev_enumerate *udev_enumerate, const char *sysattr, const char *value);
|
||||
int udev_enumerate_add_match_property(struct udev_enumerate *udev_enumerate, const char *property, const char *value);
|
||||
int udev_enumerate_add_match_sysname(struct udev_enumerate *udev_enumerate, const char *sysname);
|
||||
int udev_enumerate_add_match_tag(struct udev_enumerate *udev_enumerate, const char *tag);
|
||||
int udev_enumerate_add_match_parent(struct udev_enumerate *udev_enumerate, struct udev_device *parent);
|
||||
int udev_enumerate_add_match_is_initialized(struct udev_enumerate *udev_enumerate);
|
||||
int udev_enumerate_add_syspath(struct udev_enumerate *udev_enumerate, const char *syspath);
|
||||
int udev_enumerate_scan_devices(struct udev_enumerate *udev_enumerate);
|
||||
int udev_enumerate_scan_subsystems(struct udev_enumerate *udev_enumerate);
|
||||
struct udev_list_entry* udev_enumerate_get_list_entry(struct udev_enumerate *udev_enumerate);
|
||||
|
||||
struct udev_queue;
|
||||
struct udev_queue* udev_queue_ref(struct udev_queue *p);
|
||||
struct udev_queue* udev_queue_unref(struct udev_queue *p);
|
||||
struct udev* udev_queue_get_udev(struct udev_queue *udev_queue);
|
||||
struct udev_queue* udev_queue_new(struct udev *udev);
|
||||
int udev_queue_get_udev_is_active(struct udev_queue *udev_queue);
|
||||
int udev_queue_get_queue_is_empty(struct udev_queue *udev_queue);
|
||||
int udev_queue_get_fd(struct udev_queue *udev_queue);
|
||||
int udev_queue_flush(struct udev_queue *udev_queue);
|
||||
|
||||
struct udev_hwdb;
|
||||
struct udev_hwdb* udev_hwdb_new(struct udev *udev);
|
||||
struct udev_hwdb* udev_hwdb_ref(struct udev_hwdb *p);
|
||||
struct udev_hwdb* udev_hwdb_unref(struct udev_hwdb *p);
|
||||
struct udev_list_entry* udev_hwdb_get_properties_list_entry(struct udev_hwdb *hwdb, const char *modalias, unsigned flags);
|
||||
|
||||
int udev_util_encode_string(const char *str, char *str_enc, size_t len);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,901 @@
|
||||
use ipc::client::Client;
|
||||
use rule::parser::{Str, When};
|
||||
use rustc_hash::FxHashMap;
|
||||
use std::{
|
||||
ffi::{CStr, CString},
|
||||
str::FromStr,
|
||||
sync::atomic::{self, AtomicUsize},
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Udev {
|
||||
client: Client,
|
||||
userdata: *mut u8,
|
||||
refcount: AtomicUsize,
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn udev_new() -> *mut Udev {
|
||||
let Ok(client) = Client::connect() else {
|
||||
return std::ptr::null_mut();
|
||||
};
|
||||
Box::into_raw(Box::new(Udev {
|
||||
client,
|
||||
userdata: std::ptr::null_mut(),
|
||||
refcount: AtomicUsize::new(1),
|
||||
}))
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn udev_ref(udev: *mut Udev) -> *mut Udev {
|
||||
unsafe {
|
||||
(*udev).refcount.fetch_add(1, atomic::Ordering::SeqCst);
|
||||
}
|
||||
udev
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn udev_unref(udev: *mut Udev) -> *mut Udev {
|
||||
unsafe {
|
||||
let oldval = (*udev).refcount.fetch_sub(1, atomic::Ordering::SeqCst);
|
||||
if oldval == 1 {
|
||||
drop(Box::from_raw(udev));
|
||||
std::ptr::null_mut()
|
||||
} else {
|
||||
udev
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn udev_get_userdata(udev: *const Udev) -> *mut u8 {
|
||||
unsafe { (*udev).userdata }
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn udev_set_userdata(udev: *mut Udev, userdata: *mut u8) {
|
||||
unsafe {
|
||||
(*udev).userdata = userdata;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct UdevListEntry {
|
||||
key: CString,
|
||||
value: CString,
|
||||
next: Option<Box<UdevListEntry>>,
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn udev_list_entry_get_next(
|
||||
entry: *const UdevListEntry,
|
||||
) -> *const UdevListEntry {
|
||||
unsafe {
|
||||
(*entry)
|
||||
.next
|
||||
.as_ref()
|
||||
.map(|x| &raw const **x)
|
||||
.unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn udev_list_entry_get_by_name(
|
||||
mut entry: *const UdevListEntry,
|
||||
name: *const libc::c_char,
|
||||
) -> *const UdevListEntry {
|
||||
unsafe {
|
||||
let name = CStr::from_ptr(name);
|
||||
while !entry.is_null() {
|
||||
if (*entry).key == name {
|
||||
return entry;
|
||||
}
|
||||
entry = udev_list_entry_get_next(entry);
|
||||
}
|
||||
std::ptr::null()
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn udev_list_entry_get_name(
|
||||
entry: *const UdevListEntry,
|
||||
) -> *const libc::c_char {
|
||||
unsafe { (*entry).key.as_ptr() }
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn udev_list_entry_get_value(
|
||||
entry: *const UdevListEntry,
|
||||
) -> *const libc::c_char {
|
||||
unsafe { (*entry).value.as_ptr() }
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct UdevDevice {
|
||||
refcount: AtomicUsize,
|
||||
udev: *mut Udev,
|
||||
properties: UdevListEntry,
|
||||
syspath: Option<CString>,
|
||||
devlinks: UdevListEntry,
|
||||
tags: UdevListEntry,
|
||||
sysattr: UdevListEntry,
|
||||
action: Option<CString>,
|
||||
seqnum: libc::c_ulonglong,
|
||||
sysattr_cache: FxHashMap<CString, CString>,
|
||||
}
|
||||
impl UdevDevice {
|
||||
fn new(udev: *mut Udev, properties: FxHashMap<String, String>) -> Self {
|
||||
let syspath_str = properties.get("DEVPATH").map(|x| format!("/sys/{x}"));
|
||||
let syspath = syspath_str
|
||||
.as_deref()
|
||||
.map(|x| CString::from_str(x).unwrap_or_default());
|
||||
let devlinks = properties
|
||||
.get("SYMLINKS")
|
||||
.map(|x| x.as_str())
|
||||
.unwrap_or_default()
|
||||
.split('\0')
|
||||
.filter(|x| !x.is_empty())
|
||||
.map(|x| x.to_string())
|
||||
.collect();
|
||||
let devlinks = vec_to_list(&devlinks);
|
||||
let tags = properties
|
||||
.get("TAGS")
|
||||
.map(|x| x.as_str())
|
||||
.unwrap_or_default()
|
||||
.split('\0')
|
||||
.filter(|x| !x.is_empty())
|
||||
.map(|x| x.to_string())
|
||||
.collect();
|
||||
let tags = vec_to_list(&tags);
|
||||
let mut sysattr = Vec::new();
|
||||
if let Some(syspath_str) = &syspath_str
|
||||
&& let Ok(read_dir) = std::fs::read_dir(syspath_str)
|
||||
{
|
||||
for i in read_dir {
|
||||
let Ok(i) = i else {
|
||||
break;
|
||||
};
|
||||
sysattr.push(i.file_name().to_string_lossy().to_string());
|
||||
}
|
||||
}
|
||||
let sysattr = vec_to_list(&sysattr);
|
||||
Self {
|
||||
refcount: AtomicUsize::new(1),
|
||||
udev,
|
||||
properties: *hashmap_to_list(&properties),
|
||||
syspath,
|
||||
devlinks: *devlinks,
|
||||
tags: *tags,
|
||||
sysattr: *sysattr,
|
||||
action: None,
|
||||
seqnum: 0,
|
||||
sysattr_cache: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Drop for UdevDevice {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
udev_unref(self.udev);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn udev_device_ref(dev: *mut UdevDevice) -> *mut UdevDevice {
|
||||
unsafe {
|
||||
(*dev).refcount.fetch_add(1, atomic::Ordering::SeqCst);
|
||||
}
|
||||
dev
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn udev_device_unref(dev: *mut UdevDevice) -> *mut UdevDevice {
|
||||
unsafe {
|
||||
let oldval = (*dev).refcount.fetch_sub(1, atomic::Ordering::SeqCst);
|
||||
if oldval == 1 {
|
||||
drop(Box::from_raw(dev));
|
||||
std::ptr::null_mut()
|
||||
} else {
|
||||
dev
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn udev_device_get_udev(dev: *const UdevDevice) -> *mut Udev {
|
||||
unsafe { (*dev).udev }
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn udev_device_get_devpath(dev: *const UdevDevice) -> *const libc::c_char {
|
||||
unsafe { udev_device_get_property_value(dev, c"DEVPATH".as_ptr()) }
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn udev_device_get_subsystem(dev: *const UdevDevice) -> *const libc::c_char {
|
||||
unsafe { udev_device_get_property_value(dev, c"SUBSYSTEM".as_ptr()) }
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn udev_device_get_devtype(dev: *const UdevDevice) -> *const libc::c_char {
|
||||
unsafe { udev_device_get_property_value(dev, c"DEVTYPE".as_ptr()) }
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn udev_device_get_syspath(dev: *const UdevDevice) -> *const libc::c_char {
|
||||
unsafe {
|
||||
(*dev)
|
||||
.syspath
|
||||
.as_ref()
|
||||
.map(|x| x.as_ptr())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn udev_device_get_sysname(dev: *const UdevDevice) -> *const libc::c_char {
|
||||
unsafe {
|
||||
if dev.is_null() {
|
||||
return std::ptr::null();
|
||||
}
|
||||
let Some(syspath) = (*dev).syspath.as_ref() else {
|
||||
return std::ptr::null();
|
||||
};
|
||||
let split_pos = syspath.as_bytes().iter().rposition(|x| *x == b'/');
|
||||
let offset = split_pos.map(|x| x + 1).unwrap_or_default();
|
||||
syspath.as_ptr().add(offset)
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn udev_device_get_sysnum(dev: *const UdevDevice) -> *const libc::c_char {
|
||||
unsafe {
|
||||
let mut sysname = udev_device_get_sysname(dev);
|
||||
let mut found: *const libc::c_char = std::ptr::null();
|
||||
if sysname.is_null() {
|
||||
return std::ptr::null();
|
||||
}
|
||||
while *sysname != 0 {
|
||||
if found.is_null() && (*sysname as char).is_ascii_digit() {
|
||||
found = sysname;
|
||||
}
|
||||
if !found.is_null() && (*sysname as char).is_ascii_alphabetic() {
|
||||
found = std::ptr::null();
|
||||
}
|
||||
sysname = sysname.add(1);
|
||||
}
|
||||
found
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn udev_device_get_devnode(dev: *const UdevDevice) -> *const libc::c_char {
|
||||
unsafe { udev_device_get_property_value(dev, c"DEVNODE".as_ptr()) }
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn udev_device_get_is_initialized(_: *const UdevDevice) -> libc::c_int {
|
||||
1
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn udev_device_get_devlinks_list_entry(
|
||||
dev: *const UdevDevice,
|
||||
) -> *const UdevListEntry {
|
||||
unsafe { &raw const (*dev).devlinks }
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn udev_device_get_properties_list_entry(
|
||||
dev: *const UdevDevice,
|
||||
) -> *const UdevListEntry {
|
||||
unsafe { &raw const (*dev).properties }
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn udev_device_get_tags_list_entry(
|
||||
dev: *const UdevDevice,
|
||||
) -> *const UdevListEntry {
|
||||
unsafe { &raw const (*dev).tags }
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn udev_device_get_current_tags_list_entry(
|
||||
dev: *const UdevDevice,
|
||||
) -> *const UdevListEntry {
|
||||
unsafe { &raw const (*dev).tags }
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn udev_device_get_sysattr_list_entry(
|
||||
dev: *const UdevDevice,
|
||||
) -> *const UdevListEntry {
|
||||
unsafe { &raw const (*dev).sysattr }
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn udev_device_get_property_value(
|
||||
dev: *const UdevDevice,
|
||||
key: *const libc::c_char,
|
||||
) -> *const libc::c_char {
|
||||
unsafe { udev_list_entry_get_value(udev_list_entry_get_by_name(&(*dev).properties, key)) }
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn udev_device_get_driver(dev: *mut UdevDevice) -> *const libc::c_char {
|
||||
unsafe { udev_device_get_sysattr_value(dev, c"driver".as_ptr()) }
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn udev_device_get_devnum(dev: *const UdevDevice) -> libc::dev_t {
|
||||
unsafe {
|
||||
let devnode = udev_device_get_devnode(dev);
|
||||
if devnode.is_null() {
|
||||
return 0;
|
||||
}
|
||||
let mut statbuf = std::mem::zeroed();
|
||||
let status = libc::lstat(devnode, &mut statbuf);
|
||||
if status != 0 {
|
||||
return 0;
|
||||
}
|
||||
statbuf.st_rdev
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn udev_device_get_action(dev: *const UdevDevice) -> *const libc::c_char {
|
||||
unsafe {
|
||||
(*dev)
|
||||
.action
|
||||
.as_ref()
|
||||
.map(|x| x.as_ptr())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn udev_device_get_seqnum(dev: *const UdevDevice) -> libc::c_ulonglong {
|
||||
unsafe { (*dev).seqnum }
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn udev_device_get_usec_since_initialized(
|
||||
_: *const UdevDevice,
|
||||
) -> libc::c_ulonglong {
|
||||
0
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn udev_device_get_sysattr_value(
|
||||
dev: *mut UdevDevice,
|
||||
sysattr: *const libc::c_char,
|
||||
) -> *const libc::c_char {
|
||||
unsafe {
|
||||
let sysattr = CStr::from_ptr(sysattr);
|
||||
let Ok(sysattr_str) = sysattr.to_str() else {
|
||||
return std::ptr::null();
|
||||
};
|
||||
if let Some(val) = (*dev).sysattr_cache.get(sysattr) {
|
||||
return val.as_ptr();
|
||||
}
|
||||
let Some(syspath) = (*dev).syspath.as_ref() else {
|
||||
return std::ptr::null();
|
||||
};
|
||||
let Ok(mut path) = String::from_utf8(syspath.as_bytes().to_vec()) else {
|
||||
return std::ptr::null();
|
||||
};
|
||||
path.push('/');
|
||||
path.push_str(sysattr_str);
|
||||
let Ok(data) = std::fs::read_to_string(path) else {
|
||||
return std::ptr::null();
|
||||
};
|
||||
let Ok(data) = CString::from_str(&data) else {
|
||||
return std::ptr::null();
|
||||
};
|
||||
let ptr = data.as_ptr();
|
||||
(*dev).sysattr_cache.insert(CString::from(sysattr), data);
|
||||
ptr
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn udev_device_set_sysattr_value(
|
||||
dev: *mut UdevDevice,
|
||||
sysattr: *const libc::c_char,
|
||||
value: *const libc::c_char,
|
||||
) -> libc::c_int {
|
||||
unsafe {
|
||||
let dev = &mut *dev;
|
||||
let sysattr = match CStr::from_ptr(sysattr).to_str() {
|
||||
Ok(s) => s,
|
||||
Err(_) => return -libc::EINVAL,
|
||||
};
|
||||
let value = match CStr::from_ptr(value).to_str() {
|
||||
Ok(s) => s,
|
||||
Err(_) => return -libc::EINVAL,
|
||||
};
|
||||
let syspath = match dev.syspath.as_ref() {
|
||||
Some(p) => p,
|
||||
None => return -libc::ENOENT,
|
||||
};
|
||||
let path = match syspath.to_str() {
|
||||
Ok(p) => format!("{}/{}", p, sysattr),
|
||||
Err(_) => return -libc::ENOENT,
|
||||
};
|
||||
match std::fs::write(&path, value) {
|
||||
Ok(_) => {
|
||||
if let Ok(cstr) = CString::new(value) {
|
||||
let key = match CString::new(sysattr) {
|
||||
Ok(k) => k,
|
||||
Err(_) => return 0,
|
||||
};
|
||||
dev.sysattr_cache.insert(key, cstr);
|
||||
}
|
||||
0
|
||||
}
|
||||
Err(e) => match e.raw_os_error() {
|
||||
Some(code) => -code,
|
||||
None => -libc::EIO,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn udev_device_has_tag(
|
||||
dev: *const UdevDevice,
|
||||
tag: *const libc::c_char,
|
||||
) -> libc::c_int {
|
||||
unsafe {
|
||||
(!udev_list_entry_get_by_name(udev_device_get_tags_list_entry(dev), tag).is_null()) as _
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn udev_device_has_current_tag(
|
||||
dev: *const UdevDevice,
|
||||
tag: *const libc::c_char,
|
||||
) -> libc::c_int {
|
||||
unsafe {
|
||||
(!udev_list_entry_get_by_name(udev_device_get_tags_list_entry(dev), tag).is_null()) as _
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct UdevMonitor {
|
||||
udev: *mut Udev,
|
||||
refcount: AtomicUsize,
|
||||
}
|
||||
impl Drop for UdevMonitor {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
udev_unref(self.udev);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn udev_monitor_ref(mon: *mut UdevMonitor) -> *mut UdevMonitor {
|
||||
unsafe {
|
||||
(*mon).refcount.fetch_add(1, atomic::Ordering::SeqCst);
|
||||
}
|
||||
mon
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn udev_monitor_unref(mon: *mut UdevMonitor) -> *mut UdevMonitor {
|
||||
unsafe {
|
||||
let oldval = (*mon).refcount.fetch_sub(1, atomic::Ordering::SeqCst);
|
||||
if oldval == 1 {
|
||||
drop(Box::from_raw(mon));
|
||||
std::ptr::null_mut()
|
||||
} else {
|
||||
mon
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn udev_monitor_get_udev(mon: *const UdevMonitor) -> *mut Udev {
|
||||
unsafe { (*mon).udev }
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn udev_monitor_new_from_netlink(
|
||||
udev: *mut Udev,
|
||||
name: *const libc::c_char,
|
||||
) -> *mut UdevMonitor {
|
||||
unsafe {
|
||||
let name = CStr::from_ptr(name);
|
||||
if name != c"udev" {
|
||||
return std::ptr::null_mut();
|
||||
}
|
||||
Box::into_raw(Box::new(UdevMonitor {
|
||||
udev,
|
||||
refcount: AtomicUsize::new(1),
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn udev_monitor_enable_receiving(_: *const UdevMonitor) -> libc::c_int {
|
||||
-libc::EPERM
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn udev_monitor_set_receive_buffer_size(
|
||||
_: *const UdevMonitor,
|
||||
_: libc::c_int,
|
||||
) -> libc::c_int {
|
||||
0
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn udev_monitor_get_fd(_: *const UdevMonitor) -> libc::c_int {
|
||||
-1
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn udev_monitor_receive_device(_: *const UdevMonitor) -> *mut UdevDevice {
|
||||
std::ptr::null_mut()
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn udev_monitor_filter_add_match_subsystem_devtype(
|
||||
_mon: *mut UdevMonitor,
|
||||
_subsystem: *const libc::c_char,
|
||||
_devtype: *const libc::c_char,
|
||||
) -> libc::c_int {
|
||||
-libc::EPERM
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn udev_monitor_filter_add_match_tag(
|
||||
_mon: *mut UdevMonitor,
|
||||
_tag: *const libc::c_char,
|
||||
) -> libc::c_int {
|
||||
-libc::EPERM
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn udev_monitor_filter_update(_mon: *mut UdevMonitor) -> libc::c_int {
|
||||
-libc::EPERM
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn udev_monitor_filter_remove(_mon: *mut UdevMonitor) -> libc::c_int {
|
||||
-libc::EPERM
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct UdevEnumerate {
|
||||
udev: *mut Udev,
|
||||
refcount: AtomicUsize,
|
||||
filters: Vec<When>,
|
||||
result: Option<UdevListEntry>,
|
||||
}
|
||||
impl Drop for UdevEnumerate {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
udev_unref(self.udev);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn udev_enumerate_ref(p: *mut UdevEnumerate) -> *mut UdevEnumerate {
|
||||
unsafe {
|
||||
(*p).refcount.fetch_add(1, atomic::Ordering::SeqCst);
|
||||
}
|
||||
p
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn udev_enumerate_unref(p: *mut UdevEnumerate) -> *mut UdevEnumerate {
|
||||
unsafe {
|
||||
let oldval = (*p).refcount.fetch_sub(1, atomic::Ordering::SeqCst);
|
||||
if oldval == 1 {
|
||||
drop(Box::from_raw(p));
|
||||
std::ptr::null_mut()
|
||||
} else {
|
||||
p
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn udev_enumerate_get_udev(p: *mut UdevEnumerate) -> *mut Udev {
|
||||
unsafe { (*p).udev }
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn udev_enumerate_new(udev: *mut Udev) -> *mut UdevEnumerate {
|
||||
Box::into_raw(Box::new(UdevEnumerate {
|
||||
udev,
|
||||
refcount: AtomicUsize::new(1),
|
||||
filters: Vec::with_capacity(16),
|
||||
result: None,
|
||||
}))
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn udev_enumerate_add_match_subsystem(
|
||||
udev_enumerate: *mut UdevEnumerate,
|
||||
subsystem: *const libc::c_char,
|
||||
) -> libc::c_int {
|
||||
unsafe { udev_enumerate_match(udev_enumerate, "SUBSYSTEM", subsystem, false) }
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn udev_enumerate_add_nomatch_subsystem(
|
||||
udev_enumerate: *mut UdevEnumerate,
|
||||
subsystem: *const libc::c_char,
|
||||
) -> libc::c_int {
|
||||
unsafe { udev_enumerate_match(udev_enumerate, "SUBSYSTEM", subsystem, true) }
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn udev_enumerate_add_match_sysattr(
|
||||
udev_enumerate: *mut UdevEnumerate,
|
||||
sysattr: *const libc::c_char,
|
||||
value: *const libc::c_char,
|
||||
) -> libc::c_int {
|
||||
unsafe {
|
||||
let Ok(sysattr) = CStr::from_ptr(sysattr).to_str() else {
|
||||
return -libc::EINVAL;
|
||||
};
|
||||
udev_enumerate_match(udev_enumerate, sysattr, value, false)
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn udev_enumerate_add_nomatch_sysattr(
|
||||
udev_enumerate: *mut UdevEnumerate,
|
||||
sysattr: *const libc::c_char,
|
||||
value: *const libc::c_char,
|
||||
) -> libc::c_int {
|
||||
unsafe {
|
||||
let Ok(sysattr) = CStr::from_ptr(sysattr).to_str() else {
|
||||
return -libc::EINVAL;
|
||||
};
|
||||
udev_enumerate_match(udev_enumerate, sysattr, value, true)
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn udev_enumerate_add_match_property(
|
||||
udev_enumerate: *mut UdevEnumerate,
|
||||
property: *const libc::c_char,
|
||||
value: *const libc::c_char,
|
||||
) -> libc::c_int {
|
||||
unsafe {
|
||||
let Ok(property) = CStr::from_ptr(property).to_str() else {
|
||||
return -libc::EINVAL;
|
||||
};
|
||||
udev_enumerate_match(udev_enumerate, property, value, false)
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn udev_enumerate_add_match_sysname(
|
||||
udev_enumerate: *mut UdevEnumerate,
|
||||
sysname: *const libc::c_char,
|
||||
) -> libc::c_int {
|
||||
unsafe { udev_enumerate_match(udev_enumerate, "SYSNAME", sysname, false) }
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn udev_enumerate_add_match_tag(
|
||||
udev_enumerate: *mut UdevEnumerate,
|
||||
tag: *const libc::c_char,
|
||||
) -> libc::c_int {
|
||||
unsafe { udev_enumerate_match(udev_enumerate, "TAGS", tag, false) }
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn udev_enumerate_add_match_parent(
|
||||
_: *mut UdevEnumerate,
|
||||
_: *mut libc::c_void,
|
||||
) -> libc::c_int {
|
||||
-libc::EOPNOTSUPP
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn udev_enumerate_add_match_is_initialized(
|
||||
_: *mut UdevEnumerate,
|
||||
) -> libc::c_int {
|
||||
0
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn udev_enumerate_add_syspath(
|
||||
udev_enumerate: *mut UdevEnumerate,
|
||||
syspath: *const libc::c_char,
|
||||
) -> libc::c_int {
|
||||
unsafe { udev_enumerate_match(udev_enumerate, "SYSPATH", syspath, false) }
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn udev_enumerate_scan_devices(
|
||||
udev_enumerate: *mut UdevEnumerate,
|
||||
) -> libc::c_int {
|
||||
unsafe {
|
||||
let udev = (*udev_enumerate).udev;
|
||||
let when = When::All((*udev_enumerate).filters.clone());
|
||||
let result = match (*udev).client.list_devices(when) {
|
||||
Ok(x) => x,
|
||||
Err(_) => return -libc::EPERM,
|
||||
};
|
||||
(*udev_enumerate).result = Some(*vec_to_list(&result));
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn udev_enumerate_scan_subsystems(
|
||||
udev_enumerate: *mut UdevEnumerate,
|
||||
) -> libc::c_int {
|
||||
unsafe { udev_enumerate_scan_devices(udev_enumerate) }
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn udev_enumerate_get_list_entry(
|
||||
udev_enumerate: *mut UdevEnumerate,
|
||||
) -> *mut UdevListEntry {
|
||||
unsafe {
|
||||
(*udev_enumerate)
|
||||
.result
|
||||
.as_mut()
|
||||
.map(|x| &raw mut *x)
|
||||
.unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn udev_enumerate_match(
|
||||
this: *mut UdevEnumerate,
|
||||
key: &str,
|
||||
data: *const libc::c_char,
|
||||
not: bool,
|
||||
) -> libc::c_int {
|
||||
unsafe {
|
||||
let Ok(data) = CStr::from_ptr(data).to_str() else {
|
||||
return -libc::EINVAL;
|
||||
};
|
||||
let mut when = When::Match(Str::Direct(key.into()), Str::Direct(data.into()));
|
||||
if not {
|
||||
when = When::Not(Box::new(when));
|
||||
}
|
||||
(*this).filters.push(when);
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct UdevQueue;
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn udev_queue_ref(_p: *mut UdevQueue) -> *mut UdevQueue {
|
||||
std::ptr::null_mut()
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn udev_queue_unref(_p: *mut UdevQueue) -> *mut UdevQueue {
|
||||
std::ptr::null_mut()
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn udev_queue_get_udev(_udev_queue: *mut UdevQueue) -> *mut Udev {
|
||||
std::ptr::null_mut()
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn udev_queue_new(_udev: *mut Udev) -> *mut UdevQueue {
|
||||
std::ptr::null_mut()
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn udev_queue_get_udev_is_active(_udev_queue: *mut UdevQueue) -> libc::c_int {
|
||||
-1
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn udev_queue_get_queue_is_empty(_udev_queue: *mut UdevQueue) -> libc::c_int {
|
||||
-1
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn udev_queue_get_fd(_udev_queue: *mut UdevQueue) -> libc::c_int {
|
||||
-1
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn udev_queue_flush(_udev_queue: *mut UdevQueue) -> libc::c_int {
|
||||
-1
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct UdevHwdb;
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn udev_hwdb_new(_udev: *mut Udev) -> *mut UdevHwdb {
|
||||
std::ptr::null_mut()
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn udev_hwdb_ref(_p: *mut UdevHwdb) -> *mut UdevHwdb {
|
||||
std::ptr::null_mut()
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn udev_hwdb_unref(_p: *mut UdevHwdb) -> *mut UdevHwdb {
|
||||
std::ptr::null_mut()
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn udev_hwdb_get_properties_list_entry(
|
||||
_hwdb: *mut UdevHwdb,
|
||||
_modalias: *const libc::c_char,
|
||||
_flags: libc::c_uint,
|
||||
) -> *mut UdevListEntry {
|
||||
std::ptr::null_mut()
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn udev_util_encode_string(
|
||||
s: *const std::ffi::c_char,
|
||||
str_enc: *mut std::ffi::c_char,
|
||||
len: usize,
|
||||
) -> std::ffi::c_int {
|
||||
unsafe {
|
||||
libc::memcpy(str_enc.cast(), s.cast(), len);
|
||||
}
|
||||
len as _
|
||||
}
|
||||
|
||||
fn hashmap_to_list(hashmap: &FxHashMap<String, String>) -> Box<UdevListEntry> {
|
||||
let mut head = None;
|
||||
let mut tail = &mut head;
|
||||
|
||||
for (key, value) in hashmap {
|
||||
let key_cstr = CString::new(key.as_str()).unwrap();
|
||||
let value_cstr = CString::new(value.as_str()).unwrap();
|
||||
let entry = Box::new(UdevListEntry {
|
||||
key: key_cstr,
|
||||
value: value_cstr,
|
||||
next: None,
|
||||
});
|
||||
*tail = Some(entry);
|
||||
tail = &mut tail.as_mut().unwrap().next;
|
||||
}
|
||||
|
||||
head.unwrap_or(Box::new(UdevListEntry {
|
||||
key: CString::new("").unwrap(),
|
||||
value: CString::new("").unwrap(),
|
||||
next: None,
|
||||
}))
|
||||
}
|
||||
|
||||
fn vec_to_list(vec: &Vec<String>) -> Box<UdevListEntry> {
|
||||
let mut head = None;
|
||||
let mut tail = &mut head;
|
||||
|
||||
for s in vec {
|
||||
let cstr = CString::new(s.as_str()).unwrap();
|
||||
let entry = Box::new(UdevListEntry {
|
||||
key: cstr,
|
||||
value: CString::new("").unwrap(),
|
||||
next: None,
|
||||
});
|
||||
*tail = Some(entry);
|
||||
tail = &mut tail.as_mut().unwrap().next;
|
||||
}
|
||||
|
||||
head.unwrap_or_else(|| {
|
||||
Box::new(UdevListEntry {
|
||||
key: CString::new("").unwrap(),
|
||||
value: CString::new("").unwrap(),
|
||||
next: None,
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
[package]
|
||||
name = "ipc"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
rule = { path = "../rule" }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
thiserror = "2"
|
||||
tokio = { version = "1", features = ["net", "io-util"] }
|
||||
@@ -0,0 +1,63 @@
|
||||
use crate::{
|
||||
SOCKET_NAME,
|
||||
repr::{Error, Request, de_reply},
|
||||
};
|
||||
use rule::parser::When;
|
||||
use serde::de::DeserializeOwned;
|
||||
use std::{
|
||||
io::{Read, Write},
|
||||
os::{
|
||||
linux::net::SocketAddrExt,
|
||||
unix::net::{SocketAddr, UnixStream},
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Client {
|
||||
stream: UnixStream,
|
||||
buf: Vec<u8>,
|
||||
}
|
||||
impl Client {
|
||||
pub fn connect() -> std::io::Result<Self> {
|
||||
let addr = SocketAddr::from_abstract_name(SOCKET_NAME)?;
|
||||
Ok(Self {
|
||||
stream: UnixStream::connect_addr(&addr)?,
|
||||
buf: Vec::with_capacity(128),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn send(&mut self, req: &Request) -> std::io::Result<()> {
|
||||
self.buf.clear();
|
||||
serde_json::to_writer(&mut self.buf, req).unwrap();
|
||||
self.stream
|
||||
.write_all(&(self.buf.len() as u16).to_le_bytes())?;
|
||||
self.stream.write_all(&self.buf)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn recv<T: DeserializeOwned>(&mut self) -> std::io::Result<Result<T, Error>> {
|
||||
let mut len = [0u8; size_of::<u16>()];
|
||||
self.stream.read_exact(&mut len)?;
|
||||
let len = u16::from_le_bytes(len);
|
||||
self.buf.resize(len as usize, 0);
|
||||
self.stream.read_exact(&mut self.buf)?;
|
||||
Ok(de_reply(&String::from_utf8_lossy(&self.buf)))
|
||||
}
|
||||
|
||||
pub fn invoke<T: DeserializeOwned>(&mut self, req: &Request) -> Result<T, Error> {
|
||||
self.send(req).map_err(|_| Error::Delivery)?;
|
||||
self.recv().map_err(|_| Error::Delivery).flatten()
|
||||
}
|
||||
|
||||
pub fn trigger(&mut self, set: String, action: String) -> Result<(), Error> {
|
||||
self.invoke(&Request::Trigger(set, action))
|
||||
}
|
||||
|
||||
pub fn reload_rules(&mut self) -> Result<(), Error> {
|
||||
self.invoke(&Request::ReloadRules)
|
||||
}
|
||||
|
||||
pub fn list_devices(&mut self, when: When) -> Result<Vec<String>, Error> {
|
||||
self.invoke(&Request::ListDevices(when))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
pub mod client;
|
||||
pub mod repr;
|
||||
pub mod server;
|
||||
|
||||
pub const SOCKET_NAME: &str = "verified:org.semilabs.os/lxdeviced";
|
||||
@@ -0,0 +1,64 @@
|
||||
use rule::parser::When;
|
||||
use serde::{Deserialize, Serialize, de::DeserializeOwned};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(tag = "method", content = "params")]
|
||||
pub enum Request {
|
||||
#[serde(rename = "/org.semilabs.os/LxDeviced/Trigger@LXDEVICED_0.1.0")]
|
||||
Trigger(String, String),
|
||||
|
||||
#[serde(rename = "/org.semilabs.os/LxDeviced/ReloadRules@LXDEVICED_0.1.0")]
|
||||
ReloadRules,
|
||||
|
||||
#[serde(rename = "/org.semilabs.os/LxDeviced/ListDevices@LXDEVICED_0.1.0")]
|
||||
ListDevices(When),
|
||||
}
|
||||
|
||||
pub fn ser_reply<T: Serialize>(result: Result<T, Error>) -> String {
|
||||
const FALLBACK: &str = r#"{"error": "/AnyCall/BadReply@ANYCALL_LITE_1"}"#;
|
||||
|
||||
let reply = match result {
|
||||
Ok(x) => Reply::Ok(x),
|
||||
Err(e) => Reply::Err(e),
|
||||
};
|
||||
serde_json::to_string(&reply).unwrap_or_else(|_| FALLBACK.into())
|
||||
}
|
||||
|
||||
pub fn de_reply<T: DeserializeOwned>(s: &str) -> Result<T, Error> {
|
||||
let reply: Reply<T> = serde_json::from_str(s).map_err(|_| Error::BadReply)?;
|
||||
match reply {
|
||||
Reply::Ok(x) => Ok(x),
|
||||
Reply::Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
enum Reply<T> {
|
||||
Err(Error),
|
||||
Ok(T),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, thiserror::Error)]
|
||||
#[serde(tag = "error")]
|
||||
pub enum Error {
|
||||
#[serde(rename = "/AnyCall/BadRequest@ANYCALL_LITE_1")]
|
||||
#[error("Invalid Arguments")]
|
||||
BadRequest,
|
||||
|
||||
#[serde(rename = "/AnyCall/BadReply@ANYCALL_LITE_1")]
|
||||
#[error("Bad Reply")]
|
||||
BadReply,
|
||||
|
||||
#[serde(rename = "/AnyCall/NotImplemented@ANYCALL_LITE_1")]
|
||||
#[error("Not Implemented")]
|
||||
NotImplemented,
|
||||
|
||||
#[serde(rename = "/AnyCall/PermissionDenied@ANYCALL_LITE_1")]
|
||||
#[error("Permission Denied")]
|
||||
PermissionDenied,
|
||||
|
||||
#[serde(rename = "/AnyCall/DeliveryError@ANYCALL_LITE_1")]
|
||||
#[error("Delivery Error")]
|
||||
Delivery,
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
use crate::{
|
||||
SOCKET_NAME,
|
||||
repr::{Error, Request, ser_reply},
|
||||
};
|
||||
use serde::Serialize;
|
||||
use std::os::{linux::net::SocketAddrExt, unix::net::SocketAddr as StdSocketAddr};
|
||||
use tokio::{
|
||||
io::{AsyncReadExt, AsyncWriteExt},
|
||||
net::{UnixListener, UnixStream, unix::SocketAddr},
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Listener(UnixListener);
|
||||
impl Listener {
|
||||
pub fn bind() -> std::io::Result<Self> {
|
||||
let addr = SocketAddr::from(StdSocketAddr::from_abstract_name(SOCKET_NAME)?);
|
||||
UnixListener::bind_addr(&addr).map(Self)
|
||||
}
|
||||
|
||||
pub async fn accept(&self) -> std::io::Result<Connection> {
|
||||
let stream = self.0.accept().await?.0;
|
||||
let cred = stream.peer_cred()?;
|
||||
Ok(Connection {
|
||||
stream,
|
||||
buf: Vec::with_capacity(128),
|
||||
uid: cred.uid(),
|
||||
gid: cred.gid(),
|
||||
pid: cred.pid().unwrap_or_default() as _,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Connection {
|
||||
stream: UnixStream,
|
||||
buf: Vec<u8>,
|
||||
pub uid: u32,
|
||||
pub gid: u32,
|
||||
pub pid: u32,
|
||||
}
|
||||
impl Connection {
|
||||
pub async fn recv(&mut self) -> std::io::Result<Request> {
|
||||
loop {
|
||||
let size = self.stream.read_u16_le().await?;
|
||||
self.buf.resize(size as usize, 0);
|
||||
self.stream.read_exact(&mut self.buf).await?;
|
||||
let Ok(request) = serde_json::from_slice(&self.buf) else {
|
||||
self.send::<()>(Err(Error::BadRequest)).await?;
|
||||
continue;
|
||||
};
|
||||
break Ok(request);
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn send<T: Serialize>(&mut self, reply: Result<T, Error>) -> std::io::Result<()> {
|
||||
let reply = ser_reply(reply);
|
||||
self.stream.write_u16_le(reply.len() as u16).await?;
|
||||
self.stream.write_all(reply.as_bytes()).await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
[package]
|
||||
name = "kobject-uevent"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
netlink-sys = { version = "0.9", features = ["tokio_socket"] }
|
||||
rustc-hash = "2"
|
||||
@@ -0,0 +1,87 @@
|
||||
use netlink_sys::{
|
||||
AsyncSocket, AsyncSocketExt, SocketAddr, TokioSocket, protocols::NETLINK_KOBJECT_UEVENT,
|
||||
};
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
pub const MULTICAST_GROUP_KERNEL: u32 = 1;
|
||||
|
||||
pub struct KobjectUeventSocket(TokioSocket);
|
||||
impl KobjectUeventSocket {
|
||||
pub fn connect(group: u32) -> std::io::Result<Self> {
|
||||
let mut socket = TokioSocket::new(NETLINK_KOBJECT_UEVENT)?;
|
||||
let addr = SocketAddr::new(0, group);
|
||||
socket.socket_mut().bind(&addr)?;
|
||||
Ok(Self(socket))
|
||||
}
|
||||
|
||||
pub async fn recv(&self) -> std::io::Result<KobjectUevent> {
|
||||
let (data, sender) = self.0.recv_from_full().await?;
|
||||
if sender.port_number() != 0 {
|
||||
return Err(std::io::ErrorKind::PermissionDenied.into());
|
||||
}
|
||||
Ok(KobjectUevent::parse(data.as_slice()))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct KobjectUevent(FxHashMap<String, String>);
|
||||
impl KobjectUevent {
|
||||
pub fn inner(&self) -> &FxHashMap<String, String> {
|
||||
&self.0
|
||||
}
|
||||
pub fn parse(s: &[u8]) -> Self {
|
||||
let mut map = FxHashMap::default();
|
||||
for kvp in s.split(|x| *x == b'\0') {
|
||||
let Some(sep) = kvp.iter().position(|x| *x == b'=') else {
|
||||
continue;
|
||||
};
|
||||
let (key, val) = kvp.split_at(sep);
|
||||
let val = &val[1..];
|
||||
map.insert(
|
||||
String::from_utf8_lossy_owned(key.into()),
|
||||
String::from_utf8_lossy_owned(val.into()),
|
||||
);
|
||||
}
|
||||
Self(map)
|
||||
}
|
||||
|
||||
pub fn action(&self) -> Option<&str> {
|
||||
self.0.get("ACTION").map(String::as_str)
|
||||
}
|
||||
|
||||
pub fn devpath(&self) -> Option<&str> {
|
||||
self.0.get("DEVPATH").map(String::as_str)
|
||||
}
|
||||
|
||||
pub fn subsystem(&self) -> Option<&str> {
|
||||
self.0.get("SUBSYSTEM").map(String::as_str)
|
||||
}
|
||||
|
||||
pub fn synth_uuid(&self) -> Option<&str> {
|
||||
self.0.get("SYNTH_UUID").map(String::as_str)
|
||||
}
|
||||
|
||||
pub fn major(&self) -> Option<u32> {
|
||||
let major = self.0.get("MAJOR")?;
|
||||
major.parse().ok()
|
||||
}
|
||||
|
||||
pub fn minor(&self) -> Option<u32> {
|
||||
let minor = self.0.get("MINOR")?;
|
||||
minor.parse().ok()
|
||||
}
|
||||
|
||||
pub fn devname(&self) -> Option<&str> {
|
||||
self.0.get("DEVNAME").map(String::as_str)
|
||||
}
|
||||
|
||||
pub fn devmode(&self) -> Option<u16> {
|
||||
let devmode = self.0.get("DEVMODE")?;
|
||||
let devmode = devmode.strip_prefix("0")?;
|
||||
u16::from_str_radix(devmode, 8).ok()
|
||||
}
|
||||
|
||||
pub fn seqnum(&self) -> Option<&str> {
|
||||
self.0.get("SEQNUM").map(String::as_str)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
[package]
|
||||
name = "rule"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
libc = "0.2"
|
||||
regex = "1"
|
||||
ron = "0.12"
|
||||
rustc-hash = "2"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
thiserror = "2"
|
||||
tokio = { version = "1", features = ["process", "time"] }
|
||||
tracing = "0.1"
|
||||
@@ -0,0 +1,359 @@
|
||||
use crate::{
|
||||
Error,
|
||||
parser::{Action, OnError, Rule, RuleFile, Str, When},
|
||||
};
|
||||
use regex::Regex;
|
||||
use rustc_hash::FxHashMap;
|
||||
use std::{
|
||||
path::Path,
|
||||
process::{ExitStatus, Stdio},
|
||||
sync::{Arc, RwLock},
|
||||
time::Duration,
|
||||
};
|
||||
use tokio::{io::AsyncReadExt, process::Child};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RuleSet(Arc<Vec<RuleFile>>);
|
||||
impl RuleSet {
|
||||
pub fn empty() -> Self {
|
||||
Self(Arc::default())
|
||||
}
|
||||
|
||||
pub fn open_dirs(dirs: impl Iterator<Item = impl AsRef<Path>>) -> Self {
|
||||
let mut rule_files = Vec::with_capacity(32);
|
||||
for dir in dirs {
|
||||
let Ok(read_dir) = std::fs::read_dir(dir.as_ref()) else {
|
||||
tracing::debug!(
|
||||
"Did not read directory \"{}\" for rule files.",
|
||||
dir.as_ref().display()
|
||||
);
|
||||
continue;
|
||||
};
|
||||
for file in read_dir {
|
||||
let Ok(file) = file else {
|
||||
tracing::warn!(
|
||||
"Error while reading directory \"{}\" for rule files.",
|
||||
dir.as_ref().display()
|
||||
);
|
||||
break;
|
||||
};
|
||||
if file.file_name().as_encoded_bytes().starts_with(b".") {
|
||||
continue;
|
||||
}
|
||||
let rule_file = match RuleFile::parse(file.path()) {
|
||||
Ok(x) => x,
|
||||
Err(e) => {
|
||||
tracing::warn!(
|
||||
"Failed to parse rule file \"{}\": {}",
|
||||
file.path().display(),
|
||||
e,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
tracing::debug!("Activating rule file \"{}\"", file.path().display());
|
||||
rule_files.push(rule_file);
|
||||
}
|
||||
}
|
||||
Self(Arc::new(rule_files))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Engine {
|
||||
properties: RwLock<FxHashMap<String, String>>,
|
||||
command_policy: CommandPolicy,
|
||||
}
|
||||
impl Engine {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
properties: Default::default(),
|
||||
command_policy: CommandPolicy::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_property(&self, key: &str) -> Option<String> {
|
||||
self.properties.read().unwrap().get(key).cloned()
|
||||
}
|
||||
|
||||
pub fn set_property(&self, key: String, val: String) {
|
||||
self.properties.write().unwrap().insert(key, val);
|
||||
}
|
||||
|
||||
pub fn remove_property(&self, key: &str) {
|
||||
self.properties.write().unwrap().remove(key);
|
||||
}
|
||||
|
||||
pub fn get_list(&self, key: &str) -> Vec<String> {
|
||||
self.get_property(key)
|
||||
.unwrap_or_default()
|
||||
.split('\0')
|
||||
.filter(|x| !x.is_empty())
|
||||
.map(Into::into)
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn list_add(&self, key: String, val: &str) {
|
||||
let orig = self.get_property(&key).unwrap_or_default();
|
||||
self.set_property(key, format!("{orig}{val}\0"));
|
||||
}
|
||||
|
||||
pub async fn exec(&self, rule_set: &RuleSet) {
|
||||
for rule_file in rule_set.0.iter() {
|
||||
for rule in rule_file.rules.iter() {
|
||||
if let Err(err) = self.exec_rule(rule).await {
|
||||
if !matches!(rule.on_error, OnError::Continue) {
|
||||
tracing::warn!("Rule \"{}\" failed: {err}", rule_file.path.display());
|
||||
}
|
||||
match rule.on_error {
|
||||
OnError::Continue => {}
|
||||
OnError::Log => {}
|
||||
OnError::StopFile => break,
|
||||
OnError::StopAll => return,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn exec_rule(&self, rule: &Rule) -> Result<(), Error> {
|
||||
if self.exec_condition(&rule.when)? {
|
||||
for action in rule.actions.iter() {
|
||||
self.exec_action(&action).await?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn exec_condition(&self, when: &When) -> Result<bool, Error> {
|
||||
match when {
|
||||
When::All(children) => {
|
||||
let mut result = true;
|
||||
for child in children {
|
||||
result &= self.exec_condition(child)?;
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
When::Any(children) => {
|
||||
let mut result = false;
|
||||
for child in children {
|
||||
result |= self.exec_condition(child)?;
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
When::Not(child) => self.exec_condition(child).map(|x| !x),
|
||||
When::True => Ok(true),
|
||||
When::False => Ok(false),
|
||||
When::Match(key, regex) => {
|
||||
let key = self.bake_str(key)?;
|
||||
let regex = self.bake_str(regex)?;
|
||||
let regex = format!("^{regex}$");
|
||||
let regex = Regex::new(®ex).map_err(Error::Regex)?;
|
||||
let Some(val) = self.get_property(&key) else {
|
||||
return Ok(false);
|
||||
};
|
||||
Ok(regex.is_match(&val))
|
||||
}
|
||||
When::PartMatch(key, regex) => {
|
||||
let key = self.bake_str(key)?;
|
||||
let regex = self.bake_str(regex)?;
|
||||
let regex = Regex::new(®ex).map_err(Error::Regex)?;
|
||||
let Some(val) = self.get_property(&key) else {
|
||||
return Ok(false);
|
||||
};
|
||||
Ok(regex.is_match(&val))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn exec_action(&self, action: &Action) -> Result<(), Error> {
|
||||
match action {
|
||||
Action::Set(key, val) => {
|
||||
let key = self.bake_str(key)?;
|
||||
let val = self.bake_str(val)?;
|
||||
self.set_property(key, val);
|
||||
}
|
||||
Action::Add(key, val) => {
|
||||
let key = self.bake_str(key)?;
|
||||
let val = self.bake_str(val)?;
|
||||
self.list_add(key, &val);
|
||||
}
|
||||
Action::Unset(key) => {
|
||||
self.remove_property(&self.bake_str(key)?);
|
||||
}
|
||||
Action::Command(cmd) => {
|
||||
let cmd = self.bake_str(cmd)?;
|
||||
self.command_policy.run(&cmd).await?;
|
||||
}
|
||||
Action::UseProvider(key, cmd) => {
|
||||
let key = self.bake_str(key)?;
|
||||
let cmd = self.bake_str(cmd)?;
|
||||
let val = self.command_policy.run_provider(&cmd).await?;
|
||||
self.set_property(key, val);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn bake_str(&self, s: &Str) -> Result<String, Error> {
|
||||
match s {
|
||||
Str::Direct(x) => Ok(x.into()),
|
||||
Str::Format(template) => {
|
||||
let mut result = String::with_capacity(template.len());
|
||||
let mut current_property: Option<String> = None;
|
||||
let mut right_bracket = false;
|
||||
for ch in template.chars() {
|
||||
if right_bracket {
|
||||
if ch == '}' {
|
||||
right_bracket = false;
|
||||
result.push('}');
|
||||
} else {
|
||||
return Err(Error::InvalidStringTemplate);
|
||||
}
|
||||
} else if let Some(property) = &mut current_property {
|
||||
if property.is_empty() && ch == '{' {
|
||||
result.push('{');
|
||||
current_property = None;
|
||||
} else if ch == '}' {
|
||||
let value = self
|
||||
.get_property(property)
|
||||
.ok_or_else(|| Error::PropertyNotFound(property.clone()))?;
|
||||
result.push_str(&value);
|
||||
current_property = None;
|
||||
} else {
|
||||
property.push(ch);
|
||||
}
|
||||
} else if ch == '{' {
|
||||
current_property = Some(String::with_capacity(16));
|
||||
} else if ch == '}' {
|
||||
right_bracket = true;
|
||||
} else {
|
||||
result.push(ch);
|
||||
}
|
||||
}
|
||||
if current_property.is_some() || right_bracket {
|
||||
return Err(Error::InvalidStringTemplate);
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Default for Engine {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CommandPolicy {
|
||||
prefix: Vec<String>,
|
||||
pub timeout: Duration,
|
||||
pub timeout_behavior: CommandTimeoutBehavior,
|
||||
pub info_limit: usize,
|
||||
}
|
||||
impl CommandPolicy {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
prefix: vec![String::from("/bin/sh"), String::from("-c")],
|
||||
timeout: Duration::from_secs(30),
|
||||
timeout_behavior: CommandTimeoutBehavior::Ignore,
|
||||
info_limit: 1024,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn run(&self, cmd: &str) -> Result<(), Error> {
|
||||
let mut child = self
|
||||
._command(cmd)
|
||||
.spawn()
|
||||
.map_err(|e| Error::Command(cmd.into(), e.to_string()))?;
|
||||
let timeout_wait = tokio::time::timeout(self.timeout, child.wait()).await;
|
||||
match timeout_wait {
|
||||
Ok(Ok(wait)) => self._wait_success(cmd, wait),
|
||||
Ok(Err(_)) => Err(Error::Command(cmd.into(), "wait() failure".into())),
|
||||
Err(_) => self._timeout(cmd, child),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn run_provider(&self, cmd: &str) -> Result<String, Error> {
|
||||
let mut child = self
|
||||
._command(cmd)
|
||||
.stdout(Stdio::piped())
|
||||
.spawn()
|
||||
.map_err(|e| Error::Command(cmd.into(), e.to_string()))?;
|
||||
let mut stdout = String::with_capacity(128);
|
||||
let timeout_wait = tokio::time::timeout(self.timeout, async {
|
||||
child
|
||||
.stdout
|
||||
.take()
|
||||
.unwrap()
|
||||
.take(self.info_limit as _)
|
||||
.read_to_string(&mut stdout)
|
||||
.await?;
|
||||
child.wait().await
|
||||
})
|
||||
.await;
|
||||
match timeout_wait {
|
||||
Ok(Ok(wait)) => {
|
||||
self._wait_success(cmd, wait)?;
|
||||
Ok(stdout.trim().into())
|
||||
}
|
||||
Ok(Err(_)) => Err(Error::Command(cmd.into(), "wait() failure".into())),
|
||||
Err(_) => {
|
||||
self._timeout(cmd, child)?;
|
||||
unreachable!();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn _wait_success(&self, cmd: &str, wait: ExitStatus) -> Result<(), Error> {
|
||||
if wait.success() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::Command(
|
||||
cmd.into(),
|
||||
format!(
|
||||
"process exited with code {}",
|
||||
wait.code().unwrap_or(i32::MIN),
|
||||
),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn _command(&self, cmd: &str) -> tokio::process::Command {
|
||||
let mut command = tokio::process::Command::new(&self.prefix[0]);
|
||||
command.args(&self.prefix[1..]).arg(cmd);
|
||||
command
|
||||
}
|
||||
|
||||
fn _timeout(&self, cmd: &str, mut child: Child) -> Result<(), Error> {
|
||||
match self.timeout_behavior {
|
||||
CommandTimeoutBehavior::Ignore => {}
|
||||
CommandTimeoutBehavior::SoftKill => unsafe {
|
||||
if let Some(pid) = child.id() {
|
||||
libc::kill(pid as _, libc::SIGTERM);
|
||||
}
|
||||
},
|
||||
CommandTimeoutBehavior::HardKill => _ = child.start_kill(),
|
||||
};
|
||||
Err(Error::Command(cmd.into(), "Timed out".into()))
|
||||
}
|
||||
}
|
||||
impl Default for CommandPolicy {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
pub enum CommandTimeoutBehavior {
|
||||
#[default]
|
||||
Ignore,
|
||||
SoftKill,
|
||||
HardKill,
|
||||
}
|
||||
impl CommandTimeoutBehavior {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum Error {
|
||||
#[error("{0}")]
|
||||
Read(std::io::Error),
|
||||
|
||||
#[error("{0}")]
|
||||
Parse(ron::de::SpannedError),
|
||||
|
||||
#[error("Property \"{0}\" not found")]
|
||||
PropertyNotFound(String),
|
||||
|
||||
#[error("Invalid format string template")]
|
||||
InvalidStringTemplate,
|
||||
|
||||
#[error("Regex error: {0}")]
|
||||
Regex(regex::Error),
|
||||
|
||||
#[error("Failed to execute command \"{0}\": {1}")]
|
||||
Command(String, String),
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
pub mod engine;
|
||||
pub mod error;
|
||||
pub mod parser;
|
||||
|
||||
pub use error::Error;
|
||||
@@ -0,0 +1,124 @@
|
||||
use crate::Error;
|
||||
use serde::{Deserialize, Serialize, ser::SerializeTupleVariant};
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RuleFile {
|
||||
pub path: PathBuf,
|
||||
pub rules: Vec<Rule>,
|
||||
}
|
||||
impl RuleFile {
|
||||
pub fn parse(path: impl Into<PathBuf>) -> Result<Self, Error> {
|
||||
let path = path.into();
|
||||
let s = std::fs::read_to_string(&path).map_err(Error::Read)?;
|
||||
let rules = ron::from_str::<Vec<Rule>>(&s).map_err(Error::Parse)?;
|
||||
Ok(Self { path, rules })
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Rule {
|
||||
pub when: When,
|
||||
|
||||
#[serde(alias = "do")]
|
||||
pub actions: Vec<Action>,
|
||||
|
||||
#[serde(default)]
|
||||
pub on_error: OnError,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum When {
|
||||
All(Vec<When>),
|
||||
Any(Vec<When>),
|
||||
Not(Box<When>),
|
||||
True,
|
||||
False,
|
||||
Match(Str, Str),
|
||||
PartMatch(Str, Str),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum Action {
|
||||
Command(Str),
|
||||
Add(Str, Str),
|
||||
Set(Str, Str),
|
||||
Unset(Str),
|
||||
UseProvider(Str, Str),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
|
||||
pub enum OnError {
|
||||
/// Continue the workflow, without logging messages.
|
||||
Continue,
|
||||
|
||||
/// Continue the workflow, but outputs a warning log.
|
||||
#[default]
|
||||
Log,
|
||||
|
||||
/// Stop the file.
|
||||
StopFile,
|
||||
|
||||
/// Stop the workflow.
|
||||
StopAll,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Str {
|
||||
Direct(String),
|
||||
Format(String),
|
||||
}
|
||||
impl Serialize for Str {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
match self {
|
||||
Self::Direct(s) => s.serialize(serializer),
|
||||
Self::Format(s) => {
|
||||
let mut ser = serializer.serialize_tuple_variant("Str", 1, "f", 1)?;
|
||||
ser.serialize_field(s)?;
|
||||
ser.end()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<'de> Deserialize<'de> for Str {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
struct StrVisitor;
|
||||
impl<'de> serde::de::Visitor<'de> for StrVisitor {
|
||||
type Value = Str;
|
||||
|
||||
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
formatter.write_str("string or array with a single string")
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
Ok(Str::Direct(v.to_string()))
|
||||
}
|
||||
|
||||
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
|
||||
where
|
||||
A: serde::de::SeqAccess<'de>,
|
||||
{
|
||||
let value = seq
|
||||
.next_element::<String>()?
|
||||
.ok_or_else(|| serde::de::Error::invalid_length(0, &self))?;
|
||||
|
||||
if seq.next_element::<String>()?.is_some() {
|
||||
return Err(serde::de::Error::invalid_length(2, &self));
|
||||
}
|
||||
|
||||
Ok(Str::Format(value))
|
||||
}
|
||||
}
|
||||
|
||||
deserializer.deserialize_any(StrVisitor)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
[service]
|
||||
display-name = "Linux Device Daemon"
|
||||
description = "Userspace daemon for dynamically managing Linux device files and their properties."
|
||||
|
||||
[exec]
|
||||
start = "lxdeviced"
|
||||
|
||||
[env]
|
||||
working_dir = "/"
|
||||
clear_vars = true
|
||||
|
||||
[retry]
|
||||
max-attempts = -1
|
||||
|
||||
[env.vars]
|
||||
PATH = "/usr/bin"
|
||||
LXDEVICED_HAS_DEVTMPFS = "1"
|
||||
LXDEVICED_DEV_ROOT = "/dev"
|
||||
LXDEVICED_SYSFS_ROOT = "/sys"
|
||||
@@ -0,0 +1,66 @@
|
||||
[
|
||||
(
|
||||
when: Match("DEVPATH", "/devices/virtual/mem/null"),
|
||||
do: [
|
||||
Set("MODE", "0o666"),
|
||||
Set("OWNER", "0"),
|
||||
Set("GROUP", "0"),
|
||||
],
|
||||
),
|
||||
(
|
||||
when: Match("DEVPATH", "/devices/virtual/mem/zero"),
|
||||
do: [
|
||||
Set("MODE", "0o666"),
|
||||
Set("OWNER", "0"),
|
||||
Set("GROUP", "0"),
|
||||
],
|
||||
),
|
||||
(
|
||||
when: Match("DEVPATH", "/devices/virtual/mem/urandom"),
|
||||
do: [
|
||||
Set("MODE", "0o666"),
|
||||
Set("OWNER", "0"),
|
||||
Set("GROUP", "0"),
|
||||
],
|
||||
),
|
||||
(
|
||||
when: Match("DEVPATH", "/devices/virtual/mem/random"),
|
||||
do: [
|
||||
Set("MODE", "0o666"),
|
||||
Set("OWNER", "0"),
|
||||
Set("GROUP", "0"),
|
||||
],
|
||||
),
|
||||
(
|
||||
when: Match("DEVPATH", "/devices/virtual/mem/full"),
|
||||
do: [
|
||||
Set("MODE", "0o666"),
|
||||
Set("OWNER", "0"),
|
||||
Set("GROUP", "0"),
|
||||
],
|
||||
),
|
||||
(
|
||||
when: Match("DEVPATH", "/devices/virtual/mem/mem"),
|
||||
do: [
|
||||
Set("MODE", "0o600"),
|
||||
Set("OWNER", "0"),
|
||||
Set("GROUP", "0"),
|
||||
],
|
||||
),
|
||||
(
|
||||
when: Match("DEVPATH", "/devices/virtual/mem/port"),
|
||||
do: [
|
||||
Set("MODE", "0o600"),
|
||||
Set("OWNER", "0"),
|
||||
Set("GROUP", "0"),
|
||||
],
|
||||
),
|
||||
(
|
||||
when: Match("DEVPATH", "/devices/virtual/mem/kmsg"),
|
||||
do: [
|
||||
Set("MODE", "0o600"),
|
||||
Set("OWNER", "0"),
|
||||
Set("GROUP", "0"),
|
||||
],
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,8 @@
|
||||
[
|
||||
(
|
||||
when: Match("MODALIAS", ".*"),
|
||||
do: [
|
||||
Command(f("modprobe {MODALIAS}")),
|
||||
],
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,17 @@
|
||||
[
|
||||
(
|
||||
when: Match("SUBSYSTEM", "tty"),
|
||||
do: [
|
||||
Set("MODE", "0o600"),
|
||||
]
|
||||
),
|
||||
(
|
||||
when: All([
|
||||
Match("SUBSYSTEM", "tty"),
|
||||
Match("DEVNAME", "tty"),
|
||||
]),
|
||||
do: [
|
||||
Set("MODE", "0o666"),
|
||||
]
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,54 @@
|
||||
[
|
||||
// Register information providers that knows block devices
|
||||
(
|
||||
when: All([
|
||||
Match("ACTION", "add"),
|
||||
Match("SUBSYSTEM", "block"),
|
||||
Match("DEVNODE", ".*"),
|
||||
]),
|
||||
do: [
|
||||
UseProvider("FSUUID", f("blkid -o value -s UUID {DEVNODE}")),
|
||||
],
|
||||
on_error: Continue,
|
||||
),
|
||||
(
|
||||
when: Match("FSUUID", ""),
|
||||
do: [Unset("FSUUID")],
|
||||
),
|
||||
|
||||
// Create link aliases for block devices
|
||||
(
|
||||
when: All([
|
||||
Match("ACTION", "add"),
|
||||
Match("SUBSYSTEM", "block"),
|
||||
Match("PARTUUID", ".+"),
|
||||
]),
|
||||
do: [Add("SYMLINKS", f("{DEVROOT}/disk/by-partuuid/{PARTUUID}"))],
|
||||
),
|
||||
(
|
||||
when: All([
|
||||
Match("ACTION", "add"),
|
||||
Match("SUBSYSTEM", "block"),
|
||||
Match("DISKSEQ", ".+"),
|
||||
Match("DEVTYPE", "disk"),
|
||||
]),
|
||||
do: [Add("SYMLINKS", f("{DEVROOT}/disk/by-diskseq/{DISKSEQ}"))],
|
||||
),
|
||||
(
|
||||
when: All([
|
||||
Match("ACTION", "add"),
|
||||
Match("SUBSYSTEM", "block"),
|
||||
Match("FSUUID", ".+"),
|
||||
]),
|
||||
do: [Add("SYMLINKS", f("{DEVROOT}/disk/by-uuid/{FSUUID}"))],
|
||||
),
|
||||
(
|
||||
when: All([
|
||||
Match("ACTION", "add"),
|
||||
Match("SUBSYSTEM", "block"),
|
||||
Match("MAJOR", ".+"),
|
||||
Match("MINOR", ".+"),
|
||||
]),
|
||||
do: [Add("SYMLINKS", f("{DEVROOT}/block/{MAJOR}:{MINOR}"))],
|
||||
),
|
||||
]
|
||||
Reference in New Issue
Block a user