DefaultContainerService.java revision bb7b7bea19223c1eba74f525c7fe87ca3911813b
1/* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.defcontainer; 18 19import static android.net.TrafficStats.MB_IN_BYTES; 20 21import android.app.IntentService; 22import android.content.Context; 23import android.content.Intent; 24import android.content.pm.IPackageManager; 25import android.content.pm.PackageCleanItem; 26import android.content.pm.PackageInfoLite; 27import android.content.pm.PackageManager; 28import android.content.pm.PackageParser; 29import android.content.pm.PackageParser.PackageLite; 30import android.content.pm.PackageParser.PackageParserException; 31import android.content.res.ObbInfo; 32import android.content.res.ObbScanner; 33import android.os.Environment; 34import android.os.Environment.UserEnvironment; 35import android.os.FileUtils; 36import android.os.IBinder; 37import android.os.ParcelFileDescriptor; 38import android.os.Process; 39import android.os.RemoteException; 40import android.os.ServiceManager; 41import android.system.ErrnoException; 42import android.system.Os; 43import android.system.StructStatVfs; 44import android.util.Slog; 45 46import com.android.internal.app.IMediaContainerService; 47import com.android.internal.content.NativeLibraryHelper; 48import com.android.internal.content.PackageHelper; 49import com.android.internal.os.IParcelFileDescriptorFactory; 50import com.android.internal.util.ArrayUtils; 51 52import libcore.io.IoUtils; 53import libcore.io.Streams; 54 55import java.io.File; 56import java.io.FileInputStream; 57import java.io.IOException; 58import java.io.InputStream; 59import java.io.OutputStream; 60 61/** 62 * Service that offers to inspect and copy files that may reside on removable 63 * storage. This is designed to prevent the system process from holding onto 64 * open files that cause the kernel to kill it when the underlying device is 65 * removed. 66 */ 67public class DefaultContainerService extends IntentService { 68 private static final String TAG = "DefContainer"; 69 70 private static final String LIB_DIR_NAME = "lib"; 71 72 // TODO: migrate native code unpacking to always be a derivative work 73 74 private IMediaContainerService.Stub mBinder = new IMediaContainerService.Stub() { 75 /** 76 * Creates a new container and copies package there. 77 * 78 * @param packagePath absolute path to the package to be copied. Can be 79 * a single monolithic APK file or a cluster directory 80 * containing one or more APKs. 81 * @param containerId the id of the secure container that should be used 82 * for creating a secure container into which the resource 83 * will be copied. 84 * @param key Refers to key used for encrypting the secure container 85 * @return Returns the new cache path where the resource has been copied 86 * into 87 */ 88 @Override 89 public String copyPackageToContainer(String packagePath, String containerId, String key, 90 boolean isExternal, boolean isForwardLocked, String abiOverride) { 91 if (packagePath == null || containerId == null) { 92 return null; 93 } 94 95 if (isExternal) { 96 // Make sure the sdcard is mounted. 97 String status = Environment.getExternalStorageState(); 98 if (!status.equals(Environment.MEDIA_MOUNTED)) { 99 Slog.w(TAG, "Make sure sdcard is mounted."); 100 return null; 101 } 102 } 103 104 PackageLite pkg = null; 105 NativeLibraryHelper.Handle handle = null; 106 try { 107 final File packageFile = new File(packagePath); 108 pkg = PackageParser.parsePackageLite(packageFile, 0); 109 handle = NativeLibraryHelper.Handle.create(pkg); 110 return copyPackageToContainerInner(pkg, handle, containerId, key, isExternal, 111 isForwardLocked, abiOverride); 112 } catch (PackageParserException | IOException e) { 113 Slog.w(TAG, "Failed to copy package at " + packagePath, e); 114 return null; 115 } finally { 116 IoUtils.closeQuietly(handle); 117 } 118 } 119 120 /** 121 * Copy package to the target location. 122 * 123 * @param packagePath absolute path to the package to be copied. Can be 124 * a single monolithic APK file or a cluster directory 125 * containing one or more APKs. 126 * @return returns status code according to those in 127 * {@link PackageManager} 128 */ 129 @Override 130 public int copyPackage(String packagePath, IParcelFileDescriptorFactory target) { 131 if (packagePath == null || target == null) { 132 return PackageManager.INSTALL_FAILED_INVALID_URI; 133 } 134 135 PackageLite pkg = null; 136 try { 137 final File packageFile = new File(packagePath); 138 pkg = PackageParser.parsePackageLite(packageFile, 0); 139 return copyPackageInner(pkg, target); 140 } catch (PackageParserException | IOException | RemoteException e) { 141 Slog.w(TAG, "Failed to copy package at " + packagePath + ": " + e); 142 return PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE; 143 } 144 } 145 146 /** 147 * Parse given package and return minimal details. 148 * 149 * @param packagePath absolute path to the package to be copied. Can be 150 * a single monolithic APK file or a cluster directory 151 * containing one or more APKs. 152 */ 153 @Override 154 public PackageInfoLite getMinimalPackageInfo(String packagePath, int flags, 155 String abiOverride) { 156 final Context context = DefaultContainerService.this; 157 final boolean isForwardLocked = (flags & PackageManager.INSTALL_FORWARD_LOCK) != 0; 158 159 PackageInfoLite ret = new PackageInfoLite(); 160 if (packagePath == null) { 161 Slog.i(TAG, "Invalid package file " + packagePath); 162 ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_APK; 163 return ret; 164 } 165 166 final File packageFile = new File(packagePath); 167 final PackageParser.PackageLite pkg; 168 final long sizeBytes; 169 try { 170 pkg = PackageParser.parsePackageLite(packageFile, 0); 171 sizeBytes = calculateInstalledSizeInner(pkg, isForwardLocked, abiOverride); 172 } catch (PackageParserException | IOException e) { 173 Slog.w(TAG, "Failed to parse package at " + packagePath + ": " + e); 174 175 if (!packageFile.exists()) { 176 ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_URI; 177 } else { 178 ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_APK; 179 } 180 181 return ret; 182 } 183 184 ret.packageName = pkg.packageName; 185 ret.versionCode = pkg.versionCode; 186 ret.installLocation = pkg.installLocation; 187 ret.verifiers = pkg.verifiers; 188 ret.recommendedInstallLocation = PackageHelper.resolveInstallLocation(context, 189 pkg.packageName, pkg.installLocation, sizeBytes, flags); 190 ret.multiArch = pkg.multiArch; 191 192 return ret; 193 } 194 195 @Override 196 public ObbInfo getObbInfo(String filename) { 197 try { 198 return ObbScanner.getObbInfo(filename); 199 } catch (IOException e) { 200 Slog.d(TAG, "Couldn't get OBB info for " + filename); 201 return null; 202 } 203 } 204 205 @Override 206 public long calculateDirectorySize(String path) throws RemoteException { 207 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); 208 209 final File dir = Environment.maybeTranslateEmulatedPathToInternal(new File(path)); 210 if (dir.exists() && dir.isDirectory()) { 211 final String targetPath = dir.getAbsolutePath(); 212 return MeasurementUtils.measureDirectory(targetPath); 213 } else { 214 return 0L; 215 } 216 } 217 218 @Override 219 public long[] getFileSystemStats(String path) { 220 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); 221 222 try { 223 final StructStatVfs stat = Os.statvfs(path); 224 final long totalSize = stat.f_blocks * stat.f_bsize; 225 final long availSize = stat.f_bavail * stat.f_bsize; 226 return new long[] { totalSize, availSize }; 227 } catch (ErrnoException e) { 228 throw new IllegalStateException(e); 229 } 230 } 231 232 @Override 233 public void clearDirectory(String path) throws RemoteException { 234 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); 235 236 final File directory = new File(path); 237 if (directory.exists() && directory.isDirectory()) { 238 eraseFiles(directory); 239 } 240 } 241 242 /** 243 * Calculate estimated footprint of given package post-installation. 244 * 245 * @param packagePath absolute path to the package to be copied. Can be 246 * a single monolithic APK file or a cluster directory 247 * containing one or more APKs. 248 */ 249 @Override 250 public long calculateInstalledSize(String packagePath, boolean isForwardLocked, 251 String abiOverride) throws RemoteException { 252 final File packageFile = new File(packagePath); 253 final PackageParser.PackageLite pkg; 254 try { 255 pkg = PackageParser.parsePackageLite(packageFile, 0); 256 return calculateInstalledSizeInner(pkg, isForwardLocked, abiOverride); 257 } catch (PackageParserException | IOException e) { 258 Slog.w(TAG, "Failed to calculate installed size: " + e); 259 return Long.MAX_VALUE; 260 } 261 } 262 }; 263 264 public DefaultContainerService() { 265 super("DefaultContainerService"); 266 setIntentRedelivery(true); 267 } 268 269 @Override 270 protected void onHandleIntent(Intent intent) { 271 if (PackageManager.ACTION_CLEAN_EXTERNAL_STORAGE.equals(intent.getAction())) { 272 final IPackageManager pm = IPackageManager.Stub.asInterface( 273 ServiceManager.getService("package")); 274 PackageCleanItem item = null; 275 try { 276 while ((item = pm.nextPackageToClean(item)) != null) { 277 final UserEnvironment userEnv = new UserEnvironment(item.userId); 278 eraseFiles(userEnv.buildExternalStorageAppDataDirs(item.packageName)); 279 eraseFiles(userEnv.buildExternalStorageAppMediaDirs(item.packageName)); 280 if (item.andCode) { 281 eraseFiles(userEnv.buildExternalStorageAppObbDirs(item.packageName)); 282 } 283 } 284 } catch (RemoteException e) { 285 } 286 } 287 } 288 289 void eraseFiles(File[] paths) { 290 for (File path : paths) { 291 eraseFiles(path); 292 } 293 } 294 295 void eraseFiles(File path) { 296 if (path.isDirectory()) { 297 String[] files = path.list(); 298 if (files != null) { 299 for (String file : files) { 300 eraseFiles(new File(path, file)); 301 } 302 } 303 } 304 path.delete(); 305 } 306 307 @Override 308 public IBinder onBind(Intent intent) { 309 return mBinder; 310 } 311 312 private String copyPackageToContainerInner(PackageLite pkg, NativeLibraryHelper.Handle handle, 313 String newCid, String key, boolean isExternal, boolean isForwardLocked, 314 String abiOverride) throws IOException { 315 316 // Calculate container size, rounding up to nearest MB and adding an 317 // extra MB for filesystem overhead 318 final long sizeBytes = calculateInstalledSizeInner(pkg, handle, isForwardLocked, 319 abiOverride); 320 final int sizeMb = ((int) ((sizeBytes + MB_IN_BYTES) / MB_IN_BYTES)) + 1; 321 322 // Create new container 323 final String newMountPath = PackageHelper.createSdDir(sizeMb, newCid, key, Process.myUid(), 324 isExternal); 325 if (newMountPath == null) { 326 throw new IOException("Failed to create container " + newCid); 327 } 328 final File targetDir = new File(newMountPath); 329 330 try { 331 // Copy all APKs 332 copyFile(pkg.baseCodePath, targetDir, "base.apk", isForwardLocked); 333 if (!ArrayUtils.isEmpty(pkg.splitNames)) { 334 for (int i = 0; i < pkg.splitNames.length; i++) { 335 copyFile(pkg.splitCodePaths[i], targetDir, 336 "split_" + pkg.splitNames[i] + ".apk", isForwardLocked); 337 } 338 } 339 340 // Extract native code 341 final File libraryRoot = new File(targetDir, LIB_DIR_NAME); 342 final int res = NativeLibraryHelper.copyNativeBinariesIfNeededLI(handle, libraryRoot, 343 abiOverride, pkg.multiArch); 344 if (res != PackageManager.INSTALL_SUCCEEDED) { 345 throw new IOException("Failed to extract native code, res=" + res); 346 } 347 348 if (!PackageHelper.finalizeSdDir(newCid)) { 349 throw new IOException("Failed to finalize " + newCid); 350 } 351 352 if (PackageHelper.isContainerMounted(newCid)) { 353 PackageHelper.unMountSdDir(newCid); 354 } 355 356 } catch (ErrnoException e) { 357 PackageHelper.destroySdDir(newCid); 358 throw e.rethrowAsIOException(); 359 } catch (IOException e) { 360 PackageHelper.destroySdDir(newCid); 361 throw e; 362 } 363 364 return newMountPath; 365 } 366 367 private int copyPackageInner(PackageLite pkg, IParcelFileDescriptorFactory target) 368 throws IOException, RemoteException { 369 copyFile(pkg.baseCodePath, target, "base.apk"); 370 if (!ArrayUtils.isEmpty(pkg.splitNames)) { 371 for (int i = 0; i < pkg.splitNames.length; i++) { 372 copyFile(pkg.splitCodePaths[i], target, "split_" + pkg.splitNames[i] + ".apk"); 373 } 374 } 375 376 return PackageManager.INSTALL_SUCCEEDED; 377 } 378 379 private void copyFile(String sourcePath, IParcelFileDescriptorFactory target, String targetName) 380 throws IOException, RemoteException { 381 Slog.d(TAG, "Copying " + sourcePath + " to " + targetName); 382 InputStream in = null; 383 OutputStream out = null; 384 try { 385 in = new FileInputStream(sourcePath); 386 out = new ParcelFileDescriptor.AutoCloseOutputStream( 387 target.open(targetName, ParcelFileDescriptor.MODE_READ_WRITE)); 388 Streams.copy(in, out); 389 } finally { 390 IoUtils.closeQuietly(out); 391 IoUtils.closeQuietly(in); 392 } 393 } 394 395 private void copyFile(String sourcePath, File targetDir, String targetName, 396 boolean isForwardLocked) throws IOException, ErrnoException { 397 final File sourceFile = new File(sourcePath); 398 final File targetFile = new File(targetDir, targetName); 399 400 Slog.d(TAG, "Copying " + sourceFile + " to " + targetFile); 401 if (!FileUtils.copyFile(sourceFile, targetFile)) { 402 throw new IOException("Failed to copy " + sourceFile + " to " + targetFile); 403 } 404 405 if (isForwardLocked) { 406 final String publicTargetName = PackageHelper.replaceEnd(targetName, 407 ".apk", ".zip"); 408 final File publicTargetFile = new File(targetDir, publicTargetName); 409 410 PackageHelper.extractPublicFiles(sourceFile, publicTargetFile); 411 412 Os.chmod(targetFile.getAbsolutePath(), 0640); 413 Os.chmod(publicTargetFile.getAbsolutePath(), 0644); 414 } else { 415 Os.chmod(targetFile.getAbsolutePath(), 0644); 416 } 417 } 418 419 private long calculateInstalledSizeInner(PackageLite pkg, boolean isForwardLocked, 420 String abiOverride) throws IOException { 421 NativeLibraryHelper.Handle handle = null; 422 try { 423 handle = NativeLibraryHelper.Handle.create(pkg); 424 return calculateInstalledSizeInner(pkg, handle, isForwardLocked, abiOverride); 425 } finally { 426 IoUtils.closeQuietly(handle); 427 } 428 } 429 430 private long calculateInstalledSizeInner(PackageLite pkg, NativeLibraryHelper.Handle handle, 431 boolean isForwardLocked, String abiOverride) throws IOException { 432 long sizeBytes = 0; 433 434 // Include raw APKs, and possibly unpacked resources 435 for (String codePath : pkg.getAllCodePaths()) { 436 final File codeFile = new File(codePath); 437 sizeBytes += codeFile.length(); 438 439 if (isForwardLocked) { 440 sizeBytes += PackageHelper.extractPublicFiles(codeFile, null); 441 } 442 } 443 444 // Include all relevant native code 445 sizeBytes += NativeLibraryHelper.sumNativeBinaries(handle, abiOverride, pkg.multiArch); 446 447 return sizeBytes; 448 } 449} 450