Create host-side tool to attach bootconfig to initrd image

We are removing ABL from VM bootflow & using kernel's initrd
functionality. Since the boot configuration file is loaded with initrd
by default, it will be added to the end of the initrd, in the following
way:

[initrd][bootconfig][padding][size(le32)][checksum(le32)][#BOOTCONFIG\n]

The tool is written in Rust so that we can (in future) have the option
to dynamically attach the correct bootconfig.

Test: builds
Bug: 240235424
Change-Id: Idfca6528a16bc92001f5791b4683a0be2714cce8
diff --git a/microdroid/initrd/Android.bp b/microdroid/initrd/Android.bp
new file mode 100644
index 0000000..147c963
--- /dev/null
+++ b/microdroid/initrd/Android.bp
@@ -0,0 +1,13 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+rust_binary_host {
+    name: "initrd_bootconfig",
+    srcs: ["src/main.rs"],
+    rustlibs: [
+        "libanyhow",
+        "libstructopt",
+    ],
+    prefer_rlib: true,
+}
diff --git a/microdroid/initrd/src/main.rs b/microdroid/initrd/src/main.rs
new file mode 100644
index 0000000..1023a40
--- /dev/null
+++ b/microdroid/initrd/src/main.rs
@@ -0,0 +1,77 @@
+// Copyright 2022, 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.
+
+//! Append bootconfig to initrd image
+use anyhow::Result;
+
+use std::fs::File;
+use std::io::{Read, Write};
+use std::path::PathBuf;
+use structopt::StructOpt;
+
+const FOOTER_ALIGNMENT: usize = 4;
+const ZEROS: [u8; 4] = [0u8; 4_usize];
+
+#[derive(StructOpt, Debug)]
+struct Args {
+    /// Output
+    #[structopt(parse(from_os_str), long = "output")]
+    output: PathBuf,
+    /// Initrd (without bootconfig)
+    #[structopt(parse(from_os_str))]
+    initrd: PathBuf,
+    /// Bootconfig
+    #[structopt(parse(from_os_str))]
+    bootconfigs: Vec<PathBuf>,
+}
+
+fn get_checksum(file_path: &PathBuf) -> Result<u32> {
+    File::open(file_path)?.bytes().map(|x| Ok(x? as u32)).sum()
+}
+
+// Bootconfig is attached to the initrd in the following way:
+// [initrd][bootconfig][padding][size(le32)][checksum(le32)][#BOOTCONFIG\n]
+fn attach_bootconfig(initrd: PathBuf, bootconfigs: Vec<PathBuf>, output: PathBuf) -> Result<()> {
+    let mut output_file = File::create(&output)?;
+    let mut initrd_file = File::open(&initrd)?;
+    let initrd_size: usize = initrd_file.metadata()?.len().try_into()?;
+    let mut bootconfig_size: usize = 0;
+    let mut checksum: u32 = 0;
+
+    std::io::copy(&mut initrd_file, &mut output_file)?;
+    for bootconfig in bootconfigs {
+        let mut bootconfig_file = File::open(&bootconfig)?;
+        std::io::copy(&mut bootconfig_file, &mut output_file)?;
+        bootconfig_size += bootconfig_file.metadata()?.len() as usize;
+        checksum += get_checksum(&bootconfig)?;
+    }
+
+    let padding_size: usize = FOOTER_ALIGNMENT - (initrd_size + bootconfig_size) % FOOTER_ALIGNMENT;
+    output_file.write_all(&ZEROS[..padding_size])?;
+    output_file.write_all(&((padding_size + bootconfig_size) as u32).to_le_bytes())?;
+    output_file.write_all(&checksum.to_le_bytes())?;
+    output_file.write_all(b"#BOOTCONFIG\n")?;
+    output_file.flush()?;
+    Ok(())
+}
+
+fn try_main() -> Result<()> {
+    let args = Args::from_args_safe()?;
+    attach_bootconfig(args.initrd, args.bootconfigs, args.output)?;
+    Ok(())
+}
+
+fn main() {
+    try_main().unwrap()
+}