diff options
| -rw-r--r-- | Android.bp | 2 | ||||
| -rw-r--r-- | core/java/android/content/Context.java | 8 | ||||
| -rw-r--r-- | core/java/android/os/incremental/IIncrementalManager.aidl (renamed from core/java/android/os/incremental/IIncrementalService.aidl) | 24 | ||||
| -rw-r--r-- | core/java/android/os/incremental/IncrementalDataLoaderParams.java | 84 | ||||
| -rw-r--r-- | core/java/android/os/incremental/IncrementalDataLoaderParamsParcel.aidl | 2 | ||||
| -rw-r--r-- | core/java/android/os/incremental/IncrementalManager.java | 327 | ||||
| -rw-r--r-- | core/java/android/os/incremental/IncrementalStorage.java | 346 | ||||
| -rw-r--r-- | core/java/com/android/internal/content/NativeLibraryHelper.java | 19 | ||||
| -rw-r--r-- | services/core/java/com/android/server/pm/PackageAbiHelperImpl.java | 46 | ||||
| -rw-r--r-- | services/core/java/com/android/server/pm/PackageManagerService.java | 30 |
10 files changed, 863 insertions, 25 deletions
diff --git a/Android.bp b/Android.bp index 303cff999c1f..f60cbda91f38 100644 --- a/Android.bp +++ b/Android.bp @@ -772,7 +772,7 @@ cc_library { filegroup { name: "incremental_aidl", srcs: [ - "core/java/android/os/incremental/IIncrementalService.aidl", + "core/java/android/os/incremental/IIncrementalManager.aidl", "core/java/android/os/incremental/IIncrementalServiceProxy.aidl", "core/java/android/os/incremental/IncrementalDataLoaderParamsParcel.aidl", "core/java/android/os/incremental/IncrementalFileSystemControlParcel.aidl", diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index fc584c44aea9..777d210e21de 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -3426,6 +3426,7 @@ public abstract class Context { //@hide: SYSTEM_UPDATE_SERVICE, //@hide: TIME_DETECTOR_SERVICE, PERMISSION_SERVICE, + INCREMENTAL_SERVICE, }) @Retention(RetentionPolicy.SOURCE) public @interface ServiceName {} @@ -4959,6 +4960,13 @@ public abstract class Context { public static final String APP_INTEGRITY_SERVICE = "app_integrity"; /** + * Use with {@link #getSystemService(String)} to retrieve an + * {@link android.os.incremental.IncrementalManager}. + * @hide + */ + public static final String INCREMENTAL_SERVICE = "incremental"; + + /** * Determine whether the given permission is allowed for a particular * process and user ID running in the system. * diff --git a/core/java/android/os/incremental/IIncrementalService.aidl b/core/java/android/os/incremental/IIncrementalManager.aidl index 1c832ca9e6db..d6446d485af5 100644 --- a/core/java/android/os/incremental/IIncrementalService.aidl +++ b/core/java/android/os/incremental/IIncrementalManager.aidl @@ -19,7 +19,7 @@ package android.os.incremental; import android.os.incremental.IncrementalDataLoaderParamsParcel; /** @hide */ -interface IIncrementalService { +interface IIncrementalManager { /** * A set of flags for the |createMode| parameters when creating a new Incremental storage. */ @@ -53,6 +53,12 @@ interface IIncrementalService { int makeDirectory(int storageId, in @utf8InCpp String pathUnderStorage); /** + * Recursively creates a directory under a storage. The target directory is specified by its relative path under the storage. + * All the parent directories of the target directory will be created if they do not exist already. + */ + int makeDirectories(int storageId, in @utf8InCpp String pathUnderStorage); + + /** * Creates a file under a storage, specifying its name, size and metadata. */ int makeFile(int storageId, in @utf8InCpp String pathUnderStorage, long size, in byte[] metadata); @@ -64,10 +70,12 @@ interface IIncrementalService { int makeFileFromRange(int storageId, in @utf8InCpp String targetPathUnderStorage, in @utf8InCpp String sourcePathUnderStorage, long start, long end); /** - * Creates a hard link between two files in a storage. - * Both source and destination are specified by relative paths under storage. + * Creates a hard link between two files in two storage instances. + * Source and dest specified by parent storage IDs and their relative paths under the storage. + * The source and dest storage instances should be in the same fs mount. + * Note: destStorageId can be the same as sourceStorageId. */ - int makeLink(int storageId, in @utf8InCpp String sourcePathUnderStorage, in @utf8InCpp String destPathUnderStorage); + int makeLink(int sourceStorageId, in @utf8InCpp String sourcePathUnderStorage, int destStorageId, in @utf8InCpp String destPathUnderStorage); /** * Deletes a hard link in a storage, specified by the relative path of the link target under storage. @@ -85,12 +93,12 @@ interface IIncrementalService { byte[] getFileMetadata(int storageId, in @utf8InCpp String pathUnderStorage); /** - * Returns the list of file paths under a storage. + * Starts loading data for a storage. */ - @utf8InCpp String[] getFileList(int storageId); + boolean startLoading(int storageId); /** - * Starts loading data for a storage. + * Deletes a storage given its ID. Deletes its bind mounts and unmount it. Stop its data loader. */ - boolean startLoading(int storageId); + void deleteStorage(int storageId); } diff --git a/core/java/android/os/incremental/IncrementalDataLoaderParams.java b/core/java/android/os/incremental/IncrementalDataLoaderParams.java new file mode 100644 index 000000000000..701f1cc8de02 --- /dev/null +++ b/core/java/android/os/incremental/IncrementalDataLoaderParams.java @@ -0,0 +1,84 @@ +/* + * 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.NonNull; +import android.annotation.Nullable; +import android.os.ParcelFileDescriptor; + +import java.util.Arrays; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * This class represents the parameters used to configure an Incremental Data Loader. + * Hide for now. + * @hide + */ +public class IncrementalDataLoaderParams { + @NonNull private final IncrementalDataLoaderParamsParcel mData; + + public IncrementalDataLoaderParams(@NonNull String url, @NonNull String packageName, + @Nullable Map<String, ParcelFileDescriptor> namedFds) { + IncrementalDataLoaderParamsParcel data = new IncrementalDataLoaderParamsParcel(); + data.staticArgs = url; + data.packageName = packageName; + if (namedFds == null || namedFds.isEmpty()) { + data.dynamicArgs = new NamedParcelFileDescriptor[0]; + } else { + data.dynamicArgs = new NamedParcelFileDescriptor[namedFds.size()]; + int i = 0; + for (Map.Entry<String, ParcelFileDescriptor> namedFd : namedFds.entrySet()) { + data.dynamicArgs[i] = new NamedParcelFileDescriptor(); + data.dynamicArgs[i].name = namedFd.getKey(); + data.dynamicArgs[i].fd = namedFd.getValue(); + i += 1; + } + } + mData = data; + } + + public IncrementalDataLoaderParams(@NonNull IncrementalDataLoaderParamsParcel data) { + mData = data; + } + + /** + * @return static server's URL + */ + public final @NonNull String getStaticArgs() { + return mData.staticArgs; + } + + /** + * @return data loader's package name + */ + public final @NonNull String getPackageName() { + return mData.packageName; + } + + public final @NonNull IncrementalDataLoaderParamsParcel getData() { + return mData; + } + + /** + * @return data loader's dynamic arguments such as file descriptors + */ + public final @NonNull Map<String, ParcelFileDescriptor> getDynamicArgs() { + return Arrays.stream(mData.dynamicArgs).collect( + Collectors.toMap(p->p.name, p->p.fd)); + } +} diff --git a/core/java/android/os/incremental/IncrementalDataLoaderParamsParcel.aidl b/core/java/android/os/incremental/IncrementalDataLoaderParamsParcel.aidl index 50c28f0a4c17..cd988dcace5b 100644 --- a/core/java/android/os/incremental/IncrementalDataLoaderParamsParcel.aidl +++ b/core/java/android/os/incremental/IncrementalDataLoaderParamsParcel.aidl @@ -23,7 +23,7 @@ import android.os.incremental.NamedParcelFileDescriptor; * @hide */ parcelable IncrementalDataLoaderParamsParcel { - @utf8InCpp String staticUri; @utf8InCpp String packageName; + @utf8InCpp String staticArgs; NamedParcelFileDescriptor[] dynamicArgs; } diff --git a/core/java/android/os/incremental/IncrementalManager.java b/core/java/android/os/incremental/IncrementalManager.java new file mode 100644 index 000000000000..5aabf86e17e6 --- /dev/null +++ b/core/java/android/os/incremental/IncrementalManager.java @@ -0,0 +1,327 @@ +/* + * 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.os.RemoteException; +import android.system.ErrnoException; +import android.system.Os; +import android.util.SparseArray; + +import com.android.internal.annotations.GuardedBy; + +import java.io.File; +import java.io.IOException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.nio.file.Path; +import java.nio.file.Paths; + +/** + * Provides operations to open or create an IncrementalStorage, using IIncrementalManager service. + * Example Usage: + * + * <blockquote><pre> + * IncrementalManager manager = (IncrementalManager) getSystemService(Context.INCREMENTAL_MANAGER); + * IncrementalStorage storage = manager.openStorage("/path/to/incremental/dir"); + * </pre></blockquote> + * + * @hide + */ +@SystemService(Context.INCREMENTAL_SERVICE) +public class IncrementalManager { + private static final String TAG = "IncrementalManager"; + private final IIncrementalManager mService; + @GuardedBy("mStorages") + private final SparseArray<IncrementalStorage> mStorages = new SparseArray<>(); + + public static final int CREATE_MODE_TEMPORARY_BIND = + IIncrementalManager.CREATE_MODE_TEMPORARY_BIND; + public static final int CREATE_MODE_PERMANENT_BIND = + IIncrementalManager.CREATE_MODE_PERMANENT_BIND; + public static final int CREATE_MODE_CREATE = + IIncrementalManager.CREATE_MODE_CREATE; + public static final int CREATE_MODE_OPEN_EXISTING = + IIncrementalManager.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 { + } + + public IncrementalManager(@NonNull IIncrementalManager is) { + mService = is; + } + + /** + * Returns a storage object given a storage ID. + * + * @param storageId The storage ID to identify the storage object. + * @return IncrementalStorage object corresponding to storage ID. + */ + @Nullable + public IncrementalStorage getStorage(int storageId) { + synchronized (mStorages) { + return mStorages.get(storageId); + } + } + + /** + * 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. + * @param autoStartDataLoader Set true to immediately start data loader after creating storage. + * @return IncrementalStorage object corresponding to the mounted directory. + */ + @Nullable + public IncrementalStorage createStorage(@NonNull String path, + @NonNull IncrementalDataLoaderParams params, @CreateMode int createMode, + boolean autoStartDataLoader) { + try { + final int id = mService.createStorage(path, params.getData(), createMode); + if (id < 0) { + return null; + } + final IncrementalStorage storage = new IncrementalStorage(mService, id); + synchronized (mStorages) { + mStorages.put(id, storage); + } + if (autoStartDataLoader) { + storage.startLoading(); + } + return storage; + } 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); + synchronized (mStorages) { + mStorages.put(id, storage); + } + 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) { + try { + final int id = mService.createLinkedStorage(path, linkedStorage.getId(), createMode); + if (id < 0) { + return null; + } + final IncrementalStorage storage = new IncrementalStorage(mService, id); + synchronized (mStorages) { + mStorages.put(id, storage); + } + return storage; + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Iterates through path parents to find the base dir of an Incremental Storage. + * + * @param file Target file to search storage for. + * @return Absolute path which is a bind-mount point of Incremental File System. + */ + private Path getStoragePathForFile(File file) { + File currentPath = new File(file.getParent()); + while (currentPath.getParent() != null) { + IncrementalStorage storage = openStorage(currentPath.getAbsolutePath()); + if (storage != null) { + return currentPath.toPath(); + } + currentPath = new File(currentPath.getParent()); + } + return null; + } + + /** + * Renames an Incremental path to a new path. If source path is a file, make a link from the old + * Incremental file to the new one. If source path is a dir, unbind old dir from Incremental + * Storage and bind the new one. + * <ol> + * <li> For renaming a dir, dest dir will be created if not exists, and does not need to + * be on the same Incremental storage as the source. </li> + * <li> For renaming a file, dest file must be on the same Incremental storage as source. + * </li> + * </ol> + * + * @param sourcePath Absolute path to the source. Should be the same type as the destPath + * (file or dir). Expected to already exist and is an Incremental path. + * @param destPath Absolute path to the destination. + * @throws IllegalArgumentException when 1) source does not exist, + * or 2) source and dest type mismatch (one is file and the other is dir), + * or 3) source path is not on Incremental File System, + * @throws IOException when 1) cannot find the root path of the Incremental storage of source, + * or 2) cannot retrieve the Incremental storage instance of the source, + * or 3) renaming a file, but dest is not on the same Incremental Storage, + * or 4) renaming a dir, dest dir does not exist but fails to be created. + * + * TODO(b/136132412): add unit tests + */ + public void rename(@NonNull String sourcePath, @NonNull String destPath) throws IOException { + final File source = new File(sourcePath); + final File dest = new File(destPath); + if (!source.exists()) { + throw new IllegalArgumentException("Path not exist: " + sourcePath); + } + if (dest.exists()) { + throw new IllegalArgumentException("Target path already exists: " + destPath); + } + if (source.isDirectory() && dest.exists() && dest.isFile()) { + throw new IllegalArgumentException( + "Trying to rename a dir but destination is a file: " + destPath); + } + if (source.isFile() && dest.exists() && dest.isDirectory()) { + throw new IllegalArgumentException( + "Trying to rename a file but destination is a dir: " + destPath); + } + if (!isIncrementalPath(sourcePath)) { + throw new IllegalArgumentException("Not an Incremental path: " + sourcePath); + } + + Path storagePath = Paths.get(sourcePath); + if (source.isFile()) { + storagePath = getStoragePathForFile(source); + } + if (storagePath == null || storagePath.toAbsolutePath() == null) { + throw new IOException("Invalid source storage path for: " + sourcePath); + } + final IncrementalStorage storage = openStorage(storagePath.toAbsolutePath().toString()); + if (storage == null) { + throw new IOException("Failed to retrieve storage from Incremental Service."); + } + if (source.isFile()) { + renameFile(storage, storagePath, source, dest); + } else { + renameDir(storage, storagePath, source, dest); + } + } + + private void renameFile(IncrementalStorage storage, Path storagePath, + File source, File dest) throws IOException { + Path sourcePath = source.toPath(); + Path destPath = dest.toPath(); + if (!sourcePath.startsWith(storagePath)) { + throw new IOException("Path: " + source.getAbsolutePath() + " is not on storage at: " + + storagePath.toString()); + } + if (!destPath.startsWith(storagePath)) { + throw new IOException("Path: " + dest.getAbsolutePath() + " is not on storage at: " + + storagePath.toString()); + } + final Path sourceRelativePath = storagePath.relativize(sourcePath); + final Path destRelativePath = storagePath.relativize(destPath); + storage.moveFile(sourceRelativePath.toString(), destRelativePath.toString()); + + } + + private void renameDir(IncrementalStorage storage, Path storagePath, + File source, File dest) throws IOException { + Path destPath = dest.toPath(); + boolean usedMkdir = false; + try { + Os.mkdir(dest.getAbsolutePath(), 0755); + usedMkdir = true; + } catch (ErrnoException e) { + // Traditional mkdir fails but maybe we can create it on Incremental File System if + // the dest path is on the same Incremental storage as the source. + if (destPath.startsWith(storagePath)) { + storage.makeDirectories(storagePath.relativize(destPath).toString()); + } else { + throw new IOException("Failed to create directory: " + dest.getAbsolutePath(), e); + } + } + try { + storage.moveDir(source.getAbsolutePath(), dest.getAbsolutePath()); + } catch (Exception ex) { + if (usedMkdir) { + try { + Os.remove(dest.getAbsolutePath()); + } catch (ErrnoException ignored) { + } + } + throw new IOException( + "Failed to move " + source.getAbsolutePath() + " to " + dest.getAbsolutePath()); + } + } + + /** + * Closes a storage specified by the absolute path. If the path is not Incremental, do nothing. + * Unbinds the target dir and deletes the corresponding storage instance. + */ + public void closeStorage(@NonNull String path) { + try { + final int id = mService.openStorage(path); + if (id < 0) { + return; + } + mService.deleteStorage(id); + synchronized (mStorages) { + mStorages.remove(id); + } + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Checks if path is mounted on Incremental File System. + */ + public static boolean isIncrementalPath(@NonNull String path) { + // TODO(b/136132412): implement native method + return false; + } +} diff --git a/core/java/android/os/incremental/IncrementalStorage.java b/core/java/android/os/incremental/IncrementalStorage.java new file mode 100644 index 000000000000..2bf89ed7f7e8 --- /dev/null +++ b/core/java/android/os/incremental/IncrementalStorage.java @@ -0,0 +1,346 @@ +/* + * 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.NonNull; +import android.annotation.Nullable; +import android.os.RemoteException; + +import java.io.File; +import java.io.IOException; + +/** + * Provides operations on an Incremental File System directory, using IncrementalService. Example + * usage: + * + * <blockquote><pre> + * IncrementalManager manager = (IncrementalManager) getSystemService(Context.INCREMENTAL_MANAGER); + * IncrementalStorage storage = manager.openStorage("/path/to/incremental/dir"); + * storage.makeDirectory("subdir"); + * </pre></blockquote> + * + * @hide + */ +public final class IncrementalStorage { + private static final String TAG = "IncrementalStorage"; + private final int mId; + private final IIncrementalManager mService; + + + public IncrementalStorage(@NonNull IIncrementalManager is, int id) { + mService = is; + mId = id; + } + + public int getId() { + return mId; + } + + /** + * Temporarily bind-mounts the current storage directory to a target directory. The bind-mount + * will NOT be preserved between device reboots. + * + * @param targetPath Absolute path to the target directory. + */ + public void bind(@NonNull String targetPath) throws IOException { + bind("", targetPath); + } + + /** + * Temporarily bind-mounts a subdir under the current storage directory to a target directory. + * The bind-mount will NOT be preserved between device reboots. + * + * @param sourcePathUnderStorage Source path as a relative path under current storage + * directory. + * @param targetPath Absolute path to the target directory. + */ + public void bind(@NonNull String sourcePathUnderStorage, @NonNull String targetPath) + throws IOException { + try { + int res = mService.makeBindMount(mId, sourcePathUnderStorage, targetPath, + IIncrementalManager.BIND_TEMPORARY); + if (res < 0) { + throw new IOException("bind() failed with errno " + -res); + } + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + + + /** + * Permanently bind-mounts the current storage directory to a target directory. The bind-mount + * WILL be preserved between device reboots. + * + * @param targetPath Absolute path to the target directory. + */ + public void bindPermanent(@NonNull String targetPath) throws IOException { + bindPermanent("", targetPath); + } + + /** + * Permanently bind-mounts a subdir under the current storage directory to a target directory. + * The bind-mount WILL be preserved between device reboots. + * + * @param sourcePathUnderStorage Relative path under the current storage directory. + * @param targetPath Absolute path to the target directory. + */ + public void bindPermanent(@NonNull String sourcePathUnderStorage, @NonNull String targetPath) + throws IOException { + try { + int res = mService.makeBindMount(mId, sourcePathUnderStorage, targetPath, + IIncrementalManager.BIND_PERMANENT); + if (res < 0) { + throw new IOException("bind() permanent failed with errno " + -res); + } + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + + /** + * Unbinds a bind mount. + * + * @param targetPath Absolute path to the target directory. + */ + public void unBind(@NonNull String targetPath) throws IOException { + try { + int res = mService.deleteBindMount(mId, targetPath); + if (res < 0) { + throw new IOException("unbind() failed with errno " + -res); + } + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + + /** + * Creates a sub-directory under the current storage directory. + * + * @param pathUnderStorage Relative path of the sub-directory, e.g., "subdir" + */ + public void makeDirectory(@NonNull String pathUnderStorage) throws IOException { + try { + int res = mService.makeDirectory(mId, pathUnderStorage); + if (res < 0) { + throw new IOException("makeDirectory() failed with errno " + -res); + } + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + + /** + * Creates a sub-directory under the current storage directory. If its parent dirs do not exist, + * create the parent dirs as well. + * + * @param pathUnderStorage Relative path of the sub-directory, e.g., "subdir/subsubdir" + */ + public void makeDirectories(@NonNull String pathUnderStorage) throws IOException { + try { + int res = mService.makeDirectories(mId, pathUnderStorage); + if (res < 0) { + throw new IOException("makeDirectory() failed with errno " + -res); + } + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + + /** + * Creates a file under the current storage directory. + * + * @param pathUnderStorage Relative path of the new file. + * @param size Size of the new file in bytes. + * @param metadata Metadata bytes. + */ + public void makeFile(@NonNull String pathUnderStorage, long size, + @Nullable byte[] metadata) throws IOException { + try { + int res = mService.makeFile(mId, pathUnderStorage, size, metadata); + if (res < 0) { + throw new IOException("makeFile() failed with errno " + -res); + } + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + + /** + * Creates a file in Incremental storage. The content of the file is mapped from a range inside + * a source file in the same storage. + * + * @param destRelativePath Target relative path under storage. + * @param sourceRelativePath Source relative path under storage. + * @param rangeStart Starting offset (in bytes) in the source file. + * @param rangeEnd Ending offset (in bytes) in the source file. + */ + public void makeFileFromRange(@NonNull String destRelativePath, + @NonNull String sourceRelativePath, long rangeStart, long rangeEnd) throws IOException { + try { + int res = mService.makeFileFromRange(mId, destRelativePath, sourceRelativePath, + rangeStart, rangeEnd); + if (res < 0) { + throw new IOException("makeFileFromRange() failed, errno " + -res); + } + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + + /** + * Creates a hard-link between two paths, which can be under different storages but in the same + * Incremental File System. + * + * @param sourcePathUnderStorage The relative path of the source. + * @param destStorage The target storage of the link target. + * @param destPathUnderStorage The relative path of the target. + */ + public void makeLink(@NonNull String sourcePathUnderStorage, IncrementalStorage destStorage, + @NonNull String destPathUnderStorage) throws IOException { + try { + int res = mService.makeLink(mId, sourcePathUnderStorage, destStorage.getId(), + destPathUnderStorage); + if (res < 0) { + throw new IOException("makeLink() failed with errno " + -res); + } + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + + /** + * Deletes a hard-link under the current storage directory. + * + * @param pathUnderStorage The relative path of the target. + */ + public void unlink(@NonNull String pathUnderStorage) throws IOException { + try { + int res = mService.unlink(mId, pathUnderStorage); + if (res < 0) { + throw new IOException("unlink() failed with errno " + -res); + } + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + + /** + * Rename an old file name to a new file name under the current storage directory. + * + * @param sourcePathUnderStorage Old file path as a relative path to the storage directory. + * @param destPathUnderStorage New file path as a relative path to the storage directory. + */ + public void moveFile(@NonNull String sourcePathUnderStorage, + @NonNull String destPathUnderStorage) throws IOException { + try { + int res = mService.makeLink(mId, sourcePathUnderStorage, mId, destPathUnderStorage); + if (res < 0) { + throw new IOException("moveFile() failed at makeLink(), errno " + -res); + } + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + try { + mService.unlink(mId, sourcePathUnderStorage); + } catch (RemoteException ignored) { + } + } + + /** + * Move a directory, which is bind-mounted to a given storage, to a new location. The bind mount + * will be persistent between reboots. + * + * @param sourcePath The old path of the directory as an absolute path. + * @param destPath The new path of the directory as an absolute path, expected to already + * exist. + */ + public void moveDir(@NonNull String sourcePath, @NonNull String destPath) throws IOException { + if (!new File(destPath).exists()) { + throw new IOException("moveDir() requires that destination dir already exists."); + } + try { + int res = mService.makeBindMount(mId, "", destPath, IIncrementalManager.BIND_PERMANENT); + if (res < 0) { + throw new IOException("moveDir() failed at making bind mount, errno " + -res); + } + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + try { + mService.deleteBindMount(mId, sourcePath); + } catch (RemoteException ignored) { + } + } + + /** + * Checks whether a file under the current storage directory is fully loaded. + * + * @param pathUnderStorage The relative path of the file. + * @return True if the file is fully loaded. + */ + public boolean isFileFullyLoaded(@NonNull String pathUnderStorage) { + return isFileRangeLoaded(pathUnderStorage, 0, -1); + } + + /** + * Checks whether a range in a file if loaded. + * + * @param pathUnderStorage The relative path of the file. + * @param start The starting offset of the range. + * @param end The ending offset of the range. + * @return True if the file is fully loaded. + */ + public boolean isFileRangeLoaded(@NonNull String pathUnderStorage, long start, long end) { + try { + return mService.isFileRangeLoaded(mId, pathUnderStorage, start, end); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + return false; + } + } + + /** + * Returns the metadata object of an IncFs File. + * + * @param pathUnderStorage The relative path of the file. + * @return Byte array that contains metadata bytes. + */ + @Nullable + public byte[] getFileMetadata(@NonNull String pathUnderStorage) { + try { + return mService.getFileMetadata(mId, pathUnderStorage); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + return null; + } + } + + /** + * Informs the data loader service associated with the current storage to start data loader + * + * @return True if data loader is successfully started. + */ + public boolean startLoading() { + try { + return mService.startLoading(mId); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + return false; + } + } +} diff --git a/core/java/com/android/internal/content/NativeLibraryHelper.java b/core/java/com/android/internal/content/NativeLibraryHelper.java index fee8345d1660..0847fbdd2291 100644 --- a/core/java/com/android/internal/content/NativeLibraryHelper.java +++ b/core/java/com/android/internal/content/NativeLibraryHelper.java @@ -33,7 +33,6 @@ import android.content.pm.PackageParser.PackageLite; import android.content.pm.PackageParser.PackageParserException; import android.os.Build; import android.os.SELinux; -import android.os.SystemProperties; import android.system.ErrnoException; import android.system.Os; import android.util.Slog; @@ -444,6 +443,24 @@ public class NativeLibraryHelper { return sum; } + /** + * Configure the native library files managed by Incremental Service. Makes sure Incremental + * Service will create native library directories and set up native library binary files in the + * same structure as they are in non-incremental installations. + * + * @param pkg The package to be installed, including all the APK files. + * @param handle The pointer to an zip archive. + * @param libraryRoot The root directory of the native library files, e.g., lib/ + * @param abiList The list of ABIs that are supported by the current device. + * @param useIsaSubdir Whether or not to set up a sub dir for the ISA. + * @return ABI code if installation succeeds or error code if installation fails. + */ + public static int configureNativeBinariesForSupportedAbi(Package pkg, Handle handle, + File libraryRoot, String[] abiList, boolean useIsaSubdir) { + // TODO(b/136132412): Implement this. + return -1; + } + // We don't care about the other return values for now. private static final int BITCODE_PRESENT = 1; diff --git a/services/core/java/com/android/server/pm/PackageAbiHelperImpl.java b/services/core/java/com/android/server/pm/PackageAbiHelperImpl.java index 259200b92597..7e478009b6b3 100644 --- a/services/core/java/com/android/server/pm/PackageAbiHelperImpl.java +++ b/services/core/java/com/android/server/pm/PackageAbiHelperImpl.java @@ -19,6 +19,7 @@ package com.android.server.pm; import static android.content.pm.PackageManager.INSTALL_FAILED_INTERNAL_ERROR; import static android.content.pm.PackageParser.isApkFile; import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER; +import static android.os.incremental.IncrementalManager.isIncrementalPath; import static com.android.internal.content.NativeLibraryHelper.LIB64_DIR_NAME; import static com.android.internal.content.NativeLibraryHelper.LIB_DIR_NAME; @@ -309,6 +310,7 @@ final class PackageAbiHelperImpl implements PackageAbiHelper { final String nativeLibraryRootStr = initialLibraryPaths.nativeLibraryRootDir; final boolean useIsaSpecificSubdirs = initialLibraryPaths.nativeLibraryRootRequiresIsa; + final boolean onIncremental = isIncrementalPath(pkg.codePath); String primaryCpuAbi = null; String secondaryCpuAbi = null; @@ -341,10 +343,18 @@ final class PackageAbiHelperImpl implements PackageAbiHelper { int abi64 = PackageManager.NO_NATIVE_LIBRARIES; if (Build.SUPPORTED_32_BIT_ABIS.length > 0) { if (extractLibs) { - Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "copyNativeBinaries"); - abi32 = NativeLibraryHelper.copyNativeBinariesForSupportedAbi(handle, - nativeLibraryRoot, Build.SUPPORTED_32_BIT_ABIS, - useIsaSpecificSubdirs); + if (onIncremental) { + Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, + "incrementalNativeBinaries"); + abi32 = NativeLibraryHelper.configureNativeBinariesForSupportedAbi(pkg, + handle, nativeLibraryRoot, Build.SUPPORTED_32_BIT_ABIS, + useIsaSpecificSubdirs); + } else { + Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "copyNativeBinaries"); + abi32 = NativeLibraryHelper.copyNativeBinariesForSupportedAbi(handle, + nativeLibraryRoot, Build.SUPPORTED_32_BIT_ABIS, + useIsaSpecificSubdirs); + } } else { Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "findSupportedAbi"); abi32 = NativeLibraryHelper.findSupportedAbi( @@ -364,10 +374,18 @@ final class PackageAbiHelperImpl implements PackageAbiHelper { if (Build.SUPPORTED_64_BIT_ABIS.length > 0) { if (extractLibs) { - Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "copyNativeBinaries"); - abi64 = NativeLibraryHelper.copyNativeBinariesForSupportedAbi(handle, - nativeLibraryRoot, Build.SUPPORTED_64_BIT_ABIS, - useIsaSpecificSubdirs); + if (onIncremental) { + Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, + "incrementalNativeBinaries"); + abi64 = NativeLibraryHelper.configureNativeBinariesForSupportedAbi(pkg, + handle, nativeLibraryRoot, Build.SUPPORTED_64_BIT_ABIS, + useIsaSpecificSubdirs); + } else { + Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "copyNativeBinaries"); + abi64 = NativeLibraryHelper.copyNativeBinariesForSupportedAbi(handle, + nativeLibraryRoot, Build.SUPPORTED_64_BIT_ABIS, + useIsaSpecificSubdirs); + } } else { Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "findSupportedAbi"); abi64 = NativeLibraryHelper.findSupportedAbi( @@ -418,9 +436,15 @@ final class PackageAbiHelperImpl implements PackageAbiHelper { final int copyRet; if (extractLibs) { - Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "copyNativeBinaries"); - copyRet = NativeLibraryHelper.copyNativeBinariesForSupportedAbi(handle, - nativeLibraryRoot, abiList, useIsaSpecificSubdirs); + if (onIncremental) { + Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "incrementalNativeBinaries"); + copyRet = NativeLibraryHelper.configureNativeBinariesForSupportedAbi(pkg, + handle, nativeLibraryRoot, abiList, useIsaSpecificSubdirs); + } else { + Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "copyNativeBinaries"); + copyRet = NativeLibraryHelper.copyNativeBinariesForSupportedAbi(handle, + nativeLibraryRoot, abiList, useIsaSpecificSubdirs); + } } else { Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "findSupportedAbi"); copyRet = NativeLibraryHelper.findSupportedAbi(handle, abiList); diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 0e075b117b81..b7b54e9cbb26 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -87,6 +87,7 @@ import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.content.pm.PackageManager.RESTRICTION_NONE; import static android.content.pm.PackageParser.isApkFile; import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER; +import static android.os.incremental.IncrementalManager.isIncrementalPath; import static android.os.storage.StorageManager.FLAG_STORAGE_CE; import static android.os.storage.StorageManager.FLAG_STORAGE_DE; import static android.os.storage.StorageManager.FLAG_STORAGE_EXTERNAL; @@ -226,6 +227,7 @@ import android.os.Trace; import android.os.UserHandle; import android.os.UserManager; import android.os.UserManagerInternal; +import android.os.incremental.IncrementalManager; import android.os.storage.DiskInfo; import android.os.storage.IStorageManager; import android.os.storage.StorageEventListener; @@ -1074,6 +1076,8 @@ public class PackageManagerService extends IPackageManager.Stub private Future<?> mPrepareAppDataFuture; + private final IncrementalManager mIncrementalManager; + private static class IFVerificationParams { PackageParser.Package pkg; boolean replacing; @@ -2509,6 +2513,8 @@ public class PackageManagerService extends IPackageManager.Stub mPermissionManager = injector.getPermissionManagerServiceInternal(); mSettings = injector.getSettings(); mPermissionManagerService = (IPermissionManager) ServiceManager.getService("permissionmgr"); + mIncrementalManager = + (IncrementalManager) mContext.getSystemService(Context.INCREMENTAL_SERVICE); // CHECKSTYLE:ON IndentationCheck t.traceEnd(); @@ -8798,6 +8804,7 @@ public class PackageManagerService extends IPackageManager.Stub // Full APK verification can be skipped during certificate collection, only if the file is // in verified partition, or can be verified on access (when apk verity is enabled). In both // cases, only data in Signing Block is verified instead of the whole file. + // TODO(b/136132412): skip for Incremental installation final boolean skipVerify = scanSystemPartition || (forceCollect && canSkipForcedPackageVerification(pkg)); collectCertificatesLI(pkgSetting, pkg, forceCollect, skipVerify); @@ -14675,9 +14682,16 @@ public class PackageManagerService extends IPackageManager.Stub final File afterCodeFile = getNextCodePath(targetDir, pkg.packageName); if (DEBUG_INSTALL) Slog.d(TAG, "Renaming " + beforeCodeFile + " to " + afterCodeFile); + final boolean onIncremental = mIncrementalManager != null + && isIncrementalPath(beforeCodeFile.getAbsolutePath()); try { - Os.rename(beforeCodeFile.getAbsolutePath(), afterCodeFile.getAbsolutePath()); - } catch (ErrnoException e) { + if (onIncremental) { + mIncrementalManager.rename(beforeCodeFile.getAbsolutePath(), + afterCodeFile.getAbsolutePath()); + } else { + Os.rename(beforeCodeFile.getAbsolutePath(), afterCodeFile.getAbsolutePath()); + } + } catch (IOException | ErrnoException e) { Slog.w(TAG, "Failed to rename", e); return false; } @@ -14737,6 +14751,11 @@ public class PackageManagerService extends IPackageManager.Stub return false; } + String codePath = codeFile.getAbsolutePath(); + if (mIncrementalManager != null && isIncrementalPath(codePath)) { + mIncrementalManager.closeStorage(codePath); + } + removeCodePathLI(codeFile); if (resourceFile != null && !FileUtils.contains(codeFile, resourceFile)) { @@ -15928,6 +15947,8 @@ public class PackageManagerService extends IPackageManager.Stub & PackageManagerService.SCAN_AS_INSTANT_APP) != 0); final PackageParser.Package pkg = reconciledPkg.pkgSetting.pkg; final String packageName = pkg.packageName; + final boolean onIncremental = mIncrementalManager != null + && isIncrementalPath(pkg.codePath); prepareAppDataAfterInstallLIF(pkg); if (reconciledPkg.prepareResult.clearCodeCache) { clearAppDataLIF(pkg, UserHandle.USER_ALL, FLAG_STORAGE_DE | FLAG_STORAGE_CE @@ -15958,6 +15979,7 @@ public class PackageManagerService extends IPackageManager.Stub // We only need to dexopt if the package meets ALL of the following conditions: // 1) it is not an instant app or if it is then dexopt is enabled via gservices. // 2) it is not debuggable. + // 3) it is not on Incremental File System. // // Note that we do not dexopt instant apps by default. dexopt can take some time to // complete, so we skip this step during installation. Instead, we'll take extra time @@ -15968,7 +15990,8 @@ public class PackageManagerService extends IPackageManager.Stub final boolean performDexopt = (!instantApp || Global.getInt(mContext.getContentResolver(), Global.INSTANT_APP_DEXOPT_ENABLED, 0) != 0) - && ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) == 0); + && ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) == 0) + && (!onIncremental); if (performDexopt) { // Compile the layout resources. @@ -16200,6 +16223,7 @@ public class PackageManagerService extends IPackageManager.Stub if (args.signingDetails != PackageParser.SigningDetails.UNKNOWN) { pkg.setSigningDetails(args.signingDetails); } else { + // TODO(b/136132412): skip for Incremental installation PackageParser.collectCertificates(pkg, false /* skipVerify */); } } catch (PackageParserException e) { |