Skip to content

Commit

Permalink
Fix SystemNative_SendFile on iOS/tvOS devices (#69436)
Browse files Browse the repository at this point in the history
Even though the `sendfile()` function exists it causes a SIGSYS signal which crashes the process on devices whenever you use it (this doesn't happen on simulators).

Implement a read/write loop as a fallback to fix this, loosely based on CopyFile_ReadWrite in pal_io.c.

See similar issues in other projects:
- golang/go@029dfbc
- ndfred/iperf-ios#17
  • Loading branch information
akoeplinger authored May 21, 2022
1 parent 7626c5d commit f832309
Show file tree
Hide file tree
Showing 10 changed files with 166 additions and 112 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ public async Task Connect_AfterDisconnect_Fails()
}

[OuterLoop("Connects to external server")]
[SkipOnPlatform(TestPlatforms.OSX | TestPlatforms.FreeBSD, "Not supported on BSD like OSes.")]
[SkipOnPlatform(TestPlatforms.OSX | TestPlatforms.MacCatalyst | TestPlatforms.iOS | TestPlatforms.tvOS | TestPlatforms.FreeBSD, "Not supported on BSD like OSes.")]
[Theory]
[InlineData("1.1.1.1", false)]
[InlineData("1.1.1.1", true)]
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -501,7 +501,7 @@ public async Task APM_ExecutionContextFlowsAcrossBeginSendFileOperation(bool sup
}

[OuterLoop("Relies on finalization")]
[Fact]
[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsPreciseGcSupported))]
public void ExecutionContext_NotCachedInSocketAsyncEventArgs()
{
using (var listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ namespace System.Net.Sockets.Tests
public class InlineContinuations
{
[OuterLoop]
[Fact]
[ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
[PlatformSpecific(TestPlatforms.AnyUnix)] // Inline Socket mode is specific to Unix Socket implementation.
public void InlineSocketContinuations()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public void Socket_LingerState_Common_Boundaries_CorrectBehavior()

[OuterLoop]
[Fact]
[SkipOnPlatform(TestPlatforms.OSX, "The upper bound for linger time is drastically different on OS X.")]
[SkipOnPlatform(TestPlatforms.OSX | TestPlatforms.MacCatalyst | TestPlatforms.iOS | TestPlatforms.tvOS, "The upper bound for linger time is drastically different on Apple platforms.")]
public void Socket_LingerState_Upper_Boundaries_CorrectBehavior()
{
Socket sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
Expand All @@ -55,10 +55,10 @@ public void Socket_LingerState_Upper_Boundaries_CorrectBehavior()

[OuterLoop]
[Fact]
[PlatformSpecific(TestPlatforms.OSX)] // The upper bound for linger time is drastically different on OS X.
public void Socket_LingerState_Upper_Boundaries_CorrectBehavior_OSX()
[PlatformSpecific(TestPlatforms.OSX | TestPlatforms.MacCatalyst | TestPlatforms.iOS | TestPlatforms.tvOS)] // The upper bound for linger time is drastically different on Apple platforms.
public void Socket_LingerState_Upper_Boundaries_CorrectBehavior_Apple()
{
// The upper bound for linger time is drastically different on OS X.
// The upper bound for linger time is drastically different on Apple platforms.
Socket sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

Assert.Throws<SocketException>(() =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -831,7 +831,7 @@ await Task.WhenAll(
}

[Fact]
[PlatformSpecific(TestPlatforms.OSX)]
[PlatformSpecific(TestPlatforms.OSX | TestPlatforms.MacCatalyst | TestPlatforms.iOS | TestPlatforms.tvOS)]
public void SocketSendReceiveBufferSize_SetZero_ThrowsSocketException()
{
using (Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
Expand Down
6 changes: 0 additions & 6 deletions src/mono/cmake/config.h.in
Original file line number Diff line number Diff line change
Expand Up @@ -563,9 +563,6 @@
/* Have mktemp */
#cmakedefine HAVE_MKTEMP 1

/* Define to 1 if you have the <sys/sendfile.h> header file. */
#cmakedefine HAVE_SYS_SENDFILE_H 1

/* Define to 1 if you have the <sys/statvfs.h> header file. */
#cmakedefine HAVE_SYS_STATVFS_H 1

Expand All @@ -590,9 +587,6 @@
/* Define to 1 if you have the `vsnprintf' function. */
#cmakedefine HAVE_VSNPRINTF 1

/* Define to 1 if you have the `sendfile' function. */
#cmakedefine HAVE_SENDFILE 1

/* struct statfs */
#cmakedefine HAVE_STATFS 1

Expand Down
4 changes: 2 additions & 2 deletions src/mono/cmake/configure.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ endfunction()
ac_check_headers (
sys/types.h sys/stat.h sys/filio.h sys/sockio.h sys/utime.h sys/un.h sys/syscall.h sys/uio.h sys/param.h
sys/prctl.h sys/socket.h sys/utsname.h sys/select.h sys/poll.h sys/wait.h sys/resource.h
sys/ioctl.h sys/errno.h sys/sendfile.h sys/statvfs.h sys/statfs.h sys/mman.h sys/mount.h sys/time.h sys/random.h
sys/ioctl.h sys/errno.h sys/statvfs.h sys/statfs.h sys/mman.h sys/mount.h sys/time.h sys/random.h
strings.h stdint.h unistd.h signal.h setjmp.h syslog.h netdb.h utime.h semaphore.h alloca.h ucontext.h pwd.h elf.h
gnu/lib-names.h netinet/tcp.h netinet/in.h link.h arpa/inet.h unwind.h poll.h wchar.h
android/legacy_signal_inlines.h execinfo.h pthread.h pthread_np.h net/if.h dirent.h
Expand All @@ -79,7 +79,7 @@ ac_check_funcs (
getrusage dladdr sysconf getrlimit prctl nl_langinfo
sched_getaffinity sched_setaffinity getpwuid_r chmod lstat getdtablesize ftruncate msync
getpeername utime utimes openlog closelog atexit popen strerror_r inet_pton inet_aton
poll getfsstat mremap posix_fadvise vsnprintf sendfile statfs statvfs setpgid system
poll getfsstat mremap posix_fadvise vsnprintf statfs statvfs setpgid system
fork execv execve waitpid localtime_r mkdtemp getrandom execvp strlcpy stpcpy strtok_r rewinddir
vasprintf strndup getpwuid_r getprotobyname getprotobyname_r getaddrinfo mach_absolute_time
gethrtime read_real_time gethostbyname gethostbyname2 getnameinfo getifaddrs
Expand Down
85 changes: 71 additions & 14 deletions src/native/libs/System.Native/pal_networking.c
Original file line number Diff line number Diff line change
Expand Up @@ -3125,10 +3125,9 @@ int32_t SystemNative_SendFile(intptr_t out_fd, intptr_t in_fd, int64_t offset, i

int outfd = ToFileDescriptor(out_fd);
int infd = ToFileDescriptor(in_fd);

#if HAVE_SENDFILE_4
off_t offtOffset = (off_t)offset;

#if HAVE_SENDFILE_4
ssize_t res;
while ((res = sendfile(outfd, infd, &offtOffset, (size_t)count)) < 0 && errno == EINTR);
if (res != -1)
Expand All @@ -3146,9 +3145,9 @@ int32_t SystemNative_SendFile(intptr_t out_fd, intptr_t in_fd, int64_t offset, i
{
off_t len = count;
#if HAVE_SENDFILE_7
ssize_t res = sendfile(infd, outfd, (off_t)offset, (size_t)count, NULL, &len, 0);
ssize_t res = sendfile(infd, outfd, offtOffset, (size_t)count, NULL, &len, 0);
#else
ssize_t res = sendfile(infd, outfd, (off_t)offset, &len, NULL, 0);
ssize_t res = sendfile(infd, outfd, offtOffset, &len, NULL, 0);
#endif
assert(len >= 0);

Expand Down Expand Up @@ -3180,18 +3179,76 @@ int32_t SystemNative_SendFile(intptr_t out_fd, intptr_t in_fd, int64_t offset, i
// For everything other than EINTR, bail.
return SystemNative_ConvertErrorPlatformToPal(errno);
}

#else
// If we ever need to run on a platform that doesn't have sendfile,
// we can implement this with a simple read/send loop. For now,
// we just mark it as not supported.
(void)outfd;
(void)infd;
(void)offset;
(void)count;
// Emulate sendfile using a simple read/send loop.
*sent = 0;
errno = ENOTSUP;
return SystemNative_ConvertErrorPlatformToPal(errno);
char* buffer = NULL;

// Save the original input file position and seek to the offset position
off_t inputFileOrigOffset = lseek(in_fd, 0, SEEK_CUR);
if (inputFileOrigOffset == -1 || lseek(in_fd, offtOffset, SEEK_SET) == -1)
{
goto error;
}

// Allocate a buffer
size_t bufferLength = Min((size_t)count, 80 * 1024 * sizeof(char));
buffer = (char*)malloc(bufferLength);
if (buffer == NULL)
{
goto error;
}

// Repeatedly read from the source and write to the destination
while (count > 0)
{
size_t numBytesToRead = Min((size_t)count, bufferLength);

// Read up to what will fit in our buffer. We're done if we get back 0 bytes or read 'count' bytes
ssize_t bytesRead;
while ((bytesRead = read(in_fd, buffer, numBytesToRead)) < 0 && errno == EINTR);
if (bytesRead == -1)
{
goto error;
}
if (bytesRead == 0)
{
break;
}
assert(bytesRead > 0);

// Write what was read.
ssize_t writeOffset = 0;
while (bytesRead > 0)
{
ssize_t bytesWritten;
while ((bytesWritten = write(out_fd, buffer + writeOffset, (size_t)bytesRead)) < 0 && errno == EINTR);
if (bytesWritten == -1)
{
goto error;
}
assert(bytesWritten >= 0);
bytesRead -= bytesWritten;
count -= bytesWritten;
writeOffset += bytesWritten;
*sent += bytesWritten;
}
}

// Restore the original input file position
if (lseek(in_fd, inputFileOrigOffset, SEEK_SET) == -1)
{
goto error;
}

free(buffer);
return Error_SUCCESS;

error:
int savedErrno = errno;
free(buffer);
return SystemNative_ConvertErrorPlatformToPal(savedErrno);

#endif
}

Expand Down
51 changes: 27 additions & 24 deletions src/native/libs/configure.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -434,32 +434,35 @@ check_struct_has_member(
"sys/select.h"
HAVE_PRIVATE_FDS_BITS)

check_c_source_compiles(
"
#include <sys/sendfile.h>
int main(void) { int i = sendfile(0, 0, 0, 0); return 0; }
"
HAVE_SENDFILE_4)
# do not use sendfile() on iOS/tvOS, it causes SIGSYS at runtime on devices
if(NOT CLR_CMAKE_TARGET_IOS AND NOT CLR_CMAKE_TARGET_TVOS)
check_c_source_compiles(
"
#include <sys/sendfile.h>
int main(void) { int i = sendfile(0, 0, 0, 0); return 0; }
"
HAVE_SENDFILE_4)

check_c_source_compiles(
"
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/uio.h>
int main(void) { int i = sendfile(0, 0, 0, NULL, NULL, 0); return 0; }
"
HAVE_SENDFILE_6)
check_c_source_compiles(
"
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/uio.h>
int main(void) { int i = sendfile(0, 0, 0, NULL, NULL, 0); return 0; }
"
HAVE_SENDFILE_6)

check_c_source_compiles(
"
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/uio.h>
int main(void) { int i = sendfile(0, 0, 0, 0, NULL, NULL, 0); return 0; }
"
HAVE_SENDFILE_7)
check_c_source_compiles(
"
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/uio.h>
int main(void) { int i = sendfile(0, 0, 0, 0, NULL, NULL, 0); return 0; }
"
HAVE_SENDFILE_7)
endif()

check_symbol_exists(
fcopyfile
Expand Down

0 comments on commit f832309

Please sign in to comment.