blob: e176b7be20bbf08e828b8e4ce17a8cb40dba86d1 [file] [log] [blame]
// Copyright 2024 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Implements converting file system to FDT blob
use anyhow::{anyhow, Context, Result};
use libfdt::Fdt;
use std::ffi::{CStr, CString};
use std::fs;
use std::os::unix::ffi::OsStrExt;
use std::path::Path;
/// Trait for Fdt's file system support
pub trait FsFdt<'a> {
/// Creates a Fdt from /proc/device-tree style directory by wrapping a mutable slice
fn from_fs(fs_path: &Path, fdt_buffer: &'a mut [u8]) -> Result<&'a mut Self>;
/// Overlay an FDT from /proc/device-tree style directory at the given node path
fn overlay_onto(&mut self, fdt_node_path: &CStr, fs_path: &Path) -> Result<()>;
}
impl<'a> FsFdt<'a> for Fdt {
fn from_fs(fs_path: &Path, fdt_buffer: &'a mut [u8]) -> Result<&'a mut Fdt> {
let fdt = Fdt::create_empty_tree(fdt_buffer)
.map_err(|e| anyhow!("Failed to create FDT, {e:?}"))?;
fdt.overlay_onto(&CString::new("").unwrap(), fs_path)?;
Ok(fdt)
}
fn overlay_onto(&mut self, fdt_node_path: &CStr, fs_path: &Path) -> Result<()> {
// Recursively traverse fs_path with DFS algorithm.
let mut stack = vec![fs_path.to_path_buf()];
while let Some(dir_path) = stack.pop() {
let relative_path = dir_path
.strip_prefix(fs_path)
.context("Internal error. Path does not have expected prefix")?
.as_os_str();
let fdt_path = CString::from_vec_with_nul(
[fdt_node_path.to_bytes(), b"/", relative_path.as_bytes(), b"\0"].concat(),
)
.context("Internal error. Path is not a valid Fdt path")?;
let mut node = self
.node_mut(&fdt_path)
.map_err(|e| anyhow!("Failed to write FDT, {e:?}"))?
.ok_or_else(|| anyhow!("Failed to find {fdt_path:?} in FDT"))?;
let mut subnode_names = vec![];
let entries =
fs::read_dir(&dir_path).with_context(|| format!("Failed to read {dir_path:?}"))?;
for entry in entries {
let entry =
entry.with_context(|| format!("Failed to get an entry in {dir_path:?}"))?;
let entry_type =
entry.file_type().with_context(|| "Unsupported entry type, {entry:?}")?;
let entry_name = entry.file_name(); // binding to keep name below.
if !entry_name.is_ascii() {
return Err(anyhow!("Unsupported entry name for FDT, {entry:?}"));
}
// Safe to unwrap because validated as an ascii string above.
let name = CString::new(entry_name.as_bytes()).unwrap();
if entry_type.is_dir() {
stack.push(entry.path());
subnode_names.push(name);
} else if entry_type.is_file() {
let value = fs::read(&entry.path())?;
node.setprop(&name, &value)
.map_err(|e| anyhow!("Failed to set FDT property, {e:?}"))?;
} else {
return Err(anyhow!(
"Failed to handle {entry:?}. FDT only uses file or directory"
));
}
}
// Note: sort() is necessary to prevent FdtError::Exists from add_subnodes().
// FDT library may omit address in node name when comparing their name, so sort to add
// node without address first.
subnode_names.sort();
let subnode_names: Vec<_> = subnode_names
.iter()
.filter_map(|name| {
// Filter out subnode names which are already present in the target parent node!
let name = name.as_c_str();
let is_present_res = node.as_node().subnode(name);
match is_present_res {
Ok(Some(_)) => None,
Ok(None) => Some(Ok(name)),
Err(e) => Some(Err(e)),
}
})
.collect::<Result<_, _>>()
.map_err(|e| anyhow!("Failed to filter subnodes, {e:?}"))?;
node.add_subnodes(&subnode_names).map_err(|e| anyhow!("Failed to add node, {e:?}"))?;
}
Ok(())
}
}
#[cfg(test)]
mod test {
use super::*;
use dts::Dts;
const TEST_FS_FDT_ROOT_PATH: &str = "testdata/fs";
const BUF_SIZE_MAX: usize = 1024;
#[test]
fn test_from_fs() {
let fs_path = Path::new(TEST_FS_FDT_ROOT_PATH);
let mut data = vec![0_u8; BUF_SIZE_MAX];
let fdt = Fdt::from_fs(fs_path, &mut data).unwrap();
let expected = Dts::from_fs(fs_path).unwrap();
let actual = Dts::from_fdt(fdt).unwrap();
assert_eq!(&expected, &actual);
// Again append fdt from TEST_FS_FDT_ROOT_PATH at root & ensure it succeeds when some
// subnode are already present.
fdt.overlay_onto(&CString::new("/").unwrap(), fs_path).unwrap();
}
}