• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2024 The Android Open Source Project
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 //! Compare device tree contents.
16 //! Allows skipping over fields provided.
17 
18 use anyhow::anyhow;
19 use anyhow::Context;
20 use anyhow::Result;
21 use clap::Parser;
22 use hex::encode;
23 use libfdt::Fdt;
24 use libfdt::FdtNode;
25 
26 use std::collections::BTreeMap;
27 use std::collections::BTreeSet;
28 use std::fs::read;
29 use std::path::PathBuf;
30 
31 #[derive(Debug, Parser)]
32 /// Device Tree Compare arguments.
33 struct DtCompareArgs {
34     /// first device tree
35     #[arg(long)]
36     dt1: PathBuf,
37     /// second device tree
38     #[arg(long)]
39     dt2: PathBuf,
40     /// list of properties that should exist but are expected to hold different values in the
41     /// trees.
42     #[arg(short = 'I', long)]
43     ignore_path_value: Vec<String>,
44     /// list of paths that will be ignored, whether added, removed, or changed.
45     /// Paths can be nodes, subnodes, or even properties:
46     /// Ex: /avf/unstrusted // this is a path to a subnode. All properties and subnodes underneath
47     ///                     // it will also be ignored.
48     ///     /avf/name       // This is a path for a property. Only this property will be ignored.
49     #[arg(short = 'S', long)]
50     ignore_path: Vec<String>,
51 }
52 
main() -> Result<()>53 fn main() -> Result<()> {
54     let args = DtCompareArgs::parse();
55     let dt1: Vec<u8> = read(args.dt1)?;
56     let dt2: Vec<u8> = read(args.dt2)?;
57     let ignore_value_set = BTreeSet::from_iter(args.ignore_path_value);
58     let ignore_set = BTreeSet::from_iter(args.ignore_path);
59     compare_device_trees(dt1.as_slice(), dt2.as_slice(), ignore_value_set, ignore_set)
60 }
61 
62 // Compare device trees by doing a pre-order traversal of the trees.
compare_device_trees( dt1: &[u8], dt2: &[u8], ignore_value_set: BTreeSet<String>, ignore_set: BTreeSet<String>, ) -> Result<()>63 fn compare_device_trees(
64     dt1: &[u8],
65     dt2: &[u8],
66     ignore_value_set: BTreeSet<String>,
67     ignore_set: BTreeSet<String>,
68 ) -> Result<()> {
69     let fdt1 = Fdt::from_slice(dt1).context("invalid device tree: Dt1")?;
70     let fdt2 = Fdt::from_slice(dt2).context("invalid device tree: Dt2")?;
71     let mut errors = Vec::new();
72     compare_subnodes(
73         &fdt1.root(),
74         &fdt2.root(),
75         &ignore_value_set,
76         &ignore_set,
77         /* path */ &mut ["".to_string()],
78         &mut errors,
79     )?;
80     if !errors.is_empty() {
81         return Err(anyhow!(
82             "Following properties had different values: [\n{}\n]\ndetected {} diffs",
83             errors.join("\n"),
84             errors.len()
85         ));
86     }
87     Ok(())
88 }
89 
compare_props( root1: &FdtNode, root2: &FdtNode, ignore_value_set: &BTreeSet<String>, ignore_set: &BTreeSet<String>, path: &mut [String], errors: &mut Vec<String>, ) -> Result<()>90 fn compare_props(
91     root1: &FdtNode,
92     root2: &FdtNode,
93     ignore_value_set: &BTreeSet<String>,
94     ignore_set: &BTreeSet<String>,
95     path: &mut [String],
96     errors: &mut Vec<String>,
97 ) -> Result<()> {
98     let mut prop_map: BTreeMap<String, &[u8]> = BTreeMap::new();
99     for prop in root1.properties().context("Error getting properties")? {
100         let prop_path =
101             path.join("/") + "/" + prop.name().context("Error getting property name")?.to_str()?;
102         // Do not add to prop map if skipping
103         if ignore_set.contains(&prop_path) {
104             continue;
105         }
106         let value = prop.value().context("Error getting value")?;
107         if prop_map.insert(prop_path.clone(), value).is_some() {
108             return Err(anyhow!("Duplicate property detected in subnode: {}", prop_path));
109         }
110     }
111     for prop in root2.properties().context("Error getting properties")? {
112         let prop_path =
113             path.join("/") + "/" + prop.name().context("Error getting property name")?.to_str()?;
114         if ignore_set.contains(&prop_path) {
115             continue;
116         }
117         let Some(prop1_value) = prop_map.remove(&prop_path) else {
118             errors.push(format!("added prop_path: {}", prop_path));
119             continue;
120         };
121         let prop_compare = prop1_value == prop.value().context("Error getting value")?;
122         // Check if value should be ignored. If yes, skip field.
123         if ignore_value_set.contains(&prop_path) {
124             continue;
125         }
126         if !prop_compare {
127             errors.push(format!(
128                 "prop {} value mismatch: old: {} -> new: {}",
129                 prop_path,
130                 encode(prop1_value),
131                 encode(prop.value().context("Error getting value")?)
132             ));
133         }
134     }
135     if !prop_map.is_empty() {
136         errors.push(format!("missing properties: {:?}", prop_map));
137     }
138     Ok(())
139 }
140 
compare_subnodes( node1: &FdtNode, node2: &FdtNode, ignore_value_set: &BTreeSet<String>, ignore_set: &BTreeSet<String>, path: &mut [String], errors: &mut Vec<String>, ) -> Result<()>141 fn compare_subnodes(
142     node1: &FdtNode,
143     node2: &FdtNode,
144     ignore_value_set: &BTreeSet<String>,
145     ignore_set: &BTreeSet<String>,
146     path: &mut [String],
147     errors: &mut Vec<String>,
148 ) -> Result<()> {
149     let mut subnodes_map: BTreeMap<String, FdtNode> = BTreeMap::new();
150     for subnode in node1.subnodes().context("Error getting subnodes of first FDT")? {
151         let sn_path = path.join("/")
152             + "/"
153             + subnode.name().context("Error getting property name")?.to_str()?;
154         // Do not add to subnode map if skipping
155         if ignore_set.contains(&sn_path) {
156             continue;
157         }
158         if subnodes_map.insert(sn_path.clone(), subnode).is_some() {
159             return Err(anyhow!("Duplicate subnodes detected: {}", sn_path));
160         }
161     }
162     for sn2 in node2.subnodes().context("Error getting subnodes of second FDT")? {
163         let sn_path =
164             path.join("/") + "/" + sn2.name().context("Error getting subnode name")?.to_str()?;
165         let sn1 = subnodes_map.remove(&sn_path);
166         match sn1 {
167             Some(sn) => {
168                 compare_props(
169                     &sn,
170                     &sn2,
171                     ignore_value_set,
172                     ignore_set,
173                     &mut [sn_path.clone()],
174                     errors,
175                 )?;
176                 compare_subnodes(
177                     &sn,
178                     &sn2,
179                     ignore_value_set,
180                     ignore_set,
181                     &mut [sn_path.clone()],
182                     errors,
183                 )?;
184             }
185             None => errors.push(format!("added node: {}", sn_path)),
186         }
187     }
188     if !subnodes_map.is_empty() {
189         errors.push(format!("missing nodes: {:?}", subnodes_map));
190     }
191     Ok(())
192 }
193