• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2024 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 com.android.server.art.prereboot;
18 
19 import static com.android.server.art.IDexoptChrootSetup.CHROOT_DIR;
20 import static com.android.server.art.prereboot.PreRebootManagerInterface.SystemRequirementException;
21 import static com.android.server.art.proto.PreRebootStats.Status;
22 
23 import android.annotation.NonNull;
24 import android.annotation.Nullable;
25 import android.content.Context;
26 import android.os.ArtModuleServiceManager;
27 import android.os.Build;
28 import android.os.CancellationSignal;
29 import android.os.RemoteException;
30 import android.os.ServiceSpecificException;
31 import android.system.ErrnoException;
32 import android.system.Os;
33 
34 import androidx.annotation.RequiresApi;
35 
36 import com.android.internal.annotations.VisibleForTesting;
37 import com.android.server.LocalManagerRegistry;
38 import com.android.server.art.ArtJni;
39 import com.android.server.art.ArtManagerLocal;
40 import com.android.server.art.ArtModuleServiceInitializer;
41 import com.android.server.art.ArtdRefCache;
42 import com.android.server.art.AsLog;
43 import com.android.server.art.GlobalInjector;
44 import com.android.server.art.IArtd;
45 import com.android.server.art.IDexoptChrootSetup;
46 import com.android.server.art.PreRebootDexoptJob;
47 import com.android.server.art.ReasonMapping;
48 import com.android.server.art.Utils;
49 import com.android.server.art.model.BatchDexoptParams;
50 import com.android.server.pm.PackageManagerLocal;
51 
52 import dalvik.system.DelegateLastClassLoader;
53 
54 import libcore.io.Streams;
55 
56 import java.io.FileDescriptor;
57 import java.io.FileInputStream;
58 import java.io.FileOutputStream;
59 import java.io.IOException;
60 import java.io.InputStream;
61 import java.io.OutputStream;
62 import java.nio.file.Files;
63 import java.nio.file.Paths;
64 import java.util.Objects;
65 
66 /**
67  * Drives Pre-reboot Dexopt, through reflection.
68  *
69  * DO NOT use this class directly. Use {@link PreRebootDexoptJob}.
70  *
71  * During Pre-reboot Dexopt, the old version of this code is run.
72  *
73  * @hide
74  */
75 @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
76 public class PreRebootDriver {
77     @NonNull private final Injector mInjector;
78 
PreRebootDriver(@onNull Context context, @NonNull ArtManagerLocal artManagerLocal)79     public PreRebootDriver(@NonNull Context context, @NonNull ArtManagerLocal artManagerLocal) {
80         this(new Injector(context, artManagerLocal));
81     }
82 
83     @VisibleForTesting
PreRebootDriver(@onNull Injector injector)84     public PreRebootDriver(@NonNull Injector injector) {
85         mInjector = injector;
86     }
87 
88     /**
89      * Runs Pre-reboot Dexopt and returns the result.
90      *
91      * @param otaSlot The slot that contains the OTA update, "_a" or "_b", or null for a Mainline
92      *         update.
93      * @param mapSnapshotsForOta Whether to map/unmap snapshots. Only applicable to an OTA update.
94      */
run(@ullable String otaSlot, boolean mapSnapshotsForOta, @NonNull CancellationSignal cancellationSignal)95     public @NonNull PreRebootResult run(@Nullable String otaSlot, boolean mapSnapshotsForOta,
96             @NonNull CancellationSignal cancellationSignal) {
97         boolean systemRequirementCheckFailed = false;
98         try {
99             try (var snapshot = mInjector.getPackageManagerLocal().withFilteredSnapshot()) {
100                 BatchDexoptParams params = mInjector.getArtManagerLocal().getBatchDexoptParams(
101                         snapshot, ReasonMapping.REASON_PRE_REBOOT_DEXOPT, cancellationSignal);
102                 if (!cancellationSignal.isCanceled()) {
103                     setUp(otaSlot, mapSnapshotsForOta);
104                     runFromChroot(cancellationSignal, snapshot, params);
105                 }
106             }
107             return new PreRebootResult(true /* success */);
108         } catch (RemoteException e) {
109             Utils.logArtdException(e);
110         } catch (ServiceSpecificException e) {
111             AsLog.e("Failed to set up chroot", e);
112         } catch (SystemRequirementException e) {
113             systemRequirementCheckFailed = true;
114             AsLog.e("System requirement check failed", e);
115         } catch (ReflectiveOperationException e) {
116             Throwable cause = e.getCause();
117             if (cause != null
118                     && cause.getClass().getName().equals(
119                             SystemRequirementException.class.getName())) {
120                 // For future use only. Can't happen for now.
121                 systemRequirementCheckFailed = true;
122                 AsLog.e("System requirement check failed in chroot", cause);
123             } else {
124                 AsLog.wtf("Failed to run Pre-reboot Dexopt", e);
125             }
126         } catch (IOException | ErrnoException e) {
127             AsLog.e("Failed to run Pre-reboot Dexopt", e);
128         } finally {
129             try {
130                 // No need to pass `mapSnapshotsForOta` because `setUp` stores this information in a
131                 // temp file.
132                 tearDown();
133             } catch (RemoteException e) {
134                 Utils.logArtdException(e);
135             } catch (ServiceSpecificException | IOException e) {
136                 AsLog.e("Failed to tear down chroot", e);
137             }
138         }
139         return new PreRebootResult(false /* success */, systemRequirementCheckFailed);
140     }
141 
test()142     public void test() {
143         boolean teardownAttempted = false;
144         try {
145             setUp(null /* otaSlot */, false /* mapSnapshotsForOta */);
146             // Ideally, we should try dexopting some packages here. However, it's not trivial to
147             // pass a package list into chroot. Besides, we need to generate boot images even if we
148             // dexopt only one package, and that can easily make the test fail the CTS quality
149             // requirement on test duration (<30s).
150             teardownAttempted = true;
151             tearDown();
152         } catch (SystemRequirementException e) {
153             throw new AssertionError("System requirement check failed", e);
154         } catch (RemoteException | IOException e) {
155             throw new AssertionError("Unexpected exception", e);
156         } finally {
157             if (!teardownAttempted) {
158                 try {
159                     tearDown();
160                 } catch (RemoteException | IOException | RuntimeException e) {
161                     // Do nothing.
162                 }
163             }
164         }
165     }
166 
maybeCleanUpChroot()167     public void maybeCleanUpChroot() {
168         if (!Files.exists(Paths.get(CHROOT_DIR))) {
169             return;
170         }
171         try {
172             ArtJni.ensureNoProcessInDir(CHROOT_DIR, 5000 /* timeoutMs */);
173             mInjector.getDexoptChrootSetup().tearDown(true /* allowConcurrent */);
174         } catch (RemoteException e) {
175             Utils.logArtdException(e);
176         } catch (ServiceSpecificException | IOException e) {
177             AsLog.e("Failed to clean up leftover chroot", e);
178         }
179     }
180 
setUp(@ullable String otaSlot, boolean mapSnapshotsForOta)181     private void setUp(@Nullable String otaSlot, boolean mapSnapshotsForOta)
182             throws RemoteException, SystemRequirementException {
183         mInjector.getDexoptChrootSetup().setUp(otaSlot, mapSnapshotsForOta);
184         if (!mInjector.getArtd().checkPreRebootSystemRequirements(CHROOT_DIR)) {
185             throw new SystemRequirementException("See logs for details");
186         }
187         mInjector.getDexoptChrootSetup().init();
188     }
189 
tearDown()190     private void tearDown() throws RemoteException, IOException {
191         // In general, the teardown unmounts apexes and partitions, and open files can keep the
192         // mounts busy so that they cannot be unmounted. Therefore, a running Pre-reboot artd
193         // process can prevent the teardown from succeeding. It's managed by the service manager,
194         // and there isn't a reliable API to kill it. We deal with it in two steps:
195         // 1. Trigger GC and finalization. The service manager should gracefully shut it down, since
196         //    there is no reference to it as this point.
197         // 2. Call `ensureNoProcessInDir` to wait for it to exit. If it doesn't exit in 5 seconds,
198         //    `ensureNoProcessInDir` will then kill it.
199         Runtime.getRuntime().gc();
200         Runtime.getRuntime().runFinalization();
201         // At this point, no process other than `artd` is expected to be running. `runFromChroot`
202         // blocks on `artd` calls, even upon cancellation, and `artd` in turn waits for child
203         // processes to exit, even if they are killed due to the cancellation.
204         ArtJni.ensureNoProcessInDir(CHROOT_DIR, 5000 /* timeoutMs */);
205         mInjector.getDexoptChrootSetup().tearDown(false /* allowConcurrent */);
206     }
207 
runFromChroot(@onNull CancellationSignal cancellationSignal, @NonNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull BatchDexoptParams params)208     private void runFromChroot(@NonNull CancellationSignal cancellationSignal,
209             @NonNull PackageManagerLocal.FilteredSnapshot snapshot,
210             @NonNull BatchDexoptParams params)
211             throws ReflectiveOperationException, IOException, ErrnoException {
212         // Load the new `service-art.jar` on top of the current classloader, which has the old
213         // system server, framework, and Libcore.
214         // Note that the current classloader also includes the old `service-art.jar`, so this load
215         // inevitably introduces duplicate classes. We use `DelegateLastClassLoader` so that the
216         // classes in the new `service-art.jar` shadow the old ones, to make sure only new classes
217         // are used. Be careful not to pass an instance of a class between the old `service-art.jar`
218         // and the new `service-art.jar` (across the API boundary in `PreRebootManagerInterface`,
219         // either as a parameter or a return value).
220         // For this reason, a serialized protobuf is used for passing `BatchDexoptParams`.
221         String chrootArtDir = CHROOT_DIR + "/apex/com.android.art";
222         String dexPath = chrootArtDir + "/javalib/service-art.jar";
223 
224         // We load the dex file into the memory and close it. In this way, the classloader won't
225         // prevent unmounting even if it fails to unload.
226         ClassLoader classLoader;
227         FileDescriptor memfd = Os.memfd_create("in memory from " + dexPath, 0 /* flags */);
228         try (FileOutputStream out = new FileOutputStream(memfd);
229                 InputStream in = new FileInputStream(dexPath)) {
230             Streams.copy(in, out);
231             classLoader = new DelegateLastClassLoader("/proc/self/fd/" + memfd.getInt$(),
232                     this.getClass().getClassLoader() /* parent */);
233         }
234 
235         Class<?> preRebootManagerClass =
236                 classLoader.loadClass("com.android.server.art.prereboot.PreRebootManager");
237         // Check if the dex file is loaded successfully. Note that the constructor of
238         // `DelegateLastClassLoader` does not throw when the load fails.
239         if (preRebootManagerClass == PreRebootManager.class) {
240             throw new IllegalStateException(String.format("Failed to load %s", dexPath));
241         }
242         Object preRebootManager = preRebootManagerClass.getConstructor().newInstance();
243         preRebootManagerClass
244                 .getMethod("run", ArtModuleServiceManager.class, Context.class,
245                         CancellationSignal.class, PackageManagerLocal.FilteredSnapshot.class,
246                         byte[].class)
247                 .invoke(preRebootManager, ArtModuleServiceInitializer.getArtModuleServiceManager(),
248                         mInjector.getContext(), cancellationSignal, snapshot,
249                         params.toProto().toByteArray());
250     }
251 
252     /**
253      * @param success whether Pre-reboot Dexopt is successful. False if Pre-reboot dexopt failed,
254      *         the system requirement check failed, or system requirements are not met.
255      */
PreRebootResult(boolean success, boolean systemRequirementCheckFailed)256     public record PreRebootResult(boolean success, boolean systemRequirementCheckFailed) {
257         public PreRebootResult(boolean success) {
258             this(success, false /* systemRequirementCheckFailed */);
259         }
260     }
261 
262     /**
263      * Injector pattern for testing purpose.
264      *
265      * @hide
266      */
267     @VisibleForTesting
268     public static class Injector {
269         @NonNull private final Context mContext;
270         @NonNull private final ArtManagerLocal mArtManagerLocal;
271 
Injector(@onNull Context context, @NonNull ArtManagerLocal artManagerLocal)272         Injector(@NonNull Context context, @NonNull ArtManagerLocal artManagerLocal) {
273             mContext = context;
274             mArtManagerLocal = artManagerLocal;
275         }
276 
277         @NonNull
getContext()278         public Context getContext() {
279             return mContext;
280         }
281 
282         @NonNull
getDexoptChrootSetup()283         public IDexoptChrootSetup getDexoptChrootSetup() {
284             return GlobalInjector.getInstance().getDexoptChrootSetup();
285         }
286 
287         @NonNull
getArtd()288         public IArtd getArtd() {
289             return ArtdRefCache.getInstance().getArtd();
290         }
291 
292         @NonNull
getArtManagerLocal()293         public ArtManagerLocal getArtManagerLocal() {
294             return mArtManagerLocal;
295         }
296 
297         @NonNull
getPackageManagerLocal()298         public PackageManagerLocal getPackageManagerLocal() {
299             return Objects.requireNonNull(
300                     LocalManagerRegistry.getManager(PackageManagerLocal.class));
301         }
302     }
303 }
304