owfs/
lib.rs

1#![ doc = include_str!( concat!( env!( "CARGO_MANIFEST_DIR" ), "/", "README.md" ) ) ]
2use std::collections::HashSet;
3use std::ffi::{CStr, CString};
4use std::fmt;
5use std::os::raw::c_char;
6use std::ptr;
7
8mod devices;
9mod owcapi;
10
11pub use devices::{Device, DeviceInfo};
12
13#[derive(Debug)]
14pub struct Error {
15    code: isize,
16}
17
18impl From<std::ffi::NulError> for Error {
19    fn from(_err: std::ffi::NulError) -> Self {
20        Self::new(0)
21    }
22}
23
24impl From<std::num::TryFromIntError> for Error {
25    fn from(_err: std::num::TryFromIntError) -> Self {
26        Self::new(1)
27    }
28}
29
30impl Error {
31    #[inline]
32    pub fn new(code: isize) -> Self {
33        Self { code }
34    }
35    #[inline]
36    pub fn code(&self) -> isize {
37        self.code
38    }
39}
40
41impl fmt::Display for Error {
42    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
43        write!(f, "OWFS error: {}", self.code)
44    }
45}
46
47impl std::error::Error for Error {}
48
49pub struct OwfsGuard {}
50
51impl Drop for OwfsGuard {
52    #[inline]
53    fn drop(&mut self) {
54        finish();
55    }
56}
57
58pub fn init(path: &str) -> Result<OwfsGuard, Error> {
59    let c_path = CString::new(path)?;
60    let res = unsafe { owcapi::OW_init(c_path.as_ptr()) };
61    if res == 0 {
62        Ok(OwfsGuard {})
63    } else {
64        Err(Error::new(res))
65    }
66}
67
68pub fn get(path: &str) -> Result<String, Error> {
69    let c_path = CString::new(path)?;
70    let mut buf: *mut c_char = ptr::null_mut();
71    let buf_ptr: *const *mut c_char = &mut buf;
72    let mut buf_length: usize = 0;
73    let res = unsafe { owcapi::OW_get(c_path.as_ptr(), buf_ptr, &mut buf_length) };
74    if res >= 0 {
75        let data = unsafe { CStr::from_ptr(buf) };
76        let result = data.to_string_lossy().to_string();
77        unsafe {
78            libc::free(buf.cast::<libc::c_void>());
79        }
80        Ok(result)
81    } else {
82        unsafe {
83            libc::free(buf.cast::<libc::c_void>());
84        }
85        Err(Error::new(res))
86    }
87}
88
89pub fn get_bytes(path: &str) -> Result<Vec<u8>, Error> {
90    let c_path = CString::new(path)?;
91    let mut buf: *mut c_char = ptr::null_mut();
92    let buf_ptr: *const *mut c_char = &mut buf;
93    let mut buf_length: usize = 0;
94    let res = unsafe { owcapi::OW_get(c_path.as_ptr(), buf_ptr, &mut buf_length) };
95    if res >= 0 {
96        let result = unsafe { std::slice::from_raw_parts(buf.cast::<u8>(), buf_length).to_vec() };
97        unsafe {
98            libc::free(buf.cast::<libc::c_void>());
99        }
100        Ok(result)
101    } else {
102        unsafe {
103            libc::free(buf.cast::<libc::c_void>());
104        }
105        Err(Error::new(res))
106    }
107}
108
109pub fn set(path: &str, value: &str) -> Result<(), Error> {
110    let c_path = CString::new(path)?;
111    let c_val = CString::new(value)?;
112    let len_i: isize = c_val.as_bytes_with_nul().len().try_into()?;
113    #[allow(clippy::cast_sign_loss)]
114    let res = unsafe { owcapi::OW_put(c_path.as_ptr(), c_val.as_ptr(), len_i as usize) };
115    if res == len_i {
116        Ok(())
117    } else {
118        Err(Error::new(-2))
119    }
120}
121
122pub fn set_bytes(path: &str, value: &[u8]) -> Result<(), Error> {
123    let c_path = CString::new(path)?;
124    let len_i: isize = value.len().try_into()?;
125    #[allow(clippy::cast_sign_loss)]
126    let res = unsafe {
127        owcapi::OW_put(
128            c_path.as_ptr(),
129            value.as_ptr().cast::<c_char>(),
130            len_i as usize,
131        )
132    };
133    if res == len_i {
134        Ok(())
135    } else {
136        Err(Error::new(-2))
137    }
138}
139
140#[derive(Default)]
141pub struct ScanOptions<'a> {
142    types: Option<HashSet<&'a str>>,
143    attrs_any: Option<HashSet<&'a str>>,
144    attrs_all: Option<HashSet<&'a str>>,
145}
146
147impl<'a> ScanOptions<'a> {
148    #[inline]
149    pub fn new() -> Self {
150        Self::default()
151    }
152    #[inline]
153    pub fn types(mut self, types: &'a [&'a str]) -> Self {
154        self.types = Some(types.iter().copied().collect());
155        self
156    }
157    #[inline]
158    pub fn attrs_all(mut self, attrs_all: &'a [&'a str]) -> Self {
159        self.attrs_all = Some(attrs_all.iter().copied().collect());
160        self
161    }
162    #[inline]
163    pub fn attrs_any(mut self, attrs_any: &'a [&'a str]) -> Self {
164        self.attrs_any = Some(attrs_any.iter().copied().collect());
165        self
166    }
167    fn matches(&self, dev: &Device) -> bool {
168        if let Some(ref types) = self.types {
169            if let Ok(tp) = dev.get("type") {
170                if !types.contains(&tp.as_str()) {
171                    return false;
172                }
173            } else {
174                return false;
175            }
176        }
177        let dev_attrs: Option<HashSet<&str>> =
178            if self.attrs_all.is_some() || self.attrs_any.is_some() {
179                Some(dev.attrs().iter().copied().collect())
180            } else {
181                None
182            };
183        if let Some(ref attrs_all) = self.attrs_all {
184            let d_attrs = dev_attrs.as_ref().unwrap();
185            for a in attrs_all {
186                if !d_attrs.contains(a) {
187                    return false;
188                }
189            }
190        }
191        if let Some(ref attrs_any) = self.attrs_any {
192            let d_attrs = dev_attrs.as_ref().unwrap();
193            let mut found = false;
194            for a in attrs_any {
195                if d_attrs.contains(a) {
196                    found = true;
197                }
198            }
199            if !found {
200                return false;
201            }
202        }
203        true
204    }
205}
206
207pub fn scan(options: ScanOptions) -> Result<Vec<Device>, Error> {
208    let data = get("/uncached/")?;
209    let mut result = Vec::new();
210    for el in data.split(',') {
211        if let Some(ch) = el.chars().next() {
212            if ch.is_ascii_digit() || ('A'..='F').contains(&ch) {
213                if let Some(el_path) = el.strip_suffix('/') {
214                    let mut dev = Device::new(el_path);
215                    if dev.load().is_ok() && options.matches(&dev) {
216                        result.push(dev);
217                    }
218                }
219            }
220        }
221    }
222    Ok(result)
223}
224
225#[inline]
226fn finish() {
227    unsafe { owcapi::OW_finish() };
228}