/* * Copyright (C) 2019 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.os.incremental; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemService; import android.content.Context; import android.content.pm.DataLoaderParams; import android.content.pm.IPackageLoadingProgressCallback; import android.os.RemoteCallbackList; import android.os.RemoteException; import android.system.ErrnoException; import android.system.Os; import android.system.StructStat; import android.util.Slog; import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; import java.io.File; import java.io.FileDescriptor; import java.io.IOException; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.nio.file.FileVisitResult; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; import java.util.Objects; /** * Provides operations to open or create an IncrementalStorage, using IIncrementalService * service. Example Usage: * *
* * @hide */ @SystemService(Context.INCREMENTAL_SERVICE) public final class IncrementalManager { private static final String TAG = "IncrementalManager"; private static final String ALLOWED_PROPERTY = "incremental.allowed"; public static final int MIN_VERSION_TO_SUPPORT_FSVERITY = 2; public static final int CREATE_MODE_TEMPORARY_BIND = IIncrementalService.CREATE_MODE_TEMPORARY_BIND; public static final int CREATE_MODE_PERMANENT_BIND = IIncrementalService.CREATE_MODE_PERMANENT_BIND; public static final int CREATE_MODE_CREATE = IIncrementalService.CREATE_MODE_CREATE; public static final int CREATE_MODE_OPEN_EXISTING = IIncrementalService.CREATE_MODE_OPEN_EXISTING; @Retention(RetentionPolicy.SOURCE) @IntDef(prefix = {"CREATE_MODE_"}, value = { CREATE_MODE_TEMPORARY_BIND, CREATE_MODE_PERMANENT_BIND, CREATE_MODE_CREATE, CREATE_MODE_OPEN_EXISTING, }) public @interface CreateMode { } private final @Nullable IIncrementalService mService; private final LoadingProgressCallbacks mLoadingProgressCallbacks = new LoadingProgressCallbacks(); public IncrementalManager(IIncrementalService service) { mService = service; } /** * Opens or create an Incremental File System mounted directory and returns an * IncrementalStorage object. * * @param path Absolute path to mount Incremental File System on. * @param params IncrementalDataLoaderParams object to configure data loading. * @param createMode Mode for opening an old Incremental File System mount or creating * a new mount. * @return IncrementalStorage object corresponding to the mounted directory. */ @Nullable public IncrementalStorage createStorage(@NonNull String path, @NonNull DataLoaderParams params, @CreateMode int createMode) { Objects.requireNonNull(path); Objects.requireNonNull(params); try { final int id = mService.createStorage(path, params.getData(), createMode); if (id < 0) { return null; } return new IncrementalStorage(mService, id); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Opens an existing Incremental File System mounted directory and returns an IncrementalStorage * object. * * @param path Absolute target path that Incremental File System has been mounted on. * @return IncrementalStorage object corresponding to the mounted directory. */ @Nullable public IncrementalStorage openStorage(@NonNull String path) { try { final int id = mService.openStorage(path); if (id < 0) { return null; } final IncrementalStorage storage = new IncrementalStorage(mService, id); return storage; } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Opens or creates an IncrementalStorage that is linked to another IncrementalStorage. * * @return IncrementalStorage object corresponding to the linked storage. */ @Nullable public IncrementalStorage createStorage(@NonNull String path, @NonNull IncrementalStorage linkedStorage, @CreateMode int createMode) { int id = -1; try { // Incremental service mounts its newly created storage on top of the supplied path, // ensure that the original mode remains the same after mounting. StructStat st = Os.stat(path); id = mService.createLinkedStorage( path, linkedStorage.getId(), createMode); if (id < 0) { return null; } Os.chmod(path, st.st_mode & 07777); return new IncrementalStorage(mService, id); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } catch (ErrnoException e) { if (id >= 0) { try { mService.deleteStorage(id); } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } } throw new RuntimeException(e); } } /** * Link an app's files from the stage dir to the final installation location. * The expected outcome of this method is: * 1) The actual apk directory under /data/incremental is bind-mounted to the parent directory * of {@code afterCodeFile}. * 2) All the files under {@code beforeCodeFile} will show up under {@code afterCodeFile}. * * @param beforeCodeFile Path that is currently bind-mounted and have APKs under it. * Example: /data/app/vmdl*tmp * @param afterCodeFile Path that should will have APKs after this method is called. Its parent * directory should be bind-mounted to a directory under /data/incremental. * Example: /data/app/~~[randomStringA]/[packageName]-[randomStringB] * @throws IllegalArgumentException * @throws IOException */ public void linkCodePath(File beforeCodeFile, File afterCodeFile) throws IllegalArgumentException, IOException { final File beforeCodeAbsolute = beforeCodeFile.getAbsoluteFile(); final IncrementalStorage apkStorage = openStorage(beforeCodeAbsolute.toString()); if (apkStorage == null) { throw new IllegalArgumentException("Not an Incremental path: " + beforeCodeAbsolute); } final String targetStorageDir = afterCodeFile.getAbsoluteFile().getParent(); final IncrementalStorage linkedApkStorage = createStorage(targetStorageDir, apkStorage, IncrementalManager.CREATE_MODE_CREATE | IncrementalManager.CREATE_MODE_PERMANENT_BIND); if (linkedApkStorage == null) { throw new IOException("Failed to create linked storage at dir: " + targetStorageDir); } try { final String afterCodePathName = afterCodeFile.getName(); linkFiles(apkStorage, beforeCodeAbsolute, "", linkedApkStorage, afterCodePathName); } catch (Exception e) { linkedApkStorage.unBind(targetStorageDir); throw e; } } /** * Recursively set up directories and link all the files from source storage to target storage. * * @param sourceStorage The storage that has all the files and directories underneath. * @param sourceAbsolutePath The absolute path of the directory that holds all files and dirs. * @param sourceRelativePath The relative path on the source directory, e.g., "" or "lib". * @param targetStorage The target storage that will have the same files and directories. * @param targetRelativePath The relative path to the directory on the target storage that * should have all the files and dirs underneath, * e.g., "packageName-random". * @throws IOException When makeDirectory or makeLink fails on the Incremental File System. */ private void linkFiles(IncrementalStorage sourceStorage, File sourceAbsolutePath, String sourceRelativePath, IncrementalStorage targetStorage, String targetRelativePath) throws IOException { final Path sourceBase = sourceAbsolutePath.toPath().resolve(sourceRelativePath); final Path targetRelative = Paths.get(targetRelativePath); Files.walkFileTree(sourceAbsolutePath.toPath(), new SimpleFileVisitor* IncrementalManager manager = (IncrementalManager) getSystemService(Context.INCREMENTAL_SERVICE); * IncrementalStorage storage = manager.openStorage("/path/to/incremental/dir"); *