Home | History | Annotate | Download | only in pm
      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 
     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 
     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 
     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      */
     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      */
    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      */
    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      */
    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 
    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
    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 
    309     static File getOatDir(File codePath) {
    310         return new File(codePath, OAT_DIR_NAME);
    311     }
    312 
    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      */
    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 
    357         public ForcedUpdatePackageDexOptimizer(Installer installer, Object installLock,
    358                 Context context, String wakeLockTag) {
    359             super(installer, installLock, context, wakeLockTag);
    360         }
    361 
    362         public ForcedUpdatePackageDexOptimizer(PackageDexOptimizer from) {
    363             super(from);
    364         }
    365 
    366         @Override
    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