@@ -0,0 +1,8 @@
|
||||
[package]
|
||||
name = "db"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
rustc-hash = "2"
|
||||
serde_json = "1"
|
||||
@@ -0,0 +1,115 @@
|
||||
use rustc_hash::FxHashMap;
|
||||
use std::{
|
||||
path::PathBuf,
|
||||
sync::{Arc, RwLock},
|
||||
};
|
||||
|
||||
pub const PERSIST_PATH: &str = "/run/lxdeviced.json";
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Db {
|
||||
path: Option<PathBuf>,
|
||||
map: RwLock<FxHashMap<String, String>>,
|
||||
}
|
||||
impl Db {
|
||||
pub fn open_in_memory() -> Arc<Self> {
|
||||
Arc::new(Self {
|
||||
path: None,
|
||||
map: Default::default(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn open(path: impl Into<PathBuf>) -> Arc<Self> {
|
||||
let path = path.into();
|
||||
let text = std::fs::read_to_string(&path).unwrap_or_default();
|
||||
let map = serde_json::from_str(&text).unwrap_or_default();
|
||||
Arc::new(Self {
|
||||
path: Some(path),
|
||||
map: RwLock::new(map),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn section(self: &Arc<Self>, name: impl Into<String>) -> DbSection {
|
||||
DbSection {
|
||||
name: name.into(),
|
||||
db: self.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn save(&self) {
|
||||
if let Some(path) = &self.path {
|
||||
let text = serde_json::to_string(&*self.map.read().unwrap()).unwrap();
|
||||
_ = std::fs::write(&path, text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DbSection {
|
||||
name: String,
|
||||
db: Arc<Db>,
|
||||
}
|
||||
impl DbSection {
|
||||
pub fn db(&self) -> &Arc<Db> {
|
||||
&self.db
|
||||
}
|
||||
|
||||
pub fn clear(&self) {
|
||||
let mut map = self.db.map.write().unwrap();
|
||||
let mut keys = Vec::new();
|
||||
let prefix = self.real_key_name("");
|
||||
for key in map.keys() {
|
||||
if key.starts_with(&prefix) {
|
||||
keys.push(key.clone());
|
||||
}
|
||||
}
|
||||
for key in keys {
|
||||
map.remove(&key);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert(&self, key: &str, val: String) -> Option<String> {
|
||||
let mut map = self.db.map.write().unwrap();
|
||||
map.insert(self.real_key_name(key), val)
|
||||
}
|
||||
|
||||
pub fn get(&self, key: &str) -> Option<String> {
|
||||
self.db
|
||||
.map
|
||||
.read()
|
||||
.unwrap()
|
||||
.get(&self.real_key_name(key))
|
||||
.cloned()
|
||||
}
|
||||
|
||||
pub fn remove(&self, key: &str) -> Option<String> {
|
||||
self.db
|
||||
.map
|
||||
.write()
|
||||
.unwrap()
|
||||
.remove(&self.real_key_name(key))
|
||||
}
|
||||
|
||||
pub fn restore(&self, dump: FxHashMap<String, String>) {
|
||||
self.clear();
|
||||
for (k, v) in dump {
|
||||
self.insert(&k, v);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dump(&self) -> FxHashMap<String, String> {
|
||||
let mut result = FxHashMap::default();
|
||||
let prefix = self.real_key_name("");
|
||||
let total = self.db.map.read().unwrap();
|
||||
for (key, val) in total.iter() {
|
||||
if let Some(realkey) = key.strip_prefix(&prefix) {
|
||||
result.insert(realkey.into(), val.into());
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
fn real_key_name(&self, name: &str) -> String {
|
||||
format!("{}\0{}", self.name, name)
|
||||
}
|
||||
}
|
||||
@@ -9,3 +9,4 @@ serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
thiserror = "2"
|
||||
tokio = { version = "1", features = ["net", "io-util"] }
|
||||
rustc-hash = "2"
|
||||
|
||||
@@ -3,6 +3,7 @@ use crate::{
|
||||
repr::{Error, Request, de_reply},
|
||||
};
|
||||
use rule::parser::When;
|
||||
use rustc_hash::FxHashMap;
|
||||
use serde::de::DeserializeOwned;
|
||||
use std::{
|
||||
io::{Read, Write},
|
||||
@@ -26,6 +27,10 @@ impl Client {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn inner(&self) -> &UnixStream {
|
||||
&self.stream
|
||||
}
|
||||
|
||||
pub fn send(&mut self, req: &Request) -> std::io::Result<()> {
|
||||
self.buf.clear();
|
||||
serde_json::to_writer(&mut self.buf, req).unwrap();
|
||||
@@ -61,6 +66,10 @@ impl Client {
|
||||
self.invoke(&Request::ListDevices(when))
|
||||
}
|
||||
|
||||
pub fn get_device(&mut self, name: String) -> Result<FxHashMap<String, String>, Error> {
|
||||
self.invoke(&Request::GetDevice(name))
|
||||
}
|
||||
|
||||
pub fn wait_for_idle(&mut self) -> Result<(), Error> {
|
||||
self.invoke(&Request::WaitForIdle)
|
||||
}
|
||||
|
||||
@@ -15,6 +15,12 @@ pub enum Request {
|
||||
|
||||
#[serde(rename = "/org.semilabs.os/LxDeviced/WaitForIdle@LXDEVICED_0.1.0")]
|
||||
WaitForIdle,
|
||||
|
||||
#[serde(rename = "/org.semilabs.os/LxDeviced/WaitForEvent@LXDEVICED_0.1.0")]
|
||||
WaitForEvent,
|
||||
|
||||
#[serde(rename = "/org.semilabs.os/LxDeviced/GetDevice@LXDEVICED_0.1.0")]
|
||||
GetDevice(String),
|
||||
}
|
||||
|
||||
pub fn ser_reply<T: Serialize>(result: Result<T, Error>) -> String {
|
||||
|
||||
@@ -12,3 +12,4 @@ serde = { version = "1", features = ["derive"] }
|
||||
thiserror = "2"
|
||||
tokio = { version = "1", features = ["process", "time"] }
|
||||
tracing = "0.1"
|
||||
db = { path = "../db" }
|
||||
|
||||
+32
-49
@@ -2,12 +2,12 @@ use crate::{
|
||||
Error,
|
||||
parser::{Action, OnError, Rule, RuleFile, Str, When},
|
||||
};
|
||||
use db::DbSection;
|
||||
use regex::Regex;
|
||||
use rustc_hash::FxHashMap;
|
||||
use std::{
|
||||
path::Path,
|
||||
process::{ExitStatus, Stdio},
|
||||
sync::{Arc, RwLock},
|
||||
sync::Arc,
|
||||
time::Duration,
|
||||
};
|
||||
use tokio::{io::AsyncReadExt, process::Child};
|
||||
@@ -29,29 +29,25 @@ impl RuleSet {
|
||||
);
|
||||
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".") {
|
||||
let mut files: Vec<_> = read_dir.filter_map(|x| x.ok()).map(|x| x.path()).collect();
|
||||
files.sort_unstable();
|
||||
for file in files {
|
||||
if file
|
||||
.file_name()
|
||||
.unwrap()
|
||||
.as_encoded_bytes()
|
||||
.starts_with(b".")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
let rule_file = match RuleFile::parse(file.path()) {
|
||||
let rule_file = match RuleFile::parse(&file) {
|
||||
Ok(x) => x,
|
||||
Err(e) => {
|
||||
tracing::warn!(
|
||||
"Failed to parse rule file \"{}\": {}",
|
||||
file.path().display(),
|
||||
e,
|
||||
);
|
||||
tracing::warn!("Failed to parse rule file \"{}\": {}", file.display(), e,);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
tracing::debug!("Activating rule file \"{}\"", file.path().display());
|
||||
tracing::debug!("Activating rule file \"{}\"", file.display());
|
||||
rule_files.push(rule_file);
|
||||
}
|
||||
}
|
||||
@@ -61,31 +57,20 @@ impl RuleSet {
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Engine {
|
||||
properties: RwLock<FxHashMap<String, String>>,
|
||||
pub properties: DbSection,
|
||||
command_policy: CommandPolicy,
|
||||
}
|
||||
impl Engine {
|
||||
pub fn new() -> Self {
|
||||
pub fn with_properties(properties: DbSection) -> Self {
|
||||
Self {
|
||||
properties: Default::default(),
|
||||
properties,
|
||||
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)
|
||||
self.properties
|
||||
.get(key)
|
||||
.unwrap_or_default()
|
||||
.split('\0')
|
||||
.filter(|x| !x.is_empty())
|
||||
@@ -93,13 +78,15 @@ impl Engine {
|
||||
.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 fn list_add(&self, key: &str, val: &str) {
|
||||
let orig = self.properties.get(&key).unwrap_or_default();
|
||||
self.properties.insert(&key, format!("{orig}{val}\0"));
|
||||
}
|
||||
|
||||
pub async fn exec(&self, rule_set: &RuleSet) {
|
||||
for rule_file in rule_set.0.iter() {
|
||||
self.properties
|
||||
.insert("RULEFILE", rule_file.path.display().to_string());
|
||||
for rule in rule_file.rules.iter() {
|
||||
if let Err(err) = self.exec_rule(rule).await {
|
||||
if !matches!(rule.on_error, OnError::Continue) {
|
||||
@@ -149,7 +136,7 @@ impl Engine {
|
||||
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 {
|
||||
let Some(val) = self.properties.get(&key) else {
|
||||
return Ok(false);
|
||||
};
|
||||
Ok(regex.is_match(&val))
|
||||
@@ -158,7 +145,7 @@ impl Engine {
|
||||
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 {
|
||||
let Some(val) = self.properties.get(&key) else {
|
||||
return Ok(false);
|
||||
};
|
||||
Ok(regex.is_match(&val))
|
||||
@@ -171,15 +158,15 @@ impl Engine {
|
||||
Action::Set(key, val) => {
|
||||
let key = self.bake_str(key)?;
|
||||
let val = self.bake_str(val)?;
|
||||
self.set_property(key, val);
|
||||
self.properties.insert(&key, val);
|
||||
}
|
||||
Action::Add(key, val) => {
|
||||
let key = self.bake_str(key)?;
|
||||
let val = self.bake_str(val)?;
|
||||
self.list_add(key, &val);
|
||||
self.list_add(&key, &val);
|
||||
}
|
||||
Action::Unset(key) => {
|
||||
self.remove_property(&self.bake_str(key)?);
|
||||
self.properties.remove(&self.bake_str(key)?);
|
||||
}
|
||||
Action::Command(cmd) => {
|
||||
let cmd = self.bake_str(cmd)?;
|
||||
@@ -189,7 +176,7 @@ impl Engine {
|
||||
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);
|
||||
self.properties.insert(&key, val);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
@@ -216,7 +203,8 @@ impl Engine {
|
||||
current_property = None;
|
||||
} else if ch == '}' {
|
||||
let value = self
|
||||
.get_property(property)
|
||||
.properties
|
||||
.get(property)
|
||||
.ok_or_else(|| Error::PropertyNotFound(property.clone()))?;
|
||||
result.push_str(&value);
|
||||
current_property = None;
|
||||
@@ -239,11 +227,6 @@ impl Engine {
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Default for Engine {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CommandPolicy {
|
||||
|
||||
Reference in New Issue
Block a user