diff options
| author | Jaegeuk Kim <jaegeuk@google.com> | 2021-06-03 02:16:47 +0000 |
|---|---|---|
| committer | Jaegeuk Kim <jaegeuk@google.com> | 2021-06-03 02:18:11 +0000 |
| commit | a3090d41fe5dd114f2c77dfd5eafbf36d737204d (patch) | |
| tree | 56a61c4a9524264bdb27ec92f4d6f97b3d9cc6eb /core/java | |
| parent | 516cb2d025a8c5dc67f4a926bdbb79c0e3725b4c (diff) | |
Revert "Revert "Release cblocks back to the free pool""
This reverts commit 516cb2d025a8c5dc67f4a926bdbb79c0e3725b4c.
Reason for revert: Add back after merging f2fs/f2fs-tools changes removing immutable bit stuffs
Bug: 188928405
Change-Id: I6a6fcde1190bff14abe1a67a75b8e7be95d89072
Diffstat (limited to 'core/java')
| -rw-r--r-- | core/java/android/provider/Settings.java | 14 | ||||
| -rw-r--r-- | core/java/com/android/internal/content/F2fsUtils.java | 296 | ||||
| -rw-r--r-- | core/java/com/android/internal/content/OWNERS | 5 |
3 files changed, 315 insertions, 0 deletions
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 5cfb665745a8..3116b2612c67 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -6624,6 +6624,20 @@ public final class Settings { public static final String COMPLETED_CATEGORY_PREFIX = "suggested.completed_category."; /** + * Whether or not compress blocks should be released on install. + * <p>The setting only determines if the platform will attempt to release + * compress blocks; it does not guarantee that the files will have their + * compress blocks released. Compression is currently only supported on + * some f2fs filesystems. + * <p> + * Type: int (0 for false, 1 for true) + * + * @hide + */ + public static final String RELEASE_COMPRESS_BLOCKS_ON_INSTALL = + "release_compress_blocks_on_install"; + + /** * List of input methods that are currently enabled. This is a string * containing the IDs of all enabled input methods, each ID separated * by ':'. diff --git a/core/java/com/android/internal/content/F2fsUtils.java b/core/java/com/android/internal/content/F2fsUtils.java new file mode 100644 index 000000000000..27f1b308ed9c --- /dev/null +++ b/core/java/com/android/internal/content/F2fsUtils.java @@ -0,0 +1,296 @@ +/* + * Copyright (C) 2021 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 com.android.internal.content; + +import android.annotation.NonNull; +import android.content.ContentResolver; +import android.os.Environment; +import android.os.incremental.IncrementalManager; +import android.provider.Settings.Secure; +import android.text.TextUtils; +import android.util.Slog; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.List; + +/** + * Utility methods to work with the f2fs file system. + */ +public final class F2fsUtils { + private static final String TAG = "F2fsUtils"; + private static final boolean DEBUG_F2FS = false; + + /** Directory containing kernel features */ + private static final File sKernelFeatures = + new File("/sys/fs/f2fs/features"); + /** File containing features enabled on "/data" */ + private static final File sUserDataFeatures = + new File("/dev/sys/fs/by-name/userdata/features"); + private static final File sDataDirectory = Environment.getDataDirectory(); + /** Name of the compression feature */ + private static final String COMPRESSION_FEATURE = "compression"; + + private static final boolean sKernelCompressionAvailable; + private static final boolean sUserDataCompressionAvailable; + + static { + sKernelCompressionAvailable = isCompressionEnabledInKernel(); + if (!sKernelCompressionAvailable) { + if (DEBUG_F2FS) { + Slog.d(TAG, "f2fs compression DISABLED; feature not part of the kernel"); + } + } + sUserDataCompressionAvailable = isCompressionEnabledOnUserData(); + if (!sUserDataCompressionAvailable) { + if (DEBUG_F2FS) { + Slog.d(TAG, "f2fs compression DISABLED; feature not enabled on filesystem"); + } + } + } + + /** + * Releases compressed blocks from eligible installation artifacts. + * <p> + * Modern f2fs implementations starting in {@code S} support compression + * natively within the file system. The data blocks of specific installation + * artifacts [eg. .apk, .so, ...] can be compressed at the file system level, + * making them look and act like any other uncompressed file, but consuming + * a fraction of the space. + * <p> + * However, the unused space is not free'd automatically. Instead, we must + * manually tell the file system to release the extra blocks [the delta between + * the compressed and uncompressed block counts] back to the free pool. + * <p> + * Because of how compression works within the file system, once the blocks + * have been released, the file becomes read-only and cannot be modified until + * the free'd blocks have again been reserved from the free pool. + */ + public static void releaseCompressedBlocks(ContentResolver resolver, File file) { + if (!sKernelCompressionAvailable || !sUserDataCompressionAvailable) { + return; + } + + // NOTE: Retrieving this setting means we need to delay releasing cblocks + // of any APKs installed during the PackageManagerService constructor. Instead + // of being able to release them in the constructor, they can only be released + // immediately prior to the system being available. When we no longer need to + // read this setting, move cblock release back to the package manager constructor. + final boolean releaseCompressBlocks = + Secure.getInt(resolver, Secure.RELEASE_COMPRESS_BLOCKS_ON_INSTALL, 1) != 0; + if (!releaseCompressBlocks) { + if (DEBUG_F2FS) { + Slog.d(TAG, "SKIP; release compress blocks not enabled"); + } + return; + } + if (!isCompressionAllowed(file)) { + if (DEBUG_F2FS) { + Slog.d(TAG, "SKIP; compression not allowed"); + } + return; + } + final File[] files = getFilesToRelease(file); + if (files == null || files.length == 0) { + if (DEBUG_F2FS) { + Slog.d(TAG, "SKIP; no files to compress"); + } + return; + } + for (int i = files.length - 1; i >= 0; --i) { + final long releasedBlocks = nativeReleaseCompressedBlocks(files[i].getAbsolutePath()); + if (DEBUG_F2FS) { + Slog.d(TAG, "RELEASED " + releasedBlocks + " blocks" + + " from \"" + files[i] + "\""); + } + } + } + + /** + * Returns {@code true} if compression is allowed on the file system containing + * the given file. + * <p> + * NOTE: The return value does not mean if the given file, or any other file + * on the same file system, is actually compressed. It merely determines whether + * not files <em>may</em> be compressed. + */ + private static boolean isCompressionAllowed(@NonNull File file) { + final String filePath; + try { + filePath = file.getCanonicalPath(); + } catch (IOException e) { + if (DEBUG_F2FS) { + Slog.d(TAG, "f2fs compression DISABLED; could not determine path"); + } + return false; + } + if (IncrementalManager.isIncrementalPath(filePath)) { + if (DEBUG_F2FS) { + Slog.d(TAG, "f2fs compression DISABLED; file on incremental fs"); + } + return false; + } + if (!isChild(sDataDirectory, filePath)) { + if (DEBUG_F2FS) { + Slog.d(TAG, "f2fs compression DISABLED; file not on /data"); + } + return false; + } + if (DEBUG_F2FS) { + Slog.d(TAG, "f2fs compression ENABLED"); + } + return true; + } + + /** + * Returns {@code true} if the given child is a descendant of the base. + */ + private static boolean isChild(@NonNull File base, @NonNull String childPath) { + try { + base = base.getCanonicalFile(); + + File parentFile = new File(childPath).getCanonicalFile(); + while (parentFile != null) { + if (base.equals(parentFile)) { + return true; + } + parentFile = parentFile.getParentFile(); + } + return false; + } catch (IOException ignore) { + return false; + } + } + + /** + * Returns whether or not the compression feature is enabled in the kernel. + * <p> + * NOTE: This doesn't mean compression is enabled on a particular file system + * or any files have been compressed. Only that the functionality is enabled + * on the device. + */ + private static boolean isCompressionEnabledInKernel() { + final File[] features = sKernelFeatures.listFiles(); + if (features == null || features.length == 0) { + if (DEBUG_F2FS) { + Slog.d(TAG, "ERROR; no kernel features"); + } + return false; + } + for (int i = features.length - 1; i >= 0; --i) { + final File feature = features[i]; + if (COMPRESSION_FEATURE.equals(features[i].getName())) { + if (DEBUG_F2FS) { + Slog.d(TAG, "FOUND kernel compression feature"); + } + return true; + } + } + if (DEBUG_F2FS) { + Slog.d(TAG, "ERROR; kernel compression feature not found"); + } + return false; + } + + /** + * Returns whether or not the compression feature is enabled on user data [ie. "/data"]. + * <p> + * NOTE: This doesn't mean any files have been compressed. Only that the functionality + * is enabled on the file system. + */ + private static boolean isCompressionEnabledOnUserData() { + if (!sUserDataFeatures.exists() + || !sUserDataFeatures.isFile() + || !sUserDataFeatures.canRead()) { + if (DEBUG_F2FS) { + Slog.d(TAG, "ERROR; filesystem features not available"); + } + return false; + } + final List<String> configLines; + try { + configLines = Files.readAllLines(sUserDataFeatures.toPath()); + } catch (IOException ignore) { + if (DEBUG_F2FS) { + Slog.d(TAG, "ERROR; couldn't read filesystem features"); + } + return false; + } + if (configLines == null + || configLines.size() > 1 + || TextUtils.isEmpty(configLines.get(0))) { + if (DEBUG_F2FS) { + Slog.d(TAG, "ERROR; no filesystem features"); + } + return false; + } + final String[] features = configLines.get(0).split(","); + for (int i = features.length - 1; i >= 0; --i) { + if (COMPRESSION_FEATURE.equals(features[i].trim())) { + if (DEBUG_F2FS) { + Slog.d(TAG, "FOUND filesystem compression feature"); + } + return true; + } + } + if (DEBUG_F2FS) { + Slog.d(TAG, "ERROR; filesystem compression feature not found"); + } + return false; + } + + /** + * Returns all files contained within the directory at any depth from the given path. + */ + private static List<File> getFilesRecursive(@NonNull File path) { + final File[] allFiles = path.listFiles(); + if (allFiles == null) { + return null; + } + final ArrayList<File> files = new ArrayList<>(); + for (File f : allFiles) { + if (f.isDirectory()) { + files.addAll(getFilesRecursive(f)); + } else if (f.isFile()) { + files.add(f); + } + } + return files; + } + + /** + * Returns all files contained within the directory at any depth from the given path. + */ + private static File[] getFilesToRelease(@NonNull File codePath) { + final List<File> files = getFilesRecursive(codePath); + if (files == null) { + if (codePath.isFile()) { + return new File[] { codePath }; + } + return null; + } + if (files.size() == 0) { + return null; + } + return files.toArray(new File[files.size()]); + } + + private static native long nativeReleaseCompressedBlocks(String path); + +} diff --git a/core/java/com/android/internal/content/OWNERS b/core/java/com/android/internal/content/OWNERS new file mode 100644 index 000000000000..c42bee69410d --- /dev/null +++ b/core/java/com/android/internal/content/OWNERS @@ -0,0 +1,5 @@ +# Bug component: 36137 +include /core/java/android/content/pm/OWNERS + +per-file ReferrerIntent.aidl = file:/services/core/java/com/android/server/am/OWNERS +per-file ReferrerIntent.java = file:/services/core/java/com/android/server/am/OWNERS |
