Skip to content

Commit

Permalink
Set of offset-based APIs for thread-safe file IO (#53669)
Browse files Browse the repository at this point in the history
Co-authored-by: Stephen Toub <stoub@microsoft.com>
  • Loading branch information
adamsitnik and stephentoub authored Jun 15, 2021
1 parent 4e03f36 commit 9d771a2
Show file tree
Hide file tree
Showing 72 changed files with 3,340 additions and 1,115 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;

internal static partial class Interop
{
internal static partial class Sys
{
internal unsafe struct IOVector
{
public byte* Base;
public UIntPtr Count;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,6 @@ internal static partial class Interop
{
internal static partial class Sys
{
internal unsafe struct IOVector
{
public byte* Base;
public UIntPtr Count;
}

internal unsafe struct MessageHeader
{
public byte* SocketAddress;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Runtime.InteropServices;

internal static partial class Interop
{
internal static partial class Sys
{
[DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_PRead", SetLastError = true)]
internal static extern unsafe int PRead(SafeHandle fd, byte* buffer, int bufferSize, long fileOffset);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Runtime.InteropServices;

internal static partial class Interop
{
internal static partial class Sys
{
[DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_PReadV", SetLastError = true)]
internal static extern unsafe long PReadV(SafeHandle fd, IOVector* vectors, int vectorCount, long fileOffset);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Runtime.InteropServices;

internal static partial class Interop
{
internal static partial class Sys
{
[DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_PWrite", SetLastError = true)]
internal static extern unsafe int PWrite(SafeHandle fd, byte* buffer, int bufferSize, long fileOffset);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Runtime.InteropServices;

internal static partial class Interop
{
internal static partial class Sys
{
[DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_PWriteV", SetLastError = true)]
internal static extern unsafe long PWriteV(SafeHandle fd, IOVector* vectors, int vectorCount, long fileOffset);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Runtime.InteropServices;
using System.Threading;

internal static partial class Interop
{
internal static partial class Kernel32
{
[DllImport(Libraries.Kernel32, SetLastError = true)]
internal static extern unsafe int ReadFileScatter(
SafeHandle hFile,
long* aSegmentArray,
int nNumberOfBytesToRead,
IntPtr lpReserved,
NativeOverlapped* lpOverlapped);

[DllImport(Libraries.Kernel32, SetLastError = true)]
internal static extern unsafe int WriteFileGather(
SafeHandle hFile,
long* aSegmentArray,
int nNumberOfBytesToWrite,
IntPtr lpReserved,
NativeOverlapped* lpOverlapped);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,6 @@ internal static partial class Interop
{
internal static partial class NtDll
{
internal const uint NT_ERROR_STATUS_DISK_FULL = 0xC000007F;
internal const uint NT_ERROR_STATUS_FILE_TOO_LARGE = 0xC0000904;
internal const uint NT_STATUS_INVALID_PARAMETER = 0xC000000D;

// https://msdn.microsoft.com/en-us/library/bb432380.aspx
// https://msdn.microsoft.com/en-us/library/windows/hardware/ff566424.aspx
[DllImport(Libraries.NtDll, CharSet = CharSet.Unicode, ExactSpelling = true)]
Expand Down Expand Up @@ -76,7 +72,7 @@ internal static unsafe (uint status, IntPtr handle) CreateFile(
}
}

internal static unsafe (uint status, IntPtr handle) CreateFile(ReadOnlySpan<char> path, FileMode mode, FileAccess access, FileShare share, FileOptions options, long preallocationSize)
internal static unsafe (uint status, IntPtr handle) NtCreateFile(ReadOnlySpan<char> path, FileMode mode, FileAccess access, FileShare share, FileOptions options, long preallocationSize)
{
// For mitigating local elevation of privilege attack through named pipes
// make sure we always call NtCreateFile with SECURITY_ANONYMOUS so that the
Expand Down Expand Up @@ -120,7 +116,7 @@ private static CreateDisposition GetCreateDisposition(FileMode mode)

private static DesiredAccess GetDesiredAccess(FileAccess access, FileMode fileMode, FileOptions options)
{
DesiredAccess result = 0;
DesiredAccess result = DesiredAccess.FILE_READ_ATTRIBUTES | DesiredAccess.SYNCHRONIZE; // default values used by CreateFileW

if ((access & FileAccess.Read) != 0)
{
Expand All @@ -134,13 +130,9 @@ private static DesiredAccess GetDesiredAccess(FileAccess access, FileMode fileMo
{
result |= DesiredAccess.FILE_APPEND_DATA;
}
if ((options & FileOptions.Asynchronous) == 0)
{
result |= DesiredAccess.SYNCHRONIZE; // required by FILE_SYNCHRONOUS_IO_NONALERT
}
if ((options & FileOptions.DeleteOnClose) != 0 || fileMode == FileMode.Create)
if ((options & FileOptions.DeleteOnClose) != 0)
{
result |= DesiredAccess.DELETE; // required by FILE_DELETE_ON_CLOSE and FILE_SUPERSEDE (which deletes a file if it exists)
result |= DesiredAccess.DELETE; // required by FILE_DELETE_ON_CLOSE
}

return result;
Expand Down Expand Up @@ -190,7 +182,8 @@ private static CreateOptions GetCreateOptions(FileOptions options)
}

private static ObjectAttributes GetObjectAttributes(FileShare share)
=> (share & FileShare.Inheritable) != 0 ? ObjectAttributes.OBJ_INHERIT : 0;
=> ObjectAttributes.OBJ_CASE_INSENSITIVE | // default value used by CreateFileW
((share & FileShare.Inheritable) != 0 ? ObjectAttributes.OBJ_INHERIT : 0);

/// <summary>
/// File creation disposition when calling directly to NT APIs.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// The .NET Foundation licenses this file to you under the MIT license.

using Microsoft.Win32.SafeHandles;
using System;
using System.Runtime.InteropServices;

internal static partial class Interop
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,7 @@ internal static class StatusOptions
internal const uint STATUS_ACCOUNT_RESTRICTION = 0xC000006E;
internal const uint STATUS_NONE_MAPPED = 0xC0000073;
internal const uint STATUS_INSUFFICIENT_RESOURCES = 0xC000009A;
internal const uint STATUS_DISK_FULL = 0xC000007F;
internal const uint STATUS_FILE_TOO_LARGE = 0xC0000904;
}
}
2 changes: 2 additions & 0 deletions src/libraries/Native/Unix/Common/pal_config.h.in
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@
#cmakedefine01 HAVE_POSIX_ADVISE
#cmakedefine01 HAVE_POSIX_FALLOCATE
#cmakedefine01 HAVE_POSIX_FALLOCATE64
#cmakedefine01 HAVE_PREADV
#cmakedefine01 HAVE_PWRITEV
#cmakedefine01 PRIORITY_REQUIRES_INT_WHO
#cmakedefine01 KEVENT_REQUIRES_INT_PARAMS
#cmakedefine01 HAVE_IOCTL
Expand Down
4 changes: 4 additions & 0 deletions src/libraries/Native/Unix/System.Native/entrypoints.c
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,10 @@ static const Entry s_sysNative[] =
DllImportEntry(SystemNative_iOSSupportVersion)
DllImportEntry(SystemNative_GetErrNo)
DllImportEntry(SystemNative_SetErrNo)
DllImportEntry(SystemNative_PRead)
DllImportEntry(SystemNative_PWrite)
DllImportEntry(SystemNative_PReadV)
DllImportEntry(SystemNative_PWriteV)
};

EXTERN_C const void* SystemResolveDllImport(const char* name);
Expand Down
105 changes: 105 additions & 0 deletions src/libraries/Native/Unix/System.Native/pal_io.c
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include <sys/file.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/uio.h>
#include <syslog.h>
#include <termios.h>
#include <unistd.h>
Expand Down Expand Up @@ -1454,3 +1455,107 @@ int32_t SystemNative_ReadProcessStatusInfo(pid_t pid, ProcessStatus* processStat
return -1;
#endif // __sun
}

int32_t SystemNative_PRead(intptr_t fd, void* buffer, int32_t bufferSize, int64_t fileOffset)
{
assert(buffer != NULL);
assert(bufferSize >= 0);

ssize_t count;
while ((count = pread(ToFileDescriptor(fd), buffer, (uint32_t)bufferSize, (off_t)fileOffset)) < 0 && errno == EINTR);

assert(count >= -1 && count <= bufferSize);
return (int32_t)count;
}

int32_t SystemNative_PWrite(intptr_t fd, void* buffer, int32_t bufferSize, int64_t fileOffset)
{
assert(buffer != NULL);
assert(bufferSize >= 0);

ssize_t count;
while ((count = pwrite(ToFileDescriptor(fd), buffer, (uint32_t)bufferSize, (off_t)fileOffset)) < 0 && errno == EINTR);

assert(count >= -1 && count <= bufferSize);
return (int32_t)count;
}

int64_t SystemNative_PReadV(intptr_t fd, IOVector* vectors, int32_t vectorCount, int64_t fileOffset)
{
assert(vectors != NULL);
assert(vectorCount >= 0);

int64_t count = 0;
int fileDescriptor = ToFileDescriptor(fd);
#if HAVE_PREADV && !defined(TARGET_WASM) // preadv is buggy on WASM
while ((count = preadv(fileDescriptor, (struct iovec*)vectors, (int)vectorCount, (off_t)fileOffset)) < 0 && errno == EINTR);
#else
int64_t current;
for (int i = 0; i < vectorCount; i++)
{
IOVector vector = vectors[i];
while ((current = pread(fileDescriptor, vector.Base, vector.Count, (off_t)(fileOffset + count))) < 0 && errno == EINTR);

if (current < 0)
{
// if previous calls were succesfull, we return what we got so far
// otherwise, we return the error code
return count > 0 ? count : current;
}

count += current;

// Incomplete pread operation may happen for two reasons:
// a) We have reached EOF.
// b) The operation was interrupted by a signal handler.
// To mimic preadv, we stop on the first incomplete operation.
if (current != (int64_t)vector.Count)
{
return count;
}
}
#endif

assert(count >= -1);
return count;
}

int64_t SystemNative_PWriteV(intptr_t fd, IOVector* vectors, int32_t vectorCount, int64_t fileOffset)
{
assert(vectors != NULL);
assert(vectorCount >= 0);

int64_t count = 0;
int fileDescriptor = ToFileDescriptor(fd);
#if HAVE_PWRITEV && !defined(TARGET_WASM) // pwritev is buggy on WASM
while ((count = pwritev(fileDescriptor, (struct iovec*)vectors, (int)vectorCount, (off_t)fileOffset)) < 0 && errno == EINTR);
#else
int64_t current;
for (int i = 0; i < vectorCount; i++)
{
IOVector vector = vectors[i];
while ((current = pwrite(fileDescriptor, vector.Base, vector.Count, (off_t)(fileOffset + count))) < 0 && errno == EINTR);

if (current < 0)
{
// if previous calls were succesfull, we return what we got so far
// otherwise, we return the error code
return count > 0 ? count : current;
}

count += current;

// Incomplete pwrite operation may happen for few reasons:
// a) There was not enough space available or the file is too large for given file system.
// b) The operation was interrupted by a signal handler.
// To mimic pwritev, we stop on the first incomplete operation.
if (current != (int64_t)vector.Count)
{
return count;
}
}
#endif

assert(count >= -1);
return count;
}
36 changes: 36 additions & 0 deletions src/libraries/Native/Unix/System.Native/pal_io.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,14 @@ typedef struct
// add more fields when needed.
} ProcessStatus;

// NOTE: the layout of this type is intended to exactly match the layout of a `struct iovec`. There are
// assertions in pal_networking.c that validate this.
typedef struct
{
uint8_t* Base;
uintptr_t Count;
} IOVector;

/* Provide consistent access to nanosecond fields, if they exist. */
/* Seconds are always available through st_atime, st_mtime, st_ctime. */

Expand Down Expand Up @@ -730,3 +738,31 @@ PALEXPORT int32_t SystemNative_LChflagsCanSetHiddenFlag(void);
* Returns 1 if the process status was read; otherwise, 0.
*/
PALEXPORT int32_t SystemNative_ReadProcessStatusInfo(pid_t pid, ProcessStatus* processStatus);

/**
* Reads the number of bytes specified into the provided buffer from the specified, opened file descriptor at specified offset.
*
* Returns the number of bytes read on success; otherwise, -1 is returned an errno is set.
*/
PALEXPORT int32_t SystemNative_PRead(intptr_t fd, void* buffer, int32_t bufferSize, int64_t fileOffset);

/**
* Writes the number of bytes specified in the buffer into the specified, opened file descriptor at specified offset.
*
* Returns the number of bytes written on success; otherwise, -1 is returned an errno is set.
*/
PALEXPORT int32_t SystemNative_PWrite(intptr_t fd, void* buffer, int32_t bufferSize, int64_t fileOffset);

/**
* Reads the number of bytes specified into the provided buffers from the specified, opened file descriptor at specified offset.
*
* Returns the number of bytes read on success; otherwise, -1 is returned an errno is set.
*/
PALEXPORT int64_t SystemNative_PReadV(intptr_t fd, IOVector* vectors, int32_t vectorCount, int64_t fileOffset);

/**
* Writes the number of bytes specified in the buffers into the specified, opened file descriptor at specified offset.
*
* Returns the number of bytes written on success; otherwise, -1 is returned an errno is set.
*/
PALEXPORT int64_t SystemNative_PWriteV(intptr_t fd, IOVector* vectors, int32_t vectorCount, int64_t fileOffset);
5 changes: 2 additions & 3 deletions src/libraries/Native/Unix/System.Native/pal_networking.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

#include "pal_config.h"
#include "pal_networking.h"
#include "pal_io.h"
#include "pal_safecrt.h"
#include "pal_utilities.h"
#include <pal_networking_common.h>
Expand Down Expand Up @@ -3104,11 +3103,11 @@ int32_t SystemNative_Disconnect(intptr_t socket)
addr.sa_family = AF_UNSPEC;

err = connect(fd, &addr, sizeof(addr));
if (err != 0)
if (err != 0)
{
// On some older kernels connect(AF_UNSPEC) may fail. Fall back to shutdown in these cases:
err = shutdown(fd, SHUT_RDWR);
}
}
#elif HAVE_DISCONNECTX
// disconnectx causes a FIN close on OSX. It's the best we can do.
err = disconnectx(fd, SAE_ASSOCID_ANY, SAE_CONNID_ANY);
Expand Down
Loading

0 comments on commit 9d771a2

Please sign in to comment.