Merge "Include blkid and fsck.erofs in deapexer call"
diff --git a/compos/tests/AndroidTest.xml b/compos/tests/AndroidTest.xml
index 2a84291..f9e6837 100644
--- a/compos/tests/AndroidTest.xml
+++ b/compos/tests/AndroidTest.xml
@@ -14,6 +14,8 @@
limitations under the License.
-->
<configuration description="Tests for CompOS">
+ <option name="config-descriptor:metadata" key="mainline-param" value="com.google.android.art.apex" />
+
<target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer">
<option name="force-root" value="true" />
</target_preparer>
diff --git a/javalib/Android.bp b/javalib/Android.bp
index 51dd381..cb03fa1 100644
--- a/javalib/Android.bp
+++ b/javalib/Android.bp
@@ -31,6 +31,13 @@
// android.sysprop.*, renamed by jarjar
"com.android.system.virtualmachine.sysprop",
],
+ errorprone: {
+ // We use @GuardedBy and we want a test failure if our locking isn't consistent with it.
+ enabled: true,
+ javacflags: [
+ "-Xep:GuardedBy:ERROR",
+ ],
+ },
}
prebuilt_apis {
diff --git a/javalib/src/android/system/virtualmachine/ParcelVirtualMachine.java b/javalib/src/android/system/virtualmachine/ParcelVirtualMachine.java
new file mode 100644
index 0000000..808f30a
--- /dev/null
+++ b/javalib/src/android/system/virtualmachine/ParcelVirtualMachine.java
@@ -0,0 +1,92 @@
+/*
+ * 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.
+ */
+
+package android.system.virtualmachine;
+
+import static java.util.Objects.requireNonNull;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.ParcelFileDescriptor;
+import android.os.Parcelable;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * A parcelable that captures the state of a Virtual Machine.
+ *
+ * <p>You can capture the current state of VM by creating an instance of this class with {@link
+ * VirtualMachine#toParcelVirtualMachine()}, optionally pass it to another App, and then build an
+ * identical VM with the parcel received.
+ *
+ * @hide
+ */
+public final class ParcelVirtualMachine implements Parcelable {
+ private final @NonNull ParcelFileDescriptor mConfigFd;
+ private final @NonNull ParcelFileDescriptor mInstanceImgFd;
+ // TODO(b/243129654): Add trusted storage fd once it is available.
+
+ @Override
+ public int describeContents() {
+ return CONTENTS_FILE_DESCRIPTOR;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ mConfigFd.writeToParcel(out, flags);
+ mInstanceImgFd.writeToParcel(out, flags);
+ }
+
+ public static final Parcelable.Creator<ParcelVirtualMachine> CREATOR =
+ new Parcelable.Creator<ParcelVirtualMachine>() {
+ public ParcelVirtualMachine createFromParcel(Parcel in) {
+ return new ParcelVirtualMachine(in);
+ }
+
+ public ParcelVirtualMachine[] newArray(int size) {
+ return new ParcelVirtualMachine[size];
+ }
+ };
+
+ /**
+ * @return File descriptor of the VM configuration file config.xml.
+ * @hide
+ */
+ @VisibleForTesting
+ public @NonNull ParcelFileDescriptor getConfigFd() {
+ return mConfigFd;
+ }
+
+ /**
+ * @return File descriptor of the instance.img of the VM.
+ * @hide
+ */
+ @VisibleForTesting
+ public @NonNull ParcelFileDescriptor getInstanceImgFd() {
+ return mInstanceImgFd;
+ }
+
+ ParcelVirtualMachine(
+ @NonNull ParcelFileDescriptor configFd, @NonNull ParcelFileDescriptor instanceImgFd) {
+ mConfigFd = configFd;
+ mInstanceImgFd = instanceImgFd;
+ }
+
+ private ParcelVirtualMachine(Parcel in) {
+ mConfigFd = requireNonNull(in.readFileDescriptor());
+ mInstanceImgFd = requireNonNull(in.readFileDescriptor());
+ }
+}
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachine.java b/javalib/src/android/system/virtualmachine/VirtualMachine.java
index f3e4fe9..e750ae9 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachine.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachine.java
@@ -105,11 +105,11 @@
* @hide
*/
public class VirtualMachine implements AutoCloseable {
+ /** Map from context to a map of all that context's VMs by name. */
+ @GuardedBy("sCreateLock")
private static final Map<Context, Map<String, WeakReference<VirtualMachine>>> sInstances =
new WeakHashMap<>();
- private static final Object sInstancesLock = new Object();
-
/** Name of the directory under the files directory where all VMs created for the app exist. */
private static final String VM_DIR = "vm";
@@ -138,7 +138,6 @@
public static final String USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION =
"android.permission.USE_CUSTOM_VIRTUAL_MACHINE";
-
/**
* Status of a virtual machine
*
@@ -165,9 +164,6 @@
*/
public static final int STATUS_DELETED = 2;
- /** Lock for internal synchronization. */
- private final Object mLock = new Object();
-
/** The package which owns this VM. */
@NonNull private final String mPackageName;
@@ -175,6 +171,11 @@
@NonNull private final String mName;
/**
+ * Path to the directory containing all the files related to this VM.
+ */
+ @NonNull private final File mVmRootPath;
+
+ /**
* Path to the config file for this VM. The config file is where the configuration is persisted.
*/
@NonNull private final File mConfigFilePath;
@@ -196,38 +197,69 @@
}
/**
- * List of extra apks. Apks are specified by the vm config, and corresponding idsigs are to be
- * generated.
+ * Unmodifiable list of extra apks. Apks are specified by the vm config, and corresponding
+ * idsigs are to be generated.
*/
@NonNull private final List<ExtraApkSpec> mExtraApks;
/** Size of the instance image. 10 MB. */
private static final long INSTANCE_FILE_SIZE = 10 * 1024 * 1024;
+ // A note on lock ordering:
+ // You can take mLock while holding sCreateLock, but not vice versa.
+ // We never take any other lock while holding mCallbackLock; therefore you can
+ // take mCallbackLock while holding any other lock.
+
+ /**
+ * A lock used to synchronize the creation of virtual machines. It protects
+ * {@link #sInstances}, but is also held throughout VM creation / retrieval / deletion, to
+ * prevent these actions racing with each other.
+ */
+ static final Object sCreateLock = new Object();
+
+ /** Lock protecting our mutable state (other than callbacks). */
+ private final Object mLock = new Object();
+
+ /** Lock protecting callbacks. */
+ private final Object mCallbackLock = new Object();
+
+
/** The configuration that is currently associated with this VM. */
- @NonNull private VirtualMachineConfig mConfig;
+ @GuardedBy("mLock")
+ @NonNull
+ private VirtualMachineConfig mConfig;
/** Handle to the "running" VM. */
- @Nullable private IVirtualMachine mVirtualMachine;
+ @GuardedBy("mLock")
+ @Nullable
+ private IVirtualMachine mVirtualMachine;
+
+ @GuardedBy("mLock")
+ @Nullable
+ private ParcelFileDescriptor mConsoleReader;
+
+ @GuardedBy("mLock")
+ @Nullable
+ private ParcelFileDescriptor mConsoleWriter;
+
+ @GuardedBy("mLock")
+ @Nullable
+ private ParcelFileDescriptor mLogReader;
+
+ @GuardedBy("mLock")
+ @Nullable
+ private ParcelFileDescriptor mLogWriter;
/** The registered callback */
- @GuardedBy("mLock")
+ @GuardedBy("mCallbackLock")
@Nullable
private VirtualMachineCallback mCallback;
/** The executor on which the callback will be executed */
- @GuardedBy("mLock")
+ @GuardedBy("mCallbackLock")
@Nullable
private Executor mCallbackExecutor;
- @Nullable private ParcelFileDescriptor mConsoleReader;
- @Nullable private ParcelFileDescriptor mConsoleWriter;
-
- @Nullable private ParcelFileDescriptor mLogReader;
- @Nullable private ParcelFileDescriptor mLogWriter;
-
- @NonNull private final Context mContext;
-
static {
System.loadLibrary("virtualmachine_jni");
}
@@ -235,86 +267,111 @@
private VirtualMachine(
@NonNull Context context, @NonNull String name, @NonNull VirtualMachineConfig config)
throws VirtualMachineException {
- mContext = context;
mPackageName = context.getPackageName();
mName = requireNonNull(name, "Name must not be null");
mConfig = requireNonNull(config, "Config must not be null");
File thisVmDir = getVmDir(context, mName);
+ mVmRootPath = thisVmDir;
mConfigFilePath = new File(thisVmDir, CONFIG_FILE);
mInstanceFilePath = new File(thisVmDir, INSTANCE_IMAGE_FILE);
mIdsigFilePath = new File(thisVmDir, IDSIG_FILE);
mExtraApks = setupExtraApks(context, config, thisVmDir);
}
+ @GuardedBy("sCreateLock")
+ @NonNull
+ private static Map<String, WeakReference<VirtualMachine>> getInstancesMap(Context context) {
+ Map<String, WeakReference<VirtualMachine>> instancesMap;
+ if (sInstances.containsKey(context)) {
+ instancesMap = sInstances.get(context);
+ } else {
+ instancesMap = new HashMap<>();
+ sInstances.put(context, instancesMap);
+ }
+ return instancesMap;
+ }
+
+ @NonNull
+ private static File getVmDir(Context context, String name) {
+ File vmRoot = new File(context.getDataDir(), VM_DIR);
+ return new File(vmRoot, name);
+ }
+
/**
* Creates a virtual machine with the given name and config. Once a virtual machine is created
- * it is persisted until it is deleted by calling {@link #delete()}. The created virtual machine
- * is in {@link #STATUS_STOPPED} state. To run the VM, call {@link #run()}.
+ * it is persisted until it is deleted by calling {@link #delete}. The created virtual machine
+ * is in {@link #STATUS_STOPPED} state. To run the VM, call {@link #run}.
*/
+ @GuardedBy("sCreateLock")
@NonNull
static VirtualMachine create(
@NonNull Context context, @NonNull String name, @NonNull VirtualMachineConfig config)
throws VirtualMachineException {
- VirtualMachine vm = new VirtualMachine(context, name, config);
+ File vmDir = getVmDir(context, name);
try {
- final File thisVmDir = vm.mConfigFilePath.getParentFile();
- Files.createDirectories(thisVmDir.getParentFile().toPath());
+ // 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(thisVmDir.toPath());
-
- try (FileOutputStream output = new FileOutputStream(vm.mConfigFilePath)) {
- vm.mConfig.serialize(output);
- }
+ // 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(e);
+ throw new VirtualMachineException("failed to create directory for VM", e);
}
try {
- vm.mInstanceFilePath.createNewFile();
- } catch (IOException e) {
- throw new VirtualMachineException("failed to create instance image", e);
- }
+ VirtualMachine vm = new VirtualMachine(context, name, config);
- IVirtualizationService service =
- IVirtualizationService.Stub.asInterface(
- ServiceManager.waitForService(SERVICE_NAME));
-
- try {
- service.initializeWritablePartition(
- ParcelFileDescriptor.open(vm.mInstanceFilePath, MODE_READ_WRITE),
- INSTANCE_FILE_SIZE,
- PartitionType.ANDROID_VM_INSTANCE);
- } catch (FileNotFoundException e) {
- throw new VirtualMachineException("instance image missing", e);
- } catch (RemoteException e) {
- throw e.rethrowAsRuntimeException();
- } catch (ServiceSpecificException | IllegalArgumentException e) {
- throw new VirtualMachineException("failed to create instance partition", e);
- }
-
- synchronized (sInstancesLock) {
- Map<String, WeakReference<VirtualMachine>> instancesMap;
- if (sInstances.containsKey(context)) {
- instancesMap = sInstances.get(context);
- } else {
- instancesMap = new HashMap<>();
- sInstances.put(context, instancesMap);
+ try (FileOutputStream output = new FileOutputStream(vm.mConfigFilePath)) {
+ config.serialize(output);
+ } catch (IOException e) {
+ throw new VirtualMachineException("failed to write VM config", e);
}
- instancesMap.put(name, new WeakReference<>(vm));
- }
+ try {
+ vm.mInstanceFilePath.createNewFile();
+ } catch (IOException e) {
+ throw new VirtualMachineException("failed to create instance image", e);
+ }
- return vm;
+ IVirtualizationService service =
+ IVirtualizationService.Stub.asInterface(
+ ServiceManager.waitForService(SERVICE_NAME));
+
+ try {
+ service.initializeWritablePartition(
+ ParcelFileDescriptor.open(vm.mInstanceFilePath, MODE_READ_WRITE),
+ INSTANCE_FILE_SIZE,
+ PartitionType.ANDROID_VM_INSTANCE);
+ } catch (FileNotFoundException e) {
+ throw new VirtualMachineException("instance image missing", e);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ } catch (ServiceSpecificException | IllegalArgumentException e) {
+ throw new VirtualMachineException("failed to create instance partition", e);
+ }
+
+ 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;
+ }
}
/** Loads a virtual machine that is already created before. */
+ @GuardedBy("sCreateLock")
@Nullable
static VirtualMachine load(
@NonNull Context context, @NonNull String name) throws VirtualMachineException {
@@ -331,32 +388,50 @@
throw new VirtualMachineException("Failed to read config file", e);
}
- VirtualMachine vm = null;
- synchronized (sInstancesLock) {
- Map<String, WeakReference<VirtualMachine>> instancesMap;
- if (sInstances.containsKey(context)) {
- instancesMap = sInstances.get(context);
- } else {
- instancesMap = new HashMap<>();
- sInstances.put(context, instancesMap);
- }
+ Map<String, WeakReference<VirtualMachine>> instancesMap = getInstancesMap(context);
- if (instancesMap.containsKey(name)) {
- vm = instancesMap.get(name).get();
- }
- if (vm == null) {
- vm = new VirtualMachine(context, name, config);
- instancesMap.put(name, new WeakReference<>(vm));
- }
+ VirtualMachine vm = null;
+ if (instancesMap.containsKey(name)) {
+ vm = instancesMap.get(name).get();
+ }
+ if (vm == null) {
+ vm = new VirtualMachine(context, name, config);
}
if (!vm.mInstanceFilePath.exists()) {
throw new VirtualMachineException("instance image missing");
}
+ instancesMap.put(name, new WeakReference<>(vm));
+
return vm;
}
+ @GuardedBy("sCreateLock")
+ static void delete(Context context, String name) throws VirtualMachineException {
+ Map<String, WeakReference<VirtualMachine>> instancesMap = sInstances.get(context);
+ VirtualMachine vm;
+ if (instancesMap != null && instancesMap.containsKey(name)) {
+ vm = instancesMap.get(name).get();
+ } else {
+ vm = null;
+ }
+
+ if (vm != null) {
+ synchronized (vm.mLock) {
+ vm.checkStopped();
+ }
+ }
+
+ try {
+ deleteRecursively(getVmDir(context, name));
+ } catch (IOException e) {
+ throw new VirtualMachineException(e);
+ }
+
+ if (instancesMap != null) instancesMap.remove(name);
+ }
+
/**
* Returns the name of this virtual machine. The name is unique in the package and can't be
* changed.
@@ -379,7 +454,9 @@
*/
@NonNull
public VirtualMachineConfig getConfig() {
- return mConfig;
+ synchronized (mLock) {
+ return mConfig;
+ }
}
/**
@@ -389,27 +466,70 @@
*/
@Status
public int getStatus() {
+ IVirtualMachine virtualMachine;
+ synchronized (mLock) {
+ virtualMachine = mVirtualMachine;
+ }
+ if (virtualMachine == null) {
+ return mVmRootPath.exists() ? STATUS_STOPPED : STATUS_DELETED;
+ } else {
+ try {
+ return stateToStatus(virtualMachine.getState());
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+ }
+
+ private int stateToStatus(@VirtualMachineState int state) {
+ switch (state) {
+ case VirtualMachineState.STARTING:
+ case VirtualMachineState.STARTED:
+ case VirtualMachineState.READY:
+ case VirtualMachineState.FINISHED:
+ return STATUS_RUNNING;
+ case VirtualMachineState.NOT_STARTED:
+ case VirtualMachineState.DEAD:
+ default:
+ return STATUS_STOPPED;
+ }
+ }
+
+ // Throw an appropriate exception if we have a running VM, or the VM has been deleted.
+ @GuardedBy("mLock")
+ private void checkStopped() throws VirtualMachineException {
+ if (!mVmRootPath.exists()) {
+ throw new VirtualMachineException("VM has been deleted");
+ }
+ if (mVirtualMachine == null) {
+ return;
+ }
try {
- if (mVirtualMachine != null) {
- switch (mVirtualMachine.getState()) {
- case VirtualMachineState.NOT_STARTED:
- return STATUS_STOPPED;
- case VirtualMachineState.STARTING:
- case VirtualMachineState.STARTED:
- case VirtualMachineState.READY:
- case VirtualMachineState.FINISHED:
- return STATUS_RUNNING;
- case VirtualMachineState.DEAD:
- return STATUS_STOPPED;
+ if (stateToStatus(mVirtualMachine.getState()) != STATUS_STOPPED) {
+ throw new VirtualMachineException("VM is not in stopped state");
+ }
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ // If we have an IVirtualMachine in the running state return it, otherwise throw.
+ @GuardedBy("mLock")
+ private IVirtualMachine getRunningVm() throws VirtualMachineException {
+ try {
+ if (mVirtualMachine != null
+ && stateToStatus(mVirtualMachine.getState()) == STATUS_RUNNING) {
+ return mVirtualMachine;
+ } else {
+ if (!mVmRootPath.exists()) {
+ throw new VirtualMachineException("VM has been deleted");
+ } else {
+ throw new VirtualMachineException("VM is not in running state");
}
}
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
}
- if (!mConfigFilePath.exists()) {
- return STATUS_DELETED;
- }
- return STATUS_STOPPED;
}
/**
@@ -420,7 +540,7 @@
*/
public void setCallback(@NonNull @CallbackExecutor Executor executor,
@NonNull VirtualMachineCallback callback) {
- synchronized (mLock) {
+ synchronized (mCallbackLock) {
mCallback = callback;
mCallbackExecutor = executor;
}
@@ -432,7 +552,7 @@
* @hide
*/
public void clearCallback() {
- synchronized (mLock) {
+ synchronized (mCallbackLock) {
mCallback = null;
mCallbackExecutor = null;
}
@@ -442,7 +562,7 @@
private void executeCallback(Consumer<VirtualMachineCallback> fn) {
final VirtualMachineCallback callback;
final Executor executor;
- synchronized (mLock) {
+ synchronized (mCallbackLock) {
callback = mCallback;
executor = mCallbackExecutor;
}
@@ -469,117 +589,121 @@
*/
@RequiresPermission(MANAGE_VIRTUAL_MACHINE_PERMISSION)
public void run() throws VirtualMachineException {
- if (getStatus() != STATUS_STOPPED) {
- throw new VirtualMachineException(this + " is not in stopped state");
- }
+ synchronized (mLock) {
+ checkStopped();
- try {
- mIdsigFilePath.createNewFile();
- for (ExtraApkSpec extraApk : mExtraApks) {
- extraApk.idsig.createNewFile();
- }
- } catch (IOException e) {
- // If the file already exists, exception is not thrown.
- throw new VirtualMachineException("failed to create idsig file", e);
- }
-
- IVirtualizationService service =
- IVirtualizationService.Stub.asInterface(
- ServiceManager.waitForService(SERVICE_NAME));
-
- try {
- createVmPipes();
-
- VirtualMachineAppConfig appConfig = getConfig().toVsConfig();
- appConfig.name = mName;
-
- // Fill the idsig file by hashing the apk
- service.createOrUpdateIdsigFile(
- appConfig.apk, ParcelFileDescriptor.open(mIdsigFilePath, MODE_READ_WRITE));
-
- for (ExtraApkSpec extraApk : mExtraApks) {
- service.createOrUpdateIdsigFile(
- ParcelFileDescriptor.open(extraApk.apk, MODE_READ_ONLY),
- ParcelFileDescriptor.open(extraApk.idsig, MODE_READ_WRITE));
- }
-
- // Re-open idsig file in read-only mode
- appConfig.idsig = ParcelFileDescriptor.open(mIdsigFilePath, MODE_READ_ONLY);
- appConfig.instanceImage = ParcelFileDescriptor.open(mInstanceFilePath, MODE_READ_WRITE);
- List<ParcelFileDescriptor> extraIdsigs = new ArrayList<>();
- for (ExtraApkSpec extraApk : mExtraApks) {
- extraIdsigs.add(ParcelFileDescriptor.open(extraApk.idsig, MODE_READ_ONLY));
- }
- appConfig.extraIdsigs = extraIdsigs;
-
- android.system.virtualizationservice.VirtualMachineConfig vmConfigParcel =
- android.system.virtualizationservice.VirtualMachineConfig.appConfig(appConfig);
-
- // The VM should only be observed to die once
- AtomicBoolean onDiedCalled = new AtomicBoolean(false);
-
- IBinder.DeathRecipient deathRecipient = () -> {
- if (onDiedCalled.compareAndSet(false, true)) {
- executeCallback((cb) -> cb.onStopped(VirtualMachine.this,
- VirtualMachineCallback.STOP_REASON_VIRTUALIZATION_SERVICE_DIED));
+ try {
+ mIdsigFilePath.createNewFile();
+ for (ExtraApkSpec extraApk : mExtraApks) {
+ extraApk.idsig.createNewFile();
}
- };
+ } catch (IOException e) {
+ // If the file already exists, exception is not thrown.
+ throw new VirtualMachineException("failed to create idsig file", e);
+ }
- mVirtualMachine = service.createVm(vmConfigParcel, mConsoleWriter, mLogWriter);
- mVirtualMachine.registerCallback(
- new IVirtualMachineCallback.Stub() {
- @Override
- public void onPayloadStarted(int cid, ParcelFileDescriptor stream) {
- executeCallback(
- (cb) -> cb.onPayloadStarted(VirtualMachine.this, stream));
- }
+ IVirtualizationService service =
+ IVirtualizationService.Stub.asInterface(
+ ServiceManager.waitForService(SERVICE_NAME));
- @Override
- public void onPayloadReady(int cid) {
- executeCallback((cb) -> cb.onPayloadReady(VirtualMachine.this));
- }
+ try {
+ createVmPipes();
- @Override
- public void onPayloadFinished(int cid, int exitCode) {
- executeCallback(
- (cb) -> cb.onPayloadFinished(VirtualMachine.this, exitCode));
- }
+ VirtualMachineAppConfig appConfig = getConfig().toVsConfig();
+ appConfig.name = mName;
- @Override
- public void onError(int cid, int errorCode, String message) {
- int translatedError = getTranslatedError(errorCode);
- executeCallback(
- (cb) -> cb.onError(VirtualMachine.this, translatedError,
- message));
- }
+ // Fill the idsig file by hashing the apk
+ service.createOrUpdateIdsigFile(
+ appConfig.apk, ParcelFileDescriptor.open(mIdsigFilePath, MODE_READ_WRITE));
- @Override
- public void onDied(int cid, int reason) {
- service.asBinder().unlinkToDeath(deathRecipient, 0);
- int translatedReason = getTranslatedReason(reason);
- if (onDiedCalled.compareAndSet(false, true)) {
+ for (ExtraApkSpec extraApk : mExtraApks) {
+ service.createOrUpdateIdsigFile(
+ ParcelFileDescriptor.open(extraApk.apk, MODE_READ_ONLY),
+ ParcelFileDescriptor.open(extraApk.idsig, MODE_READ_WRITE));
+ }
+
+ // Re-open idsig file in read-only mode
+ appConfig.idsig = ParcelFileDescriptor.open(mIdsigFilePath, MODE_READ_ONLY);
+ appConfig.instanceImage = ParcelFileDescriptor.open(mInstanceFilePath,
+ MODE_READ_WRITE);
+ List<ParcelFileDescriptor> extraIdsigs = new ArrayList<>();
+ for (ExtraApkSpec extraApk : mExtraApks) {
+ extraIdsigs.add(ParcelFileDescriptor.open(extraApk.idsig, MODE_READ_ONLY));
+ }
+ appConfig.extraIdsigs = extraIdsigs;
+
+ android.system.virtualizationservice.VirtualMachineConfig vmConfigParcel =
+ android.system.virtualizationservice.VirtualMachineConfig.appConfig(
+ appConfig);
+
+ // The VM should only be observed to die once
+ AtomicBoolean onDiedCalled = new AtomicBoolean(false);
+
+ IBinder.DeathRecipient deathRecipient = () -> {
+ if (onDiedCalled.compareAndSet(false, true)) {
+ executeCallback((cb) -> cb.onStopped(VirtualMachine.this,
+ VirtualMachineCallback.STOP_REASON_VIRTUALIZATION_SERVICE_DIED));
+ }
+ };
+
+ mVirtualMachine = service.createVm(vmConfigParcel, mConsoleWriter, mLogWriter);
+ mVirtualMachine.registerCallback(
+ new IVirtualMachineCallback.Stub() {
+ @Override
+ public void onPayloadStarted(int cid, ParcelFileDescriptor stream) {
executeCallback(
- (cb) -> cb.onStopped(VirtualMachine.this,
- translatedReason));
+ (cb) -> cb.onPayloadStarted(VirtualMachine.this, stream));
+ }
+
+ @Override
+ public void onPayloadReady(int cid) {
+ executeCallback((cb) -> cb.onPayloadReady(VirtualMachine.this));
+ }
+
+ @Override
+ public void onPayloadFinished(int cid, int exitCode) {
+ executeCallback(
+ (cb) -> cb.onPayloadFinished(VirtualMachine.this,
+ exitCode));
+ }
+
+ @Override
+ public void onError(int cid, int errorCode, String message) {
+ int translatedError = getTranslatedError(errorCode);
+ executeCallback(
+ (cb) -> cb.onError(VirtualMachine.this, translatedError,
+ message));
+ }
+
+ @Override
+ public void onDied(int cid, int reason) {
+ service.asBinder().unlinkToDeath(deathRecipient, 0);
+ int translatedReason = getTranslatedReason(reason);
+ if (onDiedCalled.compareAndSet(false, true)) {
+ executeCallback(
+ (cb) -> cb.onStopped(VirtualMachine.this,
+ translatedReason));
+ }
+ }
+
+ @Override
+ public void onRamdump(int cid, ParcelFileDescriptor ramdump) {
+ executeCallback(
+ (cb) -> cb.onRamdump(VirtualMachine.this, ramdump));
}
}
-
- @Override
- public void onRamdump(int cid, ParcelFileDescriptor ramdump) {
- executeCallback(
- (cb) -> cb.onRamdump(VirtualMachine.this, ramdump));
- }
- }
- );
- service.asBinder().linkToDeath(deathRecipient, 0);
- mVirtualMachine.start();
- } catch (IOException | IllegalStateException | ServiceSpecificException e) {
- throw new VirtualMachineException(e);
- } catch (RemoteException e) {
- throw e.rethrowAsRuntimeException();
+ );
+ service.asBinder().linkToDeath(deathRecipient, 0);
+ mVirtualMachine.start();
+ } catch (IOException | IllegalStateException | ServiceSpecificException e) {
+ throw new VirtualMachineException(e);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
}
}
+ @GuardedBy("mLock")
private void createVmPipes() throws VirtualMachineException {
try {
if (mConsoleReader == null || mConsoleWriter == null) {
@@ -598,6 +722,207 @@
}
}
+ /**
+ * Returns the stream object representing the console output from the virtual machine.
+ *
+ * @throws VirtualMachineException if the stream could not be created.
+ * @hide
+ */
+ @NonNull
+ public InputStream getConsoleOutputStream() throws VirtualMachineException {
+ synchronized (mLock) {
+ createVmPipes();
+ return new FileInputStream(mConsoleReader.getFileDescriptor());
+ }
+ }
+
+ /**
+ * Returns the stream object representing the log output from the virtual machine.
+ *
+ * @throws VirtualMachineException if the stream could not be created.
+ * @hide
+ */
+ @NonNull
+ public InputStream getLogOutputStream() throws VirtualMachineException {
+ synchronized (mLock) {
+ createVmPipes();
+ return new FileInputStream(mLogReader.getFileDescriptor());
+ }
+ }
+
+ /**
+ * Stops this virtual machine. Stopping a virtual machine is like pulling the plug on a real
+ * computer; the machine halts immediately. Software running on the virtual machine is not
+ * notified of the event. A stopped virtual machine can be re-started by calling {@link
+ * #run()}.
+ *
+ * @throws VirtualMachineException if the virtual machine could not be stopped.
+ * @hide
+ */
+ public void stop() throws VirtualMachineException {
+ synchronized (mLock) {
+ if (mVirtualMachine == null) {
+ throw new VirtualMachineException("VM is not running");
+ }
+ try {
+ mVirtualMachine.stop();
+ mVirtualMachine = null;
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ } catch (ServiceSpecificException e) {
+ throw new VirtualMachineException(e);
+ }
+ }
+ }
+
+ /**
+ * Stops this virtual machine. See {@link #stop()}.
+ *
+ * @throws VirtualMachineException if the virtual machine could not be stopped.
+ * @hide
+ */
+ @Override
+ public void close() throws VirtualMachineException {
+ stop();
+ }
+
+ private static void deleteRecursively(File dir) throws IOException {
+ // Note: This doesn't follow symlinks, which is important. Instead they are just deleted
+ // (and Files.delete deletes the link not the target).
+ Files.walkFileTree(dir.toPath(), new SimpleFileVisitor<>() {
+ @Override
+ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
+ throws IOException {
+ Files.delete(file);
+ return FileVisitResult.CONTINUE;
+ }
+
+ @Override
+ public FileVisitResult postVisitDirectory(Path dir, IOException e) throws IOException {
+ // Directory is deleted after we've visited (deleted) all its contents, so it
+ // should be empty by now.
+ Files.delete(dir);
+ return FileVisitResult.CONTINUE;
+ }
+ });
+ }
+
+ /**
+ * Returns the CID of this virtual machine, if it is running.
+ *
+ * @throws VirtualMachineException if the virtual machine is not running.
+ * @hide
+ */
+ public int getCid() throws VirtualMachineException {
+ synchronized (mLock) {
+ try {
+ return getRunningVm().getCid();
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+ }
+
+ /**
+ * Changes the config of this virtual machine to a new one. This can be used to adjust things
+ * like the number of CPU and size of the RAM, depending on the situation (e.g. the size of the
+ * application to run on the virtual machine, etc.)
+ *
+ * The new config must be {@link VirtualMachineConfig#isCompatibleWith compatible with} the
+ * existing config.
+ *
+ * @return the old config
+ * @throws VirtualMachineException if the virtual machine is not stopped, or the new config is
+ * incompatible.
+ * @hide
+ */
+ @NonNull
+ public VirtualMachineConfig setConfig(@NonNull VirtualMachineConfig newConfig)
+ throws VirtualMachineException {
+ synchronized (mLock) {
+ VirtualMachineConfig oldConfig = mConfig;
+ if (!oldConfig.isCompatibleWith(newConfig)) {
+ throw new VirtualMachineException("incompatible config");
+ }
+ checkStopped();
+
+ try {
+ FileOutputStream output = new FileOutputStream(mConfigFilePath);
+ newConfig.serialize(output);
+ output.close();
+ } catch (IOException e) {
+ throw new VirtualMachineException("Failed to persist config", e);
+ }
+ mConfig = newConfig;
+ return oldConfig;
+ }
+ }
+
+ @Nullable
+ private static native IBinder nativeConnectToVsockServer(IBinder vmBinder, int port);
+
+ /**
+ * Connect to a VM's binder service via vsock and return the root IBinder object. Guest VMs are
+ * expected to set up vsock servers in their payload. After the host app receives the {@link
+ * VirtualMachineCallback#onPayloadReady(VirtualMachine)}, it can use this method to
+ * establish a connection to the guest VM.
+ *
+ * @throws VirtualMachineException if the virtual machine is not running or the connection
+ * failed.
+ * @hide
+ */
+ @NonNull
+ public IBinder connectToVsockServer(int port) throws VirtualMachineException {
+ synchronized (mLock) {
+ IBinder iBinder = nativeConnectToVsockServer(getRunningVm().asBinder(), port);
+ if (iBinder == null) {
+ throw new VirtualMachineException("Failed to connect to vsock server");
+ }
+ return iBinder;
+ }
+ }
+
+ /**
+ * Opens a vsock connection to the VM on the given port.
+ *
+ * @throws VirtualMachineException if connecting fails.
+ * @hide
+ */
+ @NonNull
+ public ParcelFileDescriptor connectVsock(int port) throws VirtualMachineException {
+ synchronized (mLock) {
+ try {
+ return getRunningVm().connectVsock(port);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ } catch (ServiceSpecificException e) {
+ throw new VirtualMachineException(e);
+ }
+ }
+ }
+
+ /**
+ * Captures the current state of the VM in a {@link ParcelVirtualMachine} instance.
+ * The VM needs to be stopped to avoid inconsistency in its state representation.
+ *
+ * @return a {@link ParcelVirtualMachine} instance that represents the VM's state.
+ * @throws VirtualMachineException if the virtual machine is not stopped, or the state could not
+ * be captured.
+ */
+ @NonNull
+ public ParcelVirtualMachine toParcelVirtualMachine() throws VirtualMachineException {
+ synchronized (mLock) {
+ checkStopped();
+ }
+ try {
+ return new ParcelVirtualMachine(
+ ParcelFileDescriptor.open(mConfigFilePath, MODE_READ_ONLY),
+ ParcelFileDescriptor.open(mInstanceFilePath, MODE_READ_ONLY));
+ } catch (IOException e) {
+ throw new VirtualMachineException(e);
+ }
+ }
+
@VirtualMachineCallback.ErrorCode
private int getTranslatedError(int reason) {
switch (reason) {
@@ -652,206 +977,6 @@
}
}
- /**
- * Returns the stream object representing the console output from the virtual machine.
- *
- * @throws VirtualMachineException if the stream could not be created.
- * @hide
- */
- @NonNull
- public InputStream getConsoleOutputStream() throws VirtualMachineException {
- createVmPipes();
- return new FileInputStream(mConsoleReader.getFileDescriptor());
- }
-
- /**
- * Returns the stream object representing the log output from the virtual machine.
- *
- * @throws VirtualMachineException if the stream could not be created.
- * @hide
- */
- @NonNull
- public InputStream getLogOutputStream() throws VirtualMachineException {
- createVmPipes();
- return new FileInputStream(mLogReader.getFileDescriptor());
- }
-
- /**
- * Stops this virtual machine. Stopping a virtual machine is like pulling the plug on a real
- * computer; the machine halts immediately. Software running on the virtual machine is not
- * notified of the event. A stopped virtual machine can be re-started by calling {@link
- * #run()}.
- *
- * @throws VirtualMachineException if the virtual machine could not be stopped.
- * @hide
- */
- public void stop() throws VirtualMachineException {
- if (mVirtualMachine == null) return;
- try {
- mVirtualMachine.stop();
- mVirtualMachine = null;
- } catch (RemoteException e) {
- throw e.rethrowAsRuntimeException();
- } catch (ServiceSpecificException e) {
- throw new VirtualMachineException(e);
- }
- }
-
- /**
- * Stops this virtual machine. See {@link #stop()}.
- *
- * @throws VirtualMachineException if the virtual machine could not be stopped.
- * @hide
- */
- @Override
- public void close() throws VirtualMachineException {
- stop();
- }
-
- static void delete(Context context, String name) throws VirtualMachineException {
- VirtualMachine vm;
- synchronized (sInstancesLock) {
- Map<String, WeakReference<VirtualMachine>> instancesMap = sInstances.get(context);
- if (instancesMap != null && instancesMap.containsKey(name)) {
- vm = instancesMap.get(name).get();
- } else {
- vm = null;
- }
- }
-
- if (vm != null && vm.getStatus() != STATUS_STOPPED) {
- throw new VirtualMachineException("Virtual machine is not stopped");
- }
-
- try {
- deleteRecursively(getVmDir(context, name));
- } catch (IOException e) {
- throw new VirtualMachineException(e);
- }
-
- synchronized (sInstancesLock) {
- Map<String, WeakReference<VirtualMachine>> instancesMap = sInstances.get(context);
- if (instancesMap != null) instancesMap.remove(name);
- }
- }
-
- private static void deleteRecursively(File dir) throws IOException {
- // Note: This doesn't follow symlinks, which is important. Instead they are just deleted
- // (and Files.delete deletes the link not the target).
- Files.walkFileTree(dir.toPath(), new SimpleFileVisitor<>() {
- @Override
- public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
- throws IOException {
- Files.delete(file);
- return FileVisitResult.CONTINUE;
- }
-
- @Override
- public FileVisitResult postVisitDirectory(Path dir, IOException e) throws IOException {
- // Directory is deleted after we've visited (deleted) all its contents, so it
- // should be empty by now.
- Files.delete(dir);
- return FileVisitResult.CONTINUE;
- }
- });
- }
-
- /**
- * Returns the CID of this virtual machine, if it is running.
- *
- * @throws VirtualMachineException if the virtual machine is not running.
- * @hide
- */
- public int getCid() throws VirtualMachineException {
- if (getStatus() != STATUS_RUNNING) {
- throw new VirtualMachineException("VM is not running");
- }
- try {
- return mVirtualMachine.getCid();
- } catch (RemoteException e) {
- throw e.rethrowAsRuntimeException();
- }
- }
-
- /**
- * Changes the config of this virtual machine to a new one. This can be used to adjust things
- * like the number of CPU and size of the RAM, depending on the situation (e.g. the size of the
- * application to run on the virtual machine, etc.)
- *
- * The new config must be {@link VirtualMachineConfig#isCompatibleWith compatible with} the
- * existing config.
- *
- * @return the old config
- * @throws VirtualMachineException if the virtual machine is not stopped, or the new config is
- * incompatible.
- * @hide
- */
- @NonNull
- public VirtualMachineConfig setConfig(@NonNull VirtualMachineConfig newConfig)
- throws VirtualMachineException {
- final VirtualMachineConfig oldConfig = getConfig();
- if (!oldConfig.isCompatibleWith(newConfig)) {
- throw new VirtualMachineException("incompatible config");
- }
- if (getStatus() != STATUS_STOPPED) {
- throw new VirtualMachineException(
- "can't change config while virtual machine is not stopped");
- }
-
- try {
- FileOutputStream output = new FileOutputStream(mConfigFilePath);
- newConfig.serialize(output);
- output.close();
- } catch (IOException e) {
- throw new VirtualMachineException("Failed to persist config", e);
- }
- mConfig = newConfig;
-
- return oldConfig;
- }
-
- @Nullable
- private static native IBinder nativeConnectToVsockServer(IBinder vmBinder, int port);
-
- /**
- * Connect to a VM's binder service via vsock and return the root IBinder object. Guest VMs are
- * expected to set up vsock servers in their payload. After the host app receives the {@link
- * VirtualMachineCallback#onPayloadReady(VirtualMachine)}, it can use this method to
- * establish a connection to the guest VM.
- *
- * @throws VirtualMachineException if the virtual machine is not running or the connection
- * failed.
- * @hide
- */
- @NonNull
- public IBinder connectToVsockServer(int port) throws VirtualMachineException {
- if (getStatus() != STATUS_RUNNING) {
- throw new VirtualMachineException("VM is not running");
- }
- IBinder iBinder = nativeConnectToVsockServer(mVirtualMachine.asBinder(), port);
- if (iBinder == null) {
- throw new VirtualMachineException("Failed to connect to vsock server");
- }
- return iBinder;
- }
-
- /**
- * Opens a vsock connection to the VM on the given port.
- *
- * @throws VirtualMachineException if connecting fails.
- * @hide
- */
- @NonNull
- public ParcelFileDescriptor connectVsock(int port) throws VirtualMachineException {
- try {
- return mVirtualMachine.connectVsock(port);
- } catch (RemoteException e) {
- throw e.rethrowAsRuntimeException();
- } catch (ServiceSpecificException e) {
- throw new VirtualMachineException(e);
- }
- }
-
@Override
public String toString() {
VirtualMachineConfig config = getConfig();
@@ -942,15 +1067,9 @@
new File(vmDir, EXTRA_IDSIG_FILE_PREFIX + i)));
}
- return extraApks;
+ return Collections.unmodifiableList(extraApks);
} catch (IOException e) {
throw new VirtualMachineException("Couldn't parse extra apks from the vm config", e);
}
}
-
- @NonNull
- private static File getVmDir(Context context, String name) {
- File vmRoot = new File(context.getDataDir(), VM_DIR);
- return new File(vmRoot, name);
- }
}
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachineManager.java b/javalib/src/android/system/virtualmachine/VirtualMachineManager.java
index f2b7802..34b9fd9 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachineManager.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachineManager.java
@@ -26,6 +26,8 @@
import android.content.Context;
import android.sysprop.HypervisorProperties;
+import com.android.internal.annotations.GuardedBy;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.ref.WeakReference;
@@ -51,6 +53,7 @@
mContext = context;
}
+ @GuardedBy("sInstances")
private static final Map<Context, WeakReference<VirtualMachineManager>> sInstances =
new WeakHashMap<>();
@@ -96,9 +99,6 @@
}
}
- /** A lock used to synchronize the creation of virtual machines */
- private static final Object sCreateLock = new Object();
-
/**
* Returns a set of flags indicating what this implementation of virtualization is capable of.
*
@@ -136,7 +136,7 @@
public VirtualMachine create(
@NonNull String name, @NonNull VirtualMachineConfig config)
throws VirtualMachineException {
- synchronized (sCreateLock) {
+ synchronized (VirtualMachine.sCreateLock) {
return VirtualMachine.create(mContext, name, config);
}
}
@@ -151,7 +151,9 @@
*/
@Nullable
public VirtualMachine get(@NonNull String name) throws VirtualMachineException {
- return VirtualMachine.load(mContext, name);
+ synchronized (VirtualMachine.sCreateLock) {
+ return VirtualMachine.load(mContext, name);
+ }
}
/**
@@ -166,7 +168,7 @@
@NonNull String name, @NonNull VirtualMachineConfig config)
throws VirtualMachineException {
VirtualMachine vm;
- synchronized (sCreateLock) {
+ synchronized (VirtualMachine.sCreateLock) {
vm = get(name);
if (vm == null) {
vm = create(name, config);
@@ -188,6 +190,8 @@
*/
public void delete(@NonNull String name) throws VirtualMachineException {
requireNonNull(name);
- VirtualMachine.delete(mContext, name);
+ synchronized (VirtualMachine.sCreateLock) {
+ VirtualMachine.delete(mContext, name);
+ }
}
}
diff --git a/microdroid_manager/src/main.rs b/microdroid_manager/src/main.rs
index b8e85e7..73c36aa 100644
--- a/microdroid_manager/src/main.rs
+++ b/microdroid_manager/src/main.rs
@@ -18,7 +18,6 @@
mod instance;
mod ioutil;
mod payload;
-mod procutil;
mod swap;
mod vm_payload_service;
@@ -26,12 +25,8 @@
use crate::instance::{ApexData, ApkData, InstanceDisk, MicrodroidData, RootHash};
use crate::vm_payload_service::register_vm_payload_service;
use android_system_virtualizationcommon::aidl::android::system::virtualizationcommon::ErrorCode::ErrorCode;
-use android_system_virtualmachineservice::aidl::android::system::virtualmachineservice::{
- IVirtualMachineService::{
+use android_system_virtualmachineservice::aidl::android::system::virtualmachineservice::IVirtualMachineService::{
IVirtualMachineService, VM_BINDER_SERVICE_PORT, VM_STREAM_SERVICE_PORT,
- },
- VirtualMachineCpuStatus::VirtualMachineCpuStatus,
- VirtualMachineMemStatus::VirtualMachineMemStatus,
};
use android_system_virtualization_payload::aidl::android::system::virtualization::payload::IVmPayloadService::VM_APK_CONTENTS_PATH;
use anyhow::{anyhow, bail, ensure, Context, Error, Result};
@@ -46,7 +41,6 @@
use microdroid_payload_config::{OsConfig, Task, TaskType, VmPayloadConfig};
use openssl::sha::Sha512;
use payload::{get_apex_data_from_payload, load_metadata, to_metadata};
-use procutil::{get_cpu_time, get_mem_info};
use rand::Fill;
use rpcbinder::get_vsock_rpc_interface;
use rustutils::system_properties;
@@ -59,12 +53,10 @@
use std::path::Path;
use std::process::{Child, Command, Stdio};
use std::str;
-use std::thread;
use std::time::{Duration, SystemTime};
use vsock::VsockStream;
const WAIT_TIMEOUT: Duration = Duration::from_secs(10);
-const SENDING_VM_STATUS_CYCLE_PERIOD: Duration = Duration::from_secs(60);
const MAIN_APK_PATH: &str = "/dev/block/by-name/microdroid-apk";
const MAIN_APK_IDSIG_PATH: &str = "/dev/block/by-name/microdroid-apk-idsig";
const MAIN_APK_DEVICE_NAME: &str = "microdroid-apk";
@@ -97,42 +89,6 @@
InvalidConfig(String),
}
-fn send_vm_status(service: &Strong<dyn IVirtualMachineService>) -> Result<()> {
- // Collect VM CPU time information and creating VmCpuStatus atom for metrics.
- let cpu_time = get_cpu_time()?;
- let vm_cpu_status = VirtualMachineCpuStatus {
- cpu_time_user: cpu_time.user,
- cpu_time_nice: cpu_time.nice,
- cpu_time_sys: cpu_time.sys,
- cpu_time_idle: cpu_time.idle,
- };
- service.notifyCpuStatus(&vm_cpu_status).expect("Can't send information about VM CPU status");
-
- // Collect VM memory information and creating VmMemStatus atom for metrics.
- let mem_info = get_mem_info()?;
- let vm_mem_status = VirtualMachineMemStatus {
- mem_total: mem_info.total,
- mem_free: mem_info.free,
- mem_available: mem_info.available,
- mem_buffer: mem_info.buffer,
- mem_cached: mem_info.cached,
- };
- service.notifyMemStatus(&vm_mem_status).expect("Can't send information about VM memory status");
-
- Ok(())
-}
-
-fn send_vm_status_periodically() -> Result<()> {
- let service = get_vms_rpc_binder()
- .context("cannot connect to VirtualMachineService")
- .map_err(|e| MicrodroidError::FailedToConnectToVirtualizationService(e.to_string()))?;
-
- loop {
- send_vm_status(&service)?;
- thread::sleep(SENDING_VM_STATUS_CYCLE_PERIOD);
- }
-}
-
fn translate_error(err: &Error) -> (ErrorCode, String) {
if let Some(e) = err.downcast_ref::<MicrodroidError>() {
match e {
@@ -225,12 +181,6 @@
.context("cannot connect to VirtualMachineService")
.map_err(|e| MicrodroidError::FailedToConnectToVirtualizationService(e.to_string()))?;
- thread::spawn(move || {
- if let Err(e) = send_vm_status_periodically() {
- error!("failed to get virtual machine status: {:?}", e);
- }
- });
-
match try_run_payload(&service) {
Ok(code) => {
info!("notifying payload finished");
@@ -450,8 +400,6 @@
ProcessState::start_thread_pool();
system_properties::write("dev.bootcomplete", "1").context("set dev.bootcomplete")?;
- send_vm_status(service)?;
-
exec_task(task, service).context("Failed to run payload")
}
@@ -783,7 +731,6 @@
service.notifyPayloadStarted()?;
let exit_status = command.spawn()?.wait()?;
- send_vm_status(service)?;
exit_status.code().ok_or_else(|| anyhow!("Failed to get exit_code from the paylaod."))
}
diff --git a/microdroid_manager/src/procutil.rs b/microdroid_manager/src/procutil.rs
deleted file mode 100644
index f9479c4..0000000
--- a/microdroid_manager/src/procutil.rs
+++ /dev/null
@@ -1,127 +0,0 @@
-// 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.
-
-use anyhow::{bail, Result};
-use libc::{sysconf, _SC_CLK_TCK};
-use std::fs::File;
-use std::io::{BufRead, BufReader};
-
-const MILLIS_PER_SEC: i64 = 1000;
-
-pub struct CpuTime {
- pub user: i64,
- pub nice: i64,
- pub sys: i64,
- pub idle: i64,
-}
-
-pub struct MemInfo {
- pub total: i64,
- pub free: i64,
- pub available: i64,
- pub buffer: i64,
- pub cached: i64,
-}
-
-// Get CPU time information from /proc/stat
-//
-// /proc/stat example(omitted):
-// cpu 24790952 21104390 10771070 10480973587 1700955 0 410931 0 316532 0
-// cpu0 169636 141307 61153 81785791 9605 0 183524 0 1345 0
-// cpu1 182431 198327 68273 81431817 10445 0 32392 0 2616 0
-// cpu2 183209 174917 68591 81933935 12239 0 10042 0 2415 0
-// cpu3 183413 177758 69908 81927474 13354 0 5853 0 2491 0
-// intr 7913477443 39 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
-// ctxt 10326710014
-// btime 1664123605
-// processes 9225712
-// procs_running 1
-// procs_blocked 0
-// softirq 2683914305 14595298 304837101 1581 327291100 16397051 0 208857783 1024640365 787932 786506094
-//
-// expected output:
-// user: 24790952
-// nice: 21104390
-// sys: 10771070
-// idle: 10480973587
-pub fn get_cpu_time() -> Result<CpuTime> {
- let mut proc_stat = BufReader::new(File::open("/proc/stat")?);
- let mut line = String::new();
- proc_stat.read_line(&mut line)?;
- let data_list: Vec<_> = line.split_whitespace().filter_map(|s| s.parse::<i64>().ok()).collect();
- if data_list.len() < 4 {
- bail!("Failed to extract numeric values in /proc/stat :\n{}", line);
- }
-
- let ticks_per_sec = unsafe { sysconf(_SC_CLK_TCK) } as i64;
- let cpu_time = CpuTime {
- user: data_list[0] * MILLIS_PER_SEC / ticks_per_sec,
- nice: data_list[1] * MILLIS_PER_SEC / ticks_per_sec,
- sys: data_list[2] * MILLIS_PER_SEC / ticks_per_sec,
- idle: data_list[3] * MILLIS_PER_SEC / ticks_per_sec,
- };
- Ok(cpu_time)
-}
-
-// Get memory information from /proc/meminfo
-//
-// /proc/meminfo example(omitted):
-// MemTotal: 263742736 kB
-// MemFree: 37144204 kB
-// MemAvailable: 249168700 kB
-// Buffers: 10231296 kB
-// Cached: 189502836 kB
-// SwapCached: 113848 kB
-// Active: 132266424 kB
-// Inactive: 73587504 kB
-// Active(anon): 1455240 kB
-// Inactive(anon): 6993584 kB
-// Active(file): 130811184 kB
-// Inactive(file): 66593920 kB
-// Unevictable: 56436 kB
-// Mlocked: 56436 kB
-// SwapTotal: 255123452 kB
-// SwapFree: 254499068 kB
-// Dirty: 596 kB
-// Writeback: 0 kB
-// AnonPages: 5295864 kB
-// Mapped: 3512608 kB
-//
-// expected output:
-// total: 263742736
-// free: 37144204
-// available: 249168700
-// buffer: 10231296
-// cached: 189502836
-pub fn get_mem_info() -> Result<MemInfo> {
- let mut proc_stat = BufReader::new(File::open("/proc/meminfo")?);
- let mut lines = String::new();
- for _ in 0..5 {
- proc_stat.read_line(&mut lines)?;
- }
- let data_list: Vec<_> =
- lines.split_whitespace().filter_map(|s| s.parse::<i64>().ok()).collect();
- if data_list.len() != 5 {
- bail!("Failed to extract numeric values in /proc/meminfo :\n{}", lines);
- }
-
- let mem_info = MemInfo {
- total: data_list[0],
- free: data_list[1],
- available: data_list[2],
- buffer: data_list[3],
- cached: data_list[4],
- };
- Ok(mem_info)
-}
diff --git a/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java b/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
index 33788ed..c9df624 100644
--- a/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
+++ b/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
@@ -485,7 +485,7 @@
}
@Test
- public void testTelemetryPushedAtomsOfEventMetrics() throws Exception {
+ public void testTelemetryPushedAtoms() throws Exception {
// Reset statsd config and report before the test
ConfigUtils.removeConfig(getDevice());
ReportUtils.clearReports(getDevice());
@@ -566,49 +566,6 @@
}
@Test
- public void testTelemetryPushedAtomsOfValueMetrics() throws Exception {
- // Reset statsd config and report before the test
- ConfigUtils.removeConfig(getDevice());
- ReportUtils.clearReports(getDevice());
-
- // Setup statsd config
- int[] atomIds = {
- AtomsProto.Atom.VM_CPU_STATUS_REPORTED_FIELD_NUMBER,
- AtomsProto.Atom.VM_MEM_STATUS_REPORTED_FIELD_NUMBER,
- };
- ConfigUtils.uploadConfigForPushedAtoms(getDevice(), PACKAGE_NAME, atomIds);
-
- // Create VM with microdroid
- final String configPath = "assets/vm_config_apex.json"; // path inside the APK
- final String cid =
- startMicrodroid(
- getDevice(),
- getBuild(),
- APK_NAME,
- PACKAGE_NAME,
- configPath,
- /* debug */ true,
- minMemorySize(),
- Optional.of(NUM_VCPUS));
-
- // Boot VM with microdroid
- adbConnectToMicrodroid(getDevice(), cid);
- waitForBootComplete();
-
- // Check VmCpuStatusReported and VmMemStatusReported atoms and clear the statsd report
- List<StatsLog.EventMetricData> data;
- data = ReportUtils.getEventMetricDataList(getDevice());
- assertThat(data.size() >= 2).isTrue();
- assertThat(data.get(0).getAtom().getPushedCase().getNumber())
- .isEqualTo(AtomsProto.Atom.VM_CPU_STATUS_REPORTED_FIELD_NUMBER);
- assertThat(data.get(1).getAtom().getPushedCase().getNumber())
- .isEqualTo(AtomsProto.Atom.VM_MEM_STATUS_REPORTED_FIELD_NUMBER);
-
- // Shutdown VM with microdroid
- shutdownMicrodroid(getDevice(), cid);
- }
-
- @Test
@CddTest(requirements = {"9.17/C-1-1", "9.17/C-1-2", "9.17/C/1-3"})
public void testMicrodroidBoots() throws Exception {
final String configPath = "assets/vm_config.json"; // path inside the APK
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 42c0e64..dd01867 100644
--- a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
+++ b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
@@ -25,9 +25,11 @@
import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
+import android.content.Context;
import android.os.Build;
import android.os.ParcelFileDescriptor;
import android.os.SystemProperties;
+import android.system.virtualmachine.ParcelVirtualMachine;
import android.system.virtualmachine.VirtualMachine;
import android.system.virtualmachine.VirtualMachineCallback;
import android.system.virtualmachine.VirtualMachineConfig;
@@ -35,6 +37,8 @@
import android.system.virtualmachine.VirtualMachineManager;
import android.util.Log;
+import androidx.test.core.app.ApplicationProvider;
+
import com.android.compatibility.common.util.CddTest;
import com.android.microdroid.test.device.MicrodroidDeviceTestBase;
import com.android.microdroid.testservice.ITestService;
@@ -54,6 +58,8 @@
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
import java.util.List;
import java.util.OptionalLong;
import java.util.UUID;
@@ -289,8 +295,7 @@
// Try to run the VM again with the previous instance.img
// We need to make sure that no changes on config don't invalidate the identity, to compare
// the result with the below "different debug level" test.
- File vmRoot = new File(getContext().getDataDir(), "vm");
- File vmInstance = new File(new File(vmRoot, "test_vm"), "instance.img");
+ File vmInstance = getVmFile("test_vm", "instance.img");
File vmInstanceBackup = File.createTempFile("instance", ".img");
Files.copy(vmInstance.toPath(), vmInstanceBackup.toPath(), REPLACE_EXISTING);
mInner.forceCreateNewVirtualMachine("test_vm", normalConfig);
@@ -476,10 +481,7 @@
mInner.forceCreateNewVirtualMachine(vmName, config);
assertThat(tryBootVm(TAG, vmName).payloadStarted).isTrue();
-
- File vmRoot = new File(getContext().getDataDir(), "vm");
- File vmDir = new File(vmRoot, vmName);
- File instanceImgPath = new File(vmDir, "instance.img");
+ File instanceImgPath = getVmFile(vmName, "instance.img");
return new RandomAccessFile(instanceImgPath, "rw");
}
@@ -575,6 +577,41 @@
assertThat(vm).isNotEqualTo(newVm);
}
+ @Test
+ public void vmConvertsToValidParcelVm() 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);
+
+ // Action
+ ParcelVirtualMachine parcelVm = vm.toParcelVirtualMachine();
+
+ // Asserts
+ assertFileContentsAreEqual(parcelVm.getConfigFd(), vmName, "config.xml");
+ assertFileContentsAreEqual(parcelVm.getInstanceImgFd(), vmName, "instance.img");
+ }
+
+ 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 File getVmFile(String vmName, String fileName) {
+ Context context = ApplicationProvider.getApplicationContext();
+ Path filePath = Paths.get(context.getDataDir().getPath(), "vm", vmName, fileName);
+ return filePath.toFile();
+ }
+
private int minMemoryRequired() {
if (Build.SUPPORTED_ABIS.length > 0) {
String primaryAbi = Build.SUPPORTED_ABIS[0];
diff --git a/virtualizationservice/Android.bp b/virtualizationservice/Android.bp
index 26d41c9..d6f4607 100644
--- a/virtualizationservice/Android.bp
+++ b/virtualizationservice/Android.bp
@@ -71,5 +71,8 @@
rust_test {
name: "virtualizationservice_device_test",
defaults: ["virtualizationservice_defaults"],
+ rustlibs: [
+ "libtempfile",
+ ],
test_suites: ["general-tests"],
}
diff --git a/virtualizationservice/aidl/android/system/virtualmachineservice/IVirtualMachineService.aidl b/virtualizationservice/aidl/android/system/virtualmachineservice/IVirtualMachineService.aidl
index 4fa5fa0..e8c1724 100644
--- a/virtualizationservice/aidl/android/system/virtualmachineservice/IVirtualMachineService.aidl
+++ b/virtualizationservice/aidl/android/system/virtualmachineservice/IVirtualMachineService.aidl
@@ -16,8 +16,6 @@
package android.system.virtualmachineservice;
import android.system.virtualizationcommon.ErrorCode;
-import android.system.virtualmachineservice.VirtualMachineCpuStatus;
-import android.system.virtualmachineservice.VirtualMachineMemStatus;
/** {@hide} */
interface IVirtualMachineService {
@@ -58,14 +56,4 @@
* Notifies that an error has occurred inside the VM..
*/
void notifyError(ErrorCode errorCode, in String message);
-
- /**
- * Notifies the current CPU status of the VM.
- */
- void notifyCpuStatus(in VirtualMachineCpuStatus cpuStatus);
-
- /**
- * Notifies the current memory status of the VM.
- */
- void notifyMemStatus(in VirtualMachineMemStatus memStatus);
}
diff --git a/virtualizationservice/aidl/android/system/virtualmachineservice/VirtualMachineCpuStatus.aidl b/virtualizationservice/aidl/android/system/virtualmachineservice/VirtualMachineCpuStatus.aidl
deleted file mode 100644
index 307c3f9..0000000
--- a/virtualizationservice/aidl/android/system/virtualmachineservice/VirtualMachineCpuStatus.aidl
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * 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.
- */
-package android.system.virtualmachineservice;
-
-parcelable VirtualMachineCpuStatus {
- long cpu_time_user;
- long cpu_time_nice;
- long cpu_time_sys;
- long cpu_time_idle;
-}
diff --git a/virtualizationservice/aidl/android/system/virtualmachineservice/VirtualMachineMemStatus.aidl b/virtualizationservice/aidl/android/system/virtualmachineservice/VirtualMachineMemStatus.aidl
deleted file mode 100644
index 3de57c6..0000000
--- a/virtualizationservice/aidl/android/system/virtualmachineservice/VirtualMachineMemStatus.aidl
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * 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.
- */
-package android.system.virtualmachineservice;
-
-parcelable VirtualMachineMemStatus {
- long mem_total;
- long mem_free;
- long mem_available;
- long mem_buffer;
- long mem_cached;
-}
diff --git a/virtualizationservice/src/aidl.rs b/virtualizationservice/src/aidl.rs
index b4ce9d2..bc697e3 100644
--- a/virtualizationservice/src/aidl.rs
+++ b/virtualizationservice/src/aidl.rs
@@ -14,10 +14,7 @@
//! Implementation of the AIDL interface of the VirtualizationService.
-use crate::atom::{
- write_vm_booted_stats, write_vm_cpu_status_stats, write_vm_creation_stats,
- write_vm_mem_status_stats,
-};
+use crate::atom::{write_vm_booted_stats, write_vm_creation_stats};
use crate::composite::make_composite_image;
use crate::crosvm::{CrosvmConfig, DiskFile, PayloadState, VmInstance, VmState};
use crate::payload::{add_microdroid_payload_images, add_microdroid_system_images};
@@ -39,13 +36,9 @@
VirtualMachineRawConfig::VirtualMachineRawConfig,
VirtualMachineState::VirtualMachineState,
};
-use android_system_virtualmachineservice::aidl::android::system::virtualmachineservice::{
- IVirtualMachineService::{
+use android_system_virtualmachineservice::aidl::android::system::virtualmachineservice::IVirtualMachineService::{
BnVirtualMachineService, IVirtualMachineService, VM_BINDER_SERVICE_PORT,
VM_STREAM_SERVICE_PORT, VM_TOMBSTONES_SERVICE_PORT,
- },
- VirtualMachineCpuStatus::VirtualMachineCpuStatus,
- VirtualMachineMemStatus::VirtualMachineMemStatus,
};
use anyhow::{anyhow, bail, Context, Result};
use apkverify::{HashAlgorithm, V4Signature};
@@ -1147,36 +1140,6 @@
))
}
}
-
- fn notifyCpuStatus(&self, status: &VirtualMachineCpuStatus) -> binder::Result<()> {
- let cid = self.cid;
- if let Some(vm) = self.state.lock().unwrap().get_vm(cid) {
- info!("VM with CID {} reported its CPU status", cid);
- write_vm_cpu_status_stats(vm.requester_uid as i32, &vm.name, status);
- Ok(())
- } else {
- error!("notifyCurrentStatus is called from an unknown CID {}", cid);
- Err(Status::new_service_specific_error_str(
- -1,
- Some(format!("cannot find a VM with CID {}", cid)),
- ))
- }
- }
-
- fn notifyMemStatus(&self, status: &VirtualMachineMemStatus) -> binder::Result<()> {
- let cid = self.cid;
- if let Some(vm) = self.state.lock().unwrap().get_vm(cid) {
- info!("VM with CID {} reported its memory status", cid);
- write_vm_mem_status_stats(vm.requester_uid as i32, &vm.name, status);
- Ok(())
- } else {
- error!("notifyCurrentStatus is called from an unknown CID {}", cid);
- Err(Status::new_service_specific_error_str(
- -1,
- Some(format!("cannot find a VM with CID {}", cid)),
- ))
- }
- }
}
impl VirtualMachineService {
diff --git a/virtualizationservice/src/atom.rs b/virtualizationservice/src/atom.rs
index 8c46ac5..eabb4cc 100644
--- a/virtualizationservice/src/atom.rs
+++ b/virtualizationservice/src/atom.rs
@@ -22,17 +22,11 @@
VirtualMachineConfig::VirtualMachineConfig,
};
use android_system_virtualizationservice::binder::{Status, Strong};
-use android_system_virtualmachineservice::aidl::android::system::virtualmachineservice::{
- VirtualMachineCpuStatus::VirtualMachineCpuStatus,
- VirtualMachineMemStatus::VirtualMachineMemStatus,
-};
use anyhow::{anyhow, Result};
use binder::{ParcelFileDescriptor, ThreadState};
use log::{trace, warn};
use microdroid_payload_config::VmPayloadConfig;
-use statslog_virtualization_rust::{
- vm_booted, vm_cpu_status_reported, vm_creation_requested, vm_exited, vm_mem_status_reported,
-};
+use statslog_virtualization_rust::{vm_booted, vm_creation_requested, vm_exited};
use std::time::{Duration, SystemTime};
use zip::ZipArchive;
@@ -212,48 +206,3 @@
Ok(_) => trace!("statslog_rust succeeded for virtualization service"),
}
}
-
-/// Write the stats of VM cpu status to statsd
-pub fn write_vm_cpu_status_stats(
- uid: i32,
- vm_identifier: &String,
- cpu_status: &VirtualMachineCpuStatus,
-) {
- let vm_cpu_status_reported = vm_cpu_status_reported::VmCpuStatusReported {
- uid,
- vm_identifier,
- cpu_time_user_millis: cpu_status.cpu_time_user,
- cpu_time_nice_millis: cpu_status.cpu_time_nice,
- cpu_time_sys_millis: cpu_status.cpu_time_sys,
- cpu_time_idle_millis: cpu_status.cpu_time_idle,
- };
- match vm_cpu_status_reported.stats_write() {
- Err(e) => {
- warn!("statslog_rust failed with error: {}", e);
- }
- Ok(_) => trace!("statslog_rust succeeded for virtualization service"),
- }
-}
-
-/// Write the stats of VM memory status to statsd
-pub fn write_vm_mem_status_stats(
- uid: i32,
- vm_identifier: &String,
- mem_status: &VirtualMachineMemStatus,
-) {
- let vm_mem_status_reported = vm_mem_status_reported::VmMemStatusReported {
- uid,
- vm_identifier,
- mem_total_kb: mem_status.mem_total,
- mem_free_kb: mem_status.mem_free,
- mem_available_kb: mem_status.mem_available,
- mem_buffer_kb: mem_status.mem_buffer,
- mem_cached_kb: mem_status.mem_cached,
- };
- match vm_mem_status_reported.stats_write() {
- Err(e) => {
- warn!("statslog_rust failed with error: {}", e);
- }
- Ok(_) => trace!("statslog_rust succeeded for virtualization service"),
- }
-}
diff --git a/virtualizationservice/src/payload.rs b/virtualizationservice/src/payload.rs
index 4190fbb..233e74b 100644
--- a/virtualizationservice/src/payload.rs
+++ b/virtualizationservice/src/payload.rs
@@ -27,7 +27,9 @@
use microdroid_metadata::{ApexPayload, ApkPayload, Metadata, PayloadConfig, PayloadMetadata};
use microdroid_payload_config::{ApexConfig, VmPayloadConfig};
use once_cell::sync::OnceCell;
-use packagemanager_aidl::aidl::android::content::pm::IPackageManagerNative::IPackageManagerNative;
+use packagemanager_aidl::aidl::android::content::pm::{
+ IPackageManagerNative::IPackageManagerNative, StagedApexInfo::StagedApexInfo,
+};
use regex::Regex;
use serde::Deserialize;
use serde_xml_rs::from_reader;
@@ -48,7 +50,7 @@
const PACKAGE_MANAGER_NATIVE_SERVICE: &str = "package_native";
/// Represents the list of APEXes
-#[derive(Clone, Debug, Deserialize)]
+#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
struct ApexInfoList {
#[serde(rename = "apex-info")]
list: Vec<ApexInfo>,
@@ -58,6 +60,8 @@
struct ApexInfo {
#[serde(rename = "moduleName")]
name: String,
+ #[serde(rename = "versionCode")]
+ version: u64,
#[serde(rename = "modulePath")]
path: PathBuf,
@@ -101,6 +105,40 @@
Ok(apex_info_list)
})
}
+
+ // Override apex info with the staged one
+ fn override_staged_apex(&mut self, staged_apex_info: &StagedApexInfo) -> Result<()> {
+ let mut need_to_add: Option<ApexInfo> = None;
+ for apex_info in self.list.iter_mut() {
+ if staged_apex_info.moduleName == apex_info.name {
+ if apex_info.is_active && apex_info.is_factory {
+ // Copy the entry to the end as factory/non-active after the loop
+ // to keep the factory version. Typically this step is unncessary,
+ // but some apexes (like sharedlibs) need to be kept even if it's inactive.
+ need_to_add.replace(ApexInfo { is_active: false, ..apex_info.clone() });
+ // And make this one as non-factory. Note that this one is still active
+ // and overridden right below.
+ apex_info.is_factory = false;
+ }
+ // Active one is overridden with the staged one.
+ if apex_info.is_active {
+ apex_info.version = staged_apex_info.versionCode as u64;
+ apex_info.path = PathBuf::from(&staged_apex_info.diskImagePath);
+ apex_info.has_classpath_jar = staged_apex_info.hasClassPathJars;
+ apex_info.last_update_seconds = last_updated(&apex_info.path)?;
+ }
+ }
+ }
+ if let Some(info) = need_to_add {
+ self.list.push(info);
+ }
+ Ok(())
+ }
+}
+
+fn last_updated<P: AsRef<Path>>(path: P) -> Result<u64> {
+ let metadata = metadata(path)?;
+ Ok(metadata.modified()?.duration_since(SystemTime::UNIX_EPOCH)?.as_secs())
}
impl ApexInfo {
@@ -137,19 +175,11 @@
.context("Failed to get service when prefer_staged is set.")?;
let staged =
pm.getStagedApexModuleNames().context("getStagedApexModuleNames failed")?;
- for apex_info in list.list.iter_mut() {
- if staged.contains(&apex_info.name) {
- if let Some(staged_apex_info) =
- pm.getStagedApexInfo(&apex_info.name).context("getStagedApexInfo failed")?
- {
- apex_info.path = PathBuf::from(staged_apex_info.diskImagePath);
- apex_info.has_classpath_jar = staged_apex_info.hasClassPathJars;
- let metadata = metadata(&apex_info.path)?;
- apex_info.last_update_seconds =
- metadata.modified()?.duration_since(SystemTime::UNIX_EPOCH)?.as_secs();
- // by definition, staged apex can't be a factory apex.
- apex_info.is_factory = false;
- }
+ for name in staged {
+ if let Some(staged_apex_info) =
+ pm.getStagedApexInfo(&name).context("getStagedApexInfo failed")?
+ {
+ list.override_staged_apex(&staged_apex_info)?;
}
}
}
@@ -243,8 +273,13 @@
let apex_list = pm.get_apex_list(vm_payload_config.prefer_staged)?;
// collect APEXes from config
- let apex_infos =
+ let mut apex_infos =
collect_apex_infos(&apex_list, &vm_payload_config.apexes, app_config.debugLevel);
+
+ // Pass sorted list of apexes. Sorting key shouldn't use `path` because it will change after
+ // reboot with prefer_staged. `last_update_seconds` is added to distinguish "samegrade"
+ // update.
+ apex_infos.sort_by_key(|info| (&info.name, &info.version, &info.last_update_seconds));
info!("Microdroid payload APEXes: {:?}", apex_infos.iter().map(|ai| &ai.name));
let metadata_file = make_metadata_file(app_config, &apex_infos, temporary_directory)?;
@@ -422,6 +457,7 @@
#[cfg(test)]
mod tests {
use super::*;
+ use tempfile::NamedTempFile;
#[test]
fn test_find_apex_names_in_classpath() {
@@ -560,4 +596,90 @@
]
);
}
+
+ #[test]
+ fn test_prefer_staged_apex_with_factory_active_apex() {
+ let single_apex = ApexInfo {
+ name: "foo".to_string(),
+ version: 1,
+ path: PathBuf::from("foo.apex"),
+ is_factory: true,
+ is_active: true,
+ ..Default::default()
+ };
+ let mut apex_info_list = ApexInfoList { list: vec![single_apex.clone()] };
+
+ let staged = NamedTempFile::new().unwrap();
+ apex_info_list
+ .override_staged_apex(&StagedApexInfo {
+ moduleName: "foo".to_string(),
+ versionCode: 2,
+ diskImagePath: staged.path().to_string_lossy().to_string(),
+ ..Default::default()
+ })
+ .expect("should be ok");
+
+ assert_eq!(
+ apex_info_list,
+ ApexInfoList {
+ list: vec![
+ ApexInfo {
+ version: 2,
+ is_factory: false,
+ path: staged.path().to_owned(),
+ last_update_seconds: last_updated(staged.path()).unwrap(),
+ ..single_apex.clone()
+ },
+ ApexInfo { is_active: false, ..single_apex },
+ ],
+ }
+ );
+ }
+
+ #[test]
+ fn test_prefer_staged_apex_with_factory_and_inactive_apex() {
+ let factory_apex = ApexInfo {
+ name: "foo".to_string(),
+ version: 1,
+ path: PathBuf::from("foo.apex"),
+ is_factory: true,
+ ..Default::default()
+ };
+ let active_apex = ApexInfo {
+ name: "foo".to_string(),
+ version: 2,
+ path: PathBuf::from("foo.downloaded.apex"),
+ is_active: true,
+ ..Default::default()
+ };
+ let mut apex_info_list =
+ ApexInfoList { list: vec![factory_apex.clone(), active_apex.clone()] };
+
+ let staged = NamedTempFile::new().unwrap();
+ apex_info_list
+ .override_staged_apex(&StagedApexInfo {
+ moduleName: "foo".to_string(),
+ versionCode: 3,
+ diskImagePath: staged.path().to_string_lossy().to_string(),
+ ..Default::default()
+ })
+ .expect("should be ok");
+
+ assert_eq!(
+ apex_info_list,
+ ApexInfoList {
+ list: vec![
+ // factory apex isn't touched
+ factory_apex,
+ // update active one
+ ApexInfo {
+ version: 3,
+ path: staged.path().to_owned(),
+ last_update_seconds: last_updated(staged.path()).unwrap(),
+ ..active_apex
+ },
+ ],
+ }
+ );
+ }
}