Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 299ea3e3 authored by Jeff Sharkey's avatar Jeff Sharkey
Browse files

`FileUtils` and `AtomicFile` under Ravenwood.

These are common utility classes used by framework code, so support
them and their relevant tests.

Since they depend on ErrnoException and IoUtils, bring over limited
versions of those classes into `libcore-fake`, and compile them into
the Ravenwood runtime using variant washing.

Bug: 292141694
Test: atest FrameworksCoreTestsRavenwood FrameworksUtilTestsRavenwood
Change-Id: Ib44dedacfa4d12a7d697973caf070477ed07202d
parent de344710
Loading
Loading
Loading
Loading
+46 −9
Original line number Diff line number Diff line
@@ -52,9 +52,11 @@ import android.provider.DocumentsContract.Document;
import android.provider.MediaStore;
import android.system.ErrnoException;
import android.system.Os;
import android.system.OsConstants;
import android.system.StructStat;
import android.text.TextUtils;
import android.util.DataUnit;
import android.util.EmptyArray;
import android.util.Log;
import android.util.Slog;
import android.webkit.MimeTypeMap;
@@ -64,7 +66,6 @@ import com.android.internal.util.ArrayUtils;
import com.android.internal.util.SizedInputStream;

import libcore.io.IoUtils;
import libcore.util.EmptyArray;

import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
@@ -94,6 +95,7 @@ import java.util.zip.CheckedInputStream;
/**
 * Utility methods useful for working with files.
 */
