From 941a8ba1a6043cf84a7bf622e44a0b4f7abd0178 Mon Sep 17 00:00:00 2001 From: Jeff Sharkey Date: Wed, 20 Aug 2014 16:26:32 -0700 Subject: [PATCH] Installing splits into ASECs! Sessions can now zero-copy data directly into pre-allocated ASEC containers. Then at commit time, we compute the total size of the final app, including any inherited APKs and unpacked libraries, and resize the container in one step. This supports both brand new ASEC installs and inheriting from existing ASEC installs. To keep things simple, it currently requires copying any inherited ASEC contents, but this could be optimized in the future. Expose new vold resize command, and allow read-write mounting of ASEC containers. Move native library extraction into the installer flow, since it needs to happen before ASEC is sealed. Move multiArch flag into NativeLibraryHelper, instead of making everyone pass it around. Migrate size calculation to shared location. Separate "other" package name in public API, provide a path to a storage device when relevant, and add more docs. Bug: 16514385 Change-Id: I06c6ce588d312ee7e64cce02733895d640b88456 --- api/current.txt | 2 + cmds/pm/src/com/android/commands/pm/Pm.java | 4 + .../android/content/pm/PackageInstaller.java | 69 ++- .../android/content/pm/PackageParser.java | 2 +- .../android/os/storage/IMountService.java | 48 ++- .../internal/content/NativeLibraryHelper.java | 49 ++- .../internal/content/PackageHelper.java | 81 +++- .../content/pm/PackageHelperTests.java | 8 +- .../src/android/os/storage/AsecTests.java | 2 +- libs/storage/IMountService.cpp | 2 + .../defcontainer/DefaultContainerService.java | 52 +-- .../java/com/android/server/MountService.java | 21 +- .../server/pm/PackageInstallerService.java | 72 ++-- .../server/pm/PackageInstallerSession.java | 401 +++++++++++++----- .../server/pm/PackageManagerService.java | 289 +++++++------ 15 files changed, 722 insertions(+), 380 deletions(-) diff --git a/api/current.txt b/api/current.txt index dfa95a002403f..093252dd9bda9 100644 --- a/api/current.txt +++ b/api/current.txt @@ -8680,10 +8680,12 @@ package android.content.pm { method public void removeSessionCallback(android.content.pm.PackageInstaller.SessionCallback); method public void uninstall(java.lang.String, android.content.IntentSender); field public static final java.lang.String ACTION_SESSION_DETAILS = "android.content.pm.action.SESSION_DETAILS"; + field public static final java.lang.String EXTRA_OTHER_PACKAGE_NAME = "android.content.pm.extra.OTHER_PACKAGE_NAME"; field public static final java.lang.String EXTRA_PACKAGE_NAME = "android.content.pm.extra.PACKAGE_NAME"; field public static final java.lang.String EXTRA_SESSION_ID = "android.content.pm.extra.SESSION_ID"; field public static final java.lang.String EXTRA_STATUS = "android.content.pm.extra.STATUS"; field public static final java.lang.String EXTRA_STATUS_MESSAGE = "android.content.pm.extra.STATUS_MESSAGE"; + field public static final java.lang.String EXTRA_STORAGE_PATH = "android.content.pm.extra.STORAGE_PATH"; field public static final int STATUS_FAILURE = 1; // 0x1 field public static final int STATUS_FAILURE_ABORTED = 3; // 0x3 field public static final int STATUS_FAILURE_BLOCKED = 2; // 0x2 diff --git a/cmds/pm/src/com/android/commands/pm/Pm.java b/cmds/pm/src/com/android/commands/pm/Pm.java index 46d8adeecd148..d9b40b102f9aa 100644 --- a/cmds/pm/src/com/android/commands/pm/Pm.java +++ b/cmds/pm/src/com/android/commands/pm/Pm.java @@ -1004,6 +1004,10 @@ public final class Pm { params.installFlags |= PackageManager.INSTALL_ALLOW_DOWNGRADE; } else if (opt.equals("-p")) { params.mode = SessionParams.MODE_INHERIT_EXISTING; + params.appPackageName = nextOptionData(); + if (params.appPackageName == null) { + throw new IllegalArgumentException("Missing inherit package name"); + } } else if (opt.equals("-S")) { params.setSize(Long.parseLong(nextOptionData())); } else if (opt.equals("--abi")) { diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java index 7419ebc83a310..9afdbf74cd585 100644 --- a/core/java/android/content/pm/PackageInstaller.java +++ b/core/java/android/content/pm/PackageInstaller.java @@ -45,6 +45,7 @@ import java.io.InputStream; import java.io.OutputStream; import java.security.MessageDigest; import java.util.ArrayList; +import java.util.Collections; import java.util.Iterator; import java.util.List; @@ -86,6 +87,8 @@ public class PackageInstaller { *

* In some cases, a matching Activity may not exist, so ensure you safeguard * against this. + *

+ * The session to show details for is defined in {@link #EXTRA_SESSION_ID}. */ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) public static final String ACTION_SESSION_DETAILS = "android.content.pm.action.SESSION_DETAILS"; @@ -95,22 +98,58 @@ public class PackageInstaller { ACTION_CONFIRM_PERMISSIONS = "android.content.pm.action.CONFIRM_PERMISSIONS"; /** - * An integer session ID. + * An integer session ID that an operation is working with. * - * @see #ACTION_SESSION_DETAILS + * @see Intent#getIntExtra(String, int) */ public static final String EXTRA_SESSION_ID = "android.content.pm.extra.SESSION_ID"; - public static final String EXTRA_STATUS = "android.content.pm.extra.STATUS"; - public static final String EXTRA_STATUS_MESSAGE = "android.content.pm.extra.STATUS_MESSAGE"; - /** - * Package name relevant to a status. + * Package name that an operation is working with. * * @see Intent#getStringExtra(String) */ public static final String EXTRA_PACKAGE_NAME = "android.content.pm.extra.PACKAGE_NAME"; + /** + * Current status of an operation. Will be one of + * {@link #STATUS_PENDING_USER_ACTION}, {@link #STATUS_SUCCESS}, + * {@link #STATUS_FAILURE}, {@link #STATUS_FAILURE_ABORTED}, + * {@link #STATUS_FAILURE_BLOCKED}, {@link #STATUS_FAILURE_CONFLICT}, + * {@link #STATUS_FAILURE_INCOMPATIBLE}, {@link #STATUS_FAILURE_INVALID}, or + * {@link #STATUS_FAILURE_STORAGE}. + *

+ * More information about a status may be available through additional + * extras; see the individual status documentation for details. + * + * @see Intent#getIntExtra(String, int) + */ + public static final String EXTRA_STATUS = "android.content.pm.extra.STATUS"; + + /** + * Detailed string representation of the status, including raw details that + * are useful for debugging. + * + * @see Intent#getStringExtra(String) + */ + public static final String EXTRA_STATUS_MESSAGE = "android.content.pm.extra.STATUS_MESSAGE"; + + /** + * Another package name relevant to a status. This is typically the package + * responsible for causing an operation failure. + * + * @see Intent#getStringExtra(String) + */ + public static final String + EXTRA_OTHER_PACKAGE_NAME = "android.content.pm.extra.OTHER_PACKAGE_NAME"; + + /** + * Storage path relevant to a status. + * + * @see Intent#getStringExtra(String) + */ + public static final String EXTRA_STORAGE_PATH = "android.content.pm.extra.STORAGE_PATH"; + /** {@hide} */ @Deprecated public static final String EXTRA_PACKAGE_NAMES = "android.content.pm.extra.PACKAGE_NAMES"; @@ -153,8 +192,12 @@ public class PackageInstaller { * The operation failed because it was blocked. For example, a device policy * may be blocking the operation, a package verifier may have blocked the * operation, or the app may be required for core system operation. + *

+ * The result may also contain {@link #EXTRA_OTHER_PACKAGE_NAME} with the + * specific package blocking the install. * * @see #EXTRA_STATUS_MESSAGE + * @see #EXTRA_OTHER_PACKAGE_NAME */ public static final int STATUS_FAILURE_BLOCKED = 2; @@ -182,10 +225,11 @@ public class PackageInstaller { * permission, incompatible certificates, etc. The user may be able to * uninstall another app to fix the issue. *

- * The result may also contain {@link #EXTRA_PACKAGE_NAME} with the + * The result may also contain {@link #EXTRA_OTHER_PACKAGE_NAME} with the * specific package identified as the cause of the conflict. * * @see #EXTRA_STATUS_MESSAGE + * @see #EXTRA_OTHER_PACKAGE_NAME */ public static final int STATUS_FAILURE_CONFLICT = 5; @@ -193,8 +237,12 @@ public class PackageInstaller { * The operation failed because of storage issues. For example, the device * may be running low on space, or external media may be unavailable. The * user may be able to help free space or insert different external media. + *

+ * The result may also contain {@link #EXTRA_STORAGE_PATH} with the path to + * the storage device that caused the failure. * * @see #EXTRA_STATUS_MESSAGE + * @see #EXTRA_STORAGE_PATH */ public static final int STATUS_FAILURE_STORAGE = 6; @@ -281,6 +329,13 @@ public class PackageInstaller { * To succeed, the caller must be the current home app. */ public @NonNull List getAllSessions() { + final ApplicationInfo info = mContext.getApplicationInfo(); + if ("com.google.android.googlequicksearchbox".equals(info.packageName) + && info.versionCode <= 300400070) { + Log.d(TAG, "Ignoring callback request from old prebuilt"); + return Collections.EMPTY_LIST; + } + try { return mInstaller.getAllSessions(mUserId); } catch (RemoteException e) { diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index eb8b7628160f3..142206abf6e72 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -264,7 +264,7 @@ public class PackageParser { public final boolean coreApp; public final boolean multiArch; - private PackageLite(String codePath, ApkLite baseApk, String[] splitNames, + public PackageLite(String codePath, ApkLite baseApk, String[] splitNames, String[] splitCodePaths) { this.packageName = baseApk.packageName; this.versionCode = baseApk.versionCode; diff --git a/core/java/android/os/storage/IMountService.java b/core/java/android/os/storage/IMountService.java index 939cda9027539..d1fadd67716bd 100644 --- a/core/java/android/os/storage/IMountService.java +++ b/core/java/android/os/storage/IMountService.java @@ -321,7 +321,7 @@ public interface IMountService extends IInterface { * Mount a secure container with the specified key and owner UID. * Returns an int consistent with MountServiceResultCode */ - public int mountSecureContainer(String id, String key, int ownerUid) + public int mountSecureContainer(String id, String key, int ownerUid, boolean readOnly) throws RemoteException { Parcel _data = Parcel.obtain(); Parcel _reply = Parcel.obtain(); @@ -331,6 +331,7 @@ public interface IMountService extends IInterface { _data.writeString(id); _data.writeString(key); _data.writeInt(ownerUid); + _data.writeInt(readOnly ? 1 : 0); mRemote.transact(Stub.TRANSACTION_mountSecureContainer, _data, _reply, 0); _reply.readException(); _result = _reply.readInt(); @@ -834,6 +835,27 @@ public interface IMountService extends IInterface { } return _result; } + + @Override + public int resizeSecureContainer(String id, int sizeMb, String key) + throws RemoteException { + Parcel _data = Parcel.obtain(); + Parcel _reply = Parcel.obtain(); + int _result; + try { + _data.writeInterfaceToken(DESCRIPTOR); + _data.writeString(id); + _data.writeInt(sizeMb); + _data.writeString(key); + mRemote.transact(Stub.TRANSACTION_resizeSecureContainer, _data, _reply, 0); + _reply.readException(); + _result = _reply.readInt(); + } finally { + _reply.recycle(); + _data.recycle(); + } + return _result; + } } private static final String DESCRIPTOR = "IMountService"; @@ -918,6 +940,8 @@ public interface IMountService extends IInterface { static final int TRANSACTION_getField = IBinder.FIRST_CALL_TRANSACTION + 39; + static final int TRANSACTION_resizeSecureContainer = IBinder.FIRST_CALL_TRANSACTION + 40; + /** * Cast an IBinder object into an IMountService interface, generating a * proxy if needed. @@ -1082,7 +1106,9 @@ public interface IMountService extends IInterface { key = data.readString(); int ownerUid; ownerUid = data.readInt(); - int resultCode = mountSecureContainer(id, key, ownerUid); + boolean readOnly; + readOnly = data.readInt() != 0; + int resultCode = mountSecureContainer(id, key, ownerUid, readOnly); reply.writeNoException(); reply.writeInt(resultCode); return true; @@ -1308,6 +1334,19 @@ public interface IMountService extends IInterface { reply.writeString(contents); return true; } + case TRANSACTION_resizeSecureContainer: { + data.enforceInterface(DESCRIPTOR); + String id; + id = data.readString(); + int sizeMb; + sizeMb = data.readInt(); + String key; + key = data.readString(); + int resultCode = resizeSecureContainer(id, sizeMb, key); + reply.writeNoException(); + reply.writeInt(resultCode); + return true; + } } return super.onTransact(code, data, reply, flags); } @@ -1405,7 +1444,8 @@ public interface IMountService extends IInterface { * Mount a secure container with the specified key and owner UID. Returns an * int consistent with MountServiceResultCode */ - public int mountSecureContainer(String id, String key, int ownerUid) throws RemoteException; + public int mountSecureContainer(String id, String key, int ownerUid, boolean readOnly) + throws RemoteException; /** * Mount external storage at given mount point. Returns an int consistent @@ -1571,4 +1611,6 @@ public interface IMountService extends IInterface { * @return contents of field */ public String getField(String field) throws RemoteException; + + public int resizeSecureContainer(String id, int sizeMb, String key) throws RemoteException; } diff --git a/core/java/com/android/internal/content/NativeLibraryHelper.java b/core/java/com/android/internal/content/NativeLibraryHelper.java index 179d5e892b425..02f675c3aaac4 100644 --- a/core/java/com/android/internal/content/NativeLibraryHelper.java +++ b/core/java/com/android/internal/content/NativeLibraryHelper.java @@ -25,6 +25,7 @@ import static android.system.OsConstants.S_IRWXU; import static android.system.OsConstants.S_IXGRP; import static android.system.OsConstants.S_IXOTH; +import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.PackageParser; import android.content.pm.PackageParser.Package; @@ -51,9 +52,11 @@ import java.util.List; */ public class NativeLibraryHelper { private static final String TAG = "NativeHelper"; - private static final boolean DEBUG_NATIVE = false; + public static final String LIB_DIR_NAME = "lib"; + public static final String LIB64_DIR_NAME = "lib64"; + // Special value for {@code PackageParser.Package#cpuAbiOverride} to indicate // that the cpuAbiOverride must be clear. public static final String CLEAR_ABI_OVERRIDE = "-"; @@ -70,6 +73,7 @@ public class NativeLibraryHelper { private volatile boolean mClosed; final long[] apkHandles; + final boolean multiArch; public static Handle create(File packageFile) throws IOException { try { @@ -81,14 +85,15 @@ public class NativeLibraryHelper { } public static Handle create(Package pkg) throws IOException { - return create(pkg.getAllCodePaths()); + return create(pkg.getAllCodePaths(), + (pkg.applicationInfo.flags & ApplicationInfo.FLAG_MULTIARCH) != 0); } public static Handle create(PackageLite lite) throws IOException { - return create(lite.getAllCodePaths()); + return create(lite.getAllCodePaths(), lite.multiArch); } - private static Handle create(List codePaths) throws IOException { + private static Handle create(List codePaths, boolean multiArch) throws IOException { final int size = codePaths.size(); final long[] apkHandles = new long[size]; for (int i = 0; i < size; i++) { @@ -103,11 +108,12 @@ public class NativeLibraryHelper { } } - return new Handle(apkHandles); + return new Handle(apkHandles, multiArch); } - Handle(long[] apkHandles) { + Handle(long[] apkHandles, boolean multiArch) { this.apkHandles = apkHandles; + this.multiArch = multiArch; mGuard.open("close"); } @@ -159,8 +165,7 @@ public class NativeLibraryHelper { * @return {@link PackageManager#INSTALL_SUCCEEDED} if successful or another * error code from that class if not */ - public static int copyNativeBinariesIfNeededLI(Handle handle, File sharedLibraryDir, - String abi) { + public static int copyNativeBinaries(Handle handle, File sharedLibraryDir, String abi) { for (long apkHandle : handle.apkHandles) { int res = nativeCopyNativeBinaries(apkHandle, sharedLibraryDir.getPath(), abi); if (res != INSTALL_SUCCEEDED) { @@ -267,7 +272,7 @@ public class NativeLibraryHelper { } } - private static long sumNativeBinaries(Handle handle, String[] abiList) { + private static long sumNativeBinariesForSupportedAbi(Handle handle, String[] abiList) { int abi = findSupportedAbi(handle, abiList); if (abi >= 0) { return sumNativeBinaries(handle, abiList[abi]); @@ -276,7 +281,7 @@ public class NativeLibraryHelper { } } - public static int copyNativeBinariesIfNeededLI(Handle handle, File libraryRoot, + public static int copyNativeBinariesForSupportedAbi(Handle handle, File libraryRoot, String[] abiList, boolean useIsaSubdir) throws IOException { createNativeLibrarySubdir(libraryRoot); @@ -300,7 +305,7 @@ public class NativeLibraryHelper { subDir = libraryRoot; } - int copyRet = copyNativeBinariesIfNeededLI(handle, subDir, abiList[abi]); + int copyRet = copyNativeBinaries(handle, subDir, abiList[abi]); if (copyRet != PackageManager.INSTALL_SUCCEEDED) { return copyRet; } @@ -309,10 +314,10 @@ public class NativeLibraryHelper { return abi; } - public static int copyNativeBinariesIfNeededLI(Handle handle, File libraryRoot, - String abiOverride, boolean multiArch) { + public static int copyNativeBinariesWithOverride(Handle handle, File libraryRoot, + String abiOverride) { try { - if (multiArch) { + if (handle.multiArch) { // Warn if we've set an abiOverride for multi-lib packages.. // By definition, we need to copy both 32 and 64 bit libraries for // such packages. @@ -322,7 +327,7 @@ public class NativeLibraryHelper { int copyRet = PackageManager.NO_NATIVE_LIBRARIES; if (Build.SUPPORTED_32_BIT_ABIS.length > 0) { - copyRet = copyNativeBinariesIfNeededLI(handle, libraryRoot, + copyRet = copyNativeBinariesForSupportedAbi(handle, libraryRoot, Build.SUPPORTED_32_BIT_ABIS, true /* use isa specific subdirs */); if (copyRet < 0 && copyRet != PackageManager.NO_NATIVE_LIBRARIES && copyRet != PackageManager.INSTALL_FAILED_NO_MATCHING_ABIS) { @@ -332,7 +337,7 @@ public class NativeLibraryHelper { } if (Build.SUPPORTED_64_BIT_ABIS.length > 0) { - copyRet = copyNativeBinariesIfNeededLI(handle, libraryRoot, + copyRet = copyNativeBinariesForSupportedAbi(handle, libraryRoot, Build.SUPPORTED_64_BIT_ABIS, true /* use isa specific subdirs */); if (copyRet < 0 && copyRet != PackageManager.NO_NATIVE_LIBRARIES && copyRet != PackageManager.INSTALL_FAILED_NO_MATCHING_ABIS) { @@ -355,7 +360,7 @@ public class NativeLibraryHelper { abiList = Build.SUPPORTED_32_BIT_ABIS; } - int copyRet = copyNativeBinariesIfNeededLI(handle, libraryRoot, abiList, + int copyRet = copyNativeBinariesForSupportedAbi(handle, libraryRoot, abiList, true /* use isa specific subdirs */); if (copyRet < 0 && copyRet != PackageManager.NO_NATIVE_LIBRARIES) { Slog.w(TAG, "Failure copying native libraries [errorCode=" + copyRet + "]"); @@ -370,10 +375,10 @@ public class NativeLibraryHelper { } } - public static long sumNativeBinaries(Handle handle, String abiOverride, boolean multiArch) + public static long sumNativeBinariesWithOverride(Handle handle, String abiOverride) throws IOException { long sum = 0; - if (multiArch) { + if (handle.multiArch) { // Warn if we've set an abiOverride for multi-lib packages.. // By definition, we need to copy both 32 and 64 bit libraries for // such packages. @@ -382,11 +387,11 @@ public class NativeLibraryHelper { } if (Build.SUPPORTED_32_BIT_ABIS.length > 0) { - sum += sumNativeBinaries(handle, Build.SUPPORTED_32_BIT_ABIS); + sum += sumNativeBinariesForSupportedAbi(handle, Build.SUPPORTED_32_BIT_ABIS); } if (Build.SUPPORTED_64_BIT_ABIS.length > 0) { - sum += sumNativeBinaries(handle, Build.SUPPORTED_64_BIT_ABIS); + sum += sumNativeBinariesForSupportedAbi(handle, Build.SUPPORTED_64_BIT_ABIS); } } else { String cpuAbiOverride = null; @@ -403,7 +408,7 @@ public class NativeLibraryHelper { abiList = Build.SUPPORTED_32_BIT_ABIS; } - sum += sumNativeBinaries(handle, abiList); + sum += sumNativeBinariesForSupportedAbi(handle, abiList); } return sum; } diff --git a/core/java/com/android/internal/content/PackageHelper.java b/core/java/com/android/internal/content/PackageHelper.java index a529cc395038a..c17f4eeb41f4f 100644 --- a/core/java/com/android/internal/content/PackageHelper.java +++ b/core/java/com/android/internal/content/PackageHelper.java @@ -16,11 +16,14 @@ package com.android.internal.content; +import static android.net.TrafficStats.MB_IN_BYTES; + import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.PackageParser.PackageLite; import android.os.Environment; import android.os.Environment.UserEnvironment; import android.os.FileUtils; @@ -77,9 +80,10 @@ public class PackageHelper { } } - public static String createSdDir(int sizeMb, String cid, String sdEncKey, int uid, + public static String createSdDir(long sizeBytes, String cid, String sdEncKey, int uid, boolean isExternal) { - // Create mount point via MountService + // Round up to nearest MB, plus another MB for filesystem overhead + final int sizeMb = (int) ((sizeBytes + MB_IN_BYTES) / MB_IN_BYTES) + 1; try { IMountService mountService = getMountService(); @@ -102,19 +106,39 @@ public class PackageHelper { return null; } - public static String mountSdDir(String cid, String key, int ownerUid) { - try { - int rc = getMountService().mountSecureContainer(cid, key, ownerUid); - if (rc != StorageResultCode.OperationSucceeded) { - Log.i(TAG, "Failed to mount container " + cid + " rc : " + rc); - return null; + public static boolean resizeSdDir(long sizeBytes, String cid, String sdEncKey) { + // Round up to nearest MB, plus another MB for filesystem overhead + final int sizeMb = (int) ((sizeBytes + MB_IN_BYTES) / MB_IN_BYTES) + 1; + try { + IMountService mountService = getMountService(); + int rc = mountService.resizeSecureContainer(cid, sizeMb, sdEncKey); + if (rc == StorageResultCode.OperationSucceeded) { + return true; + } + } catch (RemoteException e) { + Log.e(TAG, "MountService running?"); } - return getMountService().getSecureContainerPath(cid); - } catch (RemoteException e) { - Log.e(TAG, "MountService running?"); + Log.e(TAG, "Failed to create secure container " + cid); + return false; + } + + public static String mountSdDir(String cid, String key, int ownerUid) { + return mountSdDir(cid, key, ownerUid, true); + } + + public static String mountSdDir(String cid, String key, int ownerUid, boolean readOnly) { + try { + int rc = getMountService().mountSecureContainer(cid, key, ownerUid, readOnly); + if (rc != StorageResultCode.OperationSucceeded) { + Log.i(TAG, "Failed to mount container " + cid + " rc : " + rc); + return null; + } + return getMountService().getSecureContainerPath(cid); + } catch (RemoteException e) { + Log.e(TAG, "MountService running?"); + } + return null; } - return null; - } public static boolean unMountSdDir(String cid) { try { @@ -400,6 +424,37 @@ public class PackageHelper { } } + public static long calculateInstalledSize(PackageLite pkg, boolean isForwardLocked, + String abiOverride) throws IOException { + NativeLibraryHelper.Handle handle = null; + try { + handle = NativeLibraryHelper.Handle.create(pkg); + return calculateInstalledSize(pkg, handle, isForwardLocked, abiOverride); + } finally { + IoUtils.closeQuietly(handle); + } + } + + public static long calculateInstalledSize(PackageLite pkg, NativeLibraryHelper.Handle handle, + boolean isForwardLocked, String abiOverride) throws IOException { + long sizeBytes = 0; + + // Include raw APKs, and possibly unpacked resources + for (String codePath : pkg.getAllCodePaths()) { + final File codeFile = new File(codePath); + sizeBytes += codeFile.length(); + + if (isForwardLocked) { + sizeBytes += PackageHelper.extractPublicFiles(codeFile, null); + } + } + + // Include all relevant native code + sizeBytes += NativeLibraryHelper.sumNativeBinariesWithOverride(handle, abiOverride); + + return sizeBytes; + } + public static String replaceEnd(String str, String before, String after) { if (!str.endsWith(before)) { throw new IllegalArgumentException( diff --git a/core/tests/coretests/src/android/content/pm/PackageHelperTests.java b/core/tests/coretests/src/android/content/pm/PackageHelperTests.java index 7ad35d082f591..06c495e66b6af 100644 --- a/core/tests/coretests/src/android/content/pm/PackageHelperTests.java +++ b/core/tests/coretests/src/android/content/pm/PackageHelperTests.java @@ -16,7 +16,7 @@ package android.content.pm; -import com.android.internal.content.PackageHelper; +import static android.net.TrafficStats.MB_IN_BYTES; import android.os.IBinder; import android.os.RemoteException; @@ -25,6 +25,8 @@ import android.os.storage.IMountService; import android.test.AndroidTestCase; import android.util.Log; +import com.android.internal.content.PackageHelper; + public class PackageHelperTests extends AndroidTestCase { private static final boolean localLOGV = true; public static final String TAG = "PackageHelperTests"; @@ -81,8 +83,8 @@ public class PackageHelperTests extends AndroidTestCase { public void testMountAndPullSdCard() { try { fullId = PREFIX; - fullId2 = PackageHelper.createSdDir(1024, fullId, "none", android.os.Process.myUid(), - true); + fullId2 = PackageHelper.createSdDir(1024 * MB_IN_BYTES, fullId, "none", + android.os.Process.myUid(), true); Log.d(TAG,PackageHelper.getSdDir(fullId)); PackageHelper.unMountSdDir(fullId); diff --git a/core/tests/coretests/src/android/os/storage/AsecTests.java b/core/tests/coretests/src/android/os/storage/AsecTests.java index abb8eaeb8f272..4f724fe5288bd 100644 --- a/core/tests/coretests/src/android/os/storage/AsecTests.java +++ b/core/tests/coretests/src/android/os/storage/AsecTests.java @@ -90,7 +90,7 @@ public class AsecTests extends AndroidTestCase { String fullId = SECURE_CONTAINER_PREFIX + localId; IMountService ms = getMs(); - return ms.mountSecureContainer(fullId, key, android.os.Process.myUid()); + return ms.mountSecureContainer(fullId, key, android.os.Process.myUid(), true); } private int renameContainer(String localId1, String localId2) throws Exception { diff --git a/libs/storage/IMountService.cpp b/libs/storage/IMountService.cpp index 5701678b3482a..621de18b1685e 100644 --- a/libs/storage/IMountService.cpp +++ b/libs/storage/IMountService.cpp @@ -295,6 +295,8 @@ public: data.writeString16(id); data.writeString16(key); data.writeInt32(ownerUid); + // Assume read-only + data.writeInt32(1); if (remote()->transact(TRANSACTION_mountSecureContainer, data, &reply) != NO_ERROR) { ALOGD("mountSecureContainer couldn't call remote"); return -1; diff --git a/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java b/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java index fae30e589c10c..1f28324242b4f 100644 --- a/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java +++ b/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java @@ -16,7 +16,7 @@ package com.android.defcontainer; -import static android.net.TrafficStats.MB_IN_BYTES; +import static com.android.internal.content.NativeLibraryHelper.LIB_DIR_NAME; import android.app.IntentService; import android.content.Context; @@ -67,8 +67,6 @@ import java.io.OutputStream; public class DefaultContainerService extends IntentService { private static final String TAG = "DefContainer"; - private static final String LIB_DIR_NAME = "lib"; - // TODO: migrate native code unpacking to always be a derivative work private IMediaContainerService.Stub mBinder = new IMediaContainerService.Stub() { @@ -168,7 +166,7 @@ public class DefaultContainerService extends IntentService { final long sizeBytes; try { pkg = PackageParser.parsePackageLite(packageFile, 0); - sizeBytes = calculateInstalledSizeInner(pkg, isForwardLocked, abiOverride); + sizeBytes = PackageHelper.calculateInstalledSize(pkg, isForwardLocked, abiOverride); } catch (PackageParserException | IOException e) { Slog.w(TAG, "Failed to parse package at " + packagePath + ": " + e); @@ -253,7 +251,7 @@ public class DefaultContainerService extends IntentService { final PackageParser.PackageLite pkg; try { pkg = PackageParser.parsePackageLite(packageFile, 0); - return calculateInstalledSizeInner(pkg, isForwardLocked, abiOverride); + return PackageHelper.calculateInstalledSize(pkg, isForwardLocked, abiOverride); } catch (PackageParserException | IOException e) { Slog.w(TAG, "Failed to calculate installed size: " + e); return Long.MAX_VALUE; @@ -315,13 +313,12 @@ public class DefaultContainerService extends IntentService { // Calculate container size, rounding up to nearest MB and adding an // extra MB for filesystem overhead - final long sizeBytes = calculateInstalledSizeInner(pkg, handle, isForwardLocked, - abiOverride); - final int sizeMb = ((int) ((sizeBytes + MB_IN_BYTES) / MB_IN_BYTES)) + 1; + final long sizeBytes = PackageHelper.calculateInstalledSize(pkg, handle, + isForwardLocked, abiOverride); // Create new container - final String newMountPath = PackageHelper.createSdDir(sizeMb, newCid, key, Process.myUid(), - isExternal); + final String newMountPath = PackageHelper.createSdDir(sizeBytes, newCid, key, + Process.myUid(), isExternal); if (newMountPath == null) { throw new IOException("Failed to create container " + newCid); } @@ -339,8 +336,8 @@ public class DefaultContainerService extends IntentService { // Extract native code final File libraryRoot = new File(targetDir, LIB_DIR_NAME); - final int res = NativeLibraryHelper.copyNativeBinariesIfNeededLI(handle, libraryRoot, - abiOverride, pkg.multiArch); + final int res = NativeLibraryHelper.copyNativeBinariesWithOverride(handle, libraryRoot, + abiOverride); if (res != PackageManager.INSTALL_SUCCEEDED) { throw new IOException("Failed to extract native code, res=" + res); } @@ -415,35 +412,4 @@ public class DefaultContainerService extends IntentService { Os.chmod(targetFile.getAbsolutePath(), 0644); } } - - private long calculateInstalledSizeInner(PackageLite pkg, boolean isForwardLocked, - String abiOverride) throws IOException { - NativeLibraryHelper.Handle handle = null; - try { - handle = NativeLibraryHelper.Handle.create(pkg); - return calculateInstalledSizeInner(pkg, handle, isForwardLocked, abiOverride); - } finally { - IoUtils.closeQuietly(handle); - } - } - - private long calculateInstalledSizeInner(PackageLite pkg, NativeLibraryHelper.Handle handle, - boolean isForwardLocked, String abiOverride) throws IOException { - long sizeBytes = 0; - - // Include raw APKs, and possibly unpacked resources - for (String codePath : pkg.getAllCodePaths()) { - final File codeFile = new File(codePath); - sizeBytes += codeFile.length(); - - if (isForwardLocked) { - sizeBytes += PackageHelper.extractPublicFiles(codeFile, null); - } - } - - // Include all relevant native code - sizeBytes += NativeLibraryHelper.sumNativeBinaries(handle, abiOverride, pkg.multiArch); - - return sizeBytes; - } } diff --git a/services/core/java/com/android/server/MountService.java b/services/core/java/com/android/server/MountService.java index 50f2ae9ce2663..ea24d7c9543c8 100644 --- a/services/core/java/com/android/server/MountService.java +++ b/services/core/java/com/android/server/MountService.java @@ -33,7 +33,6 @@ import android.content.res.Resources; import android.content.res.TypedArray; import android.content.res.XmlResourceParser; import android.hardware.usb.UsbManager; -import android.app.admin.DevicePolicyManager; import android.net.Uri; import android.os.Binder; import android.os.Environment; @@ -1761,6 +1760,21 @@ class MountService extends IMountService.Stub return rc; } + @Override + public int resizeSecureContainer(String id, int sizeMb, String key) { + validatePermission(android.Manifest.permission.ASEC_CREATE); + waitForReady(); + warnOnNotMounted(); + + int rc = StorageResultCode.OperationSucceeded; + try { + mConnector.execute("asec", "resize", id, sizeMb, new SensitiveArg(key)); + } catch (NativeDaemonConnectorException e) { + rc = StorageResultCode.OperationFailedInternalError; + } + return rc; + } + public int finalizeSecureContainer(String id) { validatePermission(android.Manifest.permission.ASEC_CREATE); warnOnNotMounted(); @@ -1835,7 +1849,7 @@ class MountService extends IMountService.Stub return rc; } - public int mountSecureContainer(String id, String key, int ownerUid) { + public int mountSecureContainer(String id, String key, int ownerUid, boolean readOnly) { validatePermission(android.Manifest.permission.ASEC_MOUNT_UNMOUNT); waitForReady(); warnOnNotMounted(); @@ -1848,7 +1862,8 @@ class MountService extends IMountService.Stub int rc = StorageResultCode.OperationSucceeded; try { - mConnector.execute("asec", "mount", id, new SensitiveArg(key), ownerUid); + mConnector.execute("asec", "mount", id, new SensitiveArg(key), ownerUid, + readOnly ? "ro" : "rw"); } catch (NativeDaemonConnectorException e) { int code = e.getCode(); if (code != VoldResponseCode.OpFailedStorageBusy) { diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java index 5e802de7a6b7f..6f60d248e3347 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerService.java +++ b/services/core/java/com/android/server/pm/PackageInstallerService.java @@ -19,7 +19,6 @@ package com.android.server.pm; import static android.content.pm.PackageManager.INSTALL_ALL_USERS; import static android.content.pm.PackageManager.INSTALL_FROM_ADB; import static android.content.pm.PackageManager.INSTALL_REPLACE_EXISTING; -import static android.net.TrafficStats.MB_IN_BYTES; import static com.android.internal.util.XmlUtils.readBitmapAttribute; import static com.android.internal.util.XmlUtils.readBooleanAttribute; import static com.android.internal.util.XmlUtils.readIntAttribute; @@ -48,8 +47,11 @@ import android.content.pm.IPackageInstaller; import android.content.pm.IPackageInstallerCallback; import android.content.pm.IPackageInstallerSession; import android.content.pm.PackageInstaller; +import android.content.pm.PackageParser; import android.content.pm.PackageInstaller.SessionInfo; import android.content.pm.PackageInstaller.SessionParams; +import android.content.pm.PackageParser.PackageLite; +import android.content.pm.PackageParser.PackageParserException; import android.content.pm.PackageManager; import android.graphics.Bitmap; import android.net.Uri; @@ -208,7 +210,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub { // Ignore stages claimed by active sessions for (int i = 0; i < mSessions.size(); i++) { final PackageInstallerSession session = mSessions.valueAt(i); - unclaimed.remove(session.internalStageDir); + unclaimed.remove(session.stageDir); } // Clean up orphaned staging directories @@ -234,7 +236,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub { // Ignore stages claimed by active sessions for (int i = 0; i < mSessions.size(); i++) { final PackageInstallerSession session = mSessions.valueAt(i); - final String cid = session.externalStageCid; + final String cid = session.stageCid; if (unclaimed.remove(cid)) { // Claimed by active session, mount it @@ -304,10 +306,10 @@ public class PackageInstallerService extends IPackageInstaller.Stub { Slog.w(TAG, "Abandoning old session first created at " + session.createdMillis); valid = false; - } else if (session.internalStageDir != null - && !session.internalStageDir.exists()) { + } else if (session.stageDir != null + && !session.stageDir.exists()) { Slog.w(TAG, "Abandoning internal session with missing stage " - + session.internalStageDir); + + session.stageDir); valid = false; } else { valid = true; @@ -401,12 +403,12 @@ public class PackageInstallerService extends IPackageInstaller.Stub { writeStringAttribute(out, ATTR_INSTALLER_PACKAGE_NAME, session.installerPackageName); writeLongAttribute(out, ATTR_CREATED_MILLIS, session.createdMillis); - if (session.internalStageDir != null) { + if (session.stageDir != null) { writeStringAttribute(out, ATTR_SESSION_STAGE_DIR, - session.internalStageDir.getAbsolutePath()); + session.stageDir.getAbsolutePath()); } - if (session.externalStageCid != null) { - writeStringAttribute(out, ATTR_SESSION_STAGE_CID, session.externalStageCid); + if (session.stageCid != null) { + writeStringAttribute(out, ATTR_SESSION_STAGE_CID, session.stageCid); } writeBooleanAttribute(out, ATTR_SEALED, session.isSealed()); @@ -479,6 +481,8 @@ public class PackageInstallerService extends IPackageInstaller.Stub { } } + // TODO: treat INHERIT_EXISTING as install for user + // Figure out where we're going to be staging session data final boolean stageInternal; @@ -502,22 +506,36 @@ public class PackageInstallerService extends IPackageInstaller.Stub { Binder.restoreCallingIdentity(ident); } } else if (params.mode == SessionParams.MODE_INHERIT_EXISTING) { - // We always stage inheriting sessions on internal storage first, - // since we don't want to grow containers until we're sure that - // everything looks legit. - stageInternal = true; - checkInternalStorage(params.sizeBytes); - - // If we have a good hunch we'll end up on external storage, verify - // free space there too. - final ApplicationInfo info = mPm.getApplicationInfo(params.appPackageName, 0, + // Inheriting existing install, so stay on the same storage medium. + final ApplicationInfo existingApp = mPm.getApplicationInfo(params.appPackageName, 0, userId); - if (info != null && (info.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0) { - checkExternalStorage(params.sizeBytes); - - throw new UnsupportedOperationException("TODO: finish fleshing out ASEC support"); + if (existingApp == null) { + throw new IllegalStateException( + "Missing existing app " + params.appPackageName); } + final long existingSize; + try { + final PackageLite existingPkg = PackageParser.parsePackageLite( + new File(existingApp.getCodePath()), 0); + existingSize = PackageHelper.calculateInstalledSize(existingPkg, false, + params.abiOverride); + } catch (PackageParserException e) { + throw new IllegalStateException( + "Failed to calculate size of " + params.appPackageName); + } + + if ((existingApp.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) == 0) { + // Internal we can link existing install into place, so we only + // need enough space for the new data. + checkInternalStorage(params.sizeBytes); + stageInternal = true; + } else { + // External we're going to copy existing install into our + // container, so we need footprint of both. + checkExternalStorage(params.sizeBytes + existingSize); + stageInternal = false; + } } else { throw new IllegalArgumentException("Invalid install mode: " + params.mode); } @@ -641,11 +659,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub { } final String cid = "smdl" + sessionId + ".tmp"; - - // Round up to nearest MB, plus another MB for filesystem overhead - final int sizeMb = (int) ((sizeBytes + MB_IN_BYTES) / MB_IN_BYTES) + 1; - - if (PackageHelper.createSdDir(sizeMb, cid, PackageManagerService.getEncryptKey(), + if (PackageHelper.createSdDir(sizeBytes, cid, PackageManagerService.getEncryptKey(), Process.SYSTEM_UID, true) == null) { throw new IOException("Failed to create ASEC"); } @@ -857,7 +871,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub { final String existing = extras.getString( PackageManager.EXTRA_FAILURE_EXISTING_PACKAGE); if (!TextUtils.isEmpty(existing)) { - fillIn.putExtra(PackageInstaller.EXTRA_PACKAGE_NAME, existing); + fillIn.putExtra(PackageInstaller.EXTRA_OTHER_PACKAGE_NAME, existing); } } try { diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index 38a2016b1497d..5264fc4b13fd8 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -19,6 +19,7 @@ package com.android.server.pm; import static android.content.pm.PackageManager.INSTALL_FAILED_ABORTED; import static android.content.pm.PackageManager.INSTALL_FAILED_ALREADY_EXISTS; import static android.content.pm.PackageManager.INSTALL_FAILED_CONTAINER_ERROR; +import static android.content.pm.PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE; import static android.content.pm.PackageManager.INSTALL_FAILED_INTERNAL_ERROR; import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_APK; import static android.content.pm.PackageManager.INSTALL_FAILED_PACKAGE_CHANGED; @@ -38,6 +39,7 @@ import android.content.pm.PackageInstaller.SessionParams; import android.content.pm.PackageManager; import android.content.pm.PackageParser; import android.content.pm.PackageParser.ApkLite; +import android.content.pm.PackageParser.PackageLite; import android.content.pm.PackageParser.PackageParserException; import android.content.pm.Signature; import android.os.Bundle; @@ -47,6 +49,7 @@ import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.ParcelFileDescriptor; +import android.os.Process; import android.os.RemoteException; import android.os.UserHandle; import android.system.ErrnoException; @@ -59,18 +62,21 @@ import android.util.MathUtils; import android.util.Slog; import com.android.internal.annotations.GuardedBy; +import com.android.internal.content.NativeLibraryHelper; import com.android.internal.content.PackageHelper; import com.android.internal.util.ArrayUtils; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.Preconditions; import com.android.server.pm.PackageInstallerService.PackageInstallObserverAdapter; +import libcore.io.IoUtils; import libcore.io.Libcore; import java.io.File; import java.io.FileDescriptor; import java.io.IOException; import java.util.ArrayList; +import java.util.List; import java.util.concurrent.atomic.AtomicInteger; public class PackageInstallerSession extends IPackageInstallerSession.Stub { @@ -95,18 +101,9 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { final SessionParams params; final long createdMillis; - /** Internal location where staged data is written. */ - final File internalStageDir; - /** External container where staged data is written. */ - final String externalStageCid; - - /** - * When a {@link SessionParams#MODE_INHERIT_EXISTING} session is installed - * into an ASEC, this is the container where the stage is combined with the - * existing install. - */ - // TODO: persist this cid once we start splicing - String combinedCid; + /** Staging location where client data is written. */ + final File stageDir; + final String stageCid; /** Note that UID is not persisted; it's always derived at runtime. */ final int installerUid; @@ -132,20 +129,6 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { private int mFinalStatus; private String mFinalMessage; - @GuardedBy("mLock") - private File mResolvedStageDir; - - /** - * Path to the resolved base APK for this session, which may point at an APK - * inside the session (when the session defines the base), or it may point - * at the existing base APK (when adding splits to an existing app). - *

- * This is used when confirming permissions, since we can't fully stage the - * session inside an ASEC before confirming with user. - */ - @GuardedBy("mLock") - private String mResolvedBaseCodePath; - @GuardedBy("mLock") private ArrayList mBridges = new ArrayList<>(); @@ -157,6 +140,25 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { private int mVersionCode; private Signature[] mSignatures; + /** + * Path to the validated base APK for this session, which may point at an + * APK inside the session (when the session defines the base), or it may + * point at the existing base APK (when adding splits to an existing app). + *

+ * This is used when confirming permissions, since we can't fully stage the + * session inside an ASEC before confirming with user. + */ + @GuardedBy("mLock") + private File mResolvedBaseFile; + + @GuardedBy("mLock") + private File mResolvedStageDir; + + @GuardedBy("mLock") + private final List mResolvedStagedFiles = new ArrayList<>(); + @GuardedBy("mLock") + private final List mResolvedInheritedFiles = new ArrayList<>(); + private final Handler.Callback mHandlerCallback = new Handler.Callback() { @Override public boolean handleMessage(Message msg) { @@ -168,9 +170,10 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { try { commitLocked(); } catch (PackageManagerException e) { - Slog.e(TAG, "Install failed: " + e); + final String completeMsg = ExceptionUtils.getCompleteMessage(e); + Slog.e(TAG, "Commit of session " + sessionId + " failed: " + completeMsg); destroyInternal(); - dispatchSessionFinished(e.error, e.getMessage(), null); + dispatchSessionFinished(e.error, completeMsg, null); } return true; @@ -181,7 +184,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { public PackageInstallerSession(PackageInstallerService.InternalCallback callback, Context context, PackageManagerService pm, Looper looper, int sessionId, int userId, String installerPackageName, SessionParams params, long createdMillis, - File internalStageDir, String externalStageCid, boolean sealed) { + File stageDir, String stageCid, boolean sealed) { mCallback = callback; mContext = context; mPm = pm; @@ -192,12 +195,12 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { this.installerPackageName = installerPackageName; this.params = params; this.createdMillis = createdMillis; - this.internalStageDir = internalStageDir; - this.externalStageCid = externalStageCid; + this.stageDir = stageDir; + this.stageCid = stageCid; - if ((internalStageDir == null) == (externalStageCid == null)) { + if ((stageDir == null) == (stageCid == null)) { throw new IllegalArgumentException( - "Exactly one of internal or external stage must be set"); + "Exactly one of stageDir or stageCid stage must be set"); } mSealed = sealed; @@ -220,7 +223,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { synchronized (mLock) { info.sessionId = sessionId; info.installerPackageName = installerPackageName; - info.resolvedBaseCodePath = mResolvedBaseCodePath; + info.resolvedBaseCodePath = (mResolvedBaseFile != null) ? + mResolvedBaseFile.getAbsolutePath() : null; info.progress = mProgress; info.sealed = mSealed; info.open = mOpenCount.get() > 0; @@ -253,18 +257,17 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { * might point at an ASEC mount point, which is why we delay path resolution * until someone actively works with the session. */ - private File getStageDir() throws IOException { + private File resolveStageDir() throws IOException { synchronized (mLock) { if (mResolvedStageDir == null) { - if (internalStageDir != null) { - mResolvedStageDir = internalStageDir; + if (stageDir != null) { + mResolvedStageDir = stageDir; } else { - final String path = PackageHelper.getSdDir(externalStageCid); + final String path = PackageHelper.getSdDir(stageCid); if (path != null) { mResolvedStageDir = new File(path); } else { - throw new IOException( - "Failed to resolve container path for " + externalStageCid); + throw new IOException("Failed to resolve path to container " + stageCid); } } } @@ -306,7 +309,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { public String[] getNames() { assertNotSealed("getNames"); try { - return getStageDir().list(); + return resolveStageDir().list(); } catch (IOException e) { throw ExceptionUtils.wrap(e); } @@ -339,8 +342,10 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { if (!FileUtils.isValidExtFilename(name)) { throw new IllegalArgumentException("Invalid name: " + name); } - final File target = new File(getStageDir(), name); + final File target = new File(resolveStageDir(), name); + // TODO: this should delegate to DCS so the system process avoids + // holding open FDs into containers. final FileDescriptor targetFd = Libcore.os.open(target.getAbsolutePath(), O_CREAT | O_WRONLY, 0644); Os.chmod(target.getAbsolutePath(), 0644); @@ -350,7 +355,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { if (lengthBytes > 0) { final StructStat stat = Libcore.os.fstat(targetFd); final long deltaBytes = lengthBytes - stat.st_size; - if (deltaBytes > 0) { + // Only need to free up space when writing to internal stage + if (stageDir != null && deltaBytes > 0) { mPm.freeStorage(deltaBytes); } Libcore.os.posix_fallocate(targetFd, 0, lengthBytes); @@ -385,7 +391,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { if (!FileUtils.isValidExtFilename(name)) { throw new IllegalArgumentException("Invalid name: " + name); } - final File target = new File(getStageDir(), name); + final File target = new File(resolveStageDir(), name); final FileDescriptor targetFd = Libcore.os.open(target.getAbsolutePath(), O_RDONLY, 0); return new ParcelFileDescriptor(targetFd); @@ -424,22 +430,21 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { mCallback.onSessionSealed(this); } - final File stageDir; try { - stageDir = getStageDir(); + resolveStageDir(); } catch (IOException e) { throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR, - "Failed to resolve stage dir", e); + "Failed to resolve stage location", e); } // Verify that stage looks sane with respect to existing application. // This currently only ensures packageName, versionCode, and certificate // consistency. - validateInstallLocked(stageDir); + validateInstallLocked(); Preconditions.checkNotNull(mPackageName); Preconditions.checkNotNull(mSignatures); - Preconditions.checkNotNull(mResolvedBaseCodePath); + Preconditions.checkNotNull(mResolvedBaseFile); if (!mPermissionsAccepted) { // User needs to accept permissions; give installer an intent they @@ -454,17 +459,41 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { return; } + if (stageCid != null) { + // Figure out the final installed size and resize the container once + // and for all. Internally the parser handles straddling between two + // locations when inheriting. + final long finalSize = calculateInstalledSize(); + resizeContainer(stageCid, finalSize); + } + // Inherit any packages and native libraries from existing install that // haven't been overridden. if (params.mode == SessionParams.MODE_INHERIT_EXISTING) { - // TODO: implement splicing into existing ASEC - spliceExistingFilesIntoStage(stageDir); + try { + if (stageCid != null) { + // TODO: this should delegate to DCS so the system process + // avoids holding open FDs into containers. + copyFiles(mResolvedInheritedFiles, resolveStageDir()); + } else { + linkFiles(mResolvedInheritedFiles, resolveStageDir()); + } + } catch (IOException e) { + throw new PackageManagerException(INSTALL_FAILED_INSUFFICIENT_STORAGE, + "Failed to inherit existing install", e); + } } // TODO: surface more granular state from dexopt mCallback.onSessionProgressChanged(this, 0.9f); - // TODO: for ASEC based applications, grow and stream in packages + // Unpack native libraries + extractNativeLibraries(mResolvedStageDir, params.abiOverride); + + // Container is ready to go, let's seal it up! + if (stageCid != null) { + finalizeAndFixContainer(stageCid); + } // We've reached point of no return; call into PMS to install the stage. // Regardless of success or failure we always destroy session. @@ -482,7 +511,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } }; - mPm.installStage(mPackageName, this.internalStageDir, this.externalStageCid, localObserver, + mPm.installStage(mPackageName, stageDir, stageCid, localObserver, params, installerPackageName, installerUid, new UserHandle(userId)); } @@ -490,81 +519,88 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { * Validate install by confirming that all application packages are have * consistent package name, version code, and signing certificates. *

+ * Clears and populates {@link #mResolvedBaseFile}, + * {@link #mResolvedStagedFiles}, and {@link #mResolvedInheritedFiles}. + *

* Renames package files in stage to match split names defined inside. *

* Note that upgrade compatibility is still performed by * {@link PackageManagerService}. */ - private void validateInstallLocked(File stageDir) throws PackageManagerException { + private void validateInstallLocked() throws PackageManagerException { mPackageName = null; mVersionCode = -1; mSignatures = null; - mResolvedBaseCodePath = null; - final File[] files = stageDir.listFiles(); + mResolvedBaseFile = null; + mResolvedStagedFiles.clear(); + mResolvedInheritedFiles.clear(); + + final File[] files = mResolvedStageDir.listFiles(); if (ArrayUtils.isEmpty(files)) { throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, "No packages staged"); } // Verify that all staged packages are internally consistent - final ArraySet seenSplits = new ArraySet<>(); + final ArraySet stagedSplits = new ArraySet<>(); for (File file : files) { // Installers can't stage directories, so it's fine to ignore // entries like "lost+found". if (file.isDirectory()) continue; - final ApkLite info; + final ApkLite apk; try { - info = PackageParser.parseApkLite(file, PackageParser.PARSE_COLLECT_CERTIFICATES); + apk = PackageParser.parseApkLite(file, PackageParser.PARSE_COLLECT_CERTIFICATES); } catch (PackageParserException e) { throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, "Failed to parse " + file + ": " + e); } - if (!seenSplits.add(info.splitName)) { + if (!stagedSplits.add(apk.splitName)) { throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, - "Split " + info.splitName + " was defined multiple times"); + "Split " + apk.splitName + " was defined multiple times"); } // Use first package to define unknown values if (mPackageName == null) { - mPackageName = info.packageName; - mVersionCode = info.versionCode; + mPackageName = apk.packageName; + mVersionCode = apk.versionCode; } if (mSignatures == null) { - mSignatures = info.signatures; + mSignatures = apk.signatures; } - assertPackageConsistent(String.valueOf(file), info.packageName, info.versionCode, - info.signatures); + assertApkConsistent(String.valueOf(file), apk); // Take this opportunity to enforce uniform naming final String targetName; - if (info.splitName == null) { + if (apk.splitName == null) { targetName = "base.apk"; } else { - targetName = "split_" + info.splitName + ".apk"; + targetName = "split_" + apk.splitName + ".apk"; } if (!FileUtils.isValidExtFilename(targetName)) { throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, "Invalid filename: " + targetName); } - final File targetFile = new File(stageDir, targetName); + final File targetFile = new File(mResolvedStageDir, targetName); if (!file.equals(targetFile)) { file.renameTo(targetFile); } // Base is coming from session - if (info.splitName == null) { - mResolvedBaseCodePath = targetFile.getAbsolutePath(); + if (apk.splitName == null) { + mResolvedBaseFile = targetFile; } + + mResolvedStagedFiles.add(targetFile); } if (params.mode == SessionParams.MODE_FULL_INSTALL) { // Full installs must include a base package - if (!seenSplits.contains(null)) { + if (!stagedSplits.contains(null)) { throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, "Full install must include a base package"); } @@ -577,67 +613,204 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { "Missing existing base package for " + mPackageName); } - // Base might be inherited from existing install - if (mResolvedBaseCodePath == null) { - mResolvedBaseCodePath = app.getBaseCodePath(); - } - - final ApkLite info; + final PackageLite existing; + final ApkLite existingBase; try { - info = PackageParser.parseApkLite(new File(app.getBaseCodePath()), + existing = PackageParser.parsePackageLite(new File(app.getCodePath()), 0); + existingBase = PackageParser.parseApkLite(new File(app.getBaseCodePath()), PackageParser.PARSE_COLLECT_CERTIFICATES); } catch (PackageParserException e) { throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, - "Failed to parse existing base " + app.getBaseCodePath() + ": " + e); + "Failed to parse existing package " + app.getCodePath() + ": " + e); } - assertPackageConsistent("Existing base", info.packageName, info.versionCode, - info.signatures); + assertApkConsistent("Existing base", existingBase); + + // Inherit base if not overridden + if (mResolvedBaseFile == null) { + mResolvedBaseFile = new File(app.getBaseCodePath()); + mResolvedInheritedFiles.add(mResolvedBaseFile); + } + + // Inherit splits if not overridden + if (!ArrayUtils.isEmpty(existing.splitNames)) { + for (int i = 0; i < existing.splitNames.length; i++) { + final String splitName = existing.splitNames[i]; + final File splitFile = new File(existing.splitCodePaths[i]); + + if (!stagedSplits.contains(splitName)) { + mResolvedInheritedFiles.add(splitFile); + } + } + } } } - private void assertPackageConsistent(String tag, String packageName, int versionCode, - Signature[] signatures) throws PackageManagerException { - if (!mPackageName.equals(packageName)) { + private void assertApkConsistent(String tag, ApkLite apk) throws PackageManagerException { + if (!mPackageName.equals(apk.packageName)) { throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, tag + " package " - + packageName + " inconsistent with " + mPackageName); + + apk.packageName + " inconsistent with " + mPackageName); } - if (mVersionCode != versionCode) { + if (mVersionCode != apk.versionCode) { throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, tag - + " version code " + versionCode + " inconsistent with " + + " version code " + apk.versionCode + " inconsistent with " + mVersionCode); } - if (!Signature.areExactMatch(mSignatures, signatures)) { + if (!Signature.areExactMatch(mSignatures, apk.signatures)) { throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, tag + " signatures are inconsistent"); } } /** - * Application is already installed; splice existing files that haven't been - * overridden into our stage. + * Calculate the final install footprint size, combining both staged and + * existing APKs together and including unpacked native code from both. */ - private void spliceExistingFilesIntoStage(File stageDir) throws PackageManagerException { - final ApplicationInfo app = mPm.getApplicationInfo(mPackageName, 0, userId); + private long calculateInstalledSize() throws PackageManagerException { + Preconditions.checkNotNull(mResolvedBaseFile); - int n = 0; - final File[] oldFiles = new File(app.getCodePath()).listFiles(); - if (!ArrayUtils.isEmpty(oldFiles)) { - for (File oldFile : oldFiles) { - if (!PackageParser.isApkFile(oldFile)) continue; + final ApkLite baseApk; + try { + baseApk = PackageParser.parseApkLite(mResolvedBaseFile, 0); + } catch (PackageParserException e) { + throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, + "Failed to parse base package " + mResolvedBaseFile + ": " + e); + } - final File newFile = new File(stageDir, oldFile.getName()); - try { - Os.link(oldFile.getAbsolutePath(), newFile.getAbsolutePath()); - n++; - } catch (ErrnoException e) { - throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR, - "Failed to splice into stage", e); - } + final List splitPaths = new ArrayList<>(); + for (File file : mResolvedStagedFiles) { + if (mResolvedBaseFile.equals(file)) continue; + splitPaths.add(file.getAbsolutePath()); + } + for (File file : mResolvedInheritedFiles) { + if (mResolvedBaseFile.equals(file)) continue; + splitPaths.add(file.getAbsolutePath()); + } + + // This is kind of hacky; we're creating a half-parsed package that is + // straddled between the inherited and staged APKs. + final PackageLite pkg = new PackageLite(null, baseApk, null, + splitPaths.toArray(new String[splitPaths.size()])); + final boolean isForwardLocked = + (params.installFlags & PackageManager.INSTALL_FORWARD_LOCK) != 0; + + try { + return PackageHelper.calculateInstalledSize(pkg, isForwardLocked, params.abiOverride); + } catch (IOException e) { + throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, + "Failed to calculate install size", e); + } + } + + private static void linkFiles(List fromFiles, File toDir) throws IOException { + for (File fromFile : fromFiles) { + final File toFile = new File(toDir, fromFile.getName()); + try { + if (LOGD) Slog.d(TAG, "Linking " + fromFile + " to " + toFile); + Os.link(fromFile.getAbsolutePath(), toFile.getAbsolutePath()); + } catch (ErrnoException e) { + throw new IOException("Failed to link " + fromFile + " to " + toFile, e); + } + } + Slog.d(TAG, "Linked " + fromFiles.size() + " files into " + toDir); + } + + private static void copyFiles(List fromFiles, File toDir) throws IOException { + // Remove any partial files from previous attempt + for (File file : toDir.listFiles()) { + if (file.getName().endsWith(".tmp")) { + file.delete(); } } - if (LOGD) Slog.d(TAG, "Spliced " + n + " existing APKs into stage"); + for (File fromFile : fromFiles) { + final File tmpFile = File.createTempFile("inherit", ".tmp", toDir); + if (LOGD) Slog.d(TAG, "Copying " + fromFile + " to " + tmpFile); + if (!FileUtils.copyFile(fromFile, tmpFile)) { + throw new IOException("Failed to copy " + fromFile + " to " + tmpFile); + } + + final File toFile = new File(toDir, fromFile.getName()); + if (LOGD) Slog.d(TAG, "Renaming " + tmpFile + " to " + toFile); + if (!tmpFile.renameTo(toFile)) { + throw new IOException("Failed to rename " + tmpFile + " to " + toFile); + } + } + Slog.d(TAG, "Copied " + fromFiles.size() + " files into " + toDir); + } + + private static void extractNativeLibraries(File packageDir, String abiOverride) + throws PackageManagerException { + if (LOGD) Slog.v(TAG, "extractNativeLibraries()"); + + // Always start from a clean slate + final File libDir = new File(packageDir, NativeLibraryHelper.LIB_DIR_NAME); + NativeLibraryHelper.removeNativeBinariesFromDirLI(libDir, true); + + NativeLibraryHelper.Handle handle = null; + try { + handle = NativeLibraryHelper.Handle.create(packageDir); + final int res = NativeLibraryHelper.copyNativeBinariesWithOverride(handle, libDir, + abiOverride); + if (res != PackageManager.INSTALL_SUCCEEDED) { + throw new PackageManagerException(res, + "Failed to extract native libraries, res=" + res); + } + } catch (IOException e) { + throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR, + "Failed to extract native libraries", e); + } finally { + IoUtils.closeQuietly(handle); + } + } + + private static void resizeContainer(String cid, long targetSize) + throws PackageManagerException { + String path = PackageHelper.getSdDir(cid); + if (path == null) { + throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR, + "Failed to find mounted " + cid); + } + + final long currentSize = new File(path).getTotalSpace(); + if (currentSize > targetSize) { + Slog.w(TAG, "Current size " + currentSize + " is larger than target size " + + targetSize + "; skipping resize"); + return; + } + + if (!PackageHelper.unMountSdDir(cid)) { + throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR, + "Failed to unmount " + cid + " before resize"); + } + + if (!PackageHelper.resizeSdDir(targetSize, cid, + PackageManagerService.getEncryptKey())) { + throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR, + "Failed to resize " + cid + " to " + targetSize + " bytes"); + } + + path = PackageHelper.mountSdDir(cid, PackageManagerService.getEncryptKey(), + Process.SYSTEM_UID, false); + if (path == null) { + throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR, + "Failed to mount " + cid + " after resize"); + } + } + + private void finalizeAndFixContainer(String cid) throws PackageManagerException { + if (!PackageHelper.finalizeSdDir(cid)) { + throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR, + "Failed to finalize container " + cid); + } + + final int uid = mPm.getPackageUid(PackageManagerService.DEFAULT_CONTAINER_PACKAGE, + UserHandle.USER_OWNER); + final int gid = UserHandle.getSharedAppGid(uid); + if (!PackageHelper.fixSdPermissions(cid, gid, null)) { + throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR, + "Failed to fix permissions on container " + cid); + } } void setPermissionsResult(boolean accepted) { @@ -694,12 +867,12 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { mSealed = true; mDestroyed = true; } - if (internalStageDir != null) { - FileUtils.deleteContents(internalStageDir); - internalStageDir.delete(); + if (stageDir != null) { + FileUtils.deleteContents(stageDir); + stageDir.delete(); } - if (externalStageCid != null) { - PackageHelper.destroySdDir(externalStageCid); + if (stageCid != null) { + PackageHelper.destroySdDir(stageCid); } } @@ -717,8 +890,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { pw.printPair("installerPackageName", installerPackageName); pw.printPair("installerUid", installerUid); pw.printPair("createdMillis", createdMillis); - pw.printPair("internalStageDir", internalStageDir); - pw.printPair("externalStageCid", externalStageCid); + pw.printPair("stageDir", stageDir); + pw.printPair("stageCid", stageCid); pw.println(); params.dump(pw); diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index aa49b270c097c..f06992a23bbb3 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -50,6 +50,8 @@ import static android.system.OsConstants.O_CREAT; import static android.system.OsConstants.O_RDWR; import static com.android.internal.app.IntentForwarderActivity.FORWARD_INTENT_TO_MANAGED_PROFILE; import static com.android.internal.app.IntentForwarderActivity.FORWARD_INTENT_TO_USER_OWNER; +import static com.android.internal.content.NativeLibraryHelper.LIB64_DIR_NAME; +import static com.android.internal.content.NativeLibraryHelper.LIB_DIR_NAME; import static com.android.internal.util.ArrayUtils.appendInt; import static com.android.internal.util.ArrayUtils.removeInt; @@ -298,9 +300,6 @@ public class PackageManagerService extends IPackageManager.Stub { private static final String PACKAGE_MIME_TYPE = "application/vnd.android.package-archive"; - private static final String LIB_DIR_NAME = "lib"; - private static final String LIB64_DIR_NAME = "lib64"; - private static final String VENDOR_OVERLAY_DIR = "/vendor/overlay"; private static String sPreferredInstructionSet; @@ -1121,7 +1120,7 @@ public class PackageManagerService extends IPackageManager.Stub { if ((state != null) && !state.timeoutExtended()) { final InstallArgs args = state.getInstallArgs(); - final Uri originUri = Uri.fromFile(args.originFile); + final Uri originUri = Uri.fromFile(args.origin.resolvedFile); Slog.i(TAG, "Verification timed out for " + originUri); mPendingVerification.remove(verificationId); @@ -1168,7 +1167,7 @@ public class PackageManagerService extends IPackageManager.Stub { mPendingVerification.remove(verificationId); final InstallArgs args = state.getInstallArgs(); - final Uri originUri = Uri.fromFile(args.originFile); + final Uri originUri = Uri.fromFile(args.origin.resolvedFile); int ret; if (state.isInstallAllowed()) { @@ -4271,7 +4270,7 @@ public class PackageManagerService extends IPackageManager.Stub { InstallArgs args = createInstallArgsForExisting(packageFlagsToInstallFlags(ps), ps.codePathString, ps.resourcePathString, ps.legacyNativeLibraryPathString, - getAppDexInstructionSets(ps), isMultiArch(ps)); + getAppDexInstructionSets(ps)); synchronized (mInstallLock) { args.cleanUpResourcesLI(); } @@ -4334,7 +4333,7 @@ public class PackageManagerService extends IPackageManager.Stub { + " better than installed " + ps.versionCode); InstallArgs args = createInstallArgsForExisting(packageFlagsToInstallFlags(ps), ps.codePathString, ps.resourcePathString, ps.legacyNativeLibraryPathString, - getAppDexInstructionSets(ps), isMultiArch(ps)); + getAppDexInstructionSets(ps)); synchronized (mInstallLock) { args.cleanUpResourcesLI(); } @@ -5527,8 +5526,9 @@ public class PackageManagerService extends IPackageManager.Stub { if (isAsec) { abi32 = NativeLibraryHelper.findSupportedAbi(handle, Build.SUPPORTED_32_BIT_ABIS); } else { - abi32 = NativeLibraryHelper.copyNativeBinariesIfNeededLI(handle, - nativeLibraryRoot, Build.SUPPORTED_32_BIT_ABIS, useIsaSpecificSubdirs); + abi32 = NativeLibraryHelper.copyNativeBinariesForSupportedAbi(handle, + nativeLibraryRoot, Build.SUPPORTED_32_BIT_ABIS, + useIsaSpecificSubdirs); } } @@ -5539,8 +5539,9 @@ public class PackageManagerService extends IPackageManager.Stub { if (isAsec) { abi64 = NativeLibraryHelper.findSupportedAbi(handle, Build.SUPPORTED_64_BIT_ABIS); } else { - abi64 = NativeLibraryHelper.copyNativeBinariesIfNeededLI(handle, - nativeLibraryRoot, Build.SUPPORTED_64_BIT_ABIS, useIsaSpecificSubdirs); + abi64 = NativeLibraryHelper.copyNativeBinariesForSupportedAbi(handle, + nativeLibraryRoot, Build.SUPPORTED_64_BIT_ABIS, + useIsaSpecificSubdirs); } } @@ -5578,7 +5579,7 @@ public class PackageManagerService extends IPackageManager.Stub { if (isAsec) { copyRet = NativeLibraryHelper.findSupportedAbi(handle, abiList); } else { - copyRet = NativeLibraryHelper.copyNativeBinariesIfNeededLI(handle, + copyRet = NativeLibraryHelper.copyNativeBinariesForSupportedAbi(handle, nativeLibraryRoot, abiList, useIsaSpecificSubdirs); } @@ -7782,7 +7783,8 @@ public class PackageManagerService extends IPackageManager.Stub { verificationParams.setInstallerUid(uid); final Message msg = mHandler.obtainMessage(INIT_COPY); - msg.obj = new InstallParams(originFile, null, false, observer, filteredFlags, + final OriginInfo origin = new OriginInfo(originFile, null, false); + msg.obj = new InstallParams(origin, observer, filteredFlags, installerPackageName, verificationParams, user, packageAbiOverride); mHandler.sendMessage(msg); } @@ -7794,7 +7796,8 @@ public class PackageManagerService extends IPackageManager.Stub { params.referrerUri, installerUid, null); final Message msg = mHandler.obtainMessage(INIT_COPY); - msg.obj = new InstallParams(stagedDir, stagedCid, true, observer, params.installFlags, + final OriginInfo origin = new OriginInfo(stagedDir, stagedCid, true); + msg.obj = new InstallParams(origin, observer, params.installFlags, installerPackageName, verifParams, user, params.abiOverride); mHandler.sendMessage(msg); } @@ -8487,22 +8490,45 @@ public class PackageManagerService extends IPackageManager.Stub { } } - class InstallParams extends HandlerParams { + static class OriginInfo { /** * Location where install is coming from, before it has been * copied/renamed into place. This could be a single monolithic APK * file, or a cluster directory. This location may be untrusted. */ - final File originFile; - final String originCid; + final File file; + final String cid; /** - * Flag indicating that {@link #originFile} or {@link #originCid} has - * already been staged, meaning downstream users don't need to - * defensively copy the contents. + * Flag indicating that {@link #file} or {@link #cid} has already been + * staged, meaning downstream users don't need to defensively copy the + * contents. */ - boolean originStaged; + final boolean staged; + final String resolvedPath; + final File resolvedFile; + + public OriginInfo(File file, String cid, boolean staged) { + this.file = file; + this.cid = cid; + this.staged = staged; + + if (cid != null) { + resolvedPath = PackageHelper.getSdDir(cid); + resolvedFile = new File(resolvedPath); + } else if (file != null) { + resolvedPath = file.getAbsolutePath(); + resolvedFile = file; + } else { + resolvedPath = null; + resolvedFile = null; + } + } + } + + class InstallParams extends HandlerParams { + final OriginInfo origin; final IPackageInstallObserver2 observer; int flags; final String installerPackageName; @@ -8510,15 +8536,12 @@ public class PackageManagerService extends IPackageManager.Stub { private InstallArgs mArgs; private int mRet; final String packageAbiOverride; - boolean multiArch; - InstallParams(File originFile, String originCid, boolean originStaged, - IPackageInstallObserver2 observer, int flags, String installerPackageName, - VerificationParams verificationParams, UserHandle user, String packageAbiOverride) { + InstallParams(OriginInfo origin, IPackageInstallObserver2 observer, int flags, + String installerPackageName, VerificationParams verificationParams, UserHandle user, + String packageAbiOverride) { super(user); - this.originFile = originFile; - this.originCid = originCid; - this.originStaged = originStaged; + this.origin = origin; this.observer = observer; this.flags = flags; this.installerPackageName = installerPackageName; @@ -8529,7 +8552,7 @@ public class PackageManagerService extends IPackageManager.Stub { @Override public String toString() { return "InstallParams{" + Integer.toHexString(System.identityHashCode(this)) - + " file=" + originFile + " cid=" + originCid + "}"; + + " file=" + origin.file + " cid=" + origin.cid + "}"; } public ManifestDigest getManifestDigest() { @@ -8608,11 +8631,11 @@ public class PackageManagerService extends IPackageManager.Stub { int ret = PackageManager.INSTALL_SUCCEEDED; // If we're already staged, we've firmly committed to an install location - if (originStaged) { - if (originFile != null) { + if (origin.staged) { + if (origin.file != null) { flags |= PackageManager.INSTALL_INTERNAL; flags &= ~PackageManager.INSTALL_EXTERNAL; - } else if (originCid != null) { + } else if (origin.cid != null) { flags |= PackageManager.INSTALL_EXTERNAL; flags &= ~PackageManager.INSTALL_INTERNAL; } else { @@ -8622,6 +8645,7 @@ public class PackageManagerService extends IPackageManager.Stub { final boolean onSd = (flags & PackageManager.INSTALL_EXTERNAL) != 0; final boolean onInt = (flags & PackageManager.INSTALL_INTERNAL) != 0; + PackageInfoLite pkgLite = null; if (onInt && onSd) { @@ -8629,21 +8653,14 @@ public class PackageManagerService extends IPackageManager.Stub { Slog.w(TAG, "Conflicting flags specified for installing on both internal and external"); ret = PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION; } else { - // Remote call to find out default install location - final String originPath = originFile.getAbsolutePath(); - pkgLite = mContainerService.getMinimalPackageInfo(originPath, flags, + pkgLite = mContainerService.getMinimalPackageInfo(origin.resolvedPath, flags, packageAbiOverride); - // Keep track of whether this package is a multiArch package until - // we perform a full scan of it. We need to do this because we might - // end up extracting the package shared libraries before we perform - // a full scan. - multiArch = pkgLite.multiArch; /* * If we have too little free space, try to free cache * before giving up. */ - if (!originStaged && pkgLite.recommendedInstallLocation + if (!origin.staged && pkgLite.recommendedInstallLocation == PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE) { // TODO: focus freeing disk space on the target device final StorageManager storage = StorageManager.from(mContext); @@ -8651,11 +8668,11 @@ public class PackageManagerService extends IPackageManager.Stub { Environment.getDataDirectory()); final long sizeBytes = mContainerService.calculateInstalledSize( - originPath, isForwardLocked(), packageAbiOverride); + origin.resolvedPath, isForwardLocked(), packageAbiOverride); if (mInstaller.freeCache(sizeBytes + lowThreshold) >= 0) { - pkgLite = mContainerService.getMinimalPackageInfo(originPath, flags, - packageAbiOverride); + pkgLite = mContainerService.getMinimalPackageInfo(origin.resolvedPath, + flags, packageAbiOverride); } /* @@ -8729,10 +8746,10 @@ public class PackageManagerService extends IPackageManager.Stub { final int requiredUid = mRequiredVerifierPackage == null ? -1 : getPackageUid(mRequiredVerifierPackage, userIdentifier); if (requiredUid != -1 && isVerificationEnabled(userIdentifier, flags)) { - // TODO: send verifier the install session instead of uri final Intent verification = new Intent( Intent.ACTION_PACKAGE_NEEDS_VERIFICATION); - verification.setDataAndType(Uri.fromFile(originFile), PACKAGE_MIME_TYPE); + verification.setDataAndType(Uri.fromFile(new File(origin.resolvedPath)), + PACKAGE_MIME_TYPE); verification.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); final List receivers = queryIntentReceivers(verification, @@ -8890,8 +8907,7 @@ public class PackageManagerService extends IPackageManager.Stub { int mRet; MoveParams(InstallArgs srcArgs, IPackageMoveObserver observer, int flags, - String packageName, String[] instructionSets, int uid, UserHandle user, - boolean isMultiArch) { + String packageName, String[] instructionSets, int uid, UserHandle user) { super(user); this.srcArgs = srcArgs; this.observer = observer; @@ -8901,7 +8917,7 @@ public class PackageManagerService extends IPackageManager.Stub { if (srcArgs != null) { final String codePath = srcArgs.getCodePath(); targetArgs = createInstallArgsForMoveTarget(codePath, flags, packageName, - instructionSets, isMultiArch); + instructionSets); } else { targetArgs = null; } @@ -9002,8 +9018,6 @@ public class PackageManagerService extends IPackageManager.Stub { } private InstallArgs createInstallArgs(InstallParams params) { - // TODO: extend to support incoming zero-copy locations - if (installOnSd(params.flags) || params.isForwardLocked()) { return new AsecInstallArgs(params); } else { @@ -9016,8 +9030,7 @@ public class PackageManagerService extends IPackageManager.Stub { * when cleaning up old installs, or used as a move source. */ private InstallArgs createInstallArgsForExisting(int flags, String codePath, - String resourcePath, String nativeLibraryRoot, String[] instructionSets, - boolean isMultiArch) { + String resourcePath, String nativeLibraryRoot, String[] instructionSets) { final boolean isInAsec; if (installOnSd(flags)) { /* Apps on SD card are always in ASEC containers. */ @@ -9035,33 +9048,29 @@ public class PackageManagerService extends IPackageManager.Stub { if (isInAsec) { return new AsecInstallArgs(codePath, instructionSets, - installOnSd(flags), installForwardLocked(flags), isMultiArch); + installOnSd(flags), installForwardLocked(flags)); } else { return new FileInstallArgs(codePath, resourcePath, nativeLibraryRoot, - instructionSets, isMultiArch); + instructionSets); } } private InstallArgs createInstallArgsForMoveTarget(String codePath, int flags, String pkgName, - String[] instructionSets, boolean isMultiArch) { + String[] instructionSets) { final File codeFile = new File(codePath); if (installOnSd(flags) || installForwardLocked(flags)) { String cid = getNextCodePath(codePath, pkgName, "/" + AsecInstallArgs.RES_FILE_NAME); return new AsecInstallArgs(codeFile, cid, instructionSets, installOnSd(flags), - installForwardLocked(flags), isMultiArch); + installForwardLocked(flags)); } else { - return new FileInstallArgs(codeFile, instructionSets, isMultiArch); + return new FileInstallArgs(codeFile, instructionSets); } } static abstract class InstallArgs { - /** @see InstallParams#originFile */ - final File originFile; - /** @see InstallParams#originStaged */ - final boolean originStaged; - - // TODO: define inherit location + /** @see InstallParams#origin */ + final OriginInfo origin; final IPackageInstallObserver2 observer; // Always refers to PackageManager flags only @@ -9070,19 +9079,16 @@ public class PackageManagerService extends IPackageManager.Stub { final ManifestDigest manifestDigest; final UserHandle user; final String abiOverride; - final boolean multiArch; // The list of instruction sets supported by this app. This is currently // only used during the rmdex() phase to clean up resources. We can get rid of this // if we move dex files under the common app path. /* nullable */ String[] instructionSets; - InstallArgs(File originFile, boolean originStaged, IPackageInstallObserver2 observer, - int flags, String installerPackageName, ManifestDigest manifestDigest, - UserHandle user, String[] instructionSets, - String abiOverride, boolean multiArch) { - this.originFile = originFile; - this.originStaged = originStaged; + InstallArgs(OriginInfo origin, IPackageInstallObserver2 observer, int flags, + String installerPackageName, ManifestDigest manifestDigest, UserHandle user, + String[] instructionSets, String abiOverride) { + this.origin = origin; this.flags = flags; this.observer = observer; this.installerPackageName = installerPackageName; @@ -9090,7 +9096,6 @@ public class PackageManagerService extends IPackageManager.Stub { this.user = user; this.instructionSets = instructionSets; this.abiOverride = abiOverride; - this.multiArch = multiArch; } abstract int copyApk(IMediaContainerService imcs, boolean temp) throws RemoteException; @@ -9161,10 +9166,9 @@ public class PackageManagerService extends IPackageManager.Stub { /** New install */ FileInstallArgs(InstallParams params) { - super(params.originFile, params.originStaged, params.observer, params.flags, + super(params.origin, params.observer, params.flags, params.installerPackageName, params.getManifestDigest(), params.getUser(), - null /* instruction sets */, params.packageAbiOverride, - params.multiArch); + null /* instruction sets */, params.packageAbiOverride); if (isFwdLocked()) { throw new IllegalArgumentException("Forward locking only supported in ASEC"); } @@ -9172,8 +9176,8 @@ public class PackageManagerService extends IPackageManager.Stub { /** Existing install */ FileInstallArgs(String codePath, String resourcePath, String legacyNativeLibraryPath, - String[] instructionSets, boolean isMultiArch) { - super(null, false, null, 0, null, null, null, instructionSets, null, isMultiArch); + String[] instructionSets) { + super(new OriginInfo(null, null, false), null, 0, null, null, null, instructionSets, null); this.codeFile = (codePath != null) ? new File(codePath) : null; this.resourceFile = (resourcePath != null) ? new File(resourcePath) : null; this.legacyNativeLibraryPath = (legacyNativeLibraryPath != null) ? @@ -9181,13 +9185,12 @@ public class PackageManagerService extends IPackageManager.Stub { } /** New install from existing */ - FileInstallArgs(File originFile, String[] instructionSets, boolean isMultiArch) { - super(originFile, false, null, 0, null, null, null, instructionSets, null, - isMultiArch); + FileInstallArgs(File originFile, String[] instructionSets) { + super(new OriginInfo(originFile, null, false), null, 0, null, null, null, instructionSets, null); } boolean checkFreeStorage(IMediaContainerService imcs) throws RemoteException { - final long sizeBytes = imcs.calculateInstalledSize(originFile.getAbsolutePath(), + final long sizeBytes = imcs.calculateInstalledSize(origin.file.getAbsolutePath(), isFwdLocked(), abiOverride); final StorageManager storage = StorageManager.from(mContext); @@ -9195,53 +9198,53 @@ public class PackageManagerService extends IPackageManager.Stub { } int copyApk(IMediaContainerService imcs, boolean temp) throws RemoteException { - int ret = PackageManager.INSTALL_SUCCEEDED; + if (origin.staged) { + Slog.d(TAG, origin.file + " already staged; skipping copy"); + codeFile = origin.file; + resourceFile = origin.file; + return PackageManager.INSTALL_SUCCEEDED; + } - if (originStaged) { - Slog.d(TAG, originFile + " already staged; skipping copy"); - codeFile = originFile; - resourceFile = originFile; - } else { - try { - final File tempDir = mInstallerService.allocateInternalStageDirLegacy(); - codeFile = tempDir; - resourceFile = tempDir; - } catch (IOException e) { - Slog.w(TAG, "Failed to create copy file: " + e); - return PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE; - } + try { + final File tempDir = mInstallerService.allocateInternalStageDirLegacy(); + codeFile = tempDir; + resourceFile = tempDir; + } catch (IOException e) { + Slog.w(TAG, "Failed to create copy file: " + e); + return PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE; + } - final IParcelFileDescriptorFactory target = new IParcelFileDescriptorFactory.Stub() { - @Override - public ParcelFileDescriptor open(String name, int mode) throws RemoteException { - if (!FileUtils.isValidExtFilename(name)) { - throw new IllegalArgumentException("Invalid filename: " + name); - } - try { - final File file = new File(codeFile, name); - final FileDescriptor fd = Os.open(file.getAbsolutePath(), - O_RDWR | O_CREAT, 0644); - Os.chmod(file.getAbsolutePath(), 0644); - return new ParcelFileDescriptor(fd); - } catch (ErrnoException e) { - throw new RemoteException("Failed to open: " + e.getMessage()); - } + final IParcelFileDescriptorFactory target = new IParcelFileDescriptorFactory.Stub() { + @Override + public ParcelFileDescriptor open(String name, int mode) throws RemoteException { + if (!FileUtils.isValidExtFilename(name)) { + throw new IllegalArgumentException("Invalid filename: " + name); + } + try { + final File file = new File(codeFile, name); + final FileDescriptor fd = Os.open(file.getAbsolutePath(), + O_RDWR | O_CREAT, 0644); + Os.chmod(file.getAbsolutePath(), 0644); + return new ParcelFileDescriptor(fd); + } catch (ErrnoException e) { + throw new RemoteException("Failed to open: " + e.getMessage()); } - }; - - ret = imcs.copyPackage(originFile.getAbsolutePath(), target); - if (ret != PackageManager.INSTALL_SUCCEEDED) { - Slog.e(TAG, "Failed to copy package"); - return ret; } + }; + + int ret = PackageManager.INSTALL_SUCCEEDED; + ret = imcs.copyPackage(origin.file.getAbsolutePath(), target); + if (ret != PackageManager.INSTALL_SUCCEEDED) { + Slog.e(TAG, "Failed to copy package"); + return ret; } final File libraryRoot = new File(codeFile, LIB_DIR_NAME); NativeLibraryHelper.Handle handle = null; try { handle = NativeLibraryHelper.Handle.create(codeFile); - ret = NativeLibraryHelper.copyNativeBinariesIfNeededLI(handle, libraryRoot, - abiOverride, multiArch); + ret = NativeLibraryHelper.copyNativeBinariesWithOverride(handle, libraryRoot, + abiOverride); } catch (IOException e) { Slog.e(TAG, "Copying native libraries failed", e); ret = PackageManager.INSTALL_FAILED_INTERNAL_ERROR; @@ -9429,18 +9432,18 @@ public class PackageManagerService extends IPackageManager.Stub { /** New install */ AsecInstallArgs(InstallParams params) { - super(params.originFile, params.originStaged, params.observer, params.flags, + super(params.origin, params.observer, params.flags, params.installerPackageName, params.getManifestDigest(), params.getUser(), null /* instruction sets */, - params.packageAbiOverride, params.multiArch); + params.packageAbiOverride); } /** Existing install */ AsecInstallArgs(String fullCodePath, String[] instructionSets, - boolean isExternal, boolean isForwardLocked, boolean isMultiArch) { - super(null, false, null, (isExternal ? INSTALL_EXTERNAL : 0) + boolean isExternal, boolean isForwardLocked) { + super(new OriginInfo(null, null, false), null, (isExternal ? INSTALL_EXTERNAL : 0) | (isForwardLocked ? INSTALL_FORWARD_LOCK : 0), null, null, null, - instructionSets, null, isMultiArch); + instructionSets, null); // Hackily pretend we're still looking at a full code path if (!fullCodePath.endsWith(RES_FILE_NAME)) { fullCodePath = new File(fullCodePath, RES_FILE_NAME).getAbsolutePath(); @@ -9454,21 +9457,20 @@ public class PackageManagerService extends IPackageManager.Stub { setMountPath(subStr1); } - AsecInstallArgs(String cid, String[] instructionSets, boolean isForwardLocked, - boolean isMultiArch) { - super(null, false, null, (isAsecExternal(cid) ? INSTALL_EXTERNAL : 0) + AsecInstallArgs(String cid, String[] instructionSets, boolean isForwardLocked) { + super(new OriginInfo(null, null, false), null, (isAsecExternal(cid) ? INSTALL_EXTERNAL : 0) | (isForwardLocked ? INSTALL_FORWARD_LOCK : 0), null, null, null, - instructionSets, null, isMultiArch); + instructionSets, null); this.cid = cid; setMountPath(PackageHelper.getSdDir(cid)); } /** New install from existing */ AsecInstallArgs(File originPackageFile, String cid, String[] instructionSets, - boolean isExternal, boolean isForwardLocked, boolean isMultiArch) { - super(originPackageFile, false, null, (isExternal ? INSTALL_EXTERNAL : 0) + boolean isExternal, boolean isForwardLocked) { + super(new OriginInfo(originPackageFile, null, false), null, (isExternal ? INSTALL_EXTERNAL : 0) | (isForwardLocked ? INSTALL_FORWARD_LOCK : 0), null, null, null, - instructionSets, null, isMultiArch); + instructionSets, null); this.cid = cid; } @@ -9496,7 +9498,13 @@ public class PackageManagerService extends IPackageManager.Stub { } int copyApk(IMediaContainerService imcs, boolean temp) throws RemoteException { - // TODO: if already staged, we only need to extract native code + if (origin.staged) { + Slog.d(TAG, origin.cid + " already staged; skipping copy"); + cid = origin.cid; + setMountPath(PackageHelper.getSdDir(cid)); + return PackageManager.INSTALL_SUCCEEDED; + } + if (temp) { createCopyFile(); } else { @@ -9508,7 +9516,7 @@ public class PackageManagerService extends IPackageManager.Stub { } final String newMountPath = imcs.copyPackageToContainer( - originFile.getAbsolutePath(), cid, getEncryptKey(), isExternal(), + origin.file.getAbsolutePath(), cid, getEncryptKey(), isExternal(), isFwdLocked(), deriveAbiOverride(abiOverride, null /* settings */)); if (newMountPath != null) { @@ -10133,8 +10141,7 @@ public class PackageManagerService extends IPackageManager.Stub { deletedPackage.applicationInfo.getCodePath(), deletedPackage.applicationInfo.getResourcePath(), deletedPackage.applicationInfo.nativeLibraryRootDir, - getAppDexInstructionSets(deletedPackage.applicationInfo), - isMultiArch(deletedPackage.applicationInfo)); + getAppDexInstructionSets(deletedPackage.applicationInfo)); } else { res.removedInfo.args = null; } @@ -10920,7 +10927,7 @@ public class PackageManagerService extends IPackageManager.Stub { if (deleteCodeAndResources && (outInfo != null)) { outInfo.args = createInstallArgsForExisting(packageFlagsToInstallFlags(ps), ps.codePathString, ps.resourcePathString, ps.legacyNativeLibraryPathString, - getAppDexInstructionSets(ps), isMultiArch(ps)); + getAppDexInstructionSets(ps)); if (DEBUG_SD_INSTALL) Slog.i(TAG, "args=" + outInfo.args); } return true; @@ -12725,7 +12732,7 @@ public class PackageManagerService extends IPackageManager.Stub { } final AsecInstallArgs args = new AsecInstallArgs(cid, - getAppDexInstructionSets(ps), isForwardLocked(ps), isMultiArch(ps)); + getAppDexInstructionSets(ps), isForwardLocked(ps)); // The package status is changed only if the code path // matches between settings and the container id. if (ps.codePathString != null @@ -13019,7 +13026,7 @@ public class PackageManagerService extends IPackageManager.Stub { * anyway. */ if (returnCode != PackageManager.MOVE_SUCCEEDED) { - processPendingMove(new MoveParams(null, observer, 0, packageName, null, -1, user, false), + processPendingMove(new MoveParams(null, observer, 0, packageName, null, -1, user), returnCode); } else { Message msg = mHandler.obtainMessage(INIT_COPY); @@ -13027,9 +13034,9 @@ public class PackageManagerService extends IPackageManager.Stub { final boolean multiArch = isMultiArch(pkg.applicationInfo); InstallArgs srcArgs = createInstallArgsForExisting(currFlags, pkg.applicationInfo.getCodePath(), pkg.applicationInfo.getResourcePath(), - pkg.applicationInfo.nativeLibraryRootDir, instructionSets, multiArch); + pkg.applicationInfo.nativeLibraryRootDir, instructionSets); MoveParams mp = new MoveParams(srcArgs, observer, newFlags, packageName, - instructionSets, pkg.applicationInfo.uid, user, multiArch); + instructionSets, pkg.applicationInfo.uid, user); msg.obj = mp; mHandler.sendMessage(msg); } @@ -13107,8 +13114,8 @@ public class PackageManagerService extends IPackageManager.Stub { final int abi = NativeLibraryHelper.findSupportedAbi( handle, Build.SUPPORTED_ABIS); if (abi >= 0) { - NativeLibraryHelper.copyNativeBinariesIfNeededLI( - handle, newNativeDir, Build.SUPPORTED_ABIS[abi]); + NativeLibraryHelper.copyNativeBinaries(handle, + newNativeDir, Build.SUPPORTED_ABIS[abi]); } } catch (IOException ioe) { Slog.w(TAG, "Unable to extract native libs for package :"