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