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
416 lines
16 KiB
Java
416 lines
16 KiB
Java
/*
|
|
* Copyright (C) 2010 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
package com.android.defcontainer;
|
|
|
|
import static com.android.internal.content.NativeLibraryHelper.LIB_DIR_NAME;
|
|
|
|
import android.app.IntentService;
|
|
import android.content.Context;
|
|
import android.content.Intent;
|
|
import android.content.pm.IPackageManager;
|
|
import android.content.pm.PackageCleanItem;
|
|
import android.content.pm.PackageInfoLite;
|
|
import android.content.pm.PackageManager;
|
|
import android.content.pm.PackageParser;
|
|
import android.content.pm.PackageParser.PackageLite;
|
|
import android.content.pm.PackageParser.PackageParserException;
|
|
import android.content.res.ObbInfo;
|
|
import android.content.res.ObbScanner;
|
|
import android.os.Environment;
|
|
import android.os.Environment.UserEnvironment;
|
|
import android.os.FileUtils;
|
|
import android.os.IBinder;
|
|
import android.os.ParcelFileDescriptor;
|
|
import android.os.Process;
|
|
import android.os.RemoteException;
|
|
import android.os.ServiceManager;
|
|
import android.system.ErrnoException;
|
|
import android.system.Os;
|
|
import android.system.StructStatVfs;
|
|
import android.util.Slog;
|
|
|
|
import com.android.internal.app.IMediaContainerService;
|
|
import com.android.internal.content.NativeLibraryHelper;
|
|
import com.android.internal.content.PackageHelper;
|
|
import com.android.internal.os.IParcelFileDescriptorFactory;
|
|
import com.android.internal.util.ArrayUtils;
|
|
|
|
import libcore.io.IoUtils;
|
|
import libcore.io.Streams;
|
|
|
|
import java.io.File;
|
|
import java.io.FileInputStream;
|
|
import java.io.IOException;
|
|
import java.io.InputStream;
|
|
import java.io.OutputStream;
|
|
|
|
/**
|
|
* Service that offers to inspect and copy files that may reside on removable
|
|
* storage. This is designed to prevent the system process from holding onto
|
|
* open files that cause the kernel to kill it when the underlying device is
|
|
* removed.
|
|
*/
|
|
public class DefaultContainerService extends IntentService {
|
|
private static final String TAG = "DefContainer";
|
|
|
|
// TODO: migrate native code unpacking to always be a derivative work
|
|
|
|
private IMediaContainerService.Stub mBinder = new IMediaContainerService.Stub() {
|
|
/**
|
|
* Creates a new container and copies package there.
|
|
*
|
|
* @param packagePath absolute path to the package to be copied. Can be
|
|
* a single monolithic APK file or a cluster directory
|
|
* containing one or more APKs.
|
|
* @param containerId the id of the secure container that should be used
|
|
* for creating a secure container into which the resource
|
|
* will be copied.
|
|
* @param key Refers to key used for encrypting the secure container
|
|
* @return Returns the new cache path where the resource has been copied
|
|
* into
|
|
*/
|
|
@Override
|
|
public String copyPackageToContainer(String packagePath, String containerId, String key,
|
|
boolean isExternal, boolean isForwardLocked, String abiOverride) {
|
|
if (packagePath == null || containerId == null) {
|
|
return null;
|
|
}
|
|
|
|
if (isExternal) {
|
|
// Make sure the sdcard is mounted.
|
|
String status = Environment.getExternalStorageState();
|
|
if (!status.equals(Environment.MEDIA_MOUNTED)) {
|
|
Slog.w(TAG, "Make sure sdcard is mounted.");
|
|
return null;
|
|
}
|
|
}
|
|
|
|
PackageLite pkg = null;
|
|
NativeLibraryHelper.Handle handle = null;
|
|
try {
|
|
final File packageFile = new File(packagePath);
|
|
pkg = PackageParser.parsePackageLite(packageFile, 0);
|
|
handle = NativeLibraryHelper.Handle.create(pkg);
|
|
return copyPackageToContainerInner(pkg, handle, containerId, key, isExternal,
|
|
isForwardLocked, abiOverride);
|
|
} catch (PackageParserException | IOException e) {
|
|
Slog.w(TAG, "Failed to copy package at " + packagePath, e);
|
|
return null;
|
|
} finally {
|
|
IoUtils.closeQuietly(handle);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Copy package to the target location.
|
|
*
|
|
* @param packagePath absolute path to the package to be copied. Can be
|
|
* a single monolithic APK file or a cluster directory
|
|
* containing one or more APKs.
|
|
* @return returns status code according to those in
|
|
* {@link PackageManager}
|
|
*/
|
|
@Override
|
|
public int copyPackage(String packagePath, IParcelFileDescriptorFactory target) {
|
|
if (packagePath == null || target == null) {
|
|
return PackageManager.INSTALL_FAILED_INVALID_URI;
|
|
}
|
|
|
|
PackageLite pkg = null;
|
|
try {
|
|
final File packageFile = new File(packagePath);
|
|
pkg = PackageParser.parsePackageLite(packageFile, 0);
|
|
return copyPackageInner(pkg, target);
|
|
} catch (PackageParserException | IOException | RemoteException e) {
|
|
Slog.w(TAG, "Failed to copy package at " + packagePath + ": " + e);
|
|
return PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Parse given package and return minimal details.
|
|
*
|
|
* @param packagePath absolute path to the package to be copied. Can be
|
|
* a single monolithic APK file or a cluster directory
|
|
* containing one or more APKs.
|
|
*/
|
|
@Override
|
|
public PackageInfoLite getMinimalPackageInfo(String packagePath, int flags,
|
|
String abiOverride) {
|
|
final Context context = DefaultContainerService.this;
|
|
final boolean isForwardLocked = (flags & PackageManager.INSTALL_FORWARD_LOCK) != 0;
|
|
|
|
PackageInfoLite ret = new PackageInfoLite();
|
|
if (packagePath == null) {
|
|
Slog.i(TAG, "Invalid package file " + packagePath);
|
|
ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_APK;
|
|
return ret;
|
|
}
|
|
|
|
final File packageFile = new File(packagePath);
|
|
final PackageParser.PackageLite pkg;
|
|
final long sizeBytes;
|
|
try {
|
|
pkg = PackageParser.parsePackageLite(packageFile, 0);
|
|
sizeBytes = PackageHelper.calculateInstalledSize(pkg, isForwardLocked, abiOverride);
|
|
} catch (PackageParserException | IOException e) {
|
|
Slog.w(TAG, "Failed to parse package at " + packagePath + ": " + e);
|
|
|
|
if (!packageFile.exists()) {
|
|
ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_URI;
|
|
} else {
|
|
ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_APK;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
ret.packageName = pkg.packageName;
|
|
ret.versionCode = pkg.versionCode;
|
|
ret.installLocation = pkg.installLocation;
|
|
ret.verifiers = pkg.verifiers;
|
|
ret.recommendedInstallLocation = PackageHelper.resolveInstallLocation(context,
|
|
pkg.packageName, pkg.installLocation, sizeBytes, flags);
|
|
ret.multiArch = pkg.multiArch;
|
|
|
|
return ret;
|
|
}
|
|
|
|
@Override
|
|
public ObbInfo getObbInfo(String filename) {
|
|
try {
|
|
return ObbScanner.getObbInfo(filename);
|
|
} catch (IOException e) {
|
|
Slog.d(TAG, "Couldn't get OBB info for " + filename);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public long calculateDirectorySize(String path) throws RemoteException {
|
|
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
|
|
|
|
final File dir = Environment.maybeTranslateEmulatedPathToInternal(new File(path));
|
|
if (dir.exists() && dir.isDirectory()) {
|
|
final String targetPath = dir.getAbsolutePath();
|
|
return MeasurementUtils.measureDirectory(targetPath);
|
|
} else {
|
|
return 0L;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public long[] getFileSystemStats(String path) {
|
|
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
|
|
|
|
try {
|
|
final StructStatVfs stat = Os.statvfs(path);
|
|
final long totalSize = stat.f_blocks * stat.f_bsize;
|
|
final long availSize = stat.f_bavail * stat.f_bsize;
|
|
return new long[] { totalSize, availSize };
|
|
} catch (ErrnoException e) {
|
|
throw new IllegalStateException(e);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void clearDirectory(String path) throws RemoteException {
|
|
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
|
|
|
|
final File directory = new File(path);
|
|
if (directory.exists() && directory.isDirectory()) {
|
|
eraseFiles(directory);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Calculate estimated footprint of given package post-installation.
|
|
*
|
|
* @param packagePath absolute path to the package to be copied. Can be
|
|
* a single monolithic APK file or a cluster directory
|
|
* containing one or more APKs.
|
|
*/
|
|
@Override
|
|
public long calculateInstalledSize(String packagePath, boolean isForwardLocked,
|
|
String abiOverride) throws RemoteException {
|
|
final File packageFile = new File(packagePath);
|
|
final PackageParser.PackageLite pkg;
|
|
try {
|
|
pkg = PackageParser.parsePackageLite(packageFile, 0);
|
|
return PackageHelper.calculateInstalledSize(pkg, isForwardLocked, abiOverride);
|
|
} catch (PackageParserException | IOException e) {
|
|
Slog.w(TAG, "Failed to calculate installed size: " + e);
|
|
return Long.MAX_VALUE;
|
|
}
|
|
}
|
|
};
|
|
|
|
public DefaultContainerService() {
|
|
super("DefaultContainerService");
|
|
setIntentRedelivery(true);
|
|
}
|
|
|
|
@Override
|
|
protected void onHandleIntent(Intent intent) {
|
|
if (PackageManager.ACTION_CLEAN_EXTERNAL_STORAGE.equals(intent.getAction())) {
|
|
final IPackageManager pm = IPackageManager.Stub.asInterface(
|
|
ServiceManager.getService("package"));
|
|
PackageCleanItem item = null;
|
|
try {
|
|
while ((item = pm.nextPackageToClean(item)) != null) {
|
|
final UserEnvironment userEnv = new UserEnvironment(item.userId);
|
|
eraseFiles(userEnv.buildExternalStorageAppDataDirs(item.packageName));
|
|
eraseFiles(userEnv.buildExternalStorageAppMediaDirs(item.packageName));
|
|
if (item.andCode) {
|
|
eraseFiles(userEnv.buildExternalStorageAppObbDirs(item.packageName));
|
|
}
|
|
}
|
|
} catch (RemoteException e) {
|
|
}
|
|
}
|
|
}
|
|
|
|
void eraseFiles(File[] paths) {
|
|
for (File path : paths) {
|
|
eraseFiles(path);
|
|
}
|
|
}
|
|
|
|
void eraseFiles(File path) {
|
|
if (path.isDirectory()) {
|
|
String[] files = path.list();
|
|
if (files != null) {
|
|
for (String file : files) {
|
|
eraseFiles(new File(path, file));
|
|
}
|
|
}
|
|
}
|
|
path.delete();
|
|
}
|
|
|
|
@Override
|
|
public IBinder onBind(Intent intent) {
|
|
return mBinder;
|
|
}
|
|
|
|
private String copyPackageToContainerInner(PackageLite pkg, NativeLibraryHelper.Handle handle,
|
|
String newCid, String key, boolean isExternal, boolean isForwardLocked,
|
|
String abiOverride) throws IOException {
|
|
|
|
// Calculate container size, rounding up to nearest MB and adding an
|
|
// extra MB for filesystem overhead
|
|
final long sizeBytes = PackageHelper.calculateInstalledSize(pkg, handle,
|
|
isForwardLocked, abiOverride);
|
|
|
|
// Create new container
|
|
final String newMountPath = PackageHelper.createSdDir(sizeBytes, newCid, key,
|
|
Process.myUid(), isExternal);
|
|
if (newMountPath == null) {
|
|
throw new IOException("Failed to create container " + newCid);
|
|
}
|
|
final File targetDir = new File(newMountPath);
|
|
|
|
try {
|
|
// Copy all APKs
|
|
copyFile(pkg.baseCodePath, targetDir, "base.apk", isForwardLocked);
|
|
if (!ArrayUtils.isEmpty(pkg.splitNames)) {
|
|
for (int i = 0; i < pkg.splitNames.length; i++) {
|
|
copyFile(pkg.splitCodePaths[i], targetDir,
|
|
"split_" + pkg.splitNames[i] + ".apk", isForwardLocked);
|
|
}
|
|
}
|
|
|
|
// Extract native code
|
|
final File libraryRoot = new File(targetDir, LIB_DIR_NAME);
|
|
final int res = NativeLibraryHelper.copyNativeBinariesWithOverride(handle, libraryRoot,
|
|
abiOverride);
|
|
if (res != PackageManager.INSTALL_SUCCEEDED) {
|
|
throw new IOException("Failed to extract native code, res=" + res);
|
|
}
|
|
|
|
if (!PackageHelper.finalizeSdDir(newCid)) {
|
|
throw new IOException("Failed to finalize " + newCid);
|
|
}
|
|
|
|
if (PackageHelper.isContainerMounted(newCid)) {
|
|
PackageHelper.unMountSdDir(newCid);
|
|
}
|
|
|
|
} catch (ErrnoException e) {
|
|
PackageHelper.destroySdDir(newCid);
|
|
throw e.rethrowAsIOException();
|
|
} catch (IOException e) {
|
|
PackageHelper.destroySdDir(newCid);
|
|
throw e;
|
|
}
|
|
|
|
return newMountPath;
|
|
}
|
|
|
|
private int copyPackageInner(PackageLite pkg, IParcelFileDescriptorFactory target)
|
|
throws IOException, RemoteException {
|
|
copyFile(pkg.baseCodePath, target, "base.apk");
|
|
if (!ArrayUtils.isEmpty(pkg.splitNames)) {
|
|
for (int i = 0; i < pkg.splitNames.length; i++) {
|
|
copyFile(pkg.splitCodePaths[i], target, "split_" + pkg.splitNames[i] + ".apk");
|
|
}
|
|
}
|
|
|
|
return PackageManager.INSTALL_SUCCEEDED;
|
|
}
|
|
|
|
private void copyFile(String sourcePath, IParcelFileDescriptorFactory target, String targetName)
|
|
throws IOException, RemoteException {
|
|
Slog.d(TAG, "Copying " + sourcePath + " to " + targetName);
|
|
InputStream in = null;
|
|
OutputStream out = null;
|
|
try {
|
|
in = new FileInputStream(sourcePath);
|
|
out = new ParcelFileDescriptor.AutoCloseOutputStream(
|
|
target.open(targetName, ParcelFileDescriptor.MODE_READ_WRITE));
|
|
Streams.copy(in, out);
|
|
} finally {
|
|
IoUtils.closeQuietly(out);
|
|
IoUtils.closeQuietly(in);
|
|
}
|
|
}
|
|
|
|
private void copyFile(String sourcePath, File targetDir, String targetName,
|
|
boolean isForwardLocked) throws IOException, ErrnoException {
|
|
final File sourceFile = new File(sourcePath);
|
|
final File targetFile = new File(targetDir, targetName);
|
|
|
|
Slog.d(TAG, "Copying " + sourceFile + " to " + targetFile);
|
|
if (!FileUtils.copyFile(sourceFile, targetFile)) {
|
|
throw new IOException("Failed to copy " + sourceFile + " to " + targetFile);
|
|
}
|
|
|
|
if (isForwardLocked) {
|
|
final String publicTargetName = PackageHelper.replaceEnd(targetName,
|
|
".apk", ".zip");
|
|
final File publicTargetFile = new File(targetDir, publicTargetName);
|
|
|
|
PackageHelper.extractPublicFiles(sourceFile, publicTargetFile);
|
|
|
|
Os.chmod(targetFile.getAbsolutePath(), 0640);
|
|
Os.chmod(publicTargetFile.getAbsolutePath(), 0644);
|
|
} else {
|
|
Os.chmod(targetFile.getAbsolutePath(), 0644);
|
|
}
|
|
}
|
|
}
|