PackageManager plumbing to support non-staged APEX installs

This is a minimal set of changes to support non-staged APEX installs in
PackageManager and friends.

In a nutshell, PackageManager simply passes the APEX around and
delegates the actual installation to ApexManager, which will do the
following:

* Check that outer .apex container is well formed.
* Check that outer .apex container is signed with the correct cert.
* Check that install is not a downgrade.
* Call into apexd to perform an actual install.
* Update internal data structures used by PackageManager's query APIs
  (getPackageInfo, getAppliationInfo, getInstalledPackages, etc.).

In addition this CL implements the following set of policies around
non-staged APEX install (some of them are likely to be relaxd in T):

* Multi apk install of both APEXes and APKs is not supported.
* Rollback enabled non-staged install of an APEX is not supported.

Since there are a bunch of host-side tests that expects
`adb install foo.apex` to perform a staged install, I've temporarily
added `--force-non-staged` option to provide a way to trigger a
non-staged APEX update via adb. Once we've migrated all the test cases
to explicitly mention `--staged` flow, we can remove this flag and make
APEX adb install follow the same logic as APK one (if a developer wants
to perform a staged install, they need to explicitly add --staged flag).

Test: adb install --force-non-staged shim.apex
Test: atest CtsStagedInstallHostTestCases
Test: atest CtsRollbackManagerHostTestCases
Bug: 187864524
Change-Id: Ia1fb1d07dee465bae572321a510ae78b56c35614
(cherry picked from commit 0104f4e0fd99d5696992a2332f020fc74b9643bd)
This commit is contained in:
Nikita Ioffe
2021-05-17 12:57:41 +01:00
parent c11c6283ab
commit 5cd8b9e6f4
5 changed files with 127 additions and 16 deletions

View File

