1 /*
2  * Copyright (C) 2015 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.pm;
18 
19 import android.annotation.Nullable;
20 import android.content.Context;
21 import android.content.pm.ApplicationInfo;
22 import android.content.pm.PackageParser;
23 import android.os.Environment;
24 import android.os.PowerManager;
25 import android.os.UserHandle;
26 import android.os.WorkSource;
27 import android.util.Log;
28 import android.util.Slog;
29 
30 import com.android.internal.os.InstallerConnection.InstallerException;
31 import com.android.internal.util.IndentingPrintWriter;
32 
33 import java.io.File;
34 import java.io.IOException;
35 import java.util.List;
36 
37 import dalvik.system.DexFile;
38 
39 import static com.android.server.pm.Installer.DEXOPT_BOOTCOMPLETE;
40 import static com.android.server.pm.Installer.DEXOPT_DEBUGGABLE;
41 import static com.android.server.pm.Installer.DEXOPT_PROFILE_GUIDED;
42 import static com.android.server.pm.Installer.DEXOPT_PUBLIC;
43 import static com.android.server.pm.Installer.DEXOPT_SAFEMODE;
44 import static com.android.server.pm.InstructionSets.getAppDexInstructionSets;
45 import static com.android.server.pm.InstructionSets.getDexCodeInstructionSets;
46 import static com.android.server.pm.PackageManagerServiceCompilerMapping.getNonProfileGuidedCompilerFilter;
47 
48 /**
49  * Helper class for running dexopt command on packages.
50  */
51 class PackageDexOptimizer {
52     private static final String TAG = "PackageManager.DexOptimizer";
53     static final String OAT_DIR_NAME = "oat";
54     // TODO b/19550105 Remove error codes and use exceptions
55     static final int DEX_OPT_SKIPPED = 0;
56     static final int DEX_OPT_PERFORMED = 1;
57     static final int DEX_OPT_FAILED = -1;
58 
59     private final Installer mInstaller;
60     private final Object mInstallLock;
61 
62     private final PowerManager.WakeLock mDexoptWakeLock;
63     private volatile boolean mSystemReady;
64 
PackageDexOptimizer(Installer installer, Object installLock, Context context, String wakeLockTag)65     PackageDexOptimizer(Installer installer, Object installLock, Context context,
66             String wakeLockTag) {
67         this.mInstaller = installer;
68         this.mInstallLock = installLock;
69 
70         PowerManager powerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
71         mDexoptWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, wakeLockTag);
72     }
73 
PackageDexOptimizer(PackageDexOptimizer from)74     protected PackageDexOptimizer(PackageDexOptimizer from) {
75         this.mInstaller = from.mInstaller;
76         this.mInstallLock = from.mInstallLock;
77         this.mDexoptWakeLock = from.mDexoptWakeLock;
78         this.mSystemReady = from.mSystemReady;
79     }
80 
canOptimizePackage(PackageParser.Package pkg)81     static boolean canOptimizePackage(PackageParser.Package pkg) {
82         return (pkg.applicationInfo.flags & ApplicationInfo.FLAG_HAS_CODE) != 0;
83     }
84 
85     /**
86      * Performs dexopt on all code paths and libraries of the specified package for specified
87      * instruction sets.
88      *
89      * <p>Calls to {@link com.android.server.pm.Installer#dexopt} on {@link #mInstaller} are
90      * synchronized on {@link #mInstallLock}.
91      */
performDexOpt(PackageParser.Package pkg, String[] sharedLibraries, String[] instructionSets, boolean checkProfiles, String targetCompilationFilter)92     int performDexOpt(PackageParser.Package pkg, String[] sharedLibraries,
93             String[] instructionSets, boolean checkProfiles, String targetCompilationFilter) {
94         synchronized (mInstallLock) {
95             final boolean useLock = mSystemReady;
96             if (useLock) {
97                 mDexoptWakeLock.setWorkSource(new WorkSource(pkg.applicationInfo.uid));
98                 mDexoptWakeLock.acquire();
99             }
100             try {
101                 return performDexOptLI(pkg, sharedLibraries, instructionSets, checkProfiles,
102                         targetCompilationFilter);
103             } finally {
104                 if (useLock) {
105                     mDexoptWakeLock.release();
106                 }
107             }
108         }
109     }
110 
111     /**
112      * Adjust the given dexopt-needed value. Can be overridden to influence the decision to
113      * optimize or not (and in what way).
114      */
adjustDexoptNeeded(int dexoptNeeded)115     protected int adjustDexoptNeeded(int dexoptNeeded) {
116         return dexoptNeeded;
117     }
118 
119     /**
120      * Adjust the given dexopt flags that will be passed to the installer.
121      */
adjustDexoptFlags(int dexoptFlags)122     protected int adjustDexoptFlags(int dexoptFlags) {
123         return dexoptFlags;
124     }
125 
126     /**
127      * Dumps the dexopt state of the given package {@code pkg} to the given {@code PrintWriter}.
128      */
dumpDexoptState(IndentingPrintWriter pw, PackageParser.Package pkg)129     void dumpDexoptState(IndentingPrintWriter pw, PackageParser.Package pkg) {
130         final String[] instructionSets = getAppDexInstructionSets(pkg.applicationInfo);
131         final String[] dexCodeInstructionSets = getDexCodeInstructionSets(instructionSets);
132 
133         final List<String> paths = pkg.getAllCodePathsExcludingResourceOnly();
134 
135         for (String instructionSet : dexCodeInstructionSets) {
136              pw.println("Instruction Set: " + instructionSet);
137              pw.increaseIndent();
138              for (String path : paths) {
139                   String status = null;
140                   try {
141                       status = DexFile.getDexFileStatus(path, instructionSet);
142                   } catch (IOException ioe) {
143                       status = "[Exception]: " + ioe.getMessage();
144                   }
145                   pw.println("path: " + path);
146                   pw.println("status: " + status);
147              }
148              pw.decreaseIndent();
149         }
150     }
151 
performDexOptLI(PackageParser.Package pkg, String[] sharedLibraries, String[] targetInstructionSets, boolean checkProfiles, String targetCompilerFilter)152     private int performDexOptLI(PackageParser.Package pkg, String[] sharedLibraries,
153             String[] targetInstructionSets, boolean checkProfiles, String targetCompilerFilter) {
154         final String[] instructionSets = targetInstructionSets != null ?
155                 targetInstructionSets : getAppDexInstructionSets(pkg.applicationInfo);
156 
157         if (!canOptimizePackage(pkg)) {
158             return DEX_OPT_SKIPPED;
159         }
160 
161         final List<String> paths = pkg.getAllCodePathsExcludingResourceOnly();
162         final int sharedGid = UserHandle.getSharedAppGid(pkg.applicationInfo.uid);
163 
164         boolean isProfileGuidedFilter = DexFile.isProfileGuidedCompilerFilter(targetCompilerFilter);
165         // If any part of the app is used by other apps, we cannot use profile-guided
166         // compilation.
167         if (isProfileGuidedFilter && isUsedByOtherApps(pkg)) {
168             checkProfiles = false;
169 
170             targetCompilerFilter = getNonProfileGuidedCompilerFilter(targetCompilerFilter);
171             if (DexFile.isProfileGuidedCompilerFilter(targetCompilerFilter)) {
172                 throw new IllegalStateException(targetCompilerFilter);
173             }
174             isProfileGuidedFilter = false;
175         }
176 
177         // If we're asked to take profile updates into account, check now.
178         boolean newProfile = false;
179         if (checkProfiles && isProfileGuidedFilter) {
180             // Merge profiles, see if we need to do anything.
181             try {
182                 newProfile = mInstaller.mergeProfiles(sharedGid, pkg.packageName);
183             } catch (InstallerException e) {
184                 Slog.w(TAG, "Failed to merge profiles", e);
185             }
186         }
187 
188         final boolean vmSafeMode = (pkg.applicationInfo.flags & ApplicationInfo.FLAG_VM_SAFE_MODE) != 0;
189         final boolean debuggable = (pkg.applicationInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
190 
191         boolean performedDexOpt = false;
192         boolean successfulDexOpt = true;
193 
194         final String[] dexCodeInstructionSets = getDexCodeInstructionSets(instructionSets);
195         for (String dexCodeInstructionSet : dexCodeInstructionSets) {
196             for (String path : paths) {
197                 int dexoptNeeded;
198                 try {
199                     dexoptNeeded = DexFile.getDexOptNeeded(path,
200                             dexCodeInstructionSet, targetCompilerFilter, newProfile);
201                 } catch (IOException ioe) {
202                     Slog.w(TAG, "IOException reading apk: " + path, ioe);
203                     return DEX_OPT_FAILED;
204                 }
205                 dexoptNeeded = adjustDexoptNeeded(dexoptNeeded);
206                 if (PackageManagerService.DEBUG_DEXOPT) {
207                     Log.i(TAG, "DexoptNeeded for " + path + "@" + targetCompilerFilter + " is " +
208                             dexoptNeeded);
209                 }
210 
211                 final String dexoptType;
212                 String oatDir = null;
213                 switch (dexoptNeeded) {
214                     case DexFile.NO_DEXOPT_NEEDED:
215                         continue;
216                     case DexFile.DEX2OAT_NEEDED:
217                         dexoptType = "dex2oat";
218                         oatDir = createOatDirIfSupported(pkg, dexCodeInstructionSet);
219                         break;
220                     case DexFile.PATCHOAT_NEEDED:
221                         dexoptType = "patchoat";
222                         break;
223                     case DexFile.SELF_PATCHOAT_NEEDED:
224                         dexoptType = "self patchoat";
225                         break;
226                     default:
227                         throw new IllegalStateException("Invalid dexopt:" + dexoptNeeded);
228                 }
229 
230                 String sharedLibrariesPath = null;
231                 if (sharedLibraries != null && sharedLibraries.length != 0) {
232                     StringBuilder sb = new StringBuilder();
233                     for (String lib : sharedLibraries) {
234                         if (sb.length() != 0) {
235                             sb.append(":");
236                         }
237                         sb.append(lib);
238                     }
239                     sharedLibrariesPath = sb.toString();
240                 }
241                 Log.i(TAG, "Running dexopt (" + dexoptType + ") on: " + path + " pkg="
242                         + pkg.applicationInfo.packageName + " isa=" + dexCodeInstructionSet
243                         + " vmSafeMode=" + vmSafeMode + " debuggable=" + debuggable
244                         + " target-filter=" + targetCompilerFilter + " oatDir = " + oatDir
245                         + " sharedLibraries=" + sharedLibrariesPath);
246                 // Profile guide compiled oat files should not be public.
247                 final boolean isPublic = !pkg.isForwardLocked() && !isProfileGuidedFilter;
248                 final int profileFlag = isProfileGuidedFilter ? DEXOPT_PROFILE_GUIDED : 0;
249                 final int dexFlags = adjustDexoptFlags(
250                         ( isPublic ? DEXOPT_PUBLIC : 0)
251                         | (vmSafeMode ? DEXOPT_SAFEMODE : 0)
252                         | (debuggable ? DEXOPT_DEBUGGABLE : 0)
253                         | profileFlag
254                         | DEXOPT_BOOTCOMPLETE);
255 
256                 try {
257                     mInstaller.dexopt(path, sharedGid, pkg.packageName, dexCodeInstructionSet,
258                             dexoptNeeded, oatDir, dexFlags, targetCompilerFilter, pkg.volumeUuid,
259                             sharedLibrariesPath);
260                     performedDexOpt = true;
261                 } catch (InstallerException e) {
262                     Slog.w(TAG, "Failed to dexopt", e);
263                     successfulDexOpt = false;
264                 }
265             }
266         }
267 
268         if (successfulDexOpt) {
269             // If we've gotten here, we're sure that no error occurred. We've either
270             // dex-opted one or more paths or instruction sets or we've skipped
271             // all of them because they are up to date. In both cases this package
272             // doesn't need dexopt any longer.
273             return performedDexOpt ? DEX_OPT_PERFORMED : DEX_OPT_SKIPPED;
274         } else {
275             return DEX_OPT_FAILED;
276         }
277     }
278 
279     /**
280      * Creates oat dir for the specified package. In certain cases oat directory
281      * <strong>cannot</strong> be created:
282      * <ul>
283      *      <li>{@code pkg} is a system app, which is not updated.</li>
284      *      <li>Package location is not a directory, i.e. monolithic install.</li>
285      * </ul>
286      *
287      * @return Absolute path to the oat directory or null, if oat directory
288      * cannot be created.
289      */
290     @Nullable
createOatDirIfSupported(PackageParser.Package pkg, String dexInstructionSet)291     private String createOatDirIfSupported(PackageParser.Package pkg, String dexInstructionSet) {
292         if (!pkg.canHaveOatDir()) {
293             return null;
294         }
295         File codePath = new File(pkg.codePath);
296         if (codePath.isDirectory()) {
297             File oatDir = getOatDir(codePath);
298             try {
299                 mInstaller.createOatDir(oatDir.getAbsolutePath(), dexInstructionSet);
300             } catch (InstallerException e) {
301                 Slog.w(TAG, "Failed to create oat dir", e);
302                 return null;
303             }
304             return oatDir.getAbsolutePath();
305         }
306         return null;
307     }
308 
getOatDir(File codePath)309     static File getOatDir(File codePath) {
310         return new File(codePath, OAT_DIR_NAME);
311     }
312 
systemReady()313     void systemReady() {
314         mSystemReady = true;
315     }
316 
317     /**
318      * Returns true if the profiling data collected for the given app indicate
319      * that the apps's APK has been loaded by another app.
320      * Note that this returns false for all forward-locked apps and apps without
321      * any collected profiling data.
322      */
isUsedByOtherApps(PackageParser.Package pkg)323     public static boolean isUsedByOtherApps(PackageParser.Package pkg) {
324         if (pkg.isForwardLocked()) {
325             // Skip the check for forward locked packages since they don't share their code.
326             return false;
327         }
328 
329         for (String apkPath : pkg.getAllCodePathsExcludingResourceOnly()) {
330             try {
331                 apkPath = PackageManagerServiceUtils.realpath(new File(apkPath));
332             } catch (IOException e) {
333                 // Log an error but continue without it.
334                 Slog.w(TAG, "Failed to get canonical path", e);
335                 continue;
336             }
337             String useMarker = apkPath.replace('/', '@');
338             final int[] currentUserIds = UserManagerService.getInstance().getUserIds();
339             for (int i = 0; i < currentUserIds.length; i++) {
340                 File profileDir =
341                         Environment.getDataProfilesDeForeignDexDirectory(currentUserIds[i]);
342                 File foreignUseMark = new File(profileDir, useMarker);
343                 if (foreignUseMark.exists()) {
344                     return true;
345                 }
346             }
347         }
348         return false;
349     }
350 
351     /**
352      * A specialized PackageDexOptimizer that overrides already-installed checks, forcing a
353      * dexopt path.
354      */
355     public static class ForcedUpdatePackageDexOptimizer extends PackageDexOptimizer {
356 
ForcedUpdatePackageDexOptimizer(Installer installer, Object installLock, Context context, String wakeLockTag)357         public ForcedUpdatePackageDexOptimizer(Installer installer, Object installLock,
358                 Context context, String wakeLockTag) {
359             super(installer, installLock, context, wakeLockTag);
360         }
361 
ForcedUpdatePackageDexOptimizer(PackageDexOptimizer from)362         public ForcedUpdatePackageDexOptimizer(PackageDexOptimizer from) {
363             super(from);
364         }
365 
366         @Override
adjustDexoptNeeded(int dexoptNeeded)367         protected int adjustDexoptNeeded(int dexoptNeeded) {
368             // Ensure compilation, no matter the current state.
369             // TODO: The return value is wrong when patchoat is needed.
370             return DexFile.DEX2OAT_NEEDED;
371         }
372     }
373 }
374