Merge "Fix threading bugs"

This commit is contained in:
Treehugger Robot 2022-11-09 11:14:42 +00:00 committed by Gerrit Code Review
commit f2175fa423
2 changed files with 512 additions and 411 deletions

View File

@ -105,11 +105,11 @@ import java.util.zip.ZipFile;
* @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 class VirtualMachine implements AutoCloseable {
public static final String USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION =
"android.permission.USE_CUSTOM_VIRTUAL_MACHINE";
/**
* Status of a virtual machine
*
@ -165,15 +164,17 @@ public class VirtualMachine implements AutoCloseable {
*/
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;
/** Name of this VM within the package. The name should be unique in the package. */
@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.
*/
@ -196,38 +197,69 @@ public class VirtualMachine implements AutoCloseable {
}
/**
* 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,45 +267,70 @@ public class VirtualMachine implements AutoCloseable {
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 {
VirtualMachine vm = new VirtualMachine(context, name, config);
try (FileOutputStream output = new FileOutputStream(vm.mConfigFilePath)) {
config.serialize(output);
} catch (IOException e) {
throw new VirtualMachineException("failed to write VM config", e);
}
try {
@ -299,22 +356,22 @@ public class VirtualMachine implements AutoCloseable {
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);
}
instancesMap.put(name, new WeakReference<>(vm));
}
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 @@ public class VirtualMachine implements AutoCloseable {
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);
VirtualMachine vm = null;
if (instancesMap.containsKey(name)) {
vm = instancesMap.get(name).get();
}
if (vm == null) {
vm = new VirtualMachine(context, name, config);
instancesMap.put(name, new WeakReference<>(vm));
}
}
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,8 +454,10 @@ public class VirtualMachine implements AutoCloseable {
*/
@NonNull
public VirtualMachineConfig getConfig() {
synchronized (mLock) {
return mConfig;
}
}
/**
* Returns the current status of this virtual machine.
@ -389,27 +466,70 @@ public class VirtualMachine implements AutoCloseable {
*/
@Status
public int getStatus() {
IVirtualMachine virtualMachine;
synchronized (mLock) {
virtualMachine = mVirtualMachine;
}
if (virtualMachine == null) {
return mVmRootPath.exists() ? STATUS_STOPPED : STATUS_DELETED;
} else {
try {
if (mVirtualMachine != null) {
switch (mVirtualMachine.getState()) {
case VirtualMachineState.NOT_STARTED:
return STATUS_STOPPED;
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 (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 class VirtualMachine implements AutoCloseable {
*/
public void setCallback(@NonNull @CallbackExecutor Executor executor,
@NonNull VirtualMachineCallback callback) {
synchronized (mLock) {
synchronized (mCallbackLock) {
mCallback = callback;
mCallbackExecutor = executor;
}
@ -432,7 +552,7 @@ public class VirtualMachine implements AutoCloseable {
* @hide
*/
public void clearCallback() {
synchronized (mLock) {
synchronized (mCallbackLock) {
mCallback = null;
mCallbackExecutor = null;
}
@ -442,7 +562,7 @@ public class VirtualMachine implements AutoCloseable {
private void executeCallback(Consumer<VirtualMachineCallback> fn) {
final VirtualMachineCallback callback;
final Executor executor;
synchronized (mLock) {
synchronized (mCallbackLock) {
callback = mCallback;
executor = mCallbackExecutor;
}
@ -469,9 +589,8 @@ public class VirtualMachine implements AutoCloseable {
*/
@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();
@ -505,7 +624,8 @@ public class VirtualMachine implements AutoCloseable {
// Re-open idsig file in read-only mode
appConfig.idsig = ParcelFileDescriptor.open(mIdsigFilePath, MODE_READ_ONLY);
appConfig.instanceImage = ParcelFileDescriptor.open(mInstanceFilePath, MODE_READ_WRITE);
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));
@ -513,7 +633,8 @@ public class VirtualMachine implements AutoCloseable {
appConfig.extraIdsigs = extraIdsigs;
android.system.virtualizationservice.VirtualMachineConfig vmConfigParcel =
android.system.virtualizationservice.VirtualMachineConfig.appConfig(appConfig);
android.system.virtualizationservice.VirtualMachineConfig.appConfig(
appConfig);
// The VM should only be observed to die once
AtomicBoolean onDiedCalled = new AtomicBoolean(false);
@ -542,7 +663,8 @@ public class VirtualMachine implements AutoCloseable {
@Override
public void onPayloadFinished(int cid, int exitCode) {
executeCallback(
(cb) -> cb.onPayloadFinished(VirtualMachine.this, exitCode));
(cb) -> cb.onPayloadFinished(VirtualMachine.this,
exitCode));
}
@Override
@ -579,7 +701,9 @@ public class VirtualMachine implements AutoCloseable {
throw e.rethrowAsRuntimeException();
}
}
}
@GuardedBy("mLock")
private void createVmPipes() throws VirtualMachineException {
try {
if (mConsoleReader == null || mConsoleWriter == null) {
@ -598,6 +722,185 @@ public class VirtualMachine implements AutoCloseable {
}
}
/**
* 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);
}
}
}
@VirtualMachineCallback.ErrorCode
private int getTranslatedError(int reason) {
switch (reason) {
@ -652,206 +955,6 @@ public class VirtualMachine implements AutoCloseable {
}
}
/**
* 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 +1045,9 @@ public class VirtualMachine implements AutoCloseable {
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);
}
}

View File

@ -26,6 +26,8 @@ import android.annotation.SuppressLint;
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 @@ public class VirtualMachineManager {
mContext = context;
}
@GuardedBy("sInstances")
private static final Map<Context, WeakReference<VirtualMachineManager>> sInstances =
new WeakHashMap<>();
@ -96,9 +99,6 @@ public class VirtualMachineManager {
}
}
/** 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 class VirtualMachineManager {
public VirtualMachine create(
@NonNull String name, @NonNull VirtualMachineConfig config)
throws VirtualMachineException {
synchronized (sCreateLock) {
synchronized (VirtualMachine.sCreateLock) {
return VirtualMachine.create(mContext, name, config);
}
}
@ -151,8 +151,10 @@ public class VirtualMachineManager {
*/
@Nullable
public VirtualMachine get(@NonNull String name) throws VirtualMachineException {
synchronized (VirtualMachine.sCreateLock) {
return VirtualMachine.load(mContext, name);
}
}
/**
* Returns an existing {@link VirtualMachine} if it exists, or create a new one. The config
@ -166,7 +168,7 @@ public class VirtualMachineManager {
@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 class VirtualMachineManager {
*/
public void delete(@NonNull String name) throws VirtualMachineException {
requireNonNull(name);
synchronized (VirtualMachine.sCreateLock) {
VirtualMachine.delete(mContext, name);
}
}
}