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