• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
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 
17 package android.os.incremental;
18 
19 import android.annotation.IntDef;
20 import android.annotation.NonNull;
21 import android.annotation.Nullable;
22 import android.annotation.SystemService;
23 import android.content.Context;
24 import android.content.pm.DataLoaderParams;
25 import android.content.pm.IPackageLoadingProgressCallback;
26 import android.os.RemoteCallbackList;
27 import android.os.RemoteException;
28 import android.util.Slog;
29 import android.util.SparseArray;
30 
31 import com.android.internal.annotations.GuardedBy;
32 
33 import java.io.File;
34 import java.io.FileDescriptor;
35 import java.io.IOException;
36 import java.lang.annotation.Retention;
37 import java.lang.annotation.RetentionPolicy;
38 import java.nio.file.FileVisitResult;
39 import java.nio.file.Files;
40 import java.nio.file.Path;
41 import java.nio.file.Paths;
42 import java.nio.file.SimpleFileVisitor;
43 import java.nio.file.attribute.BasicFileAttributes;
44 import java.util.Objects;
45 
46 /**
47  * Provides operations to open or create an IncrementalStorage, using IIncrementalService
48  * service. Example Usage:
49  *
50  * <blockquote><pre>
51  * IncrementalManager manager = (IncrementalManager) getSystemService(Context.INCREMENTAL_SERVICE);
52  * IncrementalStorage storage = manager.openStorage("/path/to/incremental/dir");
53  * </pre></blockquote>
54  *
55  * @hide
56  */
57 @SystemService(Context.INCREMENTAL_SERVICE)
58 public final class IncrementalManager {
59     private static final String TAG = "IncrementalManager";
60 
61     private static final String ALLOWED_PROPERTY = "incremental.allowed";
62 
63     public static final int MIN_VERSION_TO_SUPPORT_FSVERITY = 2;
64 
65     public static final int CREATE_MODE_TEMPORARY_BIND =
66             IIncrementalService.CREATE_MODE_TEMPORARY_BIND;
67     public static final int CREATE_MODE_PERMANENT_BIND =
68             IIncrementalService.CREATE_MODE_PERMANENT_BIND;
69     public static final int CREATE_MODE_CREATE =
70             IIncrementalService.CREATE_MODE_CREATE;
71     public static final int CREATE_MODE_OPEN_EXISTING =
72             IIncrementalService.CREATE_MODE_OPEN_EXISTING;
73 
74     @Retention(RetentionPolicy.SOURCE)
75     @IntDef(prefix = {"CREATE_MODE_"}, value = {
76             CREATE_MODE_TEMPORARY_BIND,
77             CREATE_MODE_PERMANENT_BIND,
78             CREATE_MODE_CREATE,
79             CREATE_MODE_OPEN_EXISTING,
80     })
81     public @interface CreateMode {
82     }
83 
84     private final @Nullable IIncrementalService mService;
85 
86     private final LoadingProgressCallbacks mLoadingProgressCallbacks =
87             new LoadingProgressCallbacks();
88 
IncrementalManager(IIncrementalService service)89     public IncrementalManager(IIncrementalService service) {
90         mService = service;
91     }
92 
93     /**
94      * Opens or create an Incremental File System mounted directory and returns an
95      * IncrementalStorage object.
96      *
97      * @param path                Absolute path to mount Incremental File System on.
98      * @param params              IncrementalDataLoaderParams object to configure data loading.
99      * @param createMode          Mode for opening an old Incremental File System mount or creating
100      *                            a new mount.
101      * @return IncrementalStorage object corresponding to the mounted directory.
102      */
103     @Nullable
createStorage(@onNull String path, @NonNull DataLoaderParams params, @CreateMode int createMode)104     public IncrementalStorage createStorage(@NonNull String path,
105             @NonNull DataLoaderParams params,
106             @CreateMode int createMode) {
107         Objects.requireNonNull(path);
108         Objects.requireNonNull(params);
109         try {
110             final int id = mService.createStorage(path, params.getData(), createMode);
111             if (id < 0) {
112                 return null;
113             }
114             return new IncrementalStorage(mService, id);
115         } catch (RemoteException e) {
116             throw e.rethrowFromSystemServer();
117         }
118     }
119 
120     /**
121      * Opens an existing Incremental File System mounted directory and returns an IncrementalStorage
122      * object.
123      *
124      * @param path Absolute target path that Incremental File System has been mounted on.
125      * @return IncrementalStorage object corresponding to the mounted directory.
126      */
127     @Nullable
openStorage(@onNull String path)128     public IncrementalStorage openStorage(@NonNull String path) {
129         try {
130             final int id = mService.openStorage(path);
131             if (id < 0) {
132                 return null;
133             }
134             final IncrementalStorage storage = new IncrementalStorage(mService, id);
135             return storage;
136         } catch (RemoteException e) {
137             throw e.rethrowFromSystemServer();
138         }
139     }
140 
141     /**
142      * Opens or creates an IncrementalStorage that is linked to another IncrementalStorage.
143      *
144      * @return IncrementalStorage object corresponding to the linked storage.
145      */
146     @Nullable
createStorage(@onNull String path, @NonNull IncrementalStorage linkedStorage, @CreateMode int createMode)147     public IncrementalStorage createStorage(@NonNull String path,
148             @NonNull IncrementalStorage linkedStorage, @CreateMode int createMode) {
149         try {
150             final int id = mService.createLinkedStorage(
151                     path, linkedStorage.getId(), createMode);
152             if (id < 0) {
153                 return null;
154             }
155             return new IncrementalStorage(mService, id);
156         } catch (RemoteException e) {
157             throw e.rethrowFromSystemServer();
158         }
159     }
160 
161     /**
162      * Link an app's files from the stage dir to the final installation location.
163      * The expected outcome of this method is:
164      * 1) The actual apk directory under /data/incremental is bind-mounted to the parent directory
165      * of {@code afterCodeFile}.
166      * 2) All the files under {@code beforeCodeFile} will show up under {@code afterCodeFile}.
167      *
168      * @param beforeCodeFile Path that is currently bind-mounted and have APKs under it.
169      *                       Example: /data/app/vmdl*tmp
170      * @param afterCodeFile Path that should will have APKs after this method is called. Its parent
171      *                      directory should be bind-mounted to a directory under /data/incremental.
172      *                      Example: /data/app/~~[randomStringA]/[packageName]-[randomStringB]
173      * @throws IllegalArgumentException
174      * @throws IOException
175      */
linkCodePath(File beforeCodeFile, File afterCodeFile)176     public void linkCodePath(File beforeCodeFile, File afterCodeFile)
177             throws IllegalArgumentException, IOException {
178         final File beforeCodeAbsolute = beforeCodeFile.getAbsoluteFile();
179         final IncrementalStorage apkStorage = openStorage(beforeCodeAbsolute.toString());
180         if (apkStorage == null) {
181             throw new IllegalArgumentException("Not an Incremental path: " + beforeCodeAbsolute);
182         }
183         final String targetStorageDir = afterCodeFile.getAbsoluteFile().getParent();
184         final IncrementalStorage linkedApkStorage =
185                 createStorage(targetStorageDir, apkStorage,
186                         IncrementalManager.CREATE_MODE_CREATE
187                                 | IncrementalManager.CREATE_MODE_PERMANENT_BIND);
188         if (linkedApkStorage == null) {
189             throw new IOException("Failed to create linked storage at dir: " + targetStorageDir);
190         }
191         try {
192             final String afterCodePathName = afterCodeFile.getName();
193             linkFiles(apkStorage, beforeCodeAbsolute, "", linkedApkStorage, afterCodePathName);
194         } catch (Exception e) {
195             linkedApkStorage.unBind(targetStorageDir);
196             throw e;
197         }
198     }
199 
200     /**
201      * Recursively set up directories and link all the files from source storage to target storage.
202      *
203      * @param sourceStorage The storage that has all the files and directories underneath.
204      * @param sourceAbsolutePath The absolute path of the directory that holds all files and dirs.
205      * @param sourceRelativePath The relative path on the source directory, e.g., "" or "lib".
206      * @param targetStorage The target storage that will have the same files and directories.
207      * @param targetRelativePath The relative path to the directory on the target storage that
208      *                           should have all the files and dirs underneath,
209      *                           e.g., "packageName-random".
210      * @throws IOException When makeDirectory or makeLink fails on the Incremental File System.
211      */
linkFiles(IncrementalStorage sourceStorage, File sourceAbsolutePath, String sourceRelativePath, IncrementalStorage targetStorage, String targetRelativePath)212     private void linkFiles(IncrementalStorage sourceStorage, File sourceAbsolutePath,
213             String sourceRelativePath, IncrementalStorage targetStorage,
214             String targetRelativePath) throws IOException {
215         final Path sourceBase = sourceAbsolutePath.toPath().resolve(sourceRelativePath);
216         final Path targetRelative = Paths.get(targetRelativePath);
217         Files.walkFileTree(sourceAbsolutePath.toPath(), new SimpleFileVisitor<Path>() {
218             @Override
219             public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs)
220                     throws IOException {
221                 final Path relativeDir = sourceBase.relativize(dir);
222                 targetStorage.makeDirectory(targetRelative.resolve(relativeDir).toString());
223                 return FileVisitResult.CONTINUE;
224             }
225 
226             @Override
227             public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
228                     throws IOException {
229                 final Path relativeFile = sourceBase.relativize(file);
230                 sourceStorage.makeLink(
231                         file.toAbsolutePath().toString(), targetStorage,
232                         targetRelative.resolve(relativeFile).toString());
233                 return FileVisitResult.CONTINUE;
234             }
235         });
236     }
237 
238     /**
239      * Checks if Incremental feature is enabled on this device.
240      */
isFeatureEnabled()241     public static boolean isFeatureEnabled() {
242         return nativeIsEnabled();
243     }
244 
245     /**
246      * 0 - IncFs is disabled.
247      * 1 - IncFs v1, core features, no PerUid support. Optional in R.
248      * 2 - IncFs v2, PerUid support, fs-verity support. Required in S.
249      */
getVersion()250     public static int getVersion() {
251         return nativeIsEnabled() ? nativeIsV2Available() ? 2 : 1 : 0;
252     }
253 
254     /**
255      * Checks if Incremental installations are allowed.
256      * A developer can disable Incremental installations by setting the property.
257      */
isAllowed()258     public static boolean isAllowed() {
259         return isFeatureEnabled() && android.os.SystemProperties.getBoolean(ALLOWED_PROPERTY, true);
260     }
261 
262     /**
263      * Checks if path is mounted on Incremental File System.
264      */
isIncrementalPath(@onNull String path)265     public static boolean isIncrementalPath(@NonNull String path) {
266         return nativeIsIncrementalPath(path);
267     }
268 
269     /**
270      * Checks if an fd corresponds to a file on a mounted Incremental File System.
271      */
isIncrementalFileFd(@onNull FileDescriptor fd)272     public static boolean isIncrementalFileFd(@NonNull FileDescriptor fd) {
273         return nativeIsIncrementalFd(fd.getInt$());
274     }
275 
276     /**
277      * Returns raw signature for file if it's on Incremental File System.
278      * Unsafe, use only if you are sure what you are doing.
279      */
unsafeGetFileSignature(@onNull String path)280     public static @Nullable byte[] unsafeGetFileSignature(@NonNull String path) {
281         return nativeUnsafeGetFileSignature(path);
282     }
283 
284     /**
285      * Closes a storage specified by the absolute path. If the path is not Incremental, do nothing.
286      * Unbinds the target dir and deletes the corresponding storage instance.
287      * Deletes the package name and associated storage id from maps.
288      */
rmPackageDir(@onNull File codeFile)289     public void rmPackageDir(@NonNull File codeFile) {
290         try {
291             final String codePath = codeFile.getAbsolutePath();
292             final IncrementalStorage storage = openStorage(codePath);
293             if (storage == null) {
294                 return;
295             }
296             mLoadingProgressCallbacks.cleanUpCallbacks(storage);
297             storage.unBind(codePath);
298         } catch (IOException e) {
299             Slog.w(TAG, "Failed to remove code path", e);
300         }
301     }
302 
303     /**
304      * Called when a new callback wants to listen to the loading progress of an installed package.
305      * Increment the count of callbacks associated to the corresponding storage.
306      * Only register storage listener if there hasn't been any existing callback on the storage yet.
307      * @param codePath Path of the installed package. This path is on an Incremental Storage.
308      * @param callback To report loading progress to.
309      * @return True if the package name and associated storage id are valid. False otherwise.
310      */
registerLoadingProgressCallback(@onNull String codePath, @NonNull IPackageLoadingProgressCallback callback)311     public boolean registerLoadingProgressCallback(@NonNull String codePath,
312             @NonNull IPackageLoadingProgressCallback callback) {
313         final IncrementalStorage storage = openStorage(codePath);
314         if (storage == null) {
315             // storage does not exist, package not installed
316             return false;
317         }
318         return mLoadingProgressCallbacks.registerCallback(storage, callback);
319     }
320 
321     /**
322      * Called to stop all listeners from listening to loading progress of an installed package.
323      * @param codePath Path of the installed package
324      */
unregisterLoadingProgressCallbacks(@onNull String codePath)325     public void unregisterLoadingProgressCallbacks(@NonNull String codePath) {
326         final IncrementalStorage storage = openStorage(codePath);
327         if (storage == null) {
328             // storage does not exist, package not installed
329             return;
330         }
331         mLoadingProgressCallbacks.cleanUpCallbacks(storage);
332     }
333 
334     private static class LoadingProgressCallbacks extends IStorageLoadingProgressListener.Stub {
335         @GuardedBy("mCallbacks")
336         private final SparseArray<RemoteCallbackList<IPackageLoadingProgressCallback>> mCallbacks =
337                 new SparseArray<>();
338 
cleanUpCallbacks(@onNull IncrementalStorage storage)339         public void cleanUpCallbacks(@NonNull IncrementalStorage storage) {
340             final int storageId = storage.getId();
341             final RemoteCallbackList<IPackageLoadingProgressCallback> callbacksForStorage;
342             synchronized (mCallbacks) {
343                 callbacksForStorage = mCallbacks.removeReturnOld(storageId);
344             }
345             if (callbacksForStorage == null) {
346                 return;
347             }
348             // Unregister all existing callbacks on this storage
349             callbacksForStorage.kill();
350             storage.unregisterLoadingProgressListener();
351         }
352 
registerCallback(@onNull IncrementalStorage storage, @NonNull IPackageLoadingProgressCallback callback)353         public boolean registerCallback(@NonNull IncrementalStorage storage,
354                 @NonNull IPackageLoadingProgressCallback callback) {
355             final int storageId = storage.getId();
356             synchronized (mCallbacks) {
357                 RemoteCallbackList<IPackageLoadingProgressCallback> callbacksForStorage =
358                         mCallbacks.get(storageId);
359                 if (callbacksForStorage == null) {
360                     callbacksForStorage = new RemoteCallbackList<>();
361                     mCallbacks.put(storageId, callbacksForStorage);
362                 }
363                 // Registration in RemoteCallbackList needs to be done first, such that when events
364                 // come from Incremental Service, the callback is already registered
365                 callbacksForStorage.register(callback);
366                 if (callbacksForStorage.getRegisteredCallbackCount() > 1) {
367                     // already listening for progress for this storage
368                     return true;
369                 }
370             }
371             return storage.registerLoadingProgressListener(this);
372         }
373 
374         @Override
onStorageLoadingProgressChanged(int storageId, float progress)375         public void onStorageLoadingProgressChanged(int storageId, float progress) {
376             final RemoteCallbackList<IPackageLoadingProgressCallback> callbacksForStorage;
377             synchronized (mCallbacks) {
378                 callbacksForStorage = mCallbacks.get(storageId);
379             }
380             if (callbacksForStorage == null) {
381                 // no callback has ever been registered on this storage
382                 return;
383             }
384             final int n = callbacksForStorage.beginBroadcast();
385             // RemoteCallbackList use ArrayMap internally and it's safe to iterate this way
386             for (int i = 0; i < n; i++) {
387                 final IPackageLoadingProgressCallback callback =
388                         callbacksForStorage.getBroadcastItem(i);
389                 try {
390                     callback.onPackageLoadingProgressChanged(progress);
391                 } catch (RemoteException ignored) {
392                 }
393             }
394             callbacksForStorage.finishBroadcast();
395         }
396     }
397 
398     /**
399      * Returns the metrics of an Incremental Storage.
400      */
getMetrics(@onNull String codePath)401     public IncrementalMetrics getMetrics(@NonNull String codePath) {
402         final IncrementalStorage storage = openStorage(codePath);
403         if (storage == null) {
404             // storage does not exist, package not installed
405             return null;
406         }
407         return new IncrementalMetrics(storage.getMetrics());
408     }
409 
410     /* Native methods */
nativeIsEnabled()411     private static native boolean nativeIsEnabled();
nativeIsV2Available()412     private static native boolean nativeIsV2Available();
nativeIsIncrementalPath(@onNull String path)413     private static native boolean nativeIsIncrementalPath(@NonNull String path);
nativeIsIncrementalFd(@onNull int fd)414     private static native boolean nativeIsIncrementalFd(@NonNull int fd);
nativeUnsafeGetFileSignature(@onNull String path)415     private static native byte[] nativeUnsafeGetFileSignature(@NonNull String path);
416 }
417