cros_fdt/
path.rs

1// Copyright 2023 The ChromiumOS Authors
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! This module implements DT path handling.
6
7use std::fmt;
8use std::str::FromStr;
9
10use crate::fdt::Error;
11use crate::fdt::Result;
12
13pub(crate) const PATH_SEP: &str = "/";
14
15// Property name and offset containing a phandle value.
16#[derive(Debug, PartialEq)]
17pub(crate) struct PhandlePin(pub String, pub u32);
18
19/// Device tree path.
20#[derive(Clone, Debug, PartialEq, PartialOrd, Ord, Eq, Hash)]
21pub struct Path(String);
22
23impl Path {
24    // Verify path and strip unneeded characters.
25    fn sanitize(path: &str) -> Result<String> {
26        if path.is_empty() || !path.starts_with(PATH_SEP) {
27            return Err(Error::InvalidPath(format!("{path} is not absolute")));
28        } else if path == PATH_SEP {
29            return Ok(path.into());
30        }
31        let path = path.trim_end_matches(PATH_SEP);
32        if path.is_empty() || path.split(PATH_SEP).skip(1).any(|c| c.is_empty()) {
33            Err(Error::InvalidPath("empty component in path".into()))
34        } else {
35            assert!(path.starts_with(PATH_SEP));
36            Ok(path.into())
37        }
38    }
39
40    // Create a new Path.
41    pub(crate) fn new(path: &str) -> Result<Self> {
42        Ok(Self(Self::sanitize(path)?))
43    }
44
45    // Push a new path segment, creating a new path.
46    pub(crate) fn push(&self, subpath: &str) -> Result<Self> {
47        let mut new_path = self.0.clone();
48        if !new_path.ends_with(PATH_SEP) {
49            new_path.push_str(PATH_SEP);
50        }
51        new_path.push_str(
52            subpath
53                .trim_start_matches(PATH_SEP)
54                .trim_end_matches(PATH_SEP),
55        );
56        Ok(Self(Self::sanitize(&new_path)?))
57    }
58
59    // Iterate path segments.
60    pub(crate) fn iter(&self) -> impl Iterator<Item = &str> {
61        self.0
62            .split(PATH_SEP)
63            .skip(if self.0 == PATH_SEP { 2 } else { 1 }) // Skip empty segments at start
64    }
65
66    // Return `true` if the path points to a child of `other`.
67    pub(crate) fn is_child_of(&self, other: &Path) -> bool {
68        let mut self_iter = self.iter();
69        for elem in other.iter() {
70            if self_iter.next() != Some(elem) {
71                return false;
72            }
73        }
74        true
75    }
76}
77
78impl FromStr for Path {
79    type Err = Error;
80
81    fn from_str(value: &str) -> Result<Self> {
82        Path::new(value)
83    }
84}
85
86impl TryFrom<&str> for Path {
87    type Error = Error;
88
89    fn try_from(value: &str) -> Result<Path> {
90        value.parse()
91    }
92}
93
94impl TryFrom<String> for Path {
95    type Error = Error;
96
97    fn try_from(value: String) -> Result<Path> {
98        value.parse()
99    }
100}
101
102impl From<Path> for String {
103    fn from(val: Path) -> Self {
104        val.0 // Return path
105    }
106}
107
108impl AsRef<str> for Path {
109    fn as_ref(&self) -> &str {
110        self.0.as_str()
111    }
112}
113
114impl fmt::Display for Path {
115    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
116        write!(f, "{}", self.0)
117    }
118}
119
120// Parse a DT path string containing a node path and a property location (name and offset),
121// eg '/path/to/node:prop1:4'.
122pub(crate) fn parse_path_with_prop(value: &str) -> Result<(Path, PhandlePin)> {
123    const PROP_SEP: char = ':';
124    let mut elements = value.split(PROP_SEP);
125    let path: Path = elements.next().unwrap().parse()?; // There will always be at least one.
126    let prop = elements
127        .next()
128        .ok_or_else(|| Error::InvalidPath("missing property part".into()))?
129        .to_owned();
130    let off: u32 = elements
131        .next()
132        .ok_or_else(|| Error::InvalidPath("missing offset part".into()))?
133        .parse()
134        .map_err(|_| Error::InvalidPath("cannot parse offset as u32".into()))?;
135    Ok((path, PhandlePin(prop, off)))
136}
137
138#[cfg(test)]
139mod tests {
140    use super::*;
141
142    #[test]
143    fn fdt_parse_path() {
144        let l: Path = "/".parse().unwrap();
145        assert!(l.iter().next().is_none());
146
147        let l: Path = "/a/b/c".parse().unwrap();
148        assert!(l.iter().eq(["a", "b", "c"]));
149
150        let (path, prop) = parse_path_with_prop("/:a:0").unwrap();
151        assert!(path.iter().next().is_none());
152        assert_eq!(prop.0, "a");
153        assert_eq!(prop.1, 0);
154
155        let (path, prop) = parse_path_with_prop("/a/b/c:defg:1").unwrap();
156        assert!(path.iter().eq(["a", "b", "c"]));
157        assert_eq!(prop.0, "defg");
158        assert_eq!(prop.1, 1);
159    }
160
161    #[test]
162    fn fdt_path_parse_invalid() {
163        assert!(Path::from_str("").is_err());
164        assert!(Path::from_str("/a/b//c").is_err());
165        assert!(Path::from_str("a/b").is_err());
166        assert!(Path::from_str("a").is_err());
167        parse_path_with_prop("a").expect_err("parse error");
168        parse_path_with_prop("a::").expect_err("parse error");
169        parse_path_with_prop("/a/b:c:").expect_err("parse error");
170        parse_path_with_prop("/a/b:c:p:w").expect_err("parse error");
171    }
172
173    #[test]
174    fn fdt_path_from_empty() {
175        let mut path = Path::new("/").unwrap();
176        assert!(path.iter().next().is_none());
177        path = path.push("abc").unwrap();
178        assert!(path.iter().eq(["abc",]));
179        path = Path::new("/").unwrap();
180        path = path.push("a/b/c").unwrap();
181        assert!(path.iter().eq(["a", "b", "c"]));
182    }
183
184    #[test]
185    fn fdt_path_create() {
186        let mut path = Path::new("/a/b/c").unwrap();
187        path = path.push("de").unwrap();
188        assert!(path.iter().eq(["a", "b", "c", "de"]));
189        path = path.push("f/g/h").unwrap();
190        assert!(path.iter().eq(["a", "b", "c", "de", "f", "g", "h"]));
191    }
192
193    #[test]
194    fn fdt_path_childof() {
195        let path = Path::new("/aaa/bbb/ccc").unwrap();
196        assert!(path.is_child_of(&Path::new("/aaa").unwrap()));
197        assert!(path.is_child_of(&Path::new("/aaa/bbb").unwrap()));
198        assert!(path.is_child_of(&Path::new("/aaa/bbb/ccc").unwrap()));
199        assert!(!path.is_child_of(&Path::new("/aaa/bbb/ccc/ddd").unwrap()));
200        assert!(!path.is_child_of(&Path::new("/aa").unwrap()));
201        assert!(!path.is_child_of(&Path::new("/aaa/bb").unwrap()));
202        assert!(!path.is_child_of(&Path::new("/d").unwrap()));
203        assert!(!path.is_child_of(&Path::new("/d/e").unwrap()));
204    }
205}