@android.ravenwood.annotation.RavenwoodKeepWholeClass
public final class FileUtils {
    private static final String TAG = "FileUtils";

@@ -116,9 +118,6 @@ public final class FileUtils {
    private FileUtils() {
    }

    private static final String CAMERA_DIR_LOWER_CASE = "/storage/emulated/" + UserHandle.myUserId()
            + "/dcim/camera";

    /** Regular expression for safe filenames: no spaces or metacharacters.
      *
      * Use a preload holder so that FileUtils can be compile-time initialized.
@@ -133,6 +132,21 @@ public final class FileUtils {

    private static final long COPY_CHECKPOINT_BYTES = 524288;

    static {
        sEnableCopyOptimizations = shouldEnableCopyOptimizations();
    }

    @android.ravenwood.annotation.RavenwoodReplace
    private static boolean shouldEnableCopyOptimizations() {
        // Advanced copy operations enabled by default
        return true;
    }

    private static boolean shouldEnableCopyOptimizations$ravenwood() {
        // Disabled under Ravenwood due to missing kernel support
        return false;
    }

    /**
     * Listener that is called periodically as progress is made.
     */
@@ -150,6 +164,7 @@ public final class FileUtils {
     * @hide
     */
    @UnsupportedAppUsage
    @android.ravenwood.annotation.RavenwoodThrow(reason = "Requires kernel support")
    public static int setPermissions(File path, int mode, int uid, int gid) {
        return setPermissions(path.getAbsolutePath(), mode, uid, gid);
    }
@@ -164,6 +179,7 @@ public final class FileUtils {
     * @hide
     */
    @UnsupportedAppUsage
    @android.ravenwood.annotation.RavenwoodThrow(reason = "Requires kernel support")
    public static int setPermissions(String path, int mode, int uid, int gid) {
        try {
            Os.chmod(path, mode);
@@ -194,6 +210,7 @@ public final class FileUtils {
     * @hide
     */
    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    @android.ravenwood.annotation.RavenwoodThrow(reason = "Requires kernel support")
    public static int setPermissions(FileDescriptor fd, int mode, int uid, int gid) {
        try {
            Os.fchmod(fd, mode);
@@ -221,6 +238,7 @@ public final class FileUtils {
     * @param to File where attributes should be copied to.
     * @hide
     */
    @android.ravenwood.annotation.RavenwoodThrow(reason = "Requires kernel support")
    public static void copyPermissions(@NonNull File from, @NonNull File to) throws IOException {
        try {
            final StructStat stat = Os.stat(from.getAbsolutePath());
@@ -236,6 +254,7 @@ public final class FileUtils {
     * @hide
     */
    @Deprecated
    @android.ravenwood.annotation.RavenwoodThrow(reason = "Requires kernel support")
    public static int getUid(String path) {
        try {
            return Os.stat(path).st_uid;
@@ -314,11 +333,7 @@ public final class FileUtils {
        }
        try (FileOutputStream out = new FileOutputStream(destFile)) {
            copy(in, out);
            try {
                Os.fsync(out.getFD());
            } catch (ErrnoException e) {
                throw e.rethrowAsIOException();
            }
            sync(out);
        }
    }

@@ -475,6 +490,7 @@ public final class FileUtils {
     * @hide
     */
    @VisibleForTesting
    @android.ravenwood.annotation.RavenwoodThrow(reason = "Requires kernel support")
    public static long copyInternalSplice(FileDescriptor in, FileDescriptor out, long count,
            CancellationSignal signal, Executor executor, ProgressListener listener)
            throws ErrnoException {
@@ -516,6 +532,7 @@ public final class FileUtils {
     * @hide
     */
    @VisibleForTesting
    @android.ravenwood.annotation.RavenwoodThrow(reason = "Requires kernel support")
    public static long copyInternalSendfile(FileDescriptor in, FileDescriptor out, long count,
            CancellationSignal signal, Executor executor, ProgressListener listener)
            throws ErrnoException {
@@ -699,6 +716,7 @@ public final class FileUtils {
     *
     * @hide
     */
    @android.ravenwood.annotation.RavenwoodReplace
    public static void bytesToFile(String filename, byte[] content) throws IOException {
        if (filename.startsWith("/proc/")) {
            final int oldMask = StrictMode.allowThreadDiskWritesMask();
@@ -714,6 +732,14 @@ public final class FileUtils {
        }
    }

    /** @hide */
    public static void bytesToFile$ravenwood(String filename, byte[] content) throws IOException {
        // No StrictMode support, so we can just directly write
        try (FileOutputStream fos = new FileOutputStream(filename)) {
            fos.write(content);
        }
    }

    /**
     * Writes string to file. Basically same as "echo -n $string > $filename"
     *
@@ -1176,6 +1202,7 @@ public final class FileUtils {
     *
     * @hide
     */
    @android.ravenwood.annotation.RavenwoodThrow(blockedBy = MimeTypeMap.class)
    public static String[] splitFileName(String mimeType, String displayName) {
        String name;
        String ext;
@@ -1423,11 +1450,13 @@ public final class FileUtils {
     *   indicate a failure to flush bytes to the underlying resource.
     */
    @Deprecated
    @android.ravenwood.annotation.RavenwoodThrow(reason = "Requires ART support")
    public static void closeQuietly(@Nullable FileDescriptor fd) {
        IoUtils.closeQuietly(fd);
    }

    /** {@hide} */
    @android.ravenwood.annotation.RavenwoodThrow(blockedBy = OsConstants.class)
    public static int translateModeStringToPosix(String mode) {
        // Quick check for invalid chars
        for (int i = 0; i < mode.length(); i++) {
@@ -1462,6 +1491,7 @@ public final class FileUtils {
    }

    /** {@hide} */
    @android.ravenwood.annotation.RavenwoodThrow(blockedBy = OsConstants.class)
    public static String translateModePosixToString(int mode) {
        String res = "";
        if ((mode & O_ACCMODE) == O_RDWR) {
@@ -1483,6 +1513,7 @@ public final class FileUtils {
    }

    /** {@hide} */
    @android.ravenwood.annotation.RavenwoodThrow(blockedBy = OsConstants.class)
    public static int translateModePosixToPfd(int mode) {
        int res = 0;
        if ((mode & O_ACCMODE) == O_RDWR) {
@@ -1507,6 +1538,7 @@ public final class FileUtils {
    }

    /** {@hide} */
    @android.ravenwood.annotation.RavenwoodThrow(blockedBy = OsConstants.class)
    public static int translateModePfdToPosix(int mode) {
        int res = 0;
        if ((mode & MODE_READ_WRITE) == MODE_READ_WRITE) {
@@ -1531,6 +1563,7 @@ public final class FileUtils {
    }

    /** {@hide} */
    @android.ravenwood.annotation.RavenwoodThrow(blockedBy = OsConstants.class)
    public static int translateModeAccessToPosix(int mode) {
        if (mode == F_OK) {
            // There's not an exact mapping, so we attempt a read-only open to
@@ -1549,6 +1582,7 @@ public final class FileUtils {

    /** {@hide} */
    @VisibleForTesting
    @android.ravenwood.annotation.RavenwoodThrow(reason = "Requires kernel support")
    public static ParcelFileDescriptor convertToModernFd(FileDescriptor fd) {
        Context context = AppGlobals.getInitialApplication();
        if (UserHandle.getAppId(Process.myUid()) == getMediaProviderAppId(context)) {
@@ -1565,6 +1599,7 @@ public final class FileUtils {
        }
    }

    @android.ravenwood.annotation.RavenwoodThrow(reason = "Requires kernel support")
    private static int getMediaProviderAppId(Context context) {
        if (sMediaProviderAppId != -1) {
            return sMediaProviderAppId;
@@ -1605,10 +1640,12 @@ public final class FileUtils {
            return this;
        }

        @android.ravenwood.annotation.RavenwoodThrow(reason = "Requires kernel support")
        public static MemoryPipe createSource(byte[] data) throws IOException {
            return new MemoryPipe(data, false).startInternal();
        }

        @android.ravenwood.annotation.RavenwoodThrow(reason = "Requires kernel support")
        public static MemoryPipe createSink(byte[] data) throws IOException {
            return new MemoryPipe(data, true).startInternal();
        }
+3 −0
Original line number Diff line number Diff line
@@ -48,6 +48,7 @@ import java.util.function.Consumer;
 * be accessed or modified concurrently by multiple threads or processes. The caller is responsible
 * for ensuring appropriate mutual exclusion invariants whenever it accesses the file.
 */
@android.ravenwood.annotation.RavenwoodKeepWholeClass
public class AtomicFile {
    private static final String LOG_TAG = "AtomicFile";

@@ -68,6 +69,8 @@ public class AtomicFile {
     * @hide Internal constructor that also allows you to have the class
     * automatically log commit events.
     */
    @android.ravenwood.annotation.RavenwoodThrow(blockedBy =
            SystemConfigFileCommitEventLogger.class)
    public AtomicFile(File baseName, String commitTag) {
        this(baseName, new SystemConfigFileCommitEventLogger(commitTag));
    }
+13 −0
Original line number Diff line number Diff line
@@ -66,6 +66,7 @@ android_test {
        "testables",
        "com.android.text.flags-aconfig-java",
        "flag-junit",
        "ravenwood-junit",
    ],

    libs: [
@@ -163,3 +164,15 @@ android_library {
        "framework-res",
    ],
}

android_ravenwood_test {
    name: "FrameworksCoreTestsRavenwood",
    static_libs: [
        "androidx.annotation_annotation",
        "androidx.test.rules",
    ],
    srcs: [
        "src/android/os/FileUtilsTest.java",
    ],
    auto_gen_config: true,
}
+40 −14
Original line number Diff line number Diff line
@@ -51,20 +51,17 @@ import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import android.content.Context;
import android.os.FileUtils.MemoryPipe;
import android.platform.test.annotations.IgnoreUnderRavenwood;
import android.platform.test.ravenwood.RavenwoodRule;
import android.provider.DocumentsContract.Document;
import android.util.DataUnit;

import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;

import com.google.android.collect.Sets;

import libcore.io.Streams;

import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

@@ -74,12 +71,19 @@ import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Random;

@RunWith(AndroidJUnit4.class)
public class FileUtilsTest {
    @Rule
    public final RavenwoodRule mRavenwood = new RavenwoodRule();

    private static final String TEST_DATA =
            "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";

@@ -90,17 +94,13 @@ public class FileUtilsTest {

    private final int[] DATA_SIZES = { 32, 32_000, 32_000_000 };

    private Context getContext() {
        return InstrumentationRegistry.getContext();
    }

    @Before
    public void setUp() throws Exception {
        mDir = getContext().getDir("testing", Context.MODE_PRIVATE);
        mDir = Files.createTempDirectory("FileUtils").toFile();
        mTestFile = new File(mDir, "test.file");
        mCopyFile = new File(mDir, "copy.file");

        mTarget = getContext().getFilesDir();
        mTarget = mDir;
        FileUtils.deleteContents(mTarget);
    }

@@ -152,6 +152,7 @@ public class FileUtilsTest {
    }

    @Test
    @IgnoreUnderRavenwood(blockedBy = MemoryPipe.class)
    public void testCopy_FileToPipe() throws Exception {
        for (int size : DATA_SIZES) {
            final File src = new File(mTarget, "src");
@@ -172,6 +173,7 @@ public class FileUtilsTest {
    }

    @Test
    @IgnoreUnderRavenwood(blockedBy = MemoryPipe.class)
    public void testCopy_PipeToFile() throws Exception {
        for (int size : DATA_SIZES) {
            final File dest = new File(mTarget, "dest");
@@ -191,6 +193,7 @@ public class FileUtilsTest {
    }

    @Test
    @IgnoreUnderRavenwood(blockedBy = MemoryPipe.class)
    public void testCopy_PipeToPipe() throws Exception {
        for (int size : DATA_SIZES) {
            byte[] expected = new byte[size];
@@ -208,6 +211,7 @@ public class FileUtilsTest {
    }

    @Test
    @IgnoreUnderRavenwood(blockedBy = MemoryPipe.class)
    public void testCopy_ShortPipeToFile() throws Exception {
        byte[] source = new byte[33_000_000];
        new Random().nextBytes(source);
@@ -424,6 +428,7 @@ public class FileUtilsTest {
    }

    @Test
    @IgnoreUnderRavenwood(blockedBy = android.webkit.MimeTypeMap.class)
    public void testBuildUniqueFile_normal() throws Exception {
        assertNameEquals("test.jpg", FileUtils.buildUniqueFile(mTarget, "image/jpeg", "test"));
        assertNameEquals("test.jpg", FileUtils.buildUniqueFile(mTarget, "image/jpeg", "test.jpg"));
@@ -443,6 +448,7 @@ public class FileUtilsTest {
    }

    @Test
    @IgnoreUnderRavenwood(blockedBy = android.webkit.MimeTypeMap.class)
    public void testBuildUniqueFile_unknown() throws Exception {
        assertNameEquals("test",
                FileUtils.buildUniqueFile(mTarget, "application/octet-stream", "test"));
@@ -456,6 +462,7 @@ public class FileUtilsTest {
    }

    @Test
    @IgnoreUnderRavenwood(blockedBy = android.webkit.MimeTypeMap.class)
    public void testBuildUniqueFile_dir() throws Exception {
        assertNameEquals("test", FileUtils.buildUniqueFile(mTarget, Document.MIME_TYPE_DIR, "test"));
        new File(mTarget, "test").mkdir();
@@ -470,6 +477,7 @@ public class FileUtilsTest {
    }

    @Test
    @IgnoreUnderRavenwood(blockedBy = android.webkit.MimeTypeMap.class)
    public void testBuildUniqueFile_increment() throws Exception {
        assertNameEquals("test.jpg", FileUtils.buildUniqueFile(mTarget, "image/jpeg", "test.jpg"));
        new File(mTarget, "test.jpg").createNewFile();
@@ -489,6 +497,7 @@ public class FileUtilsTest {
    }

    @Test
    @IgnoreUnderRavenwood(blockedBy = android.webkit.MimeTypeMap.class)
    public void testBuildUniqueFile_mimeless() throws Exception {
        assertNameEquals("test.jpg", FileUtils.buildUniqueFile(mTarget, "test.jpg"));
        new File(mTarget, "test.jpg").createNewFile();
@@ -584,6 +593,7 @@ public class FileUtilsTest {
    }

    @Test
    @IgnoreUnderRavenwood(reason = "Requires kernel support")
    public void testTranslateMode() throws Exception {
        assertTranslate("r", O_RDONLY, MODE_READ_ONLY);

@@ -603,6 +613,7 @@ public class FileUtilsTest {
    }

    @Test
    @IgnoreUnderRavenwood(reason = "Requires kernel support")
    public void testMalformedTransate_int() throws Exception {
        try {
            // The non-standard Linux access mode 3 should throw
@@ -614,6 +625,7 @@ public class FileUtilsTest {
    }

    @Test
    @IgnoreUnderRavenwood(reason = "Requires kernel support")
    public void testMalformedTransate_string() throws Exception {
        try {
            // The non-standard Linux access mode 3 should throw
@@ -625,6 +637,7 @@ public class FileUtilsTest {
    }

    @Test
    @IgnoreUnderRavenwood(reason = "Requires kernel support")
    public void testTranslateMode_Invalid() throws Exception {
        try {
            translateModeStringToPosix("rwx");
@@ -639,6 +652,7 @@ public class FileUtilsTest {
    }

    @Test
    @IgnoreUnderRavenwood(reason = "Requires kernel support")
    public void testTranslateMode_Access() throws Exception {
        assertEquals(O_RDONLY, translateModeAccessToPosix(F_OK));
        assertEquals(O_RDONLY, translateModeAccessToPosix(R_OK));
@@ -648,6 +662,7 @@ public class FileUtilsTest {
    }

    @Test
    @IgnoreUnderRavenwood(reason = "Requires kernel support")
    public void testConvertToModernFd() throws Exception {
        final String nonce = String.valueOf(System.nanoTime());

@@ -720,13 +735,24 @@ public class FileUtilsTest {
    private byte[] readFile(File file) throws Exception {
        try (FileInputStream in = new FileInputStream(file);
                ByteArrayOutputStream out = new ByteArrayOutputStream()) {
            Streams.copy(in, out);
            copy(in, out);
            return out.toByteArray();
        }
    }

    private static int copy(InputStream in, OutputStream out) throws IOException {
        int total = 0;
        byte[] buffer = new byte[8192];
        int c;
        while ((c = in.read(buffer)) != -1) {
            total += c;
            out.write(buffer, 0, c);
        }
        return total;
    }

    private void assertDirContents(String... expected) {
        final HashSet<String> expectedSet = Sets.newHashSet(expected);
        final HashSet<String> expectedSet = new HashSet<>(Arrays.asList(expected));
        String[] actual = mDir.list();
        if (actual == null) actual = new String[0];

+2 −0
Original line number Diff line number Diff line
@@ -57,8 +57,10 @@ android_ravenwood_test {
    static_libs: [
        "androidx.annotation_annotation",
        "androidx.test.rules",
        "mockito_ravenwood",
    ],
    srcs: [
        "src/android/util/AtomicFileTest.java",
        "src/android/util/DataUnitTest.java",
        "src/android/util/EventLogTest.java",
        "src/android/util/IndentingPrintWriterTest.java",
Loading