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