diff options
| author | 2024-01-19 18:06:42 +0000 | |
|---|---|---|
| committer | 2024-01-19 18:06:42 +0000 | |
| commit | bb8f3fb6c0920be83d7e5e7afad06ce1fd2d448f (patch) | |
| tree | 6f2f6da5384d777c883a86ebe81bf9a6143bad46 | |
| parent | b87d24f321dd135eaa84cffe89c33e492f4bc9af (diff) | |
| parent | 744796fb34cf87cb75b1c5297bf250cde3b4993d (diff) | |
Merge "Add slice copy support for socket file type." into main
| -rw-r--r-- | core/java/android/os/FileUtils.java | 84 | ||||
| -rw-r--r-- | core/tests/coretests/src/android/os/FileUtilsTest.java | 84 |
2 files changed, 168 insertions, 0 deletions
diff --git a/core/java/android/os/FileUtils.java b/core/java/android/os/FileUtils.java index 5b24dcacbf53..3de862810a4c 100644 --- a/core/java/android/os/FileUtils.java +++ b/core/java/android/os/FileUtils.java @@ -25,6 +25,7 @@ import static android.os.ParcelFileDescriptor.MODE_WRITE_ONLY; import static android.system.OsConstants.EINVAL; import static android.system.OsConstants.ENOSYS; import static android.system.OsConstants.F_OK; +import static android.system.OsConstants.EIO; import static android.system.OsConstants.O_ACCMODE; import static android.system.OsConstants.O_APPEND; import static android.system.OsConstants.O_CREAT; @@ -37,6 +38,7 @@ 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 static android.system.OsConstants.S_ISSOCK; import static android.system.OsConstants.W_OK; import android.annotation.NonNull; @@ -459,6 +461,8 @@ public final class FileUtils { } } else if (S_ISFIFO(st_in.st_mode) || S_ISFIFO(st_out.st_mode)) { return copyInternalSplice(in, out, count, signal, executor, listener); + } else if (S_ISSOCK(st_in.st_mode) || S_ISSOCK(st_out.st_mode)) { + return copyInternalSpliceSocket(in, out, count, signal, executor, listener); } } catch (ErrnoException e) { throw e.rethrowAsIOException(); @@ -509,6 +513,86 @@ public final class FileUtils { } return progress; } + /** + * Requires one of input or output to be a socket file. + * + * @hide + */ + @VisibleForTesting + public static long copyInternalSpliceSocket(FileDescriptor in, FileDescriptor out, long count, + CancellationSignal signal, Executor executor, ProgressListener listener) + throws ErrnoException { + long progress = 0; + long checkpoint = 0; + long countToRead = count; + long countInPipe = 0; + long t; + + FileDescriptor[] pipes = Os.pipe(); + + while (countToRead > 0 || countInPipe > 0) { + if (countToRead > 0) { + t = Os.splice(in, null, pipes[1], null, Math.min(countToRead, COPY_CHECKPOINT_BYTES), + SPLICE_F_MOVE | SPLICE_F_MORE); + if (t < 0) { + // splice error + Slog.e(TAG, "splice error, fdIn --> pipe, copy size:" + count + + ", copied:" + progress + + ", read:" + (count - countToRead) + + ", in pipe:" + countInPipe); + break; + } else if (t == 0) { + // end of input, input count larger than real size + Slog.w(TAG, "Reached the end of the input file. The size to be copied exceeds the actual size, copy size:" + count + + ", copied:" + progress + + ", read:" + (count - countToRead) + + ", in pipe:" + countInPipe); + countToRead = 0; + } else { + countInPipe += t; + countToRead -= t; + } + } + + if (countInPipe > 0) { + t = Os.splice(pipes[0], null, out, null, Math.min(countInPipe, COPY_CHECKPOINT_BYTES), + SPLICE_F_MOVE | SPLICE_F_MORE); + // The data is already in the pipeline, so the return value will not be zero. + // If it is 0, it means an error has occurred. So here use t<=0. + if (t <= 0) { + Slog.e(TAG, "splice error, pipe --> fdOut, copy size:" + count + + ", copied:" + progress + + ", read:" + (count - countToRead) + + ", in pipe: " + countInPipe); + throw new ErrnoException("splice, pipe --> fdOut", EIO); + } else { + progress += t; + checkpoint += t; + countInPipe -= t; + } + } + + if (checkpoint >= COPY_CHECKPOINT_BYTES) { + if (signal != null) { + signal.throwIfCanceled(); + } + if (executor != null && listener != null) { + final long progressSnapshot = progress; + executor.execute(() -> { + listener.onProgress(progressSnapshot); + }); + } + checkpoint = 0; + } + } + if (executor != null && listener != null) { + final long progressSnapshot = progress; + executor.execute(() -> { + listener.onProgress(progressSnapshot); + }); + } + return progress; + } /** * Requires both input and output to be a regular file. diff --git a/core/tests/coretests/src/android/os/FileUtilsTest.java b/core/tests/coretests/src/android/os/FileUtilsTest.java index a0d8183b8da7..d2d745291c5f 100644 --- a/core/tests/coretests/src/android/os/FileUtilsTest.java +++ b/core/tests/coretests/src/android/os/FileUtilsTest.java @@ -29,6 +29,7 @@ import static android.os.ParcelFileDescriptor.MODE_READ_ONLY; import static android.os.ParcelFileDescriptor.MODE_READ_WRITE; import static android.os.ParcelFileDescriptor.MODE_TRUNCATE; import static android.os.ParcelFileDescriptor.MODE_WRITE_ONLY; +import static android.system.OsConstants.AF_INET; import static android.system.OsConstants.F_OK; import static android.system.OsConstants.O_APPEND; import static android.system.OsConstants.O_CREAT; @@ -37,6 +38,7 @@ import static android.system.OsConstants.O_RDWR; import static android.system.OsConstants.O_TRUNC; import static android.system.OsConstants.O_WRONLY; import static android.system.OsConstants.R_OK; +import static android.system.OsConstants.SOCK_STREAM; import static android.system.OsConstants.W_OK; import static android.system.OsConstants.X_OK; import static android.text.format.DateUtils.DAY_IN_MILLIS; @@ -54,6 +56,7 @@ import static org.junit.Assert.fail; import android.content.Context; import android.os.FileUtils.MemoryPipe; import android.provider.DocumentsContract.Document; +import android.system.Os; import android.util.DataUnit; import androidx.test.InstrumentationRegistry; @@ -70,6 +73,8 @@ import org.junit.runner.RunWith; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; import java.io.File; import java.io.FileDescriptor; import java.io.FileInputStream; @@ -77,6 +82,7 @@ import java.io.FileOutputStream; import java.util.Arrays; import java.util.HashSet; import java.util.Random; +import java.net.InetSocketAddress; @RunWith(AndroidJUnit4.class) public class FileUtilsTest { @@ -249,6 +255,84 @@ public class FileUtilsTest { assertArrayEquals(expected, actual); } + //TODO(ravenwood) Remove the _$noRavenwood suffix and add @RavenwoodIgnore instead + @Test + public void testCopy_SocketToFile_FileToSocket$noRavenwood() 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); + + // write test data in to src file + writeFile(src, expected); + + // start server, get data from client and save to dest file (socket --> file) + FileDescriptor srvSocketFd = Os.socket(AF_INET, SOCK_STREAM, 0); + Os.bind(srvSocketFd, new InetSocketAddress("localhost", 0)); + Os.listen(srvSocketFd, 5); + InetSocketAddress localSocketAddress = (InetSocketAddress) Os.getsockname(srvSocketFd); + + final Thread srv = new Thread(new Runnable() { + public void run() { + try { + InetSocketAddress peerAddress = new InetSocketAddress(); + FileDescriptor srvConnFd = Os.accept(srvSocketFd, peerAddress); + + // read file size + byte[] rcvFileSizeByteArray = new byte[8]; + Os.read(srvConnFd, rcvFileSizeByteArray, 0, rcvFileSizeByteArray.length); + long rcvFileSize = 0; + for (int i = 0; i < 8; i++) { + rcvFileSize <<= 8; + rcvFileSize |= (rcvFileSizeByteArray[i] & 0xFF); + } + + FileOutputStream fileOutputStream = new FileOutputStream(dest); + // copy data from socket to file + FileUtils.copy(srvConnFd, fileOutputStream.getFD(), rcvFileSize, null, null, null); + + fileOutputStream.close(); + Os.close(srvConnFd); + Os.close(srvSocketFd); + } catch (Exception e) { + e.printStackTrace(); + } + } + }); + + srv.start(); + + + // start client, get data from dest file and send to server (file --> socket) + FileDescriptor clientFd = Os.socket(AF_INET, SOCK_STREAM, 0); + Os.connect(clientFd, localSocketAddress.getAddress(), localSocketAddress.getPort()); + + FileInputStream fileInputStream = new FileInputStream(src); + long sndFileSize = src.length(); + // send the file size to server + byte[] sndFileSizeByteArray = new byte[8]; + for (int i = 7; i >= 0; i--) { + sndFileSizeByteArray[i] = (byte)(sndFileSize & 0xFF); + sndFileSize >>= 8; + } + Os.write(clientFd, sndFileSizeByteArray, 0, sndFileSizeByteArray.length); + + // copy data from file to socket + FileUtils.copy(fileInputStream.getFD(), clientFd, src.length(), null, null, null); + + fileInputStream.close(); + Os.close(clientFd); + + srv.join(); + + // read test data from dest file + actual = readFile(dest); + assertArrayEquals(expected, actual); + } + } + @Test public void testIsFilenameSafe() throws Exception { assertTrue(FileUtils.isFilenameSafe(new File("foobar"))); |