From bf93ca7f35e5644ebab8d00515b39d0fa0ee8b85 Mon Sep 17 00:00:00 2001 From: Nikita Ioffe Date: Thu, 9 May 2019 19:33:35 +0100 Subject: Don't use ApexInfo.packageName in StagingManager * ApexInfo.packageName has a misleading name since it represents name of apex module (e.g. com.android.tzdata), not the packageName from AndroidManifest.xml of an apex (e.g. com.google.android.tzdata). Later should be used in PackageManager-world while former is apexd-internal name. * Restructured code a little bit to make the pre-reboot verification flow easier follow. Test: atest CtsStagedInstallHostTestCases Test: adb shell pull /system/apex/com.android.resolv.apex /tmp/resolv.apex && adb install /tmp/resolv.apex && adb reboot && Bug: 132324953 Change-Id: Ifd88b5e5df84ba7a6aebd70dced7b9ab48ed0159 Merged-In: Iedfe87f6e70a2e1d2792487ac7b2891693ceb72a (cherry picked from commit e0dbc98f954be1661676436b18885bc2e7d29119) --- .../java/com/android/server/pm/ApexManager.java | 30 +-- .../java/com/android/server/pm/StagingManager.java | 210 +++++++++++---------- 2 files changed, 109 insertions(+), 131 deletions(-) diff --git a/services/core/java/com/android/server/pm/ApexManager.java b/services/core/java/com/android/server/pm/ApexManager.java index a0c00f8c9b69..ea0fc9a126b8 100644 --- a/services/core/java/com/android/server/pm/ApexManager.java +++ b/services/core/java/com/android/server/pm/ApexManager.java @@ -36,7 +36,6 @@ import android.os.RemoteException; import android.os.ServiceManager; import android.os.ServiceManager.ServiceNotFoundException; import android.sysprop.ApexProperties; -import android.util.ArrayMap; import android.util.Slog; import com.android.internal.annotations.GuardedBy; @@ -70,17 +69,6 @@ class ApexManager { */ @GuardedBy("mLock") private List mAllPackagesCache; - /** - * A map from {@code apexName} to the {@Link PackageInfo} generated from the {@code - * AndroidManifest.xml}. - * - *

