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();
+}