diff --git a/src/libraries/Common/src/Interop/Unix/System.Native/Interop.PosixFAllocate.cs b/src/libraries/Common/src/Interop/Unix/System.Native/Interop.PosixFAllocate.cs
new file mode 100644
index 00000000000000..2866ed38935bdf
--- /dev/null
+++ b/src/libraries/Common/src/Interop/Unix/System.Native/Interop.PosixFAllocate.cs
@@ -0,0 +1,17 @@
+// 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;
+using Microsoft.Win32.SafeHandles;
+
+internal static partial class Interop
+{
+ internal static partial class Sys
+ {
+ ///
+ /// Returns -1 on ENOSPC, -2 on EFBIG. On success or ignorable error, 0 is returned.
+ ///
+ [DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_PosixFAllocate", SetLastError = false)]
+ internal static extern int PosixFAllocate(SafeFileHandle fd, long offset, long length);
+ }
+}
diff --git a/src/libraries/Common/src/Interop/Windows/Interop.Errors.cs b/src/libraries/Common/src/Interop/Windows/Interop.Errors.cs
index 2186d54dfb3282..338706ea8491bc 100644
--- a/src/libraries/Common/src/Interop/Windows/Interop.Errors.cs
+++ b/src/libraries/Common/src/Interop/Windows/Interop.Errors.cs
@@ -29,6 +29,7 @@ internal static partial class Errors
internal const int ERROR_FILE_EXISTS = 0x50;
internal const int ERROR_INVALID_PARAMETER = 0x57;
internal const int ERROR_BROKEN_PIPE = 0x6D;
+ internal const int ERROR_DISK_FULL = 0x70;
internal const int ERROR_SEM_TIMEOUT = 0x79;
internal const int ERROR_CALL_NOT_IMPLEMENTED = 0x78;
internal const int ERROR_INSUFFICIENT_BUFFER = 0x7A;
@@ -43,6 +44,7 @@ internal static partial class Errors
internal const int ERROR_ENVVAR_NOT_FOUND = 0xCB;
internal const int ERROR_FILENAME_EXCED_RANGE = 0xCE;
internal const int ERROR_EXE_MACHINE_TYPE_MISMATCH = 0xD8;
+ internal const int ERROR_FILE_TOO_LARGE = 0xDF;
internal const int ERROR_PIPE_BUSY = 0xE7;
internal const int ERROR_NO_DATA = 0xE8;
internal const int ERROR_PIPE_NOT_CONNECTED = 0xE9;
diff --git a/src/libraries/Common/src/Interop/Windows/Interop.OBJECT_ATTRIBUTES.cs b/src/libraries/Common/src/Interop/Windows/Interop.OBJECT_ATTRIBUTES.cs
index f89fb97c9fdc6a..f0399f82153081 100644
--- a/src/libraries/Common/src/Interop/Windows/Interop.OBJECT_ATTRIBUTES.cs
+++ b/src/libraries/Common/src/Interop/Windows/Interop.OBJECT_ATTRIBUTES.cs
@@ -38,19 +38,19 @@ internal unsafe struct OBJECT_ATTRIBUTES
/// Optional quality of service to be applied to the object. Used to indicate
/// security impersonation level and context tracking mode (dynamic or static).
///
- public void* SecurityQualityOfService;
+ public SECURITY_QUALITY_OF_SERVICE* SecurityQualityOfService;
///
/// Equivalent of InitializeObjectAttributes macro with the exception that you can directly set SQOS.
///
- public unsafe OBJECT_ATTRIBUTES(UNICODE_STRING* objectName, ObjectAttributes attributes, IntPtr rootDirectory)
+ public unsafe OBJECT_ATTRIBUTES(UNICODE_STRING* objectName, ObjectAttributes attributes, IntPtr rootDirectory, SECURITY_QUALITY_OF_SERVICE* securityQualityOfService = null)
{
Length = (uint)sizeof(OBJECT_ATTRIBUTES);
RootDirectory = rootDirectory;
ObjectName = objectName;
Attributes = attributes;
SecurityDescriptor = null;
- SecurityQualityOfService = null;
+ SecurityQualityOfService = securityQualityOfService;
}
}
diff --git a/src/libraries/Common/src/Interop/Windows/Interop.SECURITY_QUALITY_OF_SERVICE.cs b/src/libraries/Common/src/Interop/Windows/Interop.SECURITY_QUALITY_OF_SERVICE.cs
new file mode 100644
index 00000000000000..3b63485ef987e3
--- /dev/null
+++ b/src/libraries/Common/src/Interop/Windows/Interop.SECURITY_QUALITY_OF_SERVICE.cs
@@ -0,0 +1,75 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+internal static partial class Interop
+{
+ ///
+ /// SECURITY_QUALITY_OF_SERVICE structure.
+ /// Used to support client impersonation. Client specifies this to a server to allow
+ /// it to impersonate the client.
+ ///
+ internal unsafe struct SECURITY_QUALITY_OF_SERVICE
+ {
+ public uint Length;
+ public ImpersonationLevel ImpersonationLevel;
+ public ContextTrackingMode ContextTrackingMode;
+ public BOOLEAN EffectiveOnly;
+
+ public unsafe SECURITY_QUALITY_OF_SERVICE(ImpersonationLevel impersonationLevel, ContextTrackingMode contextTrackingMode, bool effectiveOnly)
+ {
+ Length = (uint)sizeof(SECURITY_QUALITY_OF_SERVICE);
+ ImpersonationLevel = impersonationLevel;
+ ContextTrackingMode = contextTrackingMode;
+ EffectiveOnly = effectiveOnly ? BOOLEAN.TRUE : BOOLEAN.FALSE;
+ }
+ }
+
+ ///
+ /// SECURITY_IMPERSONATION_LEVEL enumeration values.
+ /// [SECURITY_IMPERSONATION_LEVEL]
+ ///
+ public enum ImpersonationLevel : uint
+ {
+ ///
+ /// The server process cannot obtain identification information about the client and cannot impersonate the client.
+ /// [SecurityAnonymous]
+ ///
+ Anonymous,
+
+ ///
+ /// The server process can obtain identification information about the client, but cannot impersonate the client.
+ /// [SecurityIdentification]
+ ///
+ Identification,
+
+ ///
+ /// The server process can impersonate the client's security context on it's local system.
+ /// [SecurityImpersonation]
+ ///
+ Impersonation,
+
+ ///
+ /// The server process can impersonate the client's security context on remote systems.
+ /// [SecurityDelegation]
+ ///
+ Delegation
+ }
+
+ ///
+ /// SECURITY_CONTEXT_TRACKING_MODE
+ ///
+ public enum ContextTrackingMode : byte
+ {
+ ///
+ /// The server is given a snapshot of the client's security context.
+ /// [SECURITY_STATIC_TRACKING]
+ ///
+ Static = 0x00,
+
+ ///
+ /// The server is continually updated with changes.
+ /// [SECURITY_DYNAMIC_TRACKING]
+ ///
+ Dynamic = 0x01
+ }
+}
diff --git a/src/libraries/Common/src/Interop/Windows/NtDll/Interop.NtCreateFile.cs b/src/libraries/Common/src/Interop/Windows/NtDll/Interop.NtCreateFile.cs
index e2f518e409dca0..9ce82c7f4ad453 100644
--- a/src/libraries/Common/src/Interop/Windows/NtDll/Interop.NtCreateFile.cs
+++ b/src/libraries/Common/src/Interop/Windows/NtDll/Interop.NtCreateFile.cs
@@ -2,39 +2,47 @@
// The .NET Foundation licenses this file to you under the MIT license.
using System;
+using System.Diagnostics;
+using System.IO;
using System.Runtime.InteropServices;
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)]
- private static extern unsafe int NtCreateFile(
+ private static extern unsafe uint NtCreateFile(
out IntPtr FileHandle,
DesiredAccess DesiredAccess,
ref OBJECT_ATTRIBUTES ObjectAttributes,
out IO_STATUS_BLOCK IoStatusBlock,
long* AllocationSize,
- System.IO.FileAttributes FileAttributes,
- System.IO.FileShare ShareAccess,
+ FileAttributes FileAttributes,
+ FileShare ShareAccess,
CreateDisposition CreateDisposition,
CreateOptions CreateOptions,
void* EaBuffer,
uint EaLength);
- internal static unsafe (int status, IntPtr handle) CreateFile(
+ internal static unsafe (uint status, IntPtr handle) CreateFile(
ReadOnlySpan path,
IntPtr rootDirectory,
CreateDisposition createDisposition,
DesiredAccess desiredAccess = DesiredAccess.FILE_GENERIC_READ | DesiredAccess.SYNCHRONIZE,
- System.IO.FileShare shareAccess = System.IO.FileShare.ReadWrite | System.IO.FileShare.Delete,
- System.IO.FileAttributes fileAttributes = 0,
+ FileShare shareAccess = FileShare.ReadWrite | FileShare.Delete,
+ FileAttributes fileAttributes = 0,
CreateOptions createOptions = CreateOptions.FILE_SYNCHRONOUS_IO_NONALERT,
ObjectAttributes objectAttributes = ObjectAttributes.OBJ_CASE_INSENSITIVE,
void* eaBuffer = null,
- uint eaLength = 0)
+ uint eaLength = 0,
+ long* preallocationSize = null,
+ SECURITY_QUALITY_OF_SERVICE* securityQualityOfService = null)
{
fixed (char* c = &MemoryMarshal.GetReference(path))
{
@@ -48,14 +56,15 @@ internal static unsafe (int status, IntPtr handle) CreateFile(
OBJECT_ATTRIBUTES attributes = new OBJECT_ATTRIBUTES(
&name,
objectAttributes,
- rootDirectory);
+ rootDirectory,
+ securityQualityOfService);
- int status = NtCreateFile(
+ uint status = NtCreateFile(
out IntPtr handle,
desiredAccess,
ref attributes,
out IO_STATUS_BLOCK statusBlock,
- AllocationSize: null,
+ AllocationSize: preallocationSize,
fileAttributes,
shareAccess,
createDisposition,
@@ -67,6 +76,122 @@ internal static unsafe (int status, IntPtr handle) CreateFile(
}
}
+ internal static unsafe (uint status, IntPtr handle) CreateFile(ReadOnlySpan 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
+ // named pipe server can't impersonate a high privileged client security context
+ SECURITY_QUALITY_OF_SERVICE securityQualityOfService = new SECURITY_QUALITY_OF_SERVICE(
+ ImpersonationLevel.Anonymous, // SECURITY_ANONYMOUS
+ ContextTrackingMode.Static,
+ effectiveOnly: false);
+
+ return CreateFile(
+ path: path,
+ rootDirectory: IntPtr.Zero,
+ createDisposition: GetCreateDisposition(mode),
+ desiredAccess: GetDesiredAccess(access, mode, options),
+ shareAccess: GetShareAccess(share),
+ fileAttributes: GetFileAttributes(options),
+ createOptions: GetCreateOptions(options),
+ objectAttributes: GetObjectAttributes(share),
+ preallocationSize: &preallocationSize,
+ securityQualityOfService: &securityQualityOfService);
+ }
+
+ private static CreateDisposition GetCreateDisposition(FileMode mode)
+ {
+ switch (mode)
+ {
+ case FileMode.CreateNew:
+ return CreateDisposition.FILE_CREATE;
+ case FileMode.Create:
+ return CreateDisposition.FILE_SUPERSEDE;
+ case FileMode.OpenOrCreate:
+ case FileMode.Append: // has extra handling in GetDesiredAccess
+ return CreateDisposition.FILE_OPEN_IF;
+ case FileMode.Truncate:
+ return CreateDisposition.FILE_OVERWRITE;
+ default:
+ Debug.Assert(mode == FileMode.Open); // the enum value is validated in FileStream ctor
+ return CreateDisposition.FILE_OPEN;
+ }
+ }
+
+ private static DesiredAccess GetDesiredAccess(FileAccess access, FileMode fileMode, FileOptions options)
+ {
+ DesiredAccess result = 0;
+
+ if ((access & FileAccess.Read) != 0)
+ {
+ result |= DesiredAccess.FILE_GENERIC_READ;
+ }
+ if ((access & FileAccess.Write) != 0)
+ {
+ result |= DesiredAccess.FILE_GENERIC_WRITE;
+ }
+ if (fileMode == FileMode.Append)
+ {
+ 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)
+ {
+ result |= DesiredAccess.DELETE; // required by FILE_DELETE_ON_CLOSE and FILE_SUPERSEDE (which deletes a file if it exists)
+ }
+
+ return result;
+ }
+
+ private static FileShare GetShareAccess(FileShare share)
+ => share & ~FileShare.Inheritable; // FileShare.Inheritable is handled in GetObjectAttributes
+
+ private static FileAttributes GetFileAttributes(FileOptions options)
+ => (options & FileOptions.Encrypted) != 0 ? FileAttributes.Encrypted : 0;
+
+ // FileOptions.Encrypted is handled in GetFileAttributes
+ private static CreateOptions GetCreateOptions(FileOptions options)
+ {
+ // Every directory is just a directory FILE.
+ // FileStream does not allow for opening directories on purpose.
+ // FILE_NON_DIRECTORY_FILE is used to ensure that
+ CreateOptions result = CreateOptions.FILE_NON_DIRECTORY_FILE;
+
+ if ((options & FileOptions.WriteThrough) != 0)
+ {
+ result |= CreateOptions.FILE_WRITE_THROUGH;
+ }
+ if ((options & FileOptions.RandomAccess) != 0)
+ {
+ result |= CreateOptions.FILE_RANDOM_ACCESS;
+ }
+ if ((options & FileOptions.SequentialScan) != 0)
+ {
+ result |= CreateOptions.FILE_SEQUENTIAL_ONLY;
+ }
+ if ((options & FileOptions.DeleteOnClose) != 0)
+ {
+ result |= CreateOptions.FILE_DELETE_ON_CLOSE; // has extra handling in GetDesiredAccess
+ }
+ if ((options & FileOptions.Asynchronous) == 0)
+ {
+ // it's async by default, so we need to disable it when async was not requested
+ result |= CreateOptions.FILE_SYNCHRONOUS_IO_NONALERT; // has extra handling in GetDesiredAccess
+ }
+ if (((int)options & 0x20000000) != 0) // NoBuffering
+ {
+ result |= CreateOptions.FILE_NO_INTERMEDIATE_BUFFERING;
+ }
+
+ return result;
+ }
+
+ private static ObjectAttributes GetObjectAttributes(FileShare share)
+ => (share & FileShare.Inheritable) != 0 ? ObjectAttributes.OBJ_INHERIT : 0;
+
///
/// File creation disposition when calling directly to NT APIs.
///
diff --git a/src/libraries/Common/src/Interop/Windows/NtDll/Interop.NtQueryInformationFile.cs b/src/libraries/Common/src/Interop/Windows/NtDll/Interop.NtQueryInformationFile.cs
index ccec236d394e21..a9c79cd1d68692 100644
--- a/src/libraries/Common/src/Interop/Windows/NtDll/Interop.NtQueryInformationFile.cs
+++ b/src/libraries/Common/src/Interop/Windows/NtDll/Interop.NtQueryInformationFile.cs
@@ -25,8 +25,6 @@ internal struct IO_STATUS_BLOCK
}
internal const uint FileModeInformation = 16;
- internal const uint FILE_SYNCHRONOUS_IO_ALERT = 0x00000010;
- internal const uint FILE_SYNCHRONOUS_IO_NONALERT = 0x00000020;
internal const int STATUS_INVALID_HANDLE = unchecked((int)0xC0000008);
}
diff --git a/src/libraries/Microsoft.IO.Redist/src/Microsoft.IO.Redist.csproj b/src/libraries/Microsoft.IO.Redist/src/Microsoft.IO.Redist.csproj
index d116870a954500..94b9552748a987 100644
--- a/src/libraries/Microsoft.IO.Redist/src/Microsoft.IO.Redist.csproj
+++ b/src/libraries/Microsoft.IO.Redist/src/Microsoft.IO.Redist.csproj
@@ -136,6 +136,8 @@
Link="Common\Interop\Windows\Interop.OBJECT_ATTRIBUTES.cs" />
+
length
+
+ while ((result = fcntl(fileDescriptor, F_PREALLOCATE, &fstore)) == -1 && errno == EINTR);
+
+ if (result == -1)
+ {
+ // we have failed to allocate contiguous space, let's try non-contiguous
+ fstore.fst_flags = F_ALLOCATEALL; // all or nothing
+ while ((result = fcntl(fileDescriptor, F_PREALLOCATE, &fstore)) == -1 && errno == EINTR);
+ }
+#elif defined(F_ALLOCSP) || defined(F_ALLOCSP64) // FreeBSD
+ #if HAVE_FLOCK64
+ struct flock64 lockArgs;
+ int command = F_ALLOCSP64;
+ #else
+ struct flock lockArgs;
+ int command = F_ALLOCSP;
+ #endif
+
+ lockArgs.l_whence = SEEK_SET;
+ lockArgs.l_start = (off_t)offset;
+ lockArgs.l_len = (off_t)length;
+
+ while ((result = fcntl(fileDescriptor, command, &lockArgs)) == -1 && errno == EINTR);
+#endif
+
+#if defined(F_PREALLOCATE) || defined(F_ALLOCSP) || defined(F_ALLOCSP64)
+ // most of the Unixes implement posix_fallocate which does NOT set the last error
+ // fctnl does, but to mimic the posix_fallocate behaviour we just return error
+ if (result == -1)
+ {
+ result = errno;
+ }
+ else
+ {
+ // align the behaviour with what posix_fallocate does (change reported file size)
+ ftruncate(fileDescriptor, length);
+ }
+#endif
+
+ // error codes can be OS-specific, so this is why this handling is done here rather than in the managed layer
+ switch (result)
+ {
+ case ENOSPC: // there was not enough space
+ return -1;
+ case EFBIG: // the file was too large
+ return -2;
+ case ENODEV: // not a regular file
+ case ESPIPE: // a pipe
+ // We ignore it, as FileStream contract makes it clear that allocationSize is ignored for non-regular files.
+ return 0;
+ case EINVAL:
+ // We control the offset and length so they are correct.
+ assert_msg(length >= 0, "Invalid length value", (int)length);
+ // But if the underlying filesystem does not support the operation, we just ignore it and treat as a hint.
+ return 0;
+ default:
+ assert(result != EINTR); // it can't happen here as we retry the call on EINTR
+ assert(result != EBADF); // it can't happen here as this method is being called after a succesfull call to open (with write permissions) before returning the SafeFileHandle to the user
+ return 0;
+ }
+}
+
int32_t SystemNative_Read(intptr_t fd, void* buffer, int32_t bufferSize)
{
return Common_Read(fd, buffer, bufferSize);
@@ -1184,7 +1263,7 @@ int32_t SystemNative_CopyFile(intptr_t sourceFd, intptr_t destinationFd)
#endif
}
// If we copied to a filesystem (eg EXFAT) that does not preserve POSIX ownership, all files appear
- // to be owned by root. If we aren't running as root, then we won't be an owner of our new file, and
+ // to be owned by root. If we aren't running as root, then we won't be an owner of our new file, and
// attempting to copy metadata to it will fail with EPERM. We have copied successfully, we just can't
// copy metadata. The best thing we can do is skip copying the metadata.
if (ret != 0 && errno != EPERM)
diff --git a/src/libraries/Native/Unix/System.Native/pal_io.h b/src/libraries/Native/Unix/System.Native/pal_io.h
index e82cffe8a8c3a9..e3d402d9be5f66 100644
--- a/src/libraries/Native/Unix/System.Native/pal_io.h
+++ b/src/libraries/Native/Unix/System.Native/pal_io.h
@@ -604,6 +604,13 @@ PALEXPORT int32_t SystemNative_Poll(PollEvent* pollEvents, uint32_t eventCount,
*/
PALEXPORT int32_t SystemNative_PosixFAdvise(intptr_t fd, int64_t offset, int64_t length, int32_t advice);
+/**
+ * Ensures that disk space is allocated.
+ *
+ * Returns -1 on ENOSPC, -2 on EFBIG. On success or ignorable error, 0 is returned.
+ */
+PALEXPORT int32_t SystemNative_PosixFAllocate(intptr_t fd, int64_t offset, int64_t length);
+
/**
* Reads the number of bytes specified into the provided buffer from the specified, opened file descriptor.
*
diff --git a/src/libraries/Native/Unix/configure.cmake b/src/libraries/Native/Unix/configure.cmake
index c430c64f7fe564..1b85454db4af22 100644
--- a/src/libraries/Native/Unix/configure.cmake
+++ b/src/libraries/Native/Unix/configure.cmake
@@ -209,6 +209,16 @@ check_symbol_exists(
fcntl.h
HAVE_POSIX_ADVISE)
+check_symbol_exists(
+ posix_fallocate
+ fcntl.h
+ HAVE_POSIX_FALLOCATE)
+
+check_symbol_exists(
+ posix_fallocate64
+ fcntl.h
+ HAVE_POSIX_FALLOCATE64)
+
check_symbol_exists(
ioctl
sys/ioctl.h
diff --git a/src/libraries/System.IO.FileSystem/src/System.IO.FileSystem.csproj b/src/libraries/System.IO.FileSystem/src/System.IO.FileSystem.csproj
index a8ce9dc0858007..8d8ff35347fe3d 100644
--- a/src/libraries/System.IO.FileSystem/src/System.IO.FileSystem.csproj
+++ b/src/libraries/System.IO.FileSystem/src/System.IO.FileSystem.csproj
@@ -1,4 +1,4 @@
-
+
true
true
@@ -105,6 +105,8 @@
Link="Common\Interop\Windows\Interop.Libraries.cs" />
+
relativePath, string fullPath)
{
- (int status, IntPtr handle) = Interop.NtDll.CreateFile(
+ (uint status, IntPtr handle) = Interop.NtDll.CreateFile(
relativePath,
_directoryHandle,
Interop.NtDll.CreateDisposition.FILE_OPEN,
@@ -68,7 +68,7 @@ private unsafe IntPtr CreateRelativeDirectoryHandle(ReadOnlySpan relativeP
createOptions: Interop.NtDll.CreateOptions.FILE_SYNCHRONOUS_IO_NONALERT | Interop.NtDll.CreateOptions.FILE_DIRECTORY_FILE
| Interop.NtDll.CreateOptions.FILE_OPEN_FOR_BACKUP_INTENT);
- switch ((uint)status)
+ switch (status)
{
case Interop.StatusOptions.STATUS_SUCCESS:
return handle;
@@ -77,7 +77,7 @@ private unsafe IntPtr CreateRelativeDirectoryHandle(ReadOnlySpan relativeP
// such as ERROR_ACCESS_DENIED. As we want to replicate Win32 handling/reporting and the mapping isn't documented,
// we should always do our logic on the converted code, not the NTSTATUS.
- int error = (int)Interop.NtDll.RtlNtStatusToDosError(status);
+ int error = (int)Interop.NtDll.RtlNtStatusToDosError((int)status);
if (ContinueOnDirectoryError(error, ignoreNotFound: true))
{
diff --git a/src/libraries/System.IO.FileSystem/tests/File/Open.cs b/src/libraries/System.IO.FileSystem/tests/File/Open.cs
index f0a7897725d10f..3a042d8a65b9ef 100644
--- a/src/libraries/System.IO.FileSystem/tests/File/Open.cs
+++ b/src/libraries/System.IO.FileSystem/tests/File/Open.cs
@@ -1,8 +1,6 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-using Xunit;
-
namespace System.IO.Tests
{
public class File_Open_str_fm : FileStream_ctor_str_fm
@@ -11,7 +9,6 @@ protected override FileStream CreateFileStream(string path, FileMode mode)
{
return File.Open(path, mode);
}
-
}
public class File_Open_str_fm_fa : FileStream_ctor_str_fm_fa
@@ -86,5 +83,4 @@ protected override string Read(string path)
return reader.ReadToEnd();
}
}
-
}
diff --git a/src/libraries/System.IO.FileSystem/tests/FileStream/Dispose.cs b/src/libraries/System.IO.FileSystem/tests/FileStream/Dispose.cs
index 283cb3e8eddc97..2a94247dc04144 100644
--- a/src/libraries/System.IO.FileSystem/tests/FileStream/Dispose.cs
+++ b/src/libraries/System.IO.FileSystem/tests/FileStream/Dispose.cs
@@ -48,7 +48,6 @@ protected override void Dispose(bool disposing)
}
}
-
[ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
public void Dispose_CallsVirtualDisposeTrueArg_ThrowsDuringFlushWriteBuffer_DisposeThrows()
{
diff --git a/src/libraries/System.IO.FileSystem/tests/FileStream/Flush.cs b/src/libraries/System.IO.FileSystem/tests/FileStream/Flush.cs
index 25dd6422410aa5..be175c6efa3015 100644
--- a/src/libraries/System.IO.FileSystem/tests/FileStream/Flush.cs
+++ b/src/libraries/System.IO.FileSystem/tests/FileStream/Flush.cs
@@ -183,6 +183,5 @@ public override void Flush(bool flushToDisk)
base.Flush(flushToDisk);
}
}
-
}
}
diff --git a/src/libraries/System.IO.FileSystem/tests/FileStream/Name.cs b/src/libraries/System.IO.FileSystem/tests/FileStream/Name.cs
index 1b088a15062a2b..cfe0c62d6c30f1 100644
--- a/src/libraries/System.IO.FileSystem/tests/FileStream/Name.cs
+++ b/src/libraries/System.IO.FileSystem/tests/FileStream/Name.cs
@@ -1,11 +1,8 @@
// 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.Globalization;
-using System.IO;
using System.Tests;
-using Microsoft.DotNet.RemoteExecutor;
using Xunit;
namespace System.IO.Tests
@@ -37,7 +34,6 @@ public void NameNormalizesPath()
}
}
-
[Fact]
public void NameReturnsUnknownForHandle()
{
diff --git a/src/libraries/System.IO.FileSystem/tests/FileStream/ToString.cs b/src/libraries/System.IO.FileSystem/tests/FileStream/ToString.cs
index 647cccf60a575a..8682f5a75b9986 100644
--- a/src/libraries/System.IO.FileSystem/tests/FileStream/ToString.cs
+++ b/src/libraries/System.IO.FileSystem/tests/FileStream/ToString.cs
@@ -1,8 +1,6 @@
// 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.IO;
using Xunit;
namespace System.IO.Tests
diff --git a/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_options_as.Browser.cs b/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_options_as.Browser.cs
new file mode 100644
index 00000000000000..03486aec6fc8c8
--- /dev/null
+++ b/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_options_as.Browser.cs
@@ -0,0 +1,19 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Xunit;
+
+namespace System.IO.Tests
+{
+ [PlatformSpecific(~TestPlatforms.Browser)]
+ public partial class FileStream_ctor_options_as : FileStream_ctor_options_as_base
+ {
+ protected override long PreallocationSize => 10;
+
+ protected override long InitialLength => 10;
+
+ private long GetExpectedFileLength(long preallocationSize) => preallocationSize;
+
+ private long GetActualPreallocationSize(FileStream fileStream) => fileStream.Length;
+ }
+}
diff --git a/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_options_as.Unix.cs b/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_options_as.Unix.cs
new file mode 100644
index 00000000000000..12e8f1641bac00
--- /dev/null
+++ b/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_options_as.Unix.cs
@@ -0,0 +1,21 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace System.IO.Tests
+{
+ public partial class FileStream_ctor_options_as : FileStream_ctor_options_as_base
+ {
+ protected override long PreallocationSize => 10;
+
+ protected override long InitialLength => 10;
+
+ private long GetExpectedFileLength(long preallocationSize) => preallocationSize;
+
+ private long GetActualPreallocationSize(FileStream fileStream)
+ {
+ // On Unix posix_fallocate modifies file length and we are using fstat to get it for verificaiton
+ Interop.Sys.FStat(fileStream.SafeFileHandle, out Interop.Sys.FileStatus fileStatus);
+ return fileStatus.Size;
+ }
+ }
+}
diff --git a/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_options_as.Windows.cs b/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_options_as.Windows.cs
new file mode 100644
index 00000000000000..bde8cd5e4692a3
--- /dev/null
+++ b/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_options_as.Windows.cs
@@ -0,0 +1,99 @@
+// 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;
+using System.Text;
+using Xunit;
+
+namespace System.IO.Tests
+{
+ public partial class FileStream_ctor_options_as : FileStream_ctor_options_as_base
+ {
+ protected override long PreallocationSize => 10;
+
+ protected override long InitialLength => 0; // Windows modifies AllocationSize, but not EndOfFile (file length)
+
+ private long GetExpectedFileLength(long preallocationSize) => 0; // Windows modifies AllocationSize, but not EndOfFile (file length)
+
+ private unsafe long GetActualPreallocationSize(FileStream fileStream)
+ {
+ Interop.Kernel32.FILE_STANDARD_INFO info;
+
+ Assert.True(Interop.Kernel32.GetFileInformationByHandleEx(fileStream.SafeFileHandle, Interop.Kernel32.FileStandardInfo, &info, (uint)sizeof(Interop.Kernel32.FILE_STANDARD_INFO)));
+
+ return info.AllocationSize;
+ }
+
+ [Theory]
+ [InlineData(@"\\?\")]
+ [InlineData(@"\??\")]
+ [InlineData("")]
+ public void ExtendedPathsAreSupported(string prefix)
+ {
+ const long preallocationSize = 123;
+
+ string filePath = prefix + Path.GetFullPath(GetPathToNonExistingFile());
+
+ using (var fs = new FileStream(filePath, GetOptions(FileMode.CreateNew, FileAccess.Write, FileShare.None, FileOptions.None, preallocationSize)))
+ {
+ Assert.True(GetActualPreallocationSize(fs) >= preallocationSize, $"Provided {preallocationSize}, actual: {GetActualPreallocationSize(fs)}");
+ }
+ }
+
+ [ConditionalTheory(nameof(IsFat32))]
+ [InlineData(FileMode.Create)]
+ [InlineData(FileMode.CreateNew)]
+ [InlineData(FileMode.OpenOrCreate)]
+ public void WhenFileIsTooLargeTheErrorMessageContainsAllDetails(FileMode mode)
+ {
+ const long tooMuch = uint.MaxValue + 1L; // more than FAT32 max size
+
+ string filePath = GetPathToNonExistingFile();
+ Assert.StartsWith(Path.GetTempPath(), filePath); // this is what IsFat32 method relies on
+
+ IOException ex = Assert.Throws(() => new FileStream(filePath, GetOptions(mode, FileAccess.Write, FileShare.None, FileOptions.None, tooMuch)));
+ Assert.Contains(filePath, ex.Message);
+ Assert.Contains(tooMuch.ToString(), ex.Message);
+
+ Assert.False(File.Exists(filePath)); // ensure it was NOT created
+ }
+
+ public static bool IsFat32
+ {
+ get
+ {
+ string testDirectory = Path.GetTempPath(); // logic taken from FileCleanupTestBase, can't call the property here as it's not static
+
+ var volumeNameBufffer = new StringBuilder(250);
+ var fileSystemNameBuffer = new StringBuilder(250);
+
+ if (GetVolumeInformation(
+ Path.GetPathRoot(testDirectory),
+ volumeNameBufffer,
+ volumeNameBufffer.Capacity,
+ out uint _,
+ out uint _,
+ out uint _,
+ fileSystemNameBuffer,
+ fileSystemNameBuffer.Capacity
+ ))
+ {
+ return fileSystemNameBuffer.ToString().Equals("FAT32", StringComparison.OrdinalIgnoreCase);
+ }
+
+ return false;
+ }
+ }
+
+ [DllImport(Interop.Libraries.Kernel32, CharSet = CharSet.Auto, SetLastError = true)]
+ public extern static bool GetVolumeInformation(
+ string rootPathName,
+ StringBuilder volumeNameBuffer,
+ int volumeNameSize,
+ out uint volumeSerialNumber,
+ out uint maximumComponentLength,
+ out uint fileSystemFlags,
+ StringBuilder fileSystemNameBuffer,
+ int fileSystemNameSize);
+ }
+}
diff --git a/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_options_as.cs b/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_options_as.cs
new file mode 100644
index 00000000000000..d98090b37f67e4
--- /dev/null
+++ b/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_options_as.cs
@@ -0,0 +1,198 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Xunit;
+
+namespace System.IO.Tests
+{
+ public abstract class FileStream_ctor_options_as_base : FileStream_ctor_str_fm_fa_fs_buffer_fo
+ {
+ protected abstract long PreallocationSize { get; }
+
+ protected override FileStream CreateFileStream(string path, FileMode mode)
+ => new FileStream(path,
+ new FileStreamOptions
+ {
+ Mode = mode,
+ Access = mode == FileMode.Append ? FileAccess.Write : FileAccess.ReadWrite,
+ PreallocationSize = PreallocationSize
+ });
+
+ protected override FileStream CreateFileStream(string path, FileMode mode, FileAccess access)
+ => new FileStream(path,
+ new FileStreamOptions
+ {
+ Mode = mode,
+ Access = access,
+ PreallocationSize = PreallocationSize
+ });
+
+ protected FileStreamOptions GetOptions(FileMode mode, FileAccess access, FileShare share, FileOptions options, long preAllocationSize)
+ => new FileStreamOptions
+ {
+ Mode = mode,
+ Access = access,
+ Share = share,
+ Options = options,
+ PreallocationSize = preAllocationSize
+ };
+ }
+
+ public class FileStream_ctor_options_as_zero : FileStream_ctor_options_as_base
+ {
+ protected override long PreallocationSize => 0; // specifying 0 should have no effect
+
+ protected override long InitialLength => 0;
+ }
+
+ [CollectionDefinition("NoParallelTests", DisableParallelization = true)]
+ public partial class NoParallelTests { }
+
+ // Don't run in parallel as the WhenDiskIsFullTheErrorMessageContainsAllDetails test
+ // consumes entire available free space on the disk (only on Linux, this is how posix_fallocate works)
+ // and if we try to run other disk-writing test in the meantime we are going to get "No space left on device" exception.
+ [Collection("NoParallelTests")]
+ public partial class FileStream_ctor_options_as : FileStream_ctor_options_as_base
+ {
+ [Fact]
+ public void NegativePreallocationSizeThrows()
+ {
+ string filePath = GetPathToNonExistingFile();
+ ArgumentOutOfRangeException ex = Assert.Throws(
+ () => new FileStream(filePath, GetOptions(FileMode.CreateNew, FileAccess.Write, FileShare.None, FileOptions.None, -1)));
+ }
+
+ [Theory]
+ [InlineData(FileMode.Create, 0L)]
+ [InlineData(FileMode.CreateNew, 0L)]
+ [InlineData(FileMode.OpenOrCreate, 0L)]
+ public void WhenFileIsCreatedWithoutPreallocationSizeSpecifiedThePreallocationSizeIsNotSet(FileMode mode, long preallocationSize)
+ {
+ using (var fs = new FileStream(GetPathToNonExistingFile(), GetOptions(mode, FileAccess.Write, FileShare.None, FileOptions.None, preallocationSize)))
+ {
+ Assert.Equal(0, GetActualPreallocationSize(fs));
+ Assert.Equal(0, fs.Length);
+ Assert.Equal(0, fs.Position);
+ }
+ }
+
+ [Theory]
+ [InlineData(FileMode.Open, 0L)]
+ [InlineData(FileMode.Open, 1L)]
+ [InlineData(FileMode.OpenOrCreate, 0L)]
+ [InlineData(FileMode.OpenOrCreate, 1L)]
+ [InlineData(FileMode.Append, 0L)]
+ [InlineData(FileMode.Append, 1L)]
+ public void WhenExistingFileIsBeingOpenedWithPreallocationSizeSpecifiedThePreallocationSizeIsNotChanged(FileMode mode, long preallocationSize)
+ {
+ const int initialSize = 1;
+ string filePath = GetPathToNonExistingFile();
+ File.WriteAllBytes(filePath, new byte[initialSize]);
+ long initialPreallocationSize;
+
+ using (var fs = new FileStream(filePath, GetOptions(mode, FileAccess.Write, FileShare.None, FileOptions.None, 0))) // preallocationSize NOT provided
+ {
+ initialPreallocationSize = GetActualPreallocationSize(fs); // just read it to ensure it's not being changed
+ }
+
+ using (var fs = new FileStream(filePath, GetOptions(mode, FileAccess.Write, FileShare.None, FileOptions.None, preallocationSize)))
+ {
+ Assert.Equal(initialPreallocationSize, GetActualPreallocationSize(fs)); // it has NOT been changed
+ Assert.Equal(initialSize, fs.Length);
+ Assert.Equal(mode == FileMode.Append ? initialSize : 0, fs.Position);
+ }
+ }
+
+ [Theory]
+ [InlineData(FileMode.Create)]
+ [InlineData(FileMode.CreateNew)]
+ [InlineData(FileMode.OpenOrCreate)]
+ public void WhenFileIsCreatedWithPreallocationSizeSpecifiedThePreallocationSizeIsSet(FileMode mode)
+ {
+ const long preallocationSize = 123;
+
+ using (var fs = new FileStream(GetPathToNonExistingFile(), GetOptions(mode, FileAccess.Write, FileShare.None, FileOptions.None, preallocationSize)))
+ {
+ // OS might allocate MORE than we have requested
+ Assert.True(GetActualPreallocationSize(fs) >= preallocationSize, $"Provided {preallocationSize}, actual: {GetActualPreallocationSize(fs)}");
+ Assert.Equal(GetExpectedFileLength(preallocationSize), fs.Length);
+ Assert.Equal(0, fs.Position);
+ }
+ }
+
+ [OuterLoop("Might allocate 1 TB file if there is enough space on the disk")]
+ // macOS fcntl doc does not mention ENOSPC error: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/fcntl.2.html
+ // But depending on the OS version, it might actually return it.
+ // Since we don't want to have unstable tests, it's better to not run it on macOS at all.
+ [PlatformSpecific(TestPlatforms.Windows | TestPlatforms.Linux)]
+ [Theory]
+ [InlineData(FileMode.Create)]
+ [InlineData(FileMode.CreateNew)]
+ [InlineData(FileMode.OpenOrCreate)]
+ public void WhenDiskIsFullTheErrorMessageContainsAllDetails(FileMode mode)
+ {
+ const long tooMuch = 1024L * 1024L * 1024L * 1024L; // 1 TB
+
+ string filePath = GetPathToNonExistingFile();
+
+ IOException ex = Assert.Throws(() => new FileStream(filePath, GetOptions(mode, FileAccess.Write, FileShare.None, FileOptions.None, tooMuch)));
+ Assert.Contains(filePath, ex.Message);
+ Assert.Contains(tooMuch.ToString(), ex.Message);
+
+ // ensure it was NOT created (provided OOTB by Windows, emulated on Unix)
+ bool exists = File.Exists(filePath);
+ if (exists)
+ {
+ File.Delete(filePath);
+ }
+ Assert.False(exists);
+ }
+
+ [Fact]
+ public void WhenFileIsTruncatedWithoutPreallocationSizeSpecifiedThePreallocationSizeIsNotSet()
+ {
+ const int initialSize = 10_000;
+
+ string filePath = GetPathToNonExistingFile();
+ File.WriteAllBytes(filePath, new byte[initialSize]);
+
+ using (var fs = new FileStream(filePath, GetOptions(FileMode.Truncate, FileAccess.Write, FileShare.None, FileOptions.None, 0)))
+ {
+ Assert.Equal(0, GetActualPreallocationSize(fs));
+ Assert.Equal(0, fs.Length);
+ Assert.Equal(0, fs.Position);
+ }
+ }
+
+ [Fact]
+ public void WhenFileIsTruncatedWithPreallocationSizeSpecifiedThePreallocationSizeIsSet()
+ {
+ const int initialSize = 10_000; // this must be more than 4kb which seems to be minimum allocation size on Windows
+ const long preallocationSize = 100;
+
+ string filePath = GetPathToNonExistingFile();
+ File.WriteAllBytes(filePath, new byte[initialSize]);
+
+ using (var fs = new FileStream(filePath, GetOptions(FileMode.Truncate, FileAccess.Write, FileShare.None, FileOptions.None, preallocationSize)))
+ {
+ Assert.True(GetActualPreallocationSize(fs) >= preallocationSize, $"Provided {preallocationSize}, actual: {GetActualPreallocationSize(fs)}");
+ // less than initial file size (file got truncated)
+ Assert.True(GetActualPreallocationSize(fs) < initialSize, $"initialSize {initialSize}, actual: {GetActualPreallocationSize(fs)}");
+ Assert.Equal(GetExpectedFileLength(preallocationSize), fs.Length);
+ Assert.Equal(0, fs.Position);
+ }
+ }
+
+ private string GetPathToNonExistingFile()
+ {
+ string filePath = GetTestFilePath();
+
+ if (File.Exists(filePath))
+ {
+ File.Delete(filePath);
+ }
+
+ return filePath;
+ }
+ }
+}
diff --git a/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_sfh_fa_buffer.cs b/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_sfh_fa_buffer.cs
index aa263624d18909..745ff57850888a 100644
--- a/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_sfh_fa_buffer.cs
+++ b/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_sfh_fa_buffer.cs
@@ -18,7 +18,6 @@ protected virtual FileStream CreateFileStream(SafeFileHandle handle, FileAccess
return new FileStream(handle, access, bufferSize);
}
-
[Theory,
InlineData(0),
InlineData(-1)]
diff --git a/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_str_fm.cs b/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_str_fm.cs
index c1b97f00cc4340..f442fbd4322190 100644
--- a/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_str_fm.cs
+++ b/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_str_fm.cs
@@ -12,6 +12,8 @@ protected virtual FileStream CreateFileStream(string path, FileMode mode)
return new FileStream(path, mode);
}
+ protected virtual long InitialLength => 0;
+
[Fact]
public void NullPathThrows()
{
@@ -50,7 +52,6 @@ public void MissingDirectory_ThrowsDirectoryNotFound(char trailingChar)
Assert.Throws(() => CreateFileStream(path, FileMode.Open));
}
-
public static TheoryData StreamSpecifiers
{
get
@@ -91,7 +92,7 @@ public void FileModeCreateExisting(string streamSpecifier)
using (FileStream fs = CreateFileStream(fileName, FileMode.Create))
{
// Ensure that the file was re-created
- Assert.Equal(0L, fs.Length);
+ Assert.Equal(InitialLength, fs.Length);
Assert.Equal(0L, fs.Position);
Assert.True(fs.CanRead);
Assert.True(fs.CanWrite);
@@ -142,7 +143,7 @@ public void FileModeOpenExisting(string streamSpecifier)
using (FileStream fs = CreateFileStream(fileName, FileMode.Open))
{
// Ensure that the file was re-opened
- Assert.Equal(1L, fs.Length);
+ Assert.Equal(Math.Max(1L, InitialLength), fs.Length);
Assert.Equal(0L, fs.Position);
Assert.True(fs.CanRead);
Assert.True(fs.CanWrite);
@@ -171,7 +172,7 @@ public void FileModeOpenOrCreateExisting(string streamSpecifier)
using (FileStream fs = CreateFileStream(fileName, FileMode.OpenOrCreate))
{
// Ensure that the file was re-opened
- Assert.Equal(1L, fs.Length);
+ Assert.Equal(Math.Max(1L, InitialLength), fs.Length);
Assert.Equal(0L, fs.Position);
Assert.True(fs.CanRead);
Assert.True(fs.CanWrite);
@@ -198,7 +199,7 @@ public void FileModeTruncateExisting(string streamSpecifier)
using (FileStream fs = CreateFileStream(fileName, FileMode.Truncate))
{
// Ensure that the file was re-opened and truncated
- Assert.Equal(0L, fs.Length);
+ Assert.Equal(InitialLength, fs.Length);
Assert.Equal(0L, fs.Position);
Assert.True(fs.CanRead);
Assert.True(fs.CanWrite);
@@ -227,8 +228,8 @@ public virtual void FileModeAppendExisting(string streamSpecifier)
using (FileStream fs = CreateFileStream(fileName, FileMode.Append))
{
// Ensure that the file was re-opened and position set to end
- Assert.Equal(1L, fs.Length);
- Assert.Equal(1L, fs.Position);
+ Assert.Equal(Math.Max(1L, InitialLength), fs.Length);
+ Assert.Equal(fs.Length, fs.Position);
Assert.False(fs.CanRead);
Assert.True(fs.CanSeek);
Assert.True(fs.CanWrite);
diff --git a/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_str_fm_fa.cs b/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_str_fm_fa.cs
index 41d7263c6f81e6..6ab972644c3000 100644
--- a/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_str_fm_fa.cs
+++ b/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_str_fm_fa.cs
@@ -1,8 +1,6 @@
// 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.IO;
using Xunit;
namespace System.IO.Tests
diff --git a/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_str_fm_fa_fs.read.cs b/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_str_fm_fa_fs.read.cs
index c1d989f12e2436..ba8c402925f2cf 100644
--- a/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_str_fm_fa_fs.read.cs
+++ b/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_str_fm_fa_fs.read.cs
@@ -1,8 +1,6 @@
// 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.IO;
using Xunit;
namespace System.IO.Tests
diff --git a/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_str_fm_fa_fs.write.cs b/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_str_fm_fa_fs.write.cs
index e0d619187c3999..ae93555bbe8316 100644
--- a/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_str_fm_fa_fs.write.cs
+++ b/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_str_fm_fa_fs.write.cs
@@ -1,8 +1,6 @@
// 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.IO;
using Xunit;
namespace System.IO.Tests
diff --git a/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_str_fm_fa_fs_buffer.cs b/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_str_fm_fa_fs_buffer.cs
index a24a4fd16d8db6..3885149161cadd 100644
--- a/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_str_fm_fa_fs_buffer.cs
+++ b/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_str_fm_fa_fs_buffer.cs
@@ -1,8 +1,6 @@
// 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.IO;
using Xunit;
namespace System.IO.Tests
diff --git a/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_str_fm_fa_fs_buffer_async.cs b/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_str_fm_fa_fs_buffer_async.cs
index c3d8f0077dc442..a93eb4acb08159 100644
--- a/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_str_fm_fa_fs_buffer_async.cs
+++ b/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_str_fm_fa_fs_buffer_async.cs
@@ -1,13 +1,11 @@
// 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.IO;
using Xunit;
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_async : FileStream_ctor_str_fm_fa_fs_buffer
{
protected sealed override FileStream CreateFileStream(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize)
@@ -20,15 +18,15 @@ protected virtual FileStream CreateFileStream(string path, FileMode mode, FileAc
return new FileStream(path, mode, access, share, bufferSize, useAsync);
}
- [Fact]
- public void ValidUseAsync()
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
+ public void ValidUseAsync(bool isAsync)
{
- using (CreateFileStream(GetTestFilePath(), FileMode.Create, FileAccess.ReadWrite, FileShare.Read, c_DefaultBufferSize, true))
- { }
-
- using (CreateFileStream(GetTestFilePath(), FileMode.Create, FileAccess.ReadWrite, FileShare.Read, c_DefaultBufferSize, false))
- { }
+ using (FileStream fs = CreateFileStream(GetTestFilePath(), FileMode.Create, FileAccess.ReadWrite, FileShare.Read, c_DefaultBufferSize, isAsync))
+ {
+ Assert.Equal(isAsync, fs.IsAsync);
+ }
}
-
}
}
diff --git a/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_str_fm_fa_fs_buffer_fo.cs b/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_str_fm_fa_fs_buffer_fo.cs
index 3c99a8005d2b17..26f3a2fc55d2b0 100644
--- a/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_str_fm_fa_fs_buffer_fo.cs
+++ b/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_str_fm_fa_fs_buffer_fo.cs
@@ -1,8 +1,6 @@
// 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.IO;
using Xunit;
namespace System.IO.Tests
@@ -47,6 +45,8 @@ public void ValidFileOptions(FileOptions option)
using (FileStream fs = CreateFileStream(GetTestFilePath(), FileMode.Create, FileAccess.ReadWrite, FileShare.Read, c_DefaultBufferSize, option))
{
+ Assert.Equal((option & FileOptions.Asynchronous) != 0, fs.IsAsync);
+
// make sure we can write, seek, and read data with this option set
fs.Write(data, 0, data.Length);
fs.Position = 0;
@@ -87,6 +87,5 @@ public void DeleteOnClose_FileDeletedAfterClose(FileOptions options)
}
Assert.False(File.Exists(path));
}
-
}
}
diff --git a/src/libraries/System.IO.FileSystem/tests/Net5CompatTests/System.IO.FileSystem.Net5Compat.Tests.csproj b/src/libraries/System.IO.FileSystem/tests/Net5CompatTests/System.IO.FileSystem.Net5Compat.Tests.csproj
index 1389ee3462649e..dfabeb02694b11 100644
--- a/src/libraries/System.IO.FileSystem/tests/Net5CompatTests/System.IO.FileSystem.Net5Compat.Tests.csproj
+++ b/src/libraries/System.IO.FileSystem/tests/Net5CompatTests/System.IO.FileSystem.Net5Compat.Tests.csproj
@@ -13,15 +13,21 @@
+
+
+
+
+
+
-
+
diff --git a/src/libraries/System.IO.FileSystem/tests/System.IO.FileSystem.Tests.csproj b/src/libraries/System.IO.FileSystem/tests/System.IO.FileSystem.Tests.csproj
index f22b4500be176b..651abf050e5cce 100644
--- a/src/libraries/System.IO.FileSystem/tests/System.IO.FileSystem.Tests.csproj
+++ b/src/libraries/System.IO.FileSystem/tests/System.IO.FileSystem.Tests.csproj
@@ -18,6 +18,7 @@
+
@@ -49,13 +50,22 @@
+
+
+
+
+
+
+
+
-
+
+
diff --git a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx
index 6e2123312c668f..516f19e4b40fe1 100644
--- a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx
+++ b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx
@@ -2644,6 +2644,12 @@
Cannot create '{0}' because a file or directory with the same name already exists.
+
+ Failed to create '{0}' with allocation size '{1}' because the disk was full.
+
+
+ Failed to create '{0}' with allocation size '{1}' because the file was too large.
+
BindHandle for ThreadPool failed on this handle.
diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems
index 6272a772fe21c2..825e376a45ac94 100644
--- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems
+++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems
@@ -1,4 +1,4 @@
-
+
true
c5ed3c1d-b572-46f1-8f96-522a85ce1179
@@ -406,6 +406,7 @@
+
@@ -1347,6 +1348,15 @@
Common\Interop\Windows\Kernel32\Interop.CreateFile.cs
+
+ Common\Interop\Windows\NtDll\Interop.NtCreateFile.cs
+
+
+ Common\Interop\Windows\NtDll\Interop.RtlNtStatusToDosError.cs
+
+
+ Common\Interop\Windows\Kernel32\Interop.DeleteFile.cs
+
Common\Interop\Windows\Kernel32\Interop.CriticalSection.cs
@@ -1515,6 +1525,15 @@
Common\Interop\Windows\Kernel32\Interop.SECURITY_ATTRIBUTES.cs
+
+ Common\Interop\Windows\Interop.UNICODE_STRING.cs
+
+
+ Common\Interop\Windows\Interop.SECURITY_QUALITY_OF_SERVICE.cs
+
+
+ Common\Interop\Windows\Interop.OBJECT_ATTRIBUTES.cs
+
Common\Interop\Windows\Kernel32\Interop.SecurityOptions.cs
@@ -1824,6 +1843,9 @@
Common\Interop\Unix\System.Native\Interop.PosixFAdvise.cs
+
+ Common\Interop\Unix\System.Native\Interop.PosixFAllocate.cs
+
Common\Interop\Unix\System.Native\Interop.Read.cs
diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/FileStream.cs b/src/libraries/System.Private.CoreLib/src/System/IO/FileStream.cs
index e5cbc2bb00e692..26e90f4467d0b9 100644
--- a/src/libraries/System.Private.CoreLib/src/System/IO/FileStream.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/IO/FileStream.cs
@@ -14,7 +14,7 @@ namespace System.IO
public class FileStream : Stream
{
internal const int DefaultBufferSize = 4096;
- private const FileShare DefaultShare = FileShare.Read;
+ internal const FileShare DefaultShare = FileShare.Read;
private const bool DefaultIsAsync = false;
/// Caches whether Serialization Guard has been disabled for file writes
@@ -135,6 +135,43 @@ public FileStream(string path, FileMode mode, FileAccess access, FileShare share
}
public FileStream(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize, FileOptions options)
+ : this(path, mode, access, share, bufferSize, options, 0)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the 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.
+ ///
+ /// A relative or absolute path for the file that the current instance will encapsulate.
+ /// An object that describes optional parameters to use.
+ /// is .
+ /// is an empty string (""), contains only white space, or contains one or more invalid characters.
+ /// -or-
+ /// refers to a non-file device, such as CON:, COM1:, LPT1:, etc. in an NTFS environment.
+ /// refers to a non-file device, such as CON:, COM1:, LPT1:, etc. in a non-NTFS environment.
+ /// is negative.
+ /// -or-
+ /// , , or contain an invalid value.
+ /// The file cannot be found, such as when is or , and the file specified by does not exist. The file must already exist in these modes.
+ /// An I/O error, such as specifying when the file specified by already exists, occurred.
+ /// -or-
+ /// The stream has been closed.
+ /// -or-
+ /// The disk was full (when was provided and was pointing to a regular file).
+ /// -or-
+ /// The file was too large (when was provided and was pointing to a regular file).
+ /// The caller does not have the required permission.
+ /// The specified path is invalid, such as being on an unmapped drive.
+ /// The requested is not permitted by the operating system for the specified , such as when is or and the file or directory is set for read-only access.
+ /// -or-
+ /// is specified for , but file encryption is not supported on the current platform.
+ /// The specified path, file name, or both exceed the system-defined maximum length.
+ public FileStream(string path, FileStreamOptions options)
+ : this(path, options.Mode, options.Access, options.Share, DefaultBufferSize, options.Options, options.PreallocationSize)
+ {
+ }
+
+ private FileStream(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize, FileOptions options, long preallocationSize)
{
if (path == null)
{
@@ -176,6 +213,10 @@ public FileStream(string path, FileMode mode, FileAccess access, FileShare share
{
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)
@@ -196,7 +237,7 @@ public FileStream(string path, FileMode mode, FileAccess access, FileShare share
SerializationInfo.ThrowIfDeserializationInProgress("AllowFileWrites", ref s_cachedSerializationSwitch);
}
- _strategy = FileStreamHelpers.ChooseStrategy(this, path, mode, access, share, bufferSize, options);
+ _strategy = FileStreamHelpers.ChooseStrategy(this, path, mode, access, share, bufferSize, options, preallocationSize);
}
[Obsolete("This property has been deprecated. Please use FileStream's SafeFileHandle property instead. https://go.microsoft.com/fwlink/?linkid=14202")]
diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/FileStreamOptions.cs b/src/libraries/System.Private.CoreLib/src/System/IO/FileStreamOptions.cs
new file mode 100644
index 00000000000000..c7dccfd2161092
--- /dev/null
+++ b/src/libraries/System.Private.CoreLib/src/System/IO/FileStreamOptions.cs
@@ -0,0 +1,31 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace System.IO
+{
+ public sealed class FileStreamOptions
+ {
+ ///
+ /// One of the enumeration values that determines how to open or create the file.
+ ///
+ public FileMode Mode { get; set; }
+ ///
+ /// A bitwise combination of the enumeration values that determines how the file can be accessed by the object. This also determines the values returned by the and properties of the object.
+ ///
+ public FileAccess Access { get; set; } = FileAccess.Read;
+ ///
+ /// A bitwise combination of the enumeration values that determines how the file will be shared by processes. The default value is .
+ ///
+ public FileShare Share { get; set; } = FileStream.DefaultShare;
+ ///
+ /// A bitwise combination of the enumeration values that specifies additional file options. The default value is , which indicates synchronous IO.
+ ///
+ public FileOptions Options { get; set; }
+ ///
+ /// 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.
+ /// When the value is negative, the constructor throws an .
+ /// In other cases (including the default 0 value), it's ignored.
+ ///
+ public long PreallocationSize { get; set; }
+ }
+}
diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/AsyncWindowsFileStreamStrategy.cs b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/AsyncWindowsFileStreamStrategy.cs
index 561bd2de7e97c0..6719dd9389566a 100644
--- a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/AsyncWindowsFileStreamStrategy.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/AsyncWindowsFileStreamStrategy.cs
@@ -17,8 +17,8 @@ internal AsyncWindowsFileStreamStrategy(SafeFileHandle handle, FileAccess access
{
}
- internal AsyncWindowsFileStreamStrategy(string path, FileMode mode, FileAccess access, FileShare share, FileOptions options)
- : base(path, mode, access, share, options)
+ internal AsyncWindowsFileStreamStrategy(string path, FileMode mode, FileAccess access, FileShare share, FileOptions options, long preallocationSize)
+ : base(path, mode, access, share, options, preallocationSize)
{
}
diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/FileStreamHelpers.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/FileStreamHelpers.Unix.cs
index 0d2c5df52be3aa..176429b41fe501 100644
--- a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/FileStreamHelpers.Unix.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/FileStreamHelpers.Unix.cs
@@ -17,10 +17,10 @@ internal static partial class FileStreamHelpers
private static FileStreamStrategy ChooseStrategyCore(SafeFileHandle handle, FileAccess access, FileShare share, int bufferSize, bool isAsync)
=> new Net5CompatFileStreamStrategy(handle, access, bufferSize, isAsync);
- private static FileStreamStrategy ChooseStrategyCore(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize, FileOptions options)
- => new Net5CompatFileStreamStrategy(path, mode, access, share, bufferSize, options);
+ 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, options, preallocationSize);
- internal static SafeFileHandle OpenHandle(string path, FileMode mode, FileAccess access, FileShare share, FileOptions options)
+ internal static SafeFileHandle OpenHandle(string path, 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);
@@ -34,7 +34,6 @@ internal static SafeFileHandle OpenHandle(string path, FileMode mode, FileAccess
Interop.Sys.Permissions.S_IRGRP | Interop.Sys.Permissions.S_IWGRP |
Interop.Sys.Permissions.S_IROTH | Interop.Sys.Permissions.S_IWOTH;
- // Open the file and store the safe handle.
return SafeFileHandle.Open(path!, openFlags, (int)OpenPermissions);
}
diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/FileStreamHelpers.Windows.cs b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/FileStreamHelpers.Windows.cs
index f33e47b0fe15c4..13dcc7cfb17e46 100644
--- a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/FileStreamHelpers.Windows.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/FileStreamHelpers.Windows.cs
@@ -5,6 +5,7 @@
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;
@@ -41,58 +42,112 @@ private static FileStreamStrategy ChooseStrategyCore(SafeFileHandle handle, File
return EnableBufferingIfNeeded(strategy, bufferSize);
}
- private static FileStreamStrategy ChooseStrategyCore(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize, FileOptions options)
+ private static FileStreamStrategy ChooseStrategyCore(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize, FileOptions options, long preallocationSize)
{
if (UseNet5CompatStrategy)
{
- return new Net5CompatFileStreamStrategy(path, mode, access, share, bufferSize, options);
+ return new Net5CompatFileStreamStrategy(path, mode, access, share, bufferSize, options, preallocationSize);
}
WindowsFileStreamStrategy strategy = (options & FileOptions.Asynchronous) != 0
- ? new AsyncWindowsFileStreamStrategy(path, mode, access, share, options)
- : new SyncWindowsFileStreamStrategy(path, mode, access, share, options);
+ ? new AsyncWindowsFileStreamStrategy(path, mode, access, share, options, preallocationSize)
+ : new SyncWindowsFileStreamStrategy(path, mode, access, share, options, preallocationSize);
return EnableBufferingIfNeeded(strategy, bufferSize);
}
- // TODO: we might want to consider strategy.IsPipe here and never enable buffering for async pipes
internal static FileStreamStrategy EnableBufferingIfNeeded(WindowsFileStreamStrategy strategy, int bufferSize)
=> bufferSize == 1 ? strategy : new BufferedFileStreamStrategy(strategy, bufferSize);
- internal static SafeFileHandle OpenHandle(string path, FileMode mode, FileAccess access, FileShare share, FileOptions options)
- => CreateFileOpenHandle(path, mode, access, share, options);
+ 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)
+ private static unsafe SafeFileHandle CreateFileOpenHandle(string path, FileMode mode, FileAccess access, FileShare share, FileOptions options, long preallocationSize)
{
- Interop.Kernel32.SECURITY_ATTRIBUTES secAttrs = GetSecAttrs(share);
+ using (DisableMediaInsertionPrompt.Create())
+ {
+ Debug.Assert(path != null);
- int fAccess =
- ((access & FileAccess.Read) == FileAccess.Read ? Interop.Kernel32.GenericOperations.GENERIC_READ : 0) |
- ((access & FileAccess.Write) == FileAccess.Write ? Interop.Kernel32.GenericOperations.GENERIC_WRITE : 0);
+ if (ShouldPreallocate(preallocationSize, access, mode))
+ {
+ IntPtr fileHandle = NtCreateFile(path, mode, access, share, options, preallocationSize);
- // 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;
+ return ValidateFileHandle(new SafeFileHandle(fileHandle, ownsHandle: true), path, (options & FileOptions.Asynchronous) != 0);
+ }
- // Must use a valid Win32 constant here...
- if (mode == FileMode.Append)
- mode = FileMode.OpenOrCreate;
+ Interop.Kernel32.SECURITY_ATTRIBUTES secAttrs = GetSecAttrs(share);
- int flagsAndAttributes = (int)options;
+ int fAccess =
+ ((access & FileAccess.Read) == FileAccess.Read ? Interop.Kernel32.GenericOperations.GENERIC_READ : 0) |
+ ((access & FileAccess.Write) == FileAccess.Write ? Interop.Kernel32.GenericOperations.GENERIC_WRITE : 0);
- // 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);
+ // 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;
- using (DisableMediaInsertionPrompt.Create())
- {
- Debug.Assert(path != null);
- return ValidateFileHandle(
+ // 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);
}
}
@@ -136,7 +191,7 @@ internal static bool GetDefaultIsAsync(SafeFileHandle handle, bool defaultIsAsyn
}
// If either of these two flags are set, the file handle is synchronous (not overlapped)
- return (fileMode & (Interop.NtDll.FILE_SYNCHRONOUS_IO_ALERT | Interop.NtDll.FILE_SYNCHRONOUS_IO_NONALERT)) > 0;
+ return (fileMode & (uint)(Interop.NtDll.CreateOptions.FILE_SYNCHRONOUS_IO_ALERT | Interop.NtDll.CreateOptions.FILE_SYNCHRONOUS_IO_NONALERT)) > 0;
}
internal static void VerifyHandleIsSync(SafeFileHandle handle)
diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/FileStreamHelpers.cs b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/FileStreamHelpers.cs
index 1ca2e563998caa..7dfaf7fe52dc73 100644
--- a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/FileStreamHelpers.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/FileStreamHelpers.cs
@@ -12,8 +12,8 @@ internal static partial class FileStreamHelpers
internal static FileStreamStrategy ChooseStrategy(FileStream fileStream, SafeFileHandle handle, FileAccess access, FileShare share, int bufferSize, bool isAsync)
=> WrapIfDerivedType(fileStream, ChooseStrategyCore(handle, access, share, bufferSize, isAsync));
- internal static FileStreamStrategy ChooseStrategy(FileStream fileStream, string path, FileMode mode, FileAccess access, FileShare share, int bufferSize, FileOptions options)
- => WrapIfDerivedType(fileStream, ChooseStrategyCore(path, mode, access, share, bufferSize, options));
+ internal static FileStreamStrategy ChooseStrategy(FileStream fileStream, string path, FileMode mode, FileAccess access, FileShare share, int bufferSize, FileOptions options, long preallocationSize)
+ => WrapIfDerivedType(fileStream, ChooseStrategyCore(path, mode, access, share, bufferSize, options, preallocationSize));
private static FileStreamStrategy WrapIfDerivedType(FileStream fileStream, FileStreamStrategy strategy)
=> fileStream.GetType() == typeof(FileStream)
@@ -35,5 +35,11 @@ e is IOException ||
e is UnauthorizedAccessException ||
e is NotSupportedException ||
(e is ArgumentException && !(e is ArgumentNullException));
+
+ internal static bool ShouldPreallocate(long preallocationSize, FileAccess access, FileMode mode)
+ => preallocationSize > 0
+ && (access & FileAccess.Write) != 0
+ && mode != FileMode.Open && mode != FileMode.Append
+ && !OperatingSystem.IsBrowser(); // WASM limitation
}
}
diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/Net5CompatFileStreamStrategy.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/Net5CompatFileStreamStrategy.Unix.cs
index eb1cebc37a7f89..f039c020433812 100644
--- a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/Net5CompatFileStreamStrategy.Unix.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/Net5CompatFileStreamStrategy.Unix.cs
@@ -39,7 +39,8 @@ internal sealed partial class Net5CompatFileStreamStrategy : FileStreamStrategy
/// What other access to the file should be allowed. This is currently ignored.
/// The original path specified for the FileStream.
/// Options, passed via arguments as we have no guarantee that _options field was already set.
- private void Init(FileMode mode, FileShare share, string originalPath, FileOptions options)
+ /// passed to posix_fallocate
+ private void Init(FileMode mode, FileShare share, string originalPath, FileOptions options, long preallocationSize)
{
// 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.)
@@ -103,6 +104,25 @@ private void Init(FileMode mode, FileShare share, string originalPath, FileOptio
}
}
}
+
+ // 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));
+ }
+ }
}
/// Initializes a stream from an already open file handle (file descriptor).
diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/Net5CompatFileStreamStrategy.Windows.cs b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/Net5CompatFileStreamStrategy.Windows.cs
index fead01218fb0c3..8b51b2597fed0e 100644
--- a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/Net5CompatFileStreamStrategy.Windows.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/Net5CompatFileStreamStrategy.Windows.cs
@@ -46,7 +46,7 @@ internal sealed partial class Net5CompatFileStreamStrategy : FileStreamStrategy
private PreAllocatedOverlapped? _preallocatedOverlapped; // optimization for async ops to avoid per-op allocations
private CompletionSource? _currentOverlappedOwner; // async op currently using the preallocated overlapped
- private void Init(FileMode mode, FileShare share, string originalPath, FileOptions options)
+ private void Init(FileMode mode, FileShare share, string originalPath, FileOptions options, long preallocationSize)
{
FileStreamHelpers.ValidateFileTypeForNonExtendedPaths(_fileHandle, originalPath);
diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/Net5CompatFileStreamStrategy.cs b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/Net5CompatFileStreamStrategy.cs
index 03ca3533e717b7..d257df97722da8 100644
--- a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/Net5CompatFileStreamStrategy.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/Net5CompatFileStreamStrategy.cs
@@ -78,7 +78,7 @@ internal Net5CompatFileStreamStrategy(SafeFileHandle handle, FileAccess access,
_fileHandle = handle;
}
- internal Net5CompatFileStreamStrategy(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize, FileOptions options)
+ internal Net5CompatFileStreamStrategy(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize, FileOptions options, long preallocationSize)
{
string fullPath = Path.GetFullPath(path);
@@ -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);
+ _fileHandle = FileStreamHelpers.OpenHandle(fullPath, mode, access, share, options, preallocationSize);
try
{
- Init(mode, share, path, options);
+ Init(mode, share, path, options, preallocationSize);
}
catch
{
diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/SyncWindowsFileStreamStrategy.cs b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/SyncWindowsFileStreamStrategy.cs
index ad96cdb4967b44..30499b660ddf00 100644
--- a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/SyncWindowsFileStreamStrategy.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/SyncWindowsFileStreamStrategy.cs
@@ -15,8 +15,8 @@ internal SyncWindowsFileStreamStrategy(SafeFileHandle handle, FileAccess access,
{
}
- internal SyncWindowsFileStreamStrategy(string path, FileMode mode, FileAccess access, FileShare share, FileOptions options)
- : base(path, mode, access, share, options)
+ internal SyncWindowsFileStreamStrategy(string path, FileMode mode, FileAccess access, FileShare share, FileOptions options, long preallocationSize)
+ : base(path, mode, access, share, options, preallocationSize)
{
}
diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/WindowsFileStreamStrategy.cs b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/WindowsFileStreamStrategy.cs
index 94686aa9c05326..a5ebdd6f5da599 100644
--- a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/WindowsFileStreamStrategy.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/WindowsFileStreamStrategy.cs
@@ -37,7 +37,7 @@ internal WindowsFileStreamStrategy(SafeFileHandle handle, FileAccess access, Fil
_fileHandle = handle;
}
- internal WindowsFileStreamStrategy(string path, FileMode mode, FileAccess access, FileShare share, FileOptions options)
+ internal WindowsFileStreamStrategy(string path, FileMode mode, FileAccess access, FileShare share, FileOptions options, long preallocationSize)
{
string fullPath = Path.GetFullPath(path);
@@ -45,7 +45,7 @@ internal WindowsFileStreamStrategy(string path, FileMode mode, FileAccess access
_access = access;
_share = share;
- _fileHandle = FileStreamHelpers.OpenHandle(fullPath, mode, access, share, options);
+ _fileHandle = FileStreamHelpers.OpenHandle(fullPath, mode, access, share, options, preallocationSize);
try
{
diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs
index 348d67c49d6081..6fcef0ceb4c81e 100644
--- a/src/libraries/System.Runtime/ref/System.Runtime.cs
+++ b/src/libraries/System.Runtime/ref/System.Runtime.cs
@@ -7299,6 +7299,15 @@ public enum FileShare
Delete = 4,
Inheritable = 16,
}
+ public sealed class FileStreamOptions
+ {
+ public FileStreamOptions() { }
+ public System.IO.FileMode Mode { get; set; }
+ public System.IO.FileAccess Access { get; set; }
+ public System.IO.FileShare Share { get; set; }
+ public System.IO.FileOptions Options { get; set; }
+ public long PreallocationSize { get; set; }
+ }
public partial class FileStream : System.IO.Stream
{
public FileStream(Microsoft.Win32.SafeHandles.SafeFileHandle handle, System.IO.FileAccess access) { }
@@ -7322,6 +7331,7 @@ public FileStream(string path, System.IO.FileMode mode, System.IO.FileAccess acc
public FileStream(string path, System.IO.FileMode mode, System.IO.FileAccess access, System.IO.FileShare share, int bufferSize) { }
public FileStream(string path, System.IO.FileMode mode, System.IO.FileAccess access, System.IO.FileShare share, int bufferSize, bool useAsync) { }
public FileStream(string path, System.IO.FileMode mode, System.IO.FileAccess access, System.IO.FileShare share, int bufferSize, System.IO.FileOptions options) { }
+ public FileStream(string path, System.IO.FileStreamOptions options) { }
public override bool CanRead { get { throw null; } }
public override bool CanSeek { get { throw null; } }
public override bool CanWrite { get { throw null; } }
diff --git a/src/libraries/System.Security.Principal.Windows/src/System.Security.Principal.Windows.csproj b/src/libraries/System.Security.Principal.Windows/src/System.Security.Principal.Windows.csproj
index 56fcfb62c407ee..4c9b0a3aaf9a0a 100644
--- a/src/libraries/System.Security.Principal.Windows/src/System.Security.Principal.Windows.csproj
+++ b/src/libraries/System.Security.Principal.Windows/src/System.Security.Principal.Windows.csproj
@@ -31,6 +31,10 @@
+
+