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