Merge "Add build.prop for microdroid"
diff --git a/apex/Android.bp b/apex/Android.bp
index bb73630..9c201b4 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -20,22 +20,42 @@
"authfs", // TODO(victorhsieh): move to microdroid once we can run the test in VM.
"crosvm",
],
+ filesystems: [
+ "microdroid_super",
+ "microdroid_boot-5.10",
+ "microdroid_vendor_boot-5.10",
+ "microdroid_vbmeta",
+ "microdroid_vbmeta_system",
+ ],
},
x86_64: {
binaries: [
"authfs", // TODO(victorhsieh): move to microdroid once we can run the test in VM.
"crosvm",
],
+ filesystems: [
+ "microdroid_super",
+ "microdroid_boot-5.10",
+ "microdroid_vendor_boot-5.10",
+ "microdroid_vbmeta",
+ "microdroid_vbmeta_system",
+ ],
},
},
binaries: [
- "assemble_cvd",
"fd_server",
+ "mk_cdisk",
+ "mk_microdroid_signature",
"virtmanager",
"vm",
],
- filesystems: ["microdroid"],
- prebuilts: ["com.android.virt.init.rc"],
+ prebuilts: [
+ "com.android.virt.init.rc",
+ "microdroid_cdisk.json",
+ "microdroid_cdisk_env.json",
+ "microdroid_uboot_env",
+ "microdroid_bootloader",
+ ],
file_contexts: ":com.android.virt-file_contexts",
}
diff --git a/authfs/TEST_MAPPING b/authfs/TEST_MAPPING
index d2dc1d8..d0c0b09 100644
--- a/authfs/TEST_MAPPING
+++ b/authfs/TEST_MAPPING
@@ -2,9 +2,6 @@
"presubmit": [
{
"name": "authfs_device_test_src_lib"
- },
- {
- "name": "AuthFsTestCases"
}
]
}
diff --git a/docs/getting_started/index.md b/docs/getting_started/index.md
index e43124c..9c06367 100644
--- a/docs/getting_started/index.md
+++ b/docs/getting_started/index.md
@@ -16,15 +16,14 @@
[packages/modules/Virtualization](https://android.googlesource.com/platform/packages/modules/Virtualization)
of the AOSP repository.
-### Host-side tests
+### Device-side tests
-These are tests where the test driver runs on the "host" (your computer) and it issues commands to
-the "target" (the connected device or emulator) over ADB. The tests spawn guest VMs and test
-different aspects of the architecture.
+The tests spawn guest VMs and test different aspects of the architecture.
You can build and run them with:
-``` shell
-atest VirtualizationHostTestCases
+
+```shell
+atest VirtualizationTestCases
```
If you run into problems, inspect the logs produced by `atest`. Their location is printed at the
@@ -35,36 +34,47 @@
[CrosVM](https://android.googlesource.com/platform/external/crosvm/) is a Rust-based Virtual Machine
Monitor (VMM) originally built for ChromeOS and ported to Android.
-It is not installed in regular Android builds (yet!), but it's installed in the
-VIM3L (yukawa) build, as part of the `com.android.virt` APEX.
-builds.
+It is not installed in regular Android builds (yet!), but it's installed in the VIM3L (yukawa)
+build, as part of the `com.android.virt` APEX.
### Spawning your own VMs
-You can spawn your own VMs by running CrosVM directly on a rooted KVM-enabled device. If your
-device is attached over ADB, you can run:
-``` shell
+You can spawn your own VMs by passing a JSON config file to the Virt Manager via the `vm` tool on a
+rooted KVM-enabled device. If your device is attached over ADB, you can run:
+
+```shell
+$ cat > vm_config.json
+{
+ "kernel": "/data/local/tmp/kernel",
+ "initrd": "/data/local/tmp/ramdisk",
+ "params": "rdinit=/bin/init"
+}
$ adb root
$ adb push <kernel> /data/local/tmp/kernel
$ adb push <ramdisk> /data/local/tmp/ramdisk
-$ adb shell /apex/com.android.virt/bin/crosvm run --initrd /data/local/tmp/ramdisk /data/local/tmp/kernel
+$ adb push vm_config.json /data/local/tmp/vm_config.json
+$ adb shell "start virtmanager"
+$ adb shell "/apex/com.android.virt/bin/vm run /data/local/tmp/vm_config.json"
```
-### Building and updating CrosVM
+The `vm` command also has other subcommands for debugging; run `/apex/com.android.virt/bin/vm help`
+for details.
-You can update CrosVM by updating the `com.android.virt` APEX where CrosVM is
-in. If your device already has `com.android.virt` (e.g. VIM3L),
+### Building and updating CrosVM and Virt Manager
-``` shell
-$ m com.android.virt
-$ adb install out/target/product/<device_name>/system/apex/com.android.virt.apex
+You can update CrosVM and the Virt Manager service by updating the `com.android.virt` APEX. If your
+device already has `com.android.virt` (e.g. VIM3L):
+
+```shell
+$ TARGET_BUILD_APPS="com.android.virt" m
+$ adb install $ANDROID_PRODUCT_OUT/system/apex/com.android.virt.apex
$ adb reboot
```
If it doesn't have the APEX yet, you first need to place it manually to the
system partition.
-``` shell
+```shell
$ adb root
$ adb disable-verity
$ adb reboot
diff --git a/microdroid/Android.bp b/microdroid/Android.bp
index 5c9f144..7466a78 100644
--- a/microdroid/Android.bp
+++ b/microdroid/Android.bp
@@ -140,6 +140,14 @@
],
}
+// TODO(jiyong): change the name to init, cause it's confusing
+microdroid_boot_cmdline = "rdinit=/bin/init_vendor " +
+ "panic=-1 " +
+ // TODO(b/181936135) make the ratelimiting conditional; ratelimiting on prod build
+ "printk.devkmsg=on " +
+ "androidboot.first_stage_console=1 " +
+ "androidboot.hardware=microdroid "
+
bootimg {
name: "microdroid_boot-5.10",
ramdisk_module: "microdroid_ramdisk-5.10",
@@ -149,19 +157,16 @@
arch: {
arm64: {
kernel_prebuilt: ":kernel_prebuilts-5.10-arm64",
+ cmdline: microdroid_boot_cmdline +
+ "androidboot.boot_devices=10000.pci",
},
x86_64: {
kernel_prebuilt: ":kernel_prebuilts-5.10-x86_64",
+ cmdline: microdroid_boot_cmdline +
+ "pci=noacpi " +
+ "androidboot.boot_devices=pci0000:00/0000:00:01.0",
},
},
- // TODO(jiyong): change the name to init, cause it's confusing
- cmdline: "rdinit=/bin/init_vendor " +
- "panic=-1 " +
- // TODO(b/181936135) make the ratelimiting conditional; ratelimiting on prod build
- "printk.devkmsg=on " +
- "androidboot.first_stage_console=1 " +
- "androidboot.hardware=microdroid " +
- "androidboot.boot_devices=10000.pci ",
dtb_prebuilt: "dummy_dtb.img",
header_version: "4",
partition_name: "boot",
@@ -233,8 +238,56 @@
}
prebuilt_etc {
+ name: "microdroid_bootloader",
+ src: ":microdroid_bootloader_gen",
+ arch: {
+ x86_64: {
+ // For unknown reason, the signed bootloader doesn't work on x86_64. Until the problem
+ // is fixed, let's use the unsigned bootloader for the architecture.
+ // TODO(b/185115783): remove this
+ src: ":cuttlefish_crosvm_bootloader",
+ },
+ },
+ filename: "microdroid_bootloader",
+}
+
+// See external/avb/avbtool.py
+// MAX_VBMETA_SIZE=64KB, MAX_FOOTER_SIZE=4KB
+avb_hash_footer_kb = "68"
+
+genrule {
+ name: "microdroid_bootloader_gen",
+ tools: ["avbtool"],
+ srcs: [
+ ":cuttlefish_crosvm_bootloader",
+ ":avb_testkey_rsa4096",
+ ],
+ out: ["bootloader-signed"],
+ // 1. Copy the input to the output becaise avbtool modifies --image in
+ // place.
+ // 2. Check if the file is big enough. For arm and x86 we have fake
+ // bootloader file whose size is 1. It can't pass avbtool.
+ // 3. Add the hash footer. The partition size is set to (image size + 68KB)
+ // rounded up to 4KB boundary.
+ cmd: "cp $(location :cuttlefish_crosvm_bootloader) $(out) && " +
+ "if [ $$(stat --format=%s $(out)) -gt 4096 ]; then " +
+ "$(location avbtool) add_hash_footer " +
+ "--algorithm SHA256_RSA4096 " +
+ "--partition_name bootloader " +
+ "--key $(location :avb_testkey_rsa4096) " +
+ "--partition_size $$(( " + avb_hash_footer_kb + " * 1024 + ( $$(stat --format=%s $(out)) + 4096 - 1 ) / 4096 * 4096 )) " +
+ "--image $(out)" +
+ "; fi",
+}
+
+prebuilt_etc {
name: "microdroid_uboot_env",
src: ":microdroid_uboot_env_gen",
+ arch: {
+ x86_64: {
+ src: ":microdroid_uboot_env_gen_x86_64",
+ },
+ },
filename: "uboot_env.img",
}
@@ -246,6 +299,14 @@
cmd: "$(location mkenvimage_host) -s 4096 -o $(out) $(in)",
}
+genrule {
+ name: "microdroid_uboot_env_gen_x86_64",
+ tools: ["mkenvimage_host"],
+ srcs: ["uboot-env-x86_64.txt"],
+ out: ["output.img"],
+ cmd: "$(location mkenvimage_host) -s 4096 -o $(out) $(in)",
+}
+
// sepolicy sha256 for vendor
prebuilt_etc {
name: "microdroid_precompiled_sepolicy.plat_sepolicy_and_mapping.sha256",
@@ -306,3 +367,13 @@
"microdroid",
],
}
+
+prebuilt_etc {
+ name: "microdroid_cdisk.json",
+ src: "microdroid_cdisk.json",
+}
+
+prebuilt_etc {
+ name: "microdroid_cdisk_env.json",
+ src: "microdroid_cdisk_env.json",
+}
diff --git a/microdroid/README.md b/microdroid/README.md
index d737e22..a4bfb6e 100644
--- a/microdroid/README.md
+++ b/microdroid/README.md
@@ -10,52 +10,33 @@
You need a VIM3L board. Instructions for building Android for the target, and
flashing the image can be found [here](../docs/getting_started/yukawa.md).
-Then you build microdroid. Note that the instruction below is very likely to
-change in the future, because this is in active development. For example, the
-`microdroid_*` modules will eventually be included in the `com.android.virt`
-APEX, which is already in the `yukawa` (VIM3L) target.
+Then you install `com.android.virt` APEX. All files needed to run microdroid are
+included in the APEX, which is already in the `yukawa` (VIM3L) target. You can
+of course build and install the APEX manually.
```
$ source build/envsetup.sh
$ choosecombo 1 aosp_arm64 userdebug // actually, any arm64-based target is ok
-$ m microdroid_super
-$ m microdroid_boot-5.10
-$ m microdroid_vendor_boot-5.10
-$ m microdroid_uboot_env
-$ m microdroid_vbmeta
-$ m microdroid_vbmeta_system
-```
-
-## Installing
-
-Push the built files to the device. In addition to that, some other files have
-to be manually created, for now. In the future, you won't need these.
-
-```
-$ adb push device/google/cuttlefish_prebuilts/bootloader/crosvm_aarch64/u-boot.bin /data/local/tmp/bootloader
-$ adb push $ANDROID_PRODUCT_OUT/system/etc/microdroid_super.img /data/local/tmp/super.img
-$ adb push $ANDROID_PRODUCT_OUT/system/etc/microdroid_boot-5.10.img /data/local/tmp/boot.img
-$ adb push $ANDROID_PRODUCT_OUT/system/etc/microdroid_vendor_boot-5.10.img /data/local/tmp/vendor_boot.img
-$ adb push $ANDROID_PRODUCT_OUT/system/etc/microdroid_vbmeta.img /data/local/tmp/vbmeta.img
-$ adb push $ANDROID_PRODUCT_OUT/system/etc/microdroid_vbmeta_system.img /data/local/tmp/vbmeta_system.img
-$ adb shell mkdir /data/local/tmp/cuttlefish_runtime.1/
-$ adb push $ANDROID_PRODUCT_OUT/system/etc/uboot_env.img /data/local/tmp/cuttlefish_runtime.1/
-$ adb shell mkdir -p /data/local/tmp/etc/cvd_config
-$ adb shell 'echo "{}" > /data/local/tmp/etc/cvd_config/cvd_config_phone.json'
-$ dd if=/dev/zero of=empty.img bs=4k count=600
-$ mkfs.ext4 -F empty.img
-$ adb push empty.img /data/local/tmp/userdata.img
-$ adb push empty.img /data/local/tmp/cache.img
+$ m com.android.virt
+$ adb install $ANDROID_PRODUCT_OUT/system/apex/com.android.virt.apex
+$ adb reboot
```
## Running
-Create the composite image using `assemble_cvd` and run it via `crosvm`. In the
-future, this shall be done via [`virtmanager`](../virtmanager/).
+Copy the artifacts to the temp directory, create the composite image using
+`mk_cdisk`, and run it via `crosvm`. For now, some other files have to be
+manually created. In the future, you won't need these, and this shall be done
+via [`virtmanager`](../virtmanager/).
```
-$ adb shell 'HOME=/data/local/tmp; PATH=$PATH:/apex/com.android.virt/bin; assemble_cvd -protected_vm < /dev/null'
-$ adb shell 'cd /data/local/tmp; /apex/com.android.virt/bin/crosvm run --cid=5 --disable-sandbox --bios=bootloader --serial=type=stdout --disk=cuttlefish_runtime/os_composite.img'
+$ adb shell 'cp /apex/com.android.virt/etc/microdroid_bootloader /data/local/tmp/bootloader'
+$ adb shell 'cp /apex/com.android.virt/etc/fs/*.img /data/local/tmp'
+$ adb shell 'cp /apex/com.android.virt/etc/uboot_env.img /data/local/tmp'
+$ adb shell 'dd if=/dev/zero of=/data/local/tmp/misc.img bs=4k count=256'
+$ adb shell 'cd /data/local/tmp; /apex/com.android.virt/bin/mk_cdisk /apex/com.android.virt/etc/microdroid_cdisk.json os_composite.img'
+$ adb shell 'cd /data/local/tmp; /apex/com.android.virt/bin/mk_cdisk /apex/com.android.virt/etc/microdroid_cdisk_env.json env_composite.img'
+$ adb shell 'cd /data/local/tmp; /apex/com.android.virt/bin/crosvm run --cid=5 --disable-sandbox --bios=bootloader --serial=type=stdout --disk=os_composite.img --disk=env_composite.img'
```
The CID in `--cid` parameter can be anything greater than 2 (`VMADDR_CID_HOST`).
diff --git a/microdroid/microdroid_cdisk.json b/microdroid/microdroid_cdisk.json
new file mode 100644
index 0000000..5721775
--- /dev/null
+++ b/microdroid/microdroid_cdisk.json
@@ -0,0 +1,44 @@
+{
+ "partitions": [
+ {
+ "label": "misc",
+ "path": "misc.img"
+ },
+ {
+ "label": "boot_a",
+ "path": "microdroid_boot-5.10.img"
+ },
+ {
+ "label": "boot_b",
+ "path": "microdroid_boot-5.10.img"
+ },
+ {
+ "label": "vendor_boot_a",
+ "path": "microdroid_vendor_boot-5.10.img"
+ },
+ {
+ "label": "vendor_boot_b",
+ "path": "microdroid_vendor_boot-5.10.img"
+ },
+ {
+ "label": "vbmeta_a",
+ "path": "microdroid_vbmeta.img"
+ },
+ {
+ "label": "vbmeta_b",
+ "path": "microdroid_vbmeta.img"
+ },
+ {
+ "label": "vbmeta_system_a",
+ "path": "microdroid_vbmeta_system.img"
+ },
+ {
+ "label": "vbmeta_system_b",
+ "path": "microdroid_vbmeta_system.img"
+ },
+ {
+ "label": "super",
+ "path": "microdroid_super.img"
+ }
+ ]
+}
diff --git a/microdroid/microdroid_cdisk_env.json b/microdroid/microdroid_cdisk_env.json
new file mode 100644
index 0000000..b0fbe44
--- /dev/null
+++ b/microdroid/microdroid_cdisk_env.json
@@ -0,0 +1,8 @@
+{
+ "partitions": [
+ {
+ "label": "uboot_env",
+ "path": "uboot_env.img"
+ }
+ ]
+}
diff --git a/microdroid/signature/Android.bp b/microdroid/signature/Android.bp
new file mode 100644
index 0000000..63c2128
--- /dev/null
+++ b/microdroid/signature/Android.bp
@@ -0,0 +1,49 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_defaults {
+ name: "microdroid_signature_default",
+ host_supported: true,
+ srcs: [
+ "microdroid_signature.proto",
+ "signature.cc",
+ ],
+ shared_libs: [
+ "libbase",
+ "liblog",
+ ],
+ export_include_dirs: ["include"],
+}
+
+cc_library_static {
+ name: "lib_microdroid_signature_proto_lite",
+ recovery_available: true,
+ proto: {
+ export_proto_headers: true,
+ type: "lite",
+ },
+ defaults: ["microdroid_signature_default"],
+ apex_available: [
+ "com.android.virt",
+ ],
+}
+
+cc_binary {
+ name: "mk_microdroid_signature",
+ srcs: [
+ "mk_microdroid_signature.cc",
+ ],
+ shared_libs: [
+ "libbase",
+ "liblog",
+ ],
+ static_libs: [
+ "lib_microdroid_signature_proto_lite",
+ "libjsoncpp",
+ "libprotobuf-cpp-lite",
+ ],
+ apex_available: [
+ "com.android.virt",
+ ],
+}
diff --git a/microdroid/signature/README.md b/microdroid/signature/README.md
new file mode 100644
index 0000000..4f434ee
--- /dev/null
+++ b/microdroid/signature/README.md
@@ -0,0 +1,56 @@
+# Microdroid Signature
+
+Microdroid Signature contains the signatures of the payloads so that the payloads are
+verified inside the Guest OS.
+
+* APEX packages that are passed to microdroid should be listed in the Microroid Signature.
+
+## Format
+
+Microdroid Signature is composed of header and body.
+
+| offset | size | description |
+|--------|------|----------------------------------------------------------------|
+| 0 | 4 | Header. unsigned int32: body length(L) in big endian |
+| 4 | L | Body. A protobuf message. [schema](microdroid_signature.proto) |
+
+## How to Create
+
+For testing purpose, use `mk_microdroid_signature` to create a Microdroid Signature.
+
+```
+$ cat signature_config.json
+{
+ "apexes": [
+ {
+ "name": "com.my.hello",
+ "path": "hello.apex"
+ }
+ ]
+}
+$ adb push signature_config.json hello.apex /data/local/tmp/
+$ adb shell 'cd /data/local/tmp; /apex/com.android.virt/bin/mk_microdroid_signature signature_config.json signature
+```
+
+Then, pass the signature as the first partition of the payload disk image.
+
+```
+$ cat payload_cdisk.json
+{
+ "partitions": [
+ {
+ "label": "signature",
+ "path": "signature"
+ },
+ {
+ "label": "com.my.hello",
+ "path": "hello.apex"
+ }
+ ]
+}
+$ adb push payload_cdisk.json /data/local/tmp/
+$ adb shell 'cd /data/local/tmp; /apex/com.android.virt/bin/mk_cdisk payload_cdisk.json payload.img
+$ adb shell 'cd /data/local/tmp; /apex/com.android.virt/bin/crosvm .... --disk=payload.img'
+```
+
+In the future, [VirtManager](../../virtmanager) will handle these stuffs.
\ No newline at end of file
diff --git a/microdroid/signature/include/microdroid/signature.h b/microdroid/signature/include/microdroid/signature.h
new file mode 100644
index 0000000..abacd6e
--- /dev/null
+++ b/microdroid/signature/include/microdroid/signature.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+#pragma once
+
+#include <android-base/result.h>
+#include <microdroid_signature.pb.h>
+
+#include <iostream>
+#include <string>
+
+namespace android {
+namespace microdroid {
+
+base::Result<MicrodroidSignature> ReadMicrodroidSignature(const std::string& path);
+
+base::Result<void> WriteMicrodroidSignature(const MicrodroidSignature& signature,
+ std::ostream& out);
+
+} // namespace microdroid
+} // namespace android
diff --git a/microdroid/signature/microdroid_signature.proto b/microdroid/signature/microdroid_signature.proto
new file mode 100644
index 0000000..8335ff5
--- /dev/null
+++ b/microdroid/signature/microdroid_signature.proto
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+syntax = "proto3";
+
+package android.microdroid;
+
+// Microdroid Signature is the body of the signature partition.
+message MicrodroidSignature {
+ uint32 version = 1;
+
+ // Lists the signature information of the payload apexes.
+ // The payload apexes are mapped to the partitions following the signature partition.
+ repeated ApexSignature apexes = 2;
+}
+
+message ApexSignature {
+ // Required.
+ // The apex name.
+ string name = 1;
+
+ // Required.
+ // The original size of the apex file.
+ uint32 size = 2;
+
+ // Optional.
+ // When specified, the public key used to sign the apex should match with it.
+ string publicKey = 3;
+
+ // Optional.
+ // When specified, the root digest of the apex should match with it.
+ string rootDigest = 4;
+}
diff --git a/microdroid/signature/mk_microdroid_signature.cc b/microdroid/signature/mk_microdroid_signature.cc
new file mode 100644
index 0000000..d5cb1a5
--- /dev/null
+++ b/microdroid/signature/mk_microdroid_signature.cc
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <fstream>
+#include <iostream>
+#include <string>
+
+#include <android-base/file.h>
+#include <android-base/result.h>
+#include <json/json.h>
+
+#include "microdroid/signature.h"
+
+using android::base::Dirname;
+using android::base::ErrnoError;
+using android::base::Error;
+using android::base::Result;
+using android::microdroid::MicrodroidSignature;
+using android::microdroid::WriteMicrodroidSignature;
+
+Result<uint32_t> GetFileSize(const std::string& path) {
+ struct stat st;
+ if (lstat(path.c_str(), &st) == -1) {
+ return ErrnoError() << "Can't lstat " << path;
+ }
+ return static_cast<uint32_t>(st.st_size);
+}
+
+// config JSON schema:
+// {
+// "apexes": [
+// {
+// "name": string, // the apex name
+// "path": string, // the path to the apex file
+// // absolute or relative to the config file
+// "publicKey": string, // optional
+// "rootDigest": string, // optional
+// }
+// ]
+// }
+
+Result<MicrodroidSignature> LoadConfig(const std::string& config_file) {
+ MicrodroidSignature signature;
+ signature.set_version(1);
+
+ const std::string dirname = Dirname(config_file);
+ std::ifstream in(config_file);
+ Json::CharReaderBuilder builder;
+ Json::Value root;
+ Json::String errs;
+ if (!parseFromStream(builder, in, &root, &errs)) {
+ return Error() << "bad config: " << errs;
+ }
+
+ for (const Json::Value& apex : root["apexes"]) {
+ auto apex_signature = signature.add_apexes();
+
+ Json::Value name = apex["name"];
+ Json::Value path = apex["path"];
+ Json::Value publicKey = apex["publicKey"];
+ Json::Value rootDigest = apex["rootDigest"];
+
+ if (name.isString()) {
+ apex_signature->set_name(name.asString());
+ } else {
+ return Error() << "bad config: apexes.name should be a string: " << path;
+ }
+
+ if (path.isString()) {
+ std::string apex_path = path.asString();
+
+ // resolve path with the config_file's dirname if not absolute
+ bool is_absolute = !apex_path.empty() && apex_path[0] == '/';
+ if (!is_absolute) {
+ apex_path = dirname + "/" + apex_path;
+ }
+
+ auto file_size = GetFileSize(apex_path);
+ if (!file_size.ok()) {
+ return Error() << "I/O error: " << file_size.error();
+ }
+ apex_signature->set_size(file_size.value());
+ } else {
+ return Error() << "bad config: apexes.path should be a string: " << path;
+ }
+
+ if (publicKey.isString()) {
+ apex_signature->set_publickey(publicKey.asString());
+ } else if (!publicKey.isNull()) {
+ return Error() << "bad config: apexes.publicKey should be a string or null: "
+ << publicKey;
+ }
+
+ if (rootDigest.isString()) {
+ apex_signature->set_rootdigest(rootDigest.asString());
+ } else if (!rootDigest.isNull()) {
+ return Error() << "bad config: apexes.rootDigest should be a string or null: "
+ << rootDigest;
+ }
+ }
+
+ return signature;
+}
+
+int main(int argc, char** argv) {
+ if (argc != 3) {
+ std::cerr << "Usage: " << argv[0] << " <config> <output>\n";
+ return 1;
+ }
+
+ auto config = LoadConfig(argv[1]);
+ if (!config.ok()) {
+ std::cerr << config.error() << '\n';
+ return 1;
+ }
+
+ std::ofstream out(argv[2]);
+ auto result = WriteMicrodroidSignature(*config, out);
+ if (!result.ok()) {
+ std::cerr << result.error() << '\n';
+ return 1;
+ }
+ return 0;
+}
\ No newline at end of file
diff --git a/microdroid/signature/signature.cc b/microdroid/signature/signature.cc
new file mode 100644
index 0000000..446159e
--- /dev/null
+++ b/microdroid/signature/signature.cc
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+#include "microdroid/signature.h"
+
+#include <android-base/endian.h>
+#include <android-base/file.h>
+
+using android::base::ErrnoError;
+using android::base::Error;
+using android::base::Result;
+
+namespace android {
+namespace microdroid {
+
+Result<MicrodroidSignature> ReadMicrodroidSignature(const std::string& path) {
+ std::string content;
+ if (!base::ReadFileToString(path, &content)) {
+ return ErrnoError() << "Failed to read " << path;
+ }
+
+ // read length prefix (4-byte, big-endian)
+ uint32_t size;
+ const size_t length_prefix_bytes = sizeof(size);
+ if (content.size() < length_prefix_bytes) {
+ return Error() << "Invalid signature: size == " << content.size();
+ }
+ size = be32toh(*reinterpret_cast<uint32_t*>(content.data()));
+ if (content.size() < length_prefix_bytes + size) {
+ return Error() << "Invalid signature: size(" << size << ") mimatches to the content size("
+ << content.size() - length_prefix_bytes << ")";
+ }
+ content = content.substr(length_prefix_bytes, size);
+
+ // parse content
+ MicrodroidSignature signature;
+ if (!signature.ParseFromString(content)) {
+ return Error() << "Can't parse MicrodroidSignature from " << path;
+ }
+ return signature;
+}
+
+Result<void> WriteMicrodroidSignature(const MicrodroidSignature& signature, std::ostream& out) {
+ // prepare content
+ std::string content;
+ if (!signature.SerializeToString(&content)) {
+ return Error() << "Failed to write protobuf.";
+ }
+
+ // write length prefix (4-byte, big-endian)
+ uint32_t size = htobe32(static_cast<uint32_t>(content.size()));
+ out.write(reinterpret_cast<const char*>(&size), sizeof(size));
+
+ // write content
+ out << content;
+
+ return {};
+}
+
+} // namespace microdroid
+} // namespace android
\ No newline at end of file
diff --git a/microdroid/uboot-env-x86_64.txt b/microdroid/uboot-env-x86_64.txt
new file mode 100644
index 0000000..ffc0462
--- /dev/null
+++ b/microdroid/uboot-env-x86_64.txt
@@ -0,0 +1,13 @@
+# Static u-boot environment variables for microdroid. See b/180481192
+
+# Boot the device following the Android boot procedure
+bootcmd=avb init virtio 0 && avb verify _a && boot_android virtio 0#misc
+
+bootdelay=0
+
+# U-Boot in x86_64 by defaults loads kernel at 0x20000000 (512MB), which is
+# out of the physical memory when the VM is launched with the default memory
+# size of 256MB. To avoid that, explicitly set the kernel load addresss using
+# loadaddr variable.
+loadaddr=0x02000000
+fdtaddr=0x40000000
diff --git a/microdroid/uboot-env.txt b/microdroid/uboot-env.txt
index b7940e5..0bdc591 100644
--- a/microdroid/uboot-env.txt
+++ b/microdroid/uboot-env.txt
@@ -1,7 +1,7 @@
# Static u-boot environment variables for microdroid. See b/180481192
# Boot the device following the Android boot procedure
-bootcmd=boot_android virtio 0#misc
+bootcmd=avb init virtio 0 && avb verify _a && boot_android virtio 0#misc
bootdelay=0
fdtaddr=0x80000000
diff --git a/tests/hostside/Android.bp b/tests/hostside/Android.bp
index c030e8d..429b737 100644
--- a/tests/hostside/Android.bp
+++ b/tests/hostside/Android.bp
@@ -7,23 +7,4 @@
srcs: ["java/**/*.java"],
test_suites: ["device-tests"],
libs: ["tradefed"],
- data: [
- ":microdroid_super",
- ":microdroid_boot-5.10",
- ":microdroid_vendor_boot-5.10",
- ":microdroid_uboot_env",
- ":cuttlefish_crosvm_bootloader",
- ":MicrodroidHostTestCase_EmptyImage",
- ":microdroid_vbmeta",
- ":microdroid_vbmeta_system",
- ],
-}
-
-genrule {
- name: "MicrodroidHostTestCase_EmptyImage",
- tools: ["mke2fs"],
- out: ["empty.img"],
- cmd: "dd if=/dev/zero of=$(out) bs=4k count=600 &&" +
- "$(location mke2fs) -t ext4 $(out)",
- visibility: ["//visibility:private"],
}
diff --git a/tests/hostside/java/android/virt/test/MicrodroidTestCase.java b/tests/hostside/java/android/virt/test/MicrodroidTestCase.java
index c5c86a6..57c7b17 100644
--- a/tests/hostside/java/android/virt/test/MicrodroidTestCase.java
+++ b/tests/hostside/java/android/virt/test/MicrodroidTestCase.java
@@ -30,9 +30,6 @@
import org.junit.Test;
import org.junit.runner.RunWith;
-import java.io.File;
-import java.nio.file.Path;
-import java.nio.file.Paths;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@@ -45,17 +42,6 @@
private static final String MICRODROID_SERIAL = "localhost:" + TEST_VM_ADB_PORT;
private static final long MICRODROID_BOOT_TIMEOUT_MILLIS = 15000;
- private void pushFile(String localName, String remoteName) {
- try {
- File localFile = getTestInformation().getDependencyFile(localName, false);
- Path remotePath = Paths.get(TEST_ROOT, remoteName);
- getDevice().executeShellCommand("mkdir -p " + remotePath.getParent());
- getDevice().pushFile(localFile, remotePath.toString());
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- }
-
private String executeCommand(String cmd) {
final long defaultCommandTimeoutMillis = 1000; // 1 sec
return executeCommand(defaultCommandTimeoutMillis, cmd);
@@ -69,50 +55,65 @@
@Test
public void testMicrodroidBoots() throws Exception {
// Prepare input files
- pushFile("u-boot.bin", "bootloader");
- pushFile("microdroid_super.img", "super.img");
- pushFile("microdroid_boot-5.10.img", "boot.img");
- pushFile("microdroid_vendor_boot-5.10.img", "vendor_boot.img");
- pushFile("uboot_env.img", "cuttlefish_runtime.1/uboot_env.img");
- pushFile("empty.img", "userdata.img");
- pushFile("microdroid_vbmeta.img", "vbmeta.img");
- pushFile("microdroid_vbmeta_system.img", "vbmeta_system.img");
- pushFile("empty.img", "cache.img");
- getDevice().executeShellCommand("mkdir -p " + TEST_ROOT + "etc/cvd_config");
- getDevice().pushString("{}", TEST_ROOT + "etc/cvd_config/cvd_config_phone.json");
+ String prepareImagesCmd =
+ String.format(
+ "mkdir -p %s; cd %s; "
+ + "cp %setc/microdroid_bootloader bootloader && "
+ + "cp %setc/fs/*.img . && "
+ + "cp %setc/uboot_env.img . && "
+ + "dd if=/dev/zero of=misc.img bs=4k count=256",
+ TEST_ROOT, TEST_ROOT, VIRT_APEX, VIRT_APEX, VIRT_APEX);
+ getDevice().executeShellCommand(prepareImagesCmd);
- // Run assemble_cvd to create os_composite.img
- getDevice().executeShellCommand("HOME=" + TEST_ROOT + "; "
- + "PATH=$PATH:" + VIRT_APEX + "bin; "
- + VIRT_APEX + "bin/assemble_cvd -protected_vm < /dev/null");
+ // Create os_composite.img and env_composite.img
+ String makeOsCompositeCmd =
+ String.format(
+ "cd %s; %sbin/mk_cdisk %setc/microdroid_cdisk.json os_composite.img",
+ TEST_ROOT, VIRT_APEX, VIRT_APEX);
+ getDevice().executeShellCommand(makeOsCompositeCmd);
+ String makeEnvCompositeCmd =
+ String.format(
+ "cd %s; %sbin/mk_cdisk %setc/microdroid_cdisk_env.json env_composite.img",
+ TEST_ROOT, VIRT_APEX, VIRT_APEX);
+ getDevice().executeShellCommand(makeEnvCompositeCmd);
- // Make sure that os_composite.img is created
- final String compositeImg = TEST_ROOT + "cuttlefish_runtime/os_composite.img";
- CommandResult result = getDevice().executeShellV2Command("du -b " + compositeImg);
+ // Make sure that the composite images are created
+ final String compositeImg = TEST_ROOT + "/os_composite.img";
+ final String envCompositeImg = TEST_ROOT + "/env_composite.img";
+ CommandResult result =
+ getDevice().executeShellV2Command("du -b " + compositeImg + " " + envCompositeImg);
assertThat(result.getExitCode(), is(0));
assertThat(result.getStdout(), is(not("")));
// Start microdroid using crosvm
ExecutorService executor = Executors.newFixedThreadPool(1);
- executor.execute(() -> {
- try {
- getDevice().executeShellV2Command("cd " + TEST_ROOT + "; "
- + VIRT_APEX + "bin/crosvm run "
- + "--cid=" + TEST_VM_CID + " "
- + "--disable-sandbox "
- + "--bios=bootloader "
- + "--serial=type=syslog "
- + "--disk=cuttlefish_runtime/os_composite.img");
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- });
+ String runMicrodroidCmd =
+ String.format(
+ "cd %s; %sbin/crosvm run --cid=%d --disable-sandbox --bios=bootloader"
+ + " --serial=type=syslog --disk=os_composite.img"
+ + " --disk=env_composite.img",
+ TEST_ROOT, VIRT_APEX, TEST_VM_CID);
+ executor.execute(
+ () -> {
+ try {
+ getDevice().executeShellV2Command(runMicrodroidCmd);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ });
// .. and wait for microdroid to boot
// TODO(jiyong): don't wait too long. We can wait less by monitoring log from microdroid
Thread.sleep(MICRODROID_BOOT_TIMEOUT_MILLIS);
// Connect to microdroid and read a system property from there
- executeCommand("adb forward tcp:" + TEST_VM_ADB_PORT + " vsock:" + TEST_VM_CID + ":5555");
+ executeCommand(
+ "adb -s "
+ + getDevice().getSerialNumber()
+ + " forward tcp:"
+ + TEST_VM_ADB_PORT
+ + " vsock:"
+ + TEST_VM_CID
+ + ":5555");
executeCommand("adb connect " + MICRODROID_SERIAL);
String prop = executeCommand("adb -s " + MICRODROID_SERIAL + " shell getprop ro.hardware");
assertThat(prop, is("microdroid"));
diff --git a/virtmanager/Android.bp b/virtmanager/Android.bp
index 1b2aec1..f1971dc 100644
--- a/virtmanager/Android.bp
+++ b/virtmanager/Android.bp
@@ -7,13 +7,14 @@
crate_name: "virtmanager",
srcs: ["src/main.rs"],
edition: "2018",
+ prefer_rlib: true,
rustlibs: [
"android.system.virtmanager-rust",
"libandroid_logger",
- "libbinder_rs", // TODO(dbrazdil): remove once b/182890877 is fixed
"liblog_rust",
"libserde_json",
"libserde",
+ "libshared_child",
"libanyhow",
],
apex_available: ["com.android.virt"],
diff --git a/virtmanager/aidl/android/system/virtmanager/IVirtualMachine.aidl b/virtmanager/aidl/android/system/virtmanager/IVirtualMachine.aidl
index 0358bfd..26aad0c 100644
--- a/virtmanager/aidl/android/system/virtmanager/IVirtualMachine.aidl
+++ b/virtmanager/aidl/android/system/virtmanager/IVirtualMachine.aidl
@@ -15,7 +15,18 @@
*/
package android.system.virtmanager;
+import android.system.virtmanager.IVirtualMachineCallback;
+
interface IVirtualMachine {
/** Get the CID allocated to the VM. */
int getCid();
+
+ /** Returns true if the VM is still running, or false if it has exited for any reason. */
+ boolean isRunning();
+
+ /**
+ * Register a Binder object to get callbacks when the state of the VM changes, such as if it
+ * dies.
+ */
+ void registerCallback(IVirtualMachineCallback callback);
}
diff --git a/virtmanager/aidl/android/system/virtmanager/IVirtualMachineCallback.aidl b/virtmanager/aidl/android/system/virtmanager/IVirtualMachineCallback.aidl
new file mode 100644
index 0000000..65a685d
--- /dev/null
+++ b/virtmanager/aidl/android/system/virtmanager/IVirtualMachineCallback.aidl
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2021 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.
+ */
+package android.system.virtmanager;
+
+import android.system.virtmanager.IVirtualMachine;
+
+/**
+ * An object which a client may register with the Virt Manager to get callbacks about the state of
+ * a particular VM.
+ */
+oneway interface IVirtualMachineCallback {
+ /**
+ * Called when the VM dies.
+ *
+ * Note that this will not be called if the Virt Manager itself dies, so you should also use
+ * `link_to_death` to handle that.
+ */
+ void onDied(int cid);
+}
diff --git a/virtmanager/aidl/android/system/virtmanager/VirtualMachineDebugInfo.aidl b/virtmanager/aidl/android/system/virtmanager/VirtualMachineDebugInfo.aidl
index d1ba9a6..7bb77ce 100644
--- a/virtmanager/aidl/android/system/virtmanager/VirtualMachineDebugInfo.aidl
+++ b/virtmanager/aidl/android/system/virtmanager/VirtualMachineDebugInfo.aidl
@@ -19,13 +19,19 @@
parcelable VirtualMachineDebugInfo {
/** The CID assigned to the VM. */
int cid;
+
/** The UID of the process which requested the VM. */
- int requester_uid;
+ int requesterUid;
+
/** The SID of the process which requested the VM. */
- @nullable String requester_sid;
+ @nullable String requesterSid;
+
/**
* The PID of the process which requested the VM. Note that this process may no longer exist and
* the PID may have been reused for a different process, so this should not be trusted.
*/
- int requester_pid;
+ int requesterPid;
+
+ /** Whether the VM is still running. */
+ boolean running;
}
diff --git a/virtmanager/src/aidl.rs b/virtmanager/src/aidl.rs
index 98af714..5a4eedc 100644
--- a/virtmanager/src/aidl.rs
+++ b/virtmanager/src/aidl.rs
@@ -17,16 +17,16 @@
use crate::config::VmConfig;
use crate::crosvm::VmInstance;
use crate::{Cid, FIRST_GUEST_CID};
-use ::binder::FromIBinder; // TODO(dbrazdil): remove once b/182890877 is fixed
use android_system_virtmanager::aidl::android::system::virtmanager::IVirtManager::IVirtManager;
use android_system_virtmanager::aidl::android::system::virtmanager::IVirtualMachine::{
BnVirtualMachine, IVirtualMachine,
};
+use android_system_virtmanager::aidl::android::system::virtmanager::IVirtualMachineCallback::IVirtualMachineCallback;
use android_system_virtmanager::aidl::android::system::virtmanager::VirtualMachineDebugInfo::VirtualMachineDebugInfo;
use android_system_virtmanager::binder::{
self, Interface, ParcelFileDescriptor, StatusCode, Strong, ThreadState,
};
-use log::error;
+use log::{debug, error};
use std::ffi::CStr;
use std::fs::File;
use std::sync::{Arc, Mutex, Weak};
@@ -55,7 +55,6 @@
log_fd: Option<&ParcelFileDescriptor>,
) -> binder::Result<Strong<dyn IVirtualMachine>> {
let state = &mut *self.state.lock().unwrap();
- let cid = state.next_cid;
let log_fd = log_fd
.map(|fd| fd.as_ref().try_clone().map_err(|_| StatusCode::UNKNOWN_ERROR))
.transpose()?;
@@ -70,16 +69,9 @@
})
});
let requester_pid = ThreadState::get_calling_pid();
- let instance = Arc::new(start_vm(
- config_fd.as_ref(),
- cid,
- log_fd,
- requester_uid,
- requester_sid,
- requester_pid,
- )?);
- // TODO(qwandor): keep track of which CIDs are currently in use so that we can reuse them.
- state.next_cid = state.next_cid.checked_add(1).ok_or(StatusCode::UNKNOWN_ERROR)?;
+ let cid = state.allocate_cid()?;
+ let instance =
+ start_vm(config_fd.as_ref(), cid, log_fd, requester_uid, requester_sid, requester_pid)?;
state.add_vm(Arc::downgrade(&instance));
Ok(VirtualMachine::create(instance))
}
@@ -97,9 +89,10 @@
.into_iter()
.map(|vm| VirtualMachineDebugInfo {
cid: vm.cid as i32,
- requester_uid: vm.requester_uid as i32,
- requester_sid: vm.requester_sid.clone(),
- requester_pid: vm.requester_pid,
+ requesterUid: vm.requester_uid as i32,
+ requesterSid: vm.requester_sid.clone(),
+ requesterPid: vm.requester_pid,
+ running: vm.running(),
})
.collect();
Ok(cids)
@@ -107,16 +100,13 @@
/// Hold a strong reference to a VM in Virt Manager. This method is only intended for debug
/// purposes, and as such is only permitted from the shell user.
- fn debugHoldVmRef(&self, vmref: &dyn IVirtualMachine) -> binder::Result<()> {
+ fn debugHoldVmRef(&self, vmref: &Strong<dyn IVirtualMachine>) -> binder::Result<()> {
if !debug_access_allowed() {
return Err(StatusCode::PERMISSION_DENIED.into());
}
- // Workaround for b/182890877.
- let vm: Strong<dyn IVirtualMachine> = FromIBinder::try_from(vmref.as_binder()).unwrap();
-
let state = &mut *self.state.lock().unwrap();
- state.debug_hold_vm(vm);
+ state.debug_hold_vm(vmref.clone());
Ok(())
}
@@ -159,6 +149,48 @@
fn getCid(&self) -> binder::Result<i32> {
Ok(self.instance.cid as i32)
}
+
+ fn isRunning(&self) -> binder::Result<bool> {
+ Ok(self.instance.running())
+ }
+
+ fn registerCallback(
+ &self,
+ callback: &Strong<dyn IVirtualMachineCallback>,
+ ) -> binder::Result<()> {
+ // TODO: Should this give an error if the VM is already dead?
+ self.instance.callbacks.add(callback.clone());
+ Ok(())
+ }
+}
+
+impl Drop for VirtualMachine {
+ fn drop(&mut self) {
+ debug!("Dropping {:?}", self);
+ self.instance.kill();
+ }
+}
+
+/// A set of Binders to be called back in response to various events on the VM, such as when it
+/// dies.
+#[derive(Debug, Default)]
+pub struct VirtualMachineCallbacks(Mutex<Vec<Strong<dyn IVirtualMachineCallback>>>);
+
+impl VirtualMachineCallbacks {
+ /// Call all registered callbacks to say that the VM has died.
+ pub fn callback_on_died(&self, cid: Cid) {
+ let callbacks = &*self.0.lock().unwrap();
+ for callback in callbacks {
+ if let Err(e) = callback.onDied(cid as i32) {
+ error!("Error calling callback: {}", e);
+ }
+ }
+ }
+
+ /// Add a new callback to the set.
+ fn add(&self, callback: Strong<dyn IVirtualMachineCallback>) {
+ self.0.lock().unwrap().push(callback);
+ }
}
/// The mutable state of the Virt Manager. There should only be one instance of this struct.
@@ -179,7 +211,7 @@
}
impl State {
- /// Get a list of VMs which are currently running.
+ /// Get a list of VMs which still have Binder references to them.
fn vms(&self) -> Vec<Arc<VmInstance>> {
// Attempt to upgrade the weak pointers to strong pointers.
self.vms.iter().filter_map(Weak::upgrade).collect()
@@ -204,6 +236,14 @@
let pos = self.debug_held_vms.iter().position(|vm| vm.getCid() == Ok(cid))?;
Some(self.debug_held_vms.swap_remove(pos))
}
+
+ /// Get the next available CID, or an error if we have run out.
+ fn allocate_cid(&mut self) -> binder::Result<Cid> {
+ // TODO(qwandor): keep track of which CIDs are currently in use so that we can reuse them.
+ let cid = self.next_cid;
+ self.next_cid = self.next_cid.checked_add(1).ok_or(StatusCode::UNKNOWN_ERROR)?;
+ Ok(cid)
+ }
}
impl Default for State {
@@ -221,7 +261,7 @@
requester_uid: u32,
requester_sid: Option<String>,
requester_pid: i32,
-) -> binder::Result<VmInstance> {
+) -> binder::Result<Arc<VmInstance>> {
let config = VmConfig::load(config_file).map_err(|e| {
error!("Failed to load VM config from {:?}: {:?}", config_file, e);
StatusCode::BAD_VALUE
diff --git a/virtmanager/src/crosvm.rs b/virtmanager/src/crosvm.rs
index bef9982..5e6f658 100644
--- a/virtmanager/src/crosvm.rs
+++ b/virtmanager/src/crosvm.rs
@@ -14,12 +14,17 @@
//! Functions for running instances of `crosvm`.
+use crate::aidl::VirtualMachineCallbacks;
use crate::config::VmConfig;
use crate::Cid;
use anyhow::Error;
-use log::{debug, error, info};
+use log::{error, info};
+use shared_child::SharedChild;
use std::fs::File;
-use std::process::{Child, Command};
+use std::process::Command;
+use std::sync::atomic::{AtomicBool, Ordering};
+use std::sync::Arc;
+use std::thread;
const CROSVM_PATH: &str = "/apex/com.android.virt/bin/crosvm";
@@ -27,7 +32,7 @@
#[derive(Debug)]
pub struct VmInstance {
/// The crosvm child process.
- child: Child,
+ child: SharedChild,
/// The CID assigned to the VM for vsock communication.
pub cid: Cid,
/// The UID of the process which requested the VM.
@@ -37,18 +42,30 @@
/// The PID of the process which requested the VM. Note that this process may no longer exist
/// and the PID may have been reused for a different process, so this should not be trusted.
pub requester_pid: i32,
+ /// Whether the VM is still running.
+ running: AtomicBool,
+ /// Callbacks to clients of the VM.
+ pub callbacks: VirtualMachineCallbacks,
}
impl VmInstance {
/// Create a new `VmInstance` for the given process.
fn new(
- child: Child,
+ child: SharedChild,
cid: Cid,
requester_uid: u32,
requester_sid: Option<String>,
requester_pid: i32,
) -> VmInstance {
- VmInstance { child, cid, requester_uid, requester_sid, requester_pid }
+ VmInstance {
+ child,
+ cid,
+ requester_uid,
+ requester_sid,
+ requester_pid,
+ running: AtomicBool::new(true),
+ callbacks: Default::default(),
+ }
}
/// Start an instance of `crosvm` to manage a new VM. The `crosvm` instance will be killed when
@@ -60,29 +77,46 @@
requester_uid: u32,
requester_sid: Option<String>,
requester_pid: i32,
- ) -> Result<VmInstance, Error> {
+ ) -> Result<Arc<VmInstance>, Error> {
let child = run_vm(config, cid, log_fd)?;
- Ok(VmInstance::new(child, cid, requester_uid, requester_sid, requester_pid))
- }
-}
+ let instance =
+ Arc::new(VmInstance::new(child, cid, requester_uid, requester_sid, requester_pid));
-impl Drop for VmInstance {
- fn drop(&mut self) {
- debug!("Dropping {:?}", self);
+ let instance_clone = instance.clone();
+ thread::spawn(move || {
+ instance_clone.monitor();
+ });
+
+ Ok(instance)
+ }
+
+ /// Wait for the crosvm child process to finish, then mark the VM as no longer running and call
+ /// any callbacks.
+ fn monitor(&self) {
+ match self.child.wait() {
+ Err(e) => error!("Error waiting for crosvm instance to die: {}", e),
+ Ok(status) => info!("crosvm exited with status {}", status),
+ }
+ self.running.store(false, Ordering::Release);
+ self.callbacks.callback_on_died(self.cid);
+ }
+
+ /// Return whether `crosvm` is still running the VM.
+ pub fn running(&self) -> bool {
+ self.running.load(Ordering::Acquire)
+ }
+
+ /// Kill the crosvm instance.
+ pub fn kill(&self) {
// TODO: Talk to crosvm to shutdown cleanly.
if let Err(e) = self.child.kill() {
error!("Error killing crosvm instance: {}", e);
}
- // We need to wait on the process after killing it to avoid zombies.
- match self.child.wait() {
- Err(e) => error!("Error waiting for crosvm instance to die: {}", e),
- Ok(status) => info!("Crosvm exited with status {}", status),
- }
}
}
/// Start an instance of `crosvm` to manage a new VM.
-fn run_vm(config: &VmConfig, cid: Cid, log_fd: Option<File>) -> Result<Child, Error> {
+fn run_vm(config: &VmConfig, cid: Cid, log_fd: Option<File>) -> Result<SharedChild, Error> {
config.validate()?;
let mut command = Command::new(CROSVM_PATH);
@@ -110,6 +144,5 @@
command.arg(kernel);
}
info!("Running {:?}", command);
- // TODO: Monitor child process, and remove from VM map if it dies.
- Ok(command.spawn()?)
+ Ok(SharedChild::spawn(&mut command)?)
}
diff --git a/vm/src/main.rs b/vm/src/main.rs
index 1f39e4a..061cfd7 100644
--- a/vm/src/main.rs
+++ b/vm/src/main.rs
@@ -17,14 +17,19 @@
mod sync;
use android_system_virtmanager::aidl::android::system::virtmanager::IVirtManager::IVirtManager;
+use android_system_virtmanager::aidl::android::system::virtmanager::IVirtualMachine::IVirtualMachine;
+use android_system_virtmanager::aidl::android::system::virtmanager::IVirtualMachineCallback::{
+ BnVirtualMachineCallback, IVirtualMachineCallback,
+};
use android_system_virtmanager::binder::{
get_interface, DeathRecipient, IBinder, ParcelFileDescriptor, ProcessState, Strong,
};
+use android_system_virtmanager::binder::{Interface, Result as BinderResult};
use anyhow::{Context, Error};
use std::fs::File;
use std::io;
use std::os::unix::io::{AsRawFd, FromRawFd};
-use std::path::PathBuf;
+use std::path::{Path, PathBuf};
use structopt::clap::AppSettings;
use structopt::StructOpt;
use sync::AtomicFlag;
@@ -73,7 +78,7 @@
/// Run a VM from the given configuration file.
fn command_run(
virt_manager: Strong<dyn IVirtManager>,
- config_path: &PathBuf,
+ config_path: &Path,
daemonize: bool,
) -> Result<(), Error> {
let config_filename = config_path.to_str().context("Failed to parse VM config path")?;
@@ -89,16 +94,28 @@
if daemonize {
// Pass the VM reference back to Virt Manager and have it hold it in the background.
- virt_manager.debugHoldVmRef(&*vm).context("Failed to pass VM to Virt Manager")
+ virt_manager.debugHoldVmRef(&vm).context("Failed to pass VM to Virt Manager")
} else {
- // Wait until the VM dies. If we just returned immediately then the IVirtualMachine Binder
- // object would be dropped and the VM would be killed.
- wait_for_death(&mut vm.as_binder())?;
- println!("VM died");
- Ok(())
+ // Wait until the VM or VirtManager dies. If we just returned immediately then the
+ // IVirtualMachine Binder object would be dropped and the VM would be killed.
+ wait_for_vm(vm)
}
}
+/// Wait until the given VM or the VirtManager itself dies.
+fn wait_for_vm(vm: Strong<dyn IVirtualMachine>) -> Result<(), Error> {
+ let dead = AtomicFlag::default();
+ let callback =
+ BnVirtualMachineCallback::new_binder(VirtualMachineCallback { dead: dead.clone() });
+ vm.registerCallback(&callback)?;
+ let death_recipient = wait_for_death(&mut vm.as_binder(), dead.clone())?;
+ dead.wait();
+ // Ensure that death_recipient isn't dropped before we wait on the flag, as it is removed
+ // from the Binder when it's dropped.
+ drop(death_recipient);
+ Ok(())
+}
+
/// Retrieve reference to a previously daemonized VM and stop it.
fn command_stop(virt_manager: Strong<dyn IVirtManager>, cid: u32) -> Result<(), Error> {
virt_manager
@@ -115,18 +132,31 @@
Ok(())
}
-/// Block until the given Binder object dies.
-fn wait_for_death(binder: &mut impl IBinder) -> Result<(), Error> {
- let dead = AtomicFlag::default();
- let mut death_recipient = {
- let dead = dead.clone();
- DeathRecipient::new(move || {
- dead.raise();
- })
- };
+/// Raise the given flag when the given Binder object dies.
+///
+/// If the returned DeathRecipient is dropped then this will no longer do anything.
+fn wait_for_death(binder: &mut impl IBinder, dead: AtomicFlag) -> Result<DeathRecipient, Error> {
+ let mut death_recipient = DeathRecipient::new(move || {
+ println!("VirtManager died");
+ dead.raise();
+ });
binder.link_to_death(&mut death_recipient)?;
- dead.wait();
- Ok(())
+ Ok(death_recipient)
+}
+
+#[derive(Debug)]
+struct VirtualMachineCallback {
+ dead: AtomicFlag,
+}
+
+impl Interface for VirtualMachineCallback {}
+
+impl IVirtualMachineCallback for VirtualMachineCallback {
+ fn onDied(&self, _cid: i32) -> BinderResult<()> {
+ println!("VM died");
+ self.dead.raise();
+ Ok(())
+ }
}
/// Safely duplicate the standard output file descriptor.