Loading cmds/content/src/com/android/commands/content/Content.java +4 −2 Original line number Diff line number Diff line Loading @@ -26,6 +26,7 @@ import android.database.Cursor; import android.net.Uri; import android.os.Binder; import android.os.Bundle; import android.os.FileUtils; import android.os.IBinder; import android.os.ParcelFileDescriptor; import android.os.Process; Loading @@ -34,6 +35,7 @@ import android.text.TextUtils; import libcore.io.Streams; import java.io.FileDescriptor; import java.io.FileInputStream; import java.io.FileOutputStream; Loading Loading @@ -583,7 +585,7 @@ public class Content { @Override public void onExecute(IContentProvider provider) throws Exception { try (ParcelFileDescriptor fd = provider.openFile(null, mUri, "r", null, null)) { Streams.copy(new FileInputStream(fd.getFileDescriptor()), System.out); FileUtils.copy(fd.getFileDescriptor(), FileDescriptor.out); } } } Loading @@ -596,7 +598,7 @@ public class Content { @Override public void onExecute(IContentProvider provider) throws Exception { try (ParcelFileDescriptor fd = provider.openFile(null, mUri, "w", null, null)) { Streams.copy(System.in, new FileOutputStream(fd.getFileDescriptor())); FileUtils.copy(FileDescriptor.in, fd.getFileDescriptor()); } } } Loading core/java/android/os/FileUtils.java +231 −17 Original line number Diff line number Diff line Loading @@ -16,6 +16,11 @@ package android.os; import static android.system.OsConstants.SPLICE_F_MORE; import static android.system.OsConstants.SPLICE_F_MOVE; import static android.system.OsConstants.S_ISFIFO; import static android.system.OsConstants.S_ISREG; import android.annotation.NonNull; import android.annotation.Nullable; import android.provider.DocumentsContract.Document; Loading @@ -29,6 +34,7 @@ import android.webkit.MimeTypeMap; import com.android.internal.annotations.VisibleForTesting; import libcore.io.IoUtils; import libcore.util.EmptyArray; import java.io.BufferedInputStream; Loading @@ -41,10 +47,12 @@ import java.io.FileOutputStream; import java.io.FilenameFilter; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Comparator; import java.util.Objects; import java.util.concurrent.TimeUnit; import java.util.regex.Pattern; import java.util.zip.CRC32; import java.util.zip.CheckedInputStream; Loading Loading @@ -81,6 +89,14 @@ public class FileUtils { private static final File[] EMPTY = new File[0]; private static final boolean ENABLE_COPY_OPTIMIZATIONS = true; private static final long COPY_CHECKPOINT_BYTES = 524288; public interface CopyListener { public void onProgress(long progress); } /** * Set owner and mode of of given {@link File}. * Loading Loading @@ -185,6 +201,9 @@ public class FileUtils { return false; } /** * @deprecated use {@link #copy(InputStream, OutputStream)} instead. */ @Deprecated public static boolean copyFile(File srcFile, File destFile) { try { Loading @@ -195,14 +214,19 @@ public class FileUtils { } } // copy a file from srcFile to destFile, return true if succeed, return // false if fail /** * @deprecated use {@link #copy(InputStream, OutputStream)} instead. */ @Deprecated public static void copyFileOrThrow(File srcFile, File destFile) throws IOException { try (InputStream in = new FileInputStream(srcFile)) { copyToFileOrThrow(in, destFile); } } /** * @deprecated use {@link #copy(InputStream, OutputStream)} instead. */ @Deprecated public static boolean copyToFile(InputStream inputStream, File destFile) { try { Loading @@ -214,28 +238,153 @@ public class FileUtils { } /** * Copy data from a source stream to destFile. * Return true if succeed, return false if failed. * @deprecated use {@link #copy(InputStream, OutputStream)} instead. */ public static void copyToFileOrThrow(InputStream inputStream, File destFile) throws IOException { @Deprecated public static void copyToFileOrThrow(InputStream in, File destFile) throws IOException { if (destFile.exists()) { destFile.delete(); } FileOutputStream out = new FileOutputStream(destFile); try (FileOutputStream out = new FileOutputStream(destFile)) { copy(in, out); try { byte[] buffer = new byte[4096]; int bytesRead; while ((bytesRead = inputStream.read(buffer)) >= 0) { out.write(buffer, 0, bytesRead); Os.fsync(out.getFD()); } catch (ErrnoException e) { throw e.rethrowAsIOException(); } } finally { out.flush(); } } public static void copy(File from, File to) throws IOException { try (FileInputStream in = new FileInputStream(from); FileOutputStream out = new FileOutputStream(to)) { copy(in, out); } } public static void copy(InputStream in, OutputStream out) throws IOException { copy(in, out, null, null); } public static void copy(InputStream in, OutputStream out, CopyListener listener, CancellationSignal signal) throws IOException { if (ENABLE_COPY_OPTIMIZATIONS) { if (in instanceof FileInputStream && out instanceof FileOutputStream) { copy(((FileInputStream) in).getFD(), ((FileOutputStream) out).getFD(), listener, signal); } } // Worse case fallback to userspace copyInternalUserspace(in, out, listener, signal); } public static void copy(FileDescriptor in, FileDescriptor out) throws IOException { copy(in, out, null, null); } public static void copy(FileDescriptor in, FileDescriptor out, CopyListener listener, CancellationSignal signal) throws IOException { if (ENABLE_COPY_OPTIMIZATIONS) { try { out.getFD().sync(); } catch (IOException e) { final StructStat st_in = Os.fstat(in); final StructStat st_out = Os.fstat(out); if (S_ISREG(st_in.st_mode) && S_ISREG(st_out.st_mode)) { copyInternalSendfile(in, out, listener, signal); } else if (S_ISFIFO(st_in.st_mode) || S_ISFIFO(st_out.st_mode)) { copyInternalSplice(in, out, listener, signal); } } catch (ErrnoException e) { throw e.rethrowAsIOException(); } } // Worse case fallback to userspace copyInternalUserspace(in, out, listener, signal); } /** * Requires one of input or output to be a pipe. */ @VisibleForTesting public static void copyInternalSplice(FileDescriptor in, FileDescriptor out, CopyListener listener, CancellationSignal signal) throws ErrnoException { long progress = 0; long checkpoint = 0; long t; while ((t = Os.splice(in, null, out, null, COPY_CHECKPOINT_BYTES, SPLICE_F_MOVE | SPLICE_F_MORE)) != 0) { progress += t; checkpoint += t; if (checkpoint >= COPY_CHECKPOINT_BYTES) { if (signal != null) { signal.throwIfCanceled(); } if (listener != null) { listener.onProgress(progress); } checkpoint = 0; } } } /** * Requires both input and output to be a regular file. */ @VisibleForTesting public static void copyInternalSendfile(FileDescriptor in, FileDescriptor out, CopyListener listener, CancellationSignal signal) throws ErrnoException { long progress = 0; long checkpoint = 0; long t; while ((t = Os.sendfile(out, in, null, COPY_CHECKPOINT_BYTES)) != 0) { progress += t; checkpoint += t; if (checkpoint >= COPY_CHECKPOINT_BYTES) { if (signal != null) { signal.throwIfCanceled(); } if (listener != null) { listener.onProgress(progress); } checkpoint = 0; } } } @VisibleForTesting public static void copyInternalUserspace(FileDescriptor in, FileDescriptor out, CopyListener listener, CancellationSignal signal) throws IOException { copyInternalUserspace(new FileInputStream(in), new FileOutputStream(out), listener, signal); } @VisibleForTesting public static void copyInternalUserspace(InputStream in, OutputStream out, CopyListener listener, CancellationSignal signal) throws IOException { long progress = 0; long checkpoint = 0; byte[] buffer = new byte[8192]; int t; while ((t = in.read(buffer)) != -1) { out.write(buffer, 0, t); progress += t; checkpoint += t; if (checkpoint >= COPY_CHECKPOINT_BYTES) { if (signal != null) { signal.throwIfCanceled(); } if (listener != null) { listener.onProgress(progress); } checkpoint = 0; } out.close(); } } Loading Loading @@ -797,4 +946,69 @@ public class FileUtils { } return val * pow; } @VisibleForTesting public static class MemoryPipe extends Thread implements AutoCloseable { private final FileDescriptor[] pipe; private final byte[] data; private final boolean sink; private MemoryPipe(byte[] data, boolean sink) throws IOException { try { this.pipe = Os.pipe(); } catch (ErrnoException e) { throw e.rethrowAsIOException(); } this.data = data; this.sink = sink; } private MemoryPipe startInternal() { super.start(); return this; } public static MemoryPipe createSource(byte[] data) throws IOException { return new MemoryPipe(data, false).startInternal(); } public static MemoryPipe createSink(byte[] data) throws IOException { return new MemoryPipe(data, true).startInternal(); } public FileDescriptor getFD() { return sink ? pipe[1] : pipe[0]; } public FileDescriptor getInternalFD() { return sink ? pipe[0] : pipe[1]; } @Override public void run() { final FileDescriptor fd = getInternalFD(); try { int i = 0; while (i < data.length) { if (sink) { i += Os.read(fd, data, i, data.length - i); } else { i += Os.write(fd, data, i, data.length - i); } } } catch (IOException | ErrnoException e) { throw new RuntimeException(e); } finally { if (sink) { SystemClock.sleep(TimeUnit.SECONDS.toMillis(1)); } IoUtils.closeQuietly(fd); } } @Override public void close() throws Exception { IoUtils.closeQuietly(getFD()); } } } core/java/com/android/internal/util/FileRotator.java +1 −1 Original line number Diff line number Diff line Loading @@ -160,7 +160,7 @@ public class FileRotator { final File file = new File(mBasePath, name); final FileInputStream is = new FileInputStream(file); try { Streams.copy(is, zos); FileUtils.copy(is, zos); } finally { IoUtils.closeQuietly(is); } Loading core/tests/benchmarks/src/android/os/FileUtilsBenchmark.java 0 → 100644 +106 −0 Original line number Diff line number Diff line /* * Copyright (C) 2018 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; import static android.os.FileUtils.copyInternalSendfile; import static android.os.FileUtils.copyInternalSplice; import static android.os.FileUtils.copyInternalUserspace; import android.os.FileUtils.MemoryPipe; import com.google.caliper.BeforeExperiment; import com.google.caliper.Param; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; public class FileUtilsBenchmark { @Param({"32", "32000", "32000000"}) private int mSize; private File mSrc; private File mDest; private byte[] mData; @BeforeExperiment protected void setUp() throws Exception { mSrc = new File("/data/local/tmp/src"); mDest = new File("/data/local/tmp/dest"); mData = new byte[mSize]; try (FileOutputStream os = new FileOutputStream(mSrc)) { os.write(mData); } } public void timeRegularUserspace(int reps) throws Exception { for (int i = 0; i < reps; i++) { try (FileInputStream in = new FileInputStream(mSrc); FileOutputStream out = new FileOutputStream(mDest)) { copyInternalUserspace(in.getFD(), out.getFD(), null, null); } } } public void timeRegularSendfile(int reps) throws Exception { for (int i = 0; i < reps; i++) { try (FileInputStream in = new FileInputStream(mSrc); FileOutputStream out = new FileOutputStream(mDest)) { copyInternalSendfile(in.getFD(), out.getFD(), null, null); } } } public void timePipeSourceUserspace(int reps) throws Exception { for (int i = 0; i < reps; i++) { try (MemoryPipe in = MemoryPipe.createSource(mData); FileOutputStream out = new FileOutputStream(mDest)) { copyInternalUserspace(in.getFD(), out.getFD(), null, null); } } } public void timePipeSourceSplice(int reps) throws Exception { for (int i = 0; i < reps; i++) { try (MemoryPipe in = MemoryPipe.createSource(mData); FileOutputStream out = new FileOutputStream(mDest)) { copyInternalSplice(in.getFD(), out.getFD(), null, null); } } } public void timePipeSinkUserspace(int reps) throws Exception { for (int i = 0; i < reps; i++) { try (FileInputStream in = new FileInputStream(mSrc); MemoryPipe out = MemoryPipe.createSink(mData)) { copyInternalUserspace(in.getFD(), out.getFD(), null, null); } } } public void timePipeSinkSplice(int reps) throws Exception { for (int i = 0; i < reps; i++) { try (FileInputStream in = new FileInputStream(mSrc); MemoryPipe out = MemoryPipe.createSink(mData)) { copyInternalSplice(in.getFD(), out.getFD(), null, null); } } } } core/tests/coretests/src/android/os/FileUtilsTest.java +103 −10 Original line number Diff line number Diff line Loading @@ -21,16 +21,19 @@ import static android.text.format.DateUtils.DAY_IN_MILLIS; import static android.text.format.DateUtils.HOUR_IN_MILLIS; import static android.text.format.DateUtils.WEEK_IN_MILLIS; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import android.content.Context; import android.os.FileUtils.MemoryPipe; import android.provider.DocumentsContract.Document; import android.support.test.InstrumentationRegistry; import android.support.test.runner.AndroidJUnit4; import libcore.io.IoUtils; import libcore.io.Streams; import com.google.android.collect.Sets; Loading @@ -40,11 +43,13 @@ import org.junit.Test; import org.junit.runner.RunWith; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.FileWriter; import java.util.Arrays; import java.util.HashSet; import java.util.Random; @RunWith(AndroidJUnit4.class) public class FileUtilsTest { Loading @@ -56,6 +61,8 @@ public class FileUtilsTest { private File mCopyFile; private File mTarget; private final int[] DATA_SIZES = { 32, 32_000, 32_000_000 }; private Context getContext() { return InstrumentationRegistry.getContext(); } Loading @@ -80,7 +87,7 @@ public class FileUtilsTest { @Test public void testCopyFile() throws Exception { stageFile(mTestFile, TEST_DATA); writeFile(mTestFile, TEST_DATA); assertFalse(mCopyFile.exists()); FileUtils.copyFile(mTestFile, mCopyFile); assertTrue(mCopyFile.exists()); Loading @@ -96,6 +103,83 @@ public class FileUtilsTest { assertEquals(s, FileUtils.readTextFile(mCopyFile, 0, null)); } @Test public void testCopy_FileToFile() throws Exception { for (int size : DATA_SIZES) { final File src = new File(mTarget, "src"); final File dest = new File(mTarget, "dest"); byte[] expected = new byte[size]; byte[] actual = new byte[size]; new Random().nextBytes(expected); writeFile(src, expected); try (FileInputStream in = new FileInputStream(src); FileOutputStream out = new FileOutputStream(dest)) { FileUtils.copy(in, out); } actual = readFile(dest); assertArrayEquals(expected, actual); } } @Test public void testCopy_FileToPipe() throws Exception { for (int size : DATA_SIZES) { final File src = new File(mTarget, "src"); byte[] expected = new byte[size]; byte[] actual = new byte[size]; new Random().nextBytes(expected); writeFile(src, expected); try (FileInputStream in = new FileInputStream(src); MemoryPipe out = MemoryPipe.createSink(actual)) { FileUtils.copy(in.getFD(), out.getFD()); out.join(); } assertArrayEquals(expected, actual); } } @Test public void testCopy_PipeToFile() throws Exception { for (int size : DATA_SIZES) { final File dest = new File(mTarget, "dest"); byte[] expected = new byte[size]; byte[] actual = new byte[size]; new Random().nextBytes(expected); try (MemoryPipe in = MemoryPipe.createSource(expected); FileOutputStream out = new FileOutputStream(dest)) { FileUtils.copy(in.getFD(), out.getFD()); } actual = readFile(dest); assertArrayEquals(expected, actual); } } @Test public void testCopy_PipeToPipe() throws Exception { for (int size : DATA_SIZES) { byte[] expected = new byte[size]; byte[] actual = new byte[size]; new Random().nextBytes(expected); try (MemoryPipe in = MemoryPipe.createSource(expected); MemoryPipe out = MemoryPipe.createSink(actual)) { FileUtils.copy(in.getFD(), out.getFD()); out.join(); } assertArrayEquals(expected, actual); } } @Test public void testIsFilenameSafe() throws Exception { assertTrue(FileUtils.isFilenameSafe(new File("foobar"))); Loading @@ -106,7 +190,7 @@ public class FileUtilsTest { @Test public void testReadTextFile() throws Exception { stageFile(mTestFile, TEST_DATA); writeFile(mTestFile, TEST_DATA); assertEquals(TEST_DATA, FileUtils.readTextFile(mTestFile, 0, null)); Loading @@ -127,7 +211,7 @@ public class FileUtilsTest { @Test public void testReadTextFileWithZeroLengthFile() throws Exception { stageFile(mTestFile, TEST_DATA); writeFile(mTestFile, TEST_DATA); new FileOutputStream(mTestFile).close(); // Zero out the file assertEquals("", FileUtils.readTextFile(mTestFile, 0, null)); assertEquals("", FileUtils.readTextFile(mTestFile, 1, "<>")); Loading Loading @@ -381,12 +465,21 @@ public class FileUtilsTest { file.setLastModified(System.currentTimeMillis() - age); } private void stageFile(File file, String data) throws Exception { FileWriter writer = new FileWriter(file); try { writer.write(data, 0, data.length()); } finally { writer.close(); private void writeFile(File file, String data) throws Exception { writeFile(file, data.getBytes()); } private void writeFile(File file, byte[] data) throws Exception { try (FileOutputStream out = new FileOutputStream(file)) { out.write(data); } } private byte[] readFile(File file) throws Exception { try (FileInputStream in = new FileInputStream(file); ByteArrayOutputStream out = new ByteArrayOutputStream()) { Streams.copy(in, out); return out.toByteArray(); } } Loading Loading
cmds/content/src/com/android/commands/content/Content.java +4 −2 Original line number Diff line number Diff line Loading @@ -26,6 +26,7 @@ import android.database.Cursor; import android.net.Uri; import android.os.Binder; import android.os.Bundle; import android.os.FileUtils; import android.os.IBinder; import android.os.ParcelFileDescriptor; import android.os.Process; Loading @@ -34,6 +35,7 @@ import android.text.TextUtils; import libcore.io.Streams; import java.io.FileDescriptor; import java.io.FileInputStream; import java.io.FileOutputStream; Loading Loading @@ -583,7 +585,7 @@ public class Content { @Override public void onExecute(IContentProvider provider) throws Exception { try (ParcelFileDescriptor fd = provider.openFile(null, mUri, "r", null, null)) { Streams.copy(new FileInputStream(fd.getFileDescriptor()), System.out); FileUtils.copy(fd.getFileDescriptor(), FileDescriptor.out); } } } Loading @@ -596,7 +598,7 @@ public class Content { @Override public void onExecute(IContentProvider provider) throws Exception { try (ParcelFileDescriptor fd = provider.openFile(null, mUri, "w", null, null)) { Streams.copy(System.in, new FileOutputStream(fd.getFileDescriptor())); FileUtils.copy(FileDescriptor.in, fd.getFileDescriptor()); } } } Loading
core/java/android/os/FileUtils.java +231 −17 Original line number Diff line number Diff line Loading @@ -16,6 +16,11 @@ package android.os; import static android.system.OsConstants.SPLICE_F_MORE; import static android.system.OsConstants.SPLICE_F_MOVE; import static android.system.OsConstants.S_ISFIFO; import static android.system.OsConstants.S_ISREG; import android.annotation.NonNull; import android.annotation.Nullable; import android.provider.DocumentsContract.Document; Loading @@ -29,6 +34,7 @@ import android.webkit.MimeTypeMap; import com.android.internal.annotations.VisibleForTesting; import libcore.io.IoUtils; import libcore.util.EmptyArray; import java.io.BufferedInputStream; Loading @@ -41,10 +47,12 @@ import java.io.FileOutputStream; import java.io.FilenameFilter; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Comparator; import java.util.Objects; import java.util.concurrent.TimeUnit; import java.util.regex.Pattern; import java.util.zip.CRC32; import java.util.zip.CheckedInputStream; Loading Loading @@ -81,6 +89,14 @@ public class FileUtils { private static final File[] EMPTY = new File[0]; private static final boolean ENABLE_COPY_OPTIMIZATIONS = true; private static final long COPY_CHECKPOINT_BYTES = 524288; public interface CopyListener { public void onProgress(long progress); } /** * Set owner and mode of of given {@link File}. * Loading Loading @@ -185,6 +201,9 @@ public class FileUtils { return false; } /** * @deprecated use {@link #copy(InputStream, OutputStream)} instead. */ @Deprecated public static boolean copyFile(File srcFile, File destFile) { try { Loading @@ -195,14 +214,19 @@ public class FileUtils { } } // copy a file from srcFile to destFile, return true if succeed, return // false if fail /** * @deprecated use {@link #copy(InputStream, OutputStream)} instead. */ @Deprecated public static void copyFileOrThrow(File srcFile, File destFile) throws IOException { try (InputStream in = new FileInputStream(srcFile)) { copyToFileOrThrow(in, destFile); } } /** * @deprecated use {@link #copy(InputStream, OutputStream)} instead. */ @Deprecated public static boolean copyToFile(InputStream inputStream, File destFile) { try { Loading @@ -214,28 +238,153 @@ public class FileUtils { } /** * Copy data from a source stream to destFile. * Return true if succeed, return false if failed. * @deprecated use {@link #copy(InputStream, OutputStream)} instead. */ public static void copyToFileOrThrow(InputStream inputStream, File destFile) throws IOException { @Deprecated public static void copyToFileOrThrow(InputStream in, File destFile) throws IOException { if (destFile.exists()) { destFile.delete(); } FileOutputStream out = new FileOutputStream(destFile); try (FileOutputStream out = new FileOutputStream(destFile)) { copy(in, out); try { byte[] buffer = new byte[4096]; int bytesRead; while ((bytesRead = inputStream.read(buffer)) >= 0) { out.write(buffer, 0, bytesRead); Os.fsync(out.getFD()); } catch (ErrnoException e) { throw e.rethrowAsIOException(); } } finally { out.flush(); } } public static void copy(File from, File to) throws IOException { try (FileInputStream in = new FileInputStream(from); FileOutputStream out = new FileOutputStream(to)) { copy(in, out); } } public static void copy(InputStream in, OutputStream out) throws IOException { copy(in, out, null, null); } public static void copy(InputStream in, OutputStream out, CopyListener listener, CancellationSignal signal) throws IOException { if (ENABLE_COPY_OPTIMIZATIONS) { if (in instanceof FileInputStream && out instanceof FileOutputStream) { copy(((FileInputStream) in).getFD(), ((FileOutputStream) out).getFD(), listener, signal); } } // Worse case fallback to userspace copyInternalUserspace(in, out, listener, signal); } public static void copy(FileDescriptor in, FileDescriptor out) throws IOException { copy(in, out, null, null); } public static void copy(FileDescriptor in, FileDescriptor out, CopyListener listener, CancellationSignal signal) throws IOException { if (ENABLE_COPY_OPTIMIZATIONS) { try { out.getFD().sync(); } catch (IOException e) { final StructStat st_in = Os.fstat(in); final StructStat st_out = Os.fstat(out); if (S_ISREG(st_in.st_mode) && S_ISREG(st_out.st_mode)) { copyInternalSendfile(in, out, listener, signal); } else if (S_ISFIFO(st_in.st_mode) || S_ISFIFO(st_out.st_mode)) { copyInternalSplice(in, out, listener, signal); } } catch (ErrnoException e) { throw e.rethrowAsIOException(); } } // Worse case fallback to userspace copyInternalUserspace(in, out, listener, signal); } /** * Requires one of input or output to be a pipe. */ @VisibleForTesting public static void copyInternalSplice(FileDescriptor in, FileDescriptor out, CopyListener listener, CancellationSignal signal) throws ErrnoException { long progress = 0; long checkpoint = 0; long t; while ((t = Os.splice(in, null, out, null, COPY_CHECKPOINT_BYTES, SPLICE_F_MOVE | SPLICE_F_MORE)) != 0) { progress += t; checkpoint += t; if (checkpoint >= COPY_CHECKPOINT_BYTES) { if (signal != null) { signal.throwIfCanceled(); } if (listener != null) { listener.onProgress(progress); } checkpoint = 0; } } } /** * Requires both input and output to be a regular file. */ @VisibleForTesting public static void copyInternalSendfile(FileDescriptor in, FileDescriptor out, CopyListener listener, CancellationSignal signal) throws ErrnoException { long progress = 0; long checkpoint = 0; long t; while ((t = Os.sendfile(out, in, null, COPY_CHECKPOINT_BYTES)) != 0) { progress += t; checkpoint += t; if (checkpoint >= COPY_CHECKPOINT_BYTES) { if (signal != null) { signal.throwIfCanceled(); } if (listener != null) { listener.onProgress(progress); } checkpoint = 0; } } } @VisibleForTesting public static void copyInternalUserspace(FileDescriptor in, FileDescriptor out, CopyListener listener, CancellationSignal signal) throws IOException { copyInternalUserspace(new FileInputStream(in), new FileOutputStream(out), listener, signal); } @VisibleForTesting public static void copyInternalUserspace(InputStream in, OutputStream out, CopyListener listener, CancellationSignal signal) throws IOException { long progress = 0; long checkpoint = 0; byte[] buffer = new byte[8192]; int t; while ((t = in.read(buffer)) != -1) { out.write(buffer, 0, t); progress += t; checkpoint += t; if (checkpoint >= COPY_CHECKPOINT_BYTES) { if (signal != null) { signal.throwIfCanceled(); } if (listener != null) { listener.onProgress(progress); } checkpoint = 0; } out.close(); } } Loading Loading @@ -797,4 +946,69 @@ public class FileUtils { } return val * pow; } @VisibleForTesting public static class MemoryPipe extends Thread implements AutoCloseable { private final FileDescriptor[] pipe; private final byte[] data; private final boolean sink; private MemoryPipe(byte[] data, boolean sink) throws IOException { try { this.pipe = Os.pipe(); } catch (ErrnoException e) { throw e.rethrowAsIOException(); } this.data = data; this.sink = sink; } private MemoryPipe startInternal() { super.start(); return this; } public static MemoryPipe createSource(byte[] data) throws IOException { return new MemoryPipe(data, false).startInternal(); } public static MemoryPipe createSink(byte[] data) throws IOException { return new MemoryPipe(data, true).startInternal(); } public FileDescriptor getFD() { return sink ? pipe[1] : pipe[0]; } public FileDescriptor getInternalFD() { return sink ? pipe[0] : pipe[1]; } @Override public void run() { final FileDescriptor fd = getInternalFD(); try { int i = 0; while (i < data.length) { if (sink) { i += Os.read(fd, data, i, data.length - i); } else { i += Os.write(fd, data, i, data.length - i); } } } catch (IOException | ErrnoException e) { throw new RuntimeException(e); } finally { if (sink) { SystemClock.sleep(TimeUnit.SECONDS.toMillis(1)); } IoUtils.closeQuietly(fd); } } @Override public void close() throws Exception { IoUtils.closeQuietly(getFD()); } } }
core/java/com/android/internal/util/FileRotator.java +1 −1 Original line number Diff line number Diff line Loading @@ -160,7 +160,7 @@ public class FileRotator { final File file = new File(mBasePath, name); final FileInputStream is = new FileInputStream(file); try { Streams.copy(is, zos); FileUtils.copy(is, zos); } finally { IoUtils.closeQuietly(is); } Loading
core/tests/benchmarks/src/android/os/FileUtilsBenchmark.java 0 → 100644 +106 −0 Original line number Diff line number Diff line /* * Copyright (C) 2018 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; import static android.os.FileUtils.copyInternalSendfile; import static android.os.FileUtils.copyInternalSplice; import static android.os.FileUtils.copyInternalUserspace; import android.os.FileUtils.MemoryPipe; import com.google.caliper.BeforeExperiment; import com.google.caliper.Param; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; public class FileUtilsBenchmark { @Param({"32", "32000", "32000000"}) private int mSize; private File mSrc; private File mDest; private byte[] mData; @BeforeExperiment protected void setUp() throws Exception { mSrc = new File("/data/local/tmp/src"); mDest = new File("/data/local/tmp/dest"); mData = new byte[mSize]; try (FileOutputStream os = new FileOutputStream(mSrc)) { os.write(mData); } } public void timeRegularUserspace(int reps) throws Exception { for (int i = 0; i < reps; i++) { try (FileInputStream in = new FileInputStream(mSrc); FileOutputStream out = new FileOutputStream(mDest)) { copyInternalUserspace(in.getFD(), out.getFD(), null, null); } } } public void timeRegularSendfile(int reps) throws Exception { for (int i = 0; i < reps; i++) { try (FileInputStream in = new FileInputStream(mSrc); FileOutputStream out = new FileOutputStream(mDest)) { copyInternalSendfile(in.getFD(), out.getFD(), null, null); } } } public void timePipeSourceUserspace(int reps) throws Exception { for (int i = 0; i < reps; i++) { try (MemoryPipe in = MemoryPipe.createSource(mData); FileOutputStream out = new FileOutputStream(mDest)) { copyInternalUserspace(in.getFD(), out.getFD(), null, null); } } } public void timePipeSourceSplice(int reps) throws Exception { for (int i = 0; i < reps; i++) { try (MemoryPipe in = MemoryPipe.createSource(mData); FileOutputStream out = new FileOutputStream(mDest)) { copyInternalSplice(in.getFD(), out.getFD(), null, null); } } } public void timePipeSinkUserspace(int reps) throws Exception { for (int i = 0; i < reps; i++) { try (FileInputStream in = new FileInputStream(mSrc); MemoryPipe out = MemoryPipe.createSink(mData)) { copyInternalUserspace(in.getFD(), out.getFD(), null, null); } } } public void timePipeSinkSplice(int reps) throws Exception { for (int i = 0; i < reps; i++) { try (FileInputStream in = new FileInputStream(mSrc); MemoryPipe out = MemoryPipe.createSink(mData)) { copyInternalSplice(in.getFD(), out.getFD(), null, null); } } } }
core/tests/coretests/src/android/os/FileUtilsTest.java +103 −10 Original line number Diff line number Diff line Loading @@ -21,16 +21,19 @@ import static android.text.format.DateUtils.DAY_IN_MILLIS; import static android.text.format.DateUtils.HOUR_IN_MILLIS; import static android.text.format.DateUtils.WEEK_IN_MILLIS; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import android.content.Context; import android.os.FileUtils.MemoryPipe; import android.provider.DocumentsContract.Document; import android.support.test.InstrumentationRegistry; import android.support.test.runner.AndroidJUnit4; import libcore.io.IoUtils; import libcore.io.Streams; import com.google.android.collect.Sets; Loading @@ -40,11 +43,13 @@ import org.junit.Test; import org.junit.runner.RunWith; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.FileWriter; import java.util.Arrays; import java.util.HashSet; import java.util.Random; @RunWith(AndroidJUnit4.class) public class FileUtilsTest { Loading @@ -56,6 +61,8 @@ public class FileUtilsTest { private File mCopyFile; private File mTarget; private final int[] DATA_SIZES = { 32, 32_000, 32_000_000 }; private Context getContext() { return InstrumentationRegistry.getContext(); } Loading @@ -80,7 +87,7 @@ public class FileUtilsTest { @Test public void testCopyFile() throws Exception { stageFile(mTestFile, TEST_DATA); writeFile(mTestFile, TEST_DATA); assertFalse(mCopyFile.exists()); FileUtils.copyFile(mTestFile, mCopyFile); assertTrue(mCopyFile.exists()); Loading @@ -96,6 +103,83 @@ public class FileUtilsTest { assertEquals(s, FileUtils.readTextFile(mCopyFile, 0, null)); } @Test public void testCopy_FileToFile() throws Exception { for (int size : DATA_SIZES) { final File src = new File(mTarget, "src"); final File dest = new File(mTarget, "dest"); byte[] expected = new byte[size]; byte[] actual = new byte[size]; new Random().nextBytes(expected); writeFile(src, expected); try (FileInputStream in = new FileInputStream(src); FileOutputStream out = new FileOutputStream(dest)) { FileUtils.copy(in, out); } actual = readFile(dest); assertArrayEquals(expected, actual); } } @Test public void testCopy_FileToPipe() throws Exception { for (int size : DATA_SIZES) { final File src = new File(mTarget, "src"); byte[] expected = new byte[size]; byte[] actual = new byte[size]; new Random().nextBytes(expected); writeFile(src, expected); try (FileInputStream in = new FileInputStream(src); MemoryPipe out = MemoryPipe.createSink(actual)) { FileUtils.copy(in.getFD(), out.getFD()); out.join(); } assertArrayEquals(expected, actual); } } @Test public void testCopy_PipeToFile() throws Exception { for (int size : DATA_SIZES) { final File dest = new File(mTarget, "dest"); byte[] expected = new byte[size]; byte[] actual = new byte[size]; new Random().nextBytes(expected); try (MemoryPipe in = MemoryPipe.createSource(expected); FileOutputStream out = new FileOutputStream(dest)) { FileUtils.copy(in.getFD(), out.getFD()); } actual = readFile(dest); assertArrayEquals(expected, actual); } } @Test public void testCopy_PipeToPipe() throws Exception { for (int size : DATA_SIZES) { byte[] expected = new byte[size]; byte[] actual = new byte[size]; new Random().nextBytes(expected); try (MemoryPipe in = MemoryPipe.createSource(expected); MemoryPipe out = MemoryPipe.createSink(actual)) { FileUtils.copy(in.getFD(), out.getFD()); out.join(); } assertArrayEquals(expected, actual); } } @Test public void testIsFilenameSafe() throws Exception { assertTrue(FileUtils.isFilenameSafe(new File("foobar"))); Loading @@ -106,7 +190,7 @@ public class FileUtilsTest { @Test public void testReadTextFile() throws Exception { stageFile(mTestFile, TEST_DATA); writeFile(mTestFile, TEST_DATA); assertEquals(TEST_DATA, FileUtils.readTextFile(mTestFile, 0, null)); Loading @@ -127,7 +211,7 @@ public class FileUtilsTest { @Test public void testReadTextFileWithZeroLengthFile() throws Exception { stageFile(mTestFile, TEST_DATA); writeFile(mTestFile, TEST_DATA); new FileOutputStream(mTestFile).close(); // Zero out the file assertEquals("", FileUtils.readTextFile(mTestFile, 0, null)); assertEquals("", FileUtils.readTextFile(mTestFile, 1, "<>")); Loading Loading @@ -381,12 +465,21 @@ public class FileUtilsTest { file.setLastModified(System.currentTimeMillis() - age); } private void stageFile(File file, String data) throws Exception { FileWriter writer = new FileWriter(file); try { writer.write(data, 0, data.length()); } finally { writer.close(); private void writeFile(File file, String data) throws Exception { writeFile(file, data.getBytes()); } private void writeFile(File file, byte[] data) throws Exception { try (FileOutputStream out = new FileOutputStream(file)) { out.write(data); } } private byte[] readFile(File file) throws Exception { try (FileInputStream in = new FileInputStream(file); ByteArrayOutputStream out = new ByteArrayOutputStream()) { Streams.copy(in, out); return out.toByteArray(); } } Loading