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}