Skip to content
Snippets Groups Projects
Commit bb8f3fb6 authored by Steven Moreland's avatar Steven Moreland Committed by Gerrit Code Review
Browse files

Merge "Add slice copy support for socket file type." into main

parents b87d24f3 744796fb
No related branches found
No related tags found
No related merge requests found
......@@ -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.
......
......@@ -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")));
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment