1pub mod error;
50
51use core::ptr;
52use std::{
53 ffi::{CStr, CString},
54 ops::{Deref, DerefMut},
55};
56
57use libuci_sys::{
58 uci_alloc_context, uci_commit, uci_context, uci_delete, uci_free_context, uci_get_errorstr,
59 uci_lookup_ptr, uci_option_type_UCI_TYPE_STRING, uci_ptr, uci_ptr_UCI_LOOKUP_COMPLETE,
60 uci_revert, uci_save, uci_set, uci_set_confdir, uci_set_savedir, uci_type_UCI_TYPE_OPTION,
61 uci_type_UCI_TYPE_SECTION, uci_unload,
62};
63use log::debug;
64
65use crate::error::{Error, Result};
66
67#[allow(clippy::cast_possible_wrap)]
68const UCI_OK: i32 = libuci_sys::UCI_OK as i32;
69
70pub struct Uci(*mut uci_context);
72
73impl Drop for Uci {
74 fn drop(&mut self) {
75 unsafe { uci_free_context(self.0) }
76 }
77}
78
79struct UciPtr(uci_ptr, *mut std::os::raw::c_char);
82
83impl Deref for UciPtr {
84 type Target = uci_ptr;
85
86 fn deref(&self) -> &Self::Target {
87 &self.0
88 }
89}
90
91impl DerefMut for UciPtr {
92 fn deref_mut(&mut self) -> &mut Self::Target {
93 &mut self.0
94 }
95}
96
97impl Drop for UciPtr {
98 fn drop(&mut self) {
99 unsafe { CString::from_raw(self.1) };
100 }
101}
102
103impl Uci {
104 pub fn new() -> Result<Uci> {
107 let ctx = unsafe { uci_alloc_context() };
108 if !ctx.is_null() {
109 Ok(Uci(ctx))
110 } else {
111 Err(Error::Message(String::from("Could not alloc uci context")))
112 }
113 }
114
115 pub fn set_config_dir(&mut self, config_dir: &str) -> Result<()> {
117 let result = unsafe {
118 let raw = CString::new(config_dir)?;
119 uci_set_confdir(
120 self.0,
121 raw.as_bytes_with_nul()
122 .as_ptr()
123 .cast::<std::os::raw::c_char>(),
124 )
125 };
126 if result == UCI_OK {
127 debug!("Set config dir to: {}", config_dir);
128 Ok(())
129 } else {
130 Err(Error::Message(format!(
131 "Cannot set config dir: {}, {}",
132 config_dir,
133 self.get_last_error()
134 .unwrap_or_else(|_| String::from("Unknown"))
135 )))
136 }
137 }
138
139 pub fn set_save_dir(&mut self, save_dir: &str) -> Result<()> {
141 let result = unsafe {
142 let raw = CString::new(save_dir)?;
143 uci_set_savedir(
144 self.0,
145 raw.as_bytes_with_nul()
146 .as_ptr()
147 .cast::<std::os::raw::c_char>(),
148 )
149 };
150 if result == UCI_OK {
151 debug!("Set save dir to: {}", save_dir);
152 Ok(())
153 } else {
154 Err(Error::Message(format!(
155 "Cannot set save dir: {}, {}",
156 save_dir,
157 self.get_last_error()
158 .unwrap_or_else(|_| String::from("Unknown"))
159 )))
160 }
161 }
162
163 pub fn delete(&mut self, identifier: &str) -> Result<()> {
170 let mut ptr = self.get_ptr(identifier)?;
171 let result = unsafe { uci_delete(self.0, &mut ptr.0) };
172 if result != UCI_OK {
173 return Err(Error::Message(format!(
174 "Could not delete uci key: {}, {}, {}",
175 identifier,
176 result,
177 self.get_last_error()
178 .unwrap_or_else(|_| String::from("Unknown"))
179 )));
180 }
181 let result = unsafe { uci_save(self.0, ptr.p) };
182 if result == UCI_OK {
183 Ok(())
184 } else {
185 Err(Error::Message(format!(
186 "Could not save uci key: {}, {}, {}",
187 identifier,
188 result,
189 self.get_last_error()
190 .unwrap_or_else(|_| String::from("Unknown"))
191 )))
192 }
193 }
194
195 pub fn revert(&mut self, identifier: &str) -> Result<()> {
201 let mut ptr = self.get_ptr(identifier)?;
202 let result = unsafe { uci_revert(self.0, &mut ptr.0) };
203 if result != UCI_OK {
204 return Err(Error::Message(format!(
205 "Could not revert uci key: {}, {}, {}",
206 identifier,
207 result,
208 self.get_last_error()
209 .unwrap_or_else(|_| String::from("Unknown"))
210 )));
211 }
212 let result = unsafe { uci_save(self.0, ptr.p) };
213 if result == UCI_OK {
214 Ok(())
215 } else {
216 Err(Error::Message(format!(
217 "Could not save uci key: {}, {}, {}",
218 identifier,
219 result,
220 self.get_last_error()
221 .unwrap_or_else(|_| String::from("Unknown"))
222 )))
223 }
224 }
225
226 pub fn set(&mut self, identifier: &str, val: &str) -> Result<()> {
233 if val.contains('\'') {
234 return Err(Error::Message(format!(
235 "Values may not contain quotes: {}={}",
236 identifier, val
237 )));
238 }
239 let mut ptr = self.get_ptr(format!("{}={}", identifier, val).as_ref())?;
240 if ptr.value.is_null() {
241 return Err(Error::Message(format!(
242 "parsed value is null: {}={}",
243 identifier, val
244 )));
245 }
246 let result = unsafe { uci_set(self.0, &mut ptr.0) };
247 if result != UCI_OK {
248 return Err(Error::Message(format!(
249 "Could not set uci key: {}={}, {}, {}",
250 identifier,
251 val,
252 result,
253 self.get_last_error()
254 .unwrap_or_else(|_| String::from("Unknown"))
255 )));
256 }
257 let result = unsafe { uci_save(self.0, ptr.p) };
258 if result == UCI_OK {
259 Ok(())
260 } else {
261 Err(Error::Message(format!(
262 "Could not save uci key: {}={}, {}, {}",
263 identifier,
264 val,
265 result,
266 self.get_last_error()
267 .unwrap_or_else(|_| String::from("Unknown"))
268 )))
269 }
270 }
271
272 pub fn commit(&mut self, package: &str) -> Result<()> {
275 let mut ptr = self.get_ptr(package)?;
276 let result = unsafe { uci_commit(self.0, &mut ptr.p, false) };
277 if result != UCI_OK {
278 return Err(Error::Message(format!(
279 "Could not set commit uci package: {}, {}, {}",
280 package,
281 result,
282 self.get_last_error()
283 .unwrap_or_else(|_| String::from("Unknown"))
284 )));
285 }
286 if !ptr.p.is_null() {
287 unsafe {
288 uci_unload(self.0, ptr.p);
289 }
290 }
291 Ok(())
292 }
293
294 pub fn get(&mut self, key: &str) -> Result<String> {
301 let ptr = self.get_ptr(key)?;
302 if ptr.flags & uci_ptr_UCI_LOOKUP_COMPLETE == 0 {
303 return Err(Error::Message(format!("Lookup failed: {}", key)));
304 }
305 let last = unsafe { *ptr.last };
306 #[allow(non_upper_case_globals)]
307 match last.type_ {
308 uci_type_UCI_TYPE_OPTION => {
309 let opt = unsafe { *ptr.o };
310 if opt.type_ != uci_option_type_UCI_TYPE_STRING {
311 return Err(Error::Message(format!(
312 "Cannot get string value of non-string: {} {}",
313 key, opt.type_
314 )));
315 }
316 if opt.section.is_null() {
317 return Err(Error::Message(format!("uci section was null: {}", key)));
318 }
319 let sect = unsafe { *opt.section };
320 if sect.package.is_null() {
321 return Err(Error::Message(format!("uci package was null: {}", key)));
322 }
323 let pack = unsafe { *sect.package };
324 let value = unsafe { CStr::from_ptr(opt.v.string).to_str()? };
325
326 debug!(
327 "{}.{}.{}={}",
328 unsafe { CStr::from_ptr(pack.e.name) }.to_str()?,
329 unsafe { CStr::from_ptr(sect.e.name) }.to_str()?,
330 unsafe { CStr::from_ptr(opt.e.name) }.to_str()?,
331 value
332 );
333 Ok(String::from(value))
334 }
335 uci_type_UCI_TYPE_SECTION => {
336 let sect = unsafe { *ptr.s };
337 if sect.package.is_null() {
338 return Err(Error::Message(format!("uci package was null: {}", key)));
339 }
340 let pack = unsafe { *sect.package };
341 let typ = unsafe { CStr::from_ptr(sect.type_).to_str()? };
342
343 debug!(
344 "{}.{}={}",
345 unsafe { CStr::from_ptr(pack.e.name) }.to_str()?,
346 unsafe { CStr::from_ptr(sect.e.name) }.to_str()?,
347 typ
348 );
349 Ok(String::from(typ))
350 }
351 _ => return Err(Error::Message(format!("unsupported type: {}", last.type_))),
352 }
353 }
354
355 fn get_ptr(&mut self, identifier: &str) -> Result<UciPtr> {
365 let mut ptr = uci_ptr {
366 target: 0,
367 flags: 0,
368 p: ptr::null_mut(),
369 s: ptr::null_mut(),
370 o: ptr::null_mut(),
371 last: ptr::null_mut(),
372 package: ptr::null(),
373 section: ptr::null(),
374 option: ptr::null(),
375 value: ptr::null(),
376 };
377 let raw = CString::new(identifier)?.into_raw();
378 let result = unsafe { uci_lookup_ptr(self.0, &mut ptr, raw, true) };
379 if result != UCI_OK {
380 return Err(Error::Message(format!(
381 "Could not parse uci key: {}, {}, {}",
382 identifier,
383 result,
384 self.get_last_error()
385 .unwrap_or_else(|_| String::from("Unknown"))
386 )));
387 }
388 debug!("{:?}", ptr);
389 if !ptr.last.is_null() {
390 Ok(UciPtr(ptr, raw))
391 } else {
392 Err(Error::Message(format!(
393 "Cannot access null value: {}",
394 identifier
395 )))
396 }
397 }
398
399 fn get_last_error(&mut self) -> Result<String> {
402 let mut raw: *mut std::os::raw::c_char = ptr::null_mut();
403 unsafe { uci_get_errorstr(self.0, &mut raw, ptr::null()) };
404 if raw.is_null() {
405 return Err(Error::Message(String::from("last_error was null")));
406 }
407 match unsafe { CStr::from_ptr(raw) }.to_str() {
408 Ok(o) => {
409 let s = String::from(o);
410 unsafe { libc::free(raw.cast::<std::os::raw::c_void>()) };
411 Ok(s)
412 }
413 Err(e) => {
414 unsafe { libc::free(raw.cast::<std::os::raw::c_void>()) };
415 Err(e.into())
416 }
417 }
418 }
419}