gpio_utils/
config.rs

1// Copyright (c) 2016, The gpio-utils Authors.
2//
3// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4// http://www.apache.org/license/LICENSE-2.0> or the MIT license
5// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
6// option.  This file may not be copied, modified, or distributed
7// except according to those terms.
8
9use glob::glob;
10use serde_derive::Deserialize;
11use std::collections::{BTreeSet, HashMap};
12use std::fmt;
13use std::fs::{self, File};
14use std::io;
15use std::io::prelude::*;
16use std::path::Path;
17use std::str::FromStr;
18use sysfs_gpio;
19use toml;
20
21const DEFAULT_SYMLINK_ROOT: &str = "/var/run/gpio";
22
23#[derive(Debug, PartialEq, Clone)]
24pub struct Direction(pub sysfs_gpio::Direction);
25
26#[derive(Deserialize, Debug)]
27#[serde(remote = "sysfs_gpio::Direction")]
28pub enum DirectionDef {
29    #[serde(rename = "in")]
30    In,
31    #[serde(rename = "out")]
32    Out,
33    #[serde(rename = "high")]
34    High,
35    #[serde(rename = "low")]
36    Low,
37}
38
39impl From<sysfs_gpio::Direction> for Direction {
40    fn from(e: sysfs_gpio::Direction) -> Self {
41        Direction(e)
42    }
43}
44
45#[derive(Debug, Clone, PartialEq, Deserialize)]
46pub struct PinConfig {
47    pub num: u64,
48    #[serde(default = "default_direction")]
49    #[serde(with = "DirectionDef")]
50    pub direction: sysfs_gpio::Direction,
51    #[serde(default)]
52    pub names: BTreeSet<String>,
53    #[serde(default = "bool_true")]
54    pub export: bool,
55    #[serde(default)]
56    pub active_low: bool,
57    pub user: Option<String>,
58    pub group: Option<String>,
59    pub mode: Option<u32>,
60}
61
62fn default_direction() -> sysfs_gpio::Direction {
63    sysfs_gpio::Direction::In
64}
65
66fn bool_true() -> bool {
67    true
68}
69
70#[derive(Clone, Debug, Default, Deserialize)]
71pub struct GpioConfig {
72    pub pins: Vec<PinConfig>,
73    #[serde(default)]
74    pub config: SysConfig,
75}
76
77#[derive(Clone, Debug, Default, Deserialize)]
78pub struct SysConfig {
79    pub symlink_root: Option<String>,
80}
81
82#[derive(Debug)]
83pub enum Error {
84    IoError(io::Error),
85    ParserErrors(toml::de::Error),
86    NoConfigFound,
87    DuplicateNames(String),
88}
89
90impl fmt::Display for Error {
91    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
92        match *self {
93            Error::IoError(ref e) => e.fmt(f),
94            Error::ParserErrors(ref e) => e.fmt(f),
95            Error::NoConfigFound => write!(f, "No Config Found"),
96            Error::DuplicateNames(ref e) => e.fmt(f),
97        }
98    }
99}
100
101impl From<io::Error> for Error {
102    fn from(e: io::Error) -> Self {
103        Error::IoError(e)
104    }
105}
106
107impl PinConfig {
108    /// Get the `sysfs_gpio::Pin` to go along with this config`
109    pub fn get_pin(&self) -> sysfs_gpio::Pin {
110        sysfs_gpio::Pin::new(self.num)
111    }
112}
113
114impl FromStr for GpioConfig {
115    type Err = Error;
116    /// Load a GPIO configuration for the provided toml string
117    fn from_str(config: &str) -> Result<Self, Error> {
118        let cfg = toml::from_str(config);
119        match cfg {
120            Ok(cfg) => {
121                let val_config: GpioConfig = toml::from_str(config).unwrap();
122                val_config.validate()?;
123                Ok(cfg)
124            }
125            Err(e) => Err(Error::ParserErrors(e)),
126        }
127    }
128}
129
130impl GpioConfig {
131    /// Validate invariants on the config that cannot easily be done earlier
132    ///
133    /// Currently, this just checks that there are no duplicated names between
134    /// different pins in the config
135    fn validate(&self) -> Result<(), Error> {
136        let mut all_names: HashMap<&str, &PinConfig> = HashMap::new();
137        for pin in &self.pins {
138            for name in &pin.names {
139                if let Some(other_pin) = all_names.get(&name[..]) {
140                    return Err(Error::DuplicateNames(format!(
141                        "Pins {} and {} share duplicate \
142                         name '{}'",
143                        pin.num, other_pin.num, name
144                    )));
145                }
146                all_names.insert(&name[..], pin);
147            }
148        }
149
150        Ok(())
151    }
152
153    /// Load a GPIO Config from the system
154    ///
155    /// This function will load the GPIO configuration from standard system
156    /// locations as well as from the additional configs passed in via the
157    /// `configs` parameter.  Each parameter is expected to be a path to a
158    /// config file in disk.
159    ///
160    /// Under the covers, this function will attempt to discover configuration
161    /// files in the following standard locations in order:
162    ///
163    /// - `/etc/gpio.toml`
164    /// - `/etc/gpio.d/*.toml`
165    /// - `configs` (parameter)
166    ///
167    /// Each config file found in these locations will be loaded and then they
168    /// will be pulled together to form a unified configuration via the
169    /// `combine` method.
170    pub fn load(configs: &[String]) -> Result<GpioConfig, Error> {
171        let mut config_instances: Vec<GpioConfig> = Vec::new();
172
173        // check /etc/gpio.toml
174        if fs::metadata("/etc/gpio.toml").is_ok() {
175            config_instances.push(Self::from_file("/etc/gpio.toml")?);
176        }
177        // /etc/gpio.d/*.toml
178        for fragment in glob("/etc/gpio.d/*.toml").unwrap().filter_map(Result::ok) {
179            config_instances.push(Self::from_file(fragment)?);
180        }
181
182        // additional from command-line
183        for fragment in configs {
184            config_instances.push(Self::from_file(fragment)?);
185        }
186
187        if config_instances.is_empty() {
188            Err(Error::NoConfigFound)
189        } else {
190            let mut cfg = config_instances.remove(0);
191            for higher_priority_cfg in config_instances {
192                cfg.update(higher_priority_cfg)?;
193            }
194            Ok(cfg)
195        }
196    }
197
198    /// Load a GPIO config from the specified path
199    pub fn from_file<P: AsRef<Path>>(path: P) -> Result<GpioConfig, Error> {
200        let mut contents = String::new();
201        let mut f = File::open(path)?;
202        f.read_to_string(&mut contents)?;
203        let config = GpioConfig::from_str(&contents[..])?;
204        config.validate()?;
205
206        Ok(config)
207    }
208
209    /// Get the pin with the provided name if present in this configuration
210    pub fn get_pin(&self, name: &str) -> Option<&PinConfig> {
211        // first, try to find pin by name
212        if let Some(pin) = self.pins.iter().find(|p| p.names.contains(name)) {
213            return Some(pin);
214        }
215
216        // Try to parse the name as a 64-bit integer and match against that
217        match name.parse::<u64>() {
218            Ok(pin_num) => self.pins.iter().find(|p| p.num == pin_num),
219            Err(_) => None,
220        }
221    }
222
223    /// Get a reference to all the pins in this config
224    pub fn get_pins(&self) -> &[PinConfig] {
225        &self.pins[..]
226    }
227
228    /// Get the symlink root specified in the config (or the default)
229    pub fn get_symlink_root(&self) -> &str {
230        match self.config.symlink_root {
231            Some(ref root) => root,
232            None => DEFAULT_SYMLINK_ROOT,
233        }
234    }
235
236    /// Merge other into self (takes ownership of other)
237    ///
238    /// If in conflict, the other GPIO config takes priority.
239    pub fn update(&mut self, other: GpioConfig) -> Result<(), Error> {
240        if let Some(symlink_root) = other.config.symlink_root {
241            self.config.symlink_root = Some(symlink_root);
242        }
243        for other_pin in other.pins {
244            // determine the case we are dealing with
245            let existing = match self.pins.iter_mut().find(|p| p.num == other_pin.num) {
246                Some(pin) => {
247                    pin.names.extend(other_pin.names.clone());
248                    pin.direction = other_pin.direction;
249                    pin.export = other_pin.export;
250                    pin.active_low = other_pin.active_low;
251                    true
252                }
253                None => false,
254            };
255
256            if !existing {
257                self.pins.push(other_pin);
258            }
259        }
260
261        // validate the resulting structure
262        self.validate()
263    }
264}
265
266#[cfg(test)]
267mod test {
268    use super::*;
269    use std::collections::BTreeSet;
270    use std::iter::FromIterator;
271    use std::str::FromStr;
272    use sysfs_gpio::Direction as D;
273
274    const BASIC_CFG: &'static str = r#"
275[[pins]]
276num = 73
277names = ["reset_button"]
278direction = "in"   # default: in
279active_low = true  # default: false
280export = true      # default: true
281
282[[pins]]
283num = 37
284names = ["status_led", "A27", "green_led"]
285direction = "out"
286"#;
287
288    const COMPACT_CFG: &'static str = r#"
289pins = [
290   { num = 73, names = ["reset_button"], direction = "in", active_low = true, export = true},
291   { num = 37, names = ["status_led", "A27", "green_led"], direction = "out"},
292]
293
294[config]
295symlink_root = "/tmp/gpio"
296"#;
297
298    const MISSING_PINNUM_CFG: &'static str = r#"
299[[pins]]
300export = true
301"#;
302
303    const DUPLICATED_NAMES_CFG: &'static str = r#"
304[[pins]]
305num = 25
306names = ["foo", "bar"]
307
308[[pins]]
309num = 26
310names = ["baz", "foo"]  # foo is repeated!
311"#;
312
313    const PARTIALLY_OVERLAPS_BASIC_CFG: &'static str = r#"
314[config]
315symlink_root = "/foo/bar/baz"
316
317# Add a new alias to pin 73
318[[pins]]
319num = 73
320names = ["new_name"]
321
322
323# Change pin 37 to be an input (not output)
324[[pins]]
325num = 37
326direction = "in"
327
328# New pin 88
329[[pins]]
330num = 88
331names = ["wildcard"]
332"#;
333
334    #[test]
335    fn test_parse_basic() {
336        let config = GpioConfig::from_str(BASIC_CFG).unwrap();
337        let status_led = config.pins.get(1).unwrap();
338        let names = BTreeSet::from_iter(vec![
339            String::from("status_led"),
340            String::from("A27"),
341            String::from("green_led"),
342        ]);
343
344        assert_eq!(config.get_symlink_root(), "/var/run/gpio");
345
346        let reset_button = config.pins.get(0).unwrap();
347        assert_eq!(reset_button.num, 73);
348        assert_eq!(
349            reset_button.names,
350            BTreeSet::from_iter(vec![String::from("reset_button")])
351        );
352        assert_eq!(reset_button.direction, D::In);
353        assert_eq!(reset_button.active_low, true);
354        assert_eq!(reset_button.export, true);
355
356        assert_eq!(status_led.names, names);
357        assert_eq!(status_led.direction, D::Out);
358        assert_eq!(status_led.active_low, false);
359        assert_eq!(status_led.export, true);
360    }
361
362    #[test]
363    fn test_get_pin_present() {
364        let config = GpioConfig::from_str(BASIC_CFG).unwrap();
365        let status_led = config.get_pin("status_led").unwrap();
366        assert_eq!(status_led.num, 37);
367    }
368
369    #[test]
370    fn test_get_pin_not_present() {
371        let config = GpioConfig::from_str(BASIC_CFG).unwrap();
372        assert_eq!(config.get_pin("missing"), None);
373    }
374
375    #[test]
376    fn test_get_pin_by_number() {
377        let config = GpioConfig::from_str(BASIC_CFG).unwrap();
378        let status_led = config.get_pin("37").unwrap();
379        assert_eq!(status_led.num, 37);
380    }
381
382    #[test]
383    fn test_get_pin_by_number_not_found() {
384        let config = GpioConfig::from_str(BASIC_CFG).unwrap();
385        assert_eq!(config.get_pin("64"), None);
386    }
387
388    #[test]
389    fn test_parser_compact() {
390        let config = GpioConfig::from_str(COMPACT_CFG).unwrap();
391        let status_led = config.pins.get(1).unwrap();
392        let names = BTreeSet::from_iter(vec![
393            String::from("status_led"),
394            String::from("A27"),
395            String::from("green_led"),
396        ]);
397        assert_eq!(status_led.names, names);
398        assert_eq!(status_led.direction, D::Out);
399        assert_eq!(status_led.active_low, false);
400        assert_eq!(status_led.export, true);
401        assert_eq!(config.get_symlink_root(), "/tmp/gpio")
402    }
403
404    #[test]
405    fn test_parser_empty_toml() {
406        let configstr = "";
407        match GpioConfig::from_str(configstr) {
408            Ok(pins) => assert_eq!(pins.pins, vec![]),
409            Err(Error::ParserErrors(_)) => {}
410            _ => panic!("Expected a parsing error"),
411        }
412    }
413
414    #[test]
415    fn test_parser_missing_pinnum() {
416        match GpioConfig::from_str(MISSING_PINNUM_CFG) {
417            Err(Error::ParserErrors(_)) => {}
418            _ => panic!("Expected a parsing error"),
419        }
420    }
421
422    #[test]
423    fn test_parse_error_bad_toml() {
424        // basically, just garbage data
425        let configstr = r"[] -*-..asdf=-=-@#$%^&*()";
426        match GpioConfig::from_str(configstr) {
427            Err(Error::ParserErrors(_)) => {}
428            _ => panic!("Did not receive parse error when expected"),
429        }
430    }
431
432    #[test]
433    fn test_error_on_duplicated_names() {
434        match GpioConfig::from_str(DUPLICATED_NAMES_CFG) {
435            Err(Error::DuplicateNames(_)) => (),
436            r => panic!("Expected DuplicateNames Error, got {:?}", r),
437        }
438    }
439
440    #[test]
441    fn test_merge_configs() {
442        let mut config = GpioConfig::from_str(BASIC_CFG).unwrap();
443        let cfg2 = GpioConfig::from_str(PARTIALLY_OVERLAPS_BASIC_CFG).unwrap();
444
445        // perform the merge
446        config.update(cfg2).unwrap();
447
448        assert_eq!(config.get_symlink_root(), "/foo/bar/baz");
449
450        let reset_button = config.pins.get(0).unwrap();
451        assert_eq!(reset_button.num, 73);
452        assert_eq!(
453            reset_button.names,
454            BTreeSet::from_iter(vec![String::from("reset_button"), String::from("new_name")])
455        );
456        assert_eq!(reset_button.direction, D::In);
457        assert_eq!(reset_button.active_low, false);
458        assert_eq!(reset_button.export, true);
459
460        let status_led = config.pins.get(1).unwrap();
461        let names = BTreeSet::from_iter(vec![
462            String::from("status_led"),
463            String::from("A27"),
464            String::from("green_led"),
465        ]);
466        assert_eq!(status_led.names, names);
467        assert_eq!(status_led.direction, D::In);
468        assert_eq!(status_led.active_low, false);
469        assert_eq!(status_led.export, true);
470
471        let wildcard = config.pins.get(2).unwrap();
472        assert_eq!(wildcard.num, 88);
473        assert_eq!(
474            wildcard.names,
475            BTreeSet::from_iter(vec![String::from("wildcard")])
476        );
477    }
478}