blob: db3aac2d46bf72ecb10627ca9b98353c8e744c9f [file] [log] [blame]
Elie Kheirallahea5dd522024-11-26 22:16:01 +00001// 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
18use anyhow::anyhow;
19use anyhow::Context;
20use anyhow::Result;
21use clap::Parser;
22use hex::encode;
23use libfdt::Fdt;
24use libfdt::FdtNode;
25
26use std::collections::BTreeMap;
27use std::collections::BTreeSet;
28use std::fs::read;
29use std::path::PathBuf;
30
31#[derive(Debug, Parser)]
32/// Device Tree Compare arguments.
33struct 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
53fn 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.
63fn 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
90fn 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
141fn 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}