Merge "virtmgr: Add libfsfdt for reading host DT from file system" into main
diff --git a/virtualizationmanager/fsfdt/Android.bp b/virtualizationmanager/fsfdt/Android.bp
new file mode 100644
index 0000000..3a42bf3
--- /dev/null
+++ b/virtualizationmanager/fsfdt/Android.bp
@@ -0,0 +1,32 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+rust_binary {
+ name: "fsfdt",
+ crate_name: "fsfdt",
+ defaults: ["avf_build_flags_rust"],
+ edition: "2021",
+ srcs: ["src/main.rs"],
+ prefer_rlib: true,
+ rustlibs: [
+ "libanyhow",
+ "libclap",
+ "libfsfdt",
+ "liblibfdt",
+ ],
+}
+
+rust_library_rlib {
+ name: "libfsfdt",
+ crate_name: "fsfdt",
+ defaults: ["avf_build_flags_rust"],
+ edition: "2021",
+ srcs: ["src/lib.rs"],
+ prefer_rlib: true,
+ rustlibs: [
+ "liblibfdt",
+ "libanyhow",
+ ],
+ apex_available: ["com.android.virt"],
+}
diff --git a/virtualizationmanager/fsfdt/src/lib.rs b/virtualizationmanager/fsfdt/src/lib.rs
new file mode 100644
index 0000000..cda2fe1
--- /dev/null
+++ b/virtualizationmanager/fsfdt/src/lib.rs
@@ -0,0 +1,90 @@
+// 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::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>;
+}
+
+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:?}"))?;
+
+ // 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([b"/", relative_path.as_bytes(), b"\0"].concat())
+ .context("Internal error. Path is not a valid Fdt path")?;
+
+ let mut node = fdt
+ .node_mut(&fdt_path)
+ .map_err(|e| anyhow!("Failed to write FDT, {e:?}"))?
+ .ok_or_else(|| anyhow!("Internal error when writing VM reference DT"))?;
+
+ 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_c_str: Vec<_> = subnode_names.iter().map(|x| x.as_c_str()).collect();
+ node.add_subnodes(&subnode_names_c_str)
+ .map_err(|e| anyhow!("Failed to add node, {e:?}"))?;
+ }
+
+ Ok(fdt)
+ }
+}
diff --git a/virtualizationmanager/fsfdt/src/main.rs b/virtualizationmanager/fsfdt/src/main.rs
new file mode 100644
index 0000000..2fe71e7
--- /dev/null
+++ b/virtualizationmanager/fsfdt/src/main.rs
@@ -0,0 +1,42 @@
+// 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.
+
+//! CLI for converting file system to FDT
+
+use clap::Parser;
+use fsfdt::FsFdt;
+use libfdt::Fdt;
+use std::fs;
+use std::path::PathBuf;
+
+const FDT_MAX_SIZE: usize = 1_000_000_usize;
+
+/// Option parser
+#[derive(Parser, Debug)]
+struct Opt {
+ /// File system path (directory path) to parse from
+ fs_path: PathBuf,
+
+ /// FDT file path for writing
+ fdt_file_path: PathBuf,
+}
+
+fn main() {
+ let opt = Opt::parse();
+
+ let mut data = vec![0_u8; FDT_MAX_SIZE];
+ let fdt = Fdt::from_fs(&opt.fs_path, &mut data).unwrap();
+ fdt.pack().unwrap();
+ fs::write(&opt.fdt_file_path, fdt.as_slice()).unwrap();
+}