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