NotifyingProducer {
+ pub fn new(base: P, notifier: N) -> Self {
+ Self { base, notifier }
+ }
+}
+
+impl Observer for NotifyingProducer {
+ type Item = P::Item;
+
+ fn capacity(&self) -> std::num::NonZeroUsize {
+ self.base.capacity()
+ }
+
+ fn read_index(&self) -> usize {
+ self.base.read_index()
+ }
+
+ fn write_index(&self) -> usize {
+ self.base.write_index()
+ }
+
+ unsafe fn unsafe_slices(&self, start: usize, end: usize) -> (&[std::mem::MaybeUninit], &[std::mem::MaybeUninit]) {
+ unsafe { self.base.unsafe_slices(start, end) }
+ }
+
+ unsafe fn unsafe_slices_mut(&self, start: usize, end: usize) -> (&mut [std::mem::MaybeUninit], &mut [std::mem::MaybeUninit]) {
+ unsafe { self.base.unsafe_slices_mut(start, end) }
+ }
+
+ fn read_is_held(&self) -> bool {
+ self.base.read_is_held()
+ }
+
+ fn write_is_held(&self) -> bool {
+ self.base.write_is_held()
+ }
+}
+
+impl Producer for NotifyingProducer {
+ unsafe fn set_write_index(&self, value: usize) {
+ unsafe { self.base.set_write_index(value); }
+ let _ = self.notifier.notify();
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::memfd_rb::MemfdRb;
+ use ringbuf::traits::{Producer, Consumer};
+
+ #[test]
+ fn test_eventfd_notify() {
+ let storage = crate::memfd_rb::MemfdStorage::::create("test_notify").unwrap();
+ let rb = MemfdRb { storage };
+
+ let prod = rb.get_producer();
+ let mut cons = rb.get_consumer();
+
+ let notifier = EventFdNotifier::new().unwrap();
+ let mut notifying_prod = NotifyingProducer::new(prod, notifier);
+
+ notifying_prod.try_push(42).unwrap();
+
+ let mut buf = [0u8; 8];
+ rustix::io::read(¬ifying_prod.notifier.fd, &mut buf).unwrap();
+ let val = u64::from_ne_bytes(buf);
+ assert_eq!(val, 1);
+
+ assert_eq!(cons.try_pop(), Some(42));
+ }
+}
+
diff --git a/memfd_ringbuf/src/lib.rs b/memfd_ringbuf/src/lib.rs
new file mode 100644
index 0000000..ae123b6
--- /dev/null
+++ b/memfd_ringbuf/src/lib.rs
@@ -0,0 +1,22 @@
+pub mod traits;
+pub mod memfd_rb;
+pub mod eventfd_notifier;
+
+pub use memfd_rb::{MemfdRb, MemfdStorage, MemfdStorageTrait};
+pub use traits::{SharedPod, RbProducerNotify};
+pub use eventfd_notifier::EventFdNotifier;
+
+pub fn add(left: u64, right: u64) -> u64 {
+ left + right
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn it_works() {
+ let result = add(2, 2);
+ assert_eq!(result, 4);
+ }
+}
diff --git a/memfd_ringbuf/src/memfd_rb.rs b/memfd_ringbuf/src/memfd_rb.rs
new file mode 100644
index 0000000..30d5fb4
--- /dev/null
+++ b/memfd_ringbuf/src/memfd_rb.rs
@@ -0,0 +1,605 @@
+// Core
+use std::{
+ io,
+ mem::{size_of, MaybeUninit},
+ os::fd::{ AsFd, OwnedFd },
+ ptr::{self, NonNull, addr_of_mut},
+ sync::atomic::{AtomicU32, AtomicU64, AtomicBool, Ordering},
+ num::NonZeroUsize
+};
+
+// libraries
+use rustix::{
+ fs::{ftruncate, memfd_create, MemfdFlags},
+ mm::{mmap, munmap, MapFlags, ProtFlags },
+ io::dup,
+};
+use ringbuf::{
+ storage::Storage,
+ traits::{Observer, Consumer, Producer, RingBuffer},
+ wrap::{Prod, Cons}
+};
+
+// Crate
+use crate::traits::SharedPod;
+
+const MAGIC: u64 = u64::from_be_bytes(*b"SHMRINGU");
+#[repr(C, align(64))]
+pub struct CacheAligned(pub T);
+
+#[repr(C, align(64))]
+pub struct Header {
+ pub magic: u64,
+ pub version: u32,
+ pub flags: u32,
+ pub total_size: u64,
+ pub ring_capacity: u64,
+ pub initialized: AtomicU32,
+}
+
+#[repr(C, align(64))]
+pub struct SharedLayout {
+ pub header: Header,
+ pub read_index: CacheAligned,
+ pub write_index: CacheAligned,
+ pub read_held: AtomicBool,
+ pub write_held: AtomicBool,
+ pub storage: [MaybeUninit; N],
+}
+
+pub struct MemfdStorage {
+ fd: OwnedFd,
+ ptr: NonNull>,
+ size: usize,
+}
+
+impl MemfdStorage {
+ pub fn create(name: &str) -> io::Result {
+ let fd = memfd_create(name, MemfdFlags::CLOEXEC | MemfdFlags::ALLOW_SEALING)
+ .map_err(io::Error::from)?;
+
+ let total_size = size_of::>();
+
+ /* Resize memfd */
+ ftruncate(fd.as_fd(), total_size as u64)
+ .map_err(io::Error::from)?;
+
+ let raw_ptr = unsafe {
+ mmap(
+ ptr::null_mut(),
+ total_size,
+ ProtFlags::READ | ProtFlags::WRITE,
+ MapFlags::SHARED,
+ fd.as_fd(),
+ 0,
+ )
+ }.map_err(io::Error::from)?
+ as *mut SharedLayout;
+
+ let ptr = NonNull::new(raw_ptr)
+ .ok_or_else(|| {
+ io::Error::new(
+ io::ErrorKind::InvalidData,
+ "Mmap returned null pointer",
+ )
+ })?;
+
+ unsafe {
+ ptr::write(
+ raw_ptr,
+ SharedLayout {
+ header: Header {
+ magic: MAGIC,
+ version: 1,
+ flags: 0,
+ total_size: total_size as u64,
+ ring_capacity: N as u64,
+ initialized: AtomicU32::new(0),
+ },
+
+ read_index: CacheAligned(AtomicU64::new(0)),
+
+ write_index: CacheAligned(AtomicU64::new(0)),
+
+ read_held: AtomicBool::new(false),
+
+ write_held: AtomicBool::new(false),
+
+ storage: std::array::from_fn(|_i| {
+ MaybeUninit::uninit()
+ }),
+ },
+ );
+
+ /* Marks as fully initialized state */
+ ptr.as_ref()
+ .header
+ .initialized
+ .store(1, Ordering::Release);
+
+ Ok(Self {
+ fd,
+ ptr,
+ size: total_size,
+ })
+ }
+ }
+
+ pub fn attach(fd: OwnedFd) -> io::Result {
+ let total_size = size_of::>();
+
+ let raw_ptr = unsafe {
+ mmap(
+ ptr::null_mut(),
+ total_size,
+ ProtFlags::READ | ProtFlags::WRITE,
+ MapFlags::SHARED,
+ fd.as_fd(),
+ 0
+ )
+ }.map_err(io::Error::from)? as *mut SharedLayout;
+
+ let ptr = NonNull::new(raw_ptr)
+ .ok_or_else(|| {
+ io::Error::new(io::ErrorKind::InvalidInput, "Null map pointer",
+ )
+ })?;
+
+ let cleanup = || unsafe {
+ munmap(raw_ptr.cast(), total_size).ok();
+ };
+
+ unsafe {
+ let layout = ptr.as_ref();
+
+ if layout.header.magic != MAGIC {
+ cleanup();
+ return Err(io::Error::new(
+ io::ErrorKind::InvalidInput,
+ "Invalid memfd magic",
+ ));
+ }
+
+ /* Validate Version */
+ if layout.header.version != 1 {
+ cleanup();
+ return Err(io::Error::new(
+ io::ErrorKind::InvalidInput,
+ "Unsupported shared memory version",
+ ));
+ }
+
+ if layout.header.ring_capacity != N as u64 {
+ cleanup();
+ return Err(io::Error::new(
+ io::ErrorKind::InvalidInput,
+ "Ring capacity mismatch",
+ ));
+ }
+
+ /* Wait/check initialization */
+ if layout.header.initialized.load(Ordering::Acquire) != 1 {
+ cleanup();
+ return Err(io::Error::new(
+ io::ErrorKind::WouldBlock,
+ "Shared memory not initialized",
+ ));
+ }
+
+ Ok(Self {
+ fd,
+ ptr,
+ size: total_size,
+ })
+ }
+ }
+
+ pub fn dup_fd(&self) -> io::Result {
+ dup(self.fd.as_fd()).map_err(io::Error::from)
+ }
+
+ pub fn layout( &self) -> &SharedLayout {
+ unsafe { self.ptr.as_ref() }
+ }
+}
+
+impl Drop for MemfdStorage {
+ fn drop(&mut self) {
+ unsafe {
+ munmap(self.ptr.as_ptr().cast(), self.size).ok();
+ }
+ }
+}
+
+unsafe impl Storage for MemfdStorage {
+ type Item = T;
+
+ fn len(&self) -> usize {
+ N
+ }
+
+ fn as_mut_ptr(&self) -> *mut MaybeUninit {
+ unsafe {
+ let layout_ptr = self.ptr.as_ptr();
+ (*layout_ptr).storage.as_mut_ptr()
+ }
+ }
+}
+
+pub unsafe trait MemfdStorageTrait: Storage {
+ fn header_ptr(&self) -> *mut Header;
+ fn read_index_ptr(&self) -> *mut AtomicU64;
+ fn write_index_ptr(&self) -> *mut AtomicU64;
+ fn read_held_ptr(&self) -> *mut u8;
+ fn write_held_ptr(&self) -> *mut u8;
+}
+
+unsafe impl MemfdStorageTrait for MemfdStorage {
+ fn header_ptr(&self) -> *mut Header {
+ unsafe {
+ let layout_ptr = self.ptr.as_ptr();
+ addr_of_mut!((*layout_ptr).header)
+ }
+ }
+
+ fn read_index_ptr(&self) -> *mut AtomicU64 {
+ unsafe {
+ let layout_ptr = self.ptr.as_ptr();
+ addr_of_mut!((*layout_ptr).read_index.0)
+ }
+ }
+
+ fn write_index_ptr(&self) -> *mut AtomicU64 {
+ unsafe {
+ let layout_ptr = self.ptr.as_ptr();
+ addr_of_mut!((*layout_ptr).write_index.0)
+ }
+ }
+
+ fn read_held_ptr(&self) -> *mut u8 {
+ unsafe {
+ let layout_ptr = self.ptr.as_ptr();
+ addr_of_mut!((*layout_ptr).read_held) as *mut u8
+ }
+ }
+
+ fn write_held_ptr(&self) -> *mut u8 {
+ unsafe {
+ let layout_ptr = self.ptr.as_ptr();
+ addr_of_mut!((*layout_ptr).write_held) as *mut u8
+ }
+ }
+}
+
+pub struct MemfdRb {
+ pub storage: S,
+}
+
+impl AsRef for MemfdRb {
+ fn as_ref(&self) -> &Self {
+ self
+ }
+}
+
+impl Observer for MemfdRb {
+ type Item = S::Item;
+
+ #[inline]
+ fn capacity(&self) -> NonZeroUsize {
+ NonZeroUsize::new(self.storage.len()).expect("Capacity must be non-zero")
+ }
+
+ fn read_index(&self) -> usize {
+ unsafe { (*self.storage.read_index_ptr()).load(Ordering::Acquire) as usize }
+ }
+
+ fn write_index(&self) -> usize {
+ unsafe { (*self.storage.write_index_ptr()).load(Ordering::Acquire) as usize }
+ }
+
+ unsafe fn unsafe_slices(&self, start: usize, end: usize)
+ -> (&[MaybeUninit], &[MaybeUninit]) {
+ unsafe {
+ let (first, second) = self.unsafe_slices_mut(start, end);
+ (first, second)
+ }
+ }
+
+ unsafe fn unsafe_slices_mut(&self, start: usize, end: usize)
+ -> (&mut [MaybeUninit], &mut [MaybeUninit]) {
+ let ptr = self.storage.as_mut_ptr();
+ let len = self.storage.len();
+ unsafe {
+ if start <= end {
+ (std::slice::from_raw_parts_mut(ptr.add(start), end - start), &mut [])
+ } else {
+ (
+ std::slice::from_raw_parts_mut(ptr.add(start), len - start),
+ std::slice::from_raw_parts_mut(ptr, end),
+ )
+ }
+ }
+ }
+
+ fn read_is_held(&self) -> bool {
+ unsafe {
+ let ptr = self.storage.read_held_ptr() as *const AtomicBool;
+ (*ptr).load(Ordering::Acquire)
+ }
+ }
+
+ fn write_is_held(&self) -> bool {
+ unsafe {
+ let ptr = self.storage.write_held_ptr() as *const AtomicBool;
+ (*ptr).load(Ordering::Acquire)
+ }
+ }
+}
+
+impl Producer for MemfdRb {
+ unsafe fn set_write_index(&self, value: usize) {
+ unsafe { (*self.storage.write_index_ptr()).store(value as u64, Ordering::Release); }
+ }
+}
+
+impl Consumer for MemfdRb {
+ unsafe fn set_read_index(&self, value: usize) {
+ unsafe { (*self.storage.read_index_ptr()).store(value as u64, Ordering::Release); }
+ }
+}
+
+impl RingBuffer for MemfdRb {
+ unsafe fn hold_read(&self, flag: bool) -> bool {
+ unsafe {
+ let ptr = self.storage.read_held_ptr() as *const AtomicBool;
+ (*ptr).swap(flag, Ordering::AcqRel)
+ }
+ }
+
+ unsafe fn hold_write(&self, flag: bool) -> bool {
+ unsafe {
+ let ptr = self.storage.write_held_ptr() as *const AtomicBool;
+ (*ptr).swap(flag, Ordering::AcqRel)
+ }
+ }
+}
+
+impl Drop for MemfdRb {
+ fn drop(&mut self) {
+ unsafe {
+ self.hold_read(false);
+ self.hold_write(false);
+ }
+ }
+}
+
+impl MemfdRb {
+ pub fn get_producer(&self) -> Prod<&Self> {
+ Prod::new(self)
+ }
+
+ pub fn get_consumer(&self) -> Cons<&Self> {
+ Cons::new(self)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ use bytemuck::{Pod, Zeroable};
+ use std::mem::{align_of, size_of};
+ use std::sync::atomic::Ordering;
+
+ #[repr(C)]
+ #[derive(Copy, Clone, Zeroable, Pod, Debug, PartialEq)]
+ struct TestMessage {
+ pub id: u64,
+ pub value: u32,
+ _padding: u32,
+ }
+
+ unsafe impl SharedPod for TestMessage {}
+
+ #[test]
+ fn create_memfd_storage() {
+ const N: usize = 128;
+
+ let storage =
+ MemfdStorage::::create("test-ring")
+ .unwrap();
+
+ let layout = unsafe {
+ storage.ptr.as_ref()
+ };
+
+ /*
+ * Validate header
+ */
+ assert_eq!(
+ layout.header.magic,
+ MAGIC
+ );
+
+ assert_eq!(layout.header.version, 1);
+
+ assert_eq!(
+ layout.header.ring_capacity,
+ N as u64
+ );
+
+ assert_eq!(
+ layout.header.initialized.load(Ordering::Acquire),
+ 1
+ );
+
+ /*
+ * Validate indexes
+ */
+ assert_eq!(
+ layout.read_index.0.load(Ordering::Acquire),
+ 0
+ );
+
+ assert_eq!(
+ layout.write_index.0.load(Ordering::Acquire),
+ 0
+ );
+
+ }
+
+ #[test]
+ fn validate_alignment() {
+ assert_eq!(
+ align_of::(),
+ 64
+ );
+
+ assert_eq!(
+ align_of::>(),
+ 64
+ );
+
+ assert_eq!(
+ align_of::>(),
+ 64
+ );
+ }
+
+ #[test]
+ fn validate_sizes() {
+ println!(
+ "Header size: {}",
+ size_of::()
+ );
+
+ println!(
+ "SharedLayout size: {}",
+ size_of::>()
+ );
+
+ /*
+ * CacheAligned should occupy full cacheline.
+ */
+ assert_eq!(
+ size_of::>(),
+ 64
+ );
+ }
+
+ #[test]
+ fn write_and_read_() {
+ const N: usize = 16;
+
+ let storage =
+ MemfdStorage::::create("rw-test")
+ .unwrap();
+
+ let layout = storage.layout();
+
+ let slot = &layout.storage[0];
+
+ let msg = TestMessage {
+ id: 42,
+ value: 777,
+ _padding: 0,
+ };
+
+ unsafe {
+ slot.as_ptr().cast::().cast_mut().write(msg);
+ }
+
+ let read_back = unsafe {
+ slot.assume_init()
+ };
+
+ assert_eq!(msg, read_back);
+ }
+
+ #[test]
+ fn producer_consumer() {
+ const N: usize = 16;
+ let storage = MemfdStorage::::create("pc-test").unwrap();
+ let rb = MemfdRb { storage };
+
+ let mut prod = rb.get_producer();
+ let mut cons = rb.get_consumer();
+
+ let msg = TestMessage { id: 1, value: 100, _padding: 0 };
+ prod.try_push(msg).unwrap();
+
+ let read = cons.try_pop().unwrap();
+ assert_eq!(msg, read);
+ }
+
+
+ #[test]
+ fn producer_consumer_attach_storage() {
+ const N: usize = 16;
+ let storage = MemfdStorage::::create("pc-test-attach").unwrap();
+
+ let dup_fd = storage.dup_fd().unwrap();
+ let attached_storage = MemfdStorage::::attach(dup_fd).unwrap();
+
+ let rb_prod = MemfdRb { storage };
+ let rb_cons = MemfdRb { storage: attached_storage };
+
+ let mut prod = rb_prod.get_producer();
+ let mut cons = rb_cons.get_consumer();
+
+ let msg = TestMessage { id: 1, value: 100, _padding: 0 };
+ prod.try_push(msg).unwrap();
+
+ let read = cons.try_pop().unwrap();
+ assert_eq!(msg, read);
+ }
+
+ #[test]
+ fn attach_existing_memfd() {
+ const N: usize = 64;
+ let storage = MemfdStorage::::create(
+ "attach-test",
+ ).unwrap();
+
+ let dup_fd = dup(storage.fd.as_fd()).unwrap();
+
+ /* Now use attach function to get the attached fd */
+ let attached_storage =
+ MemfdStorage::::attach(dup_fd).unwrap();
+
+ assert_eq!(
+ attached_storage.layout().header.magic,
+ MAGIC
+ );
+
+ assert_eq!(attached_storage.layout().header.version, 1);
+
+ /* write test */
+ storage.layout()
+ .write_index
+ .0
+ .store(42, Ordering::Release);
+
+ /* Read from attached storage */
+ let value = attached_storage.layout().write_index.0.load(Ordering::Acquire);
+ assert_eq!(value, 42);
+
+ let msg = TestMessage {
+ id: 134,
+ value: 1466,
+ _padding: 0,
+ };
+
+ unsafe {
+ storage.layout().storage[0].as_ptr().cast::().cast_mut().write(msg);
+ }
+
+ /* Read back from attached fd */
+ let read_back = unsafe {
+ attached_storage.layout().storage[0].assume_init()
+ };
+
+ assert_eq!(msg, read_back);
+
+ }
+}
diff --git a/memfd_ringbuf/src/traits.rs b/memfd_ringbuf/src/traits.rs
new file mode 100644
index 0000000..3892ab6
--- /dev/null
+++ b/memfd_ringbuf/src/traits.rs
@@ -0,0 +1,17 @@
+use bytemuck::Pod;
+use rustix::io::Result;
+
+pub unsafe trait SharedPod: Pod {}
+
+unsafe impl SharedPod for u32 {}
+unsafe impl SharedPod for u64 {}
+unsafe impl SharedPod for i32 {}
+unsafe impl SharedPod for i64 {}
+unsafe impl SharedPod for u8 {}
+unsafe impl SharedPod for i8 {}
+unsafe impl SharedPod for usize {}
+unsafe impl SharedPod for isize {}
+
+pub trait RbProducerNotify {
+ fn notify(&self) -> Result<()>;
+}
\ No newline at end of file
diff --git a/safetyguard/Cargo.toml b/safetyguard/Cargo.toml
new file mode 100644
index 0000000..9122a69
--- /dev/null
+++ b/safetyguard/Cargo.toml
@@ -0,0 +1,10 @@
+[package]
+name = "safetyguard"
+version = "0.1.0"
+edition = "2024"
+
+[dependencies]
+serde-saphyr = { version = "0.0.25", features = ["serialize","deserialize","figment"] }
+figment = { version = "0.10" }
+serde = { version = "1.0.228", features = ["derive"] }
+
diff --git a/safetyguard/src/lib.rs b/safetyguard/src/lib.rs
new file mode 100644
index 0000000..56a27a4
--- /dev/null
+++ b/safetyguard/src/lib.rs
@@ -0,0 +1,3 @@
+#![forbid(unsafe_code)]
+
+pub mod safeappdb;
diff --git a/safetyguard/src/main.rs b/safetyguard/src/main.rs
new file mode 100644
index 0000000..729f031
--- /dev/null
+++ b/safetyguard/src/main.rs
@@ -0,0 +1,5 @@
+
+
+fn main() {
+ println!("Hello, world!");
+}
\ No newline at end of file
diff --git a/safetyguard/src/safeaction.rs b/safetyguard/src/safeaction.rs
new file mode 100644
index 0000000..e69de29
diff --git a/safetyguard/src/safeappdb.rs b/safetyguard/src/safeappdb.rs
new file mode 100644
index 0000000..059665b
--- /dev/null
+++ b/safetyguard/src/safeappdb.rs
@@ -0,0 +1,2 @@
+mod appsdb;
+pub mod global;
\ No newline at end of file
diff --git a/safetyguard/src/safeappdb/appsdb.rs b/safetyguard/src/safeappdb/appsdb.rs
new file mode 100644
index 0000000..e69de29
diff --git a/safetyguard/src/safeappdb/global.rs b/safetyguard/src/safeappdb/global.rs
new file mode 100644
index 0000000..743266a
--- /dev/null
+++ b/safetyguard/src/safeappdb/global.rs
@@ -0,0 +1,201 @@
+use figment::{Figment, providers::Format};
+use serde::{Serialize, Deserialize};
+
+#[derive(Debug, Serialize, Deserialize)]
+pub struct Config {
+ #[serde(default = "Config::default_config_version")]
+ config_version: u64,
+ #[serde(default)]
+ global: Global,
+ #[serde(default)]
+ watchdog: Watchdog,
+ #[serde(default)]
+ recover: Recover,
+}
+impl Config {
+ fn default_config_version() -> u64 {
+ 1
+ }
+}
+#[derive(Debug, Serialize, Deserialize)]
+#[serde(default)]
+pub struct Global {
+ log_level: LogLevel,
+ check_interval_ms: u64,
+ state_file: String,
+ log_file: String,
+ config_file: String,
+ config_file_gen: String,
+ mode: Mode,
+ endpoint: String,
+}
+
+impl Default for Global {
+ fn default() -> Self {
+ Self {
+ log_level: LogLevel::INFO,
+ check_interval_ms: 500,
+ state_file: "/tmp/safeappdb.state".to_string(),
+ log_file: "/tmp/safeappdb.log".to_string(),
+ config_file: "/tmp/safeappdb.config".to_string(),
+ config_file_gen: "/tmp/safeappdb.config.gen".to_string(),
+ mode: Mode::DEVELOPMENT,
+ endpoint: "/tmp/saftyguard.sock".to_string(),
+ }
+ }
+}
+
+#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
+#[serde(rename_all = "lowercase")]
+pub enum LogLevel {
+ DEDUG,
+ INFO,
+ WARN,
+ ERROR,
+}
+
+#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
+#[serde(rename_all = "lowercase")]
+pub enum Mode {
+ DEVELOPMENT,
+ PRODUCTION,
+}
+
+#[derive(Debug, Serialize, Deserialize)]
+#[serde(default)]
+pub struct Watchdog {
+ device: String,
+ timeout_sec: u64,
+ pretimeout_sec: u64,
+ kick_interval_ms: u64,
+}
+
+impl Default for Watchdog {
+ fn default() -> Self {
+ Self {
+ device: "/dev/watchdog".to_string(),
+ timeout_sec: 10,
+ pretimeout_sec: 5,
+ kick_interval_ms: 3000,
+ }
+ }
+}
+#[derive(Debug, Serialize, Deserialize)]
+#[serde(default)]
+pub struct Recover {
+ system_reset_on_failure: bool,
+ max_global_failures: u64,
+}
+impl Default for Recover {
+ fn default() -> Self {
+ Self {
+ system_reset_on_failure: true,
+ max_global_failures: 5,
+ }
+ }
+}
+
+
+impl Config {
+ pub fn get_config(fp : String ) -> Result> {
+ let cfg: Config = Figment::from(serde_saphyr::figment::Yaml::file(fp))
+ .extract::()?;
+ Ok(cfg)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use std::fs;
+ use std::io::Write;
+ use super::*;
+
+
+ #[test]
+ fn test_config() -> Result<(), Box> {
+ let yml = r#"
+config_version: 1
+global:
+ log_level: info
+ check_interval_ms: 1000
+ state_file: /tmp/safeappdb.state
+ log_file: /tmp/safeappdb.log
+ config_file: /tmp/safeappdb.config
+ config_file_gen: /tmp/safeappdb.config.gen
+ mode: development # Can be development.
+ endpoint: /tmp/saftyguard.sock
+
+watchdog:
+ device: /dev/watchdog2
+ timeout_sec: 100
+ pretimeout_sec: 15
+ kick_interval_ms: 2000
+
+recover:
+ system_reset_on_failure: false
+ max_global_failures: 6
+
+"#;
+
+ let mut fp = fs::File::create("/tmp/tst.yaml")?;
+ fp.write_all(yml.as_bytes())?;
+ fp.sync_all()?;
+
+ let config = Config::get_config("/tmp/tst.yaml".to_string())?;
+ println!("{config:#?}");
+
+ assert_eq!(config.global.check_interval_ms, 1000);
+ assert_eq!(config.global.state_file, "/tmp/safeappdb.state");
+ assert_eq!(config.global.log_file, "/tmp/safeappdb.log");
+ assert_eq!(config.global.config_file, "/tmp/safeappdb.config");
+ assert_eq!(config.global.config_file_gen, "/tmp/safeappdb.config.gen");
+ assert_eq!(config.global.endpoint, "/tmp/saftyguard.sock");
+ assert_eq!(config.global.log_level, LogLevel::INFO);
+ assert_eq!(config.global.mode, Mode::DEVELOPMENT);
+
+ assert_eq!(config.recover.system_reset_on_failure, false);
+ assert_eq!(config.recover.max_global_failures, 6);
+
+ assert_eq!(config.watchdog.device, "/dev/watchdog2");
+ assert_eq!(config.watchdog.timeout_sec, 100);
+ assert_eq!(config.watchdog.pretimeout_sec, 15);
+ assert_eq!(config.watchdog.kick_interval_ms, 2000);
+
+ fs::remove_file("/tmp/tst.yaml")?;
+
+ Ok(())
+ }
+ #[test]
+ fn test_config_default() -> Result<(), Box> {
+ let yml = r#"
+"#;
+
+ let mut fp = fs::File::create("/tmp/tst2.yaml")?;
+ fp.write_all(yml.as_bytes())?;
+ fp.sync_all()?;
+
+ let config = Config::get_config("/tmp/tst2.yaml".to_string())?;
+ println!("{config:#?}");
+
+ assert_eq!(config.global.check_interval_ms, 500);
+ assert_eq!(config.global.state_file, "/tmp/safeappdb.state");
+ assert_eq!(config.global.log_file, "/tmp/safeappdb.log");
+ assert_eq!(config.global.config_file, "/tmp/safeappdb.config");
+ assert_eq!(config.global.config_file_gen, "/tmp/safeappdb.config.gen");
+ assert_eq!(config.global.endpoint, "/tmp/saftyguard.sock");
+ assert_eq!(config.global.log_level, LogLevel::INFO);
+ assert_eq!(config.global.mode, Mode::DEVELOPMENT);
+
+ assert_eq!(config.recover.system_reset_on_failure, true);
+ assert_eq!(config.recover.max_global_failures, 5);
+
+ assert_eq!(config.watchdog.device, "/dev/watchdog");
+ assert_eq!(config.watchdog.timeout_sec, 10);
+ assert_eq!(config.watchdog.pretimeout_sec, 5);
+ assert_eq!(config.watchdog.kick_interval_ms, 3000);
+
+ fs::remove_file("/tmp/tst2.yaml")?;
+
+ Ok(())
+ }
+}
\ No newline at end of file
diff --git a/safetyguard/src/safeinstrument.rs b/safetyguard/src/safeinstrument.rs
new file mode 100644
index 0000000..e69de29
diff --git a/safetyguard/src/safemonitor.rs b/safetyguard/src/safemonitor.rs
new file mode 100644
index 0000000..e69de29