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.Binder; 34 import android.os.Environment; 35 import android.os.Environment.UserEnvironment; 36 import android.os.FileUtils; 37 import android.os.IBinder; 38 import android.os.ParcelFileDescriptor; 39 import android.os.Process; 40 import android.os.RemoteException; 41 import android.os.ServiceManager; 42 import android.system.ErrnoException; 43 import android.system.Os; 44 import android.system.StructStatVfs; 45 import android.util.Slog; 46 47 import com.android.internal.app.IMediaContainerService; 48 import com.android.internal.content.NativeLibraryHelper; 49 import com.android.internal.content.PackageHelper; 50 import com.android.internal.os.IParcelFileDescriptorFactory; 51 import com.android.internal.util.ArrayUtils; 52 53 import libcore.io.IoUtils; 54 import libcore.io.Streams; 55 56 import java.io.File; 57 import java.io.FileInputStream; 58 import java.io.IOException; 59 import java.io.InputStream; 60 import 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 */ 68 public 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 DefaultContainerService()268 public DefaultContainerService() { 269 super("DefaultContainerService"); 270 setIntentRedelivery(true); 271 } 272 273 @Override onHandleIntent(Intent intent)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 eraseFiles(File[] paths)293 void eraseFiles(File[] paths) { 294 for (File path : paths) { 295 eraseFiles(path); 296 } 297 } 298 eraseFiles(File path)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 onBind(Intent intent)312 public IBinder onBind(Intent intent) { 313 return mBinder; 314 } 315 copyPackageToContainerInner(PackageLite pkg, NativeLibraryHelper.Handle handle, String newCid, String key, boolean isExternal, boolean isForwardLocked, String abiOverride)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 copyPackageInner(PackageLite pkg, IParcelFileDescriptorFactory target)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 copyFile(String sourcePath, IParcelFileDescriptorFactory target, String targetName)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 copyFile(String sourcePath, File targetDir, String targetName, boolean isForwardLocked)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