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