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 17 package com.android.defcontainer; 18 19 import static com.android.internal.content.NativeLibraryHelper.LIB_DIR_NAME; 20 21 import android.app.IntentService; 22 import android.content.Context; 23 import android.content.Intent; 24 import android.content.pm.IPackageManager; 25 import android.content.pm.PackageCleanItem; 26 import android.content.pm.PackageInfoLite; 27 import android.content.pm.PackageManager; 28 import android.content.pm.PackageParser; 29 import android.content.pm.PackageParser.PackageLite; 30 import android.content.pm.PackageParser.PackageParserException; 31 import android.content.res.ObbInfo; 32 import android.content.res.ObbScanner; 33 import android.os.Environment; 34 import android.os.Environment.UserEnvironment; 35 import android.os.FileUtils; 36 import android.os.IBinder; 37 import android.os.ParcelFileDescriptor; 38 import android.os.Process; 39 import android.os.RemoteException; 40 import android.os.ServiceManager; 41 import android.system.ErrnoException; 42 import android.system.Os; 43 import android.system.StructStatVfs; 44 import android.util.Slog; 45 46 import com.android.internal.app.IMediaContainerService; 47 import com.android.internal.content.NativeLibraryHelper; 48 import com.android.internal.content.PackageHelper; 49 import com.android.internal.os.IParcelFileDescriptorFactory; 50 import com.android.internal.util.ArrayUtils; 51 52 import libcore.io.IoUtils; 53 import libcore.io.Streams; 54 55 import java.io.File; 56 import java.io.FileInputStream; 57 import java.io.IOException; 58 import java.io.InputStream; 59 import 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 */ 67 public class DefaultContainerService extends IntentService { 68 private static final String TAG = "DefContainer"; 69 70 // TODO: migrate native code unpacking to always be a derivative work 71 72 private IMediaContainerService.Stub mBinder = new IMediaContainerService.Stub() { 73 /** 74 * Creates a new container and copies package there. 75 * 76 * @param packagePath absolute path to the package to be copied. Can be 77 * a single monolithic APK file or a cluster directory 78 * containing one or more APKs. 79 * @param containerId the id of the secure container that should be used 80 * for creating a secure container into which the resource 81 * will be copied. 82 * @param key Refers to key used for encrypting the secure container 83 * @return Returns the new cache path where the resource has been copied 84 * into 85 */ 86 @Override 87 public String copyPackageToContainer(String packagePath, String containerId, String key, 88 boolean isExternal, boolean isForwardLocked, String abiOverride) { 89 if (packagePath == null || containerId == null) { 90 return null; 91 } 92 93 if (isExternal) { 94 // Make sure the sdcard is mounted. 95 String status = Environment.getExternalStorageState(); 96 if (!status.equals(Environment.MEDIA_MOUNTED)) { 97 Slog.w(TAG, "Make sure sdcard is mounted."); 98 return null; 99 } 100 } 101 102 PackageLite pkg = null; 103 NativeLibraryHelper.Handle handle = null; 104 try { 105 final File packageFile = new File(packagePath); 106 pkg = PackageParser.parsePackageLite(packageFile, 0); 107 handle = NativeLibraryHelper.Handle.create(pkg); 108 return copyPackageToContainerInner(pkg, handle, containerId, key, isExternal, 109 isForwardLocked, abiOverride); 110 } catch (PackageParserException | IOException e) { 111 Slog.w(TAG, "Failed to copy package at " + packagePath, e); 112 return null; 113 } finally { 114 IoUtils.closeQuietly(handle); 115 } 116 } 117 118 /** 119 * Copy package to the target location. 120 * 121 * @param packagePath absolute path to the package to be copied. Can be 122 * a single monolithic APK file or a cluster directory 123 * containing one or more APKs. 124 * @return returns status code according to those in 125 * {@link PackageManager} 126 */ 127 @Override 128 public int copyPackage(String packagePath, IParcelFileDescriptorFactory target) { 129 if (packagePath == null || target == null) { 130 return PackageManager.INSTALL_FAILED_INVALID_URI; 131 } 132 133 PackageLite pkg = null; 134 try { 135 final File packageFile = new File(packagePath); 136 pkg = PackageParser.parsePackageLite(packageFile, 0); 137 return copyPackageInner(pkg, target); 138 } catch (PackageParserException | IOException | RemoteException e) { 139 Slog.w(TAG, "Failed to copy package at " + packagePath + ": " + e); 140 return PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE; 141 } 142 } 143 144 /** 145 * Parse given package and return minimal details. 146 * 147 * @param packagePath absolute path to the package to be copied. Can be 148 * a single monolithic APK file or a cluster directory 149 * containing one or more APKs. 150 */ 151 @Override 152 public PackageInfoLite getMinimalPackageInfo(String packagePath, int flags, 153 String abiOverride) { 154 final Context context = DefaultContainerService.this; 155 final boolean isForwardLocked = (flags & PackageManager.INSTALL_FORWARD_LOCK) != 0; 156 157 PackageInfoLite ret = new PackageInfoLite(); 158 if (packagePath == null) { 159 Slog.i(TAG, "Invalid package file " + packagePath); 160 ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_APK; 161 return ret; 162 } 163 164 final File packageFile = new File(packagePath); 165 final PackageParser.PackageLite pkg; 166 final long sizeBytes; 167 try { 168 pkg = PackageParser.parsePackageLite(packageFile, 0); 169 sizeBytes = PackageHelper.calculateInstalledSize(pkg, isForwardLocked, abiOverride); 170 } catch (PackageParserException | IOException e) { 171 Slog.w(TAG, "Failed to parse package at " + packagePath + ": " + e); 172 173 if (!packageFile.exists()) { 174 ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_URI; 175 } else { 176 ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_APK; 177 } 178 179 return ret; 180 } 181 182 ret.packageName = pkg.packageName; 183 ret.splitNames = pkg.splitNames; 184 ret.versionCode = pkg.versionCode; 185 ret.baseRevisionCode = pkg.baseRevisionCode; 186 ret.splitRevisionCodes = pkg.splitRevisionCodes; 187 ret.installLocation = pkg.installLocation; 188 ret.verifiers = pkg.verifiers; 189 ret.recommendedInstallLocation = PackageHelper.resolveInstallLocation(context, 190 pkg.packageName, pkg.installLocation, sizeBytes, flags); 191 ret.multiArch = pkg.multiArch; 192 193 return ret; 194 } 195 196 @Override 197 public ObbInfo getObbInfo(String filename) { 198 try { 199 return ObbScanner.getObbInfo(filename); 200 } catch (IOException e) { 201 Slog.d(TAG, "Couldn't get OBB info for " + filename); 202 return null; 203 } 204 } 205 206 @Override 207 public long calculateDirectorySize(String path) throws RemoteException { 208 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); 209 210 final File dir = Environment.maybeTranslateEmulatedPathToInternal(new File(path)); 211 if (dir.exists() && dir.isDirectory()) { 212 final String targetPath = dir.getAbsolutePath(); 213 return MeasurementUtils.measureDirectory(targetPath); 214 } else { 215 return 0L; 216 } 217 } 218 219 @Override 220 public long[] getFileSystemStats(String path) { 221 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); 222 223 try { 224 final StructStatVfs stat = Os.statvfs(path); 225 final long totalSize = stat.f_blocks * stat.f_bsize; 226 final long availSize = stat.f_bavail * stat.f_bsize; 227 return new long[] { totalSize, availSize }; 228 } catch (ErrnoException e) { 229 throw new IllegalStateException(e); 230 } 231 } 232 233 @Override 234 public void clearDirectory(String path) throws RemoteException { 235 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); 236 237 final File directory = new File(path); 238 if (directory.exists() && directory.isDirectory()) { 239 eraseFiles(directory); 240 } 241 } 242 243 /** 244 * Calculate estimated footprint of given package post-installation. 245 * 246 * @param packagePath absolute path to the package to be copied. Can be 247 * a single monolithic APK file or a cluster directory 248 * containing one or more APKs. 249 */ 250 @Override 251 public long calculateInstalledSize(String packagePath, boolean isForwardLocked, 252 String abiOverride) throws RemoteException { 253 final File packageFile = new File(packagePath); 254 final PackageParser.PackageLite pkg; 255 try { 256 pkg = PackageParser.parsePackageLite(packageFile, 0); 257 return PackageHelper.calculateInstalledSize(pkg, isForwardLocked, abiOverride); 258 } catch (PackageParserException | IOException e) { 259 Slog.w(TAG, "Failed to calculate installed size: " + e); 260 return Long.MAX_VALUE; 261 } 262 } 263 }; 264 DefaultContainerService()265 public DefaultContainerService() { 266 super("DefaultContainerService"); 267 setIntentRedelivery(true); 268 } 269 270 @Override onHandleIntent(Intent intent)271 protected void onHandleIntent(Intent intent) { 272 if (PackageManager.ACTION_CLEAN_EXTERNAL_STORAGE.equals(intent.getAction())) { 273 final IPackageManager pm = IPackageManager.Stub.asInterface( 274 ServiceManager.getService("package")); 275 PackageCleanItem item = null; 276 try { 277 while ((item = pm.nextPackageToClean(item)) != null) { 278 final UserEnvironment userEnv = new UserEnvironment(item.userId); 279 eraseFiles(userEnv.buildExternalStorageAppDataDirs(item.packageName)); 280 eraseFiles(userEnv.buildExternalStorageAppMediaDirs(item.packageName)); 281 if (item.andCode) { 282 eraseFiles(userEnv.buildExternalStorageAppObbDirs(item.packageName)); 283 } 284 } 285 } catch (RemoteException e) { 286 } 287 } 288 } 289 eraseFiles(File[] paths)290 void eraseFiles(File[] paths) { 291 for (File path : paths) { 292 eraseFiles(path); 293 } 294 } 295 eraseFiles(File path)296 void eraseFiles(File path) { 297 if (path.isDirectory()) { 298 String[] files = path.list(); 299 if (files != null) { 300 for (String file : files) { 301 eraseFiles(new File(path, file)); 302 } 303 } 304 } 305 path.delete(); 306 } 307 308 @Override onBind(Intent intent)309 public IBinder onBind(Intent intent) { 310 return mBinder; 311 } 312 copyPackageToContainerInner(PackageLite pkg, NativeLibraryHelper.Handle handle, String newCid, String key, boolean isExternal, boolean isForwardLocked, String abiOverride)313 private String copyPackageToContainerInner(PackageLite pkg, NativeLibraryHelper.Handle handle, 314 String newCid, String key, boolean isExternal, boolean isForwardLocked, 315 String abiOverride) throws IOException { 316 317 // Calculate container size, rounding up to nearest MB and adding an 318 // extra MB for filesystem overhead 319 final long sizeBytes = PackageHelper.calculateInstalledSize(pkg, handle, 320 isForwardLocked, abiOverride); 321 322 // Create new container 323 final String newMountPath = PackageHelper.createSdDir(sizeBytes, newCid, key, 324 Process.myUid(), 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.copyNativeBinariesWithOverride(handle, libraryRoot, 343 abiOverride); 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 copyPackageInner(PackageLite pkg, IParcelFileDescriptorFactory target)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 copyFile(String sourcePath, IParcelFileDescriptorFactory target, String targetName)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 copyFile(String sourcePath, File targetDir, String targetName, boolean isForwardLocked)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