Create instance.img
The file is a per-VM persistent storage which will contain identity
information like the certs of the payload.
As the first step, this change creates instance.img upon creation of a
VM and passes it to virtualizationservice. For now, the file is not
passed down to crosvm yet.
Bug: 193504400
Test: atest MicrodroidHostTestCases
Change-Id: I44115a93abdd23c36a4c52a38e5355bfa3bf69b3
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachine.java b/javalib/src/android/system/virtualmachine/VirtualMachine.java
index 3baec94..522651b 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachine.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachine.java
@@ -16,6 +16,8 @@
package android.system.virtualmachine;
+import static android.os.ParcelFileDescriptor.MODE_READ_WRITE;
+
import android.content.Context;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
@@ -24,6 +26,7 @@
import android.system.virtualizationservice.IVirtualMachine;
import android.system.virtualizationservice.IVirtualMachineCallback;
import android.system.virtualizationservice.IVirtualizationService;
+import android.system.virtualizationservice.VirtualMachineAppConfig;
import java.io.File;
import java.io.FileInputStream;
@@ -78,9 +81,12 @@
*/
private final File mConfigFilePath;
- /** Path to the instance image file for this VM. (Not implemented) */
+ /** Path to the instance image file for this VM. */
private final File mInstanceFilePath;
+ /** Size of the instance image. 10 MB. */
+ private static final long INSTANCE_FILE_SIZE = 10 * 1024 * 1024;
+
/** The configuration that is currently associated with this VM. */
private VirtualMachineConfig mConfig;
@@ -135,7 +141,26 @@
throw new VirtualMachineException(e);
}
- // TODO(jiyong): create the instance image file
+ try {
+ vm.mInstanceFilePath.createNewFile();
+ } catch (IOException e) {
+ throw new VirtualMachineException("failed to create instance image", e);
+ }
+
+ IVirtualizationService service =
+ IVirtualizationService.Stub.asInterface(
+ ServiceManager.waitForService(SERVICE_NAME));
+
+ try {
+ service.initializeWritablePartition(
+ ParcelFileDescriptor.open(vm.mInstanceFilePath, MODE_READ_WRITE),
+ INSTANCE_FILE_SIZE);
+ } catch (FileNotFoundException e) {
+ throw new VirtualMachineException("instance image missing", e);
+ } catch (RemoteException e) {
+ throw new VirtualMachineException("failed to create instance partition", e);
+ }
+
return vm;
}
@@ -144,10 +169,8 @@
throws VirtualMachineException {
VirtualMachine vm = new VirtualMachine(context, name, /* config */ null);
- try {
- FileInputStream input = new FileInputStream(vm.mConfigFilePath);
+ try (FileInputStream input = new FileInputStream(vm.mConfigFilePath)) {
VirtualMachineConfig config = VirtualMachineConfig.from(input);
- input.close();
vm.mConfig = config;
} catch (FileNotFoundException e) {
// The VM doesn't exist.
@@ -156,6 +179,13 @@
throw new VirtualMachineException(e);
}
+ // If config file exists, but the instance image file doesn't, it means that the VM is
+ // corrupted. That's different from the case that the VM doesn't exist. Throw an exception
+ // instead of returning null.
+ if (!vm.mInstanceFilePath.exists()) {
+ throw new VirtualMachineException("instance image missing");
+ }
+
return vm;
}
@@ -225,12 +255,14 @@
mConsoleReader = pipe[0];
mConsoleWriter = pipe[1];
}
- mVirtualMachine =
- service.startVm(
- android.system.virtualizationservice.VirtualMachineConfig.appConfig(
- getConfig().toParcel()),
- mConsoleWriter);
+ VirtualMachineAppConfig appConfig = getConfig().toParcel();
+ appConfig.instanceImage = ParcelFileDescriptor.open(mInstanceFilePath, MODE_READ_WRITE);
+
+ android.system.virtualizationservice.VirtualMachineConfig vmConfigParcel =
+ android.system.virtualizationservice.VirtualMachineConfig.appConfig(appConfig);
+
+ mVirtualMachine = service.startVm(vmConfigParcel, mConsoleWriter);
mVirtualMachine.registerCallback(
new IVirtualMachineCallback.Stub() {
@Override
diff --git a/tests/hostside/helper/java/android/virt/test/VirtualizationTestCaseBase.java b/tests/hostside/helper/java/android/virt/test/VirtualizationTestCaseBase.java
index 451f9ba..2d55a9c 100644
--- a/tests/hostside/helper/java/android/virt/test/VirtualizationTestCaseBase.java
+++ b/tests/hostside/helper/java/android/virt/test/VirtualizationTestCaseBase.java
@@ -43,6 +43,7 @@
private static final String VIRT_APEX = "/apex/com.android.virt/";
private static final int TEST_VM_ADB_PORT = 8000;
private static final String MICRODROID_SERIAL = "localhost:" + TEST_VM_ADB_PORT;
+ private static final String INSTANCE_IMG = "instance.img";
// This is really slow on GCE (2m 40s) but fast on localhost or actual Android phones (< 10s)
// Set the maximum timeout value big enough.
@@ -192,6 +193,7 @@
final String apkIdsigPath = TEST_ROOT + apkName + ".idsig";
getDevice().pushFile(idsigOnHost, apkIdsigPath);
+ final String instanceImg = TEST_ROOT + INSTANCE_IMG;
final String logPath = TEST_ROOT + "log.txt";
final String debugFlag = debug ? "--debug " : "";
@@ -206,6 +208,7 @@
debugFlag,
apkPath,
apkIdsigPath,
+ instanceImg,
configPath);
// Redirect log.txt to logd using logwrapper
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineAppConfig.aidl b/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineAppConfig.aidl
index 4dcfb88..a5f7c3c 100644
--- a/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineAppConfig.aidl
+++ b/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineAppConfig.aidl
@@ -23,6 +23,9 @@
/** idsig for an APK */
ParcelFileDescriptor idsig;
+ /** instance.img that has per-instance data */
+ ParcelFileDescriptor instanceImage;
+
/** Path to a configuration in an APK. This is the actual configuration for a VM. */
@utf8InCpp String configPath;
diff --git a/virtualizationservice/src/aidl.rs b/virtualizationservice/src/aidl.rs
index 3ebde6f..44d89e7 100644
--- a/virtualizationservice/src/aidl.rs
+++ b/virtualizationservice/src/aidl.rs
@@ -339,6 +339,8 @@
) -> Result<VirtualMachineRawConfig> {
let apk_file = config.apk.as_ref().unwrap().as_ref();
let idsig_file = config.idsig.as_ref().unwrap().as_ref();
+ // TODO(b/193504400) pass this to crosvm
+ let _instance_file = config.instanceImage.as_ref().unwrap().as_ref();
let config_path = &config.configPath;
let mut apk_zip = ZipArchive::new(apk_file)?;
diff --git a/vm/src/create_partition.rs b/vm/src/create_partition.rs
new file mode 100644
index 0000000..acebbf2
--- /dev/null
+++ b/vm/src/create_partition.rs
@@ -0,0 +1,40 @@
+// 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.
+
+//! Command to create an empty partition
+
+use android_system_virtualizationservice::aidl::android::system::virtualizationservice::IVirtualizationService::IVirtualizationService;
+use android_system_virtualizationservice::binder::{ParcelFileDescriptor, Strong};
+use anyhow::{Context, Error};
+use std::convert::TryInto;
+use std::fs::OpenOptions;
+use std::path::Path;
+
+/// Initialise an empty partition image of the given size to be used as a writable partition.
+pub fn command_create_partition(
+ service: Strong<dyn IVirtualizationService>,
+ image_path: &Path,
+ size: u64,
+) -> Result<(), Error> {
+ let image = OpenOptions::new()
+ .create_new(true)
+ .read(true)
+ .write(true)
+ .open(image_path)
+ .with_context(|| format!("Failed to create {:?}", image_path))?;
+ service
+ .initializeWritablePartition(&ParcelFileDescriptor::new(image), size.try_into()?)
+ .context("Failed to initialize partition with size {}, size")?;
+ Ok(())
+}
diff --git a/vm/src/main.rs b/vm/src/main.rs
index 9ca0ea6..09f11d5 100644
--- a/vm/src/main.rs
+++ b/vm/src/main.rs
@@ -14,16 +14,16 @@
//! Android VM control tool.
+mod create_partition;
mod run;
mod sync;
use android_system_virtualizationservice::aidl::android::system::virtualizationservice::IVirtualizationService::IVirtualizationService;
-use android_system_virtualizationservice::binder::{wait_for_interface, ProcessState, Strong, ParcelFileDescriptor};
+use android_system_virtualizationservice::binder::{wait_for_interface, ProcessState, Strong};
use anyhow::{Context, Error};
+use create_partition::command_create_partition;
use run::{command_run, command_run_app};
-use std::convert::TryInto;
-use std::fs::OpenOptions;
-use std::path::{PathBuf, Path};
+use std::path::PathBuf;
use structopt::clap::AppSettings;
use structopt::StructOpt;
@@ -43,6 +43,10 @@
#[structopt(parse(from_os_str))]
idsig: PathBuf,
+ /// Path to the instance image. Created if not exists.
+ #[structopt(parse(from_os_str))]
+ instance: PathBuf,
+
/// Path to VM config JSON within APK (e.g. assets/vm_config.json)
config_path: String,
@@ -101,8 +105,17 @@
.context("Failed to find VirtualizationService")?;
match opt {
- Opt::RunApp { apk, idsig, config_path, daemonize, log, debug } => {
- command_run_app(service, &apk, &idsig, &config_path, daemonize, log.as_deref(), debug)
+ Opt::RunApp { apk, idsig, instance, config_path, daemonize, log, debug } => {
+ command_run_app(
+ service,
+ &apk,
+ &idsig,
+ &instance,
+ &config_path,
+ daemonize,
+ log.as_deref(),
+ debug,
+ )
}
Opt::Run { config, daemonize, log } => {
command_run(service, &config, daemonize, log.as_deref())
@@ -128,21 +141,3 @@
println!("Running VMs: {:#?}", vms);
Ok(())
}
-
-/// Initialise an empty partition image of the given size to be used as a writable partition.
-fn command_create_partition(
- service: Strong<dyn IVirtualizationService>,
- image_path: &Path,
- size: u64,
-) -> Result<(), Error> {
- let image = OpenOptions::new()
- .create_new(true)
- .read(true)
- .write(true)
- .open(image_path)
- .with_context(|| format!("Failed to create {:?}", image_path))?;
- service
- .initializeWritablePartition(&ParcelFileDescriptor::new(image), size.try_into()?)
- .context("Failed to initialize partition with size {}, size")?;
- Ok(())
-}
diff --git a/vm/src/run.rs b/vm/src/run.rs
index 184a396..41fdabb 100644
--- a/vm/src/run.rs
+++ b/vm/src/run.rs
@@ -14,6 +14,7 @@
//! Command to run a VM.
+use crate::create_partition::command_create_partition;
use crate::sync::AtomicFlag;
use android_system_virtualizationservice::aidl::android::system::virtualizationservice::IVirtualizationService::IVirtualizationService;
use android_system_virtualizationservice::aidl::android::system::virtualizationservice::IVirtualMachine::IVirtualMachine;
@@ -33,13 +34,15 @@
use std::io::{self, BufRead, BufReader};
use std::os::unix::io::{AsRawFd, FromRawFd};
use std::path::Path;
-use vmconfig::VmConfig;
+use vmconfig::{open_parcel_file, VmConfig};
/// Run a VM from the given APK, idsig, and config.
+#[allow(clippy::too_many_arguments)]
pub fn command_run_app(
service: Strong<dyn IVirtualizationService>,
apk: &Path,
idsig: &Path,
+ instance: &Path,
config_path: &str,
daemonize: bool,
log_path: Option<&Path>,
@@ -47,9 +50,16 @@
) -> Result<(), Error> {
let apk_file = File::open(apk).context("Failed to open APK file")?;
let idsig_file = File::open(idsig).context("Failed to open idsig file")?;
+
+ if !instance.exists() {
+ const INSTANCE_FILE_SIZE: u64 = 10 * 1024 * 1024;
+ command_create_partition(service.clone(), instance, INSTANCE_FILE_SIZE)?;
+ }
+
let config = VirtualMachineConfig::AppConfig(VirtualMachineAppConfig {
apk: ParcelFileDescriptor::new(apk_file).into(),
idsig: ParcelFileDescriptor::new(idsig_file).into(),
+ instanceImage: open_parcel_file(instance, true /* writable */)?.into(),
configPath: config_path.to_owned(),
debug,
});
diff --git a/vmconfig/src/lib.rs b/vmconfig/src/lib.rs
index 1a16d30..4a5b3b1 100644
--- a/vmconfig/src/lib.rs
+++ b/vmconfig/src/lib.rs
@@ -153,7 +153,7 @@
}
/// Try to open the given file and wrap it in a [`ParcelFileDescriptor`].
-fn open_parcel_file(filename: &Path, writable: bool) -> Result<ParcelFileDescriptor> {
+pub fn open_parcel_file(filename: &Path, writable: bool) -> Result<ParcelFileDescriptor> {
Ok(ParcelFileDescriptor::new(
OpenOptions::new()
.read(true)