1 /*
2  * Copyright (C) 2020 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 android.content.pm;
18 
19 import android.annotation.IntDef;
20 import android.annotation.NonNull;
21 import android.annotation.Nullable;
22 import android.os.Build;
23 import android.os.Build.Partition;
24 import android.os.Environment;
25 import android.os.FileUtils;
26 import android.os.SystemProperties;
27 
28 import com.android.internal.annotations.VisibleForTesting;
29 
30 import java.io.File;
31 import java.io.IOException;
32 import java.lang.annotation.Retention;
33 import java.lang.annotation.RetentionPolicy;
34 import java.util.ArrayList;
35 import java.util.Arrays;
36 import java.util.function.Function;
37 
38 /**
39  * Exposes {@link #SYSTEM_PARTITIONS} which represents the partitions in which application packages
40  * can be installed. The partitions are ordered from most generic (lowest priority) to most specific
41  * (greatest priority).
42  *
43  * @hide
44  **/
45 public class PackagePartitions {
46     public static final int PARTITION_SYSTEM = 0;
47     public static final int PARTITION_VENDOR = 1;
48     public static final int PARTITION_ODM = 2;
49     public static final int PARTITION_OEM = 3;
50     public static final int PARTITION_PRODUCT = 4;
51     public static final int PARTITION_SYSTEM_EXT = 5;
52 
53     @IntDef(prefix = { "PARTITION_" }, value = {
54         PARTITION_SYSTEM,
55         PARTITION_VENDOR,
56         PARTITION_ODM,
57         PARTITION_OEM,
58         PARTITION_PRODUCT,
59         PARTITION_SYSTEM_EXT
60     })
61     @Retention(RetentionPolicy.SOURCE)
62     public @interface PartitionType {}
63 
64     /**
65      * The list of all system partitions that may contain packages in ascending order of
66      * specificity (the more generic, the earlier in the list a partition appears).
67      */
68     private static final ArrayList<SystemPartition> SYSTEM_PARTITIONS =
69             new ArrayList<>(Arrays.asList(
70                     new SystemPartition(Environment.getRootDirectory(),
71                             PARTITION_SYSTEM, Partition.PARTITION_NAME_SYSTEM,
72                             true /* containsPrivApp */, false /* containsOverlay */),
73                     new SystemPartition(Environment.getVendorDirectory(),
74                             PARTITION_VENDOR, Partition.PARTITION_NAME_VENDOR,
75                             true /* containsPrivApp */, true /* containsOverlay */),
76                     new SystemPartition(Environment.getOdmDirectory(),
77                             PARTITION_ODM, Partition.PARTITION_NAME_ODM,
78                             true /* containsPrivApp */, true /* containsOverlay */),
79                     new SystemPartition(Environment.getOemDirectory(),
80                             PARTITION_OEM, Partition.PARTITION_NAME_OEM,
81                             false /* containsPrivApp */, true /* containsOverlay */),
82                     new SystemPartition(Environment.getProductDirectory(),
83                             PARTITION_PRODUCT, Partition.PARTITION_NAME_PRODUCT,
84                             true /* containsPrivApp */, true /* containsOverlay */),
85                     new SystemPartition(Environment.getSystemExtDirectory(),
86                             PARTITION_SYSTEM_EXT, Partition.PARTITION_NAME_SYSTEM_EXT,
87                             true /* containsPrivApp */, true /* containsOverlay */)));
88 
89     /**
90      * A string to represent the fingerprint of this build and all package partitions. Using it to
91      * determine whether the system update has occurred. Different from {@link Build#FINGERPRINT},
92      * this string is digested from the fingerprints of the build and all package partitions to
93      * help detect the partition update.
94      */
95     public static final String FINGERPRINT = getFingerprint();
96 
97     /**
98      * Returns a list in which the elements are products of the specified function applied to the
99      * list of {@link #SYSTEM_PARTITIONS} in increasing specificity order.
100      */
getOrderedPartitions( @onNull Function<SystemPartition, T> producer)101     public static <T> ArrayList<T> getOrderedPartitions(
102             @NonNull Function<SystemPartition, T> producer) {
103         final ArrayList<T> out = new ArrayList<>();
104         for (int i = 0, n = SYSTEM_PARTITIONS.size(); i < n; i++) {
105             final T v = producer.apply(SYSTEM_PARTITIONS.get(i));
106             if (v != null)  {
107                 out.add(v);
108             }
109         }
110         return out;
111     }
112 
canonicalize(File path)113     private static File canonicalize(File path) {
114         try {
115             return path.getCanonicalFile();
116         } catch (IOException e) {
117             return path;
118         }
119     }
120 
121     /**
122      * Returns a fingerprint string for this build and all package partitions. The string is
123      * digested from the fingerprints of the build and all package partitions.
124      *
125      * @return A string to represent the fingerprint of this build and all package partitions.
126      */
127     @NonNull
getFingerprint()128     private static String getFingerprint() {
129         final String[] digestProperties = new String[SYSTEM_PARTITIONS.size() + 1];
130         for (int i = 0; i < SYSTEM_PARTITIONS.size(); i++) {
131             final String partitionName = SYSTEM_PARTITIONS.get(i).getName();
132             digestProperties[i] = "ro." + partitionName + ".build.fingerprint";
133         }
134         digestProperties[SYSTEM_PARTITIONS.size()] = "ro.build.fingerprint"; // build fingerprint
135         return SystemProperties.digestOf(digestProperties);
136     }
137 
138     /** Represents a partition that contains application packages. */
139     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
140     public static class SystemPartition {
141         @PartitionType
142         public final int type;
143 
144         @NonNull
145         private final String mName;
146 
147         @NonNull
148         private final DeferredCanonicalFile mFolder;
149 
150         @Nullable
151         private final DeferredCanonicalFile mAppFolder;
152 
153         @Nullable
154         private final DeferredCanonicalFile mPrivAppFolder;
155 
156         @Nullable
157         private final DeferredCanonicalFile mOverlayFolder;
158 
159         @NonNull
160         private final File mNonConicalFolder;
161 
SystemPartition(@onNull File folder, @PartitionType int type, String name, boolean containsPrivApp, boolean containsOverlay)162         private SystemPartition(@NonNull File folder, @PartitionType int type, String name,
163                 boolean containsPrivApp, boolean containsOverlay) {
164             this.type = type;
165             this.mName = name;
166             this.mFolder = new DeferredCanonicalFile(folder);
167             this.mAppFolder = new DeferredCanonicalFile(folder, "app");
168             this.mPrivAppFolder = containsPrivApp ? new DeferredCanonicalFile(folder, "priv-app")
169                     : null;
170             this.mOverlayFolder = containsOverlay ? new DeferredCanonicalFile(folder, "overlay")
171                     : null;
172             this.mNonConicalFolder = folder;
173         }
174 
SystemPartition(@onNull SystemPartition original)175         public SystemPartition(@NonNull SystemPartition original) {
176             this.type = original.type;
177             this.mName = original.mName;
178             this.mFolder = new DeferredCanonicalFile(original.mFolder.getFile());
179             this.mAppFolder = original.mAppFolder;
180             this.mPrivAppFolder = original.mPrivAppFolder;
181             this.mOverlayFolder = original.mOverlayFolder;
182             this.mNonConicalFolder = original.mNonConicalFolder;
183         }
184 
185         /**
186          * Creates a partition containing the same folders as the original partition but with a
187          * different root folder.
188          */
SystemPartition(@onNull File rootFolder, @NonNull SystemPartition partition)189         public SystemPartition(@NonNull File rootFolder, @NonNull SystemPartition partition) {
190             this(rootFolder, partition.type, partition.mName, partition.mPrivAppFolder != null,
191                     partition.mOverlayFolder != null);
192         }
193 
194         /**
195          * Returns the name identifying the partition.
196          * @see Partition
197          */
198         @NonNull
getName()199         public String getName() {
200             return mName;
201         }
202 
203         /** Returns the canonical folder of the partition. */
204         @NonNull
getFolder()205         public File getFolder() {
206             return mFolder.getFile();
207         }
208 
209         /** Returns the non-canonical folder of the partition. */
210         @NonNull
getNonConicalFolder()211         public File getNonConicalFolder() {
212             return mNonConicalFolder;
213         }
214 
215         /** Returns the canonical app folder of the partition. */
216         @Nullable
getAppFolder()217         public File getAppFolder() {
218             return mAppFolder == null ? null : mAppFolder.getFile();
219         }
220 
221         /** Returns the canonical priv-app folder of the partition, if one exists. */
222         @Nullable
getPrivAppFolder()223         public File getPrivAppFolder() {
224             return mPrivAppFolder == null ? null : mPrivAppFolder.getFile();
225         }
226 
227         /** Returns the canonical overlay folder of the partition, if one exists. */
228         @Nullable
getOverlayFolder()229         public File getOverlayFolder() {
230             return mOverlayFolder == null ? null : mOverlayFolder.getFile();
231         }
232 
233         /** Returns whether the partition contains the specified file. */
containsPath(@onNull String path)234         public boolean containsPath(@NonNull String path) {
235             return containsFile(new File(path));
236         }
237 
238         /** Returns whether the partition contains the specified file. */
containsFile(@onNull File file)239         public boolean containsFile(@NonNull File file) {
240             return FileUtils.contains(mFolder.getFile(), canonicalize(file));
241         }
242 
243         /** Returns whether the partition contains the specified file in its priv-app folder. */
containsPrivApp(@onNull File scanFile)244         public boolean containsPrivApp(@NonNull File scanFile) {
245             return mPrivAppFolder != null
246                     && FileUtils.contains(mPrivAppFolder.getFile(), canonicalize(scanFile));
247         }
248 
249         /** Returns whether the partition contains the specified file in its app folder. */
containsApp(@onNull File scanFile)250         public boolean containsApp(@NonNull File scanFile) {
251             return mAppFolder != null
252                     && FileUtils.contains(mAppFolder.getFile(), canonicalize(scanFile));
253         }
254 
255         /** Returns whether the partition contains the specified file in its overlay folder. */
containsOverlay(@onNull File scanFile)256         public boolean containsOverlay(@NonNull File scanFile) {
257             return mOverlayFolder != null
258                     && FileUtils.contains(mOverlayFolder.getFile(), canonicalize(scanFile));
259         }
260     }
261 
262     /**
263      * A class that defers the canonicalization of its underlying file. This must be done so
264      * processes do not attempt to canonicalize files in directories for which the process does not
265      * have the correct selinux policies.
266      */
267     private static class DeferredCanonicalFile {
268         private boolean mIsCanonical = false;
269 
270         @NonNull
271         private File mFile;
272 
DeferredCanonicalFile(@onNull File dir)273         private DeferredCanonicalFile(@NonNull File dir) {
274             mFile = dir;
275         }
276 
DeferredCanonicalFile(@onNull File dir, @NonNull String fileName)277         private DeferredCanonicalFile(@NonNull File dir, @NonNull String fileName) {
278             mFile = new File(dir, fileName);
279         }
280 
281         @NonNull
getFile()282         private File getFile() {
283             if (!mIsCanonical) {
284                 mFile = canonicalize(mFile);
285                 mIsCanonical = true;
286             }
287             return mFile;
288         }
289     }
290 }
291