1 /* 2 * Copyright (C) 2024 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.server.art; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.os.Build; 22 23 import androidx.annotation.RequiresApi; 24 25 import com.android.internal.annotations.VisibleForTesting; 26 import com.android.server.art.model.DexMetadata; 27 import com.android.server.art.proto.DexMetadataConfig; 28 29 import java.io.FileNotFoundException; 30 import java.io.IOException; 31 import java.io.InputStream; 32 import java.nio.file.NoSuchFileException; 33 import java.util.zip.ZipEntry; 34 import java.util.zip.ZipFile; 35 36 /** 37 * A helper class to handle dex metadata (dm) files. 38 * 39 * Note that this is not the only consumer of dm files. A dm file is a container that contains 40 * various types of files for various purposes, passed down to various layers and consumed by them. 41 * 42 * @hide 43 */ 44 @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) 45 public class DexMetadataHelper { 46 @NonNull private final Injector mInjector; 47 DexMetadataHelper()48 public DexMetadataHelper() { 49 this(new Injector()); 50 } 51 52 @VisibleForTesting DexMetadataHelper(@onNull Injector injector)53 public DexMetadataHelper(@NonNull Injector injector) { 54 mInjector = injector; 55 } 56 57 @NonNull getDexMetadataInfo(@ullable DexMetadataPath dmPath)58 public DexMetadataInfo getDexMetadataInfo(@Nullable DexMetadataPath dmPath) { 59 if (dmPath == null) { 60 return getDefaultDexMetadataInfo(DexMetadata.TYPE_NONE); 61 } 62 63 String realDmPath = getDmPath(dmPath); 64 try (var zipFile = mInjector.openZipFile(realDmPath)) { 65 ZipEntry entry = zipFile.getEntry("config.pb"); 66 if (entry == null) { 67 return new DexMetadataInfo( 68 dmPath, DexMetadataConfig.getDefaultInstance(), getType(zipFile)); 69 } 70 try (InputStream stream = zipFile.getInputStream(entry)) { 71 return new DexMetadataInfo( 72 dmPath, DexMetadataConfig.parseFrom(stream), getType(zipFile)); 73 } 74 } catch (IOException e) { 75 if (e instanceof FileNotFoundException || e instanceof NoSuchFileException) { 76 return getDefaultDexMetadataInfo(DexMetadata.TYPE_NONE); 77 } else { 78 AsLog.e(String.format("Failed to read dm file '%s'", realDmPath), e); 79 return getDefaultDexMetadataInfo(DexMetadata.TYPE_ERROR); 80 } 81 } 82 } 83 84 @NonNull getDefaultDexMetadataInfo(@exMetadata.Type int type)85 private DexMetadataInfo getDefaultDexMetadataInfo(@DexMetadata.Type int type) { 86 return new DexMetadataInfo(null /* dmPath */, DexMetadataConfig.getDefaultInstance(), type); 87 } 88 89 @NonNull getDmPath(@onNull DexMetadataPath dmPath)90 public static String getDmPath(@NonNull DexMetadataPath dmPath) { 91 return Utils.replaceFileExtension(dmPath.dexPath, ArtConstants.DEX_METADATA_FILE_EXT); 92 } 93 getType(@onNull ZipFile zipFile)94 private static @DexMetadata.Type int getType(@NonNull ZipFile zipFile) { 95 var profile = zipFile.getEntry(ArtConstants.DEX_METADATA_PROFILE_ENTRY); 96 var vdex = zipFile.getEntry(ArtConstants.DEX_METADATA_VDEX_ENTRY); 97 98 if (profile != null && vdex != null) { 99 return DexMetadata.TYPE_PROFILE_AND_VDEX; 100 } else if (profile != null) { 101 return DexMetadata.TYPE_PROFILE; 102 } else if (vdex != null) { 103 return DexMetadata.TYPE_VDEX; 104 } else { 105 return DexMetadata.TYPE_NONE; 106 } 107 } 108 109 /** 110 * @param dmPath Represents the path to the dm file, if it exists. Or null if the file doesn't 111 * exist or an error occurred. 112 * @param config The config deserialized from `config.pb`, if it exists. Or the default instance 113 * if the file doesn't exist or an error occurred. 114 * @param type An enum value representing whether the dm file contains a profile, a VDEX file, 115 * none, or both. 116 */ DexMetadataInfo(@ullable DexMetadataPath dmPath, @NonNull DexMetadataConfig config, @DexMetadata.Type int type)117 public record DexMetadataInfo(@Nullable DexMetadataPath dmPath, 118 @NonNull DexMetadataConfig config, @DexMetadata.Type int type) {} 119 120 /** 121 * Injector pattern for testing purpose. 122 * 123 * @hide 124 */ 125 @VisibleForTesting 126 public static class Injector { 127 @NonNull openZipFile(@onNull String path)128 ZipFile openZipFile(@NonNull String path) throws IOException { 129 return new ZipFile(path); 130 } 131 } 132 } 133