未验证 提交 9d771a26 编写于 作者: A Adam Sitnik 提交者: GitHub

Set of offset-based APIs for thread-safe file IO (#53669)

Co-authored-by: NStephen Toub <stoub@microsoft.com>
上级 4e03f366
// 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;
}
}
}
......@@ -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;
......
// 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);
}
}
// 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);
}
}
// 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);
}
}
// 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);
}
}
// 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);
}
}
......@@ -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)]
......@@ -76,7 +72,7 @@ internal static partial class NtDll
}
}
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
......@@ -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)
{
......@@ -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;
......@@ -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.
......
......@@ -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
......
......@@ -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;
}
}
......@@ -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
......
......@@ -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);
......
......@@ -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>
......@@ -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;
}
......@@ -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. */
......@@ -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);
......@@ -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>
......@@ -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);
......
......@@ -4,6 +4,7 @@
#pragma once
#include "pal_compiler.h"
#include "pal_io.h"
#include "pal_types.h"
#include "pal_errno.h"
#include <pal_networking_common.h>
......@@ -275,14 +276,6 @@ typedef struct
int32_t Seconds; // Number of seconds to linger for
} LingerOption;
// NOTE: the layout of this type is intended to exactly match the layout of a `struct iovec`. There are
// assertions in pal_networking.cpp that validate this.
typedef struct
{
uint8_t* Base;
uintptr_t Count;
} IOVector;
typedef struct
{
uint8_t* SocketAddress;
......
......@@ -219,6 +219,16 @@ check_symbol_exists(
fcntl.h
HAVE_POSIX_FALLOCATE64)
check_symbol_exists(
preadv
sys/uio.h
HAVE_PREADV)
check_symbol_exists(
pwritev
sys/uio.h
HAVE_PWRITEV)
check_symbol_exists(
ioctl
sys/ioctl.h
......
......@@ -86,7 +86,7 @@ public override Task TaskAlreadyCanceledAsync()
}
}
[ActiveIssue("https://github.com/dotnet/runtime/issues/34583", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)]
[ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)]
public class File_AppendAllLinesAsync_Encoded : File_AppendAllLinesAsync
{
protected override Task WriteAsync(string path, string[] content) =>
......
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Linq;
using Xunit;
namespace System.IO.Tests
......@@ -51,6 +52,37 @@ public void ValidCreation()
}
}
[Fact]
public void CreateAndOverwrite()
{
const byte initialContent = 1;
DirectoryInfo testDir = Directory.CreateDirectory(GetTestFilePath());
string testFile = Path.Combine(testDir.FullName, GetTestFileName());
// Create
using (FileStream stream = Create(testFile))
{
Assert.True(File.Exists(testFile));
stream.WriteByte(initialContent);
Assert.Equal(1, stream.Length);
Assert.Equal(1, stream.Position);
}
Assert.Equal(initialContent, File.ReadAllBytes(testFile).Single());
// Overwrite
using (FileStream stream = Create(testFile))
{
Assert.Equal(0, stream.Length);
Assert.Equal(0, stream.Position);
}
Assert.Empty(File.ReadAllBytes(testFile));
}
[ConditionalFact(nameof(UsingNewNormalization))]
[PlatformSpecific(TestPlatforms.Windows)] // Valid Windows path extended prefix
public void ValidCreation_ExtendedSyntax()
......@@ -335,7 +367,7 @@ public void NegativeBuffer()
Assert.Throws<ArgumentOutOfRangeException>(() => Create(GetTestFilePath(), -100));
}
}
[ActiveIssue("https://github.com/dotnet/runtime/issues/34583", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)]
[ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)]
public class File_Create_str_i_fo : File_Create_str_i
{
public override FileStream Create(string path)
......
......@@ -7,7 +7,7 @@
namespace System.IO.Tests
{
[ActiveIssue("https://github.com/dotnet/runtime/issues/34583", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)]
[ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)]
public class EncryptDecrypt : FileSystemTest
{
[Fact]
......
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using Microsoft.Win32.SafeHandles;
using Xunit;
namespace System.IO.Tests
{
// to avoid a lot of code duplication, we reuse FileStream tests
public class File_OpenHandle : FileStream_ctor_options_as
{
protected override string GetExpectedParamName(string paramName) => paramName;
protected override FileStream CreateFileStream(string path, FileMode mode)
{
FileAccess access = mode == FileMode.Append ? FileAccess.Write : FileAccess.ReadWrite;
return new FileStream(File.OpenHandle(path, mode, access, preallocationSize: PreallocationSize), access);
}
protected override FileStream CreateFileStream(string path, FileMode mode, FileAccess access)
=> new FileStream(File.OpenHandle(path, mode, access, preallocationSize: PreallocationSize), access);
protected override FileStream CreateFileStream(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize, FileOptions options)
=> new FileStream(File.OpenHandle(path, mode, access, share, options, PreallocationSize), access, bufferSize, (options & FileOptions.Asynchronous) != 0);
[Fact]
public override void NegativePreallocationSizeThrows()
{
ArgumentOutOfRangeException ex = Assert.Throws<ArgumentOutOfRangeException>(
() => File.OpenHandle("validPath", FileMode.CreateNew, FileAccess.Write, FileShare.None, FileOptions.None, preallocationSize: -1));
}
[ActiveIssue("https://github.com/dotnet/runtime/issues/53432")]
[Theory, MemberData(nameof(StreamSpecifiers))]
public override void FileModeAppendExisting(string streamSpecifier)
{
_ = streamSpecifier; // to keep the xUnit analyser happy
}
[Theory]
[InlineData(FileOptions.None)]
[InlineData(FileOptions.Asynchronous)]
public void SafeFileHandle_IsAsync_ReturnsCorrectInformation(FileOptions options)
{
using (var handle = File.OpenHandle(GetTestFilePath(), FileMode.Create, FileAccess.Write, options: options))
{
Assert.Equal((options & FileOptions.Asynchronous) != 0, handle.IsAsync);
// the following code exercises the code path where we don't know FileOptions used for opening the handle
// and instead we ask the OS about it
if (OperatingSystem.IsWindows()) // async file handles are a Windows concept
{
SafeFileHandle createdFromIntPtr = new SafeFileHandle(handle.DangerousGetHandle(), ownsHandle: false);
Assert.Equal((options & FileOptions.Asynchronous) != 0, createdFromIntPtr.IsAsync);
}
}
}
// Unix doesn't directly support DeleteOnClose
// For FileStream created out of path, we mimic it by closing the handle first
// and then unlinking the path
// Since SafeFileHandle does not always have the path and we can't find path for given file descriptor on Unix
// this test runs only on Windows
[PlatformSpecific(TestPlatforms.Windows)]
[Theory]
[InlineData(FileOptions.DeleteOnClose)]
[InlineData(FileOptions.DeleteOnClose | FileOptions.Asynchronous)]
public override void DeleteOnClose_FileDeletedAfterClose(FileOptions options) => base.DeleteOnClose_FileDeletedAfterClose(options);
}
}
......@@ -8,7 +8,7 @@
namespace System.IO.Tests
{
[ActiveIssue("https://github.com/dotnet/runtime/issues/34583", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)]
[ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)]
public class File_ReadWriteAllBytesAsync : FileSystemTest
{
[Fact]
......
......@@ -10,7 +10,7 @@
namespace System.IO.Tests
{
[ActiveIssue("https://github.com/dotnet/runtime/issues/34583", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)]
[ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)]
public class File_ReadWriteAllLines_EnumerableAsync : FileSystemTest
{
#region Utilities
......
......@@ -9,7 +9,7 @@
namespace System.IO.Tests
{
[ActiveIssue("https://github.com/dotnet/runtime/issues/34583", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)]
[ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)]
public class File_ReadWriteAllTextAsync : FileSystemTest
{
#region Utilities
......@@ -145,7 +145,7 @@ public virtual Task TaskAlreadyCanceledAsync()
#endregion
}
[ActiveIssue("https://github.com/dotnet/runtime/issues/34583", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)]
[ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)]
public class File_ReadWriteAllText_EncodedAsync : File_ReadWriteAllTextAsync
{
protected override Task WriteAsync(string path, string content) =>
......
......@@ -8,7 +8,7 @@
namespace System.IO.Tests
{
[ActiveIssue("https://github.com/dotnet/runtime/issues/34583", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)]
[ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)]
public class FileStream_CopyToAsync : FileSystemTest
{
[Theory]
......
......@@ -205,7 +205,7 @@ public class BufferedSyncFileStreamStandaloneConformanceTests : FileStreamStanda
protected override int BufferSize => 10;
}
[ActiveIssue("https://github.com/dotnet/runtime/issues/34583", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)]
[ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)]
[SkipOnPlatform(TestPlatforms.Browser, "lots of operations aren't supported on browser")] // copied from StreamConformanceTests base class due to https://github.com/xunit/xunit/issues/2186
public class UnbufferedAsyncFileStreamStandaloneConformanceTests : FileStreamStandaloneConformanceTests
{
......@@ -218,7 +218,7 @@ public class UnbufferedAsyncFileStreamStandaloneConformanceTests : FileStreamSta
#endif
}
[ActiveIssue("https://github.com/dotnet/runtime/issues/34583", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)]
[ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)]
[SkipOnPlatform(TestPlatforms.Browser, "lots of operations aren't supported on browser")] // copied from StreamConformanceTests base class due to https://github.com/xunit/xunit/issues/2186
public class BufferedAsyncFileStreamStandaloneConformanceTests : FileStreamStandaloneConformanceTests
{
......
......@@ -8,7 +8,7 @@
namespace System.IO.Tests
{
[ActiveIssue("https://github.com/dotnet/runtime/issues/34583", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)]
[ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)]
public class FileStream_IsAsync : FileSystemTest
{
[Fact]
......
......@@ -94,14 +94,14 @@ public async Task ReadAsyncCanceledFile()
}
}
[ActiveIssue("https://github.com/dotnet/runtime/issues/34583", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)]
[ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)]
public class FileStream_ReadAsync_AsyncReads : FileStream_AsyncReads
{
protected override Task<int> ReadAsync(FileStream stream, byte[] buffer, int offset, int count, CancellationToken cancellationToken) =>
stream.ReadAsync(buffer, offset, count, cancellationToken);
}
[ActiveIssue("https://github.com/dotnet/runtime/issues/34583", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)]
[ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)]
public class FileStream_BeginEndRead_AsyncReads : FileStream_AsyncReads
{
protected override Task<int> ReadAsync(FileStream stream, byte[] buffer, int offset, int count, CancellationToken cancellationToken) =>
......
......@@ -9,7 +9,7 @@
namespace System.IO.Tests
{
[ActiveIssue("https://github.com/dotnet/runtime/issues/34583", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)]
[ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)]
public class FileStream_SafeFileHandle : FileSystemTest
{
[Fact]
......
......@@ -324,7 +324,7 @@ public async Task WriteAsyncMiniStress()
}
}
[ActiveIssue("https://github.com/dotnet/runtime/issues/34583", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)]
[ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)]
public class FileStream_WriteAsync_AsyncWrites : FileStream_AsyncWrites
{
protected override Task WriteAsync(FileStream stream, byte[] buffer, int offset, int count, CancellationToken cancellationToken) =>
......@@ -371,7 +371,7 @@ public void CancelledTokenFastPath()
}
}
[ActiveIssue("https://github.com/dotnet/runtime/issues/34583", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)]
[ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)]
public class FileStream_BeginEndWrite_AsyncWrites : FileStream_AsyncWrites
{
protected override Task WriteAsync(FileStream stream, byte[] buffer, int offset, int count, CancellationToken cancellationToken) =>
......
......@@ -69,7 +69,7 @@ public partial class NoParallelTests { }
public partial class FileStream_ctor_options_as : FileStream_ctor_options_as_base
{
[Fact]
public void NegativePreallocationSizeThrows()
public virtual void NegativePreallocationSizeThrows()
{
string filePath = GetPathToNonExistingFile();
ArgumentOutOfRangeException ex = Assert.Throws<ArgumentOutOfRangeException>(
......
......@@ -25,7 +25,7 @@ public void InvalidHandle_Throws()
[Fact]
public void InvalidAccess_Throws()
{
using (var handle = new SafeFileHandle(new IntPtr(1), ownsHandle: false))
using (var handle = File.OpenHandle(GetTestFilePath(), FileMode.Create, FileAccess.Write))
{
AssertExtensions.Throws<ArgumentOutOfRangeException>("access", () => CreateFileStream(handle, ~FileAccess.Read));
}
......@@ -34,7 +34,7 @@ public void InvalidAccess_Throws()
[Fact]
public void InvalidAccess_DoesNotCloseHandle()
{
using (var handle = new SafeFileHandle(new IntPtr(1), ownsHandle: false))
using (var handle = File.OpenHandle(GetTestFilePath(), FileMode.Create, FileAccess.Write))
{
Assert.Throws<ArgumentOutOfRangeException>(() => CreateFileStream(handle, ~FileAccess.Read));
GC.Collect();
......
......@@ -18,21 +18,19 @@ protected virtual FileStream CreateFileStream(SafeFileHandle handle, FileAccess
return new FileStream(handle, access, bufferSize);
}
[Theory,
InlineData(0),
InlineData(-1)]
public void InvalidBufferSize_Throws(int size)
[Fact]
public void NegativeBufferSize_Throws()
{
using (var handle = new SafeFileHandle(new IntPtr(1), ownsHandle: false))
using (var handle = File.OpenHandle(GetTestFilePath(), FileMode.Create, FileAccess.Write))
{
AssertExtensions.Throws<ArgumentOutOfRangeException>("bufferSize", () => CreateFileStream(handle, FileAccess.Read, size));
AssertExtensions.Throws<ArgumentOutOfRangeException>("bufferSize", () => CreateFileStream(handle, FileAccess.Read, -1));
}
}
[Fact]
public void InvalidBufferSize_DoesNotCloseHandle()
{
using (var handle = new SafeFileHandle(new IntPtr(1), ownsHandle: false))
using (var handle = File.OpenHandle(GetTestFilePath(), FileMode.Create, FileAccess.Write))
{
Assert.Throws<ArgumentOutOfRangeException>(() => CreateFileStream(handle, FileAccess.Read, -1));
GC.Collect();
......
......@@ -6,7 +6,7 @@
namespace System.IO.Tests
{
[ActiveIssue("https://github.com/dotnet/runtime/issues/34583", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)]
[ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)]
public class FileStream_ctor_sfh_fa_buffer_async : FileStream_ctor_sfh_fa_buffer
{
protected sealed override FileStream CreateFileStream(SafeFileHandle handle, FileAccess access, int bufferSize)
......
......@@ -5,7 +5,7 @@
namespace System.IO.Tests
{
[ActiveIssue("https://github.com/dotnet/runtime/issues/34583", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)]
[ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)]
public class FileStream_ctor_str_fm_fa_fs_buffer_fo : FileStream_ctor_str_fm_fa_fs_buffer
{
protected sealed override FileStream CreateFileStream(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize)
......@@ -79,7 +79,7 @@ public void ValidFileOptions_Encrypted(FileOptions option)
[Theory]
[InlineData(FileOptions.DeleteOnClose)]
[InlineData(FileOptions.DeleteOnClose | FileOptions.Asynchronous)]
public void DeleteOnClose_FileDeletedAfterClose(FileOptions options)
public virtual void DeleteOnClose_FileDeletedAfterClose(FileOptions options)
{
string path = GetTestFilePath();
Assert.False(File.Exists(path));
......
......@@ -19,10 +19,12 @@
<ItemGroup Condition="'$(TargetsWindows)' == 'true'">
<Compile Remove="..\**\*.Unix.cs" />
<Compile Remove="..\**\*.Browser.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Interop.BOOL.cs" Link="Interop\Windows\Interop.BOOL.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.FILE_STANDARD_INFO.cs" Link="Interop\Windows\Interop.FILE_STANDARD_INFO.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Interop.Libraries.cs" Link="Interop\Windows\Interop.Libraries.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.GetFileInformationByHandleEx.cs" Link="Interop\Windows\Interop.GetFileInformationByHandleEx.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Interop.BOOL.cs" Link="Common\Interop\Windows\Interop.BOOL.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Interop.Libraries.cs" Link="Common\Interop\Windows\Interop.Libraries.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.FILE_STANDARD_INFO.cs" Link="Common\Interop\Windows\Interop.FILE_STANDARD_INFO.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.GetFileInformationByHandleEx.cs" Link="Common\Interop\Windows\Interop.GetFileInformationByHandleEx.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.MemOptions.cs" Link="Common\Interop\Windows\Interop.MemOptions.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.VirtualAlloc_Ptr.cs" Link="Common\Interop\Windows\Interop.VirtualAlloc_Ptr.cs" />
</ItemGroup>
<ItemGroup Condition="'$(TargetsBrowser)' == 'true'">
<Compile Remove="..\**\*.Unix.cs" />
......
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.IO.Pipes;
using System.Threading;
using Microsoft.Win32.SafeHandles;
using Xunit;
namespace System.IO.Tests
{
public abstract class RandomAccess_Base<T> : FileSystemTest
{
protected abstract T MethodUnderTest(SafeFileHandle handle, byte[] bytes, long fileOffset);
protected virtual bool ShouldThrowForSyncHandle => false;
protected virtual bool ShouldThrowForAsyncHandle => false;
protected virtual bool UsesOffsets => true;
[Fact]
public void ThrowsArgumentNullExceptionForNullHandle()
{
AssertExtensions.Throws<ArgumentNullException>("handle", () => MethodUnderTest(null, Array.Empty<byte>(), 0));
}
[Fact]
public void ThrowsArgumentExceptionForInvalidHandle()
{
SafeFileHandle handle = new SafeFileHandle(new IntPtr(-1), ownsHandle: false);
AssertExtensions.Throws<ArgumentException>("handle", () => MethodUnderTest(handle, Array.Empty<byte>(), 0));
}
[Fact]
public void ThrowsObjectDisposedExceptionForDisposedHandle()
{
SafeFileHandle handle = File.OpenHandle(GetTestFilePath(), FileMode.Create, FileAccess.Write);
handle.Dispose();
Assert.Throws<ObjectDisposedException>(() => MethodUnderTest(handle, Array.Empty<byte>(), 0));
}
[Fact]
[SkipOnPlatform(TestPlatforms.Browser, "System.IO.Pipes aren't supported on browser")]
public void ThrowsNotSupportedExceptionForUnseekableFile()
{
using (var server = new AnonymousPipeServerStream(PipeDirection.Out))
using (SafeFileHandle handle = new SafeFileHandle(server.SafePipeHandle.DangerousGetHandle(), true))
{
Assert.Throws<NotSupportedException>(() => MethodUnderTest(handle, Array.Empty<byte>(), 0));
}
}
[Fact]
public void ThrowsArgumentOutOfRangeExceptionForNegativeFileOffset()
{
if (UsesOffsets)
{
FileOptions options = ShouldThrowForAsyncHandle ? FileOptions.None : FileOptions.Asynchronous;
using (SafeFileHandle handle = File.OpenHandle(GetTestFilePath(), FileMode.CreateNew, FileAccess.Write, options: options))
{
AssertExtensions.Throws<ArgumentOutOfRangeException>("fileOffset", () => MethodUnderTest(handle, Array.Empty<byte>(), -1));
}
}
}
[Fact]
[ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)]
[SkipOnPlatform(TestPlatforms.Browser, "async file IO is not supported on browser")]
public void ThrowsArgumentExceptionForAsyncFileHandle()
{
if (ShouldThrowForAsyncHandle)
{
using (SafeFileHandle handle = File.OpenHandle(GetTestFilePath(), FileMode.CreateNew, FileAccess.Write, options: FileOptions.Asynchronous))
{
AssertExtensions.Throws<ArgumentException>("handle", () => MethodUnderTest(handle, new byte[100], 0));
}
}
}
[Fact]
public void ThrowsArgumentExceptionForSyncFileHandle()
{
if (ShouldThrowForSyncHandle)
{
using (SafeFileHandle handle = File.OpenHandle(GetTestFilePath(), FileMode.CreateNew, FileAccess.Write, options: FileOptions.None))
{
AssertExtensions.Throws<ArgumentException>("handle", () => MethodUnderTest(handle, new byte[100], 0));
}
}
}
protected static CancellationTokenSource GetCancelledTokenSource()
{
CancellationTokenSource source = new CancellationTokenSource();
source.Cancel();
return source;
}
protected SafeFileHandle GetHandleToExistingFile(FileAccess access)
{
string filePath = GetTestFilePath();
File.WriteAllBytes(filePath, new byte[1]);
FileOptions options = ShouldThrowForAsyncHandle ? FileOptions.None : FileOptions.Asynchronous;
return File.OpenHandle(filePath, FileMode.Open, access, FileShare.None, options);
}
}
}
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using Microsoft.Win32.SafeHandles;
using Xunit;
namespace System.IO.Tests
{
public class RandomAccess_GetLength : RandomAccess_Base<long>
{
protected override long MethodUnderTest(SafeFileHandle handle, byte[] bytes, long fileOffset)
=> RandomAccess.GetLength(handle);
protected override bool UsesOffsets => false;
[Fact]
public void ReturnsZeroForEmptyFile()
{
using (SafeFileHandle handle = File.OpenHandle(GetTestFilePath(), FileMode.CreateNew, FileAccess.Write))
{
Assert.Equal(0, RandomAccess.GetLength(handle));
}
}
[Fact]
public void ReturnsExactSizeForNonEmptyFiles()
{
const int fileSize = 123;
string filePath = GetTestFilePath();
File.WriteAllBytes(filePath, new byte[fileSize]);
using (SafeFileHandle handle = File.OpenHandle(filePath, FileMode.Open))
{
Assert.Equal(fileSize, RandomAccess.GetLength(handle));
}
}
}
}
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Security.Cryptography;
using System.Threading.Tasks;
using Microsoft.Win32.SafeHandles;
using Xunit;
namespace System.IO.Tests
{
[ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)]
[SkipOnPlatform(TestPlatforms.Browser, "async file IO is not supported on browser")]
public class RandomAccess_NoBuffering : FileSystemTest
{
private const FileOptions NoBuffering = (FileOptions)0x20000000;
[Fact]
public async Task ReadAsyncUsingSingleBuffer()
{
const int fileSize = 1_000_000; // 1 MB
string filePath = GetTestFilePath();
byte[] expected = RandomNumberGenerator.GetBytes(fileSize);
File.WriteAllBytes(filePath, expected);
using (SafeFileHandle handle = File.OpenHandle(filePath, FileMode.Open, options: FileOptions.Asynchronous | NoBuffering))
using (SectorAlignedMemory<byte> buffer = SectorAlignedMemory<byte>.Allocate(Environment.SystemPageSize))
{
int current = 0;
int total = 0;
// From https://docs.microsoft.com/en-us/windows/win32/fileio/file-buffering:
// "File access sizes, including the optional file offset in the OVERLAPPED structure,
// if specified, must be for a number of bytes that is an integer multiple of the volume sector size."
// So if buffer and physical sector size is 4096 and the file size is 4097:
// the read from offset=0 reads 4096 bytes
// the read from offset=4096 reads 1 byte
// the read from offset=4097 THROWS (Invalid argument, offset is not a multiple of sector size!)
// That is why we stop at the first incomplete read (the next one would throw).
// It's possible to get 0 if we are lucky and file size is a multiple of physical sector size.
do
{
current = await RandomAccess.ReadAsync(handle, buffer.Memory, fileOffset: total);
Assert.True(expected.AsSpan(total, current).SequenceEqual(buffer.GetSpan().Slice(0, current)));
total += current;
}
while (current == buffer.Memory.Length);
Assert.Equal(fileSize, total);
}
}
[Fact]
public async Task ReadAsyncUsingMultipleBuffers()
{
const int fileSize = 1_000_000; // 1 MB
string filePath = GetTestFilePath();
byte[] expected = RandomNumberGenerator.GetBytes(fileSize);
File.WriteAllBytes(filePath, expected);
using (SafeFileHandle handle = File.OpenHandle(filePath, FileMode.Open, options: FileOptions.Asynchronous | NoBuffering))
using (SectorAlignedMemory<byte> buffer_1 = SectorAlignedMemory<byte>.Allocate(Environment.SystemPageSize))
using (SectorAlignedMemory<byte> buffer_2 = SectorAlignedMemory<byte>.Allocate(Environment.SystemPageSize))
{
long current = 0;
long total = 0;
do
{
current = await RandomAccess.ReadAsync(
handle,
new Memory<byte>[]
{
buffer_1.Memory,
buffer_2.Memory,
},
fileOffset: total);
int takeFromFirst = Math.Min(buffer_1.Memory.Length, (int)current);
Assert.True(expected.AsSpan((int)total, takeFromFirst).SequenceEqual(buffer_1.GetSpan().Slice(0, takeFromFirst)));
int takeFromSecond = (int)current - takeFromFirst;
Assert.True(expected.AsSpan((int)total + takeFromFirst, takeFromSecond).SequenceEqual(buffer_2.GetSpan().Slice(0, takeFromSecond)));
total += current;
} while (current == buffer_1.Memory.Length + buffer_2.Memory.Length);
Assert.Equal(fileSize, total);
}
}
[Fact]
public async Task WriteAsyncUsingSingleBuffer()
{
string filePath = GetTestFilePath();
int bufferSize = Environment.SystemPageSize;
int fileSize = bufferSize * 10;
byte[] content = RandomNumberGenerator.GetBytes(fileSize);
using (SafeFileHandle handle = File.OpenHandle(filePath, FileMode.CreateNew, FileAccess.Write, FileShare.None, FileOptions.Asynchronous | NoBuffering))
using (SectorAlignedMemory<byte> buffer = SectorAlignedMemory<byte>.Allocate(bufferSize))
{
int total = 0;
while (total != fileSize)
{
int take = Math.Min(content.Length - total, bufferSize);
content.AsSpan(total, take).CopyTo(buffer.GetSpan());
total += await RandomAccess.WriteAsync(
handle,
buffer.Memory,
fileOffset: total);
}
}
Assert.Equal(content, File.ReadAllBytes(filePath));
}
[Fact]
public async Task WriteAsyncUsingMultipleBuffers()
{
string filePath = GetTestFilePath();
int bufferSize = Environment.SystemPageSize;
int fileSize = bufferSize * 10;
byte[] content = RandomNumberGenerator.GetBytes(fileSize);
using (SafeFileHandle handle = File.OpenHandle(filePath, FileMode.CreateNew, FileAccess.Write, FileShare.None, FileOptions.Asynchronous | NoBuffering))
using (SectorAlignedMemory<byte> buffer_1 = SectorAlignedMemory<byte>.Allocate(bufferSize))
using (SectorAlignedMemory<byte> buffer_2 = SectorAlignedMemory<byte>.Allocate(bufferSize))
{
long total = 0;
while (total != fileSize)
{
content.AsSpan((int)total, bufferSize).CopyTo(buffer_1.GetSpan());
content.AsSpan((int)total + bufferSize, bufferSize).CopyTo(buffer_2.GetSpan());
total += await RandomAccess.WriteAsync(
handle,
new ReadOnlyMemory<byte>[]
{
buffer_1.Memory,
buffer_2.Memory,
},
fileOffset: total);
}
}
Assert.Equal(content, File.ReadAllBytes(filePath));
}
}
}
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Linq;
using System.Security.Cryptography;
using Microsoft.Win32.SafeHandles;
using Xunit;
namespace System.IO.Tests
{
public class RandomAccess_Read : RandomAccess_Base<int>
{
protected override int MethodUnderTest(SafeFileHandle handle, byte[] bytes, long fileOffset)
=> RandomAccess.Read(handle, bytes, fileOffset);
protected override bool ShouldThrowForAsyncHandle
=> OperatingSystem.IsWindows(); // on Windows we can NOT perform sync IO using async handle
[Fact]
public void ThrowsOnWriteAccess()
{
using (SafeFileHandle handle = GetHandleToExistingFile(FileAccess.Write))
{
Assert.Throws<UnauthorizedAccessException>(() => RandomAccess.Read(handle, new byte[1], 0));
}
}
[Fact]
public void ReadToAnEmptyBufferReturnsZero()
{
string filePath = GetTestFilePath();
File.WriteAllBytes(filePath, new byte[1]);
using (SafeFileHandle handle = File.OpenHandle(filePath, FileMode.Open))
{
Assert.Equal(0, RandomAccess.Read(handle, Array.Empty<byte>(), fileOffset: 0));
}
}
[Fact]
public void ReadsBytesFromGivenFileAtGivenOffset()
{
const int fileSize = 4_001;
string filePath = GetTestFilePath();
byte[] expected = RandomNumberGenerator.GetBytes(fileSize);
File.WriteAllBytes(filePath, expected);
using (SafeFileHandle handle = File.OpenHandle(filePath, FileMode.Open))
{
byte[] actual = new byte[fileSize + 1];
int current = 0;
int total = 0;
do
{
Span<byte> buffer = actual.AsSpan(total, Math.Min(actual.Length - total, fileSize / 4));
current = RandomAccess.Read(handle, buffer, fileOffset: total);
Assert.InRange(current, 0, buffer.Length);
total += current;
} while (current != 0);
Assert.Equal(fileSize, total);
Assert.Equal(expected, actual.Take(total).ToArray());
}
}
}
}
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Linq;
using System.Security.Cryptography;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Win32.SafeHandles;
using Xunit;
namespace System.IO.Tests
{
[ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)]
[SkipOnPlatform(TestPlatforms.Browser, "async file IO is not supported on browser")]
public class RandomAccess_ReadAsync : RandomAccess_Base<ValueTask<int>>
{
protected override ValueTask<int> MethodUnderTest(SafeFileHandle handle, byte[] bytes, long fileOffset)
=> RandomAccess.ReadAsync(handle, bytes, fileOffset);
protected override bool ShouldThrowForSyncHandle
=> OperatingSystem.IsWindows(); // on Windows we can NOT perform async IO using sync handle
[Fact]
public async Task TaskAlreadyCanceledAsync()
{
using (SafeFileHandle handle = File.OpenHandle(GetTestFilePath(), FileMode.CreateNew, FileAccess.ReadWrite, options: FileOptions.Asynchronous))
{
CancellationTokenSource cts = GetCancelledTokenSource();
CancellationToken token = cts.Token;
Assert.True(RandomAccess.ReadAsync(handle, new byte[1], 0, token).IsCanceled);
TaskCanceledException ex = await Assert.ThrowsAsync<TaskCanceledException>(() => RandomAccess.ReadAsync(handle, new byte[1], 0, token).AsTask());
Assert.Equal(token, ex.CancellationToken);
}
}
[Fact]
public async Task ThrowsOnWriteAccess()
{
using (SafeFileHandle handle = GetHandleToExistingFile(FileAccess.Write))
{
await Assert.ThrowsAsync<UnauthorizedAccessException>(async () => await RandomAccess.ReadAsync(handle, new byte[1], 0));
}
}
[Fact]
public async Task ReadToAnEmptyBufferReturnsZeroAsync()
{
string filePath = GetTestFilePath();
File.WriteAllBytes(filePath, new byte[1]);
using (SafeFileHandle handle = File.OpenHandle(filePath, FileMode.Open, options: FileOptions.Asynchronous))
{
Assert.Equal(0, await RandomAccess.ReadAsync(handle, Array.Empty<byte>(), fileOffset: 0));
}
}
[Fact]
public async Task ReadsBytesFromGivenFileAtGivenOffsetAsync()
{
const int fileSize = 4_001;
string filePath = GetTestFilePath();
byte[] expected = RandomNumberGenerator.GetBytes(fileSize);
File.WriteAllBytes(filePath, expected);
using (SafeFileHandle handle = File.OpenHandle(filePath, FileMode.Open, options: FileOptions.Asynchronous))
{
byte[] actual = new byte[fileSize + 1];
int current = 0;
int total = 0;
do
{
Memory<byte> buffer = actual.AsMemory(total, Math.Min(actual.Length - total, fileSize / 4));
current = await RandomAccess.ReadAsync(handle, buffer, fileOffset: total);
Assert.InRange(current, 0, buffer.Length);
total += current;
} while (current != 0);
Assert.Equal(fileSize, total);
Assert.Equal(expected, actual.Take(total).ToArray());
}
}
}
}
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Linq;
using System.Security.Cryptography;
using Microsoft.Win32.SafeHandles;
using Xunit;
namespace System.IO.Tests
{
public class RandomAccess_ReadScatter : RandomAccess_Base<long>
{
protected override long MethodUnderTest(SafeFileHandle handle, byte[] bytes, long fileOffset)
=> RandomAccess.Read(handle, new Memory<byte>[] { bytes }, fileOffset);
protected override bool ShouldThrowForAsyncHandle
=> OperatingSystem.IsWindows(); // on Windows we can NOT perform sync IO using async handle
[Fact]
public void ThrowsArgumentNullExceptionForNullBuffers()
{
using (SafeFileHandle handle = File.OpenHandle(GetTestFilePath(), FileMode.CreateNew, FileAccess.Write))
{
AssertExtensions.Throws<ArgumentNullException>("buffers", () => RandomAccess.Read(handle, buffers: null, 0));
}
}
[Fact]
public void ThrowsOnWriteAccess()
{
using (SafeFileHandle handle = GetHandleToExistingFile(FileAccess.Write))
{
Assert.Throws<UnauthorizedAccessException>(() => RandomAccess.Read(handle, new Memory<byte>[] { new byte[1] }, 0));
}
}
[Fact]
public void ReadToAnEmptyBufferReturnsZero()
{
string filePath = GetTestFilePath();
File.WriteAllBytes(filePath, new byte[1]);
using (SafeFileHandle handle = File.OpenHandle(filePath, FileMode.Open))
{
Assert.Equal(0, RandomAccess.Read(handle, new Memory<byte>[] { Array.Empty<byte>() }, fileOffset: 0));
}
}
[Fact]
public void ReadsBytesFromGivenFileAtGivenOffset()
{
const int fileSize = 4_001;
string filePath = GetTestFilePath();
byte[] expected = RandomNumberGenerator.GetBytes(fileSize);
File.WriteAllBytes(filePath, expected);
using (SafeFileHandle handle = File.OpenHandle(filePath, FileMode.Open))
{
byte[] actual = new byte[fileSize + 1];
long current = 0;
long total = 0;
do
{
int firstBufferLength = (int)Math.Min(actual.Length - total, fileSize / 4);
Memory<byte> buffer_1 = actual.AsMemory((int)total, firstBufferLength);
Memory<byte> buffer_2 = actual.AsMemory((int)total + firstBufferLength);
current = RandomAccess.Read(
handle,
new Memory<byte>[]
{
buffer_1,
Array.Empty<byte>(),
buffer_2
},
fileOffset: total);
Assert.InRange(current, 0, buffer_1.Length + buffer_2.Length);
total += current;
} while (current != 0);
Assert.Equal(fileSize, total);
Assert.Equal(expected, actual.Take((int)total).ToArray());
}
}
[Fact]
public void ReadToTheSameBufferOverwritesContent()
{
string filePath = GetTestFilePath();
File.WriteAllBytes(filePath, new byte[3] { 1, 2, 3 });
using (SafeFileHandle handle = File.OpenHandle(filePath, FileMode.Open))
{
byte[] buffer = new byte[1];
Assert.Equal(buffer.Length + buffer.Length, RandomAccess.Read(handle, Enumerable.Repeat(buffer.AsMemory(), 2).ToList(), fileOffset: 0));
Assert.Equal(2, buffer[0]);
}
}
}
}
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Linq;
using System.Security.Cryptography;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Win32.SafeHandles;
using Xunit;
namespace System.IO.Tests
{
[ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)]
[SkipOnPlatform(TestPlatforms.Browser, "async file IO is not supported on browser")]
public class RandomAccess_ReadScatterAsync : RandomAccess_Base<ValueTask<long>>
{
protected override ValueTask<long> MethodUnderTest(SafeFileHandle handle, byte[] bytes, long fileOffset)
=> RandomAccess.ReadAsync(handle, new Memory<byte>[] { bytes }, fileOffset);
protected override bool ShouldThrowForSyncHandle
=> OperatingSystem.IsWindows(); // on Windows we can NOT perform async IO using sync handle
[Fact]
public void ThrowsArgumentNullExceptionForNullBuffers()
{
using (SafeFileHandle handle = File.OpenHandle(GetTestFilePath(), FileMode.CreateNew, FileAccess.Write, options: FileOptions.Asynchronous))
{
AssertExtensions.Throws<ArgumentNullException>("buffers", () => RandomAccess.ReadAsync(handle, buffers: null, 0));
}
}
[Fact]
public async Task TaskAlreadyCanceledAsync()
{
using (SafeFileHandle handle = File.OpenHandle(GetTestFilePath(), FileMode.CreateNew, FileAccess.ReadWrite, options: FileOptions.Asynchronous))
{
CancellationTokenSource cts = GetCancelledTokenSource();
CancellationToken token = cts.Token;
Assert.True(RandomAccess.ReadAsync(handle, new Memory<byte>[] { new byte[1] }, 0, token).IsCanceled);
TaskCanceledException ex = await Assert.ThrowsAsync<TaskCanceledException>(() => RandomAccess.ReadAsync(handle, new Memory<byte>[] { new byte[1] }, 0, token).AsTask());
Assert.Equal(token, ex.CancellationToken);
}
}
[Fact]
public async Task ThrowsOnWriteAccess()
{
using (SafeFileHandle handle = GetHandleToExistingFile(FileAccess.Write))
{
await Assert.ThrowsAsync<UnauthorizedAccessException>(async () => await RandomAccess.ReadAsync(handle, new Memory<byte>[] { new byte[1] }, 0));
}
}
[Fact]
public async Task ReadToAnEmptyBufferReturnsZeroAsync()
{
string filePath = GetTestFilePath();
File.WriteAllBytes(filePath, new byte[1]);
using (SafeFileHandle handle = File.OpenHandle(filePath, FileMode.Open, options: FileOptions.Asynchronous))
{
Assert.Equal(0, await RandomAccess.ReadAsync(handle, new Memory<byte>[] { Array.Empty<byte>() }, fileOffset: 0));
}
}
[Fact]
public async Task ReadsBytesFromGivenFileAtGivenOffsetAsync()
{
const int fileSize = 4_001;
string filePath = GetTestFilePath();
byte[] expected = RandomNumberGenerator.GetBytes(fileSize);
File.WriteAllBytes(filePath, expected);
using (SafeFileHandle handle = File.OpenHandle(filePath, FileMode.Open, options: FileOptions.Asynchronous))
{
byte[] actual = new byte[fileSize + 1];
long current = 0;
long total = 0;
do
{
int firstBufferLength = (int)Math.Min(actual.Length - total, fileSize / 4);
Memory<byte> buffer_1 = actual.AsMemory((int)total, firstBufferLength);
Memory<byte> buffer_2 = actual.AsMemory((int)total + firstBufferLength);
current = await RandomAccess.ReadAsync(
handle,
new Memory<byte>[]
{
buffer_1,
buffer_2
},
fileOffset: total);
Assert.InRange(current, 0, buffer_1.Length + buffer_2.Length);
total += current;
} while (current != 0);
Assert.Equal(fileSize, total);
Assert.Equal(expected, actual.Take((int)total).ToArray());
}
}
[Fact]
public async Task ReadToTheSameBufferOverwritesContent()
{
string filePath = GetTestFilePath();
File.WriteAllBytes(filePath, new byte[3] { 1, 2, 3 });
using (SafeFileHandle handle = File.OpenHandle(filePath, FileMode.Open, options: FileOptions.Asynchronous))
{
byte[] buffer = new byte[1];
Assert.Equal(buffer.Length + buffer.Length, await RandomAccess.ReadAsync(handle, Enumerable.Repeat(buffer.AsMemory(), 2).ToList(), fileOffset: 0));
Assert.Equal(2, buffer[0]);
}
}
}
}
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Buffers;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading;
using static Interop.Kernel32;
namespace System.IO.Tests
{
internal sealed class SectorAlignedMemory<T> : MemoryManager<T>
{
private bool _disposed;
private int _refCount;
private IntPtr _memory;
private int _length;
private unsafe SectorAlignedMemory(void* memory, int length)
{
_memory = (IntPtr)memory;
_length = length;
}
public static unsafe SectorAlignedMemory<T> Allocate(int length)
{
void* memory = VirtualAlloc(
IntPtr.Zero.ToPointer(),
new UIntPtr((uint)(Marshal.SizeOf<T>() * length)),
MemOptions.MEM_COMMIT | MemOptions.MEM_RESERVE,
PageOptions.PAGE_READWRITE);
return new SectorAlignedMemory<T>(memory, length);
}
public bool IsDisposed => _disposed;
public unsafe override Span<T> GetSpan() => new Span<T>((void*)_memory, _length);
public override MemoryHandle Pin(int elementIndex = 0)
{
unsafe
{
Retain();
if ((uint)elementIndex > _length) throw new ArgumentOutOfRangeException(nameof(elementIndex));
void* pointer = Unsafe.Add<T>((void*)_memory, elementIndex);
return new MemoryHandle(pointer, default, this);
}
}
private bool Release()
{
int newRefCount = Interlocked.Decrement(ref _refCount);
if (newRefCount < 0)
{
throw new InvalidOperationException("Unmatched Release/Retain");
}
return newRefCount != 0;
}
private void Retain()
{
if (_disposed)
{
throw new ObjectDisposedException(nameof(SectorAlignedMemory<T>));
}
Interlocked.Increment(ref _refCount);
}
protected override unsafe void Dispose(bool disposing)
{
if (_disposed)
{
return;
}
VirtualAlloc(
_memory.ToPointer(),
new UIntPtr((uint)(Marshal.SizeOf<T>() * _length)),
MemOptions.MEM_FREE,
PageOptions.PAGE_READWRITE);
_memory = IntPtr.Zero;
_disposed = true;
}
protected override bool TryGetArray(out ArraySegment<T> arraySegment)
{
// cannot expose managed array
arraySegment = default;
return false;
}
public override void Unpin()
{
Release();
}
}
}
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Security.Cryptography;
using Microsoft.Win32.SafeHandles;
using Xunit;
namespace System.IO.Tests
{
public class RandomAccess_Write : RandomAccess_Base<int>
{
protected override int MethodUnderTest(SafeFileHandle handle, byte[] bytes, long fileOffset)
=> RandomAccess.Write(handle, bytes, fileOffset);
protected override bool ShouldThrowForAsyncHandle
=> OperatingSystem.IsWindows(); // on Windows we can NOT perform sync IO using async handle
[Fact]
public void ThrowsOnReadAccess()
{
using (SafeFileHandle handle = GetHandleToExistingFile(FileAccess.Read))
{
Assert.Throws<UnauthorizedAccessException>(() => RandomAccess.Write(handle, new byte[1], 0));
}
}
[Fact]
public void WriteUsingEmptyBufferReturnsZero()
{
using (SafeFileHandle handle = File.OpenHandle(GetTestFilePath(), FileMode.Create, FileAccess.Write))
{
Assert.Equal(0, RandomAccess.Write(handle, Array.Empty<byte>(), fileOffset: 0));
}
}
[Fact]
public void WritesBytesFromGivenBufferToGivenFileAtGivenOffset()
{
const int fileSize = 4_001;
string filePath = GetTestFilePath();
byte[] content = RandomNumberGenerator.GetBytes(fileSize);
using (SafeFileHandle handle = File.OpenHandle(filePath, FileMode.CreateNew, FileAccess.Write, FileShare.None))
{
int total = 0;
int current = 0;
while (total != fileSize)
{
Span<byte> buffer = content.AsSpan(total, Math.Min(content.Length - total, fileSize / 4));
current = RandomAccess.Write(handle, buffer, fileOffset: total);
Assert.InRange(current, 0, buffer.Length);
total += current;
}
}
Assert.Equal(content, File.ReadAllBytes(filePath));
}
}
}
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Security.Cryptography;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Win32.SafeHandles;
using Xunit;
namespace System.IO.Tests
{
[ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)]
[SkipOnPlatform(TestPlatforms.Browser, "async file IO is not supported on browser")]
public class RandomAccess_WriteAsync : RandomAccess_Base<ValueTask<int>>
{
protected override ValueTask<int> MethodUnderTest(SafeFileHandle handle, byte[] bytes, long fileOffset)
=> RandomAccess.WriteAsync(handle, bytes, fileOffset);
protected override bool ShouldThrowForSyncHandle
=> OperatingSystem.IsWindows(); // on Windows we can NOT perform async IO using sync handle
[Fact]
public async Task TaskAlreadyCanceledAsync()
{
using (SafeFileHandle handle = File.OpenHandle(GetTestFilePath(), FileMode.CreateNew, FileAccess.ReadWrite, options: FileOptions.Asynchronous))
{
CancellationTokenSource cts = GetCancelledTokenSource();
CancellationToken token = cts.Token;
Assert.True(RandomAccess.WriteAsync(handle, new byte[1], 0, token).IsCanceled);
TaskCanceledException ex = await Assert.ThrowsAsync<TaskCanceledException>(() => RandomAccess.WriteAsync(handle, new byte[1], 0, token).AsTask());
Assert.Equal(token, ex.CancellationToken);
}
}
[Fact]
public async Task ThrowsOnReadAccess()
{
using (SafeFileHandle handle = GetHandleToExistingFile(FileAccess.Read))
{
await Assert.ThrowsAsync<UnauthorizedAccessException>(async () => await RandomAccess.WriteAsync(handle, new byte[1], 0));
}
}
[Fact]
public async Task WriteUsingEmptyBufferReturnsZeroAsync()
{
using (SafeFileHandle handle = File.OpenHandle(GetTestFilePath(), FileMode.Create, FileAccess.Write, options: FileOptions.Asynchronous))
{
Assert.Equal(0, await RandomAccess.WriteAsync(handle, Array.Empty<byte>(), fileOffset: 0));
}
}
[Fact]
public async Task WritesBytesFromGivenBufferToGivenFileAtGivenOffsetAsync()
{
const int fileSize = 4_001;
string filePath = GetTestFilePath();
byte[] content = RandomNumberGenerator.GetBytes(fileSize);
using (SafeFileHandle handle = File.OpenHandle(filePath, FileMode.CreateNew, FileAccess.Write, FileShare.None, FileOptions.Asynchronous))
{
int total = 0;
int current = 0;
while (total != fileSize)
{
Memory<byte> buffer = content.AsMemory(total, Math.Min(content.Length - total, fileSize / 4));
current = await RandomAccess.WriteAsync(handle, buffer, fileOffset: total);
Assert.InRange(current, 0, buffer.Length);
total += current;
}
}
Assert.Equal(content, File.ReadAllBytes(filePath));
}
}
}
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using Microsoft.Win32.SafeHandles;
using Xunit;
namespace System.IO.Tests
{
public class RandomAccess_WriteGather : RandomAccess_Base<long>
{
protected override long MethodUnderTest(SafeFileHandle handle, byte[] bytes, long fileOffset)
=> RandomAccess.Write(handle, new ReadOnlyMemory<byte>[] { bytes }, fileOffset);
protected override bool ShouldThrowForAsyncHandle
=> OperatingSystem.IsWindows(); // on Windows we can NOT perform sync IO using async handle
[Fact]
public void ThrowsArgumentNullExceptionForNullBuffers()
{
using (SafeFileHandle handle = File.OpenHandle(GetTestFilePath(), FileMode.CreateNew, FileAccess.Write))
{
AssertExtensions.Throws<ArgumentNullException>("buffers", () => RandomAccess.Write(handle, buffers: null, 0));
}
}
[Fact]
public void ThrowsOnReadAccess()
{
using (SafeFileHandle handle = GetHandleToExistingFile(FileAccess.Read))
{
Assert.Throws<UnauthorizedAccessException>(() => RandomAccess.Write(handle, new ReadOnlyMemory<byte>[] { new byte[1] }, 0));
}
}
[Fact]
public void WriteUsingEmptyBufferReturnsZero()
{
using (SafeFileHandle handle = File.OpenHandle(GetTestFilePath(), FileMode.Create, FileAccess.Write))
{
Assert.Equal(0, RandomAccess.Write(handle, new ReadOnlyMemory<byte>[] { Array.Empty<byte>() }, fileOffset: 0));
}
}
[Fact]
public void WritesBytesFromGivenBuffersToGivenFileAtGivenOffset()
{
const int fileSize = 4_001;
string filePath = GetTestFilePath();
byte[] content = RandomNumberGenerator.GetBytes(fileSize);
using (SafeFileHandle handle = File.OpenHandle(filePath, FileMode.CreateNew, FileAccess.Write, FileShare.None))
{
long total = 0;
long current = 0;
while (total != fileSize)
{
int firstBufferLength = (int)Math.Min(content.Length - total, fileSize / 4);
Memory<byte> buffer_1 = content.AsMemory((int)total, firstBufferLength);
Memory<byte> buffer_2 = content.AsMemory((int)total + firstBufferLength);
current = RandomAccess.Write(
handle,
new ReadOnlyMemory<byte>[]
{
buffer_1,
Array.Empty<byte>(),
buffer_2
},
fileOffset: total);
Assert.InRange(current, 0, buffer_1.Length + buffer_2.Length);
total += current;
}
}
Assert.Equal(content, File.ReadAllBytes(filePath));
}
[Fact]
public void DuplicatedBufferDuplicatesContent()
{
const byte value = 1;
const int repeatCount = 2;
string filePath = GetTestFilePath();
ReadOnlyMemory<byte> buffer = new byte[1] { value };
List<ReadOnlyMemory<byte>> buffers = Enumerable.Repeat(buffer, repeatCount).ToList();
using (SafeFileHandle handle = File.OpenHandle(filePath, FileMode.Create, FileAccess.Write))
{
Assert.Equal(repeatCount, RandomAccess.Write(handle, buffers, fileOffset: 0));
}
byte[] actualContent = File.ReadAllBytes(filePath);
Assert.Equal(repeatCount, actualContent.Length);
Assert.All(actualContent, actual => Assert.Equal(value, actual));
}
}
}
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Win32.SafeHandles;
using Xunit;
namespace System.IO.Tests
{
[ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)]
[SkipOnPlatform(TestPlatforms.Browser, "async file IO is not supported on browser")]
public class RandomAccess_WriteGatherAsync : RandomAccess_Base<ValueTask<long>>
{
protected override ValueTask<long> MethodUnderTest(SafeFileHandle handle, byte[] bytes, long fileOffset)
=> RandomAccess.WriteAsync(handle, new ReadOnlyMemory<byte>[] { bytes }, fileOffset);
protected override bool ShouldThrowForSyncHandle
=> OperatingSystem.IsWindows(); // on Windows we can NOT perform async IO using sync handle
[Fact]
public void ThrowsArgumentNullExceptionForNullBuffers()
{
using (SafeFileHandle handle = File.OpenHandle(GetTestFilePath(), FileMode.CreateNew, FileAccess.Write, FileShare.None, FileOptions.Asynchronous))
{
AssertExtensions.Throws<ArgumentNullException>("buffers", () => RandomAccess.WriteAsync(handle, buffers: null, 0));
}
}
[Fact]
public async Task TaskAlreadyCanceledAsync()
{
using (SafeFileHandle handle = File.OpenHandle(GetTestFilePath(), FileMode.CreateNew, FileAccess.ReadWrite, options: FileOptions.Asynchronous))
{
CancellationTokenSource cts = GetCancelledTokenSource();
CancellationToken token = cts.Token;
Assert.True(RandomAccess.WriteAsync(handle, new ReadOnlyMemory<byte>[] { new byte[1] }, 0, token).IsCanceled);
TaskCanceledException ex = await Assert.ThrowsAsync<TaskCanceledException>(() => RandomAccess.WriteAsync(handle, new ReadOnlyMemory<byte>[] { new byte[1] }, 0, token).AsTask());
Assert.Equal(token, ex.CancellationToken);
}
}
[Fact]
public async Task ThrowsOnReadAccess()
{
using (SafeFileHandle handle = GetHandleToExistingFile(FileAccess.Read))
{
await Assert.ThrowsAsync<UnauthorizedAccessException>(async () => await RandomAccess.WriteAsync(handle, new ReadOnlyMemory<byte>[] { new byte[1] }, 0));
}
}
[Fact]
public async Task WriteUsingEmptyBufferReturnsZeroAsync()
{
using (SafeFileHandle handle = File.OpenHandle(GetTestFilePath(), FileMode.Create, FileAccess.Write, options: FileOptions.Asynchronous))
{
Assert.Equal(0, await RandomAccess.WriteAsync(handle, new ReadOnlyMemory<byte>[] { Array.Empty<byte>() }, fileOffset: 0));
}
}
[Fact]
public async Task WritesBytesFromGivenBufferToGivenFileAtGivenOffsetAsync()
{
const int fileSize = 4_001;
string filePath = GetTestFilePath();
byte[] content = RandomNumberGenerator.GetBytes(fileSize);
using (SafeFileHandle handle = File.OpenHandle(filePath, FileMode.CreateNew, FileAccess.Write, FileShare.None, FileOptions.Asynchronous))
{
long total = 0;
long current = 0;
while (total != fileSize)
{
int firstBufferLength = (int)Math.Min(content.Length - total, fileSize / 4);
Memory<byte> buffer_1 = content.AsMemory((int)total, firstBufferLength);
Memory<byte> buffer_2 = content.AsMemory((int)total + firstBufferLength);
current = await RandomAccess.WriteAsync(
handle,
new ReadOnlyMemory<byte>[]
{
buffer_1,
buffer_2
},
fileOffset: total);
Assert.InRange(current, 0, buffer_1.Length + buffer_2.Length);
total += current;
}
}
Assert.Equal(content, File.ReadAllBytes(filePath));
}
[Fact]
public async Task DuplicatedBufferDuplicatesContentAsync()
{
const byte value = 1;
const int repeatCount = 2;
string filePath = GetTestFilePath();
ReadOnlyMemory<byte> buffer = new byte[1] { value };
List<ReadOnlyMemory<byte>> buffers = Enumerable.Repeat(buffer, repeatCount).ToList();
using (SafeFileHandle handle = File.OpenHandle(filePath, FileMode.Create, FileAccess.Write, options: FileOptions.Asynchronous))
{
Assert.Equal(repeatCount, await RandomAccess.WriteAsync(handle, buffers, fileOffset: 0));
}
byte[] actualContent = File.ReadAllBytes(filePath);
Assert.Equal(repeatCount, actualContent.Length);
Assert.All(actualContent, actual => Assert.Equal(value, actual));
}
}
}
......@@ -51,6 +51,16 @@
<Compile Include="Enumeration\ExampleTests.cs" />
<Compile Include="Enumeration\RemovedDirectoryTests.cs" />
<Compile Include="Enumeration\SymbolicLinksTests.cs" />
<Compile Include="RandomAccess\Base.cs" />
<Compile Include="RandomAccess\GetLength.cs" />
<Compile Include="RandomAccess\Read.cs" />
<Compile Include="RandomAccess\ReadAsync.cs" />
<Compile Include="RandomAccess\ReadScatter.cs" />
<Compile Include="RandomAccess\ReadScatterAsync.cs" />
<Compile Include="RandomAccess\Write.cs" />
<Compile Include="RandomAccess\WriteAsync.cs" />
<Compile Include="RandomAccess\WriteGather.cs" />
<Compile Include="RandomAccess\WriteGatherAsync.cs" />
</ItemGroup>
<ItemGroup Condition="'$(TargetsUnix)' == 'true'">
<Compile Include="FileSystemTest.Unix.cs" />
......@@ -61,10 +71,14 @@
<ItemGroup Condition="'$(TargetsWindows)' == 'true'">
<Compile Include="FileSystemTest.Windows.cs" />
<Compile Include="FileStream\ctor_options_as.Windows.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Interop.BOOL.cs" Link="Interop\Windows\Interop.BOOL.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.FILE_STANDARD_INFO.cs" Link="Interop\Windows\Interop.FILE_STANDARD_INFO.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Interop.Libraries.cs" Link="Interop\Windows\Interop.Libraries.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.GetFileInformationByHandleEx.cs" Link="Interop\Windows\Interop.GetFileInformationByHandleEx.cs" />
<Compile Include="RandomAccess\NoBuffering.Windows.cs" />
<Compile Include="RandomAccess\SectorAlignedMemory.Windows.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Interop.BOOL.cs" Link="Common\Interop\Windows\Interop.BOOL.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Interop.Libraries.cs" Link="Common\Interop\Windows\Interop.Libraries.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.FILE_STANDARD_INFO.cs" Link="Common\Interop\Windows\Interop.FILE_STANDARD_INFO.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.GetFileInformationByHandleEx.cs" Link="Common\Interop\Windows\Interop.GetFileInformationByHandleEx.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.MemOptions.cs" Link="Common\Interop\Windows\Interop.MemOptions.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.VirtualAlloc_Ptr.cs" Link="Common\Interop\Windows\Interop.VirtualAlloc_Ptr.cs" />
</ItemGroup>
<ItemGroup Condition="'$(TargetsBrowser)' == 'true'">
<Compile Include="FileSystemTest.Browser.cs" />
......@@ -158,6 +172,7 @@
<Compile Include="File\Exists.cs" />
<Compile Include="File\GetSetTimes.cs" />
<Compile Include="File\Open.cs" />
<Compile Include="File\OpenHandle.cs" />
<Compile Include="FileInfo\Create.cs" />
<Compile Include="FileInfo\Delete.cs" />
<Compile Include="FileInfo\Exists.cs" />
......
......@@ -236,6 +236,8 @@
Link="Common\Interop\Unix\Interop.Stat.cs" />
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.Listen.cs"
Link="Common\Interop\Unix\System.Native\Interop.Listen.cs" />
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.IOVector.cs"
Link="Common\Interop\Unix\System.Native\Interop.IOVector.cs" />
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.MessageHeader.cs"
Link="Common\Interop\Unix\System.Native\Interop.MessageHeader.cs" />
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.MulticastOption.cs"
......
......@@ -4,11 +4,15 @@
using System;
using System.Diagnostics;
using System.IO;
using System.IO.Strategies;
namespace Microsoft.Win32.SafeHandles
{
public sealed class SafeFileHandle : SafeHandleZeroOrMinusOneIsInvalid
{
// not using bool? as it's not thread safe
private volatile NullableBool _canSeek = NullableBool.Undefined;
public SafeFileHandle() : this(ownsHandle: true)
{
}
......@@ -24,14 +28,16 @@ public SafeFileHandle(IntPtr preexistingHandle, bool ownsHandle) : this(ownsHand
SetHandle(preexistingHandle);
}
internal bool? IsAsync { get; set; }
public bool IsAsync { get; private set; }
internal bool CanSeek => !IsClosed && GetCanSeek();
/// <summary>Opens the specified file with the requested flags and mode.</summary>
/// <param name="path">The path to the file.</param>
/// <param name="flags">The flags with which to open the file.</param>
/// <param name="mode">The mode for opening the file.</param>
/// <returns>A SafeFileHandle for the opened file.</returns>
internal static SafeFileHandle Open(string path, Interop.Sys.OpenFlags flags, int mode)
private static SafeFileHandle Open(string path, Interop.Sys.OpenFlags flags, int mode)
{
Debug.Assert(path != null);
SafeFileHandle handle = Interop.Sys.Open(path, flags, mode);
......@@ -73,6 +79,15 @@ internal static SafeFileHandle Open(string path, Interop.Sys.OpenFlags flags, in
throw Interop.GetExceptionForIoErrno(Interop.Error.EACCES.Info(), path, isDirectory: true);
}
if ((status.Mode & Interop.Sys.FileTypes.S_IFMT) == Interop.Sys.FileTypes.S_IFREG)
{
// we take advantage of the information provided by the fstat syscall
// and for regular files (most common case)
// avoid one extra sys call for determining whether file can be seeked
handle._canSeek = NullableBool.True;
Debug.Assert(Interop.Sys.LSeek(handle, 0, Interop.Sys.SeekWhence.SEEK_CUR) >= 0);
}
return handle;
}
......@@ -125,5 +140,189 @@ public override bool IsInvalid
return h < 0 || h > int.MaxValue;
}
}
internal static SafeFileHandle Open(string fullPath, FileMode mode, FileAccess access, FileShare share, FileOptions options, long preallocationSize)
{
// Translate the arguments into arguments for an open call.
Interop.Sys.OpenFlags openFlags = PreOpenConfigurationFromOptions(mode, access, share, options);
// If the file gets created a new, we'll select the permissions for it. Most Unix utilities by default use 666 (read and
// write for all), so we do the same (even though this doesn't match Windows, where by default it's possible to write out
// a file and then execute it). No matter what we choose, it'll be subject to the umask applied by the system, such that the
// actual permissions will typically be less than what we select here.
const Interop.Sys.Permissions OpenPermissions =
Interop.Sys.Permissions.S_IRUSR | Interop.Sys.Permissions.S_IWUSR |
Interop.Sys.Permissions.S_IRGRP | Interop.Sys.Permissions.S_IWGRP |
Interop.Sys.Permissions.S_IROTH | Interop.Sys.Permissions.S_IWOTH;
SafeFileHandle safeFileHandle = Open(fullPath, openFlags, (int)OpenPermissions);
try
{
safeFileHandle.Init(fullPath, mode, access, share, options, preallocationSize);
return safeFileHandle;
}
catch (Exception)
{
safeFileHandle.Dispose();
throw;
}
}
/// <summary>Translates the FileMode, FileAccess, and FileOptions values into flags to be passed when opening the file.</summary>
/// <param name="mode">The FileMode provided to the stream's constructor.</param>
/// <param name="access">The FileAccess provided to the stream's constructor</param>
/// <param name="share">The FileShare provided to the stream's constructor</param>
/// <param name="options">The FileOptions provided to the stream's constructor</param>
/// <returns>The flags value to be passed to the open system call.</returns>
private static Interop.Sys.OpenFlags PreOpenConfigurationFromOptions(FileMode mode, FileAccess access, FileShare share, FileOptions options)
{
// Translate FileMode. Most of the values map cleanly to one or more options for open.
Interop.Sys.OpenFlags flags = default;
switch (mode)
{
default:
case FileMode.Open: // Open maps to the default behavior for open(...). No flags needed.
case FileMode.Truncate: // We truncate the file after getting the lock
break;
case FileMode.Append: // Append is the same as OpenOrCreate, except that we'll also separately jump to the end later
case FileMode.OpenOrCreate:
case FileMode.Create: // We truncate the file after getting the lock
flags |= Interop.Sys.OpenFlags.O_CREAT;
break;
case FileMode.CreateNew:
flags |= (Interop.Sys.OpenFlags.O_CREAT | Interop.Sys.OpenFlags.O_EXCL);
break;
}
// Translate FileAccess. All possible values map cleanly to corresponding values for open.
switch (access)
{
case FileAccess.Read:
flags |= Interop.Sys.OpenFlags.O_RDONLY;
break;
case FileAccess.ReadWrite:
flags |= Interop.Sys.OpenFlags.O_RDWR;
break;
case FileAccess.Write:
flags |= Interop.Sys.OpenFlags.O_WRONLY;
break;
}
// Handle Inheritable, other FileShare flags are handled by Init
if ((share & FileShare.Inheritable) == 0)
{
flags |= Interop.Sys.OpenFlags.O_CLOEXEC;
}
// Translate some FileOptions; some just aren't supported, and others will be handled after calling open.
// - Asynchronous: Handled in ctor, setting _useAsync and SafeFileHandle.IsAsync to true
// - DeleteOnClose: Doesn't have a Unix equivalent, but we approximate it in Dispose
// - Encrypted: No equivalent on Unix and is ignored
// - RandomAccess: Implemented after open if posix_fadvise is available
// - SequentialScan: Implemented after open if posix_fadvise is available
// - WriteThrough: Handled here
if ((options & FileOptions.WriteThrough) != 0)
{
flags |= Interop.Sys.OpenFlags.O_SYNC;
}
return flags;
}
private void Init(string path, FileMode mode, FileAccess access, FileShare share, FileOptions options, long preallocationSize)
{
IsAsync = (options & FileOptions.Asynchronous) != 0;
// Lock the file if requested via FileShare. This is only advisory locking. FileShare.None implies an exclusive
// lock on the file and all other modes use a shared lock. While this is not as granular as Windows, not mandatory,
// and not atomic with file opening, it's better than nothing.
Interop.Sys.LockOperations lockOperation = (share == FileShare.None) ? Interop.Sys.LockOperations.LOCK_EX : Interop.Sys.LockOperations.LOCK_SH;
if (Interop.Sys.FLock(this, lockOperation | Interop.Sys.LockOperations.LOCK_NB) < 0)
{
// The only error we care about is EWOULDBLOCK, which indicates that the file is currently locked by someone
// else and we would block trying to access it. Other errors, such as ENOTSUP (locking isn't supported) or
// EACCES (the file system doesn't allow us to lock), will only hamper FileStream's usage without providing value,
// given again that this is only advisory / best-effort.
Interop.ErrorInfo errorInfo = Interop.Sys.GetLastErrorInfo();
if (errorInfo.Error == Interop.Error.EWOULDBLOCK)
{
throw Interop.GetExceptionForIoErrno(errorInfo, path, isDirectory: false);
}
}
// These provide hints around how the file will be accessed. Specifying both RandomAccess
// and Sequential together doesn't make sense as they are two competing options on the same spectrum,
// so if both are specified, we prefer RandomAccess (behavior on Windows is unspecified if both are provided).
Interop.Sys.FileAdvice fadv =
(options & FileOptions.RandomAccess) != 0 ? Interop.Sys.FileAdvice.POSIX_FADV_RANDOM :
(options & FileOptions.SequentialScan) != 0 ? Interop.Sys.FileAdvice.POSIX_FADV_SEQUENTIAL :
0;
if (fadv != 0)
{
FileStreamHelpers.CheckFileCall(Interop.Sys.PosixFAdvise(this, 0, 0, fadv), path,
ignoreNotSupported: true); // just a hint.
}
if (mode == FileMode.Create || mode == FileMode.Truncate)
{
// Truncate the file now if the file mode requires it. This ensures that the file only will be truncated
// if opened successfully.
if (Interop.Sys.FTruncate(this, 0) < 0)
{
Interop.ErrorInfo errorInfo = Interop.Sys.GetLastErrorInfo();
if (errorInfo.Error != Interop.Error.EBADF && errorInfo.Error != Interop.Error.EINVAL)
{
// We know the file descriptor is valid and we know the size argument to FTruncate is correct,
// so if EBADF or EINVAL is returned, it means we're dealing with a special file that can't be
// truncated. Ignore the error in such cases; in all others, throw.
throw Interop.GetExceptionForIoErrno(errorInfo, path, isDirectory: false);
}
}
}
// If preallocationSize has been provided for a creatable and writeable file
if (FileStreamHelpers.ShouldPreallocate(preallocationSize, access, mode))
{
int fallocateResult = Interop.Sys.PosixFAllocate(this, 0, preallocationSize);
if (fallocateResult != 0)
{
Dispose();
Interop.Sys.Unlink(path!); // remove the file to mimic Windows behaviour (atomic operation)
Debug.Assert(fallocateResult == -1 || fallocateResult == -2);
throw new IOException(SR.Format(
fallocateResult == -1 ? SR.IO_DiskFull_Path_AllocationSize : SR.IO_FileTooLarge_Path_AllocationSize,
path,
preallocationSize));
}
}
}
private bool GetCanSeek()
{
Debug.Assert(!IsClosed);
Debug.Assert(!IsInvalid);
NullableBool canSeek = _canSeek;
if (canSeek == NullableBool.Undefined)
{
_canSeek = canSeek = Interop.Sys.LSeek(this, 0, Interop.Sys.SeekWhence.SEEK_CUR) >= 0 ? NullableBool.True : NullableBool.False;
}
return canSeek == NullableBool.True;
}
private enum NullableBool
{
Undefined = 0,
False = -1,
True = 1
}
}
}
// 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.Buffers;
using System.Diagnostics;
using System.IO;
using System.Threading;
using System.Threading.Tasks.Sources;
namespace System.IO.Strategies
namespace Microsoft.Win32.SafeHandles
{
internal sealed partial class AsyncWindowsFileStreamStrategy : WindowsFileStreamStrategy
public sealed partial class SafeFileHandle : SafeHandleZeroOrMinusOneIsInvalid
{
private ValueTaskSource? _reusableValueTaskSource; // reusable ValueTaskSource that is currently NOT being used
// Rent the reusable ValueTaskSource, or create a new one to use if we couldn't get one (which
// should only happen on first use or if the FileStream is being used concurrently).
internal ValueTaskSource GetValueTaskSource() => Interlocked.Exchange(ref _reusableValueTaskSource, null) ?? new ValueTaskSource(this);
protected override bool ReleaseHandle()
{
bool result = Interop.Kernel32.CloseHandle(handle);
Interlocked.Exchange(ref _reusableValueTaskSource, null)?.Dispose();
return result;
}
private void TryToReuse(ValueTaskSource source)
{
source._source.Reset();
if (Interlocked.CompareExchange(ref _reusableValueTaskSource, source, null) is not null)
{
source._preallocatedOverlapped.Dispose();
}
}
/// <summary>Reusable IValueTaskSource for FileStream ValueTask-returning async operations.</summary>
private sealed unsafe class ValueTaskSource : IValueTaskSource<int>, IValueTaskSource
internal sealed unsafe class ValueTaskSource : IValueTaskSource<int>, IValueTaskSource
{
internal static readonly IOCompletionCallback s_ioCallback = IOCallback;
internal readonly PreAllocatedOverlapped _preallocatedOverlapped;
private readonly AsyncWindowsFileStreamStrategy _strategy;
private readonly SafeFileHandle _fileHandle;
internal MemoryHandle _memoryHandle;
internal ManualResetValueTaskSourceCore<int> _source; // mutable struct; do not make this readonly
private NativeOverlapped* _overlapped;
......@@ -28,9 +55,9 @@ private sealed unsafe class ValueTaskSource : IValueTaskSource<int>, IValueTaskS
/// </summary>
internal ulong _result;
internal ValueTaskSource(AsyncWindowsFileStreamStrategy strategy)
internal ValueTaskSource(SafeFileHandle fileHandle)
{
_strategy = strategy;
_fileHandle = fileHandle;
_source.RunContinuationsAsynchronously = true;
_preallocatedOverlapped = PreAllocatedOverlapped.UnsafeCreate(s_ioCallback, this, null);
}
......@@ -41,11 +68,18 @@ internal void Dispose()
_preallocatedOverlapped.Dispose();
}
internal NativeOverlapped* PrepareForOperation(ReadOnlyMemory<byte> memory)
internal static Exception GetIOError(int errorCode, string? path)
=> errorCode == Interop.Errors.ERROR_HANDLE_EOF
? ThrowHelper.CreateEndOfFileException()
: Win32Marshal.GetExceptionForWin32Error(errorCode, path);
internal NativeOverlapped* PrepareForOperation(ReadOnlyMemory<byte> memory, long fileOffset)
{
_result = 0;
_memoryHandle = memory.Pin();
_overlapped = _strategy._fileHandle.ThreadPoolBinding!.AllocateNativeOverlapped(_preallocatedOverlapped);
_overlapped = _fileHandle.ThreadPoolBinding!.AllocateNativeOverlapped(_preallocatedOverlapped);
_overlapped->OffsetLow = (int)fileOffset;
_overlapped->OffsetHigh = (int)(fileOffset >> 32);
return _overlapped;
}
......@@ -63,7 +97,7 @@ private int GetResultAndRelease(short token)
finally
{
// The instance is ready to be reused
_strategy.TryToReuse(this);
_fileHandle.TryToReuse(this);
}
}
......@@ -79,11 +113,11 @@ internal void RegisterForCancellation(CancellationToken cancellationToken)
_cancellationRegistration = cancellationToken.UnsafeRegister(static (s, token) =>
{
ValueTaskSource vts = (ValueTaskSource)s!;
if (!vts._strategy._fileHandle.IsInvalid)
if (!vts._fileHandle.IsInvalid)
{
try
{
Interop.Kernel32.CancelIoEx(vts._strategy._fileHandle, vts._overlapped);
Interop.Kernel32.CancelIoEx(vts._fileHandle, vts._overlapped);
// Ignore all failures: no matter whether it succeeds or fails, completion is handled via the IOCallback.
}
catch (ObjectDisposedException) { } // in case the SafeHandle is (erroneously) closed concurrently
......@@ -112,7 +146,7 @@ internal void ReleaseResources()
// Free the overlapped.
if (_overlapped != null)
{
_strategy._fileHandle.ThreadPoolBinding!.FreeNativeOverlapped(_overlapped);
_fileHandle.ThreadPoolBinding!.FreeNativeOverlapped(_overlapped);
_overlapped = null;
}
}
......@@ -161,6 +195,7 @@ internal void Complete(uint errorCode, uint numBytes)
case 0:
case Interop.Errors.ERROR_BROKEN_PIPE:
case Interop.Errors.ERROR_NO_DATA:
case Interop.Errors.ERROR_HANDLE_EOF: // logically success with 0 bytes read (read at end of file)
// Success
_source.SetResult((int)numBytes);
break;
......
......@@ -2,35 +2,198 @@
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Threading;
namespace Microsoft.Win32.SafeHandles
{
public sealed class SafeFileHandle : SafeHandleZeroOrMinusOneIsInvalid
public sealed partial class SafeFileHandle : SafeHandleZeroOrMinusOneIsInvalid
{
private bool? _isAsync;
internal const FileOptions NoBuffering = (FileOptions)0x20000000;
private volatile FileOptions _fileOptions = (FileOptions)(-1);
private volatile int _fileType = -1;
public SafeFileHandle() : base(true)
{
_isAsync = null;
}
public SafeFileHandle(IntPtr preexistingHandle, bool ownsHandle) : base(ownsHandle)
{
SetHandle(preexistingHandle);
_isAsync = null;
}
internal bool? IsAsync
private SafeFileHandle(IntPtr preexistingHandle, bool ownsHandle, FileOptions fileOptions) : base(ownsHandle)
{
get => _isAsync;
set => _isAsync = value;
SetHandle(preexistingHandle);
_fileOptions = fileOptions;
}
public bool IsAsync => (GetFileOptions() & FileOptions.Asynchronous) != 0;
internal bool CanSeek => !IsClosed && GetFileType() == Interop.Kernel32.FileTypes.FILE_TYPE_DISK;
internal bool IsPipe => GetFileType() == Interop.Kernel32.FileTypes.FILE_TYPE_PIPE;
internal ThreadPoolBoundHandle? ThreadPoolBinding { get; set; }
protected override bool ReleaseHandle() =>
Interop.Kernel32.CloseHandle(handle);
internal static unsafe SafeFileHandle Open(string fullPath, FileMode mode, FileAccess access, FileShare share, FileOptions options, long preallocationSize)
{
using (DisableMediaInsertionPrompt.Create())
{
SafeFileHandle fileHandle = new SafeFileHandle(
NtCreateFile(fullPath, mode, access, share, options, preallocationSize),
ownsHandle: true,
options);
fileHandle.InitThreadPoolBindingIfNeeded();
return fileHandle;
}
}
private static IntPtr NtCreateFile(string fullPath, FileMode mode, FileAccess access, FileShare share, FileOptions options, long preallocationSize)
{
uint ntStatus;
IntPtr fileHandle;
const string MandatoryNtPrefix = @"\??\";
if (fullPath.StartsWith(MandatoryNtPrefix, StringComparison.Ordinal))
{
(ntStatus, fileHandle) = Interop.NtDll.NtCreateFile(fullPath, mode, access, share, options, preallocationSize);
}
else
{
var vsb = new ValueStringBuilder(stackalloc char[256]);
vsb.Append(MandatoryNtPrefix);
if (fullPath.StartsWith(@"\\?\", StringComparison.Ordinal)) // NtCreateFile does not support "\\?\" prefix, only "\??\"
{
vsb.Append(fullPath.AsSpan(4));
}
else
{
vsb.Append(fullPath);
}
(ntStatus, fileHandle) = Interop.NtDll.NtCreateFile(vsb.AsSpan(), mode, access, share, options, preallocationSize);
vsb.Dispose();
}
switch (ntStatus)
{
case Interop.StatusOptions.STATUS_SUCCESS:
return fileHandle;
case Interop.StatusOptions.STATUS_DISK_FULL:
throw new IOException(SR.Format(SR.IO_DiskFull_Path_AllocationSize, fullPath, preallocationSize));
// NtCreateFile has a bug and it reports STATUS_INVALID_PARAMETER for files
// that are too big for the current file system. Example: creating a 4GB+1 file on a FAT32 drive.
case Interop.StatusOptions.STATUS_INVALID_PARAMETER when preallocationSize > 0:
case Interop.StatusOptions.STATUS_FILE_TOO_LARGE:
throw new IOException(SR.Format(SR.IO_FileTooLarge_Path_AllocationSize, fullPath, preallocationSize));
default:
int error = (int)Interop.NtDll.RtlNtStatusToDosError((int)ntStatus);
throw Win32Marshal.GetExceptionForWin32Error(error, fullPath);
}
}
internal void InitThreadPoolBindingIfNeeded()
{
if (IsAsync == true && ThreadPoolBinding == null)
{
// This is necessary for async IO using IO Completion ports via our
// managed Threadpool API's. This (theoretically) calls the OS's
// BindIoCompletionCallback method, and passes in a stub for the
// LPOVERLAPPED_COMPLETION_ROUTINE. This stub looks at the Overlapped
// struct for this request and gets a delegate to a managed callback
// from there, which it then calls on a threadpool thread. (We allocate
// our native OVERLAPPED structs 2 pointers too large and store EE state
// & GC handles there, one to an IAsyncResult, the other to a delegate.)
try
{
ThreadPoolBinding = ThreadPoolBoundHandle.BindHandle(this);
}
catch (ArgumentException ex)
{
if (OwnsHandle)
{
// We should close the handle so that the handle is not open until SafeFileHandle GC
Dispose();
}
throw new IOException(SR.IO_BindHandleFailed, ex);
}
}
}
internal unsafe FileOptions GetFileOptions()
{
FileOptions fileOptions = _fileOptions;
if (fileOptions != (FileOptions)(-1))
{
return fileOptions;
}
Interop.NtDll.CreateOptions options;
int ntStatus = Interop.NtDll.NtQueryInformationFile(
FileHandle: this,
IoStatusBlock: out _,
FileInformation: &options,
Length: sizeof(uint),
FileInformationClass: Interop.NtDll.FileModeInformation);
if (ntStatus != Interop.StatusOptions.STATUS_SUCCESS)
{
int error = (int)Interop.NtDll.RtlNtStatusToDosError(ntStatus);
throw Win32Marshal.GetExceptionForWin32Error(error);
}
FileOptions result = FileOptions.None;
if ((options & (Interop.NtDll.CreateOptions.FILE_SYNCHRONOUS_IO_ALERT | Interop.NtDll.CreateOptions.FILE_SYNCHRONOUS_IO_NONALERT)) == 0)
{
result |= FileOptions.Asynchronous;
}
if ((options & Interop.NtDll.CreateOptions.FILE_WRITE_THROUGH) != 0)
{
result |= FileOptions.WriteThrough;
}
if ((options & Interop.NtDll.CreateOptions.FILE_RANDOM_ACCESS) != 0)
{
result |= FileOptions.RandomAccess;
}
if ((options & Interop.NtDll.CreateOptions.FILE_SEQUENTIAL_ONLY) != 0)
{
result |= FileOptions.SequentialScan;
}
if ((options & Interop.NtDll.CreateOptions.FILE_DELETE_ON_CLOSE) != 0)
{
result |= FileOptions.DeleteOnClose;
}
if ((options & Interop.NtDll.CreateOptions.FILE_NO_INTERMEDIATE_BUFFERING) != 0)
{
result |= NoBuffering;
}
return _fileOptions = result;
}
internal int GetFileType()
{
int fileType = _fileType;
if (fileType == -1)
{
_fileType = fileType = Interop.Kernel32.GetFileType(this);
Debug.Assert(fileType == Interop.Kernel32.FileTypes.FILE_TYPE_DISK
|| fileType == Interop.Kernel32.FileTypes.FILE_TYPE_PIPE
|| fileType == Interop.Kernel32.FileTypes.FILE_TYPE_CHAR,
$"Unknown file type: {fileType}");
}
return fileType;
}
}
}
......@@ -431,6 +431,7 @@
<Compile Include="$(MSBuildThisFileDirectory)System\IO\PathInternal.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\IO\PathTooLongException.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\IO\PinnedBufferMemoryStream.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\IO\RandomAccess.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\IO\ReadLinesIterator.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\IO\SearchOption.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\IO\SearchTarget.cs" />
......@@ -1595,6 +1596,9 @@
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.ReadFile_SafeHandle_NativeOverlapped.cs">
<Link>Common\Interop\Windows\Kernel32\Interop.ReadFile_SafeHandle_NativeOverlapped.cs</Link>
</Compile>
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.FileScatterGather.cs">
<Link>Common\Interop\Windows\Kernel32\Interop.FileScatterGather.cs</Link>
</Compile>
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.RemoveDirectory.cs">
<Link>Common\Interop\Windows\Kernel32\Interop.RemoveDirectory.cs</Link>
</Compile>
......@@ -1613,9 +1617,6 @@
<Compile Include="$(CommonPath)Interop\Windows\Interop.OBJECT_ATTRIBUTES.cs">
<Link>Common\Interop\Windows\Interop.OBJECT_ATTRIBUTES.cs</Link>
</Compile>
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.SecurityOptions.cs">
<Link>Common\Interop\Windows\Kernel32\Interop.SecurityOptions.cs</Link>
</Compile>
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.SetCurrentDirectory.cs">
<Link>Common\Interop\Windows\Kernel32\Interop.SetCurrentDirectory.cs</Link>
</Compile>
......@@ -1754,6 +1755,7 @@
<Compile Include="$(MSBuildThisFileDirectory)Internal\Console.Windows.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Internal\Win32\RegistryKey.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Microsoft\Win32\SafeHandles\SafeFileHandle.Windows.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Microsoft\Win32\SafeHandles\SafeFileHandle.ValueTaskSource.Windows.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Microsoft\Win32\SafeHandles\SafeFindHandle.Windows.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Microsoft\Win32\SafeHandles\SafeRegistryHandle.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Microsoft\Win32\SafeHandles\SafeRegistryHandle.Windows.cs" />
......@@ -1780,11 +1782,11 @@
<Compile Include="$(MSBuildThisFileDirectory)System\IO\Path.Windows.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\IO\PathHelper.Windows.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\IO\PathInternal.Windows.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\IO\RandomAccess.Windows.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\IO\Enumeration\FileSystemEntry.Windows.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\IO\Enumeration\FileSystemEnumerator.Win32.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\IO\Enumeration\FileSystemEnumerator.Windows.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\IO\Strategies\AsyncWindowsFileStreamStrategy.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\IO\Strategies\AsyncWindowsFileStreamStrategy.ValueTaskSource.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\IO\Strategies\FileStreamHelpers.Windows.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\IO\Strategies\Net5CompatFileStreamStrategy.CompletionSource.Windows.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\IO\Strategies\Net5CompatFileStreamStrategy.Windows.cs" />
......@@ -1924,6 +1926,9 @@
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.GetUnixRelease.cs">
<Link>Common\Interop\Unix\System.Native\Interop.GetUnixRelease.cs</Link>
</Compile>
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.IOVector.cs">
<Link>Common\Interop\Unix\System.Native\Interop.IOVector.cs</Link>
</Compile>
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.LChflags.cs">
<Link>Common\Interop\Unix\System.Native\Interop.LChflags.cs</Link>
</Compile>
......@@ -1972,6 +1977,18 @@
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.PosixFAllocate.cs">
<Link>Common\Interop\Unix\System.Native\Interop.PosixFAllocate.cs</Link>
</Compile>
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.PRead.cs">
<Link>Common\Interop\Unix\System.Native\Interop.PRead.cs</Link>
</Compile>
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.PReadV.cs">
<Link>Common\Interop\Unix\System.Native\Interop.PReadV.cs</Link>
</Compile>
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.PWrite.cs">
<Link>Common\Interop\Unix\System.Native\Interop.PWrite.cs</Link>
</Compile>
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.PWriteV.cs">
<Link>Common\Interop\Unix\System.Native\Interop.PWriteV.cs</Link>
</Compile>
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.Read.cs">
<Link>Common\Interop\Unix\System.Native\Interop.Read.cs</Link>
</Compile>
......@@ -2039,6 +2056,7 @@
<Compile Include="$(MSBuildThisFileDirectory)System\IO\Path.Unix.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\IO\PathInternal.Unix.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\IO\PersistedFiles.Names.Unix.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\IO\RandomAccess.Unix.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\IO\Enumeration\FileSystemEntry.Unix.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\IO\Enumeration\FileSystemEnumerator.Unix.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\IO\Strategies\FileStreamHelpers.Unix.cs" />
......
// 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.Buffers;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Runtime.ExceptionServices;
using System.Runtime.Versioning;
using System.Text;
......@@ -14,6 +12,9 @@
using System.Threading.Tasks;
#if MS_IO_REDIST
using System;
using System.IO;
namespace Microsoft.IO
#else
namespace System.IO
......@@ -363,52 +364,6 @@ public static byte[] ReadAllBytes(string path)
}
}
#if !MS_IO_REDIST
private static byte[] ReadAllBytesUnknownLength(FileStream fs)
{
byte[]? rentedArray = null;
Span<byte> buffer = stackalloc byte[512];
try
{
int bytesRead = 0;
while (true)
{
if (bytesRead == buffer.Length)
{
uint newLength = (uint)buffer.Length * 2;
if (newLength > MaxByteArrayLength)
{
newLength = (uint)Math.Max(MaxByteArrayLength, buffer.Length + 1);
}
byte[] tmp = ArrayPool<byte>.Shared.Rent((int)newLength);
buffer.CopyTo(tmp);
if (rentedArray != null)
{
ArrayPool<byte>.Shared.Return(rentedArray);
}
buffer = rentedArray = tmp;
}
Debug.Assert(bytesRead < buffer.Length);
int n = fs.Read(buffer.Slice(bytesRead));
if (n == 0)
{
return buffer.Slice(0, bytesRead).ToArray();
}
bytesRead += n;
}
}
finally
{
if (rentedArray != null)
{
ArrayPool<byte>.Shared.Return(rentedArray);
}
}
}
#endif
public static void WriteAllBytes(string path, byte[] bytes)
{
if (path == null)
......
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Buffers;
using System.Diagnostics;
using Microsoft.Win32.SafeHandles;
namespace System.IO
{
public static partial class File
{
/// <summary>
/// Initializes a new instance of the <see cref="System.IO.FileStream" /> class with the specified path, creation mode, read/write and sharing permission, the access other FileStreams can have to the same file, the buffer size, additional file options and the allocation size.
/// Initializes a new instance of the <see cref="FileStream" /> class with the specified path, creation mode, read/write and sharing permission, the access other FileStreams can have to the same file, the buffer size, additional file options and the allocation size.
/// </summary>
/// <remarks><see cref="System.IO.FileStream(string,System.IO.FileStreamOptions)"/> for information about exceptions.</remarks>
/// <remarks><see cref="FileStream(string,System.IO.FileStreamOptions)"/> for information about exceptions.</remarks>
public static FileStream Open(string path, FileStreamOptions options) => new FileStream(path, options);
/// <summary>
/// Initializes a new instance of the <see cref="Microsoft.Win32.SafeHandles.SafeFileHandle" /> class with the specified path, creation mode, read/write and sharing permission, the access other SafeFileHandles can have to the same file, additional file options and the allocation size.
/// </summary>
/// <param name="path">A relative or absolute path for the file that the current <see cref="Microsoft.Win32.SafeHandles.SafeFileHandle" /> instance will encapsulate.</param>
/// <param name="mode">One of the enumeration values that determines how to open or create the file. The default value is <see cref="FileMode.Open" /></param>
/// <param name="access">A bitwise combination of the enumeration values that determines how the file can be accessed. The default value is <see cref="FileAccess.Read" /></param>
/// <param name="share">A bitwise combination of the enumeration values that determines how the file will be shared by processes. The default value is <see cref="FileShare.Read" />.</param>
/// <param name="preallocationSize">The initial allocation size in bytes for the file. A positive value is effective only when a regular file is being created, overwritten, or replaced.
/// Negative values are not allowed. In other cases (including the default 0 value), it's ignored.</param>
/// <param name="options">An object that describes optional <see cref="Microsoft.Win32.SafeHandles.SafeFileHandle" /> parameters to use.</param>
/// <exception cref="T:System.ArgumentNullException"><paramref name="path" /> is <see langword="null" />.</exception>
/// <exception cref="T:System.ArgumentException"><paramref name="path" /> is an empty string (""), contains only white space, or contains one or more invalid characters.
/// -or-
/// <paramref name="path" /> refers to a non-file device, such as <c>CON:</c>, <c>COM1:</c>, <c>LPT1:</c>, etc. in an NTFS environment.</exception>
/// <exception cref="T:System.NotSupportedException"><paramref name="path" /> refers to a non-file device, such as <c>CON:</c>, <c>COM1:</c>, <c>LPT1:</c>, etc. in a non-NTFS environment.</exception>
/// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="preallocationSize" /> is negative.
/// -or-
/// <paramref name="mode" />, <paramref name="access" />, or <paramref name="share" /> contain an invalid value.</exception>
/// <exception cref="T:System.IO.FileNotFoundException">The file cannot be found, such as when <paramref name="mode" /> is <see cref="FileMode.Truncate" /> or <see cref="FileMode.Open" />, and the file specified by <paramref name="path" /> does not exist. The file must already exist in these modes.</exception>
/// <exception cref="T:System.IO.IOException">An I/O error, such as specifying <see cref="FileMode.CreateNew" /> when the file specified by <paramref name="path" /> already exists, occurred.
/// -or-
/// The disk was full (when <paramref name="preallocationSize" /> was provided and <paramref name="path" /> was pointing to a regular file).
/// -or-
/// The file was too large (when <paramref name="preallocationSize" /> was provided and <paramref name="path" /> was pointing to a regular file).</exception>
/// <exception cref="T:System.Security.SecurityException">The caller does not have the required permission.</exception>
/// <exception cref="T:System.IO.DirectoryNotFoundException">The specified path is invalid, such as being on an unmapped drive.</exception>
/// <exception cref="T:System.UnauthorizedAccessException">The <paramref name="access" /> requested is not permitted by the operating system for the specified <paramref name="path" />, such as when <paramref name="access" /> is <see cref="FileAccess.Write" /> or <see cref="FileAccess.ReadWrite" /> and the file or directory is set for read-only access.
/// -or-
/// <see cref="F:System.IO.FileOptions.Encrypted" /> is specified for <paramref name="options" />, but file encryption is not supported on the current platform.</exception>
/// <exception cref="T:System.IO.PathTooLongException">The specified path, file name, or both exceed the system-defined maximum length. </exception>
public static SafeFileHandle OpenHandle(string path, FileMode mode = FileMode.Open, FileAccess access = FileAccess.Read,
FileShare share = FileShare.Read, FileOptions options = FileOptions.None, long preallocationSize = 0)
{
Strategies.FileStreamHelpers.ValidateArguments(path, mode, access, share, bufferSize: 0, options, preallocationSize);
return SafeFileHandle.Open(Path.GetFullPath(path), mode, access, share, options, preallocationSize);
}
private static byte[] ReadAllBytesUnknownLength(FileStream fs)
{
byte[]? rentedArray = null;
Span<byte> buffer = stackalloc byte[512];
try
{
int bytesRead = 0;
while (true)
{
if (bytesRead == buffer.Length)
{
uint newLength = (uint)buffer.Length * 2;
if (newLength > Array.MaxLength)
{
newLength = (uint)Math.Max(Array.MaxLength, buffer.Length + 1);
}
byte[] tmp = ArrayPool<byte>.Shared.Rent((int)newLength);
buffer.CopyTo(tmp);
byte[]? oldRentedArray = rentedArray;
buffer = rentedArray = tmp;
if (oldRentedArray != null)
{
ArrayPool<byte>.Shared.Return(oldRentedArray);
}
}
Debug.Assert(bytesRead < buffer.Length);
int n = fs.Read(buffer.Slice(bytesRead));
if (n == 0)
{
return buffer.Slice(0, bytesRead).ToArray();
}
bytesRead += n;
}
}
finally
{
if (rentedArray != null)
{
ArrayPool<byte>.Shared.Return(rentedArray);
}
}
}
}
}
......@@ -3,7 +3,6 @@
using System.ComponentModel;
using System.IO.Strategies;
using System.Runtime.Serialization;
using System.Runtime.Versioning;
using System.Threading;
using System.Threading.Tasks;
......@@ -17,29 +16,26 @@ public class FileStream : Stream
internal const FileShare DefaultShare = FileShare.Read;
private const bool DefaultIsAsync = false;
/// <summary>Caches whether Serialization Guard has been disabled for file writes</summary>
private static int s_cachedSerializationSwitch;
private readonly FileStreamStrategy _strategy;
[EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete("This constructor has been deprecated. Please use new FileStream(SafeFileHandle handle, FileAccess access) instead. https://go.microsoft.com/fwlink/?linkid=14202")]
public FileStream(IntPtr handle, FileAccess access)
: this(handle, access, true, DefaultBufferSize, false)
: this(handle, access, true, DefaultBufferSize, DefaultIsAsync)
{
}
[EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete("This constructor has been deprecated. Please use new FileStream(SafeFileHandle handle, FileAccess access) instead, and optionally make a new SafeFileHandle with ownsHandle=false if needed. https://go.microsoft.com/fwlink/?linkid=14202")]
public FileStream(IntPtr handle, FileAccess access, bool ownsHandle)
: this(handle, access, ownsHandle, DefaultBufferSize, false)
: this(handle, access, ownsHandle, DefaultBufferSize, DefaultIsAsync)
{
}
[EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete("This constructor has been deprecated. Please use new FileStream(SafeFileHandle handle, FileAccess access, int bufferSize) instead, and optionally make a new SafeFileHandle with ownsHandle=false if needed. https://go.microsoft.com/fwlink/?linkid=14202")]
public FileStream(IntPtr handle, FileAccess access, bool ownsHandle, int bufferSize)
: this(handle, access, ownsHandle, bufferSize, false)
: this(handle, access, ownsHandle, bufferSize, DefaultIsAsync)
{
}
......@@ -68,7 +64,7 @@ public FileStream(IntPtr handle, FileAccess access, bool ownsHandle, int bufferS
}
}
private static void ValidateHandle(SafeFileHandle handle, FileAccess access, int bufferSize, bool isAsync)
private static void ValidateHandle(SafeFileHandle handle, FileAccess access, int bufferSize)
{
if (handle.IsInvalid)
{
......@@ -78,17 +74,27 @@ private static void ValidateHandle(SafeFileHandle handle, FileAccess access, int
{
throw new ArgumentOutOfRangeException(nameof(access), SR.ArgumentOutOfRange_Enum);
}
else if (bufferSize <= 0)
else if (bufferSize < 0)
{
throw new ArgumentOutOfRangeException(nameof(bufferSize), SR.ArgumentOutOfRange_NeedPosNum);
ThrowHelper.ThrowArgumentOutOfRangeException_NeedNonNegNum(nameof(bufferSize));
}
else if (handle.IsClosed)
{
ThrowHelper.ThrowObjectDisposedException_FileClosed();
}
else if (handle.IsAsync.HasValue && isAsync != handle.IsAsync.GetValueOrDefault())
}
private static void ValidateHandle(SafeFileHandle handle, FileAccess access, int bufferSize, bool isAsync)
{
ValidateHandle(handle, access, bufferSize);
if (isAsync && !handle.IsAsync)
{
ThrowHelper.ThrowArgumentException_HandleNotAsync(nameof(handle));
}
else if (!isAsync && handle.IsAsync)
{
throw new ArgumentException(SR.Arg_HandleNotAsync, nameof(handle));
ThrowHelper.ThrowArgumentException_HandleNotSync(nameof(handle));
}
}
......@@ -98,8 +104,10 @@ public FileStream(SafeFileHandle handle, FileAccess access)
}
public FileStream(SafeFileHandle handle, FileAccess access, int bufferSize)
: this(handle, access, bufferSize, FileStreamHelpers.GetDefaultIsAsync(handle, DefaultIsAsync))
{
ValidateHandle(handle, access, bufferSize);
_strategy = FileStreamHelpers.ChooseStrategy(this, handle, access, DefaultShare, bufferSize, handle.IsAsync);
}
public FileStream(SafeFileHandle handle, FileAccess access, int bufferSize, bool isAsync)
......@@ -188,10 +196,8 @@ public FileStream(string path, FileStreamOptions options)
throw new ArgumentException(SR.Format(SR.Argument_InvalidFileModeAndAccessCombo, options.Mode, options.Access), nameof(options));
}
}
else if ((options.Access & FileAccess.Write) == FileAccess.Write)
{
SerializationInfo.ThrowIfDeserializationInProgress("AllowFileWrites", ref s_cachedSerializationSwitch);
}
FileStreamHelpers.SerializationGuard(options.Access);
_strategy = FileStreamHelpers.ChooseStrategy(
this, path, options.Mode, options.Access, options.Share, options.BufferSize, options.Options, options.PreallocationSize);
......@@ -199,69 +205,7 @@ public FileStream(string path, FileStreamOptions options)
private FileStream(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize, FileOptions options, long preallocationSize)
{
if (path == null)
{
throw new ArgumentNullException(nameof(path), SR.ArgumentNull_Path);
}
else if (path.Length == 0)
{
throw new ArgumentException(SR.Argument_EmptyPath, nameof(path));
}
// don't include inheritable in our bounds check for share
FileShare tempshare = share & ~FileShare.Inheritable;
string? badArg = null;
if (mode < FileMode.CreateNew || mode > FileMode.Append)
{
badArg = nameof(mode);
}
else if (access < FileAccess.Read || access > FileAccess.ReadWrite)
{
badArg = nameof(access);
}
else if (tempshare < FileShare.None || tempshare > (FileShare.ReadWrite | FileShare.Delete))
{
badArg = nameof(share);
}
if (badArg != null)
{
throw new ArgumentOutOfRangeException(badArg, SR.ArgumentOutOfRange_Enum);
}
// NOTE: any change to FileOptions enum needs to be matched here in the error validation
if (options != FileOptions.None && (options & ~(FileOptions.WriteThrough | FileOptions.Asynchronous | FileOptions.RandomAccess | FileOptions.DeleteOnClose | FileOptions.SequentialScan | FileOptions.Encrypted | (FileOptions)0x20000000 /* NoBuffering */)) != 0)
{
throw new ArgumentOutOfRangeException(nameof(options), SR.ArgumentOutOfRange_Enum);
}
else if (bufferSize < 0)
{
throw new ArgumentOutOfRangeException(nameof(bufferSize), SR.ArgumentOutOfRange_NeedPosNum);
}
else if (preallocationSize < 0)
{
throw new ArgumentOutOfRangeException(nameof(preallocationSize), SR.ArgumentOutOfRange_NeedNonNegNum);
}
// Write access validation
if ((access & FileAccess.Write) == 0)
{
if (mode == FileMode.Truncate || mode == FileMode.CreateNew || mode == FileMode.Create || mode == FileMode.Append)
{
// No write access, mode and access disagree but flag access since mode comes first
throw new ArgumentException(SR.Format(SR.Argument_InvalidFileModeAndAccessCombo, mode, access), nameof(access));
}
}
if ((access & FileAccess.Read) != 0 && mode == FileMode.Append)
{
throw new ArgumentException(SR.Argument_InvalidAppendMode, nameof(access));
}
else if ((access & FileAccess.Write) == FileAccess.Write)
{
SerializationInfo.ThrowIfDeserializationInProgress("AllowFileWrites", ref s_cachedSerializationSwitch);
}
FileStreamHelpers.ValidateArguments(path, mode, access, share, bufferSize, options, preallocationSize);
_strategy = FileStreamHelpers.ChooseStrategy(this, path, mode, access, share, bufferSize, options, preallocationSize);
}
......@@ -277,7 +221,7 @@ public virtual void Lock(long position, long length)
{
if (position < 0 || length < 0)
{
throw new ArgumentOutOfRangeException(position < 0 ? nameof(position) : nameof(length), SR.ArgumentOutOfRange_NeedNonNegNum);
ThrowHelper.ThrowArgumentOutOfRangeException_NeedNonNegNum(position < 0 ? nameof(position) : nameof(length));
}
else if (_strategy.IsClosed)
{
......@@ -295,7 +239,7 @@ public virtual void Unlock(long position, long length)
{
if (position < 0 || length < 0)
{
throw new ArgumentOutOfRangeException(position < 0 ? nameof(position) : nameof(length), SR.ArgumentOutOfRange_NeedNonNegNum);
ThrowHelper.ThrowArgumentOutOfRangeException_NeedNonNegNum(position < 0 ? nameof(position) : nameof(length));
}
else if (_strategy.IsClosed)
{
......
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Buffers;
using System.Collections.Generic;
using System.IO.Strategies;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Win32.SafeHandles;
namespace System.IO
{
public static partial class RandomAccess
{
// IovStackThreshold matches Linux's UIO_FASTIOV, which is the number of 'struct iovec'
// that get stackalloced in the Linux kernel.
private const int IovStackThreshold = 8;
internal static long GetFileLength(SafeFileHandle handle, string? path)
{
int result = Interop.Sys.FStat(handle, out Interop.Sys.FileStatus status);
FileStreamHelpers.CheckFileCall(result, path);
return status.Size;
}
private static unsafe int ReadAtOffset(SafeFileHandle handle, Span<byte> buffer, long fileOffset)
{
fixed (byte* bufPtr = &MemoryMarshal.GetReference(buffer))
{
int result = Interop.Sys.PRead(handle, bufPtr, buffer.Length, fileOffset);
FileStreamHelpers.CheckFileCall(result, path: null);
return result;
}
}
private static unsafe long ReadScatterAtOffset(SafeFileHandle handle, IReadOnlyList<Memory<byte>> buffers, long fileOffset)
{
MemoryHandle[] handles = new MemoryHandle[buffers.Count];
Span<Interop.Sys.IOVector> vectors = buffers.Count <= IovStackThreshold ? stackalloc Interop.Sys.IOVector[IovStackThreshold] : new Interop.Sys.IOVector[buffers.Count];
long result;
try
{
int buffersCount = buffers.Count;
for (int i = 0; i < buffersCount; i++)
{
Memory<byte> buffer = buffers[i];
MemoryHandle memoryHandle = buffer.Pin();
vectors[i] = new Interop.Sys.IOVector { Base = (byte*)memoryHandle.Pointer, Count = (UIntPtr)buffer.Length };
handles[i] = memoryHandle;
}
fixed (Interop.Sys.IOVector* pinnedVectors = &MemoryMarshal.GetReference(vectors))
{
result = Interop.Sys.PReadV(handle, pinnedVectors, buffers.Count, fileOffset);
}
}
finally
{
foreach (MemoryHandle memoryHandle in handles)
{
memoryHandle.Dispose();
}
}
return FileStreamHelpers.CheckFileCall(result, path: null);
}
private static ValueTask<int> ReadAtOffsetAsync(SafeFileHandle handle, Memory<byte> buffer, long fileOffset,
CancellationToken cancellationToken)
{
return new ValueTask<int>(Task.Factory.StartNew(static state =>
{
var args = ((SafeFileHandle handle, Memory<byte> buffer, long fileOffset))state!;
return ReadAtOffset(args.handle, args.buffer.Span, args.fileOffset);
}, (handle, buffer, fileOffset), cancellationToken, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default));
}
private static ValueTask<long> ReadScatterAtOffsetAsync(SafeFileHandle handle, IReadOnlyList<Memory<byte>> buffers,
long fileOffset, CancellationToken cancellationToken)
{
return new ValueTask<long>(Task.Factory.StartNew(static state =>
{
var args = ((SafeFileHandle handle, IReadOnlyList<Memory<byte>> buffers, long fileOffset))state!;
return ReadScatterAtOffset(args.handle, args.buffers, args.fileOffset);
}, (handle, buffers, fileOffset), cancellationToken, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default));
}
private static unsafe int WriteAtOffset(SafeFileHandle handle, ReadOnlySpan<byte> buffer, long fileOffset)
{
fixed (byte* bufPtr = &MemoryMarshal.GetReference(buffer))
{
int result = Interop.Sys.PWrite(handle, bufPtr, buffer.Length, fileOffset);
FileStreamHelpers.CheckFileCall(result, path: null);
return result;
}
}
private static unsafe long WriteGatherAtOffset(SafeFileHandle handle, IReadOnlyList<ReadOnlyMemory<byte>> buffers, long fileOffset)
{
MemoryHandle[] handles = new MemoryHandle[buffers.Count];
Span<Interop.Sys.IOVector> vectors = buffers.Count <= IovStackThreshold ? stackalloc Interop.Sys.IOVector[IovStackThreshold] : new Interop.Sys.IOVector[buffers.Count ];
long result;
try
{
int buffersCount = buffers.Count;
for (int i = 0; i < buffersCount; i++)
{
ReadOnlyMemory<byte> buffer = buffers[i];
MemoryHandle memoryHandle = buffer.Pin();
vectors[i] = new Interop.Sys.IOVector { Base = (byte*)memoryHandle.Pointer, Count = (UIntPtr)buffer.Length };
handles[i] = memoryHandle;
}
fixed (Interop.Sys.IOVector* pinnedVectors = &MemoryMarshal.GetReference(vectors))
{
result = Interop.Sys.PWriteV(handle, pinnedVectors, buffers.Count, fileOffset);
}
}
finally
{
foreach (MemoryHandle memoryHandle in handles)
{
memoryHandle.Dispose();
}
}
return FileStreamHelpers.CheckFileCall(result, path: null);
}
private static ValueTask<int> WriteAtOffsetAsync(SafeFileHandle handle, ReadOnlyMemory<byte> buffer, long fileOffset,
CancellationToken cancellationToken)
{
return new ValueTask<int>(Task.Factory.StartNew(static state =>
{
var args = ((SafeFileHandle handle, ReadOnlyMemory<byte> buffer, long fileOffset))state!;
return WriteAtOffset(args.handle, args.buffer.Span, args.fileOffset);
}, (handle, buffer, fileOffset), cancellationToken, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default));
}
private static ValueTask<long> WriteGatherAtOffsetAsync(SafeFileHandle handle, IReadOnlyList<ReadOnlyMemory<byte>> buffers,
long fileOffset, CancellationToken cancellationToken)
{
return new ValueTask<long>(Task.Factory.StartNew(static state =>
{
var args = ((SafeFileHandle handle, IReadOnlyList<ReadOnlyMemory<byte>> buffers, long fileOffset))state!;
return WriteGatherAtOffset(args.handle, args.buffers, args.fileOffset);
}, (handle, buffers, fileOffset), cancellationToken, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default));
}
}
}
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Win32.SafeHandles;
namespace System.IO
{
public static partial class RandomAccess
{
/// <summary>
/// Gets the length of the file in bytes.
/// </summary>
/// <param name="handle">The file handle.</param>
/// <returns>A long value representing the length of the file in bytes.</returns>
/// <exception cref="T:System.ArgumentNullException"><paramref name="handle" /> is <see langword="null" />.</exception>
/// <exception cref="T:System.ArgumentException"><paramref name="handle" /> is invalid.</exception>
/// <exception cref="T:System.ObjectDisposedException">The file is closed.</exception>
/// <exception cref="T:System.NotSupportedException">The file does not support seeking (pipe or socket).</exception>
public static long GetLength(SafeFileHandle handle)
{
ValidateInput(handle, fileOffset: 0);
return GetFileLength(handle, path: null);
}
/// <summary>
/// Reads a sequence of bytes from given file at given offset.
/// </summary>
/// <param name="handle">The file handle.</param>
/// <param name="buffer">A region of memory. When this method returns, the contents of this region are replaced by the bytes read from the file.</param>
/// <param name="fileOffset">The file position to read from.</param>
/// <returns>The total number of bytes read into the buffer. This can be less than the number of bytes allocated in the buffer if that many bytes are not currently available, or zero (0) if the end of the file has been reached.</returns>
/// <exception cref="T:System.ArgumentNullException"><paramref name="handle" /> is <see langword="null" />.</exception>
/// <exception cref="T:System.ArgumentException"><paramref name="handle" /> is invalid.</exception>
/// <exception cref="T:System.ObjectDisposedException">The file is closed.</exception>
/// <exception cref="T:System.NotSupportedException">The file does not support seeking (pipe or socket).</exception>
/// <exception cref="T:System.ArgumentException"><paramref name="handle" /> was opened for async IO.</exception>
/// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="fileOffset" /> is negative.</exception>
/// <exception cref="T:System.UnauthorizedAccessException"><paramref name="handle" /> was not opened for reading.</exception>
/// <exception cref="T:System.IO.IOException">An I/O error occurred.</exception>
/// <remarks>Position of the file is not advanced.</remarks>
public static int Read(SafeFileHandle handle, Span<byte> buffer, long fileOffset)
{
ValidateInput(handle, fileOffset, mustBeSync: OperatingSystem.IsWindows());
return ReadAtOffset(handle, buffer, fileOffset);
}
/// <summary>
/// Reads a sequence of bytes from given file at given offset.
/// </summary>
/// <param name="handle">The file handle.</param>
/// <param name="buffers">A list of memory buffers. When this method returns, the contents of the buffers are replaced by the bytes read from the file.</param>
/// <param name="fileOffset">The file position to read from.</param>
/// <returns>The total number of bytes read into the buffers. This can be less than the number of bytes allocated in the buffers if that many bytes are not currently available, or zero (0) if the end of the file has been reached.</returns>
/// <exception cref="T:System.ArgumentNullException"><paramref name="handle" /> or <paramref name="buffers" /> is <see langword="null" />.</exception>
/// <exception cref="T:System.ArgumentException"><paramref name="handle" /> is invalid.</exception>
/// <exception cref="T:System.ObjectDisposedException">The file is closed.</exception>
/// <exception cref="T:System.NotSupportedException">The file does not support seeking (pipe or socket).</exception>
/// <exception cref="T:System.ArgumentException"><paramref name="handle" /> was opened for async IO.</exception>
/// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="fileOffset" /> is negative.</exception>
/// <exception cref="T:System.UnauthorizedAccessException"><paramref name="handle" /> was not opened for reading.</exception>
/// <exception cref="T:System.IO.IOException">An I/O error occurred.</exception>
/// <remarks>Position of the file is not advanced.</remarks>
public static long Read(SafeFileHandle handle, IReadOnlyList<Memory<byte>> buffers, long fileOffset)
{
ValidateInput(handle, fileOffset, mustBeSync: OperatingSystem.IsWindows());
ValidateBuffers(buffers);
return ReadScatterAtOffset(handle, buffers, fileOffset);
}
/// <summary>
/// Reads a sequence of bytes from given file at given offset.
/// </summary>
/// <param name="handle">The file handle.</param>
/// <param name="buffer">A region of memory. When this method returns, the contents of this region are replaced by the bytes read from the file.</param>
/// <param name="fileOffset">The file position to read from.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests. The default value is <see cref="P:System.Threading.CancellationToken.None" />.</param>
/// <returns>The total number of bytes read into the buffer. This can be less than the number of bytes allocated in the buffer if that many bytes are not currently available, or zero (0) if the end of the file has been reached.</returns>
/// <exception cref="T:System.ArgumentNullException"><paramref name="handle" /> is <see langword="null" />.</exception>
/// <exception cref="T:System.ArgumentException"><paramref name="handle" /> is invalid.</exception>
/// <exception cref="T:System.ObjectDisposedException">The file is closed.</exception>
/// <exception cref="T:System.NotSupportedException">The file does not support seeking (pipe or socket).</exception>
/// <exception cref="T:System.ArgumentException"><paramref name="handle" /> was not opened for async IO.</exception>
/// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="fileOffset" /> is negative.</exception>
/// <exception cref="T:System.UnauthorizedAccessException"><paramref name="handle" /> was not opened for reading.</exception>
/// <exception cref="T:System.IO.IOException">An I/O error occurred.</exception>
/// <remarks>Position of the file is not advanced.</remarks>
public static ValueTask<int> ReadAsync(SafeFileHandle handle, Memory<byte> buffer, long fileOffset, CancellationToken cancellationToken = default)
{
ValidateInput(handle, fileOffset, mustBeAsync: OperatingSystem.IsWindows());
if (cancellationToken.IsCancellationRequested)
{
return ValueTask.FromCanceled<int>(cancellationToken);
}
return ReadAtOffsetAsync(handle, buffer, fileOffset, cancellationToken);
}
/// <summary>
/// Reads a sequence of bytes from given file at given offset.
/// </summary>
/// <param name="handle">The file handle.</param>
/// <param name="buffers">A list of memory buffers. When this method returns, the contents of these buffers are replaced by the bytes read from the file.</param>
/// <param name="fileOffset">The file position to read from.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests. The default value is <see cref="P:System.Threading.CancellationToken.None" />.</param>
/// <returns>The total number of bytes read into the buffers. This can be less than the number of bytes allocated in the buffers if that many bytes are not currently available, or zero (0) if the end of the file has been reached.</returns>
/// <exception cref="T:System.ArgumentNullException"><paramref name="handle" /> or <paramref name="buffers" /> is <see langword="null" />.</exception>
/// <exception cref="T:System.ArgumentException"><paramref name="handle" /> is invalid.</exception>
/// <exception cref="T:System.ObjectDisposedException">The file is closed.</exception>
/// <exception cref="T:System.NotSupportedException">The file does not support seeking (pipe or socket).</exception>
/// <exception cref="T:System.ArgumentException"><paramref name="handle" /> was not opened for async IO.</exception>
/// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="fileOffset" /> is negative.</exception>
/// <exception cref="T:System.UnauthorizedAccessException"><paramref name="handle" /> was not opened for reading.</exception>
/// <exception cref="T:System.IO.IOException">An I/O error occurred.</exception>
/// <remarks>Position of the file is not advanced.</remarks>
public static ValueTask<long> ReadAsync(SafeFileHandle handle, IReadOnlyList<Memory<byte>> buffers, long fileOffset, CancellationToken cancellationToken = default)
{
ValidateInput(handle, fileOffset, mustBeAsync: OperatingSystem.IsWindows());
ValidateBuffers(buffers);
if (cancellationToken.IsCancellationRequested)
{
return ValueTask.FromCanceled<long>(cancellationToken);
}
return ReadScatterAtOffsetAsync(handle, buffers, fileOffset, cancellationToken);
}
/// <summary>
/// Writes a sequence of bytes from given buffer to given file at given offset.
/// </summary>
/// <param name="handle">The file handle.</param>
/// <param name="buffer">A region of memory. This method copies the contents of this region to the file.</param>
/// <param name="fileOffset">The file position to write to.</param>
/// <returns>The total number of bytes written into the file. This can be less than the number of bytes provided in the buffer and it's not an error.</returns>
/// <exception cref="T:System.ArgumentNullException"><paramref name="handle" /> is <see langword="null" />.</exception>
/// <exception cref="T:System.ArgumentException"><paramref name="handle" /> is invalid.</exception>
/// <exception cref="T:System.ObjectDisposedException">The file is closed.</exception>
/// <exception cref="T:System.NotSupportedException">The file does not support seeking (pipe or socket).</exception>
/// <exception cref="T:System.ArgumentException"><paramref name="handle" /> was opened for async IO.</exception>
/// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="fileOffset" /> is negative.</exception>
/// <exception cref="T:System.UnauthorizedAccessException"><paramref name="handle" /> was not opened for writing.</exception>
/// <exception cref="T:System.IO.IOException">An I/O error occurred.</exception>
/// <remarks>Position of the file is not advanced.</remarks>
public static int Write(SafeFileHandle handle, ReadOnlySpan<byte> buffer, long fileOffset)
{
ValidateInput(handle, fileOffset, mustBeSync: OperatingSystem.IsWindows());
return WriteAtOffset(handle, buffer, fileOffset);
}
/// <summary>
/// Writes a sequence of bytes from given buffers to given file at given offset.
/// </summary>
/// <param name="handle">The file handle.</param>
/// <param name="buffers">A list of memory buffers. This method copies the contents of these buffers to the file.</param>
/// <param name="fileOffset">The file position to write to.</param>
/// <returns>The total number of bytes written into the file. This can be less than the number of bytes provided in the buffers and it's not an error.</returns>
/// <exception cref="T:System.ArgumentNullException"><paramref name="handle" /> or <paramref name="buffers" /> is <see langword="null" />.</exception>
/// <exception cref="T:System.ArgumentException"><paramref name="handle" /> is invalid.</exception>
/// <exception cref="T:System.ObjectDisposedException">The file is closed.</exception>
/// <exception cref="T:System.NotSupportedException">The file does not support seeking (pipe or socket).</exception>
/// <exception cref="T:System.ArgumentException"><paramref name="handle" /> was opened for async IO.</exception>
/// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="fileOffset" /> is negative.</exception>
/// <exception cref="T:System.UnauthorizedAccessException"><paramref name="handle" /> was not opened for writing.</exception>
/// <exception cref="T:System.IO.IOException">An I/O error occurred.</exception>
/// <remarks>Position of the file is not advanced.</remarks>
public static long Write(SafeFileHandle handle, IReadOnlyList<ReadOnlyMemory<byte>> buffers, long fileOffset)
{
ValidateInput(handle, fileOffset, mustBeSync: OperatingSystem.IsWindows());
ValidateBuffers(buffers);
return WriteGatherAtOffset(handle, buffers, fileOffset);
}
/// <summary>
/// Writes a sequence of bytes from given buffer to given file at given offset.
/// </summary>
/// <param name="handle">The file handle.</param>
/// <param name="buffer">A region of memory. This method copies the contents of this region to the file.</param>
/// <param name="fileOffset">The file position to write to.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests. The default value is <see cref="P:System.Threading.CancellationToken.None" />.</param>
/// <returns>The total number of bytes written into the file. This can be less than the number of bytes provided in the buffer and it's not an error.</returns>
/// <exception cref="T:System.ArgumentNullException"><paramref name="handle" /> is <see langword="null" />.</exception>
/// <exception cref="T:System.ArgumentException"><paramref name="handle" /> is invalid.</exception>
/// <exception cref="T:System.ObjectDisposedException">The file is closed.</exception>
/// <exception cref="T:System.NotSupportedException">The file does not support seeking (pipe or socket).</exception>
/// <exception cref="T:System.ArgumentException"><paramref name="handle" /> was not opened for async IO.</exception>
/// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="fileOffset" /> is negative.</exception>
/// <exception cref="T:System.UnauthorizedAccessException"><paramref name="handle" /> was not opened for writing.</exception>
/// <exception cref="T:System.IO.IOException">An I/O error occurred.</exception>
/// <remarks>Position of the file is not advanced.</remarks>
public static ValueTask<int> WriteAsync(SafeFileHandle handle, ReadOnlyMemory<byte> buffer, long fileOffset, CancellationToken cancellationToken = default)
{
ValidateInput(handle, fileOffset, mustBeAsync: OperatingSystem.IsWindows());
if (cancellationToken.IsCancellationRequested)
{
return ValueTask.FromCanceled<int>(cancellationToken);
}
return WriteAtOffsetAsync(handle, buffer, fileOffset, cancellationToken);
}
/// <summary>
/// Writes a sequence of bytes from given buffers to given file at given offset.
/// </summary>
/// <param name="handle">The file handle.</param>
/// <param name="buffers">A list of memory buffers. This method copies the contents of these buffers to the file.</param>
/// <param name="fileOffset">The file position to write to.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests. The default value is <see cref="P:System.Threading.CancellationToken.None" />.</param>
/// <returns>The total number of bytes written into the file. This can be less than the number of bytes provided in the buffers and it's not an error.</returns>
/// <exception cref="T:System.ArgumentNullException"><paramref name="handle" /> or <paramref name="buffers"/> is <see langword="null" />.</exception>
/// <exception cref="T:System.ArgumentException"><paramref name="handle" /> is invalid.</exception>
/// <exception cref="T:System.ObjectDisposedException">The file is closed.</exception>
/// <exception cref="T:System.NotSupportedException">The file does not support seeking (pipe or socket).</exception>
/// <exception cref="T:System.ArgumentException"><paramref name="handle" /> was not opened for async IO.</exception>
/// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="fileOffset" /> is negative.</exception>
/// <exception cref="T:System.UnauthorizedAccessException"><paramref name="handle" /> was not opened for writing.</exception>
/// <exception cref="T:System.IO.IOException">An I/O error occurred.</exception>
/// <remarks>Position of the file is not advanced.</remarks>
public static ValueTask<long> WriteAsync(SafeFileHandle handle, IReadOnlyList<ReadOnlyMemory<byte>> buffers, long fileOffset, CancellationToken cancellationToken = default)
{
ValidateInput(handle, fileOffset, mustBeAsync: OperatingSystem.IsWindows());
ValidateBuffers(buffers);
if (cancellationToken.IsCancellationRequested)
{
return ValueTask.FromCanceled<long>(cancellationToken);
}
return WriteGatherAtOffsetAsync(handle, buffers, fileOffset, cancellationToken);
}
private static void ValidateInput(SafeFileHandle handle, long fileOffset, bool mustBeSync = false, bool mustBeAsync = false)
{
if (handle is null)
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.handle);
}
else if (handle.IsInvalid)
{
ThrowHelper.ThrowArgumentException_InvalidHandle(nameof(handle));
}
else if (!handle.CanSeek)
{
// CanSeek calls IsClosed, we don't want to call it twice for valid handles
if (handle.IsClosed)
{
ThrowHelper.ThrowObjectDisposedException_FileClosed();
}
ThrowHelper.ThrowNotSupportedException_UnseekableStream();
}
else if (mustBeSync && handle.IsAsync)
{
ThrowHelper.ThrowArgumentException_HandleNotSync(nameof(handle));
}
else if (mustBeAsync && !handle.IsAsync)
{
ThrowHelper.ThrowArgumentException_HandleNotAsync(nameof(handle));
}
else if (fileOffset < 0)
{
ThrowHelper.ThrowArgumentOutOfRangeException_NeedPosNum(nameof(fileOffset));
}
}
private static void ValidateBuffers<T>(IReadOnlyList<T> buffers)
{
if (buffers is null)
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.buffers);
}
}
}
}
......@@ -10,8 +10,6 @@ namespace System.IO.Strategies
{
internal sealed partial class AsyncWindowsFileStreamStrategy : WindowsFileStreamStrategy
{
private ValueTaskSource? _reusableValueTaskSource; // reusable ValueTaskSource that is currently NOT being used
internal AsyncWindowsFileStreamStrategy(SafeFileHandle handle, FileAccess access, FileShare share)
: base(handle, access, share)
{
......@@ -24,94 +22,6 @@ internal AsyncWindowsFileStreamStrategy(string path, FileMode mode, FileAccess a
internal override bool IsAsync => true;
public override ValueTask DisposeAsync()
{
// the base class must dispose ThreadPoolBinding and FileHandle
// before _preallocatedOverlapped is disposed
ValueTask result = base.DisposeAsync();
Debug.Assert(result.IsCompleted, "the method must be sync, as it performs no flushing");
Interlocked.Exchange(ref _reusableValueTaskSource, null)?.Dispose();
return result;
}
protected override void Dispose(bool disposing)
{
// the base class must dispose ThreadPoolBinding and FileHandle
// before _preallocatedOverlapped is disposed
base.Dispose(disposing);
Interlocked.Exchange(ref _reusableValueTaskSource, null)?.Dispose();
}
protected override void OnInitFromHandle(SafeFileHandle handle)
{
// This is necessary for async IO using IO Completion ports via our
// managed Threadpool API's. This calls the OS's
// BindIoCompletionCallback method, and passes in a stub for the
// LPOVERLAPPED_COMPLETION_ROUTINE. This stub looks at the Overlapped
// struct for this request and gets a delegate to a managed callback
// from there, which it then calls on a threadpool thread. (We allocate
// our native OVERLAPPED structs 2 pointers too large and store EE
// state & a handle to a delegate there.)
//
// If, however, we've already bound this file handle to our completion port,
// don't try to bind it again because it will fail. A handle can only be
// bound to a single completion port at a time.
if (handle.IsAsync != true)
{
try
{
handle.ThreadPoolBinding = ThreadPoolBoundHandle.BindHandle(handle);
}
catch (Exception ex)
{
// If you passed in a synchronous handle and told us to use
// it asynchronously, throw here.
throw new ArgumentException(SR.Arg_HandleNotAsync, nameof(handle), ex);
}
}
}
protected override void OnInit()
{
// This is necessary for async IO using IO Completion ports via our
// managed Threadpool API's. This (theoretically) calls the OS's
// BindIoCompletionCallback method, and passes in a stub for the
// LPOVERLAPPED_COMPLETION_ROUTINE. This stub looks at the Overlapped
// struct for this request and gets a delegate to a managed callback
// from there, which it then calls on a threadpool thread. (We allocate
// our native OVERLAPPED structs 2 pointers too large and store EE state
// & GC handles there, one to an IAsyncResult, the other to a delegate.)
try
{
_fileHandle.ThreadPoolBinding = ThreadPoolBoundHandle.BindHandle(_fileHandle);
}
catch (ArgumentException ex)
{
throw new IOException(SR.IO_BindHandleFailed, ex);
}
finally
{
if (_fileHandle.ThreadPoolBinding == null)
{
// We should close the handle so that the handle is not open until SafeFileHandle GC
_fileHandle.Dispose();
}
}
}
private void TryToReuse(ValueTaskSource source)
{
source._source.Reset();
if (Interlocked.CompareExchange(ref _reusableValueTaskSource, source, null) is not null)
{
source._preallocatedOverlapped.Dispose();
}
}
public override int Read(byte[] buffer, int offset, int count)
{
ValueTask<int> vt = ReadAsyncInternal(new Memory<byte>(buffer, offset, count), CancellationToken.None);
......@@ -133,75 +43,27 @@ private unsafe ValueTask<int> ReadAsyncInternal(Memory<byte> destination, Cancel
ThrowHelper.ThrowNotSupportedException_UnreadableStream();
}
// Rent the reusable ValueTaskSource, or create a new one to use if we couldn't get one (which
// should only happen on first use or if the FileStream is being used concurrently).
ValueTaskSource vts = Interlocked.Exchange(ref _reusableValueTaskSource, null) ?? new ValueTaskSource(this);
try
long positionBefore = _filePosition;
if (CanSeek)
{
NativeOverlapped* nativeOverlapped = vts.PrepareForOperation(destination);
Debug.Assert(vts._memoryHandle.Pointer != null);
// Calculate position in the file we should be at after the read is done
long positionBefore = _filePosition;
if (CanSeek)
long len = Length;
if (positionBefore + destination.Length > len)
{
long len = Length;
if (positionBefore + destination.Length > len)
{
destination = positionBefore <= len ?
destination.Slice(0, (int)(len - positionBefore)) :
default;
}
// Now set the position to read from in the NativeOverlapped struct
// For pipes, we should leave the offset fields set to 0.
nativeOverlapped->OffsetLow = unchecked((int)positionBefore);
nativeOverlapped->OffsetHigh = (int)(positionBefore >> 32);
// When using overlapped IO, the OS is not supposed to
// touch the file pointer location at all. We will adjust it
// ourselves, but only in memory. This isn't threadsafe.
_filePosition += destination.Length;
destination = positionBefore <= len ?
destination.Slice(0, (int)(len - positionBefore)) :
default;
}
// Queue an async ReadFile operation.
if (Interop.Kernel32.ReadFile(_fileHandle, (byte*)vts._memoryHandle.Pointer, destination.Length, IntPtr.Zero, nativeOverlapped) == 0)
{
// The operation failed, or it's pending.
int errorCode = FileStreamHelpers.GetLastWin32ErrorAndDisposeHandleIfInvalid(_fileHandle);
switch (errorCode)
{
case Interop.Errors.ERROR_IO_PENDING:
// Common case: IO was initiated, completion will be handled by callback.
// Register for cancellation now that the operation has been initiated.
vts.RegisterForCancellation(cancellationToken);
break;
case Interop.Errors.ERROR_BROKEN_PIPE:
// EOF on a pipe. Callback will not be called.
// We clear the overlapped status bit for this special case (failure
// to do so looks like we are freeing a pending overlapped later).
nativeOverlapped->InternalLow = IntPtr.Zero;
vts.Dispose();
return ValueTask.FromResult(0);
default:
// Error. Callback will not be called.
vts.Dispose();
return ValueTask.FromException<int>(HandleIOError(positionBefore, errorCode));
}
}
}
catch
{
vts.Dispose();
throw;
// When using overlapped IO, the OS is not supposed to
// touch the file pointer location at all. We will adjust it
// ourselves, but only in memory. This isn't threadsafe.
_filePosition += destination.Length;
}
// Completion handled by callback.
vts.FinishedScheduling();
return new ValueTask<int>(vts, vts.Version);
(SafeFileHandle.ValueTaskSource? vts, int errorCode) = RandomAccess.QueueAsyncReadFile(_fileHandle, destination, positionBefore, cancellationToken);
return vts != null
? new ValueTask<int>(vts, vts.Version)
: (errorCode == 0) ? ValueTask.FromResult(0) : ValueTask.FromException<int>(HandleIOError(positionBefore, errorCode));
}
public override void Write(byte[] buffer, int offset, int count)
......@@ -220,59 +82,20 @@ private unsafe ValueTask WriteAsyncInternal(ReadOnlyMemory<byte> source, Cancell
ThrowHelper.ThrowNotSupportedException_UnwritableStream();
}
// Rent the reusable ValueTaskSource, or create a new one to use if we couldn't get one (which
// should only happen on first use or if the FileStream is being used concurrently).
ValueTaskSource vts = Interlocked.Exchange(ref _reusableValueTaskSource, null) ?? new ValueTaskSource(this);
try
{
NativeOverlapped* nativeOverlapped = vts.PrepareForOperation(source);
Debug.Assert(vts._memoryHandle.Pointer != null);
long positionBefore = _filePosition;
if (CanSeek)
{
// Now set the position to read from in the NativeOverlapped struct
// For pipes, we should leave the offset fields set to 0.
nativeOverlapped->OffsetLow = (int)positionBefore;
nativeOverlapped->OffsetHigh = (int)(positionBefore >> 32);
// When using overlapped IO, the OS is not supposed to
// touch the file pointer location at all. We will adjust it
// ourselves, but only in memory. This isn't threadsafe.
_filePosition += source.Length;
UpdateLengthOnChangePosition();
}
// Queue an async WriteFile operation.
if (Interop.Kernel32.WriteFile(_fileHandle, (byte*)vts._memoryHandle.Pointer, source.Length, IntPtr.Zero, nativeOverlapped) == 0)
{
// The operation failed, or it's pending.
int errorCode = FileStreamHelpers.GetLastWin32ErrorAndDisposeHandleIfInvalid(_fileHandle);
if (errorCode == Interop.Errors.ERROR_IO_PENDING)
{
// Common case: IO was initiated, completion will be handled by callback.
// Register for cancellation now that the operation has been initiated.
vts.RegisterForCancellation(cancellationToken);
}
else
{
// Error. Callback will not be invoked.
vts.Dispose();
return errorCode == Interop.Errors.ERROR_NO_DATA ? // EOF on a pipe. IO callback will not be called.
ValueTask.CompletedTask :
ValueTask.FromException(HandleIOError(positionBefore, errorCode));
}
}
}
catch
long positionBefore = _filePosition;
if (CanSeek)
{
vts.Dispose();
throw;
// When using overlapped IO, the OS is not supposed to
// touch the file pointer location at all. We will adjust it
// ourselves, but only in memory. This isn't threadsafe.
_filePosition += source.Length;
UpdateLengthOnChangePosition();
}
// Completion handled by callback.
vts.FinishedScheduling();
return new ValueTask(vts, vts.Version);
(SafeFileHandle.ValueTaskSource? vts, int errorCode) = RandomAccess.QueueAsyncWriteFile(_fileHandle, source, positionBefore, cancellationToken);
return vts != null
? new ValueTask(vts, vts.Version)
: (errorCode == 0) ? ValueTask.CompletedTask : ValueTask.FromException(HandleIOError(positionBefore, errorCode));
}
private Exception HandleIOError(long positionBefore, int errorCode)
......@@ -283,9 +106,7 @@ private Exception HandleIOError(long positionBefore, int errorCode)
_filePosition = positionBefore;
}
return errorCode == Interop.Errors.ERROR_HANDLE_EOF ?
ThrowHelper.CreateEndOfFileException() :
Win32Marshal.GetExceptionForWin32Error(errorCode, _path);
return SafeFileHandle.ValueTaskSource.GetIOError(errorCode, _path);
}
public override Task FlushAsync(CancellationToken cancellationToken) => Task.CompletedTask; // no buffering = nothing to flush
......
......@@ -2,11 +2,6 @@
// The .NET Foundation licenses this file to you under the MIT license.
using Microsoft.Win32.SafeHandles;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Threading;
using System.Threading.Tasks;
namespace System.IO.Strategies
{
......@@ -20,88 +15,18 @@ private static FileStreamStrategy ChooseStrategyCore(SafeFileHandle handle, File
private static FileStreamStrategy ChooseStrategyCore(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize, FileOptions options, long preallocationSize)
=> new Net5CompatFileStreamStrategy(path, mode, access, share, bufferSize == 0 ? 1 : bufferSize, options, preallocationSize);
internal static SafeFileHandle OpenHandle(string path, FileMode mode, FileAccess access, FileShare share, FileOptions options, long preallocationSize)
internal static long CheckFileCall(long result, string? path, bool ignoreNotSupported = false)
{
// Translate the arguments into arguments for an open call.
Interop.Sys.OpenFlags openFlags = PreOpenConfigurationFromOptions(mode, access, share, options);
// If the file gets created a new, we'll select the permissions for it. Most Unix utilities by default use 666 (read and
// write for all), so we do the same (even though this doesn't match Windows, where by default it's possible to write out
// a file and then execute it). No matter what we choose, it'll be subject to the umask applied by the system, such that the
// actual permissions will typically be less than what we select here.
const Interop.Sys.Permissions OpenPermissions =
Interop.Sys.Permissions.S_IRUSR | Interop.Sys.Permissions.S_IWUSR |
Interop.Sys.Permissions.S_IRGRP | Interop.Sys.Permissions.S_IWGRP |
Interop.Sys.Permissions.S_IROTH | Interop.Sys.Permissions.S_IWOTH;
return SafeFileHandle.Open(path!, openFlags, (int)OpenPermissions);
}
internal static bool GetDefaultIsAsync(SafeFileHandle handle, bool defaultIsAsync) => handle.IsAsync ?? defaultIsAsync;
/// <summary>Translates the FileMode, FileAccess, and FileOptions values into flags to be passed when opening the file.</summary>
/// <param name="mode">The FileMode provided to the stream's constructor.</param>
/// <param name="access">The FileAccess provided to the stream's constructor</param>
/// <param name="share">The FileShare provided to the stream's constructor</param>
/// <param name="options">The FileOptions provided to the stream's constructor</param>
/// <returns>The flags value to be passed to the open system call.</returns>
private static Interop.Sys.OpenFlags PreOpenConfigurationFromOptions(FileMode mode, FileAccess access, FileShare share, FileOptions options)
{
// Translate FileMode. Most of the values map cleanly to one or more options for open.
Interop.Sys.OpenFlags flags = default;
switch (mode)
{
default:
case FileMode.Open: // Open maps to the default behavior for open(...). No flags needed.
case FileMode.Truncate: // We truncate the file after getting the lock
break;
case FileMode.Append: // Append is the same as OpenOrCreate, except that we'll also separately jump to the end later
case FileMode.OpenOrCreate:
case FileMode.Create: // We truncate the file after getting the lock
flags |= Interop.Sys.OpenFlags.O_CREAT;
break;
case FileMode.CreateNew:
flags |= (Interop.Sys.OpenFlags.O_CREAT | Interop.Sys.OpenFlags.O_EXCL);
break;
}
// Translate FileAccess. All possible values map cleanly to corresponding values for open.
switch (access)
{
case FileAccess.Read:
flags |= Interop.Sys.OpenFlags.O_RDONLY;
break;
case FileAccess.ReadWrite:
flags |= Interop.Sys.OpenFlags.O_RDWR;
break;
case FileAccess.Write:
flags |= Interop.Sys.OpenFlags.O_WRONLY;
break;
}
// Handle Inheritable, other FileShare flags are handled by Init
if ((share & FileShare.Inheritable) == 0)
{
flags |= Interop.Sys.OpenFlags.O_CLOEXEC;
}
// Translate some FileOptions; some just aren't supported, and others will be handled after calling open.
// - Asynchronous: Handled in ctor, setting _useAsync and SafeFileHandle.IsAsync to true
// - DeleteOnClose: Doesn't have a Unix equivalent, but we approximate it in Dispose
// - Encrypted: No equivalent on Unix and is ignored
// - RandomAccess: Implemented after open if posix_fadvise is available
// - SequentialScan: Implemented after open if posix_fadvise is available
// - WriteThrough: Handled here
if ((options & FileOptions.WriteThrough) != 0)
if (result < 0)
{
flags |= Interop.Sys.OpenFlags.O_SYNC;
Interop.ErrorInfo errorInfo = Interop.Sys.GetLastErrorInfo();
if (!(ignoreNotSupported && errorInfo.Error == Interop.Error.ENOTSUP))
{
throw Interop.GetExceptionForIoErrno(errorInfo, path, isDirectory: false);
}
}
return flags;
return result;
}
}
}
......@@ -5,7 +5,6 @@
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Win32.SafeHandles;
......@@ -61,202 +60,6 @@ private static FileStreamStrategy ChooseStrategyCore(string path, FileMode mode,
internal static FileStreamStrategy EnableBufferingIfNeeded(WindowsFileStreamStrategy strategy, int bufferSize)
=> bufferSize > 1 ? new BufferedFileStreamStrategy(strategy, bufferSize) : strategy;
internal static SafeFileHandle OpenHandle(string path, FileMode mode, FileAccess access, FileShare share, FileOptions options, long preallocationSize)
=> CreateFileOpenHandle(path, mode, access, share, options, preallocationSize);
private static unsafe SafeFileHandle CreateFileOpenHandle(string path, FileMode mode, FileAccess access, FileShare share, FileOptions options, long preallocationSize)
{
using (DisableMediaInsertionPrompt.Create())
{
Debug.Assert(path != null);
if (ShouldPreallocate(preallocationSize, access, mode))
{
IntPtr fileHandle = NtCreateFile(path, mode, access, share, options, preallocationSize);
return ValidateFileHandle(new SafeFileHandle(fileHandle, ownsHandle: true), path, (options & FileOptions.Asynchronous) != 0);
}
Interop.Kernel32.SECURITY_ATTRIBUTES secAttrs = GetSecAttrs(share);
int fAccess =
((access & FileAccess.Read) == FileAccess.Read ? Interop.Kernel32.GenericOperations.GENERIC_READ : 0) |
((access & FileAccess.Write) == FileAccess.Write ? Interop.Kernel32.GenericOperations.GENERIC_WRITE : 0);
// Our Inheritable bit was stolen from Windows, but should be set in
// the security attributes class. Don't leave this bit set.
share &= ~FileShare.Inheritable;
// Must use a valid Win32 constant here...
if (mode == FileMode.Append)
mode = FileMode.OpenOrCreate;
int flagsAndAttributes = (int)options;
// For mitigating local elevation of privilege attack through named pipes
// make sure we always call CreateFile with SECURITY_ANONYMOUS so that the
// named pipe server can't impersonate a high privileged client security context
// (note that this is the effective default on CreateFile2)
flagsAndAttributes |= (Interop.Kernel32.SecurityOptions.SECURITY_SQOS_PRESENT | Interop.Kernel32.SecurityOptions.SECURITY_ANONYMOUS);
SafeFileHandle safeFileHandle = ValidateFileHandle(
Interop.Kernel32.CreateFile(path, fAccess, share, &secAttrs, mode, flagsAndAttributes, IntPtr.Zero),
path,
(options & FileOptions.Asynchronous) != 0);
return safeFileHandle;
}
}
private static IntPtr NtCreateFile(string fullPath, FileMode mode, FileAccess access, FileShare share, FileOptions options, long preallocationSize)
{
uint ntStatus;
IntPtr fileHandle;
const string mandatoryNtPrefix = @"\??\";
if (fullPath.StartsWith(mandatoryNtPrefix, StringComparison.Ordinal))
{
(ntStatus, fileHandle) = Interop.NtDll.CreateFile(fullPath, mode, access, share, options, preallocationSize);
}
else
{
var vsb = new ValueStringBuilder(stackalloc char[1024]);
vsb.Append(mandatoryNtPrefix);
if (fullPath.StartsWith(@"\\?\", StringComparison.Ordinal)) // NtCreateFile does not support "\\?\" prefix, only "\??\"
{
vsb.Append(fullPath.AsSpan(4));
}
else
{
vsb.Append(fullPath);
}
(ntStatus, fileHandle) = Interop.NtDll.CreateFile(vsb.AsSpan(), mode, access, share, options, preallocationSize);
vsb.Dispose();
}
switch (ntStatus)
{
case 0:
return fileHandle;
case Interop.NtDll.NT_ERROR_STATUS_DISK_FULL:
throw new IOException(SR.Format(SR.IO_DiskFull_Path_AllocationSize, fullPath, preallocationSize));
// NtCreateFile has a bug and it reports STATUS_INVALID_PARAMETER for files
// that are too big for the current file system. Example: creating a 4GB+1 file on a FAT32 drive.
case Interop.NtDll.NT_STATUS_INVALID_PARAMETER:
case Interop.NtDll.NT_ERROR_STATUS_FILE_TOO_LARGE:
throw new IOException(SR.Format(SR.IO_FileTooLarge_Path_AllocationSize, fullPath, preallocationSize));
default:
int error = (int)Interop.NtDll.RtlNtStatusToDosError((int)ntStatus);
throw Win32Marshal.GetExceptionForWin32Error(error, fullPath);
}
}
internal static bool GetDefaultIsAsync(SafeFileHandle handle, bool defaultIsAsync)
{
return handle.IsAsync ?? !IsHandleSynchronous(handle, ignoreInvalid: true) ?? defaultIsAsync;
}
internal static unsafe bool? IsHandleSynchronous(SafeFileHandle fileHandle, bool ignoreInvalid)
{
if (fileHandle.IsInvalid)
return null;
uint fileMode;
int status = Interop.NtDll.NtQueryInformationFile(
FileHandle: fileHandle,
IoStatusBlock: out _,
FileInformation: &fileMode,
Length: sizeof(uint),
FileInformationClass: Interop.NtDll.FileModeInformation);
switch (status)
{
case 0:
// We were successful
break;
case Interop.NtDll.STATUS_INVALID_HANDLE:
if (!ignoreInvalid)
{
throw Win32Marshal.GetExceptionForWin32Error(Interop.Errors.ERROR_INVALID_HANDLE);
}
else
{
return null;
}
default:
// Something else is preventing access
Debug.Fail("Unable to get the file mode information, status was" + status.ToString());
return null;
}
// If either of these two flags are set, the file handle is synchronous (not overlapped)
return (fileMode & (uint)(Interop.NtDll.CreateOptions.FILE_SYNCHRONOUS_IO_ALERT | Interop.NtDll.CreateOptions.FILE_SYNCHRONOUS_IO_NONALERT)) > 0;
}
internal static void VerifyHandleIsSync(SafeFileHandle handle)
{
// As we can accurately check the handle type when we have access to NtQueryInformationFile we don't need to skip for
// any particular file handle type.
// If the handle was passed in without an explicit async setting, we already looked it up in GetDefaultIsAsync
if (!handle.IsAsync.HasValue)
return;
// If we can't check the handle, just assume it is ok.
if (!(IsHandleSynchronous(handle, ignoreInvalid: false) ?? true))
ThrowHelper.ThrowArgumentException_HandleNotSync(nameof(handle));
}
private static unsafe Interop.Kernel32.SECURITY_ATTRIBUTES GetSecAttrs(FileShare share)
{
Interop.Kernel32.SECURITY_ATTRIBUTES secAttrs = default;
if ((share & FileShare.Inheritable) != 0)
{
secAttrs = new Interop.Kernel32.SECURITY_ATTRIBUTES
{
nLength = (uint)sizeof(Interop.Kernel32.SECURITY_ATTRIBUTES),
bInheritHandle = Interop.BOOL.TRUE
};
}
return secAttrs;
}
private static SafeFileHandle ValidateFileHandle(SafeFileHandle fileHandle, string path, bool useAsyncIO)
{
if (fileHandle.IsInvalid)
{
// Return a meaningful exception with the full path.
// NT5 oddity - when trying to open "C:\" as a Win32FileStream,
// we usually get ERROR_PATH_NOT_FOUND from the OS. We should
// probably be consistent w/ every other directory.
int errorCode = Marshal.GetLastPInvokeError();
if (errorCode == Interop.Errors.ERROR_PATH_NOT_FOUND && path!.Length == PathInternal.GetRootLength(path))
errorCode = Interop.Errors.ERROR_ACCESS_DENIED;
throw Win32Marshal.GetExceptionForWin32Error(errorCode, path);
}
fileHandle.IsAsync = useAsyncIO;
return fileHandle;
}
internal static unsafe long GetFileLength(SafeFileHandle handle, string? path)
{
Interop.Kernel32.FILE_STANDARD_INFO info;
if (!Interop.Kernel32.GetFileInformationByHandleEx(handle, Interop.Kernel32.FileStandardInfo, &info, (uint)sizeof(Interop.Kernel32.FILE_STANDARD_INFO)))
{
throw Win32Marshal.GetExceptionForLastWin32Error(path);
}
return info.EndOfFile;
}
internal static void FlushToDisk(SafeFileHandle handle, string? path)
{
if (!Interop.Kernel32.FlushFileBuffers(handle))
......@@ -347,7 +150,7 @@ internal static void ValidateFileTypeForNonExtendedPaths(SafeFileHandle handle,
// we were explicitly passed a path that has \\?\. GetFullPath() will turn paths like C:\foo\con.txt into
// \\.\CON, so we'll only allow the \\?\ syntax.
int fileType = Interop.Kernel32.GetFileType(handle);
int fileType = handle.GetFileType();
if (fileType != Interop.Kernel32.FileTypes.FILE_TYPE_DISK)
{
int errorCode = fileType == Interop.Kernel32.FileTypes.FILE_TYPE_UNKNOWN
......@@ -365,18 +168,6 @@ internal static void ValidateFileTypeForNonExtendedPaths(SafeFileHandle handle,
}
}
internal static void GetFileTypeSpecificInformation(SafeFileHandle handle, out bool canSeek, out bool isPipe)
{
int handleType = Interop.Kernel32.GetFileType(handle);
Debug.Assert(handleType == Interop.Kernel32.FileTypes.FILE_TYPE_DISK
|| handleType == Interop.Kernel32.FileTypes.FILE_TYPE_PIPE
|| handleType == Interop.Kernel32.FileTypes.FILE_TYPE_CHAR,
"FileStream was passed an unknown file type!");
canSeek = handleType == Interop.Kernel32.FileTypes.FILE_TYPE_DISK;
isPipe = handleType == Interop.Kernel32.FileTypes.FILE_TYPE_PIPE;
}
internal static unsafe void SetFileLength(SafeFileHandle handle, string? path, long length)
{
var eofInfo = new Interop.Kernel32.FILE_END_OF_FILE_INFO
......@@ -397,70 +188,7 @@ internal static unsafe void SetFileLength(SafeFileHandle handle, string? path, l
}
}
internal static unsafe int ReadFileNative(SafeFileHandle handle, Span<byte> bytes, bool syncUsingOverlapped, NativeOverlapped* overlapped, out int errorCode)
{
Debug.Assert(handle != null, "handle != null");
int r;
int numBytesRead = 0;
fixed (byte* p = &MemoryMarshal.GetReference(bytes))
{
r = overlapped != null ?
(syncUsingOverlapped
? Interop.Kernel32.ReadFile(handle, p, bytes.Length, out numBytesRead, overlapped)
: Interop.Kernel32.ReadFile(handle, p, bytes.Length, IntPtr.Zero, overlapped))
: Interop.Kernel32.ReadFile(handle, p, bytes.Length, out numBytesRead, IntPtr.Zero);
}
if (r == 0)
{
errorCode = GetLastWin32ErrorAndDisposeHandleIfInvalid(handle);
if (syncUsingOverlapped && errorCode == Interop.Errors.ERROR_HANDLE_EOF)
{
// https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-readfile#synchronization-and-file-position :
// "If lpOverlapped is not NULL, then when a synchronous read operation reaches the end of a file,
// ReadFile returns FALSE and GetLastError returns ERROR_HANDLE_EOF"
return numBytesRead;
}
return -1;
}
else
{
errorCode = 0;
return numBytesRead;
}
}
internal static unsafe int WriteFileNative(SafeFileHandle handle, ReadOnlySpan<byte> buffer, bool syncUsingOverlapped, NativeOverlapped* overlapped, out int errorCode)
{
Debug.Assert(handle != null, "handle != null");
int numBytesWritten = 0;
int r;
fixed (byte* p = &MemoryMarshal.GetReference(buffer))
{
r = overlapped != null ?
(syncUsingOverlapped
? Interop.Kernel32.WriteFile(handle, p, buffer.Length, out numBytesWritten, overlapped)
: Interop.Kernel32.WriteFile(handle, p, buffer.Length, IntPtr.Zero, overlapped))
: Interop.Kernel32.WriteFile(handle, p, buffer.Length, out numBytesWritten, IntPtr.Zero);
}
if (r == 0)
{
errorCode = GetLastWin32ErrorAndDisposeHandleIfInvalid(handle);
return -1;
}
else
{
errorCode = 0;
return numBytesWritten;
}
}
internal static async Task AsyncModeCopyToAsync(SafeFileHandle handle, string? path, bool canSeek, long filePosition, Stream destination, int bufferSize, CancellationToken cancellationToken)
{
......@@ -537,7 +265,7 @@ internal static async Task AsyncModeCopyToAsync(SafeFileHandle handle, string? p
}
// Kick off the read.
synchronousSuccess = ReadFileNative(handle, copyBuffer, false, readAwaitable._nativeOverlapped, out errorCode) >= 0;
synchronousSuccess = RandomAccess.ReadFileNative(handle, copyBuffer, false, readAwaitable._nativeOverlapped, out errorCode) >= 0;
}
// If the operation did not synchronously succeed, it either failed or initiated the asynchronous operation.
......
// 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.Serialization;
using Microsoft.Win32.SafeHandles;
namespace System.IO.Strategies
{
internal static partial class FileStreamHelpers
{
/// <summary>Caches whether Serialization Guard has been disabled for file writes</summary>
private static int s_cachedSerializationSwitch;
internal static bool UseNet5CompatStrategy { get; } = AppContextConfigHelper.GetBooleanConfig("System.IO.UseNet5CompatFileStream", "DOTNET_SYSTEM_IO_USENET5COMPATFILESTREAM");
internal static FileStreamStrategy ChooseStrategy(FileStream fileStream, SafeFileHandle handle, FileAccess access, FileShare share, int bufferSize, bool isAsync)
......@@ -40,5 +44,78 @@ internal static bool ShouldPreallocate(long preallocationSize, FileAccess access
=> preallocationSize > 0
&& (access & FileAccess.Write) != 0
&& mode != FileMode.Open && mode != FileMode.Append;
internal static void ValidateArguments(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize, FileOptions options, long preallocationSize)
{
if (path == null)
{
throw new ArgumentNullException(nameof(path), SR.ArgumentNull_Path);
}
else if (path.Length == 0)
{
throw new ArgumentException(SR.Argument_EmptyPath, nameof(path));
}
// don't include inheritable in our bounds check for share
FileShare tempshare = share & ~FileShare.Inheritable;
string? badArg = null;
if (mode < FileMode.CreateNew || mode > FileMode.Append)
{
badArg = nameof(mode);
}
else if (access < FileAccess.Read || access > FileAccess.ReadWrite)
{
badArg = nameof(access);
}
else if (tempshare < FileShare.None || tempshare > (FileShare.ReadWrite | FileShare.Delete))
{
badArg = nameof(share);
}
if (badArg != null)
{
throw new ArgumentOutOfRangeException(badArg, SR.ArgumentOutOfRange_Enum);
}
// NOTE: any change to FileOptions enum needs to be matched here in the error validation
if (options != FileOptions.None && (options & ~(FileOptions.WriteThrough | FileOptions.Asynchronous | FileOptions.RandomAccess | FileOptions.DeleteOnClose | FileOptions.SequentialScan | FileOptions.Encrypted | (FileOptions)0x20000000 /* NoBuffering */)) != 0)
{
throw new ArgumentOutOfRangeException(nameof(options), SR.ArgumentOutOfRange_Enum);
}
else if (bufferSize < 0)
{
ThrowHelper.ThrowArgumentOutOfRangeException_NeedNonNegNum(nameof(bufferSize));
}
else if (preallocationSize < 0)
{
ThrowHelper.ThrowArgumentOutOfRangeException_NeedNonNegNum(nameof(preallocationSize));
}
// Write access validation
if ((access & FileAccess.Write) == 0)
{
if (mode == FileMode.Truncate || mode == FileMode.CreateNew || mode == FileMode.Create || mode == FileMode.Append)
{
// No write access, mode and access disagree but flag access since mode comes first
throw new ArgumentException(SR.Format(SR.Argument_InvalidFileModeAndAccessCombo, mode, access), nameof(access));
}
}
if ((access & FileAccess.Read) != 0 && mode == FileMode.Append)
{
throw new ArgumentException(SR.Argument_InvalidAppendMode, nameof(access));
}
SerializationGuard(access);
}
internal static void SerializationGuard(FileAccess access)
{
if ((access & FileAccess.Write) == FileAccess.Write)
{
SerializationInfo.ThrowIfDeserializationInProgress("AllowFileWrites", ref s_cachedSerializationSwitch);
}
}
}
}
......@@ -13,9 +13,6 @@ namespace System.IO.Strategies
/// <summary>Provides an implementation of a file stream for Unix files.</summary>
internal sealed partial class Net5CompatFileStreamStrategy : FileStreamStrategy
{
/// <summary>File mode.</summary>
private FileMode _mode;
/// <summary>Advanced options requested when opening the file.</summary>
private FileOptions _options;
......@@ -31,56 +28,16 @@ internal sealed partial class Net5CompatFileStreamStrategy : FileStreamStrategy
/// </summary>
private AsyncState? _asyncState;
/// <summary>Lazily-initialized value for whether the file supports seeking.</summary>
private bool? _canSeek;
/// <summary>Initializes a stream for reading or writing a Unix file.</summary>
/// <param name="mode">How the file should be opened.</param>
/// <param name="share">What other access to the file should be allowed. This is currently ignored.</param>
/// <param name="originalPath">The original path specified for the FileStream.</param>
/// <param name="options">Options, passed via arguments as we have no guarantee that _options field was already set.</param>
/// <param name="preallocationSize">passed to posix_fallocate</param>
private void Init(FileMode mode, FileShare share, string originalPath, FileOptions options, long preallocationSize)
private void Init(FileMode mode, string originalPath, FileOptions options)
{
// FileStream performs most of the general argument validation. We can assume here that the arguments
// are all checked and consistent (e.g. non-null-or-empty path; valid enums in mode, access, share, and options; etc.)
// Store the arguments
_mode = mode;
_options = options;
if (_useAsyncIO)
_asyncState = new AsyncState();
_fileHandle.IsAsync = _useAsyncIO;
// Lock the file if requested via FileShare. This is only advisory locking. FileShare.None implies an exclusive
// lock on the file and all other modes use a shared lock. While this is not as granular as Windows, not mandatory,
// and not atomic with file opening, it's better than nothing.
Interop.Sys.LockOperations lockOperation = (share == FileShare.None) ? Interop.Sys.LockOperations.LOCK_EX : Interop.Sys.LockOperations.LOCK_SH;
if (Interop.Sys.FLock(_fileHandle, lockOperation | Interop.Sys.LockOperations.LOCK_NB) < 0)
{
// The only error we care about is EWOULDBLOCK, which indicates that the file is currently locked by someone
// else and we would block trying to access it. Other errors, such as ENOTSUP (locking isn't supported) or
// EACCES (the file system doesn't allow us to lock), will only hamper FileStream's usage without providing value,
// given again that this is only advisory / best-effort.
Interop.ErrorInfo errorInfo = Interop.Sys.GetLastErrorInfo();
if (errorInfo.Error == Interop.Error.EWOULDBLOCK)
{
throw Interop.GetExceptionForIoErrno(errorInfo, _path, isDirectory: false);
}
}
// These provide hints around how the file will be accessed. Specifying both RandomAccess
// and Sequential together doesn't make sense as they are two competing options on the same spectrum,
// so if both are specified, we prefer RandomAccess (behavior on Windows is unspecified if both are provided).
Interop.Sys.FileAdvice fadv =
(options & FileOptions.RandomAccess) != 0 ? Interop.Sys.FileAdvice.POSIX_FADV_RANDOM :
(options & FileOptions.SequentialScan) != 0 ? Interop.Sys.FileAdvice.POSIX_FADV_SEQUENTIAL :
0;
if (fadv != 0)
{
CheckFileCall(Interop.Sys.PosixFAdvise(_fileHandle, 0, 0, fadv),
ignoreNotSupported: true); // just a hint.
_asyncState = new AsyncState();
}
if (mode == FileMode.Append)
......@@ -88,41 +45,8 @@ private void Init(FileMode mode, FileShare share, string originalPath, FileOptio
// Jump to the end of the file if opened as Append.
_appendStart = SeekCore(_fileHandle, 0, SeekOrigin.End);
}
else if (mode == FileMode.Create || mode == FileMode.Truncate)
{
// Truncate the file now if the file mode requires it. This ensures that the file only will be truncated
// if opened successfully.
if (Interop.Sys.FTruncate(_fileHandle, 0) < 0)
{
Interop.ErrorInfo errorInfo = Interop.Sys.GetLastErrorInfo();
if (errorInfo.Error != Interop.Error.EBADF && errorInfo.Error != Interop.Error.EINVAL)
{
// We know the file descriptor is valid and we know the size argument to FTruncate is correct,
// so if EBADF or EINVAL is returned, it means we're dealing with a special file that can't be
// truncated. Ignore the error in such cases; in all others, throw.
throw Interop.GetExceptionForIoErrno(errorInfo, _path, isDirectory: false);
}
}
}
// If preallocationSize has been provided for a creatable and writeable file
if (FileStreamHelpers.ShouldPreallocate(preallocationSize, _access, mode))
{
int fallocateResult = Interop.Sys.PosixFAllocate(_fileHandle, 0, preallocationSize);
if (fallocateResult != 0)
{
_fileHandle.Dispose();
Interop.Sys.Unlink(_path!); // remove the file to mimic Windows behaviour (atomic operation)
if (fallocateResult == -1)
{
throw new IOException(SR.Format(SR.IO_DiskFull_Path_AllocationSize, _path, preallocationSize));
}
Debug.Assert(fallocateResult == -2);
throw new IOException(SR.Format(SR.IO_FileTooLarge_Path_AllocationSize, _path, preallocationSize));
}
}
Debug.Assert(_fileHandle.IsAsync == _useAsyncIO);
}
/// <summary>Initializes a stream from an already open file handle (file descriptor).</summary>
......@@ -131,42 +55,18 @@ private void InitFromHandle(SafeFileHandle handle, FileAccess access, bool useAs
if (useAsyncIO)
_asyncState = new AsyncState();
if (CanSeekCore(handle)) // use non-virtual CanSeekCore rather than CanSeek to avoid making virtual call during ctor
if (handle.CanSeek)
SeekCore(handle, 0, SeekOrigin.Current);
}
/// <summary>Gets a value indicating whether the current stream supports seeking.</summary>
public override bool CanSeek => CanSeekCore(_fileHandle);
/// <summary>Gets a value indicating whether the current stream supports seeking.</summary>
/// <remarks>
/// Separated out of CanSeek to enable making non-virtual call to this logic.
/// We also pass in the file handle to allow the constructor to use this before it stashes the handle.
/// </remarks>
private bool CanSeekCore(SafeFileHandle fileHandle)
{
if (fileHandle.IsClosed)
{
return false;
}
if (!_canSeek.HasValue)
{
// Lazily-initialize whether we're able to seek, tested by seeking to our current location.
_canSeek = Interop.Sys.LSeek(fileHandle, 0, Interop.Sys.SeekWhence.SEEK_CUR) >= 0;
}
return _canSeek.GetValueOrDefault();
}
public override bool CanSeek => _fileHandle.CanSeek;
public override long Length
{
get
{
// Get the length of the file as reported by the OS
Interop.Sys.FileStatus status;
CheckFileCall(Interop.Sys.FStat(_fileHandle, out status));
long length = status.Size;
long length = RandomAccess.GetFileLength(_fileHandle, _path);
// But we may have buffered some data to be written that puts our length
// beyond what the OS is aware of. Update accordingly.
......@@ -710,31 +610,18 @@ public override long Seek(long offset, SeekOrigin origin)
/// <returns>The new position in the stream.</returns>
private long SeekCore(SafeFileHandle fileHandle, long offset, SeekOrigin origin, bool closeInvalidHandle = false)
{
Debug.Assert(!fileHandle.IsClosed && CanSeekCore(fileHandle));
Debug.Assert(!fileHandle.IsInvalid);
Debug.Assert(fileHandle.CanSeek);
Debug.Assert(origin >= SeekOrigin.Begin && origin <= SeekOrigin.End);
long pos = CheckFileCall(Interop.Sys.LSeek(fileHandle, offset, (Interop.Sys.SeekWhence)(int)origin)); // SeekOrigin values are the same as Interop.libc.SeekWhence values
long pos = FileStreamHelpers.CheckFileCall(Interop.Sys.LSeek(fileHandle, offset, (Interop.Sys.SeekWhence)(int)origin), _path); // SeekOrigin values are the same as Interop.libc.SeekWhence values
_filePosition = pos;
return pos;
}
private long CheckFileCall(long result, bool ignoreNotSupported = false)
{
if (result < 0)
{
Interop.ErrorInfo errorInfo = Interop.Sys.GetLastErrorInfo();
if (!(ignoreNotSupported && errorInfo.Error == Interop.Error.ENOTSUP))
{
throw Interop.GetExceptionForIoErrno(errorInfo, _path, isDirectory: false);
}
}
return result;
}
private int CheckFileCall(int result, bool ignoreNotSupported = false)
{
CheckFileCall((long)result, ignoreNotSupported);
FileStreamHelpers.CheckFileCall(result, _path, ignoreNotSupported);
return result;
}
......
......@@ -89,11 +89,11 @@ internal Net5CompatFileStreamStrategy(string path, FileMode mode, FileAccess acc
if ((options & FileOptions.Asynchronous) != 0)
_useAsyncIO = true;
_fileHandle = FileStreamHelpers.OpenHandle(fullPath, mode, access, share, options, preallocationSize);
_fileHandle = SafeFileHandle.Open(fullPath, mode, access, share, options, preallocationSize);
try
{
Init(mode, share, path, options, preallocationSize);
Init(mode, path, options);
}
catch
{
......
......@@ -74,6 +74,8 @@ protected SafeHandle(IntPtr invalidHandleValue, bool ownsHandle)
}
#endif
internal bool OwnsHandle => _ownsHandle;
protected internal void SetHandle(IntPtr handle) => this.handle = handle;
public IntPtr DangerousGetHandle() => handle;
......
......@@ -21,6 +21,7 @@ public sealed partial class SafeFileHandle : Microsoft.Win32.SafeHandles.SafeHan
public SafeFileHandle() : base (default(bool)) { }
public SafeFileHandle(System.IntPtr preexistingHandle, bool ownsHandle) : base (default(bool)) { }
public override bool IsInvalid { get { throw null; } }
public bool IsAsync { get { throw null; } }
protected override bool ReleaseHandle() { throw null; }
}
public abstract partial class SafeHandleMinusOneIsInvalid : System.Runtime.InteropServices.SafeHandle
......@@ -7579,6 +7580,7 @@ public static partial class File
public static System.IO.FileStream Open(string path, System.IO.FileMode mode, System.IO.FileAccess access) { throw null; }
public static System.IO.FileStream Open(string path, System.IO.FileMode mode, System.IO.FileAccess access, System.IO.FileShare share) { throw null; }
public static System.IO.FileStream Open(string path, System.IO.FileStreamOptions options) { throw null; }
public static Microsoft.Win32.SafeHandles.SafeFileHandle OpenHandle(string path, System.IO.FileMode mode = System.IO.FileMode.Open, System.IO.FileAccess access = System.IO.FileAccess.Read, System.IO.FileShare share = System.IO.FileShare.Read, System.IO.FileOptions options = System.IO.FileOptions.None, long preallocationSize = 0) { throw null; }
public static System.IO.FileStream OpenRead(string path) { throw null; }
public static System.IO.StreamReader OpenText(string path) { throw null; }
public static System.IO.FileStream OpenWrite(string path) { throw null; }
......@@ -8092,6 +8094,18 @@ public partial class UnmanagedMemoryStream : System.IO.Stream
public override System.Threading.Tasks.ValueTask WriteAsync(System.ReadOnlyMemory<byte> buffer, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public override void WriteByte(byte value) { }
}
public static partial class RandomAccess
{
public static long GetLength(Microsoft.Win32.SafeHandles.SafeFileHandle handle) { throw null; }
public static int Read(Microsoft.Win32.SafeHandles.SafeFileHandle handle, System.Span<byte> buffer, long fileOffset) { throw null; }
public static long Read(Microsoft.Win32.SafeHandles.SafeFileHandle handle, System.Collections.Generic.IReadOnlyList<System.Memory<byte>> buffers, long fileOffset) { throw null; }
public static System.Threading.Tasks.ValueTask<int> ReadAsync(Microsoft.Win32.SafeHandles.SafeFileHandle handle, System.Memory<byte> buffer, long fileOffset, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public static System.Threading.Tasks.ValueTask<long> ReadAsync(Microsoft.Win32.SafeHandles.SafeFileHandle handle, System.Collections.Generic.IReadOnlyList<System.Memory<byte>> buffers, long fileOffset, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public static int Write(Microsoft.Win32.SafeHandles.SafeFileHandle handle, System.ReadOnlySpan<byte> buffer, long fileOffset) { throw null; }
public static long Write(Microsoft.Win32.SafeHandles.SafeFileHandle handle, System.Collections.Generic.IReadOnlyList<System.ReadOnlyMemory<byte>> buffers, long fileOffset) { throw null; }
public static System.Threading.Tasks.ValueTask<int> WriteAsync(Microsoft.Win32.SafeHandles.SafeFileHandle handle, System.ReadOnlyMemory<byte> buffer, long fileOffset, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public static System.Threading.Tasks.ValueTask<long> WriteAsync(Microsoft.Win32.SafeHandles.SafeFileHandle handle, System.Collections.Generic.IReadOnlyList<System.ReadOnlyMemory<byte>> buffers, long fileOffset, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
}
}
namespace System.IO.Enumeration
{
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册