PackageDexUsage.java revision 51f521c3bf46e6040f36757bc53ea57ddc7be85e
1/* 2 * Copyright (C) 2016 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.server.pm.dex; 18 19import android.util.AtomicFile; 20import android.util.Slog; 21import android.os.Build; 22 23import com.android.internal.annotations.GuardedBy; 24import com.android.internal.util.FastPrintWriter; 25import com.android.server.pm.AbstractStatsBase; 26import com.android.server.pm.PackageManagerServiceUtils; 27 28import java.io.BufferedReader; 29import java.io.File; 30import java.io.FileNotFoundException; 31import java.io.FileOutputStream; 32import java.io.InputStreamReader; 33import java.io.IOException; 34import java.io.OutputStreamWriter; 35import java.io.Reader; 36import java.io.StringWriter; 37import java.io.Writer; 38import java.util.Iterator; 39import java.util.HashMap; 40import java.util.HashSet; 41import java.util.Map; 42import java.util.Set; 43 44import dalvik.system.VMRuntime; 45import libcore.io.IoUtils; 46 47/** 48 * Stat file which store usage information about dex files. 49 */ 50public class PackageDexUsage extends AbstractStatsBase<Void> { 51 private final static String TAG = "PackageDexUsage"; 52 53 private final static int PACKAGE_DEX_USAGE_VERSION = 1; 54 private final static String PACKAGE_DEX_USAGE_VERSION_HEADER = 55 "PACKAGE_MANAGER__PACKAGE_DEX_USAGE__"; 56 57 private final static String SPLIT_CHAR = ","; 58 private final static String DEX_LINE_CHAR = "#"; 59 60 // Map which structures the information we have on a package. 61 // Maps package name to package data (which stores info about UsedByOtherApps and 62 // secondary dex files.). 63 // Access to this map needs synchronized. 64 @GuardedBy("mPackageUseInfoMap") 65 private Map<String, PackageUseInfo> mPackageUseInfoMap; 66 67 public PackageDexUsage() { 68 super("package-dex-usage.list", "PackageDexUsage_DiskWriter", /*lock*/ false); 69 mPackageUseInfoMap = new HashMap<>(); 70 } 71 72 /** 73 * Record a dex file load. 74 * 75 * Note this is called when apps load dex files and as such it should return 76 * as fast as possible. 77 * 78 * @param loadingPackage the package performing the load 79 * @param dexPath the path of the dex files being loaded 80 * @param ownerUserId the user id which runs the code loading the dex files 81 * @param loaderIsa the ISA of the app loading the dex files 82 * @param isUsedByOtherApps whether or not this dex file was not loaded by its owning package 83 * @param primaryOrSplit whether or not the dex file is a primary/split dex. True indicates 84 * the file is either primary or a split. False indicates the file is secondary dex. 85 * @return true if the dex load constitutes new information, or false if this information 86 * has been seen before. 87 */ 88 public boolean record(String owningPackageName, String dexPath, int ownerUserId, 89 String loaderIsa, boolean isUsedByOtherApps, boolean primaryOrSplit) { 90 if (!PackageManagerServiceUtils.checkISA(loaderIsa)) { 91 throw new IllegalArgumentException("loaderIsa " + loaderIsa + " is unsupported"); 92 } 93 synchronized (mPackageUseInfoMap) { 94 PackageUseInfo packageUseInfo = mPackageUseInfoMap.get(owningPackageName); 95 if (packageUseInfo == null) { 96 // This is the first time we see the package. 97 packageUseInfo = new PackageUseInfo(); 98 if (primaryOrSplit) { 99 // If we have a primary or a split apk, set isUsedByOtherApps. 100 // We do not need to record the loaderIsa or the owner because we compile 101 // primaries for all users and all ISAs. 102 packageUseInfo.mIsUsedByOtherApps = isUsedByOtherApps; 103 } else { 104 // For secondary dex files record the loaderISA and the owner. We'll need 105 // to know under which user to compile and for what ISA. 106 packageUseInfo.mDexUseInfoMap.put( 107 dexPath, new DexUseInfo(isUsedByOtherApps, ownerUserId, loaderIsa)); 108 } 109 mPackageUseInfoMap.put(owningPackageName, packageUseInfo); 110 return true; 111 } else { 112 // We already have data on this package. Amend it. 113 if (primaryOrSplit) { 114 // We have a possible update on the primary apk usage. Merge 115 // isUsedByOtherApps information and return if there was an update. 116 return packageUseInfo.merge(isUsedByOtherApps); 117 } else { 118 DexUseInfo newData = new DexUseInfo( 119 isUsedByOtherApps, ownerUserId, loaderIsa); 120 DexUseInfo existingData = packageUseInfo.mDexUseInfoMap.get(dexPath); 121 if (existingData == null) { 122 // It's the first time we see this dex file. 123 packageUseInfo.mDexUseInfoMap.put(dexPath, newData); 124 return true; 125 } else { 126 if (ownerUserId != existingData.mOwnerUserId) { 127 // Oups, this should never happen, the DexManager who calls this should 128 // do the proper checks and not call record if the user does not own the 129 // dex path. 130 // Secondary dex files are stored in the app user directory. A change in 131 // owningUser for the same path means that something went wrong at some 132 // higher level, and the loaderUser was allowed to cross 133 // user-boundaries and access data from what we know to be the owner 134 // user. 135 throw new IllegalArgumentException("Trying to change ownerUserId for " 136 + " dex path " + dexPath + " from " + existingData.mOwnerUserId 137 + " to " + ownerUserId); 138 } 139 // Merge the information into the existing data. 140 // Returns true if there was an update. 141 return existingData.merge(newData); 142 } 143 } 144 } 145 } 146 } 147 148 /** 149 * Convenience method for sync reads which does not force the user to pass a useless 150 * (Void) null. 151 */ 152 public void read() { 153 read((Void) null); 154 } 155 156 /** 157 * Convenience method for async writes which does not force the user to pass a useless 158 * (Void) null. 159 */ 160 public void maybeWriteAsync() { 161 maybeWriteAsync((Void) null); 162 } 163 164 @Override 165 protected void writeInternal(Void data) { 166 AtomicFile file = getFile(); 167 FileOutputStream f = null; 168 169 try { 170 f = file.startWrite(); 171 OutputStreamWriter osw = new OutputStreamWriter(f); 172 write(osw); 173 osw.flush(); 174 file.finishWrite(f); 175 } catch (IOException e) { 176 if (f != null) { 177 file.failWrite(f); 178 } 179 Slog.e(TAG, "Failed to write usage for dex files", e); 180 } 181 } 182 183 /** 184 * File format: 185 * 186 * file_magic_version 187 * package_name_1 188 * #dex_file_path_1_1 189 * user_1_1, used_by_other_app_1_1, user_isa_1_1_1, user_isa_1_1_2 190 * #dex_file_path_1_2 191 * user_1_2, used_by_other_app_1_2, user_isa_1_2_1, user_isa_1_2_2 192 * ... 193 * package_name_2 194 * #dex_file_path_2_1 195 * user_2_1, used_by_other_app_2_1, user_isa_2_1_1, user_isa_2_1_2 196 * #dex_file_path_2_2, 197 * user_2_2, used_by_other_app_2_2, user_isa_2_2_1, user_isa_2_2_2 198 * ... 199 */ 200 /* package */ void write(Writer out) { 201 // Make a clone to avoid locking while writing to disk. 202 Map<String, PackageUseInfo> packageUseInfoMapClone = clonePackageUseInfoMap(); 203 204 FastPrintWriter fpw = new FastPrintWriter(out); 205 206 // Write the header. 207 fpw.print(PACKAGE_DEX_USAGE_VERSION_HEADER); 208 fpw.println(PACKAGE_DEX_USAGE_VERSION); 209 210 for (Map.Entry<String, PackageUseInfo> pEntry : packageUseInfoMapClone.entrySet()) { 211 // Write the package line. 212 String packageName = pEntry.getKey(); 213 PackageUseInfo packageUseInfo = pEntry.getValue(); 214 215 fpw.println(String.join(SPLIT_CHAR, packageName, 216 writeBoolean(packageUseInfo.mIsUsedByOtherApps))); 217 218 // Write dex file lines. 219 for (Map.Entry<String, DexUseInfo> dEntry : packageUseInfo.mDexUseInfoMap.entrySet()) { 220 String dexPath = dEntry.getKey(); 221 DexUseInfo dexUseInfo = dEntry.getValue(); 222 fpw.println(DEX_LINE_CHAR + dexPath); 223 fpw.print(String.join(SPLIT_CHAR, Integer.toString(dexUseInfo.mOwnerUserId), 224 writeBoolean(dexUseInfo.mIsUsedByOtherApps))); 225 for (String isa : dexUseInfo.mLoaderIsas) { 226 fpw.print(SPLIT_CHAR + isa); 227 } 228 fpw.println(); 229 } 230 } 231 fpw.flush(); 232 } 233 234 @Override 235 protected void readInternal(Void data) { 236 AtomicFile file = getFile(); 237 BufferedReader in = null; 238 try { 239 in = new BufferedReader(new InputStreamReader(file.openRead())); 240 read(in); 241 } catch (FileNotFoundException expected) { 242 // The file may not be there. E.g. When we first take the OTA with this feature. 243 } catch (IOException e) { 244 Slog.w(TAG, "Failed to parse package dex usage.", e); 245 } finally { 246 IoUtils.closeQuietly(in); 247 } 248 } 249 250 /* package */ void read(Reader reader) throws IOException { 251 Map<String, PackageUseInfo> data = new HashMap<>(); 252 BufferedReader in = new BufferedReader(reader); 253 // Read header, do version check. 254 String versionLine = in.readLine(); 255 if (versionLine == null) { 256 throw new IllegalStateException("No version line found."); 257 } else { 258 if (!versionLine.startsWith(PACKAGE_DEX_USAGE_VERSION_HEADER)) { 259 // TODO(calin): the caller is responsible to clear the file. 260 throw new IllegalStateException("Invalid version line: " + versionLine); 261 } 262 int version = Integer.parseInt( 263 versionLine.substring(PACKAGE_DEX_USAGE_VERSION_HEADER.length())); 264 if (version != PACKAGE_DEX_USAGE_VERSION) { 265 throw new IllegalStateException("Unexpected version: " + version); 266 } 267 } 268 269 String s = null; 270 String currentPakage = null; 271 PackageUseInfo currentPakageData = null; 272 273 Set<String> supportedIsas = new HashSet<>(); 274 for (String abi : Build.SUPPORTED_ABIS) { 275 supportedIsas.add(VMRuntime.getInstructionSet(abi)); 276 } 277 while ((s = in.readLine()) != null) { 278 if (s.startsWith(DEX_LINE_CHAR)) { 279 // This is the start of the the dex lines. 280 // We expect two lines for each dex entry: 281 // #dexPaths 282 // onwerUserId,isUsedByOtherApps,isa1,isa2 283 if (currentPakage == null) { 284 throw new IllegalStateException( 285 "Malformed PackageDexUsage file. Expected package line before dex line."); 286 } 287 288 // First line is the dex path. 289 String dexPath = s.substring(DEX_LINE_CHAR.length()); 290 // Next line is the dex data. 291 s = in.readLine(); 292 if (s == null) { 293 throw new IllegalStateException("Could not fine dexUseInfo for line: " + s); 294 } 295 296 // We expect at least 3 elements (isUsedByOtherApps, userId, isa). 297 String[] elems = s.split(SPLIT_CHAR); 298 if (elems.length < 3) { 299 throw new IllegalStateException("Invalid PackageDexUsage line: " + s); 300 } 301 int ownerUserId = Integer.parseInt(elems[0]); 302 boolean isUsedByOtherApps = readBoolean(elems[1]); 303 DexUseInfo dexUseInfo = new DexUseInfo(isUsedByOtherApps, ownerUserId); 304 for (int i = 2; i < elems.length; i++) { 305 String isa = elems[i]; 306 if (supportedIsas.contains(isa)) { 307 dexUseInfo.mLoaderIsas.add(elems[i]); 308 } else { 309 // Should never happen unless someone crafts the file manually. 310 // In theory it could if we drop a supported ISA after an OTA but we don't 311 // do that. 312 Slog.wtf(TAG, "Unsupported ISA when parsing PackageDexUsage: " + isa); 313 } 314 } 315 if (supportedIsas.isEmpty()) { 316 Slog.wtf(TAG, "Ignore dexPath when parsing PackageDexUsage because of " + 317 "unsupported isas. dexPath=" + dexPath); 318 continue; 319 } 320 currentPakageData.mDexUseInfoMap.put(dexPath, dexUseInfo); 321 } else { 322 // This is a package line. 323 // We expect it to be: `packageName,isUsedByOtherApps`. 324 String[] elems = s.split(SPLIT_CHAR); 325 if (elems.length != 2) { 326 throw new IllegalStateException("Invalid PackageDexUsage line: " + s); 327 } 328 currentPakage = elems[0]; 329 currentPakageData = new PackageUseInfo(); 330 currentPakageData.mIsUsedByOtherApps = readBoolean(elems[1]); 331 data.put(currentPakage, currentPakageData); 332 } 333 } 334 335 synchronized (mPackageUseInfoMap) { 336 mPackageUseInfoMap.clear(); 337 mPackageUseInfoMap.putAll(data); 338 } 339 } 340 341 /** 342 * Syncs the existing data with the set of available packages by removing obsolete entries. 343 */ 344 public void syncData(Map<String, Set<Integer>> packageToUsersMap) { 345 synchronized (mPackageUseInfoMap) { 346 Iterator<Map.Entry<String, PackageUseInfo>> pIt = 347 mPackageUseInfoMap.entrySet().iterator(); 348 while (pIt.hasNext()) { 349 Map.Entry<String, PackageUseInfo> pEntry = pIt.next(); 350 String packageName = pEntry.getKey(); 351 PackageUseInfo packageUseInfo = pEntry.getValue(); 352 Set<Integer> users = packageToUsersMap.get(packageName); 353 if (users == null) { 354 // The package doesn't exist anymore, remove the record. 355 pIt.remove(); 356 } else { 357 // The package exists but we can prune the entries associated with non existing 358 // users. 359 Iterator<Map.Entry<String, DexUseInfo>> dIt = 360 packageUseInfo.mDexUseInfoMap.entrySet().iterator(); 361 while (dIt.hasNext()) { 362 DexUseInfo dexUseInfo = dIt.next().getValue(); 363 if (!users.contains(dexUseInfo.mOwnerUserId)) { 364 // User was probably removed. Delete its dex usage info. 365 dIt.remove(); 366 } 367 } 368 if (!packageUseInfo.mIsUsedByOtherApps 369 && packageUseInfo.mDexUseInfoMap.isEmpty()) { 370 // The package is not used by other apps and we removed all its dex files 371 // records. Remove the entire package record as well. 372 pIt.remove(); 373 } 374 } 375 } 376 } 377 } 378 379 /** 380 * Remove all the records about package {@code packageName} belonging to user {@code userId}. 381 */ 382 public boolean removeUserPackage(String packageName, int userId) { 383 synchronized (mPackageUseInfoMap) { 384 PackageUseInfo packageUseInfo = mPackageUseInfoMap.get(packageName); 385 if (packageUseInfo == null) { 386 return false; 387 } 388 boolean updated = false; 389 Iterator<Map.Entry<String, DexUseInfo>> dIt = 390 packageUseInfo.mDexUseInfoMap.entrySet().iterator(); 391 while (dIt.hasNext()) { 392 DexUseInfo dexUseInfo = dIt.next().getValue(); 393 if (dexUseInfo.mOwnerUserId == userId) { 394 dIt.remove(); 395 updated = true; 396 } 397 } 398 return updated; 399 } 400 } 401 402 /** 403 * Remove the secondary dex file record belonging to the package {@code packageName} 404 * and user {@code userId}. 405 */ 406 public boolean removeDexFile(String packageName, String dexFile, int userId) { 407 synchronized (mPackageUseInfoMap) { 408 PackageUseInfo packageUseInfo = mPackageUseInfoMap.get(packageName); 409 if (packageUseInfo == null) { 410 return false; 411 } 412 return removeDexFile(packageUseInfo, dexFile, userId); 413 } 414 } 415 416 private boolean removeDexFile(PackageUseInfo packageUseInfo, String dexFile, int userId) { 417 DexUseInfo dexUseInfo = packageUseInfo.mDexUseInfoMap.get(dexFile); 418 if (dexUseInfo == null) { 419 return false; 420 } 421 if (dexUseInfo.mOwnerUserId == userId) { 422 packageUseInfo.mDexUseInfoMap.remove(dexFile); 423 return true; 424 } 425 return false; 426 } 427 428 public PackageUseInfo getPackageUseInfo(String packageName) { 429 synchronized (mPackageUseInfoMap) { 430 PackageUseInfo useInfo = mPackageUseInfoMap.get(packageName); 431 // The useInfo contains a map for secondary dex files which could be modified 432 // concurrently after this method returns and thus outside the locking we do here. 433 // (i.e. the map is updated when new class loaders are created, which can happen anytime 434 // after this method returns) 435 // Make a defensive copy to be sure we don't get concurrent modifications. 436 return useInfo == null ? null : new PackageUseInfo(useInfo); 437 } 438 } 439 440 /** 441 * Return all packages that contain records of secondary dex files. 442 */ 443 public Set<String> getAllPackagesWithSecondaryDexFiles() { 444 Set<String> packages = new HashSet<>(); 445 synchronized (mPackageUseInfoMap) { 446 for (Map.Entry<String, PackageUseInfo> entry : mPackageUseInfoMap.entrySet()) { 447 if (!entry.getValue().mDexUseInfoMap.isEmpty()) { 448 packages.add(entry.getKey()); 449 } 450 } 451 } 452 return packages; 453 } 454 455 public void clear() { 456 synchronized (mPackageUseInfoMap) { 457 mPackageUseInfoMap.clear(); 458 } 459 } 460 // Creates a deep copy of the class' mPackageUseInfoMap. 461 private Map<String, PackageUseInfo> clonePackageUseInfoMap() { 462 Map<String, PackageUseInfo> clone = new HashMap<>(); 463 synchronized (mPackageUseInfoMap) { 464 for (Map.Entry<String, PackageUseInfo> e : mPackageUseInfoMap.entrySet()) { 465 clone.put(e.getKey(), new PackageUseInfo(e.getValue())); 466 } 467 } 468 return clone; 469 } 470 471 private String writeBoolean(boolean bool) { 472 return bool ? "1" : "0"; 473 } 474 475 private boolean readBoolean(String bool) { 476 if ("0".equals(bool)) return false; 477 if ("1".equals(bool)) return true; 478 throw new IllegalArgumentException("Unknown bool encoding: " + bool); 479 } 480 481 private boolean contains(int[] array, int elem) { 482 for (int i = 0; i < array.length; i++) { 483 if (elem == array[i]) { 484 return true; 485 } 486 } 487 return false; 488 } 489 490 public String dump() { 491 StringWriter sw = new StringWriter(); 492 write(sw); 493 return sw.toString(); 494 } 495 496 /** 497 * Stores data on how a package and its dex files are used. 498 */ 499 public static class PackageUseInfo { 500 // This flag is for the primary and split apks. It is set to true whenever one of them 501 // is loaded by another app. 502 private boolean mIsUsedByOtherApps; 503 // Map dex paths to their data (isUsedByOtherApps, owner id, loader isa). 504 private final Map<String, DexUseInfo> mDexUseInfoMap; 505 506 public PackageUseInfo() { 507 mIsUsedByOtherApps = false; 508 mDexUseInfoMap = new HashMap<>(); 509 } 510 511 // Creates a deep copy of the `other`. 512 public PackageUseInfo(PackageUseInfo other) { 513 mIsUsedByOtherApps = other.mIsUsedByOtherApps; 514 mDexUseInfoMap = new HashMap<>(); 515 for (Map.Entry<String, DexUseInfo> e : other.mDexUseInfoMap.entrySet()) { 516 mDexUseInfoMap.put(e.getKey(), new DexUseInfo(e.getValue())); 517 } 518 } 519 520 private boolean merge(boolean isUsedByOtherApps) { 521 boolean oldIsUsedByOtherApps = mIsUsedByOtherApps; 522 mIsUsedByOtherApps = mIsUsedByOtherApps || isUsedByOtherApps; 523 return oldIsUsedByOtherApps != this.mIsUsedByOtherApps; 524 } 525 526 public boolean isUsedByOtherApps() { 527 return mIsUsedByOtherApps; 528 } 529 530 public Map<String, DexUseInfo> getDexUseInfoMap() { 531 return mDexUseInfoMap; 532 } 533 } 534 535 /** 536 * Stores data about a loaded dex files. 537 */ 538 public static class DexUseInfo { 539 private boolean mIsUsedByOtherApps; 540 private final int mOwnerUserId; 541 private final Set<String> mLoaderIsas; 542 543 public DexUseInfo(boolean isUsedByOtherApps, int ownerUserId) { 544 this(isUsedByOtherApps, ownerUserId, null); 545 } 546 547 public DexUseInfo(boolean isUsedByOtherApps, int ownerUserId, String loaderIsa) { 548 mIsUsedByOtherApps = isUsedByOtherApps; 549 mOwnerUserId = ownerUserId; 550 mLoaderIsas = new HashSet<>(); 551 if (loaderIsa != null) { 552 mLoaderIsas.add(loaderIsa); 553 } 554 } 555 556 // Creates a deep copy of the `other`. 557 public DexUseInfo(DexUseInfo other) { 558 mIsUsedByOtherApps = other.mIsUsedByOtherApps; 559 mOwnerUserId = other.mOwnerUserId; 560 mLoaderIsas = new HashSet<>(other.mLoaderIsas); 561 } 562 563 private boolean merge(DexUseInfo dexUseInfo) { 564 boolean oldIsUsedByOtherApps = mIsUsedByOtherApps; 565 mIsUsedByOtherApps = mIsUsedByOtherApps || dexUseInfo.mIsUsedByOtherApps; 566 boolean updateIsas = mLoaderIsas.addAll(dexUseInfo.mLoaderIsas); 567 return updateIsas || (oldIsUsedByOtherApps != mIsUsedByOtherApps); 568 } 569 570 public boolean isUsedByOtherApps() { 571 return mIsUsedByOtherApps; 572 } 573 574 public int getOwnerUserId() { 575 return mOwnerUserId; 576 } 577 578 public Set<String> getLoaderIsas() { 579 return mLoaderIsas; 580 } 581 } 582} 583