Note that key of this map is {@code apexName} field which corresponds to the {@code name} - * field of {@code apex_manifest.json}. - */ - // TODO(b/132324953): remove. - @GuardedBy("mLock") - private ArrayMap mApexNameToPackageInfoCache; - ApexManager(Context context) { mContext = context; @@ -121,7 +109,6 @@ class ApexManager { if (mAllPackagesCache != null) { return; } - mApexNameToPackageInfoCache = new ArrayMap<>(); try { mAllPackagesCache = new ArrayList<>(); HashSet activePackagesSet = new HashSet<>(); @@ -144,9 +131,7 @@ class ApexManager { "Two active packages have the same name: " + pkg.packageName); } - activePackagesSet.add(pkg.packageName); - // TODO(b/132324953): remove. - mApexNameToPackageInfoCache.put(ai.moduleName, pkg); + activePackagesSet.add(ai.packageName); } if (ai.isFactory) { if (factoryPackagesSet.contains(pkg.packageName)) { @@ -196,19 +181,6 @@ class ApexManager { return null; } - /** - * Returns a {@link PackageInfo} for an active APEX package keyed by it's {@code apexName}. - * - * @deprecated this API will soon be deleted, please don't depend on it. - */ - // TODO(b/132324953): delete. - @Deprecated - @Nullable PackageInfo getPackageInfoForApexName(String apexName) { - if (!isApexSupported()) return null; - populateAllPackagesCacheIfNeeded(); - return mApexNameToPackageInfoCache.get(apexName); - } - /** * Retrieves information about all active APEX packages. * diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java index 88d681f973c4..18bbfed3364f 100644 --- a/services/core/java/com/android/server/pm/StagingManager.java +++ b/services/core/java/com/android/server/pm/StagingManager.java @@ -30,6 +30,7 @@ import android.content.pm.PackageInfo; import android.content.pm.PackageInstaller; import android.content.pm.PackageInstaller.SessionInfo; import android.content.pm.PackageManager; +import android.content.pm.PackageParser; import android.content.pm.PackageParser.PackageParserException; import android.content.pm.PackageParser.SigningDetails; import android.content.pm.PackageParser.SigningDetails.SignatureSchemeVersion; @@ -43,6 +44,7 @@ import android.os.ParcelFileDescriptor; import android.os.PowerManager; import android.os.RemoteException; import android.os.ServiceManager; +import android.util.IntArray; import android.util.Slog; import android.util.SparseArray; import android.util.apk.ApkSignatureVerifier; @@ -104,21 +106,22 @@ public class StagingManager { return new ParceledListSlice<>(result); } - private boolean validateApexSignature(String apexPath, String apexModuleName) { + private void validateApexSignature(String apexPath, String packageName) + throws PackageManagerException { final SigningDetails signingDetails; try { signingDetails = ApkSignatureVerifier.verify(apexPath, SignatureSchemeVersion.JAR); } catch (PackageParserException e) { - Slog.e(TAG, "Unable to parse APEX package: " + apexPath, e); - return false; + throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, + "Failed to parse APEX package " + apexPath, e); } - final PackageInfo packageInfo = mApexManager.getPackageInfoForApexName(apexModuleName); - + final PackageInfo packageInfo = mApexManager.getPackageInfo(packageName, + ApexManager.MATCH_ACTIVE_PACKAGE); if (packageInfo == null) { - // Don't allow installation of new APEX. - Slog.e(TAG, "Attempted to install a new apex " + apexModuleName + ". Rejecting"); - return false; + // This should never happen, because submitSessionToApexService ensures that no new + // apexes were installed. + throw new IllegalStateException("Unknown apex package " + packageName); } final SigningDetails existingSigningDetails; @@ -126,73 +129,98 @@ public class StagingManager { existingSigningDetails = ApkSignatureVerifier.verify( packageInfo.applicationInfo.sourceDir, SignatureSchemeVersion.JAR); } catch (PackageParserException e) { - Slog.e(TAG, "Unable to parse APEX package: " - + packageInfo.applicationInfo.sourceDir, e); - return false; + throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, + "Failed to parse APEX package " + packageInfo.applicationInfo.sourceDir, e); } // Now that we have both sets of signatures, demand that they're an exact match. if (Signature.areExactMatch(existingSigningDetails.signatures, signingDetails.signatures)) { - return true; + return; } - return false; + throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, + "APK-container signature verification failed for package " + + packageName + ". Signature of file " + + apexPath + " does not match the signature of " + + " the package already installed."); } - private boolean submitSessionToApexService(@NonNull PackageInstallerSession session, - List childSessions, - ApexInfoList apexInfoList) { - boolean submittedToApexd = mApexManager.submitStagedSession( - session.sessionId, - childSessions != null - ? childSessions.stream().mapToInt(s -> s.sessionId).toArray() : - new int[]{}, - apexInfoList); + private List submitSessionToApexService( + @NonNull PackageInstallerSession session) throws PackageManagerException { + final IntArray childSessionsIds = new IntArray(); + if (session.isMultiPackage()) { + for (int id : session.getChildSessionIds()) { + if (isApexSession(mStagedSessions.get(id))) { + childSessionsIds.add(id); + } + } + } + final ApexInfoList apexInfoList = new ApexInfoList(); + boolean submittedToApexd = mApexManager.submitStagedSession(session.sessionId, + childSessionsIds.toArray(), apexInfoList); if (!submittedToApexd) { - session.setStagedSessionFailed( - SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, + throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, "APEX staging failed, check logcat messages from apexd for more details."); - return false; } - for (ApexInfo newModule : apexInfoList.apexInfos) { - PackageInfo activePackage = mApexManager.getPackageInfoForApexName( - newModule.moduleName); + final List result = new ArrayList<>(); + for (ApexInfo newPackage : apexInfoList.apexInfos) { + final PackageInfo pkg; + try { + pkg = PackageParser.generatePackageInfoFromApex(newPackage, + PackageManager.GET_META_DATA); + } catch (PackageParserException e) { + throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, + "Failed to parse APEX package " + newPackage.packagePath, e); + } + final PackageInfo activePackage = mApexManager.getPackageInfo(pkg.packageName, + ApexManager.MATCH_ACTIVE_PACKAGE); if (activePackage == null) { - continue; - } - long activeVersion = activePackage.applicationInfo.longVersionCode; - if (session.params.requiredInstalledVersionCode - != PackageManager.VERSION_CODE_HIGHEST) { - if (activeVersion != session.params.requiredInstalledVersionCode) { - session.setStagedSessionFailed( - SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, - "Installed version of APEX package " + activePackage.packageName - + " does not match required. Active version: " + activeVersion - + " required: " + session.params.requiredInstalledVersionCode); - - if (!mApexManager.abortActiveSession()) { - Slog.e(TAG, "Failed to abort apex session " + session.sessionId); - } - return false; - } + Slog.w(TAG, "Attempting to install new APEX package " + pkg.packageName); + throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, + "It is forbidden to install new APEX packages."); } + checkRequiredVersionCode(session, activePackage); + checkDowngrade(session, activePackage, pkg); + result.add(pkg); + } + return result; + } - boolean allowsDowngrade = PackageManagerServiceUtils.isDowngradePermitted( - session.params.installFlags, activePackage.applicationInfo.flags); - if (activeVersion > newModule.versionCode && !allowsDowngrade) { - session.setStagedSessionFailed( - SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, - "Downgrade of APEX package " + activePackage.packageName - + " is not allowed. Active version: " + activeVersion - + " attempted: " + newModule.versionCode); + private void checkRequiredVersionCode(final PackageInstallerSession session, + final PackageInfo activePackage) throws PackageManagerException { + if (session.params.requiredInstalledVersionCode == PackageManager.VERSION_CODE_HIGHEST) { + return; + } + final long activeVersion = activePackage.applicationInfo.longVersionCode; + if (activeVersion != session.params.requiredInstalledVersionCode) { + if (!mApexManager.abortActiveSession()) { + Slog.e(TAG, "Failed to abort apex session " + session.sessionId); + } + throw new PackageManagerException( + SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, + "Installed version of APEX package " + activePackage.packageName + + " does not match required. Active version: " + activeVersion + + " required: " + session.params.requiredInstalledVersionCode); + } + } - if (!mApexManager.abortActiveSession()) { - Slog.e(TAG, "Failed to abort apex session " + session.sessionId); - } - return false; + private void checkDowngrade(final PackageInstallerSession session, + final PackageInfo activePackage, final PackageInfo newPackage) + throws PackageManagerException { + final long activeVersion = activePackage.applicationInfo.longVersionCode; + final long newVersionCode = newPackage.applicationInfo.longVersionCode; + boolean allowsDowngrade = PackageManagerServiceUtils.isDowngradePermitted( + session.params.installFlags, activePackage.applicationInfo.flags); + if (activeVersion > newVersionCode && !allowsDowngrade) { + if (!mApexManager.abortActiveSession()) { + Slog.e(TAG, "Failed to abort apex session " + session.sessionId); } + throw new PackageManagerException( + SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, + "Downgrade of APEX package " + newPackage.packageName + + " is not allowed. Active version: " + activeVersion + + " attempted: " + newVersionCode); } - return true; } private static boolean isApexSession(@NonNull PackageInstallerSession session) { @@ -200,31 +228,20 @@ public class StagingManager { } private void preRebootVerification(@NonNull PackageInstallerSession session) { - boolean success = true; - - final ApexInfoList apexInfoList = new ApexInfoList(); + final boolean hasApex = sessionContainsApex(session); // APEX checks. For single-package sessions, check if they contain an APEX. For // multi-package sessions, find all the child sessions that contain an APEX. - if (!session.isMultiPackage() - && isApexSession(session)) { - success = submitSessionToApexService(session, null, apexInfoList); - - } else if (session.isMultiPackage()) { - List childSessions = - Arrays.stream(session.getChildSessionIds()) - // Retrieve cached sessions matching ids. - .mapToObj(i -> mStagedSessions.get(i)) - // Filter only the ones containing APEX. - .filter(childSession -> isApexSession(childSession)) - .collect(Collectors.toList()); - if (!childSessions.isEmpty()) { - success = submitSessionToApexService(session, childSessions, apexInfoList); - } // else this is a staged multi-package session with no APEX files. - } - - if (!success) { - // submitSessionToApexService will populate error. - return; + if (hasApex) { + try { + final List apexPackages = submitSessionToApexService(session); + for (PackageInfo apexPackage : apexPackages) { + validateApexSignature(apexPackage.applicationInfo.sourceDir, + apexPackage.packageName); + } + } catch (PackageManagerException e) { + session.setStagedSessionFailed(e.error, e.getMessage()); + return; + } } if (sessionContainsApk(session)) { @@ -237,25 +254,6 @@ public class StagingManager { } } - if (apexInfoList.apexInfos != null && apexInfoList.apexInfos.length > 0) { - // For APEXes, we validate the signature here before we mark the session as ready, - // so we fail the session early if there is a signature mismatch. For APKs, the - // signature verification will be done by the package manager at the point at which - // it applies the staged install. - for (ApexInfo apexModule : apexInfoList.apexInfos) { - if (!validateApexSignature(apexModule.modulePath, - apexModule.moduleName)) { - session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, - "APK-container signature verification failed for package " - + apexModule.moduleName + ". Signature of file " - + apexModule.modulePath + " does not match the signature of " - + " the package already installed."); - // TODO(b/118865310): abort the session on apexd. - return; - } - } - } - if ((session.params.installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0) { // If rollback is enabled for this session, we call through to the RollbackManager // with the list of sessions it must enable rollback for. Note that notifyStagedSession @@ -273,9 +271,17 @@ public class StagingManager { } } + // Proactively mark session as ready before calling apexd. Although this call order looks + // counter-intuitive, this is the easiest way to ensure that session won't end up in the + // inconsistent state: + // - If device gets rebooted right before call to apexd, then apexd will never activate + // apex files of this staged session. This will result in StagingManager failing the + // session. + // On the other hand, if the order of the calls was inverted (first call apexd, then mark + // session as ready), then if a device gets rebooted right after the call to apexd, only + // apex part of the train will be applied, leaving device in an inconsistent state. session.setStagedSessionReady(); - if (sessionContainsApex(session) - && !mApexManager.markStagedSessionReady(session.sessionId)) { + if (hasApex && !mApexManager.markStagedSessionReady(session.sessionId)) { session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, "APEX staging failed, check logcat messages from apexd for more " + "details."); -- cgit v1.2.3-59-g8ed1b From ca8f1c060e6e79d0ed73900afe7b12c1c7c761ca Mon Sep 17 00:00:00 2001 From: Nikita Ioffe Date: Thu, 4 Jul 2019 20:48:25 +0100 Subject: Make ApexManager an abstract class And a two implementations: * ApexManagerImpl - in case device supports updatable APEX. * ApexManagerNoOp - in case device does not. This is a more future-proof way of handling both new flow that supports APEX and legacy one that does not. Test: atest CtsStagedInstallHostTestCases Bug: 131697251 Exempt-From-Owner-Approval: already approved in https://googleplex-android-review.git.corp.google.com/c/platform/frameworks/base/+/8483219/ Change-Id: Ieeeb2c7f52ff63ebf9c7e913e95e7cf62d8c7441 Merged-In: If27fcdb95d4ec60834bd5334b68f53d4235a5d61 (cherry picked from commit 278af8f43c72a67dd9c471b159e5e184fb3e5f0d) --- .../java/com/android/server/pm/ApexManager.java | 716 ++++++++++++--------- .../android/server/pm/PackageManagerService.java | 3 +- 2 files changed, 417 insertions(+), 302 deletions(-) diff --git a/services/core/java/com/android/server/pm/ApexManager.java b/services/core/java/com/android/server/pm/ApexManager.java index ea0fc9a126b8..7e1bc79399b9 100644 --- a/services/core/java/com/android/server/pm/ApexManager.java +++ b/services/core/java/com/android/server/pm/ApexManager.java @@ -11,7 +11,7 @@ * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and - * limitations under the License.s + * limitations under the License. */ package com.android.server.pm; @@ -31,10 +31,8 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageParser; -import android.content.pm.PackageParser.PackageParserException; import android.os.RemoteException; import android.os.ServiceManager; -import android.os.ServiceManager.ServiceNotFoundException; import android.sysprop.ApexProperties; import android.util.Slog; @@ -55,103 +53,33 @@ import java.util.stream.Collectors; * ApexManager class handles communications with the apex service to perform operation and queries, * as well as providing caching to avoid unnecessary calls to the service. */ -class ApexManager { - static final String TAG = "ApexManager"; - private final IApexService mApexService; - private final Context mContext; - private final Object mLock = new Object(); - /** - * A map from {@code APEX packageName} to the {@Link PackageInfo} generated from the {@code - * AndroidManifest.xml} - * - *

