1 /*
2  * Copyright (C) 2021 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.system.virtualmachine;
18 
19 import static android.os.ParcelFileDescriptor.AutoCloseInputStream;
20 import static android.os.ParcelFileDescriptor.MODE_READ_ONLY;
21 import static android.os.ParcelFileDescriptor.MODE_READ_WRITE;
22 import static android.system.virtualmachine.VirtualMachineCallback.ERROR_PAYLOAD_CHANGED;
23 import static android.system.virtualmachine.VirtualMachineCallback.ERROR_PAYLOAD_INVALID_CONFIG;
24 import static android.system.virtualmachine.VirtualMachineCallback.ERROR_PAYLOAD_VERIFICATION_FAILED;
25 import static android.system.virtualmachine.VirtualMachineCallback.ERROR_UNKNOWN;
26 import static android.system.virtualmachine.VirtualMachineCallback.STOP_REASON_CRASH;
27 import static android.system.virtualmachine.VirtualMachineCallback.STOP_REASON_HANGUP;
28 import static android.system.virtualmachine.VirtualMachineCallback.STOP_REASON_INFRASTRUCTURE_ERROR;
29 import static android.system.virtualmachine.VirtualMachineCallback.STOP_REASON_KILLED;
30 import static android.system.virtualmachine.VirtualMachineCallback.STOP_REASON_MICRODROID_FAILED_TO_CONNECT_TO_VIRTUALIZATION_SERVICE;
31 import static android.system.virtualmachine.VirtualMachineCallback.STOP_REASON_MICRODROID_INVALID_PAYLOAD_CONFIG;
32 import static android.system.virtualmachine.VirtualMachineCallback.STOP_REASON_MICRODROID_PAYLOAD_HAS_CHANGED;
33 import static android.system.virtualmachine.VirtualMachineCallback.STOP_REASON_MICRODROID_PAYLOAD_VERIFICATION_FAILED;
34 import static android.system.virtualmachine.VirtualMachineCallback.STOP_REASON_MICRODROID_UNKNOWN_RUNTIME_ERROR;
35 import static android.system.virtualmachine.VirtualMachineCallback.STOP_REASON_PVM_FIRMWARE_INSTANCE_IMAGE_CHANGED;
36 import static android.system.virtualmachine.VirtualMachineCallback.STOP_REASON_PVM_FIRMWARE_PUBLIC_KEY_MISMATCH;
37 import static android.system.virtualmachine.VirtualMachineCallback.STOP_REASON_REBOOT;
38 import static android.system.virtualmachine.VirtualMachineCallback.STOP_REASON_SHUTDOWN;
39 import static android.system.virtualmachine.VirtualMachineCallback.STOP_REASON_START_FAILED;
40 import static android.system.virtualmachine.VirtualMachineCallback.STOP_REASON_UNKNOWN;
41 import static android.system.virtualmachine.VirtualMachineCallback.STOP_REASON_VIRTUALIZATION_SERVICE_DIED;
42 
43 import static java.util.Objects.requireNonNull;
44 
45 import android.annotation.CallbackExecutor;
46 import android.annotation.FlaggedApi;
47 import android.annotation.IntDef;
48 import android.annotation.IntRange;
49 import android.annotation.NonNull;
50 import android.annotation.Nullable;
51 import android.annotation.RequiresPermission;
52 import android.annotation.SuppressLint;
53 import android.annotation.SystemApi;
54 import android.annotation.TestApi;
55 import android.annotation.WorkerThread;
56 import android.content.ComponentCallbacks2;
57 import android.content.Context;
58 import android.content.pm.ApplicationInfo;
59 import android.content.pm.PackageManager;
60 import android.content.res.Configuration;
61 import android.os.Binder;
62 import android.os.IBinder;
63 import android.os.ParcelFileDescriptor;
64 import android.os.RemoteException;
65 import android.os.ServiceSpecificException;
66 import android.system.ErrnoException;
67 import android.system.OsConstants;
68 import android.system.virtualizationcommon.DeathReason;
69 import android.system.virtualizationcommon.ErrorCode;
70 import android.system.virtualizationservice.IVirtualMachine;
71 import android.system.virtualizationservice.IVirtualMachineCallback;
72 import android.system.virtualizationservice.IVirtualizationService;
73 import android.system.virtualizationservice.InputDevice;
74 import android.system.virtualizationservice.MemoryTrimLevel;
75 import android.system.virtualizationservice.PartitionType;
76 import android.system.virtualizationservice.VirtualMachineAppConfig;
77 import android.system.virtualizationservice.VirtualMachineRawConfig;
78 import android.system.virtualizationservice.VirtualMachineState;
79 import android.util.JsonReader;
80 import android.util.Log;
81 import android.view.KeyEvent;
82 import android.view.MotionEvent;
83 
84 import com.android.internal.annotations.GuardedBy;
85 import com.android.system.virtualmachine.flags.Flags;
86 
87 import libcore.io.IoBridge;
88 import libcore.io.IoUtils;
89 
90 import java.io.File;
91 import java.io.FileDescriptor;
92 import java.io.FileInputStream;
93 import java.io.FileNotFoundException;
94 import java.io.FileOutputStream;
95 import java.io.IOException;
96 import java.io.InputStream;
97 import java.io.InputStreamReader;
98 import java.io.OutputStream;
99 import java.lang.annotation.Retention;
100 import java.lang.annotation.RetentionPolicy;
101 import java.nio.ByteBuffer;
102 import java.nio.ByteOrder;
103 import java.nio.channels.FileChannel;
104 import java.nio.charset.StandardCharsets;
105 import java.nio.file.FileAlreadyExistsException;
106 import java.nio.file.FileVisitResult;
107 import java.nio.file.Files;
108 import java.nio.file.Path;
109 import java.nio.file.SimpleFileVisitor;
110 import java.nio.file.attribute.BasicFileAttributes;
111 import java.util.ArrayList;
112 import java.util.Arrays;
113 import java.util.Collection;
114 import java.util.Collections;
115 import java.util.List;
116 import java.util.concurrent.Executor;
117 import java.util.concurrent.Executors;
118 import java.util.concurrent.atomic.AtomicBoolean;
119 import java.util.function.Consumer;
120 import java.util.zip.ZipFile;
121 
122 /**
123  * Represents an VM instance, with its own configuration and state. Instances are persistent and are
124  * created or retrieved via {@link VirtualMachineManager}.
125  *
126  * <p>The {@link #run} method actually starts up the VM and allows the payload code to execute. It
127  * will continue until it exits or {@link #stop} is called. Updates on the state of the VM can be
128  * received using {@link #setCallback}. The app can communicate with the VM using {@link
129  * #connectToVsockServer} or {@link #connectVsock}.
130  *
131  * <p>The payload code running inside the VM has access to a set of native APIs; see the <a
132  * href="https://cs.android.com/android/platform/superproject/+/master:packages/modules/Virtualization/vm_payload/README.md">README
133  * file</a> for details.
134  *
135  * <p>Each VM has a unique secret, computed from the APK that contains the code running in it, the
136  * VM configuration, and a random per-instance salt. The secret can be accessed by the payload code
137  * running inside the VM (using {@code AVmPayload_getVmInstanceSecret}) but is not made available
138  * outside it.
139  *
140  * @hide
141  */
142 @SystemApi
143 public class VirtualMachine implements AutoCloseable {
144     /** The permission needed to create or run a virtual machine. */
145     public static final String MANAGE_VIRTUAL_MACHINE_PERMISSION =
146             "android.permission.MANAGE_VIRTUAL_MACHINE";
147 
148     /**
149      * The permission needed to create a virtual machine with more advanced configuration options.
150      */
151     public static final String USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION =
152             "android.permission.USE_CUSTOM_VIRTUAL_MACHINE";
153 
154     /**
155      * The lowest port number that can be used to communicate with the virtual machine payload.
156      *
157      * @see #connectToVsockServer
158      * @see #connectVsock
159      */
160     @SuppressLint("MinMaxConstant") // Won't change: see man 7 vsock.
161     public static final long MIN_VSOCK_PORT = 1024;
162 
163     /**
164      * The highest port number that can be used to communicate with the virtual machine payload.
165      *
166      * @see #connectToVsockServer
167      * @see #connectVsock
168      */
169     @SuppressLint("MinMaxConstant") // Won't change: see man 7 vsock.
170     public static final long MAX_VSOCK_PORT = (1L << 32) - 1;
171 
172     private ParcelFileDescriptor mTouchSock;
173     private ParcelFileDescriptor mKeySock;
174     private ParcelFileDescriptor mMouseSock;
175 
176     /**
177      * Status of a virtual machine
178      *
179      * @hide
180      */
181     @Retention(RetentionPolicy.SOURCE)
182     @IntDef(prefix = "STATUS_", value = {
183             STATUS_STOPPED,
184             STATUS_RUNNING,
185             STATUS_DELETED
186     })
187     public @interface Status {}
188 
189     /** The virtual machine has just been created, or {@link #stop} was called on it. */
190     public static final int STATUS_STOPPED = 0;
191 
192     /** The virtual machine is running. */
193     public static final int STATUS_RUNNING = 1;
194 
195     /**
196      * The virtual machine has been deleted. This is an irreversible state. Once a virtual machine
197      * is deleted all its secrets are permanently lost, and it cannot be run. A new virtual machine
198      * with the same name and config may be created, with new and different secrets.
199      */
200     public static final int STATUS_DELETED = 2;
201 
202     private static final String TAG = "VirtualMachine";
203 
204     /** Name of the directory under the files directory where all VMs created for the app exist. */
205     private static final String VM_DIR = "vm";
206 
207     /** Name of the persisted config file for a VM. */
208     private static final String CONFIG_FILE = "config.xml";
209 
210     /** Name of the instance image file for a VM. (Not implemented) */
211     private static final String INSTANCE_IMAGE_FILE = "instance.img";
212 
213     /** Name of the file for a VM containing Id. */
214     private static final String INSTANCE_ID_FILE = "instance_id";
215 
216     /** Name of the idsig file for a VM */
217     private static final String IDSIG_FILE = "idsig";
218 
219     /** Name of the idsig files for extra APKs. */
220     private static final String EXTRA_IDSIG_FILE_PREFIX = "extra_idsig_";
221 
222     /** Size of the instance image. 10 MB. */
223     private static final long INSTANCE_FILE_SIZE = 10 * 1024 * 1024;
224 
225     /** Name of the file backing the encrypted storage */
226     private static final String ENCRYPTED_STORE_FILE = "storage.img";
227 
228     /** The package which owns this VM. */
229     @NonNull private final String mPackageName;
230 
231     /** Name of this VM within the package. The name should be unique in the package. */
232     @NonNull private final String mName;
233 
234     /**
235      * Path to the directory containing all the files related to this VM.
236      */
237     @NonNull private final File mVmRootPath;
238 
239     /**
240      * Path to the config file for this VM. The config file is where the configuration is persisted.
241      */
242     @NonNull private final File mConfigFilePath;
243 
244     /** Path to the instance image file for this VM. */
245     @NonNull private final File mInstanceFilePath;
246 
247     /** Path to the idsig file for this VM. */
248     @NonNull private final File mIdsigFilePath;
249 
250     /** File that backs the encrypted storage - Will be null if not enabled. */
251     @Nullable private final File mEncryptedStoreFilePath;
252 
253     /** File that contains the Id. This is NULL iff FEATURE_LLPVM is disabled */
254     @Nullable private final File mInstanceIdPath;
255 
256     /**
257      * Unmodifiable list of extra apks. Apks are specified by the vm config, and corresponding
258      * idsigs are to be generated.
259      */
260     @NonNull private final List<ExtraApkSpec> mExtraApks;
261 
262     private class MemoryManagementCallbacks implements ComponentCallbacks2 {
263         @Override
onConfigurationChanged(@onNull Configuration newConfig)264         public void onConfigurationChanged(@NonNull Configuration newConfig) {}
265 
266         @Override
onLowMemory()267         public void onLowMemory() {}
268 
269         @Override
onTrimMemory(int level)270         public void onTrimMemory(int level) {
271             @MemoryTrimLevel int vmTrimLevel;
272 
273             switch (level) {
274                 case ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL:
275                     vmTrimLevel = MemoryTrimLevel.TRIM_MEMORY_RUNNING_CRITICAL;
276                     break;
277                 case ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW:
278                     vmTrimLevel = MemoryTrimLevel.TRIM_MEMORY_RUNNING_LOW;
279                     break;
280                 case ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE:
281                     vmTrimLevel = MemoryTrimLevel.TRIM_MEMORY_RUNNING_MODERATE;
282                     break;
283                 case ComponentCallbacks2.TRIM_MEMORY_BACKGROUND:
284                 case ComponentCallbacks2.TRIM_MEMORY_MODERATE:
285                 case ComponentCallbacks2.TRIM_MEMORY_COMPLETE:
286                     /* Release as much memory as we can. The app is on the LMKD LRU kill list. */
287                     vmTrimLevel = MemoryTrimLevel.TRIM_MEMORY_RUNNING_CRITICAL;
288                     break;
289                 default:
290                     /* Treat unrecognised messages as generic low-memory warnings. */
291                     vmTrimLevel = MemoryTrimLevel.TRIM_MEMORY_RUNNING_LOW;
292                     break;
293             }
294 
295             synchronized (mLock) {
296                 try {
297                     if (mVirtualMachine != null) {
298                         mVirtualMachine.onTrimMemory(vmTrimLevel);
299                     }
300                 } catch (Exception e) {
301                     /* Caller doesn't want our exceptions. Log them instead. */
302                     Log.w(TAG, "TrimMemory failed: ", e);
303                 }
304             }
305         }
306     }
307 
308     /** Running instance of virtmgr that hosts VirtualizationService for this VM. */
309     @NonNull private final VirtualizationService mVirtualizationService;
310 
311     @NonNull private final MemoryManagementCallbacks mMemoryManagementCallbacks;
312 
313     @NonNull private final Context mContext;
314 
315     // A note on lock ordering:
316     // You can take mLock while holding VirtualMachineManager.sCreateLock, but not vice versa.
317     // We never take any other lock while holding mCallbackLock; therefore you can
318     // take mCallbackLock while holding any other lock.
319 
320     /** Lock protecting our mutable state (other than callbacks). */
321     private final Object mLock = new Object();
322 
323     /** Lock protecting callbacks. */
324     private final Object mCallbackLock = new Object();
325 
326     private final boolean mVmOutputCaptured;
327 
328     private final boolean mVmConsoleInputSupported;
329 
330     private final boolean mConnectVmConsole;
331 
332     private final Executor mConsoleExecutor = Executors.newSingleThreadExecutor();
333 
334     /** The configuration that is currently associated with this VM. */
335     @GuardedBy("mLock")
336     @NonNull
337     private VirtualMachineConfig mConfig;
338 
339     /** Handle to the "running" VM. */
340     @GuardedBy("mLock")
341     @Nullable
342     private IVirtualMachine mVirtualMachine;
343 
344     @GuardedBy("mLock")
345     @Nullable
346     private ParcelFileDescriptor mConsoleOutReader;
347 
348     @GuardedBy("mLock")
349     @Nullable
350     private ParcelFileDescriptor mConsoleOutWriter;
351 
352     @GuardedBy("mLock")
353     @Nullable
354     private ParcelFileDescriptor mConsoleInReader;
355 
356     @GuardedBy("mLock")
357     @Nullable
358     private ParcelFileDescriptor mConsoleInWriter;
359 
360     @GuardedBy("mLock")
361     @Nullable
362     private ParcelFileDescriptor mTeeConsoleOutReader;
363 
364     @GuardedBy("mLock")
365     @Nullable
366     private ParcelFileDescriptor mTeeConsoleOutWriter;
367 
368     @GuardedBy("mLock")
369     @Nullable
370     private ParcelFileDescriptor mPtyFd;
371 
372     @GuardedBy("mLock")
373     @Nullable
374     private ParcelFileDescriptor mPtsFd;
375 
376     @GuardedBy("mLock")
377     @Nullable
378     private String mPtsName;
379 
380     @GuardedBy("mLock")
381     @Nullable
382     private ParcelFileDescriptor mLogReader;
383 
384     @GuardedBy("mLock")
385     @Nullable
386     private ParcelFileDescriptor mLogWriter;
387 
388     @GuardedBy("mLock")
389     private boolean mWasDeleted = false;
390 
391     /** The registered callback */
392     @GuardedBy("mCallbackLock")
393     @Nullable
394     private VirtualMachineCallback mCallback;
395 
396     /** The executor on which the callback will be executed */
397     @GuardedBy("mCallbackLock")
398     @Nullable
399     private Executor mCallbackExecutor;
400 
401     private static class ExtraApkSpec {
402         public final File apk;
403         public final File idsig;
404 
ExtraApkSpec(File apk, File idsig)405         ExtraApkSpec(File apk, File idsig) {
406             this.apk = apk;
407             this.idsig = idsig;
408         }
409     }
410 
411     static {
412         System.loadLibrary("virtualmachine_jni");
413     }
414 
VirtualMachine( @onNull Context context, @NonNull String name, @NonNull VirtualMachineConfig config, @NonNull VirtualizationService service)415     private VirtualMachine(
416             @NonNull Context context,
417             @NonNull String name,
418             @NonNull VirtualMachineConfig config,
419             @NonNull VirtualizationService service)
420             throws VirtualMachineException {
421         mPackageName = context.getPackageName();
422         mName = requireNonNull(name, "Name must not be null");
423         mConfig = requireNonNull(config, "Config must not be null");
424         mVirtualizationService = service;
425 
426         File thisVmDir = getVmDir(context, mName);
427         mVmRootPath = thisVmDir;
428         mConfigFilePath = new File(thisVmDir, CONFIG_FILE);
429         try {
430             mInstanceIdPath =
431                     (mVirtualizationService
432                                     .getBinder()
433                                     .isFeatureEnabled(IVirtualizationService.FEATURE_LLPVM_CHANGES))
434                             ? new File(thisVmDir, INSTANCE_ID_FILE)
435                             : null;
436         } catch (RemoteException e) {
437             throw e.rethrowAsRuntimeException();
438         }
439         mInstanceFilePath = new File(thisVmDir, INSTANCE_IMAGE_FILE);
440         mIdsigFilePath = new File(thisVmDir, IDSIG_FILE);
441         mExtraApks = setupExtraApks(context, config, thisVmDir);
442         mMemoryManagementCallbacks = new MemoryManagementCallbacks();
443         mContext = context;
444         mEncryptedStoreFilePath =
445                 (config.isEncryptedStorageEnabled())
446                         ? new File(thisVmDir, ENCRYPTED_STORE_FILE)
447                         : null;
448 
449         mVmOutputCaptured = config.isVmOutputCaptured();
450         mVmConsoleInputSupported = config.isVmConsoleInputSupported();
451         mConnectVmConsole = config.isConnectVmConsole();
452     }
453 
454     /**
455      * Creates a virtual machine from an {@link VirtualMachineDescriptor} object and associates it
456      * with the given name.
457      *
458      * <p>The new virtual machine will be in the same state as the descriptor indicates.
459      *
460      * <p>Once a virtual machine is imported it is persisted until it is deleted by calling {@link
461      * #delete}. The imported virtual machine is in {@link #STATUS_STOPPED} state. To run the VM,
462      * call {@link #run}.
463      */
464     @GuardedBy("VirtualMachineManager.sCreateLock")
465     @NonNull
fromDescriptor( @onNull Context context, @NonNull String name, @NonNull VirtualMachineDescriptor vmDescriptor)466     static VirtualMachine fromDescriptor(
467             @NonNull Context context,
468             @NonNull String name,
469             @NonNull VirtualMachineDescriptor vmDescriptor)
470             throws VirtualMachineException {
471         File vmDir = createVmDir(context, name);
472         try {
473             VirtualMachine vm;
474             try (vmDescriptor) {
475                 VirtualMachineConfig config = VirtualMachineConfig.from(vmDescriptor.getConfigFd());
476                 vm = new VirtualMachine(context, name, config, VirtualizationService.getInstance());
477                 config.serialize(vm.mConfigFilePath);
478                 try {
479                     vm.mInstanceFilePath.createNewFile();
480                 } catch (IOException e) {
481                     throw new VirtualMachineException("failed to create instance image", e);
482                 }
483                 vm.importInstanceFrom(vmDescriptor.getInstanceImgFd());
484 
485                 if (vmDescriptor.getEncryptedStoreFd() != null) {
486                     try {
487                         vm.mEncryptedStoreFilePath.createNewFile();
488                     } catch (IOException e) {
489                         throw new VirtualMachineException(
490                                 "failed to create encrypted storage image", e);
491                     }
492                     vm.importEncryptedStoreFrom(vmDescriptor.getEncryptedStoreFd());
493                 }
494                 if (vm.mInstanceIdPath != null) {
495                     vm.importInstanceIdFrom(vmDescriptor.getInstanceIdFd());
496                     vm.claimInstance();
497                 }
498             }
499             return vm;
500         } catch (VirtualMachineException | RuntimeException e) {
501             // If anything goes wrong, delete any files created so far and the VM's directory
502             try {
503                 deleteRecursively(vmDir);
504             } catch (Exception innerException) {
505                 e.addSuppressed(innerException);
506             }
507             throw e;
508         }
509     }
510 
511     /**
512      * Creates a virtual machine with the given name and config. Once a virtual machine is created
513      * it is persisted until it is deleted by calling {@link #delete}. The created virtual machine
514      * is in {@link #STATUS_STOPPED} state. To run the VM, call {@link #run}.
515      */
516     @GuardedBy("VirtualMachineManager.sCreateLock")
517     @NonNull
create( @onNull Context context, @NonNull String name, @NonNull VirtualMachineConfig config)518     static VirtualMachine create(
519             @NonNull Context context, @NonNull String name, @NonNull VirtualMachineConfig config)
520             throws VirtualMachineException {
521         File vmDir = createVmDir(context, name);
522 
523         try {
524             VirtualMachine vm =
525                     new VirtualMachine(context, name, config, VirtualizationService.getInstance());
526             config.serialize(vm.mConfigFilePath);
527             try {
528                 vm.mInstanceFilePath.createNewFile();
529             } catch (IOException e) {
530                 throw new VirtualMachineException("failed to create instance image", e);
531             }
532             if (config.isEncryptedStorageEnabled()) {
533                 try {
534                     vm.mEncryptedStoreFilePath.createNewFile();
535                 } catch (IOException e) {
536                     throw new VirtualMachineException(
537                             "failed to create encrypted storage image", e);
538                 }
539             }
540 
541             IVirtualizationService service = vm.mVirtualizationService.getBinder();
542 
543             if (vm.mInstanceIdPath != null) {
544                 try (FileOutputStream stream = new FileOutputStream(vm.mInstanceIdPath)) {
545                     byte[] id = service.allocateInstanceId();
546                     stream.write(id);
547                 } catch (FileNotFoundException e) {
548                     throw new VirtualMachineException("instance_id file missing", e);
549                 } catch (IOException e) {
550                     throw new VirtualMachineException("failed to persist instance_id", e);
551                 } catch (RemoteException e) {
552                     throw e.rethrowAsRuntimeException();
553                 } catch (ServiceSpecificException | IllegalArgumentException e) {
554                     throw new VirtualMachineException("failed to create instance_id", e);
555                 }
556             }
557 
558             try {
559                 service.initializeWritablePartition(
560                         ParcelFileDescriptor.open(vm.mInstanceFilePath, MODE_READ_WRITE),
561                         INSTANCE_FILE_SIZE,
562                         PartitionType.ANDROID_VM_INSTANCE);
563             } catch (FileNotFoundException e) {
564                 throw new VirtualMachineException("instance image missing", e);
565             } catch (RemoteException e) {
566                 throw e.rethrowAsRuntimeException();
567             } catch (ServiceSpecificException | IllegalArgumentException e) {
568                 throw new VirtualMachineException("failed to create instance partition", e);
569             }
570 
571             if (config.isEncryptedStorageEnabled()) {
572                 try {
573                     service.initializeWritablePartition(
574                             ParcelFileDescriptor.open(vm.mEncryptedStoreFilePath, MODE_READ_WRITE),
575                             config.getEncryptedStorageBytes(),
576                             PartitionType.ENCRYPTEDSTORE);
577                 } catch (FileNotFoundException e) {
578                     throw new VirtualMachineException("encrypted storage image missing", e);
579                 } catch (RemoteException e) {
580                     throw e.rethrowAsRuntimeException();
581                 } catch (ServiceSpecificException | IllegalArgumentException e) {
582                     throw new VirtualMachineException(
583                             "failed to create encrypted storage partition", e);
584                 }
585             }
586             return vm;
587         } catch (VirtualMachineException | RuntimeException e) {
588             // If anything goes wrong, delete any files created so far and the VM's directory
589             try {
590                 vmInstanceCleanup(context, name);
591             } catch (Exception innerException) {
592                 e.addSuppressed(innerException);
593             }
594             throw e;
595         }
596     }
597 
598     /** Loads a virtual machine that is already created before. */
599     @GuardedBy("VirtualMachineManager.sCreateLock")
600     @Nullable
load(@onNull Context context, @NonNull String name)601     static VirtualMachine load(@NonNull Context context, @NonNull String name)
602             throws VirtualMachineException {
603         File thisVmDir = getVmDir(context, name);
604         if (!thisVmDir.exists()) {
605             // The VM doesn't exist.
606             return null;
607         }
608         File configFilePath = new File(thisVmDir, CONFIG_FILE);
609         VirtualMachineConfig config = VirtualMachineConfig.from(configFilePath);
610         VirtualMachine vm =
611                 new VirtualMachine(context, name, config, VirtualizationService.getInstance());
612 
613         if (vm.mInstanceIdPath != null && !vm.mInstanceIdPath.exists()) {
614             throw new VirtualMachineException("instance_id file missing");
615         }
616         if (!vm.mInstanceFilePath.exists()) {
617             throw new VirtualMachineException("instance image missing");
618         }
619         if (config.isEncryptedStorageEnabled() && !vm.mEncryptedStoreFilePath.exists()) {
620             throw new VirtualMachineException("Storage image missing");
621         }
622         return vm;
623     }
624 
625     @GuardedBy("VirtualMachineManager.sCreateLock")
delete(Context context, String name)626     void delete(Context context, String name) throws VirtualMachineException {
627         synchronized (mLock) {
628             checkStopped();
629             // Once we explicitly delete a VM it must remain permanently in the deleted state;
630             // if a new VM is created with the same name (and files) that's unrelated.
631             mWasDeleted = true;
632         }
633         vmInstanceCleanup(context, name);
634     }
635 
636     // Delete the full VM directory and notify VirtualizationService to remove this
637     // VM instance for housekeeping.
638     @GuardedBy("VirtualMachineManager.sCreateLock")
vmInstanceCleanup(Context context, String name)639     static void vmInstanceCleanup(Context context, String name) throws VirtualMachineException {
640         File vmDir = getVmDir(context, name);
641         notifyInstanceRemoval(vmDir, VirtualizationService.getInstance());
642         try {
643             deleteRecursively(vmDir);
644         } catch (IOException e) {
645             throw new VirtualMachineException(e);
646         }
647     }
648 
notifyInstanceRemoval( File vmDirectory, @NonNull VirtualizationService service)649     private static void notifyInstanceRemoval(
650             File vmDirectory, @NonNull VirtualizationService service) {
651         File instanceIdFile = new File(vmDirectory, INSTANCE_ID_FILE);
652         try {
653             byte[] instanceId = Files.readAllBytes(instanceIdFile.toPath());
654             service.getBinder().removeVmInstance(instanceId);
655         } catch (Exception e) {
656             // Deliberately ignoring error in removing VM instance. This potentially leads to
657             // unaccounted instances in the VS' database. But, nothing much can be done by caller.
658             Log.w(TAG, "Failed to notify VS to remove the VM instance", e);
659         }
660     }
661 
662     // Claim the instance. This notifies the global VS about the ownership of this
663     // instance_id for housekeeping purpose.
claimInstance()664     void claimInstance() throws VirtualMachineException {
665         if (mInstanceIdPath != null) {
666             IVirtualizationService service = mVirtualizationService.getBinder();
667             try {
668                 byte[] instanceId = Files.readAllBytes(mInstanceIdPath.toPath());
669                 service.claimVmInstance(instanceId);
670             }
671             catch (IOException e) {
672                 throw new VirtualMachineException("failed to read instance_id", e);
673             } catch (RemoteException e) {
674                 throw e.rethrowAsRuntimeException();
675             }
676         }
677     }
678 
679     @GuardedBy("VirtualMachineManager.sCreateLock")
680     @NonNull
createVmDir(@onNull Context context, @NonNull String name)681     private static File createVmDir(@NonNull Context context, @NonNull String name)
682             throws VirtualMachineException {
683         File vmDir = getVmDir(context, name);
684         try {
685             // We don't need to undo this even if VM creation fails.
686             Files.createDirectories(vmDir.getParentFile().toPath());
687 
688             // The checking of the existence of this directory and the creation of it is done
689             // atomically. If the directory already exists (i.e. the VM with the same name was
690             // already created), FileAlreadyExistsException is thrown.
691             Files.createDirectory(vmDir.toPath());
692         } catch (FileAlreadyExistsException e) {
693             throw new VirtualMachineException("virtual machine already exists", e);
694         } catch (IOException e) {
695             throw new VirtualMachineException("failed to create directory for VM", e);
696         }
697         return vmDir;
698     }
699 
700     @NonNull
getVmDir(@onNull Context context, @NonNull String name)701     private static File getVmDir(@NonNull Context context, @NonNull String name) {
702         if (name.contains(File.separator) || name.equals(".") || name.equals("..")) {
703             throw new IllegalArgumentException("Invalid VM name: " + name);
704         }
705         File vmRoot = new File(context.getDataDir(), VM_DIR);
706         return new File(vmRoot, name);
707     }
708 
709     /**
710      * Returns the name of this virtual machine. The name is unique in the package and can't be
711      * changed.
712      *
713      * @hide
714      */
715     @SystemApi
716     @NonNull
getName()717     public String getName() {
718         return mName;
719     }
720 
721     /**
722      * Returns the currently selected config of this virtual machine. There can be multiple virtual
723      * machines sharing the same config. Even in that case, the virtual machines are completely
724      * isolated from each other; they have different secrets. It is also possible that a virtual
725      * machine can change its config, which can be done by calling {@link #setConfig}.
726      *
727      * <p>NOTE: This method may block and should not be called on the main thread.
728      *
729      * @hide
730      */
731     @SystemApi
732     @WorkerThread
733     @NonNull
getConfig()734     public VirtualMachineConfig getConfig() {
735         synchronized (mLock) {
736             return mConfig;
737         }
738     }
739 
740     /**
741      * Returns the current status of this virtual machine.
742      *
743      * <p>NOTE: This method may block and should not be called on the main thread.
744      *
745      * @hide
746      */
747     @SystemApi
748     @WorkerThread
749     @Status
getStatus()750     public int getStatus() {
751         IVirtualMachine virtualMachine;
752         synchronized (mLock) {
753             if (mWasDeleted) {
754                 return STATUS_DELETED;
755             }
756             virtualMachine = mVirtualMachine;
757         }
758 
759         int status;
760         if (virtualMachine == null) {
761             status = STATUS_STOPPED;
762         } else {
763             try {
764                 status = stateToStatus(virtualMachine.getState());
765             } catch (RemoteException e) {
766                 throw e.rethrowAsRuntimeException();
767             }
768         }
769         if (status == STATUS_STOPPED && !mVmRootPath.exists()) {
770             // A VM can quite happily keep running if its backing files have been deleted.
771             // But once it stops, it's gone forever.
772             synchronized (mLock) {
773                 dropVm();
774             }
775             return STATUS_DELETED;
776         }
777         return status;
778     }
779 
stateToStatus(@irtualMachineState int state)780     private int stateToStatus(@VirtualMachineState int state) {
781         switch (state) {
782             case VirtualMachineState.STARTING:
783             case VirtualMachineState.STARTED:
784             case VirtualMachineState.READY:
785             case VirtualMachineState.FINISHED:
786                 return STATUS_RUNNING;
787             case VirtualMachineState.NOT_STARTED:
788             case VirtualMachineState.DEAD:
789             default:
790                 return STATUS_STOPPED;
791         }
792     }
793 
794     // Throw an appropriate exception if we have a running VM, or the VM has been deleted.
795     @GuardedBy("mLock")
checkStopped()796     private void checkStopped() throws VirtualMachineException {
797         if (mWasDeleted || !mVmRootPath.exists()) {
798             throw new VirtualMachineException("VM has been deleted");
799         }
800         if (mVirtualMachine == null) {
801             return;
802         }
803         try {
804             if (stateToStatus(mVirtualMachine.getState()) != STATUS_STOPPED) {
805                 throw new VirtualMachineException("VM is not in stopped state");
806             }
807         } catch (RemoteException e) {
808             throw e.rethrowAsRuntimeException();
809         }
810         // It's stopped, but we still have a reference to it - we can fix that.
811         dropVm();
812     }
813 
814     /**
815      * This should only be called when we know our VM has stopped; we no longer need to hold a
816      * reference to it (this allows resources to be GC'd) and we no longer need to be informed of
817      * memory pressure.
818      */
819     @GuardedBy("mLock")
dropVm()820     private void dropVm() {
821         mContext.unregisterComponentCallbacks(mMemoryManagementCallbacks);
822         mVirtualMachine = null;
823     }
824 
825     /** If we have an IVirtualMachine in the running state return it, otherwise throw. */
826     @GuardedBy("mLock")
getRunningVm()827     private IVirtualMachine getRunningVm() throws VirtualMachineException {
828         try {
829             if (mVirtualMachine != null
830                     && stateToStatus(mVirtualMachine.getState()) == STATUS_RUNNING) {
831                 return mVirtualMachine;
832             } else {
833                 if (mWasDeleted || !mVmRootPath.exists()) {
834                     throw new VirtualMachineException("VM has been deleted");
835                 } else {
836                     throw new VirtualMachineException("VM is not in running state");
837                 }
838             }
839         } catch (RemoteException e) {
840             throw e.rethrowAsRuntimeException();
841         }
842     }
843 
844     /**
845      * Registers the callback object to get events from the virtual machine. If a callback was
846      * already registered, it is replaced with the new one.
847      *
848      * @hide
849      */
850     @SystemApi
setCallback( @onNull @allbackExecutor Executor executor, @NonNull VirtualMachineCallback callback)851     public void setCallback(
852             @NonNull @CallbackExecutor Executor executor,
853             @NonNull VirtualMachineCallback callback) {
854         synchronized (mCallbackLock) {
855             mCallback = callback;
856             mCallbackExecutor = executor;
857         }
858     }
859 
860     /**
861      * Clears the currently registered callback.
862      *
863      * @hide
864      */
865     @SystemApi
clearCallback()866     public void clearCallback() {
867         synchronized (mCallbackLock) {
868             mCallback = null;
869             mCallbackExecutor = null;
870         }
871     }
872 
873     /** Executes a callback on the callback executor. */
executeCallback(Consumer<VirtualMachineCallback> fn)874     private void executeCallback(Consumer<VirtualMachineCallback> fn) {
875         final VirtualMachineCallback callback;
876         final Executor executor;
877         synchronized (mCallbackLock) {
878             callback = mCallback;
879             executor = mCallbackExecutor;
880         }
881         if (callback == null || executor == null) {
882             return;
883         }
884         final long restoreToken = Binder.clearCallingIdentity();
885         try {
886             executor.execute(() -> fn.accept(callback));
887         } finally {
888             Binder.restoreCallingIdentity(restoreToken);
889         }
890     }
891 
892     private android.system.virtualizationservice.VirtualMachineConfig
createVirtualMachineConfigForRawFrom(VirtualMachineConfig vmConfig)893             createVirtualMachineConfigForRawFrom(VirtualMachineConfig vmConfig)
894                     throws IllegalStateException, IOException {
895         VirtualMachineRawConfig rawConfig = vmConfig.toVsRawConfig();
896 
897         // Handle input devices here
898         List<InputDevice> inputDevices = new ArrayList<>();
899         if (vmConfig.getCustomImageConfig() != null
900                 && rawConfig.displayConfig != null) {
901             if (vmConfig.getCustomImageConfig().useTouch()) {
902                 ParcelFileDescriptor[] pfds = ParcelFileDescriptor.createSocketPair();
903                 mTouchSock = pfds[0];
904                 InputDevice.SingleTouch t = new InputDevice.SingleTouch();
905                 t.width = rawConfig.displayConfig.width;
906                 t.height = rawConfig.displayConfig.height;
907                 t.pfd = pfds[1];
908                 inputDevices.add(InputDevice.singleTouch(t));
909             }
910             if (vmConfig.getCustomImageConfig().useKeyboard()) {
911                 ParcelFileDescriptor[] pfds = ParcelFileDescriptor.createSocketPair();
912                 mKeySock = pfds[0];
913                 InputDevice.Keyboard k = new InputDevice.Keyboard();
914                 k.pfd = pfds[1];
915                 inputDevices.add(InputDevice.keyboard(k));
916             }
917             if (vmConfig.getCustomImageConfig().useMouse()) {
918                 ParcelFileDescriptor[] pfds = ParcelFileDescriptor.createSocketPair();
919                 mMouseSock = pfds[0];
920                 InputDevice.Mouse m = new InputDevice.Mouse();
921                 m.pfd = pfds[1];
922                 inputDevices.add(InputDevice.mouse(m));
923             }
924         }
925         rawConfig.inputDevices = inputDevices.toArray(new InputDevice[0]);
926 
927         return android.system.virtualizationservice.VirtualMachineConfig.rawConfig(rawConfig);
928     }
929 
InputEvent(short type, short code, int value)930     private static record InputEvent(short type, short code, int value) {}
931 
932     /** @hide */
sendKeyEvent(KeyEvent event)933     public boolean sendKeyEvent(KeyEvent event) {
934         if (mKeySock == null) {
935             Log.d(TAG, "mKeySock == null");
936             return false;
937         }
938         // from include/uapi/linux/input-event-codes.h in the kernel.
939         short EV_SYN = 0x00;
940         short EV_KEY = 0x01;
941         short SYN_REPORT = 0x00;
942         boolean down = event.getAction() != MotionEvent.ACTION_UP;
943 
944         return writeEventsToSock(
945                 mKeySock,
946                 Arrays.asList(
947                         new InputEvent(EV_KEY, (short) event.getScanCode(), down ? 1 : 0),
948                         new InputEvent(EV_SYN, SYN_REPORT, 0)));
949     }
950 
951     /** @hide */
sendMouseEvent(MotionEvent event)952     public boolean sendMouseEvent(MotionEvent event) {
953         if (mMouseSock == null) {
954             Log.d(TAG, "mMouseSock == null");
955             return false;
956         }
957         // from include/uapi/linux/input-event-codes.h in the kernel.
958         short EV_SYN = 0x00;
959         short EV_REL = 0x02;
960         short EV_KEY = 0x01;
961         short REL_X = 0x00;
962         short REL_Y = 0x01;
963         short SYN_REPORT = 0x00;
964         switch (event.getAction()) {
965             case MotionEvent.ACTION_MOVE:
966                 int x = (int) event.getX();
967                 int y = (int) event.getY();
968                 return writeEventsToSock(
969                         mMouseSock,
970                         Arrays.asList(
971                                 new InputEvent(EV_REL, REL_X, x),
972                                 new InputEvent(EV_REL, REL_Y, y),
973                                 new InputEvent(EV_SYN, SYN_REPORT, 0)));
974             case MotionEvent.ACTION_BUTTON_PRESS:
975             case MotionEvent.ACTION_BUTTON_RELEASE:
976                 short BTN_LEFT = 0x110;
977                 short BTN_RIGHT = 0x111;
978                 short BTN_MIDDLE = 0x112;
979                 short keyCode;
980                 switch (event.getActionButton()) {
981                     case MotionEvent.BUTTON_PRIMARY:
982                         keyCode = BTN_LEFT;
983                         break;
984                     case MotionEvent.BUTTON_SECONDARY:
985                         keyCode = BTN_RIGHT;
986                         break;
987                     case MotionEvent.BUTTON_TERTIARY:
988                         keyCode = BTN_MIDDLE;
989                         break;
990                     default:
991                         Log.d(TAG, event.toString());
992                         return false;
993                 }
994                 return writeEventsToSock(
995                         mMouseSock,
996                         Arrays.asList(
997                                 new InputEvent(
998                                         EV_KEY,
999                                         keyCode,
1000                                         event.getAction() == MotionEvent.ACTION_BUTTON_PRESS
1001                                                 ? 1
1002                                                 : 0),
1003                                 new InputEvent(EV_SYN, SYN_REPORT, 0)));
1004             case MotionEvent.ACTION_SCROLL:
1005                 short REL_HWHEEL = 0x06;
1006                 short REL_WHEEL = 0x08;
1007                 int scrollX = (int) event.getAxisValue(MotionEvent.AXIS_HSCROLL);
1008                 int scrollY = (int) event.getAxisValue(MotionEvent.AXIS_VSCROLL);
1009                 boolean status = true;
1010                 if (scrollX != 0) {
1011                     status &=
1012                             writeEventsToSock(
1013                                     mMouseSock,
1014                                     Arrays.asList(
1015                                             new InputEvent(EV_REL, REL_HWHEEL, scrollX),
1016                                             new InputEvent(EV_SYN, SYN_REPORT, 0)));
1017                 } else if (scrollY != 0) {
1018                     status &=
1019                             writeEventsToSock(
1020                                     mMouseSock,
1021                                     Arrays.asList(
1022                                             new InputEvent(EV_REL, REL_WHEEL, scrollY),
1023                                             new InputEvent(EV_SYN, SYN_REPORT, 0)));
1024                 } else {
1025                     Log.d(TAG, event.toString());
1026                     return false;
1027                 }
1028                 return status;
1029             case MotionEvent.ACTION_UP:
1030             case MotionEvent.ACTION_DOWN:
1031                 // Ignored because it's handled by ACTION_BUTTON_PRESS and ACTION_BUTTON_RELEASE
1032                 return true;
1033             default:
1034                 Log.d(TAG, event.toString());
1035                 return false;
1036         }
1037     }
1038 
1039     /** @hide */
sendSingleTouchEvent(MotionEvent event)1040     public boolean sendSingleTouchEvent(MotionEvent event) {
1041         if (mTouchSock == null) {
1042             Log.d(TAG, "mTouchSock == null");
1043             return false;
1044         }
1045         // from include/uapi/linux/input-event-codes.h in the kernel.
1046         short EV_SYN = 0x00;
1047         short EV_ABS = 0x03;
1048         short EV_KEY = 0x01;
1049         short BTN_TOUCH = 0x14a;
1050         short ABS_X = 0x00;
1051         short ABS_Y = 0x01;
1052         short SYN_REPORT = 0x00;
1053 
1054         int x = (int) event.getX();
1055         int y = (int) event.getY();
1056         boolean down = event.getAction() != MotionEvent.ACTION_UP;
1057 
1058         return writeEventsToSock(
1059                 mTouchSock,
1060                 Arrays.asList(
1061                         new InputEvent(EV_ABS, ABS_X, x),
1062                         new InputEvent(EV_ABS, ABS_Y, y),
1063                         new InputEvent(EV_KEY, BTN_TOUCH, down ? 1 : 0),
1064                         new InputEvent(EV_SYN, SYN_REPORT, 0)));
1065     }
1066 
writeEventsToSock(ParcelFileDescriptor sock, List<InputEvent> evtList)1067     private boolean writeEventsToSock(ParcelFileDescriptor sock, List<InputEvent> evtList) {
1068         ByteBuffer byteBuffer =
1069                 ByteBuffer.allocate(8 /* (type: u16 + code: u16 + value: i32) */ * evtList.size());
1070         byteBuffer.clear();
1071         byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
1072         for (InputEvent e : evtList) {
1073             byteBuffer.putShort(e.type);
1074             byteBuffer.putShort(e.code);
1075             byteBuffer.putInt(e.value);
1076         }
1077         try {
1078             IoBridge.write(
1079                     sock.getFileDescriptor(), byteBuffer.array(), 0, byteBuffer.array().length);
1080         } catch (IOException e) {
1081             Log.d(TAG, "cannot send event", e);
1082             return false;
1083         }
1084         return true;
1085     }
1086 
1087     private android.system.virtualizationservice.VirtualMachineConfig
createVirtualMachineConfigForAppFrom( VirtualMachineConfig vmConfig, IVirtualizationService service)1088             createVirtualMachineConfigForAppFrom(
1089                     VirtualMachineConfig vmConfig, IVirtualizationService service)
1090                     throws RemoteException, IOException, VirtualMachineException {
1091         VirtualMachineAppConfig appConfig = vmConfig.toVsConfig(mContext.getPackageManager());
1092         appConfig.instanceImage = ParcelFileDescriptor.open(mInstanceFilePath, MODE_READ_WRITE);
1093         appConfig.name = mName;
1094         if (mInstanceIdPath != null) {
1095             appConfig.instanceId = Files.readAllBytes(mInstanceIdPath.toPath());
1096         } else {
1097             // FEATURE_LLPVM_CHANGES is disabled, instance_id is not used.
1098             appConfig.instanceId = new byte[64];
1099         }
1100         if (mEncryptedStoreFilePath != null) {
1101             appConfig.encryptedStorageImage =
1102                     ParcelFileDescriptor.open(mEncryptedStoreFilePath, MODE_READ_WRITE);
1103         }
1104 
1105         if (!vmConfig.getExtraApks().isEmpty()) {
1106             // Extra APKs were specified directly, rather than via config file.
1107             // We've already populated the file names for the extra APKs and IDSigs
1108             // (via setupExtraApks). But we also need to open the APK files and add
1109             // fds for them to the payload config.
1110             // This isn't needed when the extra APKs are specified in a config file -
1111             // then
1112             // Virtualization Manager opens them itself.
1113             List<ParcelFileDescriptor> extraApkFiles = new ArrayList<>(mExtraApks.size());
1114             for (ExtraApkSpec extraApk : mExtraApks) {
1115                 try {
1116                     extraApkFiles.add(ParcelFileDescriptor.open(extraApk.apk, MODE_READ_ONLY));
1117                 } catch (FileNotFoundException e) {
1118                     throw new VirtualMachineException("Failed to open extra APK", e);
1119                 }
1120             }
1121             appConfig.payload.getPayloadConfig().extraApks = extraApkFiles;
1122         }
1123 
1124         try {
1125             createIdSigsAndUpdateConfig(service, appConfig);
1126         } catch (FileNotFoundException e) {
1127             throw new VirtualMachineException("Failed to generate APK signature", e);
1128         }
1129         return android.system.virtualizationservice.VirtualMachineConfig.appConfig(appConfig);
1130     }
1131 
1132     /**
1133      * Runs this virtual machine. The returning of this method however doesn't mean that the VM has
1134      * actually started running or the OS has booted there. Such events can be notified by
1135      * registering a callback using {@link #setCallback} before calling {@code run()}.
1136      *
1137      * <p>NOTE: This method may block and should not be called on the main thread.
1138      *
1139      * @throws VirtualMachineException if the virtual machine is not stopped or could not be
1140      *     started.
1141      * @hide
1142      */
1143     @SystemApi
1144     @WorkerThread
1145     @RequiresPermission(MANAGE_VIRTUAL_MACHINE_PERMISSION)
run()1146     public void run() throws VirtualMachineException {
1147         synchronized (mLock) {
1148             checkStopped();
1149 
1150             try {
1151                 mIdsigFilePath.createNewFile();
1152                 for (ExtraApkSpec extraApk : mExtraApks) {
1153                     extraApk.idsig.createNewFile();
1154                 }
1155             } catch (IOException e) {
1156                 // If the file already exists, exception is not thrown.
1157                 throw new VirtualMachineException("Failed to create APK signature file", e);
1158             }
1159 
1160             IVirtualizationService service = mVirtualizationService.getBinder();
1161 
1162             try {
1163                 if (mConnectVmConsole) {
1164                     createPtyConsole();
1165                 }
1166 
1167                 if (mVmOutputCaptured) {
1168                     createVmOutputPipes();
1169                 }
1170 
1171                 if (mVmConsoleInputSupported) {
1172                     createVmInputPipes();
1173                 }
1174 
1175                 ParcelFileDescriptor consoleOutFd = null;
1176                 if (mConnectVmConsole && mVmOutputCaptured) {
1177                     // If we are enabling output pipes AND the host console, then we tee the console
1178                     // output to both.
1179                     ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe();
1180                     mTeeConsoleOutReader = pipe[0];
1181                     mTeeConsoleOutWriter = pipe[1];
1182                     consoleOutFd = mTeeConsoleOutWriter;
1183                     TeeWorker tee =
1184                             new TeeWorker(
1185                                     mName + " console",
1186                                     new FileInputStream(mTeeConsoleOutReader.getFileDescriptor()),
1187                                     List.of(
1188                                             new FileOutputStream(mPtyFd.getFileDescriptor()),
1189                                             new FileOutputStream(
1190                                                     mConsoleOutWriter.getFileDescriptor())));
1191                     // If the VM is stopped then the tee worker thread would get an EOF or read()
1192                     // error which would tear down itself.
1193                     mConsoleExecutor.execute(tee);
1194                 } else if (mConnectVmConsole) {
1195                     consoleOutFd = mPtyFd;
1196                 } else if (mVmOutputCaptured) {
1197                     consoleOutFd = mConsoleOutWriter;
1198                 }
1199 
1200                 ParcelFileDescriptor consoleInFd = null;
1201                 if (mConnectVmConsole) {
1202                     consoleInFd = mPtyFd;
1203                 } else if (mVmConsoleInputSupported) {
1204                     consoleInFd = mConsoleInReader;
1205                 }
1206 
1207                 VirtualMachineConfig vmConfig = getConfig();
1208                 android.system.virtualizationservice.VirtualMachineConfig vmConfigParcel =
1209                         vmConfig.getCustomImageConfig() != null
1210                                 ? createVirtualMachineConfigForRawFrom(vmConfig)
1211                                 : createVirtualMachineConfigForAppFrom(vmConfig, service);
1212 
1213                 mVirtualMachine =
1214                         service.createVm(vmConfigParcel, consoleOutFd, consoleInFd, mLogWriter);
1215                 mVirtualMachine.registerCallback(new CallbackTranslator(service));
1216                 mContext.registerComponentCallbacks(mMemoryManagementCallbacks);
1217                 mVirtualMachine.start();
1218             } catch (IOException e) {
1219                 throw new VirtualMachineException("failed to persist files", e);
1220             } catch (IllegalStateException | ServiceSpecificException e) {
1221                 throw new VirtualMachineException(e);
1222             } catch (RemoteException e) {
1223                 throw e.rethrowAsRuntimeException();
1224             }
1225         }
1226     }
1227 
createIdSigsAndUpdateConfig( IVirtualizationService service, VirtualMachineAppConfig appConfig)1228     private void createIdSigsAndUpdateConfig(
1229             IVirtualizationService service, VirtualMachineAppConfig appConfig)
1230             throws RemoteException, FileNotFoundException {
1231         // Fill the idsig file by hashing the apk
1232         service.createOrUpdateIdsigFile(
1233                 appConfig.apk, ParcelFileDescriptor.open(mIdsigFilePath, MODE_READ_WRITE));
1234 
1235         for (ExtraApkSpec extraApk : mExtraApks) {
1236             service.createOrUpdateIdsigFile(
1237                     ParcelFileDescriptor.open(extraApk.apk, MODE_READ_ONLY),
1238                     ParcelFileDescriptor.open(extraApk.idsig, MODE_READ_WRITE));
1239         }
1240 
1241         // Re-open idsig files in read-only mode
1242         appConfig.idsig = ParcelFileDescriptor.open(mIdsigFilePath, MODE_READ_ONLY);
1243         List<ParcelFileDescriptor> extraIdsigs = new ArrayList<>();
1244         for (ExtraApkSpec extraApk : mExtraApks) {
1245             extraIdsigs.add(ParcelFileDescriptor.open(extraApk.idsig, MODE_READ_ONLY));
1246         }
1247         appConfig.extraIdsigs = extraIdsigs;
1248     }
1249 
1250     @GuardedBy("mLock")
createVmOutputPipes()1251     private void createVmOutputPipes() throws VirtualMachineException {
1252         try {
1253             if (mConsoleOutReader == null || mConsoleOutWriter == null) {
1254                 ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe();
1255                 mConsoleOutReader = pipe[0];
1256                 mConsoleOutWriter = pipe[1];
1257             }
1258 
1259             if (mLogReader == null || mLogWriter == null) {
1260                 ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe();
1261                 mLogReader = pipe[0];
1262                 mLogWriter = pipe[1];
1263             }
1264         } catch (IOException e) {
1265             throw new VirtualMachineException("Failed to create output stream for VM", e);
1266         }
1267     }
1268 
1269     @GuardedBy("mLock")
createVmInputPipes()1270     private void createVmInputPipes() throws VirtualMachineException {
1271         try {
1272             if (mConsoleInReader == null || mConsoleInWriter == null) {
1273                 if (mConnectVmConsole) {
1274                     // If we are enabling input pipes AND the host console, then we should just use
1275                     // the host pty peer end as the console write end.
1276                     createPtyConsole();
1277                     mConsoleInReader = mPtyFd.dup();
1278                     mConsoleInWriter = mPtsFd.dup();
1279                 } else {
1280                     ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe();
1281                     mConsoleInReader = pipe[0];
1282                     mConsoleInWriter = pipe[1];
1283                 }
1284             }
1285         } catch (IOException e) {
1286             throw new VirtualMachineException("Failed to create input stream for VM", e);
1287         }
1288     }
1289 
1290     @FunctionalInterface
1291     private static interface OpenPtyCallback {
apply(FileDescriptor mfd, FileDescriptor sfd, byte[] name)1292         public void apply(FileDescriptor mfd, FileDescriptor sfd, byte[] name);
1293     }
1294 
1295     // Opens a pty and set the master end to raw mode and O_NONBLOCK.
nativeOpenPtyRawNonblock(OpenPtyCallback resultCallback)1296     private static native void nativeOpenPtyRawNonblock(OpenPtyCallback resultCallback)
1297             throws IOException;
1298 
1299     @GuardedBy("mLock")
createPtyConsole()1300     private void createPtyConsole() throws VirtualMachineException {
1301         if (mPtyFd != null && mPtsFd != null) {
1302             return;
1303         }
1304         List<FileDescriptor> fd = new ArrayList<>(2);
1305         StringBuilder nameBuilder = new StringBuilder();
1306         try {
1307             try {
1308                 nativeOpenPtyRawNonblock(
1309                         (FileDescriptor mfd, FileDescriptor sfd, byte[] ptsName) -> {
1310                             fd.add(mfd);
1311                             fd.add(sfd);
1312                             nameBuilder.append(new String(ptsName, StandardCharsets.UTF_8));
1313                         });
1314             } catch (Exception e) {
1315                 fd.forEach(IoUtils::closeQuietly);
1316                 throw e;
1317             }
1318         } catch (IOException e) {
1319             throw new VirtualMachineException(
1320                     "Failed to create host console to connect to the VM console", e);
1321         }
1322         mPtyFd = new ParcelFileDescriptor(fd.get(0));
1323         mPtsFd = new ParcelFileDescriptor(fd.get(1));
1324         mPtsName = nameBuilder.toString();
1325         Log.d(TAG, "Serial console device: " + mPtsName);
1326     }
1327 
1328     /**
1329      * Returns the name of the peer end (ptsname) of the host console. The host console is only
1330      * available if the {@link VirtualMachineConfig} specifies that a host console should
1331      * {@linkplain VirtualMachineConfig#isConnectVmConsole connect} to the VM console.
1332      *
1333      * @throws VirtualMachineException if the host pseudoterminal could not be created, or
1334      *     connecting to the VM console is not enabled.
1335      * @hide
1336      */
1337     @NonNull
getHostConsoleName()1338     public String getHostConsoleName() throws VirtualMachineException {
1339         if (!mConnectVmConsole) {
1340             throw new VirtualMachineException("Host console is not enabled");
1341         }
1342         synchronized (mLock) {
1343             createPtyConsole();
1344             return mPtsName;
1345         }
1346     }
1347 
1348     /**
1349      * Returns the stream object representing the console output from the virtual machine. The
1350      * console output is only available if the {@link VirtualMachineConfig} specifies that it should
1351      * be {@linkplain VirtualMachineConfig#isVmOutputCaptured captured}.
1352      *
1353      * <p>If you turn on output capture, you must consume data from {@code getConsoleOutput} -
1354      * because otherwise the code in the VM may get blocked when the pipe buffer fills up.
1355      *
1356      * <p>NOTE: This method may block and should not be called on the main thread.
1357      *
1358      * @throws VirtualMachineException if the stream could not be created, or capturing is turned
1359      *     off.
1360      * @hide
1361      */
1362     @SystemApi
1363     @WorkerThread
1364     @NonNull
getConsoleOutput()1365     public InputStream getConsoleOutput() throws VirtualMachineException {
1366         if (!mVmOutputCaptured) {
1367             throw new VirtualMachineException("Capturing vm outputs is turned off");
1368         }
1369         synchronized (mLock) {
1370             createVmOutputPipes();
1371             return new FileInputStream(mConsoleOutReader.getFileDescriptor());
1372         }
1373     }
1374 
1375     /**
1376      * Returns the stream object representing the console input to the virtual machine. The console
1377      * input is only available if the {@link VirtualMachineConfig} specifies that it should be
1378      * {@linkplain VirtualMachineConfig#isVmConsoleInputSupported supported}.
1379      *
1380      * <p>NOTE: This method may block and should not be called on the main thread.
1381      *
1382      * @throws VirtualMachineException if the stream could not be created, or console input is not
1383      *     supported.
1384      * @hide
1385      */
1386     @TestApi
1387     @WorkerThread
1388     @NonNull
getConsoleInput()1389     public OutputStream getConsoleInput() throws VirtualMachineException {
1390         if (!mVmConsoleInputSupported) {
1391             throw new VirtualMachineException("VM console input is not supported");
1392         }
1393         synchronized (mLock) {
1394             createVmInputPipes();
1395             return new FileOutputStream(mConsoleInWriter.getFileDescriptor());
1396         }
1397     }
1398 
1399 
1400     /**
1401      * Returns the stream object representing the log output from the virtual machine. The log
1402      * output is only available if the VirtualMachineConfig specifies that it should be {@linkplain
1403      * VirtualMachineConfig#isVmOutputCaptured captured}.
1404      *
1405      * <p>If you turn on output capture, you must consume data from {@code getLogOutput} - because
1406      * otherwise the code in the VM may get blocked when the pipe buffer fills up.
1407      *
1408      * <p>NOTE: This method may block and should not be called on the main thread.
1409      *
1410      * @throws VirtualMachineException if the stream could not be created, or capturing is turned
1411      *     off.
1412      * @hide
1413      */
1414     @SystemApi
1415     @WorkerThread
1416     @NonNull
getLogOutput()1417     public InputStream getLogOutput() throws VirtualMachineException {
1418         if (!mVmOutputCaptured) {
1419             throw new VirtualMachineException("Capturing vm outputs is turned off");
1420         }
1421         synchronized (mLock) {
1422             createVmOutputPipes();
1423             return new FileInputStream(mLogReader.getFileDescriptor());
1424         }
1425     }
1426 
1427     /**
1428      * Stops this virtual machine. Stopping a virtual machine is like pulling the plug on a real
1429      * computer; the machine halts immediately. Software running on the virtual machine is not
1430      * notified of the event. Writes to {@linkplain
1431      * VirtualMachineConfig.Builder#setEncryptedStorageBytes encrypted storage} might not be
1432      * persisted, and the instance might be left in an inconsistent state.
1433      *
1434      * <p>For a graceful shutdown, you could request the payload to call {@code exit()}, e.g. via a
1435      * {@linkplain #connectToVsockServer binder request}, and wait for {@link
1436      * VirtualMachineCallback#onPayloadFinished} to be called.
1437      *
1438      * <p>A stopped virtual machine can be re-started by calling {@link #run()}.
1439      *
1440      * <p>NOTE: This method may block and should not be called on the main thread.
1441      *
1442      * @throws VirtualMachineException if the virtual machine is not running or could not be
1443      *     stopped.
1444      * @hide
1445      */
1446     @SystemApi
1447     @WorkerThread
stop()1448     public void stop() throws VirtualMachineException {
1449         synchronized (mLock) {
1450             if (mVirtualMachine == null) {
1451                 throw new VirtualMachineException("VM is not running");
1452             }
1453             try {
1454                 mVirtualMachine.stop();
1455                 dropVm();
1456             } catch (RemoteException e) {
1457                 throw e.rethrowAsRuntimeException();
1458             } catch (ServiceSpecificException e) {
1459                 throw new VirtualMachineException(e);
1460             }
1461         }
1462     }
1463 
1464     /**
1465      * Stops this virtual machine, if it is running.
1466      *
1467      * <p>NOTE: This method may block and should not be called on the main thread.
1468      *
1469      * @see #stop()
1470      * @hide
1471      */
1472     @SystemApi
1473     @WorkerThread
1474     @Override
close()1475     public void close() {
1476         synchronized (mLock) {
1477             if (mVirtualMachine == null) {
1478                 return;
1479             }
1480             try {
1481                 if (stateToStatus(mVirtualMachine.getState()) == STATUS_RUNNING) {
1482                     mVirtualMachine.stop();
1483                     dropVm();
1484                 }
1485             } catch (RemoteException e) {
1486                 throw e.rethrowAsRuntimeException();
1487             } catch (ServiceSpecificException e) {
1488                 // Deliberately ignored; this almost certainly means the VM exited just as
1489                 // we tried to stop it.
1490                 Log.i(TAG, "Ignoring error on close()", e);
1491             }
1492         }
1493     }
1494 
deleteRecursively(File dir)1495     private static void deleteRecursively(File dir) throws IOException {
1496         // Note: This doesn't follow symlinks, which is important. Instead they are just deleted
1497         // (and Files.delete deletes the link not the target).
1498         Files.walkFileTree(dir.toPath(), new SimpleFileVisitor<>() {
1499             @Override
1500             public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
1501                     throws IOException {
1502                 Files.delete(file);
1503                 return FileVisitResult.CONTINUE;
1504             }
1505 
1506             @Override
1507             public FileVisitResult postVisitDirectory(Path dir, IOException e) throws IOException {
1508                 // Directory is deleted after we've visited (deleted) all its contents, so it
1509                 // should be empty by now.
1510                 Files.delete(dir);
1511                 return FileVisitResult.CONTINUE;
1512             }
1513         });
1514     }
1515 
1516     /**
1517      * Changes the config of this virtual machine to a new one. This can be used to adjust things
1518      * like the number of CPU and size of the RAM, depending on the situation (e.g. the size of the
1519      * application to run on the virtual machine, etc.)
1520      *
1521      * <p>The new config must be {@linkplain VirtualMachineConfig#isCompatibleWith compatible with}
1522      * the existing config.
1523      *
1524      * <p>NOTE: This method may block and should not be called on the main thread.
1525      *
1526      * @return the old config
1527      * @throws VirtualMachineException if the virtual machine is not stopped, or the new config is
1528      *     incompatible.
1529      * @hide
1530      */
1531     @SystemApi
1532     @WorkerThread
1533     @NonNull
setConfig(@onNull VirtualMachineConfig newConfig)1534     public VirtualMachineConfig setConfig(@NonNull VirtualMachineConfig newConfig)
1535             throws VirtualMachineException {
1536         synchronized (mLock) {
1537             VirtualMachineConfig oldConfig = mConfig;
1538             if (!oldConfig.isCompatibleWith(newConfig)) {
1539                 throw new VirtualMachineException("incompatible config");
1540             }
1541             checkStopped();
1542 
1543             if (oldConfig != newConfig) {
1544                 // Delete any existing file before recreating; that ensures any
1545                 // VirtualMachineDescriptor that refers to the old file does not see the new config.
1546                 mConfigFilePath.delete();
1547                 newConfig.serialize(mConfigFilePath);
1548                 mConfig = newConfig;
1549             }
1550             return oldConfig;
1551         }
1552     }
1553 
1554     @Nullable
nativeConnectToVsockServer(IBinder vmBinder, int port)1555     private static native IBinder nativeConnectToVsockServer(IBinder vmBinder, int port);
1556 
1557     /**
1558      * Connect to a VM's binder service via vsock and return the root IBinder object. Guest VMs are
1559      * expected to set up vsock servers in their payload. After the host app receives the {@link
1560      * VirtualMachineCallback#onPayloadReady}, it can use this method to establish a connection to
1561      * the guest VM.
1562      *
1563      * <p>NOTE: This method may block and should not be called on the main thread.
1564      *
1565      * @throws VirtualMachineException if the virtual machine is not running or the connection
1566      *     failed.
1567      * @hide
1568      */
1569     @SystemApi
1570     @WorkerThread
1571     @NonNull
connectToVsockServer( @ntRangefrom = MIN_VSOCK_PORT, to = MAX_VSOCK_PORT) long port)1572     public IBinder connectToVsockServer(
1573             @IntRange(from = MIN_VSOCK_PORT, to = MAX_VSOCK_PORT) long port)
1574             throws VirtualMachineException {
1575 
1576         synchronized (mLock) {
1577             IBinder iBinder =
1578                     nativeConnectToVsockServer(getRunningVm().asBinder(), validatePort(port));
1579             if (iBinder == null) {
1580                 throw new VirtualMachineException("Failed to connect to vsock server");
1581             }
1582             return iBinder;
1583         }
1584     }
1585 
1586     /**
1587      * Opens a vsock connection to the VM on the given port.
1588      *
1589      * <p>The caller is responsible for closing the returned {@code ParcelFileDescriptor}.
1590      *
1591      * <p>NOTE: This method may block and should not be called on the main thread.
1592      *
1593      * @throws VirtualMachineException if connecting fails.
1594      * @hide
1595      */
1596     @SystemApi
1597     @WorkerThread
1598     @NonNull
connectVsock( @ntRangefrom = MIN_VSOCK_PORT, to = MAX_VSOCK_PORT) long port)1599     public ParcelFileDescriptor connectVsock(
1600             @IntRange(from = MIN_VSOCK_PORT, to = MAX_VSOCK_PORT) long port)
1601             throws VirtualMachineException {
1602         synchronized (mLock) {
1603             try {
1604                 return getRunningVm().connectVsock(validatePort(port));
1605             } catch (RemoteException e) {
1606                 throw e.rethrowAsRuntimeException();
1607             } catch (ServiceSpecificException e) {
1608                 throw new VirtualMachineException(e);
1609             }
1610         }
1611     }
1612 
validatePort(long port)1613     private int validatePort(long port) {
1614         // Ports below 1024 are "privileged" (payload code can't bind to these), and port numbers
1615         // are 32-bit unsigned numbers at the OS level, even though we pass them as 32-bit signed
1616         // numbers internally.
1617         if (port < MIN_VSOCK_PORT || port > MAX_VSOCK_PORT) {
1618             throw new IllegalArgumentException("Bad port " + port);
1619         }
1620         return (int) port;
1621     }
1622 
1623     /**
1624      * Returns the root directory where all files related to this {@link VirtualMachine} (e.g.
1625      * {@code instance.img}, {@code apk.idsig}, etc) are stored.
1626      *
1627      * @hide
1628      */
1629     @TestApi
1630     @NonNull
getRootDir()1631     public File getRootDir() {
1632         return mVmRootPath;
1633     }
1634 
1635     /**
1636      * Enables the VM to request attestation in testing mode.
1637      *
1638      * <p>This function provisions a key pair for the VM attestation testing, a fake certificate
1639      * will be associated to the fake key pair when the VM requests attestation in testing mode.
1640      *
1641      * <p>The provisioned key pair can only be used in subsequent calls to {@link
1642      * AVmPayload_requestAttestationForTesting} within a running VM.
1643      *
1644      * @hide
1645      */
1646     @TestApi
1647     @RequiresPermission(USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION)
1648     @FlaggedApi(Flags.FLAG_AVF_V_TEST_APIS)
enableTestAttestation()1649     public void enableTestAttestation() throws VirtualMachineException {
1650         try {
1651             mVirtualizationService.getBinder().enableTestAttestation();
1652         } catch (RemoteException e) {
1653             throw e.rethrowAsRuntimeException();
1654         }
1655     }
1656 
1657     /**
1658      * Captures the current state of the VM in a {@link VirtualMachineDescriptor} instance. The VM
1659      * needs to be stopped to avoid inconsistency in its state representation.
1660      *
1661      * <p>The state of the VM is not actually copied until {@link
1662      * VirtualMachineManager#importFromDescriptor} is called. It is recommended that the VM not be
1663      * started until that operation is complete.
1664      *
1665      * <p>NOTE: This method may block and should not be called on the main thread.
1666      *
1667      * @return a {@link VirtualMachineDescriptor} instance that represents the VM's state.
1668      * @throws VirtualMachineException if the virtual machine is not stopped, or the state could not
1669      *     be captured.
1670      * @hide
1671      */
1672     @SystemApi
1673     @WorkerThread
1674     @NonNull
toDescriptor()1675     public VirtualMachineDescriptor toDescriptor() throws VirtualMachineException {
1676         synchronized (mLock) {
1677             checkStopped();
1678             try {
1679                 return new VirtualMachineDescriptor(
1680                         ParcelFileDescriptor.open(mConfigFilePath, MODE_READ_ONLY),
1681                         mInstanceIdPath != null
1682                                 ? ParcelFileDescriptor.open(mInstanceIdPath, MODE_READ_ONLY)
1683                                 : null,
1684                         ParcelFileDescriptor.open(mInstanceFilePath, MODE_READ_ONLY),
1685                         mEncryptedStoreFilePath != null
1686                                 ? ParcelFileDescriptor.open(mEncryptedStoreFilePath, MODE_READ_ONLY)
1687                                 : null);
1688             } catch (IOException e) {
1689                 throw new VirtualMachineException(e);
1690             }
1691         }
1692     }
1693 
1694     @Override
toString()1695     public String toString() {
1696         VirtualMachineConfig config = getConfig();
1697         String payloadConfigPath = config.getPayloadConfigPath();
1698         String payloadBinaryName = config.getPayloadBinaryName();
1699 
1700         StringBuilder result = new StringBuilder();
1701         result.append("VirtualMachine(")
1702                 .append("name:")
1703                 .append(getName())
1704                 .append(", ");
1705         if (payloadBinaryName != null) {
1706             result.append("payload:").append(payloadBinaryName).append(", ");
1707         }
1708         if (payloadConfigPath != null) {
1709             result.append("config:")
1710                     .append(payloadConfigPath)
1711                     .append(", ");
1712         }
1713         result.append("package: ")
1714                 .append(mPackageName)
1715                 .append(")");
1716         return result.toString();
1717     }
1718 
1719     /**
1720      * Reads the payload config inside the application, parses extra APK information, and then
1721      * creates corresponding idsig file paths.
1722      */
setupExtraApks( @onNull Context context, @NonNull VirtualMachineConfig config, @NonNull File vmDir)1723     private static List<ExtraApkSpec> setupExtraApks(
1724             @NonNull Context context, @NonNull VirtualMachineConfig config, @NonNull File vmDir)
1725             throws VirtualMachineException {
1726         String configPath = config.getPayloadConfigPath();
1727         List<String> extraApks = config.getExtraApks();
1728         if (configPath != null) {
1729             return setupExtraApksFromConfigFile(context, vmDir, configPath);
1730         } else if (!extraApks.isEmpty()) {
1731             return setupExtraApksFromList(context, vmDir, extraApks);
1732         } else {
1733             return Collections.emptyList();
1734         }
1735     }
1736 
setupExtraApksFromConfigFile( Context context, File vmDir, String configPath)1737     private static List<ExtraApkSpec> setupExtraApksFromConfigFile(
1738             Context context, File vmDir, String configPath) throws VirtualMachineException {
1739         try (ZipFile zipFile = new ZipFile(context.getPackageCodePath())) {
1740             InputStream inputStream = zipFile.getInputStream(zipFile.getEntry(configPath));
1741             List<String> apkList =
1742                     parseExtraApkListFromPayloadConfig(
1743                             new JsonReader(new InputStreamReader(inputStream)));
1744 
1745             List<ExtraApkSpec> extraApks = new ArrayList<>(apkList.size());
1746             for (int i = 0; i < apkList.size(); ++i) {
1747                 extraApks.add(
1748                         new ExtraApkSpec(
1749                                 new File(apkList.get(i)),
1750                                 new File(vmDir, EXTRA_IDSIG_FILE_PREFIX + i)));
1751             }
1752 
1753             return extraApks;
1754         } catch (IOException e) {
1755             throw new VirtualMachineException("Couldn't parse extra apks from the vm config", e);
1756         }
1757     }
1758 
parseExtraApkListFromPayloadConfig(JsonReader reader)1759     private static List<String> parseExtraApkListFromPayloadConfig(JsonReader reader)
1760             throws VirtualMachineException {
1761         /*
1762          * JSON schema from packages/modules/Virtualization/microdroid/payload/config/src/lib.rs:
1763          *
1764          * <p>{ "extra_apks": [ { "path": "/system/app/foo.apk", }, ... ], ... }
1765          */
1766         try {
1767             List<String> apks = new ArrayList<>();
1768 
1769             reader.beginObject();
1770             while (reader.hasNext()) {
1771                 if (reader.nextName().equals("extra_apks")) {
1772                     reader.beginArray();
1773                     while (reader.hasNext()) {
1774                         reader.beginObject();
1775                         String name = reader.nextName();
1776                         if (name.equals("path")) {
1777                             apks.add(reader.nextString());
1778                         } else {
1779                             reader.skipValue();
1780                         }
1781                         reader.endObject();
1782                     }
1783                     reader.endArray();
1784                 } else {
1785                     reader.skipValue();
1786                 }
1787             }
1788             reader.endObject();
1789             return apks;
1790         } catch (IOException e) {
1791             throw new VirtualMachineException(e);
1792         }
1793     }
1794 
setupExtraApksFromList( Context context, File vmDir, List<String> extraApkInfo)1795     private static List<ExtraApkSpec> setupExtraApksFromList(
1796             Context context, File vmDir, List<String> extraApkInfo) throws VirtualMachineException {
1797         int count = extraApkInfo.size();
1798         List<ExtraApkSpec> extraApks = new ArrayList<>(count);
1799         for (int i = 0; i < count; i++) {
1800             String packageName = extraApkInfo.get(i);
1801             ApplicationInfo appInfo;
1802             try {
1803                 appInfo =
1804                         context.getPackageManager()
1805                                 .getApplicationInfo(
1806                                         packageName, PackageManager.ApplicationInfoFlags.of(0));
1807             } catch (PackageManager.NameNotFoundException e) {
1808                 throw new VirtualMachineException("Extra APK package not found", e);
1809             }
1810 
1811             extraApks.add(
1812                     new ExtraApkSpec(
1813                             new File(appInfo.sourceDir),
1814                             new File(vmDir, EXTRA_IDSIG_FILE_PREFIX + i)));
1815         }
1816         return extraApks;
1817     }
1818 
importInstanceIdFrom(@onNull ParcelFileDescriptor instanceIdFd)1819     private void importInstanceIdFrom(@NonNull ParcelFileDescriptor instanceIdFd)
1820             throws VirtualMachineException {
1821         try (FileChannel idOutput = new FileOutputStream(mInstanceIdPath).getChannel();
1822                 FileChannel idInput = new AutoCloseInputStream(instanceIdFd).getChannel()) {
1823             idOutput.transferFrom(idInput, /*position=*/ 0, idInput.size());
1824         } catch (IOException e) {
1825             throw new VirtualMachineException("failed to copy instance_id", e);
1826         }
1827     }
1828 
importInstanceFrom(@onNull ParcelFileDescriptor instanceFd)1829     private void importInstanceFrom(@NonNull ParcelFileDescriptor instanceFd)
1830             throws VirtualMachineException {
1831         try (FileChannel instance = new FileOutputStream(mInstanceFilePath).getChannel();
1832                 FileChannel instanceInput = new AutoCloseInputStream(instanceFd).getChannel()) {
1833             instance.transferFrom(instanceInput, /*position=*/ 0, instanceInput.size());
1834         } catch (IOException e) {
1835             throw new VirtualMachineException("failed to transfer instance image", e);
1836         }
1837     }
1838 
importEncryptedStoreFrom(@onNull ParcelFileDescriptor encryptedStoreFd)1839     private void importEncryptedStoreFrom(@NonNull ParcelFileDescriptor encryptedStoreFd)
1840             throws VirtualMachineException {
1841         try (FileChannel storeOutput = new FileOutputStream(mEncryptedStoreFilePath).getChannel();
1842                 FileChannel storeInput = new AutoCloseInputStream(encryptedStoreFd).getChannel()) {
1843             storeOutput.transferFrom(storeInput, /*position=*/ 0, storeInput.size());
1844         } catch (IOException e) {
1845             throw new VirtualMachineException("failed to transfer encryptedstore image", e);
1846         }
1847     }
1848 
1849     /** Map the raw AIDL (& binder) callbacks to what the client expects. */
1850     private class CallbackTranslator extends IVirtualMachineCallback.Stub {
1851         private final IVirtualizationService mService;
1852         private final DeathRecipient mDeathRecipient;
1853 
1854         // The VM should only be observed to die once
1855         private final AtomicBoolean mOnDiedCalled = new AtomicBoolean(false);
1856 
CallbackTranslator(IVirtualizationService service)1857         public CallbackTranslator(IVirtualizationService service) throws RemoteException {
1858             this.mService = service;
1859             this.mDeathRecipient = () -> reportStopped(STOP_REASON_VIRTUALIZATION_SERVICE_DIED);
1860             service.asBinder().linkToDeath(mDeathRecipient, 0);
1861         }
1862 
1863         @Override
onPayloadStarted(int cid)1864         public void onPayloadStarted(int cid) {
1865             executeCallback((cb) -> cb.onPayloadStarted(VirtualMachine.this));
1866         }
1867 
1868         @Override
onPayloadReady(int cid)1869         public void onPayloadReady(int cid) {
1870             executeCallback((cb) -> cb.onPayloadReady(VirtualMachine.this));
1871         }
1872 
1873         @Override
onPayloadFinished(int cid, int exitCode)1874         public void onPayloadFinished(int cid, int exitCode) {
1875             executeCallback((cb) -> cb.onPayloadFinished(VirtualMachine.this, exitCode));
1876         }
1877 
1878         @Override
onError(int cid, int errorCode, String message)1879         public void onError(int cid, int errorCode, String message) {
1880             int translatedError = getTranslatedError(errorCode);
1881             executeCallback((cb) -> cb.onError(VirtualMachine.this, translatedError, message));
1882         }
1883 
1884         @Override
onDied(int cid, int reason)1885         public void onDied(int cid, int reason) {
1886             int translatedReason = getTranslatedReason(reason);
1887             reportStopped(translatedReason);
1888             mService.asBinder().unlinkToDeath(mDeathRecipient, 0);
1889         }
1890 
reportStopped(@irtualMachineCallback.StopReason int reason)1891         private void reportStopped(@VirtualMachineCallback.StopReason int reason) {
1892             if (mOnDiedCalled.compareAndSet(false, true)) {
1893                 executeCallback((cb) -> cb.onStopped(VirtualMachine.this, reason));
1894             }
1895         }
1896 
1897         @VirtualMachineCallback.ErrorCode
getTranslatedError(int reason)1898         private int getTranslatedError(int reason) {
1899             switch (reason) {
1900                 case ErrorCode.PAYLOAD_VERIFICATION_FAILED:
1901                     return ERROR_PAYLOAD_VERIFICATION_FAILED;
1902                 case ErrorCode.PAYLOAD_CHANGED:
1903                     return ERROR_PAYLOAD_CHANGED;
1904                 case ErrorCode.PAYLOAD_INVALID_CONFIG:
1905                     return ERROR_PAYLOAD_INVALID_CONFIG;
1906                 default:
1907                     return ERROR_UNKNOWN;
1908             }
1909         }
1910 
1911         @VirtualMachineCallback.StopReason
getTranslatedReason(int reason)1912         private int getTranslatedReason(int reason) {
1913             switch (reason) {
1914                 case DeathReason.INFRASTRUCTURE_ERROR:
1915                     return STOP_REASON_INFRASTRUCTURE_ERROR;
1916                 case DeathReason.KILLED:
1917                     return STOP_REASON_KILLED;
1918                 case DeathReason.SHUTDOWN:
1919                     return STOP_REASON_SHUTDOWN;
1920                 case DeathReason.START_FAILED:
1921                     return STOP_REASON_START_FAILED;
1922                 case DeathReason.REBOOT:
1923                     return STOP_REASON_REBOOT;
1924                 case DeathReason.CRASH:
1925                     return STOP_REASON_CRASH;
1926                 case DeathReason.PVM_FIRMWARE_PUBLIC_KEY_MISMATCH:
1927                     return STOP_REASON_PVM_FIRMWARE_PUBLIC_KEY_MISMATCH;
1928                 case DeathReason.PVM_FIRMWARE_INSTANCE_IMAGE_CHANGED:
1929                     return STOP_REASON_PVM_FIRMWARE_INSTANCE_IMAGE_CHANGED;
1930                 case DeathReason.MICRODROID_FAILED_TO_CONNECT_TO_VIRTUALIZATION_SERVICE:
1931                     return STOP_REASON_MICRODROID_FAILED_TO_CONNECT_TO_VIRTUALIZATION_SERVICE;
1932                 case DeathReason.MICRODROID_PAYLOAD_HAS_CHANGED:
1933                     return STOP_REASON_MICRODROID_PAYLOAD_HAS_CHANGED;
1934                 case DeathReason.MICRODROID_PAYLOAD_VERIFICATION_FAILED:
1935                     return STOP_REASON_MICRODROID_PAYLOAD_VERIFICATION_FAILED;
1936                 case DeathReason.MICRODROID_INVALID_PAYLOAD_CONFIG:
1937                     return STOP_REASON_MICRODROID_INVALID_PAYLOAD_CONFIG;
1938                 case DeathReason.MICRODROID_UNKNOWN_RUNTIME_ERROR:
1939                     return STOP_REASON_MICRODROID_UNKNOWN_RUNTIME_ERROR;
1940                 case DeathReason.HANGUP:
1941                     return STOP_REASON_HANGUP;
1942                 default:
1943                     return STOP_REASON_UNKNOWN;
1944             }
1945         }
1946     }
1947 
1948     /**
1949      * Duplicates {@code InputStream} data to multiple {@code OutputStream}. Like the "tee" command.
1950      *
1951      * <p>Supports non-blocking writes to the output streams by ignoring EAGAIN error.
1952      */
1953     private static class TeeWorker implements Runnable {
1954         private final String mName;
1955         private final InputStream mIn;
1956         private final List<OutputStream> mOuts;
1957 
TeeWorker(String name, InputStream in, Collection<OutputStream> outs)1958         TeeWorker(String name, InputStream in, Collection<OutputStream> outs) {
1959             mName = name;
1960             mIn = in;
1961             mOuts = new ArrayList<>(outs);
1962         }
1963 
1964         @Override
run()1965         public void run() {
1966             byte[] buffer = new byte[2048];
1967             try {
1968                 while (!Thread.interrupted()) {
1969                     int len = mIn.read(buffer);
1970                     if (len < 0) {
1971                         break;
1972                     }
1973                     for (OutputStream out : mOuts) {
1974                         try {
1975                             out.write(buffer, 0, len);
1976                         } catch (IOException e) {
1977                             // EAGAIN is expected because the file description has O_NONBLOCK flag.
1978                             if (!isErrnoError(e, OsConstants.EAGAIN)) {
1979                                 throw e;
1980                             }
1981                         }
1982                     }
1983                 }
1984             } catch (Exception e) {
1985                 Log.e(TAG, "Tee " + mName, e);
1986             }
1987         }
1988 
asErrnoException(Throwable e)1989         private static ErrnoException asErrnoException(Throwable e) {
1990             if (e instanceof ErrnoException) {
1991                 return (ErrnoException) e;
1992             } else if (e instanceof IOException) {
1993                 // Try to unwrap ErrnoException#rethrowAsIOException()
1994                 return asErrnoException(e.getCause());
1995             }
1996             return null;
1997         }
1998 
isErrnoError(Exception e, int expectedValue)1999         private static boolean isErrnoError(Exception e, int expectedValue) {
2000             ErrnoException errno = asErrnoException(e);
2001             return errno != null && errno.errno == expectedValue;
2002         }
2003     }
2004 }
2005