blob: 916edfae679f32efba7352c03653991c58a7ad81 [file] [log] [blame]
Alan Viverette3da604b2020-06-10 18:34:39 +00001/*
2 * Copyright (C) 2019 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 android.os.incremental;
18
19import android.annotation.IntDef;
20import android.annotation.NonNull;
21import android.annotation.Nullable;
22import android.annotation.SystemService;
23import android.content.Context;
24import android.content.pm.DataLoaderParams;
25import android.content.pm.IDataLoaderStatusListener;
26import android.os.RemoteException;
27import android.util.SparseArray;
28
29import com.android.internal.annotations.GuardedBy;
30
31import java.io.File;
32import java.io.IOException;
33import java.lang.annotation.Retention;
34import java.lang.annotation.RetentionPolicy;
35import java.nio.file.FileVisitResult;
36import java.nio.file.Files;
37import java.nio.file.Path;
38import java.nio.file.Paths;
39import java.nio.file.SimpleFileVisitor;
40import java.nio.file.attribute.BasicFileAttributes;
41
42/**
43 * Provides operations to open or create an IncrementalStorage, using IIncrementalService
44 * service. Example Usage:
45 *
46 * <blockquote><pre>
47 * IncrementalManager manager = (IncrementalManager) getSystemService(Context.INCREMENTAL_SERVICE);
48 * IncrementalStorage storage = manager.openStorage("/path/to/incremental/dir");
49 * </pre></blockquote>
50 *
51 * @hide
52 */
53@SystemService(Context.INCREMENTAL_SERVICE)
54public final class IncrementalManager {
55 private static final String TAG = "IncrementalManager";
56
57 private static final String ALLOWED_PROPERTY = "incremental.allowed";
58
59 public static final int CREATE_MODE_TEMPORARY_BIND =
60 IIncrementalService.CREATE_MODE_TEMPORARY_BIND;
61 public static final int CREATE_MODE_PERMANENT_BIND =
62 IIncrementalService.CREATE_MODE_PERMANENT_BIND;
63 public static final int CREATE_MODE_CREATE =
64 IIncrementalService.CREATE_MODE_CREATE;
65 public static final int CREATE_MODE_OPEN_EXISTING =
66 IIncrementalService.CREATE_MODE_OPEN_EXISTING;
67
68 @Retention(RetentionPolicy.SOURCE)
69 @IntDef(prefix = {"CREATE_MODE_"}, value = {
70 CREATE_MODE_TEMPORARY_BIND,
71 CREATE_MODE_PERMANENT_BIND,
72 CREATE_MODE_CREATE,
73 CREATE_MODE_OPEN_EXISTING,
74 })
75 public @interface CreateMode {
76 }
77
78 private final @Nullable IIncrementalService mService;
79 @GuardedBy("mStorages")
80 private final SparseArray<IncrementalStorage> mStorages = new SparseArray<>();
81
82 public IncrementalManager(IIncrementalService service) {
83 mService = service;
84 }
85
86 /**
87 * Returns a storage object given a storage ID.
88 *
89 * @param storageId The storage ID to identify the storage object.
90 * @return IncrementalStorage object corresponding to storage ID.
91 */
92 // TODO(b/136132412): remove this
93 @Nullable
94 public IncrementalStorage getStorage(int storageId) {
95 synchronized (mStorages) {
96 return mStorages.get(storageId);
97 }
98 }
99
100 /**
101 * Opens or create an Incremental File System mounted directory and returns an
102 * IncrementalStorage object.
103 *
104 * @param path Absolute path to mount Incremental File System on.
105 * @param params IncrementalDataLoaderParams object to configure data loading.
106 * @param createMode Mode for opening an old Incremental File System mount or creating
107 * a new mount.
108 * @param autoStartDataLoader Set true to immediately start data loader after creating storage.
109 * @return IncrementalStorage object corresponding to the mounted directory.
110 */
111 @Nullable
112 public IncrementalStorage createStorage(@NonNull String path,
113 @NonNull DataLoaderParams params, @Nullable IDataLoaderStatusListener listener,
114 @CreateMode int createMode,
115 boolean autoStartDataLoader) {
116 try {
117 final int id = mService.createStorage(path, params.getData(), listener, createMode);
118 if (id < 0) {
119 return null;
120 }
121 final IncrementalStorage storage = new IncrementalStorage(mService, id);
122 synchronized (mStorages) {
123 mStorages.put(id, storage);
124 }
125 if (autoStartDataLoader) {
126 storage.startLoading();
127 }
128 return storage;
129 } catch (RemoteException e) {
130 throw e.rethrowFromSystemServer();
131 }
132 }
133
134 /**
135 * Opens an existing Incremental File System mounted directory and returns an IncrementalStorage
136 * object.
137 *
138 * @param path Absolute target path that Incremental File System has been mounted on.
139 * @return IncrementalStorage object corresponding to the mounted directory.
140 */
141 @Nullable
142 public IncrementalStorage openStorage(@NonNull String path) {
143 try {
144 final int id = mService.openStorage(path);
145 if (id < 0) {
146 return null;
147 }
148 final IncrementalStorage storage = new IncrementalStorage(mService, id);
149 synchronized (mStorages) {
150 mStorages.put(id, storage);
151 }
152 return storage;
153 } catch (RemoteException e) {
154 throw e.rethrowFromSystemServer();
155 }
156 }
157
158 /**
159 * Opens or creates an IncrementalStorage that is linked to another IncrementalStorage.
160 *
161 * @return IncrementalStorage object corresponding to the linked storage.
162 */
163 @Nullable
164 public IncrementalStorage createStorage(@NonNull String path,
165 @NonNull IncrementalStorage linkedStorage, @CreateMode int createMode) {
166 try {
167 final int id = mService.createLinkedStorage(
168 path, linkedStorage.getId(), createMode);
169 if (id < 0) {
170 return null;
171 }
172 final IncrementalStorage storage = new IncrementalStorage(mService, id);
173 synchronized (mStorages) {
174 mStorages.put(id, storage);
175 }
176 return storage;
177 } catch (RemoteException e) {
178 throw e.rethrowFromSystemServer();
179 }
180 }
181
182 /**
183 * Set up an app's code path. The expected outcome of this method is:
184 * 1) The actual apk directory under /data/incremental is bind-mounted to the parent directory
185 * of {@code afterCodeFile}.
186 * 2) All the files under {@code beforeCodeFile} will show up under {@code afterCodeFile}.
187 *
188 * @param beforeCodeFile Path that is currently bind-mounted and have APKs under it.
189 * Should no longer have any APKs after this method is called.
190 * Example: /data/app/vmdl*tmp
191 * @param afterCodeFile Path that should will have APKs after this method is called. Its parent
192 * directory should be bind-mounted to a directory under /data/incremental.
193 * Example: /data/app/~~[randomStringA]/[packageName]-[randomStringB]
194 * @throws IllegalArgumentException
195 * @throws IOException
196 * TODO(b/147371381): add unit tests
197 */
198 public void renameCodePath(File beforeCodeFile, File afterCodeFile)
199 throws IllegalArgumentException, IOException {
200 final File beforeCodeAbsolute = beforeCodeFile.getAbsoluteFile();
201 final IncrementalStorage apkStorage = openStorage(beforeCodeAbsolute.toString());
202 if (apkStorage == null) {
203 throw new IllegalArgumentException("Not an Incremental path: " + beforeCodeAbsolute);
204 }
205 final String targetStorageDir = afterCodeFile.getAbsoluteFile().getParent();
206 final IncrementalStorage linkedApkStorage =
207 createStorage(targetStorageDir, apkStorage,
208 IncrementalManager.CREATE_MODE_CREATE
209 | IncrementalManager.CREATE_MODE_PERMANENT_BIND);
210 if (linkedApkStorage == null) {
211 throw new IOException("Failed to create linked storage at dir: " + targetStorageDir);
212 }
213 try {
214 final String afterCodePathName = afterCodeFile.getName();
215 linkFiles(apkStorage, beforeCodeAbsolute, "", linkedApkStorage, afterCodePathName);
216 apkStorage.unBind(beforeCodeAbsolute.toString());
217 } catch (Exception e) {
218 linkedApkStorage.unBind(targetStorageDir);
219 throw e;
220 }
221 }
222
223 /**
224 * Recursively set up directories and link all the files from source storage to target storage.
225 *
226 * @param sourceStorage The storage that has all the files and directories underneath.
227 * @param sourceAbsolutePath The absolute path of the directory that holds all files and dirs.
228 * @param sourceRelativePath The relative path on the source directory, e.g., "" or "lib".
229 * @param targetStorage The target storage that will have the same files and directories.
230 * @param targetRelativePath The relative path to the directory on the target storage that
231 * should have all the files and dirs underneath,
232 * e.g., "packageName-random".
233 * @throws IOException When makeDirectory or makeLink fails on the Incremental File System.
234 */
235 private void linkFiles(IncrementalStorage sourceStorage, File sourceAbsolutePath,
236 String sourceRelativePath, IncrementalStorage targetStorage,
237 String targetRelativePath) throws IOException {
238 final Path sourceBase = sourceAbsolutePath.toPath().resolve(sourceRelativePath);
239 final Path targetRelative = Paths.get(targetRelativePath);
240 Files.walkFileTree(sourceAbsolutePath.toPath(), new SimpleFileVisitor<Path>() {
241 @Override
242 public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs)
243 throws IOException {
244 final Path relativeDir = sourceBase.relativize(dir);
245 targetStorage.makeDirectory(targetRelative.resolve(relativeDir).toString());
246 return FileVisitResult.CONTINUE;
247 }
248
249 @Override
250 public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
251 throws IOException {
252 final Path relativeFile = sourceBase.relativize(file);
253 sourceStorage.makeLink(
254 file.toAbsolutePath().toString(), targetStorage,
255 targetRelative.resolve(relativeFile).toString());
256 return FileVisitResult.CONTINUE;
257 }
258 });
259 }
260
261 /**
262 * Closes a storage specified by the absolute path. If the path is not Incremental, do nothing.
263 * Unbinds the target dir and deletes the corresponding storage instance.
264 */
265 public void closeStorage(@NonNull String path) {
266 try {
267 final int id = mService.openStorage(path);
268 if (id < 0) {
269 return;
270 }
271 mService.deleteStorage(id);
272 synchronized (mStorages) {
273 mStorages.remove(id);
274 }
275 } catch (RemoteException e) {
276 throw e.rethrowFromSystemServer();
277 }
278 }
279
280 /**
281 * Checks if Incremental feature is enabled on this device.
282 */
283 public static boolean isFeatureEnabled() {
284 return nativeIsEnabled();
285 }
286
287 /**
288 * Checks if Incremental installations are allowed.
289 * A developer can disable Incremental installations by setting the property.
290 */
291 public static boolean isAllowed() {
292 return isFeatureEnabled() && android.os.SystemProperties.getBoolean(ALLOWED_PROPERTY, true);
293 }
294
295 /**
296 * Checks if path is mounted on Incremental File System.
297 */
298 public static boolean isIncrementalPath(@NonNull String path) {
299 return nativeIsIncrementalPath(path);
300 }
301
302 /**
303 * Returns raw signature for file if it's on Incremental File System.
304 * Unsafe, use only if you are sure what you are doing.
305 */
306 public static @Nullable byte[] unsafeGetFileSignature(@NonNull String path) {
307 return nativeUnsafeGetFileSignature(path);
308 }
309
310 /* Native methods */
311 private static native boolean nativeIsEnabled();
312 private static native boolean nativeIsIncrementalPath(@NonNull String path);
313 private static native byte[] nativeUnsafeGetFileSignature(@NonNull String path);
314}