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