PackageDexUsage.java revision 61fd6eab463d5b86ef177537c149ee45a0a40dcc
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 * @return true if the record was found and actually deleted, 382 * false if the record doesn't exist 383 */ 384 public boolean removeUserPackage(String packageName, int userId) { 385 synchronized (mPackageUseInfoMap) { 386 PackageUseInfo packageUseInfo = mPackageUseInfoMap.get(packageName); 387 if (packageUseInfo == null) { 388 return false; 389 } 390 boolean updated = false; 391 Iterator<Map.Entry<String, DexUseInfo>> dIt = 392 packageUseInfo.mDexUseInfoMap.entrySet().iterator(); 393 while (dIt.hasNext()) { 394 DexUseInfo dexUseInfo = dIt.next().getValue(); 395 if (dexUseInfo.mOwnerUserId == userId) { 396 dIt.remove(); 397 updated = true; 398 } 399 } 400 return updated; 401 } 402 } 403 404 /** 405 * Remove the secondary dex file record belonging to the package {@code packageName} 406 * and user {@code userId}. 407 * @return true if the record was found and actually deleted, 408 * false if the record doesn't exist 409 */ 410 public boolean removeDexFile(String packageName, String dexFile, int userId) { 411 synchronized (mPackageUseInfoMap) { 412 PackageUseInfo packageUseInfo = mPackageUseInfoMap.get(packageName); 413 if (packageUseInfo == null) { 414 return false; 415 } 416 return removeDexFile(packageUseInfo, dexFile, userId); 417 } 418 } 419 420 private boolean removeDexFile(PackageUseInfo packageUseInfo, String dexFile, int userId) { 421 DexUseInfo dexUseInfo = packageUseInfo.mDexUseInfoMap.get(dexFile); 422 if (dexUseInfo == null) { 423 return false; 424 } 425 if (dexUseInfo.mOwnerUserId == userId) { 426 packageUseInfo.mDexUseInfoMap.remove(dexFile); 427 return true; 428 } 429 return false; 430 } 431 432 public PackageUseInfo getPackageUseInfo(String packageName) { 433 synchronized (mPackageUseInfoMap) { 434 PackageUseInfo useInfo = mPackageUseInfoMap.get(packageName); 435 // The useInfo contains a map for secondary dex files which could be modified 436 // concurrently after this method returns and thus outside the locking we do here. 437 // (i.e. the map is updated when new class loaders are created, which can happen anytime 438 // after this method returns) 439 // Make a defensive copy to be sure we don't get concurrent modifications. 440 return useInfo == null ? null : new PackageUseInfo(useInfo); 441 } 442 } 443 444 /** 445 * Return all packages that contain records of secondary dex files. 446 */ 447 public Set<String> getAllPackagesWithSecondaryDexFiles() { 448 Set<String> packages = new HashSet<>(); 449 synchronized (mPackageUseInfoMap) { 450 for (Map.Entry<String, PackageUseInfo> entry : mPackageUseInfoMap.entrySet()) { 451 if (!entry.getValue().mDexUseInfoMap.isEmpty()) { 452 packages.add(entry.getKey()); 453 } 454 } 455 } 456 return packages; 457 } 458 459 public void clear() { 460 synchronized (mPackageUseInfoMap) { 461 mPackageUseInfoMap.clear(); 462 } 463 } 464 // Creates a deep copy of the class' mPackageUseInfoMap. 465 private Map<String, PackageUseInfo> clonePackageUseInfoMap() { 466 Map<String, PackageUseInfo> clone = new HashMap<>(); 467 synchronized (mPackageUseInfoMap) { 468 for (Map.Entry<String, PackageUseInfo> e : mPackageUseInfoMap.entrySet()) { 469 clone.put(e.getKey(), new PackageUseInfo(e.getValue())); 470 } 471 } 472 return clone; 473 } 474 475 private String writeBoolean(boolean bool) { 476 return bool ? "1" : "0"; 477 } 478 479 private boolean readBoolean(String bool) { 480 if ("0".equals(bool)) return false; 481 if ("1".equals(bool)) return true; 482 throw new IllegalArgumentException("Unknown bool encoding: " + bool); 483 } 484 485 private boolean contains(int[] array, int elem) { 486 for (int i = 0; i < array.length; i++) { 487 if (elem == array[i]) { 488 return true; 489 } 490 } 491 return false; 492 } 493 494 public String dump() { 495 StringWriter sw = new StringWriter(); 496 write(sw); 497 return sw.toString(); 498 } 499 500 /** 501 * Stores data on how a package and its dex files are used. 502 */ 503 public static class PackageUseInfo { 504 // This flag is for the primary and split apks. It is set to true whenever one of them 505 // is loaded by another app. 506 private boolean mIsUsedByOtherApps; 507 // Map dex paths to their data (isUsedByOtherApps, owner id, loader isa). 508 private final Map<String, DexUseInfo> mDexUseInfoMap; 509 510 public PackageUseInfo() { 511 mIsUsedByOtherApps = false; 512 mDexUseInfoMap = new HashMap<>(); 513 } 514 515 // Creates a deep copy of the `other`. 516 public PackageUseInfo(PackageUseInfo other) { 517 mIsUsedByOtherApps = other.mIsUsedByOtherApps; 518 mDexUseInfoMap = new HashMap<>(); 519 for (Map.Entry<String, DexUseInfo> e : other.mDexUseInfoMap.entrySet()) { 520 mDexUseInfoMap.put(e.getKey(), new DexUseInfo(e.getValue())); 521 } 522 } 523 524 private boolean merge(boolean isUsedByOtherApps) { 525 boolean oldIsUsedByOtherApps = mIsUsedByOtherApps; 526 mIsUsedByOtherApps = mIsUsedByOtherApps || isUsedByOtherApps; 527 return oldIsUsedByOtherApps != this.mIsUsedByOtherApps; 528 } 529 530 public boolean isUsedByOtherApps() { 531 return mIsUsedByOtherApps; 532 } 533 534 public Map<String, DexUseInfo> getDexUseInfoMap() { 535 return mDexUseInfoMap; 536 } 537 } 538 539 /** 540 * Stores data about a loaded dex files. 541 */ 542 public static class DexUseInfo { 543 private boolean mIsUsedByOtherApps; 544 private final int mOwnerUserId; 545 private final Set<String> mLoaderIsas; 546 547 public DexUseInfo(boolean isUsedByOtherApps, int ownerUserId) { 548 this(isUsedByOtherApps, ownerUserId, null); 549 } 550 551 public DexUseInfo(boolean isUsedByOtherApps, int ownerUserId, String loaderIsa) { 552 mIsUsedByOtherApps = isUsedByOtherApps; 553 mOwnerUserId = ownerUserId; 554 mLoaderIsas = new HashSet<>(); 555 if (loaderIsa != null) { 556 mLoaderIsas.add(loaderIsa); 557 } 558 } 559 560 // Creates a deep copy of the `other`. 561 public DexUseInfo(DexUseInfo other) { 562 mIsUsedByOtherApps = other.mIsUsedByOtherApps; 563 mOwnerUserId = other.mOwnerUserId; 564 mLoaderIsas = new HashSet<>(other.mLoaderIsas); 565 } 566 567 private boolean merge(DexUseInfo dexUseInfo) { 568 boolean oldIsUsedByOtherApps = mIsUsedByOtherApps; 569 mIsUsedByOtherApps = mIsUsedByOtherApps || dexUseInfo.mIsUsedByOtherApps; 570 boolean updateIsas = mLoaderIsas.addAll(dexUseInfo.mLoaderIsas); 571 return updateIsas || (oldIsUsedByOtherApps != mIsUsedByOtherApps); 572 } 573 574 public boolean isUsedByOtherApps() { 575 return mIsUsedByOtherApps; 576 } 577 578 public int getOwnerUserId() { 579 return mOwnerUserId; 580 } 581 582 public Set<String> getLoaderIsas() { 583 return mLoaderIsas; 584 } 585 } 586} 587