Note that key of this map is {@code packageName} field of the corresponding {@code - * AndroidManifest.xml}. - */ - @GuardedBy("mLock") - private List mAllPackagesCache; - - ApexManager(Context context) { - mContext = context; - if (!isApexSupported()) { - mApexService = null; - return; - } - try { - mApexService = IApexService.Stub.asInterface( - ServiceManager.getServiceOrThrow("apexservice")); - } catch (ServiceNotFoundException e) { - throw new IllegalStateException("Required service apexservice not available"); - } - } +abstract class ApexManager { + + private static final String TAG = "ApexManager"; static final int MATCH_ACTIVE_PACKAGE = 1 << 0; static final int MATCH_FACTORY_PACKAGE = 1 << 1; - @IntDef( - flag = true, - prefix = { "MATCH_"}, - value = {MATCH_ACTIVE_PACKAGE, MATCH_FACTORY_PACKAGE}) - @Retention(RetentionPolicy.SOURCE) - @interface PackageInfoFlags{} - - void systemReady() { - if (!isApexSupported()) return; - mContext.registerReceiver(new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - onBootCompleted(); - mContext.unregisterReceiver(this); - } - }, new IntentFilter(Intent.ACTION_BOOT_COMPLETED)); - } - private void populateAllPackagesCacheIfNeeded() { - synchronized (mLock) { - if (mAllPackagesCache != null) { - return; - } + /** + * Returns an instance of either {@link ApexManagerImpl} or {@link ApexManagerNoOp} depending + * on whenever this device supports APEX, i.e. {@link ApexProperties#updatable()} evaluates to + * {@code true}. + */ + static ApexManager create(Context systemContext) { + if (ApexProperties.updatable().orElse(false)) { try { - mAllPackagesCache = new ArrayList<>(); - HashSet activePackagesSet = new HashSet<>(); - HashSet factoryPackagesSet = new HashSet<>(); - final ApexInfo[] allPkgs = mApexService.getAllPackages(); - for (ApexInfo ai : allPkgs) { - // If the device is using flattened APEX, don't report any APEX - // packages since they won't be managed or updated by PackageManager. - if ((new File(ai.modulePath)).isDirectory()) { - break; - } - try { - final PackageInfo pkg = PackageParser.generatePackageInfoFromApex( - ai, PackageManager.GET_META_DATA - | PackageManager.GET_SIGNING_CERTIFICATES); - mAllPackagesCache.add(pkg); - if (ai.isActive) { - if (activePackagesSet.contains(pkg.packageName)) { - throw new IllegalStateException( - "Two active packages have the same name: " - + pkg.packageName); - } - activePackagesSet.add(ai.packageName); - } - if (ai.isFactory) { - if (factoryPackagesSet.contains(pkg.packageName)) { - throw new IllegalStateException( - "Two factory packages have the same name: " - + pkg.packageName); - } - factoryPackagesSet.add(pkg.packageName); - } - } catch (PackageParserException pe) { - throw new IllegalStateException("Unable to parse: " + ai, pe); - } - } - } catch (RemoteException re) { - Slog.e(TAG, "Unable to retrieve packages from apexservice: " + re.toString()); - throw new RuntimeException(re); + return new ApexManagerImpl(systemContext, IApexService.Stub.asInterface( + ServiceManager.getServiceOrThrow("apexservice"))); + } catch (ServiceManager.ServiceNotFoundException e) { + throw new IllegalStateException("Required service apexservice not available"); } + } else { + return new ApexManagerNoOp(); } } + abstract void systemReady(); + /** * Retrieves information about an APEX package. * @@ -164,22 +92,8 @@ class ApexManager { * @return a PackageInfo object with the information about the package, or null if the package * is not found. */ - @Nullable PackageInfo getPackageInfo(String packageName, @PackageInfoFlags int flags) { - if (!isApexSupported()) return null; - populateAllPackagesCacheIfNeeded(); - boolean matchActive = (flags & MATCH_ACTIVE_PACKAGE) != 0; - boolean matchFactory = (flags & MATCH_FACTORY_PACKAGE) != 0; - for (PackageInfo packageInfo: mAllPackagesCache) { - if (!packageInfo.packageName.equals(packageName)) { - continue; - } - if ((!matchActive || isActive(packageInfo)) - && (!matchFactory || isFactory(packageInfo))) { - return packageInfo; - } - } - return null; - } + @Nullable + abstract PackageInfo getPackageInfo(String packageName, @PackageInfoFlags int flags); /** * Retrieves information about all active APEX packages. @@ -187,14 +101,7 @@ class ApexManager { * @return a List of PackageInfo object, each one containing information about a different * active package. */ - List getActivePackages() { - if (!isApexSupported()) return Collections.emptyList(); - populateAllPackagesCacheIfNeeded(); - return mAllPackagesCache - .stream() - .filter(item -> isActive(item)) - .collect(Collectors.toList()); - } + abstract List getActivePackages(); /** * Retrieves information about all active pre-installed APEX packages. @@ -202,14 +109,7 @@ class ApexManager { * @return a List of PackageInfo object, each one containing information about a different * active pre-installed package. */ - List getFactoryPackages() { - if (!isApexSupported()) return Collections.emptyList(); - populateAllPackagesCacheIfNeeded(); - return mAllPackagesCache - .stream() - .filter(item -> isFactory(item)) - .collect(Collectors.toList()); - } + abstract List getFactoryPackages(); /** * Retrieves information about all inactive APEX packages. @@ -217,14 +117,7 @@ class ApexManager { * @return a List of PackageInfo object, each one containing information about a different * inactive package. */ - List getInactivePackages() { - if (!isApexSupported()) return Collections.emptyList(); - populateAllPackagesCacheIfNeeded(); - return mAllPackagesCache - .stream() - .filter(item -> !isActive(item)) - .collect(Collectors.toList()); - } + abstract List getInactivePackages(); /** * Checks if {@code packageName} is an apex package. @@ -232,16 +125,7 @@ class ApexManager { * @param packageName package to check. * @return {@code true} if {@code packageName} is an apex package. */ - boolean isApexPackage(String packageName) { - if (!isApexSupported()) return false; - populateAllPackagesCacheIfNeeded(); - for (PackageInfo packageInfo : mAllPackagesCache) { - if (packageInfo.packageName.equals(packageName)) { - return true; - } - } - return false; - } + abstract boolean isApexPackage(String packageName); /** * Retrieves information about an apexd staged session i.e. the internal state used by apexd to @@ -250,19 +134,8 @@ class ApexManager { * @param sessionId the identifier of the session. * @return an ApexSessionInfo object, or null if the session is not known. */ - @Nullable ApexSessionInfo getStagedSessionInfo(int sessionId) { - if (!isApexSupported()) return null; - try { - ApexSessionInfo apexSessionInfo = mApexService.getStagedSessionInfo(sessionId); - if (apexSessionInfo.isUnknown) { - return null; - } - return apexSessionInfo; - } catch (RemoteException re) { - Slog.e(TAG, "Unable to contact apexservice", re); - throw new RuntimeException(re); - } - } + @Nullable + abstract ApexSessionInfo getStagedSessionInfo(int sessionId); /** * Submit a staged session to apex service. This causes the apex service to perform some initial @@ -280,20 +153,8 @@ class ApexManager { * the session. * @return whether the submission of the session was successful. */ - boolean submitStagedSession( - int sessionId, @NonNull int[] childSessionIds, @NonNull ApexInfoList apexInfoList) { - if (!isApexSupported()) return false; - try { - mApexService.submitStagedSession(sessionId, childSessionIds, apexInfoList); - return true; - } catch (RemoteException re) { - Slog.e(TAG, "Unable to contact apexservice", re); - throw new RuntimeException(re); - } catch (Exception e) { - Slog.e(TAG, "apexd verification failed", e); - return false; - } - } + abstract boolean submitStagedSession( + int sessionId, @NonNull int[] childSessionIds, @NonNull ApexInfoList apexInfoList); /** * Mark a staged session previously submitted using {@code submitStagedSession} as ready to be @@ -302,19 +163,7 @@ class ApexManager { * @param sessionId the identifier of the {@link PackageInstallerSession} being marked as ready. * @return true upon success, false if the session is unknown. */ - boolean markStagedSessionReady(int sessionId) { - if (!isApexSupported()) return false; - try { - mApexService.markStagedSessionReady(sessionId); - return true; - } catch (RemoteException re) { - Slog.e(TAG, "Unable to contact apexservice", re); - throw new RuntimeException(re); - } catch (Exception e) { - Slog.e(TAG, "Failed to mark session " + sessionId + " ready", e); - return false; - } - } + abstract boolean markStagedSessionReady(int sessionId); /** * Marks a staged session as successful. @@ -324,44 +173,21 @@ class ApexManager { * @param sessionId the identifier of the {@link PackageInstallerSession} being marked as * successful. */ - void markStagedSessionSuccessful(int sessionId) { - if (!isApexSupported()) return; - try { - mApexService.markStagedSessionSuccessful(sessionId); - } catch (RemoteException re) { - Slog.e(TAG, "Unable to contact apexservice", re); - throw new RuntimeException(re); - } catch (Exception e) { - // It is fine to just log an exception in this case. APEXd will be able to recover in - // case markStagedSessionSuccessful fails. - Slog.e(TAG, "Failed to mark session " + sessionId + " as successful", e); - } - } + abstract void markStagedSessionSuccessful(int sessionId); /** * Whether the current device supports the management of APEX packages. * * @return true if APEX packages can be managed on this device, false otherwise. */ - boolean isApexSupported() { - return ApexProperties.updatable().orElse(false); - } + abstract boolean isApexSupported(); /** * Abandons the (only) active session previously submitted. * * @return {@code true} upon success, {@code false} if any remote exception occurs */ - boolean abortActiveSession() { - if (!isApexSupported()) return false; - try { - mApexService.abortActiveSession(); - return true; - } catch (RemoteException re) { - Slog.e(TAG, "Unable to contact apexservice", re); - return false; - } - } + abstract boolean abortActiveSession(); /** * Uninstalls given {@code apexPackage}. @@ -371,120 +197,408 @@ class ApexManager { * @param apexPackagePath package to uninstall. * @return {@code true} upon successful uninstall, {@code false} otherwise. */ - boolean uninstallApex(String apexPackagePath) { - if (!isApexSupported()) return false; - try { - mApexService.unstagePackages(Collections.singletonList(apexPackagePath)); - return true; - } catch (Exception e) { - return false; - } - } + abstract boolean uninstallApex(String apexPackagePath); /** - * Whether an APEX package is active or not. + * Dumps various state information to the provided {@link PrintWriter} object. * - * @param packageInfo the package to check - * @return {@code true} if this package is active, {@code false} otherwise. + * @param pw the {@link PrintWriter} object to send information to. + * @param packageName a {@link String} containing a package name, or {@code null}. If set, only + * information about that specific package will be dumped. */ - private static boolean isActive(PackageInfo packageInfo) { - return (packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_INSTALLED) != 0; - } + abstract void dump(PrintWriter pw, @Nullable String packageName); - /** - * Whether the APEX package is pre-installed or not. - * - * @param packageInfo the package to check - * @return {@code true} if this package is pre-installed, {@code false} otherwise. - */ - private static boolean isFactory(PackageInfo packageInfo) { - return (packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0; - } + @IntDef( + flag = true, + prefix = { "MATCH_"}, + value = {MATCH_ACTIVE_PACKAGE, MATCH_FACTORY_PACKAGE}) + @Retention(RetentionPolicy.SOURCE) + @interface PackageInfoFlags{} /** - * Dump information about the packages contained in a particular cache - * @param packagesCache the cache to print information about. - * @param packageName a {@link String} containing a package name, or {@code null}. If set, only - * information about that specific package will be dumped. - * @param ipw the {@link IndentingPrintWriter} object to send information to. + * An implementation of {@link ApexManager} that should be used in case device supports updating + * APEX packages. */ - void dumpFromPackagesCache( - List packagesCache, - @Nullable String packageName, - IndentingPrintWriter ipw) { - ipw.println(); - ipw.increaseIndent(); - for (PackageInfo pi : packagesCache) { - if (packageName != null && !packageName.equals(pi.packageName)) { - continue; + private static class ApexManagerImpl extends ApexManager { + private final IApexService mApexService; + private final Context mContext; + private final Object mLock = new Object(); + /** + * A map from {@code APEX packageName} to the {@Link PackageInfo} generated from the {@code + * AndroidManifest.xml} + * + *

Note that key of this map is {@code packageName} field of the corresponding {@code + * AndroidManifest.xml}. + */ + @GuardedBy("mLock") + private List mAllPackagesCache; + + ApexManagerImpl(Context context, IApexService apexService) { + mContext = context; + mApexService = apexService; + } + + /** + * Whether an APEX package is active or not. + * + * @param packageInfo the package to check + * @return {@code true} if this package is active, {@code false} otherwise. + */ + private static boolean isActive(PackageInfo packageInfo) { + return (packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_INSTALLED) != 0; + } + + /** + * Whether the APEX package is pre-installed or not. + * + * @param packageInfo the package to check + * @return {@code true} if this package is pre-installed, {@code false} otherwise. + */ + private static boolean isFactory(PackageInfo packageInfo) { + return (packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0; + } + + @Override + void systemReady() { + mContext.registerReceiver(new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + populateAllPackagesCacheIfNeeded(); + mContext.unregisterReceiver(this); + } + }, new IntentFilter(Intent.ACTION_BOOT_COMPLETED)); + } + + private void populateAllPackagesCacheIfNeeded() { + synchronized (mLock) { + if (mAllPackagesCache != null) { + return; + } + try { + mAllPackagesCache = new ArrayList<>(); + HashSet activePackagesSet = new HashSet<>(); + HashSet factoryPackagesSet = new HashSet<>(); + final ApexInfo[] allPkgs = mApexService.getAllPackages(); + for (ApexInfo ai : allPkgs) { + // If the device is using flattened APEX, don't report any APEX + // packages since they won't be managed or updated by PackageManager. + if ((new File(ai.modulePath)).isDirectory()) { + break; + } + try { + final PackageInfo pkg = PackageParser.generatePackageInfoFromApex( + ai, PackageManager.GET_META_DATA + | PackageManager.GET_SIGNING_CERTIFICATES); + mAllPackagesCache.add(pkg); + if (ai.isActive) { + if (activePackagesSet.contains(pkg.packageName)) { + throw new IllegalStateException( + "Two active packages have the same name: " + + pkg.packageName); + } + activePackagesSet.add(pkg.packageName); + } + if (ai.isFactory) { + if (factoryPackagesSet.contains(pkg.packageName)) { + throw new IllegalStateException( + "Two factory packages have the same name: " + + pkg.packageName); + } + factoryPackagesSet.add(pkg.packageName); + } + } catch (PackageParser.PackageParserException pe) { + throw new IllegalStateException("Unable to parse: " + ai, pe); + } + } + } catch (RemoteException re) { + Slog.e(TAG, "Unable to retrieve packages from apexservice: " + re.toString()); + throw new RuntimeException(re); + } } - ipw.println(pi.packageName); - ipw.increaseIndent(); - ipw.println("Version: " + pi.versionCode); - ipw.println("Path: " + pi.applicationInfo.sourceDir); - ipw.println("IsActive: " + isActive(pi)); - ipw.println("IsFactory: " + isFactory(pi)); - ipw.decreaseIndent(); } - ipw.decreaseIndent(); - ipw.println(); - } - /** - * Dumps various state information to the provided {@link PrintWriter} object. - * - * @param pw the {@link PrintWriter} object to send information to. - * @param packageName a {@link String} containing a package name, or {@code null}. If set, only - * information about that specific package will be dumped. - */ - void dump(PrintWriter pw, @Nullable String packageName) { - if (!isApexSupported()) return; - final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ", 120); - try { + @Override + @Nullable PackageInfo getPackageInfo(String packageName, @PackageInfoFlags int flags) { populateAllPackagesCacheIfNeeded(); + boolean matchActive = (flags & MATCH_ACTIVE_PACKAGE) != 0; + boolean matchFactory = (flags & MATCH_FACTORY_PACKAGE) != 0; + for (PackageInfo packageInfo: mAllPackagesCache) { + if (!packageInfo.packageName.equals(packageName)) { + continue; + } + if ((!matchActive || isActive(packageInfo)) + && (!matchFactory || isFactory(packageInfo))) { + return packageInfo; + } + } + return null; + } + + @Override + List getActivePackages() { + populateAllPackagesCacheIfNeeded(); + return mAllPackagesCache + .stream() + .filter(item -> isActive(item)) + .collect(Collectors.toList()); + } + + @Override + List getFactoryPackages() { + populateAllPackagesCacheIfNeeded(); + return mAllPackagesCache + .stream() + .filter(item -> isFactory(item)) + .collect(Collectors.toList()); + } + + @Override + List getInactivePackages() { + populateAllPackagesCacheIfNeeded(); + return mAllPackagesCache + .stream() + .filter(item -> !isActive(item)) + .collect(Collectors.toList()); + } + + @Override + boolean isApexPackage(String packageName) { + if (!isApexSupported()) return false; + populateAllPackagesCacheIfNeeded(); + for (PackageInfo packageInfo : mAllPackagesCache) { + if (packageInfo.packageName.equals(packageName)) { + return true; + } + } + return false; + } + + @Override + @Nullable ApexSessionInfo getStagedSessionInfo(int sessionId) { + try { + ApexSessionInfo apexSessionInfo = mApexService.getStagedSessionInfo(sessionId); + if (apexSessionInfo.isUnknown) { + return null; + } + return apexSessionInfo; + } catch (RemoteException re) { + Slog.e(TAG, "Unable to contact apexservice", re); + throw new RuntimeException(re); + } + } + + @Override + boolean submitStagedSession( + int sessionId, @NonNull int[] childSessionIds, @NonNull ApexInfoList apexInfoList) { + try { + return mApexService.submitStagedSession(sessionId, childSessionIds, apexInfoList); + } catch (RemoteException re) { + Slog.e(TAG, "Unable to contact apexservice", re); + throw new RuntimeException(re); + } + } + + @Override + boolean markStagedSessionReady(int sessionId) { + try { + return mApexService.markStagedSessionReady(sessionId); + } catch (RemoteException re) { + Slog.e(TAG, "Unable to contact apexservice", re); + throw new RuntimeException(re); + } + } + + @Override + void markStagedSessionSuccessful(int sessionId) { + try { + mApexService.markStagedSessionSuccessful(sessionId); + } catch (RemoteException re) { + Slog.e(TAG, "Unable to contact apexservice", re); + throw new RuntimeException(re); + } catch (Exception e) { + // It is fine to just log an exception in this case. APEXd will be able to recover + // in case markStagedSessionSuccessful fails. + Slog.e(TAG, "Failed to mark session " + sessionId + " as successful", e); + } + } + + @Override + boolean isApexSupported() { + return true; + } + + @Override + boolean abortActiveSession() { + try { + mApexService.abortActiveSession(); + return true; + } catch (RemoteException re) { + Slog.e(TAG, "Unable to contact apexservice", re); + return false; + } + } + + @Override + boolean uninstallApex(String apexPackagePath) { + try { + mApexService.unstagePackages(Collections.singletonList(apexPackagePath)); + return true; + } catch (Exception e) { + return false; + } + } + + /** + * Dump information about the packages contained in a particular cache + * @param packagesCache the cache to print information about. + * @param packageName a {@link String} containing a package name, or {@code null}. If set, + * only information about that specific package will be dumped. + * @param ipw the {@link IndentingPrintWriter} object to send information to. + */ + void dumpFromPackagesCache( + List packagesCache, + @Nullable String packageName, + IndentingPrintWriter ipw) { ipw.println(); - ipw.println("Active APEX packages:"); - dumpFromPackagesCache(getActivePackages(), packageName, ipw); - ipw.println("Inactive APEX packages:"); - dumpFromPackagesCache(getInactivePackages(), packageName, ipw); - ipw.println("Factory APEX packages:"); - dumpFromPackagesCache(getFactoryPackages(), packageName, ipw); ipw.increaseIndent(); - ipw.println("APEX session state:"); - ipw.increaseIndent(); - final ApexSessionInfo[] sessions = mApexService.getSessions(); - for (ApexSessionInfo si : sessions) { - ipw.println("Session ID: " + si.sessionId); - ipw.increaseIndent(); - if (si.isUnknown) { - ipw.println("State: UNKNOWN"); - } else if (si.isVerified) { - ipw.println("State: VERIFIED"); - } else if (si.isStaged) { - ipw.println("State: STAGED"); - } else if (si.isActivated) { - ipw.println("State: ACTIVATED"); - } else if (si.isActivationFailed) { - ipw.println("State: ACTIVATION FAILED"); - } else if (si.isSuccess) { - ipw.println("State: SUCCESS"); - } else if (si.isRollbackInProgress) { - ipw.println("State: ROLLBACK IN PROGRESS"); - } else if (si.isRolledBack) { - ipw.println("State: ROLLED BACK"); - } else if (si.isRollbackFailed) { - ipw.println("State: ROLLBACK FAILED"); + for (PackageInfo pi : packagesCache) { + if (packageName != null && !packageName.equals(pi.packageName)) { + continue; } + ipw.println(pi.packageName); + ipw.increaseIndent(); + ipw.println("Version: " + pi.versionCode); + ipw.println("Path: " + pi.applicationInfo.sourceDir); + ipw.println("IsActive: " + isActive(pi)); + ipw.println("IsFactory: " + isFactory(pi)); ipw.decreaseIndent(); } ipw.decreaseIndent(); - } catch (RemoteException e) { - ipw.println("Couldn't communicate with apexd."); + ipw.println(); + } + + @Override + void dump(PrintWriter pw, @Nullable String packageName) { + final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ", 120); + try { + populateAllPackagesCacheIfNeeded(); + ipw.println(); + ipw.println("Active APEX packages:"); + dumpFromPackagesCache(getActivePackages(), packageName, ipw); + ipw.println("Inactive APEX packages:"); + dumpFromPackagesCache(getInactivePackages(), packageName, ipw); + ipw.println("Factory APEX packages:"); + dumpFromPackagesCache(getFactoryPackages(), packageName, ipw); + ipw.increaseIndent(); + ipw.println("APEX session state:"); + ipw.increaseIndent(); + final ApexSessionInfo[] sessions = mApexService.getSessions(); + for (ApexSessionInfo si : sessions) { + ipw.println("Session ID: " + si.sessionId); + ipw.increaseIndent(); + if (si.isUnknown) { + ipw.println("State: UNKNOWN"); + } else if (si.isVerified) { + ipw.println("State: VERIFIED"); + } else if (si.isStaged) { + ipw.println("State: STAGED"); + } else if (si.isActivated) { + ipw.println("State: ACTIVATED"); + } else if (si.isActivationFailed) { + ipw.println("State: ACTIVATION FAILED"); + } else if (si.isSuccess) { + ipw.println("State: SUCCESS"); + } else if (si.isRollbackInProgress) { + ipw.println("State: ROLLBACK IN PROGRESS"); + } else if (si.isRolledBack) { + ipw.println("State: ROLLED BACK"); + } else if (si.isRollbackFailed) { + ipw.println("State: ROLLBACK FAILED"); + } + ipw.decreaseIndent(); + } + ipw.decreaseIndent(); + } catch (RemoteException e) { + ipw.println("Couldn't communicate with apexd."); + } } } - public void onBootCompleted() { - if (!isApexSupported()) return; - populateAllPackagesCacheIfNeeded(); + /** + * An implementation of {@link ApexManager} that should be used in case device does not support + * updating APEX packages. + */ + private static final class ApexManagerNoOp extends ApexManager { + + @Override + void systemReady() { + // No-op + } + + @Override + PackageInfo getPackageInfo(String packageName, int flags) { + return null; + } + + @Override + List getActivePackages() { + return Collections.emptyList(); + } + + @Override + List getFactoryPackages() { + return Collections.emptyList(); + } + + @Override + List getInactivePackages() { + return Collections.emptyList(); + } + + @Override + boolean isApexPackage(String packageName) { + return false; + } + + @Override + ApexSessionInfo getStagedSessionInfo(int sessionId) { + throw new UnsupportedOperationException(); + } + + @Override + boolean submitStagedSession(int sessionId, int[] childSessionIds, + ApexInfoList apexInfoList) { + throw new UnsupportedOperationException(); + } + + @Override + boolean markStagedSessionReady(int sessionId) { + throw new UnsupportedOperationException(); + } + + @Override + void markStagedSessionSuccessful(int sessionId) { + throw new UnsupportedOperationException(); + } + + @Override + boolean isApexSupported() { + return false; + } + + @Override + boolean abortActiveSession() { + throw new UnsupportedOperationException(); + } + + @Override + boolean uninstallApex(String apexPackagePath) { + throw new UnsupportedOperationException(); + } + + @Override + void dump(PrintWriter pw, String packageName) { + // No-op + } } } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 461743e9ba53..b4e8ea4945e1 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -2474,7 +2474,8 @@ public class PackageManagerService extends IPackageManager.Stub mProtectedPackages = new ProtectedPackages(mContext); - mApexManager = new ApexManager(context); + mApexManager = ApexManager.create(context); + // CHECKSTYLE:OFF IndentationCheck synchronized (mInstallLock) { // writer synchronized (mPackages) { -- cgit v1.2.3-59-g8ed1b From 529f3d2ff4c5d467432577c7b021c9a9222e2eaf Mon Sep 17 00:00:00 2001 From: Nikita Ioffe Date: Fri, 5 Jul 2019 15:49:45 +0100 Subject: Populate error message if apexd verification fails Test: atest CtsStagedInstallHostTestCases Test: adb install --wait system/apex/shim/com.android.apex.cts.shim.v2_wrong_sha.apex Bug: 136548037 Bug: 122952270 Change-Id: I5ceb6a6c37d1222358de9c6f3261da64f8010658 Merged-In: I5ea596ea6417825845c16bbb6e26e9c13845139b (cherry picked from commit 4e7d24a71a8ad25dc9b49c6da922702fb5ef11ff) --- .../java/com/android/server/pm/ApexManager.java | 30 ++++++++++++---------- .../java/com/android/server/pm/StagingManager.java | 11 +++----- 2 files changed, 21 insertions(+), 20 deletions(-) diff --git a/services/core/java/com/android/server/pm/ApexManager.java b/services/core/java/com/android/server/pm/ApexManager.java index 7e1bc79399b9..2301e3fb093d 100644 --- a/services/core/java/com/android/server/pm/ApexManager.java +++ b/services/core/java/com/android/server/pm/ApexManager.java @@ -29,6 +29,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; +import android.content.pm.PackageInstaller; import android.content.pm.PackageManager; import android.content.pm.PackageParser; import android.os.RemoteException; @@ -147,14 +148,10 @@ abstract class ApexManager { * @param childSessionIds if {@code sessionId} is a multi-package session, this should contain * an array of identifiers of all the child sessions. Otherwise it should * be an empty array. - * @param apexInfoList this is an output parameter, which needs to be initialized by tha caller - * and will be filled with a list of {@link ApexInfo} objects, each of which - * contains metadata about one of the packages being submitted as part of - * the session. - * @return whether the submission of the session was successful. + * @throws PackageManagerException if call to apexd fails */ - abstract boolean submitStagedSession( - int sessionId, @NonNull int[] childSessionIds, @NonNull ApexInfoList apexInfoList); + abstract ApexInfoList submitStagedSession(int sessionId, @NonNull int[] childSessionIds) + throws PackageManagerException; /** * Mark a staged session previously submitted using {@code submitStagedSession} as ready to be @@ -388,13 +385,19 @@ abstract class ApexManager { } @Override - boolean submitStagedSession( - int sessionId, @NonNull int[] childSessionIds, @NonNull ApexInfoList apexInfoList) { + ApexInfoList submitStagedSession(int sessionId, @NonNull int[] childSessionIds) + throws PackageManagerException { try { - return mApexService.submitStagedSession(sessionId, childSessionIds, apexInfoList); + final ApexInfoList apexInfoList = new ApexInfoList(); + mApexService.submitStagedSession(sessionId, childSessionIds, apexInfoList); + return apexInfoList; } catch (RemoteException re) { Slog.e(TAG, "Unable to contact apexservice", re); throw new RuntimeException(re); + } catch (Exception e) { + throw new PackageManagerException( + PackageInstaller.SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, + "apexd verification failed : " + e.getMessage()); } } @@ -566,9 +569,10 @@ abstract class ApexManager { } @Override - boolean submitStagedSession(int sessionId, int[] childSessionIds, - ApexInfoList apexInfoList) { - throw new UnsupportedOperationException(); + ApexInfoList submitStagedSession(int sessionId, int[] childSessionIds) + throws PackageManagerException { + throw new PackageManagerException(PackageManager.INSTALL_FAILED_INTERNAL_ERROR, + "Device doesn't support updating APEX"); } @Override diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java index 18bbfed3364f..8dad741ef366 100644 --- a/services/core/java/com/android/server/pm/StagingManager.java +++ b/services/core/java/com/android/server/pm/StagingManager.java @@ -155,13 +155,10 @@ public class StagingManager { } } } - final ApexInfoList apexInfoList = new ApexInfoList(); - boolean submittedToApexd = mApexManager.submitStagedSession(session.sessionId, - childSessionsIds.toArray(), apexInfoList); - if (!submittedToApexd) { - throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, - "APEX staging failed, check logcat messages from apexd for more details."); - } + // submitStagedSession will throw a PackageManagerException if apexd verification fails, + // which will be propagated to populate stagedSessionErrorMessage of this session. + final ApexInfoList apexInfoList = mApexManager.submitStagedSession(session.sessionId, + childSessionsIds.toArray()); final List result = new ArrayList<>(); for (ApexInfo newPackage : apexInfoList.apexInfos) { final PackageInfo pkg; -- cgit v1.2.3-59-g8ed1b From 0d167a0750f66982230ecd3171c3dc2500c71981 Mon Sep 17 00:00:00 2001 From: Nikita Ioffe Date: Tue, 9 Jul 2019 20:48:54 +0100 Subject: Populate error message if apexd.markStagedSessionReady fails Test: atest CtsStagedInstallHostTestCases Bug: 136548037 Bug: 122952270 Change-Id: Ia7823c4334f77b3cd3f66f1c6a9f3cc32250b805 Merged-In: I6fde70418990ee27c1966619badc437c9bffb697 (cherry picked from commit ab4d7354ea374fed6261e107be90bb349ea48c49) --- services/core/java/com/android/server/pm/ApexManager.java | 14 +++++++++----- .../core/java/com/android/server/pm/StagingManager.java | 12 ++++++++---- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/services/core/java/com/android/server/pm/ApexManager.java b/services/core/java/com/android/server/pm/ApexManager.java index 2301e3fb093d..dd099b15a464 100644 --- a/services/core/java/com/android/server/pm/ApexManager.java +++ b/services/core/java/com/android/server/pm/ApexManager.java @@ -158,9 +158,9 @@ abstract class ApexManager { * applied at next reboot. * * @param sessionId the identifier of the {@link PackageInstallerSession} being marked as ready. - * @return true upon success, false if the session is unknown. + * @throws PackageManagerException if call to apexd fails */ - abstract boolean markStagedSessionReady(int sessionId); + abstract void markStagedSessionReady(int sessionId) throws PackageManagerException; /** * Marks a staged session as successful. @@ -402,12 +402,16 @@ abstract class ApexManager { } @Override - boolean markStagedSessionReady(int sessionId) { + void markStagedSessionReady(int sessionId) throws PackageManagerException { try { - return mApexService.markStagedSessionReady(sessionId); + mApexService.markStagedSessionReady(sessionId); } catch (RemoteException re) { Slog.e(TAG, "Unable to contact apexservice", re); throw new RuntimeException(re); + } catch (Exception e) { + throw new PackageManagerException( + PackageInstaller.SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, + "Failed to mark apexd session as ready : " + e.getMessage()); } } @@ -576,7 +580,7 @@ abstract class ApexManager { } @Override - boolean markStagedSessionReady(int sessionId) { + void markStagedSessionReady(int sessionId) { throw new UnsupportedOperationException(); } diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java index 8dad741ef366..6ae66e631fed 100644 --- a/services/core/java/com/android/server/pm/StagingManager.java +++ b/services/core/java/com/android/server/pm/StagingManager.java @@ -278,10 +278,14 @@ public class StagingManager { // session as ready), then if a device gets rebooted right after the call to apexd, only // apex part of the train will be applied, leaving device in an inconsistent state. session.setStagedSessionReady(); - if (hasApex && !mApexManager.markStagedSessionReady(session.sessionId)) { - session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, - "APEX staging failed, check logcat messages from apexd for more " - + "details."); + if (!hasApex) { + // Session doesn't contain apex, nothing to do. + return; + } + try { + mApexManager.markStagedSessionReady(session.sessionId); + } catch (PackageManagerException e) { + session.setStagedSessionFailed(e.error, e.getMessage()); } } -- cgit v1.2.3-59-g8ed1b From 1138012b5488170879e9635e95499f72085fe8ca Mon Sep 17 00:00:00 2001 From: Oli Lan Date: Wed, 31 Jul 2019 15:27:22 +0100 Subject: Clean up generatePackageInfoFromApex() API This changes the API for generatePackageInfoFromApex in PackageParser in response to review suggestions made in ag/6701090. The method is renamed to generatePackageInfo with a PackageParser.Package parameter (as well as an ApexInfo and flags). The implementation is combined with the main generatePackageInfo method. Callers are changed to perform the parsing themselves, including the collection of certificates if necessary. Note: the method signature and implementation had already changed since ag/6701090, so the suggestions from that CL may not apply directly. Bug: 129261524 Test: atest PackageParserTest Test: atest CtsStagedInstallHostTestCases Change-Id: Iee213e025583c2201ff9d65c447e4278b925a2f0 Merged-In: I495539679812110f89d5e3d93f2622ba2dab36c0 (cherry picked from commit c2c7a22333b828cd3a630fe9efb1b18b0e6af289) --- core/java/android/content/pm/PackageParser.java | 130 +++++++++------------ .../src/android/content/pm/PackageParserTest.java | 7 +- .../java/com/android/server/pm/ApexManager.java | 49 ++++---- .../java/com/android/server/pm/StagingManager.java | 22 ++-- 4 files changed, 105 insertions(+), 103 deletions(-) diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index f15b5d75d616..5c5c13d566c5 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -617,21 +617,6 @@ public class PackageParser { return path.endsWith(APK_FILE_EXTENSION); } - /** - * Generate and return the {@link PackageInfo} for a parsed package. - * - * @param p the parsed package. - * @param flags indicating which optional information is included. - */ - @UnsupportedAppUsage - public static PackageInfo generatePackageInfo(PackageParser.Package p, - int gids[], int flags, long firstInstallTime, long lastUpdateTime, - Set grantedPermissions, PackageUserState state) { - - return generatePackageInfo(p, gids, flags, firstInstallTime, lastUpdateTime, - grantedPermissions, state, UserHandle.getCallingUserId()); - } - /** * Returns true if the package is installed and not hidden, or if the caller * explicitly wanted all uninstalled and hidden packages as well. @@ -658,8 +643,45 @@ public class PackageParser { return checkUseInstalledOrHidden(0, state, null); } + /** + * Generate and return the {@link PackageInfo} for a parsed package. + * + * @param p the parsed package. + * @param flags indicating which optional information is included. + */ + @UnsupportedAppUsage + public static PackageInfo generatePackageInfo(PackageParser.Package p, + int[] gids, int flags, long firstInstallTime, long lastUpdateTime, + Set grantedPermissions, PackageUserState state) { + + return generatePackageInfo(p, gids, flags, firstInstallTime, lastUpdateTime, + grantedPermissions, state, UserHandle.getCallingUserId()); + } + @UnsupportedAppUsage public static PackageInfo generatePackageInfo(PackageParser.Package p, + int[] gids, int flags, long firstInstallTime, long lastUpdateTime, + Set grantedPermissions, PackageUserState state, int userId) { + + return generatePackageInfo(p, null, gids, flags, firstInstallTime, lastUpdateTime, + grantedPermissions, state, userId); + } + + /** + * PackageInfo generator specifically for apex files. + * + * @param pkg Package to generate info from. Should be derived from an apex. + * @param apexInfo Apex info relating to the package. + * @return PackageInfo + * @throws PackageParserException + */ + public static PackageInfo generatePackageInfo( + PackageParser.Package pkg, ApexInfo apexInfo, int flags) { + return generatePackageInfo(pkg, apexInfo, EmptyArray.INT, flags, 0, 0, + Collections.emptySet(), new PackageUserState(), UserHandle.getCallingUserId()); + } + + private static PackageInfo generatePackageInfo(PackageParser.Package p, ApexInfo apexInfo, int gids[], int flags, long firstInstallTime, long lastUpdateTime, Set grantedPermissions, PackageUserState state, int userId) { if (!checkUseInstalledOrHidden(flags, state, p.applicationInfo) || !p.isMatch(flags)) { @@ -806,8 +828,27 @@ public class PackageParser { } } } + + if (apexInfo != null) { + File apexFile = new File(apexInfo.modulePath); + + pi.applicationInfo.sourceDir = apexFile.getPath(); + pi.applicationInfo.publicSourceDir = apexFile.getPath(); + if (apexInfo.isFactory) { + pi.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM; + } else { + pi.applicationInfo.flags &= ~ApplicationInfo.FLAG_SYSTEM; + } + if (apexInfo.isActive) { + pi.applicationInfo.flags |= ApplicationInfo.FLAG_INSTALLED; + } else { + pi.applicationInfo.flags &= ~ApplicationInfo.FLAG_INSTALLED; + } + pi.isApex = true; + } + // deprecated method of getting signing certificates - if ((flags&PackageManager.GET_SIGNATURES) != 0) { + if ((flags & PackageManager.GET_SIGNATURES) != 0) { if (p.mSigningDetails.hasPastSigningCertificates()) { // Package has included signing certificate rotation information. Return the oldest // cert so that programmatic checks keep working even if unaware of key rotation. @@ -8379,61 +8420,4 @@ public class PackageParser { } } - // TODO(b/129261524): Clean up API - /** - * PackageInfo parser specifically for apex files. - * NOTE: It will collect certificates - * - * @param apexInfo - * @return PackageInfo - * @throws PackageParserException - */ - public static PackageInfo generatePackageInfoFromApex(ApexInfo apexInfo, int flags) - throws PackageParserException { - PackageParser pp = new PackageParser(); - File apexFile = new File(apexInfo.modulePath); - final Package p = pp.parsePackage(apexFile, flags, false); - PackageUserState state = new PackageUserState(); - PackageInfo pi = generatePackageInfo(p, EmptyArray.INT, flags, 0, 0, - Collections.emptySet(), state); - pi.applicationInfo.sourceDir = apexFile.getPath(); - pi.applicationInfo.publicSourceDir = apexFile.getPath(); - if (apexInfo.isFactory) { - pi.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM; - } else { - pi.applicationInfo.flags &= ~ApplicationInfo.FLAG_SYSTEM; - } - if (apexInfo.isActive) { - pi.applicationInfo.flags |= ApplicationInfo.FLAG_INSTALLED; - } else { - pi.applicationInfo.flags &= ~ApplicationInfo.FLAG_INSTALLED; - } - pi.isApex = true; - - // Collect certificates - if ((flags & PackageManager.GET_SIGNING_CERTIFICATES) != 0) { - collectCertificates(p, apexFile, false); - // Keep legacy mechanism for handling signatures. While this is deprecated, it's - // still part of the public API and needs to be maintained - if (p.mSigningDetails.hasPastSigningCertificates()) { - // Package has included signing certificate rotation information. Return - // the oldest cert so that programmatic checks keep working even if unaware - // of key rotation. - pi.signatures = new Signature[1]; - pi.signatures[0] = p.mSigningDetails.pastSigningCertificates[0]; - } else if (p.mSigningDetails.hasSignatures()) { - // otherwise keep old behavior - int numberOfSigs = p.mSigningDetails.signatures.length; - pi.signatures = new Signature[numberOfSigs]; - System.arraycopy(p.mSigningDetails.signatures, 0, pi.signatures, 0, numberOfSigs); - } - if (p.mSigningDetails != SigningDetails.UNKNOWN) { - // only return a valid SigningInfo if there is signing information to report - pi.signingInfo = new SigningInfo(p.mSigningDetails); - } else { - pi.signingInfo = null; - } - } - return pi; - } } diff --git a/core/tests/coretests/src/android/content/pm/PackageParserTest.java b/core/tests/coretests/src/android/content/pm/PackageParserTest.java index 58c43ac2cf91..5e41355601e4 100644 --- a/core/tests/coretests/src/android/content/pm/PackageParserTest.java +++ b/core/tests/coretests/src/android/content/pm/PackageParserTest.java @@ -508,7 +508,12 @@ public class PackageParserTest { apexInfo.modulePath = apexFile.getPath(); apexInfo.versionCode = 191000070; int flags = PackageManager.GET_META_DATA | PackageManager.GET_SIGNING_CERTIFICATES; - PackageInfo pi = PackageParser.generatePackageInfoFromApex(apexInfo, flags); + + PackageParser pp = new PackageParser(); + Package p = pp.parsePackage(apexFile, flags, false); + PackageParser.collectCertificates(p, false); + PackageInfo pi = PackageParser.generatePackageInfo(p, apexInfo, flags); + assertEquals("com.google.android.tzdata", pi.applicationInfo.packageName); assertTrue(pi.applicationInfo.enabled); assertEquals(28, pi.applicationInfo.targetSdkVersion); diff --git a/services/core/java/com/android/server/pm/ApexManager.java b/services/core/java/com/android/server/pm/ApexManager.java index dd099b15a464..a1b6d492a4a3 100644 --- a/services/core/java/com/android/server/pm/ApexManager.java +++ b/services/core/java/com/android/server/pm/ApexManager.java @@ -282,30 +282,39 @@ abstract class ApexManager { if ((new File(ai.modulePath)).isDirectory()) { break; } + int flags = PackageManager.GET_META_DATA + | PackageManager.GET_SIGNING_CERTIFICATES + | PackageManager.GET_SIGNATURES; + PackageParser.Package pkg; try { - final PackageInfo pkg = PackageParser.generatePackageInfoFromApex( - ai, PackageManager.GET_META_DATA - | PackageManager.GET_SIGNING_CERTIFICATES); - mAllPackagesCache.add(pkg); - if (ai.isActive) { - if (activePackagesSet.contains(pkg.packageName)) { - throw new IllegalStateException( - "Two active packages have the same name: " - + pkg.packageName); - } - activePackagesSet.add(pkg.packageName); - } - if (ai.isFactory) { - if (factoryPackagesSet.contains(pkg.packageName)) { - throw new IllegalStateException( - "Two factory packages have the same name: " - + pkg.packageName); - } - factoryPackagesSet.add(pkg.packageName); - } + File apexFile = new File(ai.modulePath); + PackageParser pp = new PackageParser(); + pkg = pp.parsePackage(apexFile, flags, false); + PackageParser.collectCertificates(pkg, false); } catch (PackageParser.PackageParserException pe) { throw new IllegalStateException("Unable to parse: " + ai, pe); } + + final PackageInfo packageInfo = + PackageParser.generatePackageInfo(pkg, ai, flags); + mAllPackagesCache.add(packageInfo); + if (ai.isActive) { + if (activePackagesSet.contains(packageInfo.packageName)) { + throw new IllegalStateException( + "Two active packages have the same name: " + + packageInfo.packageName); + } + activePackagesSet.add(packageInfo.packageName); + } + if (ai.isFactory) { + if (factoryPackagesSet.contains(packageInfo.packageName)) { + throw new IllegalStateException( + "Two factory packages have the same name: " + + packageInfo.packageName); + } + factoryPackagesSet.add(packageInfo.packageName); + } + } } catch (RemoteException re) { Slog.e(TAG, "Unable to retrieve packages from apexservice: " + re.toString()); diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java index 6ae66e631fed..2705455078ff 100644 --- a/services/core/java/com/android/server/pm/StagingManager.java +++ b/services/core/java/com/android/server/pm/StagingManager.java @@ -160,25 +160,29 @@ public class StagingManager { final ApexInfoList apexInfoList = mApexManager.submitStagedSession(session.sessionId, childSessionsIds.toArray()); final List result = new ArrayList<>(); - for (ApexInfo newPackage : apexInfoList.apexInfos) { - final PackageInfo pkg; + for (ApexInfo apexInfo : apexInfoList.apexInfos) { + final PackageInfo packageInfo; + int flags = PackageManager.GET_META_DATA; + PackageParser.Package pkg; try { - pkg = PackageParser.generatePackageInfoFromApex(newPackage, - PackageManager.GET_META_DATA); + File apexFile = new File(apexInfo.modulePath); + PackageParser pp = new PackageParser(); + pkg = pp.parsePackage(apexFile, flags, false); } catch (PackageParserException e) { throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, - "Failed to parse APEX package " + newPackage.packagePath, e); + "Failed to parse APEX package " + apexInfo.modulePath, e); } - final PackageInfo activePackage = mApexManager.getPackageInfo(pkg.packageName, + packageInfo = PackageParser.generatePackageInfo(pkg, apexInfo, flags); + final PackageInfo activePackage = mApexManager.getPackageInfo(packageInfo.packageName, ApexManager.MATCH_ACTIVE_PACKAGE); if (activePackage == null) { - Slog.w(TAG, "Attempting to install new APEX package " + pkg.packageName); + Slog.w(TAG, "Attempting to install new APEX package " + packageInfo.packageName); throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, "It is forbidden to install new APEX packages."); } checkRequiredVersionCode(session, activePackage); - checkDowngrade(session, activePackage, pkg); - result.add(pkg); + checkDowngrade(session, activePackage, packageInfo); + result.add(packageInfo); } return result; } -- cgit v1.2.3-59-g8ed1b