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 //! Implements converting file system to FDT blob
16 
17 use anyhow::{anyhow, Context, Result};
18 use libfdt::Fdt;
19 use std::ffi::{CStr, CString};
20 use std::fs;
21 use std::os::unix::ffi::OsStrExt;
22 use std::path::Path;
23 
24 /// Trait for Fdt's file system support
25 pub trait FsFdt<'a> {
26     /// Creates a Fdt from /proc/device-tree style directory by wrapping a mutable slice
from_fs(fs_path: &Path, fdt_buffer: &'a mut [u8]) -> Result<&'a mut Self>27     fn from_fs(fs_path: &Path, fdt_buffer: &'a mut [u8]) -> Result<&'a mut Self>;
28 
29     /// Overlay an FDT from /proc/device-tree style directory at the given node path
overlay_onto(&mut self, fdt_node_path: &CStr, fs_path: &Path) -> Result<()>30     fn overlay_onto(&mut self, fdt_node_path: &CStr, fs_path: &Path) -> Result<()>;
31 }
32 
33 impl<'a> FsFdt<'a> for Fdt {
from_fs(fs_path: &Path, fdt_buffer: &'a mut [u8]) -> Result<&'a mut Fdt>34     fn from_fs(fs_path: &Path, fdt_buffer: &'a mut [u8]) -> Result<&'a mut Fdt> {
35         let fdt = Fdt::create_empty_tree(fdt_buffer)
36             .map_err(|e| anyhow!("Failed to create FDT, {e:?}"))?;
37 
38         fdt.overlay_onto(&CString::new("").unwrap(), fs_path)?;
39 
40         Ok(fdt)
41     }
42 
overlay_onto(&mut self, fdt_node_path: &CStr, fs_path: &Path) -> Result<()>43     fn overlay_onto(&mut self, fdt_node_path: &CStr, fs_path: &Path) -> Result<()> {
44         // Recursively traverse fs_path with DFS algorithm.
45         let mut stack = vec![fs_path.to_path_buf()];
46         while let Some(dir_path) = stack.pop() {
47             let relative_path = dir_path
48                 .strip_prefix(fs_path)
49                 .context("Internal error. Path does not have expected prefix")?
50                 .as_os_str();
51             let fdt_path = CString::from_vec_with_nul(
52                 [fdt_node_path.to_bytes(), b"/", relative_path.as_bytes(), b"\0"].concat(),
53             )
54             .context("Internal error. Path is not a valid Fdt path")?;
55 
56             let mut node = self
57                 .node_mut(&fdt_path)
58                 .map_err(|e| anyhow!("Failed to write FDT, {e:?}"))?
59                 .ok_or_else(|| anyhow!("Failed to find {fdt_path:?} in FDT"))?;
60 
61             let mut subnode_names = vec![];
62             let entries =
63                 fs::read_dir(&dir_path).with_context(|| format!("Failed to read {dir_path:?}"))?;
64             for entry in entries {
65                 let entry =
66                     entry.with_context(|| format!("Failed to get an entry in {dir_path:?}"))?;
67                 let entry_type =
68                     entry.file_type().with_context(|| "Unsupported entry type, {entry:?}")?;
69                 let entry_name = entry.file_name(); // binding to keep name below.
70                 if !entry_name.is_ascii() {
71                     return Err(anyhow!("Unsupported entry name for FDT, {entry:?}"));
72                 }
73                 // Safe to unwrap because validated as an ascii string above.
74                 let name = CString::new(entry_name.as_bytes()).unwrap();
75                 if entry_type.is_dir() {
76                     stack.push(entry.path());
77                     subnode_names.push(name);
78                 } else if entry_type.is_file() {
79                     let value = fs::read(&entry.path())?;
80 
81                     node.setprop(&name, &value)
82                         .map_err(|e| anyhow!("Failed to set FDT property, {e:?}"))?;
83                 } else {
84                     return Err(anyhow!(
85                         "Failed to handle {entry:?}. FDT only uses file or directory"
86                     ));
87                 }
88             }
89             // Note: sort() is necessary to prevent FdtError::Exists from add_subnodes().
90             // FDT library may omit address in node name when comparing their name, so sort to add
91             // node without address first.
92             subnode_names.sort();
93             let subnode_names: Vec<_> = subnode_names
94                 .iter()
95                 .filter_map(|name| {
96                     // Filter out subnode names which are already present in the target parent node!
97                     let name = name.as_c_str();
98                     let is_present_res = node.as_node().subnode(name);
99                     match is_present_res {
100                         Ok(Some(_)) => None,
101                         Ok(None) => Some(Ok(name)),
102                         Err(e) => Some(Err(e)),
103                     }
104                 })
105                 .collect::<Result<_, _>>()
106                 .map_err(|e| anyhow!("Failed to filter subnodes, {e:?}"))?;
107             node.add_subnodes(&subnode_names).map_err(|e| anyhow!("Failed to add node, {e:?}"))?;
108         }
109 
110         Ok(())
111     }
112 }
113 
114 #[cfg(test)]
115 mod test {
116     use super::*;
117     use dts::Dts;
118 
119     const TEST_FS_FDT_ROOT_PATH: &str = "testdata/fs";
120     const BUF_SIZE_MAX: usize = 1024;
121 
122     #[test]
test_from_fs()123     fn test_from_fs() {
124         let fs_path = Path::new(TEST_FS_FDT_ROOT_PATH);
125 
126         let mut data = vec![0_u8; BUF_SIZE_MAX];
127         let fdt = Fdt::from_fs(fs_path, &mut data).unwrap();
128 
129         let expected = Dts::from_fs(fs_path).unwrap();
130         let actual = Dts::from_fdt(fdt).unwrap();
131 
132         assert_eq!(&expected, &actual);
133         // Again append fdt from TEST_FS_FDT_ROOT_PATH at root & ensure it succeeds when some
134         // subnode are already present.
135         fdt.overlay_onto(&CString::new("/").unwrap(), fs_path).unwrap();
136     }
137 }
138