Initial commit

Signed-off-by: sisungo <[email protected]>
This commit is contained in:
2026-09-01 13:01:13 +00:00
commit 5163bdc367
34 changed files with 2625 additions and 0 deletions
+2
View File
@@ -0,0 +1,2 @@
/target
/Cargo.lock
+3
View File
@@ -0,0 +1,3 @@
[workspace]
members = ["cli", "daemon", "libcompat/udev", "libs/ipc", "libs/kobject-uevent", "libs/rule"]
resolver = "3"
+24
View File
@@ -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/>
+9
View File
@@ -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" }
+18
View File
@@ -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}");
}
}
+17
View File
@@ -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(())
}
+17
View File
@@ -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"] }
+209
View File
@@ -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()
}
+67
View File
@@ -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())
})
}
+25
View File
@@ -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;
}
+42
View File
@@ -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()
}
+62
View File
@@ -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())
}
+22
View File
@@ -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(())
}
+88
View File
@@ -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)
}
+10
View File
@@ -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" }
+124
View File
@@ -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
+901
View File
@@ -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,
})
})
}
+11
View File
@@ -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"] }
+63
View File
@@ -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))
}
}
+5
View File
@@ -0,0 +1,5 @@
pub mod client;
pub mod repr;
pub mod server;
pub const SOCKET_NAME: &str = "verified:org.semilabs.os/lxdeviced";
+64
View File
@@ -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,
}
+61
View File
@@ -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(())
}
}
+8
View File
@@ -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"
+87
View File
@@ -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)
}
}
+14
View File
@@ -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"
+359
View File
@@ -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(&regex).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(&regex).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()
}
}
+20
View File
@@ -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),
}
+5
View File
@@ -0,0 +1,5 @@
pub mod engine;
pub mod error;
pub mod parser;
pub use error::Error;
+124
View File
@@ -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)
}
}
+19
View File
@@ -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"
+66
View File
@@ -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"),
],
),
]
+8
View File
@@ -0,0 +1,8 @@
[
(
when: Match("MODALIAS", ".*"),
do: [
Command(f("modprobe {MODALIAS}")),
],
),
]
+17
View File
@@ -0,0 +1,17 @@
[
(
when: Match("SUBSYSTEM", "tty"),
do: [
Set("MODE", "0o600"),
]
),
(
when: All([
Match("SUBSYSTEM", "tty"),
Match("DEVNAME", "tty"),
]),
do: [
Set("MODE", "0o666"),
]
),
]
+54
View File
@@ -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}"))],
),
]