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 265 public DefaultContainerService() { 266 super("DefaultContainerService"); 267 setIntentRedelivery(true); 268 } 269 270 @Override 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 290 void eraseFiles(File[] paths) { 291 for (File path : paths) { 292 eraseFiles(path); 293 } 294 } 295 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 309 public IBinder onBind(Intent intent) { 310 return mBinder; 311 } 312 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 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