virtmgr: Add libfsfdt for reading host DT from file system
Host DT blob exists at /sys/firmware/fdt, but it's only readable by
root. (permission is 400).
To workaround the permission issue, this CL reads host DT from file system
which doesn't have such permission issue. We already did the similar
for the debug policy.
Here are alternatives, but I didn't pick:
- Use libfdt in root process (e.g. vfio_handler) or modify the
file's permission: No-go. I can do the same with less permission.
No reason for having super-power.
- Use dtc implementation: No-go. I don't want to include a foreign
binary in our APEX. In addition, dtc exits current process if
something goes wrong, so wrapper library doesn't fit.
Bug: 318431695
Test: manually tested with host DT by using fsfdt and dtdiff tool.
Change-Id: Ic0640d5dee3165d957cffed958126d31de50633a
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();
+}