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.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 // 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.versionCode = pkg.versionCode; 184 ret.installLocation = pkg.installLocation; 185 ret.verifiers = pkg.verifiers; 186 ret.recommendedInstallLocation = PackageHelper.resolveInstallLocation(context, 187 pkg.packageName, pkg.installLocation, sizeBytes, flags); 188 ret.multiArch = pkg.multiArch; 189 190 return ret; 191 } 192 193 @Override 194 public ObbInfo getObbInfo(String filename) { 195 try { 196 return ObbScanner.getObbInfo(filename); 197 } catch (IOException e) { 198 Slog.d(TAG, "Couldn't get OBB info for " + filename); 199 return null; 200 } 201 } 202 203 @Override 204 public long calculateDirectorySize(String path) throws RemoteException { 205 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); 206 207 final File dir = Environment.maybeTranslateEmulatedPathToInternal(new File(path)); 208 if (dir.exists() && dir.isDirectory()) { 209 final String targetPath = dir.getAbsolutePath(); 210 return MeasurementUtils.measureDirectory(targetPath); 211 } else { 212 return 0L; 213 } 214 } 215 216 @Override 217 public long[] getFileSystemStats(String path) { 218 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); 219 220 try { 221 final StructStatVfs stat = Os.statvfs(path); 222 final long totalSize = stat.f_blocks * stat.f_bsize; 223 final long availSize = stat.f_bavail * stat.f_bsize; 224 return new long[] { totalSize, availSize }; 225 } catch (ErrnoException e) { 226 throw new IllegalStateException(e); 227 } 228 } 229 230 @Override 231 public void clearDirectory(String path) throws RemoteException { 232 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); 233 234 final File directory = new File(path); 235 if (directory.exists() && directory.isDirectory()) { 236 eraseFiles(directory); 237 } 238 } 239 240 /** 241 * Calculate estimated footprint of given package post-installation. 242 * 243 * @param packagePath absolute path to the package to be copied. Can be 244 * a single monolithic APK file or a cluster directory 245 * containing one or more APKs. 246 */ 247 @Override 248 public long calculateInstalledSize(String packagePath, boolean isForwardLocked, 249 String abiOverride) throws RemoteException { 250 final File packageFile = new File(packagePath); 251 final PackageParser.PackageLite pkg; 252 try { 253 pkg = PackageParser.parsePackageLite(packageFile, 0); 254 return PackageHelper.calculateInstalledSize(pkg, isForwardLocked, abiOverride); 255 } catch (PackageParserException | IOException e) { 256 Slog.w(TAG, "Failed to calculate installed size: " + e); 257 return Long.MAX_VALUE; 258 } 259 } 260 }; 261 262 public DefaultContainerService() { 263 super("DefaultContainerService"); 264 setIntentRedelivery(true); 265 } 266 267 @Override 268 protected void onHandleIntent(Intent intent) { 269 if (PackageManager.ACTION_CLEAN_EXTERNAL_STORAGE.equals(intent.getAction())) { 270 final IPackageManager pm = IPackageManager.Stub.asInterface( 271 ServiceManager.getService("package")); 272 PackageCleanItem item = null; 273 try { 274 while ((item = pm.nextPackageToClean(item)) != null) { 275 final UserEnvironment userEnv = new UserEnvironment(item.userId); 276 eraseFiles(userEnv.buildExternalStorageAppDataDirs(item.packageName)); 277 eraseFiles(userEnv.buildExternalStorageAppMediaDirs(item.packageName)); 278 if (item.andCode) { 279 eraseFiles(userEnv.buildExternalStorageAppObbDirs(item.packageName)); 280 } 281 } 282 } catch (RemoteException e) { 283 } 284 } 285 } 286 287 void eraseFiles(File[] paths) { 288 for (File path : paths) { 289 eraseFiles(path); 290 } 291 } 292 293 void eraseFiles(File path) { 294 if (path.isDirectory()) { 295 String[] files = path.list(); 296 if (files != null) { 297 for (String file : files) { 298 eraseFiles(new File(path, file)); 299 } 300 } 301 } 302 path.delete(); 303 } 304 305 @Override 306 public IBinder onBind(Intent intent) { 307 return mBinder; 308 } 309 310 private String copyPackageToContainerInner(PackageLite pkg, NativeLibraryHelper.Handle handle, 311 String newCid, String key, boolean isExternal, boolean isForwardLocked, 312 String abiOverride) throws IOException { 313 314 // Calculate container size, rounding up to nearest MB and adding an 315 // extra MB for filesystem overhead 316 final long sizeBytes = PackageHelper.calculateInstalledSize(pkg, handle, 317 isForwardLocked, abiOverride); 318 319 // Create new container 320 final String newMountPath = PackageHelper.createSdDir(sizeBytes, newCid, key, 321 Process.myUid(), isExternal); 322 if (newMountPath == null) { 323 throw new IOException("Failed to create container " + newCid); 324 } 325 final File targetDir = new File(newMountPath); 326 327 try { 328 // Copy all APKs 329 copyFile(pkg.baseCodePath, targetDir, "base.apk", isForwardLocked); 330 if (!ArrayUtils.isEmpty(pkg.splitNames)) { 331 for (int i = 0; i < pkg.splitNames.length; i++) { 332 copyFile(pkg.splitCodePaths[i], targetDir, 333 "split_" + pkg.splitNames[i] + ".apk", isForwardLocked); 334 } 335 } 336 337 // Extract native code 338 final File libraryRoot = new File(targetDir, LIB_DIR_NAME); 339 final int res = NativeLibraryHelper.copyNativeBinariesWithOverride(handle, libraryRoot, 340 abiOverride); 341 if (res != PackageManager.INSTALL_SUCCEEDED) { 342 throw new IOException("Failed to extract native code, res=" + res); 343 } 344 345 if (!PackageHelper.finalizeSdDir(newCid)) { 346 throw new IOException("Failed to finalize " + newCid); 347 } 348 349 if (PackageHelper.isContainerMounted(newCid)) { 350 PackageHelper.unMountSdDir(newCid); 351 } 352 353 } catch (ErrnoException e) { 354 PackageHelper.destroySdDir(newCid); 355 throw e.rethrowAsIOException(); 356 } catch (IOException e) { 357 PackageHelper.destroySdDir(newCid); 358 throw e; 359 } 360 361 return newMountPath; 362 } 363 364 private int copyPackageInner(PackageLite pkg, IParcelFileDescriptorFactory target) 365 throws IOException, RemoteException { 366 copyFile(pkg.baseCodePath, target, "base.apk"); 367 if (!ArrayUtils.isEmpty(pkg.splitNames)) { 368 for (int i = 0; i < pkg.splitNames.length; i++) { 369 copyFile(pkg.splitCodePaths[i], target, "split_" + pkg.splitNames[i] + ".apk"); 370 } 371 } 372 373 return PackageManager.INSTALL_SUCCEEDED; 374 } 375 376 private void copyFile(String sourcePath, IParcelFileDescriptorFactory target, String targetName) 377 throws IOException, RemoteException { 378 Slog.d(TAG, "Copying " + sourcePath + " to " + targetName); 379 InputStream in = null; 380 OutputStream out = null; 381 try { 382 in = new FileInputStream(sourcePath); 383 out = new ParcelFileDescriptor.AutoCloseOutputStream( 384 target.open(targetName, ParcelFileDescriptor.MODE_READ_WRITE)); 385 Streams.copy(in, out); 386 } finally { 387 IoUtils.closeQuietly(out); 388 IoUtils.closeQuietly(in); 389 } 390 } 391 392 private void copyFile(String sourcePath, File targetDir, String targetName, 393 boolean isForwardLocked) throws IOException, ErrnoException { 394 final File sourceFile = new File(sourcePath); 395 final File targetFile = new File(targetDir, targetName); 396 397 Slog.d(TAG, "Copying " + sourceFile + " to " + targetFile); 398 if (!FileUtils.copyFile(sourceFile, targetFile)) { 399 throw new IOException("Failed to copy " + sourceFile + " to " + targetFile); 400 } 401 402 if (isForwardLocked) { 403 final String publicTargetName = PackageHelper.replaceEnd(targetName, 404 ".apk", ".zip"); 405 final File publicTargetFile = new File(targetDir, publicTargetName); 406 407 PackageHelper.extractPublicFiles(sourceFile, publicTargetFile); 408 409 Os.chmod(targetFile.getAbsolutePath(), 0640); 410 Os.chmod(publicTargetFile.getAbsolutePath(), 0644); 411 } else { 412 Os.chmod(targetFile.getAbsolutePath(), 0644); 413 } 414 } 415} 416