@@ -389,6 +389,11 @@ public abstract class ApexManager {
public abstract void reserveSpaceForCompressedApex(CompressedApexInfoList infoList) public abstract void reserveSpaceForCompressedApex(CompressedApexInfoList infoList)
throws RemoteException; throws RemoteException;
/**
* Performs a non-staged install of an APEX package with given {@code packagePath}.
*/
abstract void installPackage(String packagePath) throws PackageManagerException;
/** /**
* Dumps various state information to the provided {@link PrintWriter} object. * Dumps various state information to the provided {@link PrintWriter} object.
* *
@@ -974,6 +979,22 @@ public abstract class ApexManager {
waitForApexService().reserveSpaceForCompressedApex(infoList); waitForApexService().reserveSpaceForCompressedApex(infoList);
} }
@Override
void installPackage(String packagePath) throws PackageManagerException {
try {
// TODO(b/187864524): do pre-install verification.
waitForApexService().installAndActivatePackage(packagePath);
// TODO(b/187864524): update mAllPackagesCache.
} catch (RemoteException e) {
throw new PackageManagerException(PackageManager.INSTALL_FAILED_INTERNAL_ERROR,
"apexservice not available");
} catch (Exception e) {
// TODO(b/187864524): is INSTALL_FAILED_INTERNAL_ERROR is the right error code here?
throw new PackageManagerException(PackageManager.INSTALL_FAILED_INTERNAL_ERROR,
e.getMessage());
}
}
/** /**
* Dump information about the packages contained in a particular cache * Dump information about the packages contained in a particular cache
* @param packagesCache the cache to print information about. * @param packagesCache the cache to print information about.
@@ -1240,6 +1261,11 @@ public abstract class ApexManager {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
@Override
void installPackage(String packagePath) {
throw new UnsupportedOperationException("APEX updates are not supported");
}
@Override @Override
void dump(PrintWriter pw, String packageName) { void dump(PrintWriter pw, String packageName) {
// No-op // No-op

View File

@@ -632,13 +632,14 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
throw new IllegalArgumentException( throw new IllegalArgumentException(
"This device doesn't support the installation of APEX files"); "This device doesn't support the installation of APEX files");
} }
if (!params.isStaged) {
throw new IllegalArgumentException(
"APEX files can only be installed as part of a staged session.");
}
if (params.isMultiPackage) { if (params.isMultiPackage) {
throw new IllegalArgumentException("A multi-session can't be set as APEX."); throw new IllegalArgumentException("A multi-session can't be set as APEX.");
} }
if (!params.isStaged
&& (params.installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0) {
throw new IllegalArgumentException(
"Non-staged APEX session doesn't support INSTALL_ENABLE_ROLLBACK");
}
} }
if ((params.installFlags & PackageManager.INSTALL_INSTANT_APP) != 0 if ((params.installFlags & PackageManager.INSTALL_INSTANT_APP) != 0
@@ -874,7 +875,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
} }
private File buildSessionDir(int sessionId, SessionParams params) { private File buildSessionDir(int sessionId, SessionParams params) {
if (params.isStaged) { if (params.isStaged || (params.installFlags & PackageManager.INSTALL_APEX) != 0) {
final File sessionStagingDir = Environment.getDataStagingDirectory(params.volumeUuid); final File sessionStagingDir = Environment.getDataStagingDirectory(params.volumeUuid);
return new File(sessionStagingDir, "session_" + sessionId); return new File(sessionStagingDir, "session_" + sessionId);
} }

View File

@@ -2334,6 +2334,13 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
synchronized (mLock) { synchronized (mLock) {
childSessions = getChildSessionsLocked(); childSessions = getChildSessionsLocked();
} }
// Spot check to reject a non-staged multi package install of APEXes and APKs.
if (!params.isStaged && containsApkSession()
&& sessionContains(s -> s.isApexSession())) {
throw new PackageManagerException(
PackageManager.INSTALL_FAILED_SESSION_INVALID,
"Non-staged multi package install of APEX and APK packages is not supported");
}
List<PackageManagerService.VerificationParams> verifyingChildSessions = List<PackageManagerService.VerificationParams> verifyingChildSessions =
new ArrayList<>(childSessions.size()); new ArrayList<>(childSessions.size());
boolean success = true; boolean success = true;
@@ -2376,8 +2383,6 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
private void installNonStaged() private void installNonStaged()
throws PackageManagerException { throws PackageManagerException {
Preconditions.checkArgument(containsApkSession());
final PackageManagerService.InstallParams installingSession = makeInstallParams(); final PackageManagerService.InstallParams installingSession = makeInstallParams();
if (installingSession == null) { if (installingSession == null) {
throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR, throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
@@ -2606,9 +2611,6 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
return; return;
} }
// APEX sessions should be handled above
Preconditions.checkState(!isApexSession());
install(); install();
} }
@@ -2630,8 +2632,9 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
} }
} }
// Do not try to install apex session. Parent session will have at least one apk session. // Do not try to install staged apex session. Parent session will have at least one apk
if (!isMultiPackage() && isApexSession()) { // session.
if (!isMultiPackage() && isApexSession() && params.isStaged) {
sendUpdateToRemoteStatusReceiver(INSTALL_SUCCEEDED, sendUpdateToRemoteStatusReceiver(INSTALL_SUCCEEDED,
"Apex package should have been installed by apexd", null); "Apex package should have been installed by apexd", null);
return null; return null;

View File

@@ -16428,25 +16428,95 @@ public class PackageManagerService extends IPackageManager.Stub
private void processInstallRequestsAsync(boolean success, private void processInstallRequestsAsync(boolean success,
List<InstallRequest> installRequests) { List<InstallRequest> installRequests) {
mHandler.post(() -> { mHandler.post(() -> {
List<InstallRequest> apexInstallRequests = new ArrayList<>();
List<InstallRequest> apkInstallRequests = new ArrayList<>();
for (InstallRequest request : installRequests) {
if ((request.args.installFlags & PackageManager.INSTALL_APEX) != 0) {
apexInstallRequests.add(request);
} else {
apkInstallRequests.add(request);
}
}
// Note: supporting multi package install of both APEXes and APKs might requir some
// thinking to ensure atomicity of the install.
if (!apexInstallRequests.isEmpty() && !apkInstallRequests.isEmpty()) {
// This should've been caught at the validation step, but for some reason wasn't.
throw new IllegalStateException(
"Attempted to do a multi package install of both APEXes and APKs");
}
if (!apexInstallRequests.isEmpty()) {
if (success) {
// Since installApexPackages requires talking to external service (apexd), we
// schedule to run it async. Once it finishes, it will resume the install.
Thread t = new Thread(() -> installApexPackagesTraced(apexInstallRequests),
"installApexPackages");
t.start();
} else {
// Non-staged APEX installation failed somewhere before
// processInstallRequestAsync. In that case just notify the observer about the
// failure.
InstallRequest request = apexInstallRequests.get(0);
notifyInstallObserver(request.installResult, request.args.observer);
}
return;
}
if (success) { if (success) {
for (InstallRequest request : installRequests) { for (InstallRequest request : apkInstallRequests) {
request.args.doPreInstall(request.installResult.returnCode); request.args.doPreInstall(request.installResult.returnCode);
} }
synchronized (mInstallLock) { synchronized (mInstallLock) {
installPackagesTracedLI(installRequests); installPackagesTracedLI(apkInstallRequests);
} }
for (InstallRequest request : installRequests) { for (InstallRequest request : apkInstallRequests) {
request.args.doPostInstall( request.args.doPostInstall(
request.installResult.returnCode, request.installResult.uid); request.installResult.returnCode, request.installResult.uid);
} }
} }
for (InstallRequest request : installRequests) { for (InstallRequest request : apkInstallRequests) {
restoreAndPostInstall(request.args.user.getIdentifier(), request.installResult, restoreAndPostInstall(request.args.user.getIdentifier(), request.installResult,
new PostInstallData(request.args, request.installResult, null)); new PostInstallData(request.args, request.installResult, null));
} }
}); });
} }
private void installApexPackagesTraced(List<InstallRequest> requests) {
try {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "installApexPackages");
installApexPackages(requests);
} finally {
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
}
private void installApexPackages(List<InstallRequest> requests) {
if (requests.isEmpty()) {
return;
}
if (requests.size() != 1) {
throw new IllegalStateException(
"Only a non-staged install of a single APEX is supported");
}
InstallRequest request = requests.get(0);
try {
// Should directory scanning logic be moved to ApexManager for better test coverage?
final File dir = request.args.origin.resolvedFile;
final String[] apexes = dir.list();
if (apexes == null) {
throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
dir.getAbsolutePath() + " is not a directory");
}
if (apexes.length != 1) {
throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
"Expected exactly one .apex file under " + dir.getAbsolutePath()
+ " got: " + apexes.length);
}
mApexManager.installPackage(dir.getAbsolutePath() + "/" + apexes[0]);
} catch (PackageManagerException e) {
request.installResult.setError("APEX installation failed", e);
}
notifyInstallObserver(request.installResult, request.args.observer);
}
private PackageInstalledInfo createPackageInstalledInfo( private PackageInstalledInfo createPackageInstalledInfo(
int currentStatus) { int currentStatus) {
PackageInstalledInfo res = new PackageInstalledInfo(); PackageInstalledInfo res = new PackageInstalledInfo();
@@ -17058,6 +17128,10 @@ public class PackageManagerService extends IPackageManager.Stub
* on the install location. * on the install location.
*/ */
public void handleStartCopy() { public void handleStartCopy() {
if ((installFlags & PackageManager.INSTALL_APEX) != 0) {
mRet = INSTALL_SUCCEEDED;
return;
}
PackageInfoLite pkgLite = PackageManagerServiceUtils.getMinimalPackageInfo(mContext, PackageInfoLite pkgLite = PackageManagerServiceUtils.getMinimalPackageInfo(mContext,
mPackageLite, origin.resolvedPath, installFlags, packageAbiOverride); mPackageLite, origin.resolvedPath, installFlags, packageAbiOverride);

View File

@@ -2687,6 +2687,7 @@ class PackageManagerShellCommand extends ShellCommand {
String opt; String opt;
boolean replaceExisting = true; boolean replaceExisting = true;
boolean forceNonStaged = false;
while ((opt = getNextOption()) != null) { while ((opt = getNextOption()) != null) {
switch (opt) { switch (opt) {
case "-r": // ignore case "-r": // ignore
@@ -2781,6 +2782,9 @@ class PackageManagerShellCommand extends ShellCommand {
sessionParams.setInstallAsApex(); sessionParams.setInstallAsApex();
sessionParams.setStaged(); sessionParams.setStaged();
break; break;
case "--force-non-staged":
forceNonStaged = true;
break;
case "--multi-package": case "--multi-package":
sessionParams.setMultiPackage(); sessionParams.setMultiPackage();
break; break;
@@ -2816,6 +2820,9 @@ class PackageManagerShellCommand extends ShellCommand {
if (replaceExisting) { if (replaceExisting) {
sessionParams.installFlags |= PackageManager.INSTALL_REPLACE_EXISTING; sessionParams.installFlags |= PackageManager.INSTALL_REPLACE_EXISTING;
} }
if (forceNonStaged) {
sessionParams.isStaged = false;
}
return params; return params;
} }