Merge "Add framework-virtualization to combined_apis"
diff --git a/apkdmverity/src/main.rs b/apkdmverity/src/main.rs
index a69b583..6e12e38 100644
--- a/apkdmverity/src/main.rs
+++ b/apkdmverity/src/main.rs
@@ -162,7 +162,7 @@
}
fn create_block_aligned_file(path: &Path, data: &[u8]) {
- let mut f = File::create(&path).unwrap();
+ let mut f = File::create(path).unwrap();
f.write_all(data).unwrap();
// Add padding so that the size of the file is multiple of 4096.
diff --git a/compos/src/artifact_signer.rs b/compos/src/artifact_signer.rs
index e51b8dd..d3843fc 100644
--- a/compos/src/artifact_signer.rs
+++ b/compos/src/artifact_signer.rs
@@ -46,7 +46,7 @@
pub fn add_artifact(&mut self, path: &Path) -> Result<()> {
// The path we store is where the file will be when it is verified, not where it is now.
let suffix = path
- .strip_prefix(&self.base_directory)
+ .strip_prefix(self.base_directory)
.context("Artifacts must be under base directory")?;
let target_path = Path::new(TARGET_DIRECTORY).join(suffix);
let target_path = target_path.to_str().ok_or_else(|| anyhow!("Invalid path"))?;
diff --git a/compos/src/compsvc.rs b/compos/src/compsvc.rs
index 4330bbf..0e8b9f5 100644
--- a/compos/src/compsvc.rs
+++ b/compos/src/compsvc.rs
@@ -144,7 +144,7 @@
fn add_artifacts(target_dir: &Path, artifact_signer: &mut ArtifactSigner) -> Result<()> {
for entry in
- read_dir(&target_dir).with_context(|| format!("Traversing {}", target_dir.display()))?
+ read_dir(target_dir).with_context(|| format!("Traversing {}", target_dir.display()))?
{
let entry = entry?;
let file_type = entry.file_type()?;
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachine.java b/javalib/src/android/system/virtualmachine/VirtualMachine.java
index 6233e0a..c200d00 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachine.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachine.java
@@ -16,6 +16,7 @@
package android.system.virtualmachine;
+import static android.os.ParcelFileDescriptor.AutoCloseInputStream;
import static android.os.ParcelFileDescriptor.MODE_READ_ONLY;
import static android.os.ParcelFileDescriptor.MODE_READ_WRITE;
import static android.system.virtualmachine.VirtualMachineCallback.ERROR_PAYLOAD_CHANGED;
@@ -69,12 +70,14 @@
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.ref.WeakReference;
+import java.nio.channels.FileChannel;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
@@ -284,10 +287,45 @@
return sInstances.computeIfAbsent(context, unused -> new HashMap<>());
}
+ /**
+ * Builds a virtual machine from an {@link VirtualMachineDescriptor} object and associates it
+ * with the given name.
+ *
+ * <p>The new virtual machine will be in the same state as the descriptor indicates.
+ *
+ * <p>Once a virtual machine is imported it is persisted until it is deleted by calling {@link
+ * #delete}. The imported virtual machine is in {@link #STATUS_STOPPED} state. To run the VM,
+ * call {@link #run}.
+ */
+ @GuardedBy("sCreateLock")
@NonNull
- private static File getVmDir(Context context, String name) {
- File vmRoot = new File(context.getDataDir(), VM_DIR);
- return new File(vmRoot, name);
+ static VirtualMachine fromDescriptor(
+ @NonNull Context context,
+ @NonNull String name,
+ @NonNull VirtualMachineDescriptor vmDescriptor)
+ throws VirtualMachineException {
+ VirtualMachineConfig config = VirtualMachineConfig.from(vmDescriptor.getConfigFd());
+ File vmDir = createVmDir(context, name);
+ try {
+ VirtualMachine vm = new VirtualMachine(context, name, config);
+ config.serialize(vm.mConfigFilePath);
+ try {
+ vm.mInstanceFilePath.createNewFile();
+ } catch (IOException e) {
+ throw new VirtualMachineException("failed to create instance image", e);
+ }
+ vm.importInstanceFrom(vmDescriptor.getInstanceImgFd());
+ getInstancesMap(context).put(name, new WeakReference<>(vm));
+ return vm;
+ } catch (VirtualMachineException | RuntimeException e) {
+ // If anything goes wrong, delete any files created so far and the VM's directory
+ try {
+ deleteRecursively(vmDir);
+ } catch (IOException innerException) {
+ e.addSuppressed(innerException);
+ }
+ throw e;
+ }
}
/**
@@ -300,21 +338,7 @@
static VirtualMachine create(
@NonNull Context context, @NonNull String name, @NonNull VirtualMachineConfig config)
throws VirtualMachineException {
- File vmDir = getVmDir(context, name);
-
- try {
- // We don't need to undo this even if VM creation fails.
- Files.createDirectories(vmDir.getParentFile().toPath());
-
- // The checking of the existence of this directory and the creation of it is done
- // atomically. If the directory already exists (i.e. the VM with the same name was
- // already created), FileAlreadyExistsException is thrown.
- Files.createDirectory(vmDir.toPath());
- } catch (FileAlreadyExistsException e) {
- throw new VirtualMachineException("virtual machine already exists", e);
- } catch (IOException e) {
- throw new VirtualMachineException("failed to create directory for VM", e);
- }
+ File vmDir = createVmDir(context, name);
try {
VirtualMachine vm = new VirtualMachine(context, name, config);
@@ -412,6 +436,33 @@
if (instancesMap != null) instancesMap.remove(name);
}
+ @GuardedBy("sCreateLock")
+ @NonNull
+ private static File createVmDir(@NonNull Context context, @NonNull String name)
+ throws VirtualMachineException {
+ File vmDir = getVmDir(context, name);
+ try {
+ // We don't need to undo this even if VM creation fails.
+ Files.createDirectories(vmDir.getParentFile().toPath());
+
+ // The checking of the existence of this directory and the creation of it is done
+ // atomically. If the directory already exists (i.e. the VM with the same name was
+ // already created), FileAlreadyExistsException is thrown.
+ Files.createDirectory(vmDir.toPath());
+ } catch (FileAlreadyExistsException e) {
+ throw new VirtualMachineException("virtual machine already exists", e);
+ } catch (IOException e) {
+ throw new VirtualMachineException("failed to create directory for VM", e);
+ }
+ return vmDir;
+ }
+
+ @NonNull
+ private static File getVmDir(Context context, String name) {
+ File vmRoot = new File(context.getDataDir(), VM_DIR);
+ return new File(vmRoot, name);
+ }
+
/**
* Returns the name of this virtual machine. The name is unique in the package and can't be
* changed.
@@ -889,6 +940,7 @@
* @return a {@link VirtualMachineDescriptor} instance that represents the VM's state.
* @throws VirtualMachineException if the virtual machine is not stopped, or the state could not
* be captured.
+ * @hide
*/
@NonNull
public VirtualMachineDescriptor toDescriptor() throws VirtualMachineException {
@@ -1053,4 +1105,14 @@
throw new VirtualMachineException("Couldn't parse extra apks from the vm config", e);
}
}
+
+ private void importInstanceFrom(@NonNull ParcelFileDescriptor instanceFd)
+ throws VirtualMachineException {
+ try (FileChannel instance = new FileOutputStream(mInstanceFilePath).getChannel();
+ FileChannel instanceInput = new AutoCloseInputStream(instanceFd).getChannel()) {
+ instance.transferFrom(instanceInput, /*position=*/ 0, instanceInput.size());
+ } catch (IOException e) {
+ throw new VirtualMachineException("failed to transfer instance image", e);
+ }
+ }
}
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java b/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java
index 593a57d..a660306 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java
@@ -16,6 +16,7 @@
package android.system.virtualmachine;
+import static android.os.ParcelFileDescriptor.AutoCloseInputStream;
import static android.os.ParcelFileDescriptor.MODE_READ_ONLY;
import static java.util.Objects.requireNonNull;
@@ -193,6 +194,17 @@
}
}
+ /** Loads a config from a {@link ParcelFileDescriptor}. */
+ @NonNull
+ static VirtualMachineConfig from(@NonNull ParcelFileDescriptor fd)
+ throws VirtualMachineException {
+ try (AutoCloseInputStream input = new AutoCloseInputStream(fd)) {
+ return fromInputStream(input);
+ } catch (IOException e) {
+ throw new VirtualMachineException("failed to read VM config from file descriptor", e);
+ }
+ }
+
/** Loads a config from a stream, for example a file. */
@NonNull
private static VirtualMachineConfig fromInputStream(@NonNull InputStream input)
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachineDescriptor.java b/javalib/src/android/system/virtualmachine/VirtualMachineDescriptor.java
index 70532fc..b51cbce 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachineDescriptor.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachineDescriptor.java
@@ -23,8 +23,6 @@
import android.os.ParcelFileDescriptor;
import android.os.Parcelable;
-import com.android.internal.annotations.VisibleForTesting;
-
/**
* A VM descriptor that captures the state of a Virtual Machine.
*
@@ -35,8 +33,8 @@
* @hide
*/
public final class VirtualMachineDescriptor implements Parcelable {
- private final @NonNull ParcelFileDescriptor mConfigFd;
- private final @NonNull ParcelFileDescriptor mInstanceImgFd;
+ @NonNull private final ParcelFileDescriptor mConfigFd;
+ @NonNull private final ParcelFileDescriptor mInstanceImgFd;
// TODO(b/243129654): Add trusted storage fd once it is available.
@Override
@@ -45,13 +43,14 @@
}
@Override
- public void writeToParcel(Parcel out, int flags) {
+ public void writeToParcel(@NonNull Parcel out, int flags) {
mConfigFd.writeToParcel(out, flags);
mInstanceImgFd.writeToParcel(out, flags);
}
+ @NonNull
public static final Parcelable.Creator<VirtualMachineDescriptor> CREATOR =
- new Parcelable.Creator<VirtualMachineDescriptor>() {
+ new Parcelable.Creator<>() {
public VirtualMachineDescriptor createFromParcel(Parcel in) {
return new VirtualMachineDescriptor(in);
}
@@ -63,19 +62,17 @@
/**
* @return File descriptor of the VM configuration file config.xml.
- * @hide
*/
- @VisibleForTesting
- public @NonNull ParcelFileDescriptor getConfigFd() {
+ @NonNull
+ ParcelFileDescriptor getConfigFd() {
return mConfigFd;
}
/**
* @return File descriptor of the instance.img of the VM.
- * @hide
*/
- @VisibleForTesting
- public @NonNull ParcelFileDescriptor getInstanceImgFd() {
+ @NonNull
+ ParcelFileDescriptor getInstanceImgFd() {
return mInstanceImgFd;
}
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachineManager.java b/javalib/src/android/system/virtualmachine/VirtualMachineManager.java
index 34b9fd9..c357f50 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachineManager.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachineManager.java
@@ -142,6 +142,24 @@
}
/**
+ * Imports a virtual machine from an {@link VirtualMachineDescriptor} object and associates it
+ * with the given name.
+ *
+ * <p>The new virtual machine will be in the same state as the descriptor indicates.
+ *
+ * @throws VirtualMachineException if the VM cannot be imported.
+ * @hide
+ */
+ @NonNull
+ public VirtualMachine importFromDescriptor(
+ @NonNull String name, @NonNull VirtualMachineDescriptor vmDescriptor)
+ throws VirtualMachineException {
+ synchronized (VirtualMachine.sCreateLock) {
+ return VirtualMachine.fromDescriptor(mContext, name, vmDescriptor);
+ }
+ }
+
+ /**
* Returns an existing {@link VirtualMachine} with the given name. Returns null if there is no
* such virtual machine.
*
diff --git a/libs/devicemapper/src/lib.rs b/libs/devicemapper/src/lib.rs
index b9fb5c3..ebe71e4 100644
--- a/libs/devicemapper/src/lib.rs
+++ b/libs/devicemapper/src/lib.rs
@@ -212,7 +212,7 @@
dm_dev_suspend(self, &mut data).context("failed to activate")?;
// Step 4: wait unti the device is created and return the device path
- let path = Path::new(MAPPER_DEV_ROOT).join(&name);
+ let path = Path::new(MAPPER_DEV_ROOT).join(name);
wait_for_path(&path)?;
Ok(path)
}
@@ -250,13 +250,13 @@
}
fn write_to_dev(path: &Path, data: &[u8]) {
- let mut f = OpenOptions::new().read(true).write(true).open(&path).unwrap();
+ let mut f = OpenOptions::new().read(true).write(true).open(path).unwrap();
f.write_all(data).unwrap();
}
fn delete_device(dm: &DeviceMapper, name: &str) -> Result<()> {
dm.delete_device_deferred(name)?;
- wait_for_path_disappears(Path::new(MAPPER_DEV_ROOT).join(&name))?;
+ wait_for_path_disappears(Path::new(MAPPER_DEV_ROOT).join(name))?;
Ok(())
}
diff --git a/tests/aidl/com/android/microdroid/testservice/IBenchmarkService.aidl b/tests/aidl/com/android/microdroid/testservice/IBenchmarkService.aidl
index 260f804..16e4893 100644
--- a/tests/aidl/com/android/microdroid/testservice/IBenchmarkService.aidl
+++ b/tests/aidl/com/android/microdroid/testservice/IBenchmarkService.aidl
@@ -25,7 +25,7 @@
*
* @return The read rate in MB/s.
*/
- double measureReadRate(String filename, long fileSizeBytes, boolean isRand);
+ double measureReadRate(String filename, boolean isRand);
/** Returns an entry from /proc/meminfo. */
long getMemInfoEntry(String name);
diff --git a/tests/benchmark/src/java/com/android/microdroid/benchmark/MicrodroidBenchmarks.java b/tests/benchmark/src/java/com/android/microdroid/benchmark/MicrodroidBenchmarks.java
index db74358..28852e8 100644
--- a/tests/benchmark/src/java/com/android/microdroid/benchmark/MicrodroidBenchmarks.java
+++ b/tests/benchmark/src/java/com/android/microdroid/benchmark/MicrodroidBenchmarks.java
@@ -257,18 +257,10 @@
private static class VirtioBlkListener implements BenchmarkVmListener.InnerListener {
private static final String FILENAME = APEX_ETC_FS + "microdroid_super.img";
- private final long mFileSizeBytes;
private final List<Double> mReadRates;
private final boolean mIsRand;
VirtioBlkListener(List<Double> readRates, boolean isRand) {
- File file = new File(FILENAME);
- try {
- mFileSizeBytes = Files.size(file.toPath());
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- assertThat(mFileSizeBytes).isGreaterThan((long) SIZE_MB);
mReadRates = readRates;
mIsRand = isRand;
}
@@ -276,7 +268,7 @@
@Override
public void onPayloadReady(VirtualMachine vm, IBenchmarkService benchmarkService)
throws RemoteException {
- double readRate = benchmarkService.measureReadRate(FILENAME, mFileSizeBytes, mIsRand);
+ double readRate = benchmarkService.measureReadRate(FILENAME, mIsRand);
mReadRates.add(readRate);
}
}
diff --git a/tests/benchmark/src/native/benchmarkbinary.cpp b/tests/benchmark/src/native/benchmarkbinary.cpp
index 70c6884..e43025c 100644
--- a/tests/benchmark/src/native/benchmarkbinary.cpp
+++ b/tests/benchmark/src/native/benchmarkbinary.cpp
@@ -23,6 +23,8 @@
#include <fcntl.h>
#include <linux/vm_sockets.h>
#include <stdio.h>
+#include <sys/stat.h>
+#include <sys/types.h>
#include <time.h>
#include <unistd.h>
#include <vm_main.h>
@@ -56,9 +58,9 @@
class IOBenchmarkService : public aidl::com::android::microdroid::testservice::BnBenchmarkService {
public:
- ndk::ScopedAStatus measureReadRate(const std::string& filename, int64_t fileSizeBytes,
- bool isRand, double* out) override {
- auto res = measure_read_rate(filename, fileSizeBytes, isRand);
+ ndk::ScopedAStatus measureReadRate(const std::string& filename, bool isRand,
+ double* out) override {
+ auto res = measure_read_rate(filename, isRand);
if (res.ok()) {
*out = res.value();
}
@@ -90,10 +92,17 @@
}
private:
- /** Measures the read rate for reading the given file. */
- Result<double> measure_read_rate(const std::string& filename, int64_t fileSizeBytes,
- bool is_rand) {
- const int64_t block_count = fileSizeBytes / kBlockSizeBytes;
+ /**
+ * Measures the read rate for reading the given file.
+ * @return The read rate in MB/s.
+ */
+ Result<double> measure_read_rate(const std::string& filename, bool is_rand) {
+ struct stat file_stats;
+ if (stat(filename.c_str(), &file_stats) == -1) {
+ return Error() << "failed to get file stats";
+ }
+ const int64_t file_size_bytes = file_stats.st_size;
+ const int64_t block_count = file_size_bytes / kBlockSizeBytes;
std::vector<uint64_t> offsets(block_count);
for (auto i = 0; i < block_count; ++i) {
offsets[i] = i * kBlockSizeBytes;
@@ -118,8 +127,8 @@
}
}
double elapsed_seconds = ((double)clock() - start) / CLOCKS_PER_SEC;
- double read_rate = (double)fileSizeBytes / kNumBytesPerMB / elapsed_seconds;
- return {read_rate};
+ double file_size_mb = (double)file_size_bytes / kNumBytesPerMB;
+ return {file_size_mb / elapsed_seconds};
}
Result<size_t> read_meminfo_entry(const std::string& stat) {
diff --git a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
index 492eb33..5e86798 100644
--- a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
+++ b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
@@ -61,6 +61,7 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
+import java.util.Arrays;
import java.util.List;
import java.util.OptionalLong;
import java.util.UUID;
@@ -600,31 +601,46 @@
}
@Test
- public void vmConvertsToValidDescriptor() throws Exception {
+ public void importedVmIsEqualToTheOriginalVm() throws Exception {
// Arrange
VirtualMachineConfig config =
mInner.newVmConfigBuilder()
.setPayloadBinaryPath("MicrodroidTestNativeLib.so")
.setDebugLevel(DEBUG_LEVEL_NONE)
.build();
- String vmName = "test_vm";
- VirtualMachine vm = mInner.forceCreateNewVirtualMachine(vmName, config);
+ String vmNameOrig = "test_vm_orig", vmNameImport = "test_vm_import";
+ VirtualMachine vmOrig = mInner.forceCreateNewVirtualMachine(vmNameOrig, config);
+ // Run something to make the instance.img different with the initialized one.
+ TestResults origTestResults = runVmTestService(vmOrig);
+ assertThat(origTestResults.mException).isNull();
+ assertThat(origTestResults.mAddInteger).isEqualTo(123 + 456);
+ VirtualMachineDescriptor descriptor = vmOrig.toDescriptor();
+ VirtualMachineManager vmm = mInner.getVirtualMachineManager();
+ if (vmm.get(vmNameImport) != null) {
+ vmm.delete(vmNameImport);
+ }
// Action
- VirtualMachineDescriptor descriptor = vm.toDescriptor();
+ VirtualMachine vmImport = vmm.importFromDescriptor(vmNameImport, descriptor);
// Asserts
- assertFileContentsAreEqual(descriptor.getConfigFd(), vmName, "config.xml");
- assertFileContentsAreEqual(descriptor.getInstanceImgFd(), vmName, "instance.img");
+ assertFileContentsAreEqualInTwoVms("config.xml", vmNameOrig, vmNameImport);
+ assertFileContentsAreEqualInTwoVms("instance.img", vmNameOrig, vmNameImport);
+ assertThat(vmImport).isNotEqualTo(vmOrig);
+ vmm.delete(vmNameOrig);
+ assertThat(vmImport).isEqualTo(vmm.get(vmNameImport));
+ TestResults testResults = runVmTestService(vmImport);
+ assertThat(testResults.mException).isNull();
+ assertThat(testResults.mAddInteger).isEqualTo(123 + 456);
}
- private void assertFileContentsAreEqual(
- ParcelFileDescriptor parcelFd, String vmName, String fileName) throws IOException {
- File file = getVmFile(vmName, fileName);
- // Use try-with-resources to close the files automatically after assert.
- try (FileInputStream input1 = new FileInputStream(parcelFd.getFileDescriptor());
- FileInputStream input2 = new FileInputStream(file)) {
- assertThat(input1.readAllBytes()).isEqualTo(input2.readAllBytes());
+ private void assertFileContentsAreEqualInTwoVms(String fileName, String vmName1, String vmName2)
+ throws IOException {
+ File file1 = getVmFile(vmName1, fileName);
+ File file2 = getVmFile(vmName2, fileName);
+ try (FileInputStream input1 = new FileInputStream(file1);
+ FileInputStream input2 = new FileInputStream(file2)) {
+ assertThat(Arrays.equals(input1.readAllBytes(), input2.readAllBytes())).isTrue();
}
}
diff --git a/virtualizationservice/src/aidl.rs b/virtualizationservice/src/aidl.rs
index 340fc68..a8f68bc 100644
--- a/virtualizationservice/src/aidl.rs
+++ b/virtualizationservice/src/aidl.rs
@@ -500,8 +500,7 @@
}
fn prepare_ramdump_file(ramdump_path: &Path) -> Result<File> {
- File::create(&ramdump_path)
- .context(format!("Failed to create ramdump file {:?}", &ramdump_path))
+ File::create(ramdump_path).context(format!("Failed to create ramdump file {:?}", &ramdump_path))
}
/// Given the configuration for a disk image, assembles the `DiskFile` to pass to crosvm.
diff --git a/virtualizationservice/src/composite.rs b/virtualizationservice/src/composite.rs
index c9a68ac..fe17ff4 100644
--- a/virtualizationservice/src/composite.rs
+++ b/virtualizationservice/src/composite.rs
@@ -51,7 +51,7 @@
OpenOptions::new().create_new(true).read(true).write(true).open(footer_path).with_context(
|| format!("Failed to create composite image header {:?}", footer_path),
)?;
- let zero_filler_file = File::open(&zero_filler_path).with_context(|| {
+ let zero_filler_file = File::open(zero_filler_path).with_context(|| {
format!("Failed to open composite image zero filler {:?}", zero_filler_path)
})?;
@@ -66,7 +66,7 @@
)?;
// Re-open the composite image as read-only.
- let composite_image = File::open(&output_path)
+ let composite_image = File::open(output_path)
.with_context(|| format!("Failed to open composite image {:?}", output_path))?;
files.push(header_file);