From ec5de067f11a5e40794890fd3e4f9944ec2f9771 Mon Sep 17 00:00:00 2001 From: Marcus Weiner Date: Fri, 1 May 2026 02:27:17 +0200 Subject: [PATCH 01/24] wip --- libuci-sys/src/lib.rs | 10 +++ rust-uci/src/config/mod.rs | 119 +++++++++++++++++++++++++++++++++ rust-uci/src/config/option.rs | 45 +++++++++++++ rust-uci/src/config/package.rs | 97 +++++++++++++++++++++++++++ rust-uci/src/config/section.rs | 76 +++++++++++++++++++++ rust-uci/src/lib.rs | 78 +++++++++++---------- 6 files changed, 389 insertions(+), 36 deletions(-) create mode 100644 rust-uci/src/config/mod.rs create mode 100644 rust-uci/src/config/option.rs create mode 100644 rust-uci/src/config/package.rs create mode 100644 rust-uci/src/config/section.rs diff --git a/libuci-sys/src/lib.rs b/libuci-sys/src/lib.rs index 0ab82cd..f40e19e 100644 --- a/libuci-sys/src/lib.rs +++ b/libuci-sys/src/lib.rs @@ -105,6 +105,16 @@ pub unsafe fn list_to_element(ptr: *const uci_list) -> *const uci_element { container_of!(ptr, uci_element, list) } +/// casts an [`uci_element`] pointer to the containing [`uci_package`]. +/// +/// # Safety +/// The caller must ensure that `ptr` points to an element which is member of an [`uci_package`]. +/// The `ptr` must not point to an element which is not contained in an uci_package. +pub unsafe fn uci_to_package(ptr: *const uci_element) -> *const uci_package { + // safety: uci_package.e has type uci_element, ptr points to uci_element + container_of!(ptr, uci_package, e) +} + /// casts an [`uci_element`] pointer to the containing [`uci_section`]. /// /// # Safety diff --git a/rust-uci/src/config/mod.rs b/rust-uci/src/config/mod.rs new file mode 100644 index 0000000..fac243c --- /dev/null +++ b/rust-uci/src/config/mod.rs @@ -0,0 +1,119 @@ +use std::{ffi::CString, marker::PhantomData, option::Option as StdOption, ptr}; + +use libuci_sys::{list_to_element, uci_element, uci_list, uci_lookup_next, uci_to_package}; + +use crate::{ + error::{Error, Result}, + libuci_locked, Uci, UCI_ERR_NOTFOUND, UCI_OK, +}; + +mod option; + +mod package; +use package::{Package, PackageRef}; + +mod section; + +#[derive(Clone)] +pub(crate) enum NameOrRef { + Name(N), + Ref(T), +} + +unsafe trait FromUciElement { + unsafe fn from_uci_element(elem: *const uci_element) -> Self; +} + +struct UciListIter { + list: *const uci_list, + _out: PhantomData, +} + +impl UciListIter { + fn new(list: *const uci_list) -> Self { + Self { + list, + _out: PhantomData, + } + } +} + +impl Iterator for UciListIter +where + Item: FromUciElement, +{ + type Item = Item; + + fn next(&mut self) -> StdOption { + if self.list.is_null() { + return None; + } + + let node = unsafe { (*self.list).next }; + if node.is_null() { + return None; + } + if node.cast_const() == self.list { + return None; + } + + let elem = unsafe { list_to_element(node.cast_const()) }; + Some(unsafe { Item::from_uci_element(elem) }) + } +} + +/// represents the root of the config tree +/// It's the parent structure to [Package]s +pub struct Config { + uci: Uci, +} + +impl Config { + pub fn new() -> Result { + Ok(Self { uci: Uci::new()? }) + } + + /// return a single [Package] by its name + /// also works if the package is not defined yet + pub fn package<'a>(&'a mut self, name: impl AsRef) -> Result> { + let root = &mut (unsafe { *self.uci.ctx }).root; + let pkg = lookup_child(&mut self.uci, root, &name)? + .map(|e| NameOrRef::Ref(unsafe { uci_to_package(e) }.into())) + .unwrap_or_else(|| NameOrRef::Name(name.as_ref().to_owned())); + Ok(Package::new(&mut self.uci, pkg)) + } + + /// list all [Package]s in the config + pub fn packages(&mut self) -> impl Iterator { + // Safety: + // - self.uci.ctx is not null + let packages = unsafe { (&(*self.uci.ctx).root) as *const _ }; + UciListIter::new(packages) + } +} + +fn lookup_child( + uci: &mut Uci, + parent: *mut uci_list, + name: impl AsRef, +) -> Result> { + let raw = CString::new(name.as_ref())?; + let mut elem = ptr::null_mut(); + let result = libuci_locked!(uci, { + unsafe { uci_lookup_next(uci.ctx, &mut elem as *mut _, parent, raw.as_ptr()) } + }); + match result { + UCI_OK => (), + UCI_ERR_NOTFOUND => { + return Ok(None); + } + _ => { + return Err(Error::Message( + uci.get_last_error() + .unwrap_or_else(|_| String::from("Unknown")), + )); + } + } + + Ok(Some(elem)) +} diff --git a/rust-uci/src/config/option.rs b/rust-uci/src/config/option.rs new file mode 100644 index 0000000..8186f1f --- /dev/null +++ b/rust-uci/src/config/option.rs @@ -0,0 +1,45 @@ +use std::option::Option as StdOption; + +use crate::{Result, UciPtr}; + +/// represents the value of an [Option] +pub enum Value { + String(String), + Boolean(bool), + Integer(i64), + List(Vec), +} + +/// represents an option within a [Section] +pub struct Option { + _ptr: UciPtr, +} + +impl Option { + /// name of the option + pub fn name(&mut self) -> Result { + todo!() + } + + /// returns the current value of the option, None if not set + pub fn get(&mut self) -> Result> { + todo!() + } + + /// sets the value of the option, overriding the previous value + /// will create the [Package] or [Section] along the way if they do + /// not exist + pub fn set(&mut self, _value: Value) -> Result<()> { + todo!() + } + + /// adds a value to the existing value + /// behaves like `uci add_list` which will: + /// - create the option if it doesn't exist (not as a list) + /// - turn a single-value option into a list + /// + /// returns the resulting value + pub fn add_list(&mut self, _value: Value) -> Result { + todo!() + } +} diff --git a/rust-uci/src/config/package.rs b/rust-uci/src/config/package.rs new file mode 100644 index 0000000..7102fba --- /dev/null +++ b/rust-uci/src/config/package.rs @@ -0,0 +1,97 @@ +use std::ffi::CStr; + +use libuci_sys::{uci_element, uci_package, uci_to_package, uci_to_section}; + +use crate::{config::lookup_child, Result, Uci}; + +use super::{section::Section, FromUciElement, NameOrRef}; + +#[derive(Clone)] +pub struct PackageRef { + pkg: *const uci_package, +} + +impl From<*const uci_package> for PackageRef { + fn from(pkg: *const uci_package) -> Self { + Self { pkg } + } +} + +impl PackageRef { + pub fn get<'a>(self, uci: &'a mut Uci) -> Package<'a> { + Package { + uci, + pkg: NameOrRef::Ref(self), + } + } + + pub fn name(&self) -> Result<&str> { + unsafe { CStr::from_ptr((*self.pkg).path) } + .to_str() + .map_err(Into::into) + } +} + +/// represents a single package in the config tree +/// parent to different [Section]s +pub struct Package<'a> { + uci: &'a mut Uci, + pkg: NameOrRef, +} + +impl<'a> Package<'a> { + pub(crate) fn new(uci: &'a mut Uci, pkg: NameOrRef) -> Self { + Self { uci, pkg } + } + + /// name of this package + pub fn name(&self) -> Result<&str> { + match &self.pkg { + NameOrRef::Name(name) => Ok(name), + NameOrRef::Ref(pkg) => pkg.name(), + } + } + + /// return a single [Section] by its name + /// also works if the section is not defined yet + pub fn section( + &'a mut self, + name: impl AsRef, + section_type: impl AsRef, + ) -> Result> { + let name = name.as_ref(); + let section_type = section_type.as_ref(); + + let pkg = match &self.pkg { + NameOrRef::Name(_) => { + return Ok(Section::new( + self.uci, + self.pkg.clone(), + NameOrRef::Name((name.to_owned(), section_type.to_owned())), + )) + } + NameOrRef::Ref(pkg) => pkg, + }; + + let section = lookup_child(self.uci, &mut unsafe { *pkg.pkg }.sections, name)? + .map(|ptr| NameOrRef::Ref(unsafe { uci_to_section(ptr) }.into())) + .unwrap_or_else(|| NameOrRef::Name((name.to_owned(), section_type.to_owned()))); + Ok(Section::new(self.uci, self.pkg.clone(), section)) + } + + /// list all [Section]s in this package + pub fn sections(&'a mut self) -> Result>> { + Ok(vec![].into_iter()) + } +} + +unsafe impl FromUciElement for PackageRef { + /// # Safety + /// - Caller must guarantee that elem contains a `uci_package` + /// - Caller must guarantee that elem is not null + unsafe fn from_uci_element(elem: *const uci_element) -> Self { + Self { + pkg: uci_to_package(elem), + } + } +} diff --git a/rust-uci/src/config/section.rs b/rust-uci/src/config/section.rs new file mode 100644 index 0000000..c2c4a15 --- /dev/null +++ b/rust-uci/src/config/section.rs @@ -0,0 +1,76 @@ +use std::ffi::CStr; + +use libuci_sys::uci_section; + +use crate::{ + config::{package::PackageRef, NameOrRef}, + Result, Uci, +}; + +use super::option::Option; + +pub struct SectionRef { + section: *const uci_section, +} + +impl SectionRef { + pub fn name(&self) -> Result<&str> { + let ptr = unsafe { *self.section }.e.name; + Ok(unsafe { CStr::from_ptr(ptr) }.to_str()?) + } +} + +impl From<*const uci_section> for SectionRef { + fn from(section: *const uci_section) -> Self { + Self { section } + } +} + +/// represents a single section +/// parent to different [Option]s +pub struct Section<'a> { + uci: &'a mut Uci, + parent: NameOrRef, + section: NameOrRef<(String, String), SectionRef>, +} + +impl<'a> Section<'a> { + pub(crate) fn new( + uci: &'a mut Uci, + parent: NameOrRef, + section: NameOrRef<(String, String), SectionRef>, + ) -> Self { + Self { + uci, + parent, + section, + } + } + + /// returns the name of named sections, otherwise None + pub fn name(&mut self) -> Result<&str> { + match &self.section { + NameOrRef::Name((name, _)) => Ok(name), + NameOrRef::Ref(_) => todo!(), + } + } + + /// returns the type of the section + pub fn r#type(&mut self) -> Result<&str> { + match &self.section { + NameOrRef::Name((_, stype)) => Ok(stype), + NameOrRef::Ref(_) => todo!(), + } + } + + /// returns a specific [Option] by name + /// also works if the option is not defined yet + pub fn option(&mut self, _name: impl AsRef) -> Result