blob: 10384a2749e8cfb550063808ee9c7c2c3f19b580 [file] [log] [blame]
Calin Juravle6ea5ace2016-12-01 17:53:07 +00001/*
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 public PackageUseInfo getPackageUseInfo(String packageName) {
380 synchronized (mPackageUseInfoMap) {
381 return mPackageUseInfoMap.get(packageName);
382 }
383 }
384
385 public void clear() {
386 synchronized (mPackageUseInfoMap) {
387 mPackageUseInfoMap.clear();
388 }
389 }
390 // Creates a deep copy of the class' mPackageUseInfoMap.
391 private Map<String, PackageUseInfo> clonePackageUseInfoMap() {
392 Map<String, PackageUseInfo> clone = new HashMap<>();
393 synchronized (mPackageUseInfoMap) {
394 for (Map.Entry<String, PackageUseInfo> e : mPackageUseInfoMap.entrySet()) {
395 clone.put(e.getKey(), new PackageUseInfo(e.getValue()));
396 }
397 }
398 return clone;
399 }
400
401 private String writeBoolean(boolean bool) {
402 return bool ? "1" : "0";
403 }
404
405 private boolean readBoolean(String bool) {
406 if ("0".equals(bool)) return false;
407 if ("1".equals(bool)) return true;
408 throw new IllegalArgumentException("Unknown bool encoding: " + bool);
409 }
410
411 private boolean contains(int[] array, int elem) {
412 for (int i = 0; i < array.length; i++) {
413 if (elem == array[i]) {
414 return true;
415 }
416 }
417 return false;
418 }
419
420 public String dump() {
421 StringWriter sw = new StringWriter();
422 write(sw);
423 return sw.toString();
424 }
425
426 /**
427 * Stores data on how a package and its dex files are used.
428 */
429 public static class PackageUseInfo {
430 // This flag is for the primary and split apks. It is set to true whenever one of them
431 // is loaded by another app.
432 private boolean mIsUsedByOtherApps;
433 // Map dex paths to their data (isUsedByOtherApps, owner id, loader isa).
434 private final Map<String, DexUseInfo> mDexUseInfoMap;
435
436 public PackageUseInfo() {
437 mIsUsedByOtherApps = false;
438 mDexUseInfoMap = new HashMap<>();
439 }
440
441 // Creates a deep copy of the `other`.
442 public PackageUseInfo(PackageUseInfo other) {
443 mIsUsedByOtherApps = other.mIsUsedByOtherApps;
444 mDexUseInfoMap = new HashMap<>();
445 for (Map.Entry<String, DexUseInfo> e : other.mDexUseInfoMap.entrySet()) {
446 mDexUseInfoMap.put(e.getKey(), new DexUseInfo(e.getValue()));
447 }
448 }
449
450 private boolean merge(boolean isUsedByOtherApps) {
451 boolean oldIsUsedByOtherApps = mIsUsedByOtherApps;
452 mIsUsedByOtherApps = mIsUsedByOtherApps || isUsedByOtherApps;
453 return oldIsUsedByOtherApps != this.mIsUsedByOtherApps;
454 }
455
456 public boolean isUsedByOtherApps() {
457 return mIsUsedByOtherApps;
458 }
459
460 public Map<String, DexUseInfo> getDexUseInfoMap() {
461 return mDexUseInfoMap;
462 }
463 }
464
465 /**
466 * Stores data about a loaded dex files.
467 */
468 public static class DexUseInfo {
469 private boolean mIsUsedByOtherApps;
470 private final int mOwnerUserId;
471 private final Set<String> mLoaderIsas;
472
473 public DexUseInfo(boolean isUsedByOtherApps, int ownerUserId) {
474 this(isUsedByOtherApps, ownerUserId, null);
475 }
476
477 public DexUseInfo(boolean isUsedByOtherApps, int ownerUserId, String loaderIsa) {
478 mIsUsedByOtherApps = isUsedByOtherApps;
479 mOwnerUserId = ownerUserId;
480 mLoaderIsas = new HashSet<>();
481 if (loaderIsa != null) {
482 mLoaderIsas.add(loaderIsa);
483 }
484 }
485
486 // Creates a deep copy of the `other`.
487 public DexUseInfo(DexUseInfo other) {
488 mIsUsedByOtherApps = other.mIsUsedByOtherApps;
489 mOwnerUserId = other.mOwnerUserId;
490 mLoaderIsas = new HashSet<>(other.mLoaderIsas);
491 }
492
493 private boolean merge(DexUseInfo dexUseInfo) {
494 boolean oldIsUsedByOtherApps = mIsUsedByOtherApps;
495 mIsUsedByOtherApps = mIsUsedByOtherApps || dexUseInfo.mIsUsedByOtherApps;
496 boolean updateIsas = mLoaderIsas.addAll(dexUseInfo.mLoaderIsas);
497 return updateIsas || (oldIsUsedByOtherApps != mIsUsedByOtherApps);
498 }
499
500 public boolean isUsedByOtherApps() {
501 return mIsUsedByOtherApps;
502 }
503
504 public int getOwnerUserId() {
505 return mOwnerUserId;
506 }
507
508 public Set<String> getLoaderIsas() {
509 return mLoaderIsas;
510 }
511 }
512}