Allow to specify file preallocation size (#51111)
authorAdam Sitnik <adam.sitnik@gmail.com>
Tue, 18 May 2021 07:52:03 +0000 (09:52 +0200)
committerGitHub <noreply@github.com>
Tue, 18 May 2021 07:52:03 +0000 (09:52 +0200)
Co-authored-by: Carlos Sanchez <1175054+carlossanlop@users.noreply.github.com>
Co-authored-by: David CantĂș <dacantu@microsoft.com>
48 files changed:
src/libraries/Common/src/Interop/Unix/System.Native/Interop.PosixFAllocate.cs [new file with mode: 0644]
src/libraries/Common/src/Interop/Windows/Interop.Errors.cs
src/libraries/Common/src/Interop/Windows/Interop.OBJECT_ATTRIBUTES.cs
src/libraries/Common/src/Interop/Windows/Interop.SECURITY_QUALITY_OF_SERVICE.cs [new file with mode: 0644]
src/libraries/Common/src/Interop/Windows/NtDll/Interop.NtCreateFile.cs
src/libraries/Common/src/Interop/Windows/NtDll/Interop.NtQueryInformationFile.cs
src/libraries/Microsoft.IO.Redist/src/Microsoft.IO.Redist.csproj
src/libraries/Native/Unix/Common/pal_config.h.in
src/libraries/Native/Unix/System.Native/entrypoints.c
src/libraries/Native/Unix/System.Native/pal_io.c
src/libraries/Native/Unix/System.Native/pal_io.h
src/libraries/Native/Unix/configure.cmake
src/libraries/System.IO.FileSystem/src/System.IO.FileSystem.csproj
src/libraries/System.IO.FileSystem/src/System/IO/Enumeration/FileSystemEnumerator.Win32.cs
src/libraries/System.IO.FileSystem/tests/File/Open.cs
src/libraries/System.IO.FileSystem/tests/FileStream/Dispose.cs
src/libraries/System.IO.FileSystem/tests/FileStream/Flush.cs
src/libraries/System.IO.FileSystem/tests/FileStream/Name.cs
src/libraries/System.IO.FileSystem/tests/FileStream/ToString.cs
src/libraries/System.IO.FileSystem/tests/FileStream/ctor_options_as.Browser.cs [new file with mode: 0644]
src/libraries/System.IO.FileSystem/tests/FileStream/ctor_options_as.Unix.cs [new file with mode: 0644]
src/libraries/System.IO.FileSystem/tests/FileStream/ctor_options_as.Windows.cs [new file with mode: 0644]
src/libraries/System.IO.FileSystem/tests/FileStream/ctor_options_as.cs [new file with mode: 0644]
src/libraries/System.IO.FileSystem/tests/FileStream/ctor_sfh_fa_buffer.cs
src/libraries/System.IO.FileSystem/tests/FileStream/ctor_str_fm.cs
src/libraries/System.IO.FileSystem/tests/FileStream/ctor_str_fm_fa.cs
src/libraries/System.IO.FileSystem/tests/FileStream/ctor_str_fm_fa_fs.read.cs
src/libraries/System.IO.FileSystem/tests/FileStream/ctor_str_fm_fa_fs.write.cs
src/libraries/System.IO.FileSystem/tests/FileStream/ctor_str_fm_fa_fs_buffer.cs
src/libraries/System.IO.FileSystem/tests/FileStream/ctor_str_fm_fa_fs_buffer_async.cs
src/libraries/System.IO.FileSystem/tests/FileStream/ctor_str_fm_fa_fs_buffer_fo.cs
src/libraries/System.IO.FileSystem/tests/Net5CompatTests/System.IO.FileSystem.Net5Compat.Tests.csproj
src/libraries/System.IO.FileSystem/tests/System.IO.FileSystem.Tests.csproj
src/libraries/System.Private.CoreLib/src/Resources/Strings.resx
src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems
src/libraries/System.Private.CoreLib/src/System/IO/FileStream.cs
src/libraries/System.Private.CoreLib/src/System/IO/FileStreamOptions.cs [new file with mode: 0644]
src/libraries/System.Private.CoreLib/src/System/IO/Strategies/AsyncWindowsFileStreamStrategy.cs
src/libraries/System.Private.CoreLib/src/System/IO/Strategies/FileStreamHelpers.Unix.cs
src/libraries/System.Private.CoreLib/src/System/IO/Strategies/FileStreamHelpers.Windows.cs
src/libraries/System.Private.CoreLib/src/System/IO/Strategies/FileStreamHelpers.cs
src/libraries/System.Private.CoreLib/src/System/IO/Strategies/Net5CompatFileStreamStrategy.Unix.cs
src/libraries/System.Private.CoreLib/src/System/IO/Strategies/Net5CompatFileStreamStrategy.Windows.cs
src/libraries/System.Private.CoreLib/src/System/IO/Strategies/Net5CompatFileStreamStrategy.cs
src/libraries/System.Private.CoreLib/src/System/IO/Strategies/SyncWindowsFileStreamStrategy.cs
src/libraries/System.Private.CoreLib/src/System/IO/Strategies/WindowsFileStreamStrategy.cs
src/libraries/System.Runtime/ref/System.Runtime.cs
src/libraries/System.Security.Principal.Windows/src/System.Security.Principal.Windows.csproj

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 (file)
index 0000000..2866ed3
--- /dev/null
@@ -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
+    {
+        /// <summary>
+        /// Returns -1 on ENOSPC, -2 on EFBIG. On success or ignorable error, 0 is returned.
+        /// </summary>
+        [DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_PosixFAllocate", SetLastError = false)]
+        internal static extern int PosixFAllocate(SafeFileHandle fd, long offset, long length);
+    }
+}
index 2186d54dfb3282b0e17558f25f12538227cede11..338706ea8491bca38afce1f84e219321b99ab4d9 100644 (file)
@@ -29,6 +29,7 @@ internal static partial class Interop
         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 Interop
         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;
index f89fb97c9fdc6aded3c81935d73b0503846bcf36..f0399f82153081c3ea20d8a9b9fef66d79183453 100644 (file)
@@ -38,19 +38,19 @@ internal static partial class Interop
         /// Optional quality of service to be applied to the object. Used to indicate
         /// security impersonation level and context tracking mode (dynamic or static).
         /// </summary>
-        public void* SecurityQualityOfService;
+        public SECURITY_QUALITY_OF_SERVICE* SecurityQualityOfService;
 
         /// <summary>
         /// Equivalent of InitializeObjectAttributes macro with the exception that you can directly set SQOS.
         /// </summary>
-        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 (file)
index 0000000..3b63485
--- /dev/null
@@ -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
+{
+    /// <summary>
+    /// <a href="https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-security_quality_of_service">SECURITY_QUALITY_OF_SERVICE</a> structure.
+    ///  Used to support client impersonation. Client specifies this to a server to allow
+    ///  it to impersonate the client.
+    /// </summary>
+    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;
+        }
+    }
+
+    /// <summary>
+    /// <a href="https://msdn.microsoft.com/en-us/library/windows/desktop/aa379572.aspx">SECURITY_IMPERSONATION_LEVEL</a> enumeration values.
+    ///  [SECURITY_IMPERSONATION_LEVEL]
+    /// </summary>
+    public enum ImpersonationLevel : uint
+    {
+        /// <summary>
+        ///  The server process cannot obtain identification information about the client and cannot impersonate the client.
+        ///  [SecurityAnonymous]
+        /// </summary>
+        Anonymous,
+
+        /// <summary>
+        ///  The server process can obtain identification information about the client, but cannot impersonate the client.
+        ///  [SecurityIdentification]
+        /// </summary>
+        Identification,
+
+        /// <summary>
+        ///  The server process can impersonate the client's security context on it's local system.
+        ///  [SecurityImpersonation]
+        /// </summary>
+        Impersonation,
+
+        /// <summary>
+        ///  The server process can impersonate the client's security context on remote systems.
+        ///  [SecurityDelegation]
+        /// </summary>
+        Delegation
+    }
+
+    /// <summary>
+    /// <a href="https://msdn.microsoft.com/en-us/library/cc234317.aspx">SECURITY_CONTEXT_TRACKING_MODE</a>
+    /// </summary>
+    public enum ContextTrackingMode : byte
+    {
+        /// <summary>
+        ///  The server is given a snapshot of the client's security context.
+        ///  [SECURITY_STATIC_TRACKING]
+        /// </summary>
+        Static = 0x00,
+
+        /// <summary>
+        ///  The server is continually updated with changes.
+        ///  [SECURITY_DYNAMIC_TRACKING]
+        /// </summary>
+        Dynamic = 0x01
+    }
+}
index e2f518e409dca0815bf6e9e87a725a556b678581..9ce82c7f4ad453c23338439fd077153eda9f4154 100644 (file)
@@ -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<char> 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 partial class Interop
                 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 partial class Interop
             }
         }
 
+        internal static unsafe (uint status, IntPtr handle) CreateFile(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
+            // 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;
+
         /// <summary>
         /// File creation disposition when calling directly to NT APIs.
         /// </summary>
index ccec236d394e21b6354d5fe792a63b7c0721af5b..a9c79cd1d686920a4eeb5cc05ad44575ca4d1230 100644 (file)
@@ -25,8 +25,6 @@ internal static partial class Interop
         }
 
         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);
     }
index d116870a954500016f554d41208792e523b5c759..94b9552748a987b742fbba6639098c6c33d52119 100644 (file)
              Link="Common\Interop\Windows\Interop.OBJECT_ATTRIBUTES.cs" />
     <Compile Include="$(CommonPath)Interop\Windows\Interop.UNICODE_STRING.cs"
              Link="Common\Interop\Windows\Interop.UNICODE_STRING.cs" />
+    <Compile Include="$(CommonPath)Interop\Windows\Interop.SECURITY_QUALITY_OF_SERVICE.cs"
+             Link="Common\Interop\Windows\Interop.SECURITY_QUALITY_OF_SERVICE.cs" />
     <Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.CopyFile.cs"
              Link="Common\Interop\Windows\Interop.CopyFile.cs" />
     <Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.CopyFileEx.cs"
index 67158f915a7a48f79c7bd1573c462e473cc88d6d..b7bd484e629ffac4bca6cf3c3b183d7c266448cf 100644 (file)
@@ -34,6 +34,8 @@
 #cmakedefine01 HAVE_STRLCAT
 #cmakedefine01 HAVE_SHM_OPEN_THAT_WORKS_WELL_ENOUGH_WITH_MMAP
 #cmakedefine01 HAVE_POSIX_ADVISE
+#cmakedefine01 HAVE_POSIX_FALLOCATE
+#cmakedefine01 HAVE_POSIX_FALLOCATE64
 #cmakedefine01 PRIORITY_REQUIRES_INT_WHO
 #cmakedefine01 KEVENT_REQUIRES_INT_PARAMS
 #cmakedefine01 HAVE_IOCTL
index a8be7305be7a9368e05c5a7d8a7d90f0d3f487e9..0f7b1a12fa5076aa54da502d8f55dfca3f9106c5 100644 (file)
@@ -90,6 +90,7 @@ static const Entry s_sysNative[] =
     DllImportEntry(SystemNative_FTruncate)
     DllImportEntry(SystemNative_Poll)
     DllImportEntry(SystemNative_PosixFAdvise)
+    DllImportEntry(SystemNative_PosixFAllocate)
     DllImportEntry(SystemNative_Read)
     DllImportEntry(SystemNative_ReadLink)
     DllImportEntry(SystemNative_Rename)
index 96b69525471987b594f74e1fb46cc54f0688e32a..c63eb8898cace95bce36a39b9f59ef70cc0d02b5 100644 (file)
@@ -662,7 +662,7 @@ int32_t SystemNative_FSync(intptr_t fd)
     int fileDescriptor = ToFileDescriptor(fd);
 
     int32_t result;
-    while ((result = 
+    while ((result =
 #if defined(TARGET_OSX) && HAVE_F_FULLFSYNC
     fcntl(fileDescriptor, F_FULLFSYNC)
 #else
@@ -991,6 +991,85 @@ int32_t SystemNative_PosixFAdvise(intptr_t fd, int64_t offset, int64_t length, i
 #endif
 }
 
+int32_t SystemNative_PosixFAllocate(intptr_t fd, int64_t offset, int64_t length)
+{
+    assert_msg(offset == 0, "Invalid offset value", (int)offset);
+
+    int fileDescriptor = ToFileDescriptor(fd);
+    int32_t result;
+#if HAVE_POSIX_FALLOCATE64 // 64-bit Linux
+    while ((result = posix_fallocate64(fileDescriptor, (off64_t)offset, (off64_t)length)) == EINTR);
+#elif HAVE_POSIX_FALLOCATE // 32-bit Linux
+    while ((result = posix_fallocate(fileDescriptor, (off_t)offset, (off_t)length)) == EINTR);
+#elif defined(F_PREALLOCATE) // macOS
+    fstore_t fstore;
+    fstore.fst_flags = F_ALLOCATECONTIG; // ensure contiguous space
+    fstore.fst_posmode = F_PEOFPOSMODE;  // allocate from the physical end of file, as offset MUST NOT be 0 for F_VOLPOSMODE
+    fstore.fst_offset = (off_t)offset;
+    fstore.fst_length = (off_t)length;
+    fstore.fst_bytesalloc = 0; // output size, can be > 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)
index e82cffe8a8c3a93c37eea7ef49e92eac536600a2..e3d402d9be5f661fe60eab78d568484316dde824 100644 (file)
@@ -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.
  *
index c430c64f7fe5645f9e5827e17606441646d0bc2b..1b85454db4af226229aa45545c48dfa7824dff79 100644 (file)
@@ -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
index a4995ef913cbb6bd8a67be027d17818ff0d91126..0938c5336422729efc22f7cc7a08687a8437588a 100644 (file)
@@ -1,4 +1,4 @@
-<Project Sdk="Microsoft.NET.Sdk">
+ï»ż<Project Sdk="Microsoft.NET.Sdk">
   <PropertyGroup>
     <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
     <IsPartialFacadeAssembly>true</IsPartialFacadeAssembly>
              Link="Common\Interop\Windows\Interop.Libraries.cs" />
     <Compile Include="$(CommonPath)Interop\Windows\Interop.LongFileTime.cs"
              Link="Common\Interop\Windows\Interop.LongFileTime.cs" />
+    <Compile Include="$(CommonPath)Interop\Windows\Interop.SECURITY_QUALITY_OF_SERVICE.cs"
+             Link="Common\Interop\Windows\Interop.SECURITY_QUALITY_OF_SERVICE.cs" />
     <Compile Include="$(CommonPath)Interop\Windows\Interop.OBJECT_ATTRIBUTES.cs"
              Link="Common\Interop\Windows\Interop.OBJECT_ATTRIBUTES.cs" />
     <Compile Include="$(CommonPath)Interop\Windows\Interop.UNICODE_STRING.cs"
index 44c8cec1317d269ac93c9760bb6540448f686e74..22e990194f5d885b39ae3a10bdfbb82a8e1aa397 100644 (file)
@@ -60,7 +60,7 @@ namespace System.IO.Enumeration
 
         private unsafe IntPtr CreateRelativeDirectoryHandle(ReadOnlySpan<char> 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 @@ namespace System.IO.Enumeration
                 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 @@ namespace System.IO.Enumeration
                     // 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))
                     {
index f0a7897725d10f3b058097797d92f42336c4fbfa..3a042d8a65b9efaf31602fe8189754dfb6e448b0 100644 (file)
@@ -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 @@ namespace System.IO.Tests
         {
             return File.Open(path, mode);
         }
-
     }
 
     public class File_Open_str_fm_fa : FileStream_ctor_str_fm_fa
@@ -86,5 +83,4 @@ namespace System.IO.Tests
                 return reader.ReadToEnd();
         }
     }
-
 }
index 283cb3e8eddc97b3f20464d3011a8fff0e7517b8..2a94247dc0414412479649452a1ffedace4dae52 100644 (file)
@@ -48,7 +48,6 @@ namespace System.IO.Tests
             }
         }
 
-
         [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
         public void Dispose_CallsVirtualDisposeTrueArg_ThrowsDuringFlushWriteBuffer_DisposeThrows()
         {
index 25dd6422410aa50407f2c515a4562a01613f59b5..be175c6efa3015f048e1d58255d42d8ad34fd025 100644 (file)
@@ -183,6 +183,5 @@ namespace System.IO.Tests
                 base.Flush(flushToDisk);
             }
         }
-
     }
 }
index 1b088a15062a2bad2547de714b0c2f0222ad4a8a..cfe0c62d6c30f10da82ca56fbe49c5e91c88bef6 100644 (file)
@@ -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 @@ namespace System.IO.Tests
             }
         }
 
-
         [Fact]
         public void NameReturnsUnknownForHandle()
         {
index 647cccf60a575a06bf5353714fc48b6b1d6911b4..8682f5a75b998631e31c923ebcadff7e1c6c4d52 100644 (file)
@@ -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 (file)
index 0000000..03486ae
--- /dev/null
@@ -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 (file)
index 0000000..12e8f16
--- /dev/null
@@ -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 (file)
index 0000000..bde8cd5
--- /dev/null
@@ -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<IOException>(() => 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 (file)
index 0000000..d98090b
--- /dev/null
@@ -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<ArgumentOutOfRangeException>(
+                () => 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<IOException>(() => 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;
+        }
+    }
+}
index aa263624d189092d9bc62e19bf7aae26539b7cf3..745ff57850888ad278ffbfe05dae662e8308aa41 100644 (file)
@@ -18,7 +18,6 @@ namespace System.IO.Tests
             return new FileStream(handle, access, bufferSize);
         }
 
-
         [Theory,
             InlineData(0),
             InlineData(-1)]
index c1b97f00cc4340eb69b39ac59ed51ed06fc9dd0a..f442fbd432219090324ecaae6069ffe1a2853958 100644 (file)
@@ -12,6 +12,8 @@ namespace System.IO.Tests
             return new FileStream(path, mode);
         }
 
+        protected virtual long InitialLength => 0;
+
         [Fact]
         public void NullPathThrows()
         {
@@ -50,7 +52,6 @@ namespace System.IO.Tests
             Assert.Throws<DirectoryNotFoundException>(() => CreateFileStream(path, FileMode.Open));
         }
 
-
         public static TheoryData<string> StreamSpecifiers
         {
             get
@@ -91,7 +92,7 @@ namespace System.IO.Tests
             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 @@ namespace System.IO.Tests
             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 @@ namespace System.IO.Tests
             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 @@ namespace System.IO.Tests
             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 @@ namespace System.IO.Tests
             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);
index 41d7263c6f81e67ca7755f31064d49ab5ef53d7a..6ab972644c3000460fee2db0d981c41d16340763 100644 (file)
@@ -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
index c1d989f12e24368ca40225563ddfa1e4913fc41c..ba8c402925f2cf38754f3df9bb83de73dba0afe1 100644 (file)
@@ -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
index e0d619187c3999829aa69a7c5060364fba1339dc..ae93555bbe8316b7bb716b3921402ae2c0bac360 100644 (file)
@@ -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
index a24a4fd16d8db67f7dcbe1be9edaacabc97c9283..3885149161cadd6d24e842c95902411882bfad21 100644 (file)
@@ -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
index c3d8f0077dc4429cbf6fab262ae43485a5c0bf25..a93eb4acb081592d2ad73c71addd5910fcd9ebcf 100644 (file)
@@ -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 @@ namespace System.IO.Tests
             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);
+            }
         }
-
     }
 }
index 3c99a8005d2b1765c5cdd2287c2bd5d44d9126b1..26f3a2fc55d2b0192df6348d4ea78e6e4435bd9a 100644 (file)
@@ -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 @@ namespace System.IO.Tests
 
             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 @@ namespace System.IO.Tests
             }
             Assert.False(File.Exists(path));
         }
-
     }
 }
index 1389ee3462649eaf308438cd14575bdb196fd053..dfabeb02694b11d8f455fd89e793111727d73668 100644 (file)
   <ItemGroup Condition="'$(TargetsUnix)' == 'true'">
     <Compile Remove="..\**\*.Windows.cs" />
     <Compile Remove="..\**\*.Browser.cs" />
+    <Compile Include="$(CommonPath)Interop\Unix\Interop.Libraries.cs" Link="Interop\Unix\Interop.Libraries.cs" />
+    <Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.Stat.cs" Link="Interop\Unix\System.Native\Interop.Stat.cs" />
   </ItemGroup>
   <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" />
   </ItemGroup>
   <ItemGroup Condition="'$(TargetsBrowser)' == 'true'">
     <Compile Remove="..\**\*.Unix.cs" />
     <Compile Remove="..\**\*.Windows.cs" />
-  </ItemGroup>  
+  </ItemGroup>
   <ItemGroup>
     <!-- Helpers -->
     <Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.GenericOperations.cs" Link="Interop\Windows\Interop.GenericOperations.cs" />
index f22b4500be176ba2ea25babf3ca67157ad14b233..651abf050e5cce1769d1de2a1185cd440db9c898 100644 (file)
@@ -18,6 +18,7 @@
     <Compile Include="FileInfo\GetSetAttributesCommon.cs" />
     <Compile Include="FileInfo\IsReadOnly.cs" />
     <Compile Include="FileInfo\Replace.cs" />
+    <Compile Include="FileStream\ctor_options_as.cs" />
     <Compile Include="FileStream\Handle.cs" />
     <Compile Include="Directory\GetLogicalDrives.cs" />
     <Compile Include="FileStream\LockUnlock.cs" />
   </ItemGroup>
   <ItemGroup Condition="'$(TargetsUnix)' == 'true'">
     <Compile Include="FileSystemTest.Unix.cs" />
+    <Compile Include="FileStream\ctor_options_as.Unix.cs" />
+    <Compile Include="$(CommonPath)Interop\Unix\Interop.Libraries.cs" Link="Interop\Unix\Interop.Libraries.cs" />
+    <Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.Stat.cs" Link="Interop\Unix\System.Native\Interop.Stat.cs" />
   </ItemGroup>
   <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" />
   </ItemGroup>
   <ItemGroup Condition="'$(TargetsBrowser)' == 'true'">
     <Compile Include="FileSystemTest.Browser.cs" />
-  </ItemGroup>  
+    <Compile Include="FileStream\ctor_options_as.Browser.cs" />
+  </ItemGroup>
   <ItemGroup>
     <!-- Rewritten -->
     <Compile Include="DirectoryInfo\GetSetAttributes.cs" />
index 6e2123312c668f01da8da24ddb34c108c9139421..516f19e4b40fe1f8563481c05f6862bd7a0ec1dc 100644 (file)
   <data name="IO_AlreadyExists_Name" xml:space="preserve">
     <value>Cannot create '{0}' because a file or directory with the same name already exists.</value>
   </data>
+  <data name="IO_DiskFull_Path_AllocationSize" xml:space="preserve">
+    <value>Failed to create '{0}' with allocation size '{1}' because the disk was full.</value>
+  </data>
+  <data name="IO_FileTooLarge_Path_AllocationSize" xml:space="preserve">
+    <value>Failed to create '{0}' with allocation size '{1}' because the file was too large.</value>
+  </data>
   <data name="IO_BindHandleFailed" xml:space="preserve">
     <value>BindHandle for ThreadPool failed on this handle.</value>
   </data>
index 92df381751bf23b01bd3793d9ff711fbd186922e..291862956e57b4eed5a5ea587dc8cad4f84506f2 100644 (file)
@@ -1,4 +1,4 @@
-ï»ż<Project>
+<Project>
   <PropertyGroup>
     <HasSharedItems>true</HasSharedItems>
     <SharedGUID>c5ed3c1d-b572-46f1-8f96-522a85ce1179</SharedGUID>
     <Compile Include="$(MSBuildThisFileDirectory)System\IO\FileOptions.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)System\IO\FileShare.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)System\IO\FileStream.cs" />
+    <Compile Include="$(MSBuildThisFileDirectory)System\IO\FileStreamOptions.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)System\IO\HandleInheritability.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)System\IO\InvalidDataException.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)System\IO\IOException.cs" />
     <Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.CreateFile.cs">
       <Link>Common\Interop\Windows\Kernel32\Interop.CreateFile.cs</Link>
     </Compile>
+    <Compile Include="$(CommonPath)Interop\Windows\NtDll\Interop.NtCreateFile.cs">
+      <Link>Common\Interop\Windows\NtDll\Interop.NtCreateFile.cs</Link>
+    </Compile>
+    <Compile Include="$(CommonPath)Interop\Windows\NtDll\Interop.RtlNtStatusToDosError.cs">
+      <Link>Common\Interop\Windows\NtDll\Interop.RtlNtStatusToDosError.cs</Link>
+    </Compile>
+    <Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.DeleteFile.cs">
+      <Link>Common\Interop\Windows\Kernel32\Interop.DeleteFile.cs</Link>
+    </Compile>
     <Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.CriticalSection.cs">
       <Link>Common\Interop\Windows\Kernel32\Interop.CriticalSection.cs</Link>
     </Compile>
     <Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.SECURITY_ATTRIBUTES.cs">
       <Link>Common\Interop\Windows\Kernel32\Interop.SECURITY_ATTRIBUTES.cs</Link>
     </Compile>
+    <Compile Include="$(CommonPath)Interop\Windows\Interop.UNICODE_STRING.cs">
+      <Link>Common\Interop\Windows\Interop.UNICODE_STRING.cs</Link>
+    </Compile>
+    <Compile Include="$(CommonPath)Interop\Windows\Interop.SECURITY_QUALITY_OF_SERVICE.cs">
+      <Link>Common\Interop\Windows\Interop.SECURITY_QUALITY_OF_SERVICE.cs</Link>
+    </Compile>
+    <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\Unix\System.Native\Interop.PosixFAdvise.cs">
       <Link>Common\Interop\Unix\System.Native\Interop.PosixFAdvise.cs</Link>
     </Compile>
+    <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.Read.cs">
       <Link>Common\Interop\Unix\System.Native\Interop.Read.cs</Link>
     </Compile>
index e5cbc2bb00e69296339328576c5e05d75622e78f..26e90f4467d0b936eff491b5979cc1ce768257c0 100644 (file)
@@ -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;
 
         /// <summary>Caches whether Serialization Guard has been disabled for file writes</summary>
@@ -135,6 +135,43 @@ namespace System.IO
         }
 
         public FileStream(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize, FileOptions options)
+            : this(path, mode, access, share, bufferSize, options, 0)
+        {
+        }
+
+        /// <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.
+        /// </summary>
+        /// <param name="path">A relative or absolute path for the file that the current <see cref="System.IO.FileStream" /> instance will encapsulate.</param>
+        /// <param name="options">An object that describes optional <see cref="System.IO.FileStream" /> 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"><see cref="System.IO.FileStreamOptions.PreallocationSize" /> is negative.
+        /// -or-
+        /// <see cref="System.IO.FileStreamOptions.Mode" />, <see cref="System.IO.FileStreamOptions.Access" />, or <see cref="System.IO.FileStreamOptions.Share" /> contain an invalid value.</exception>
+        /// <exception cref="T:System.IO.FileNotFoundException">The file cannot be found, such as when <see cref="System.IO.FileStreamOptions.Mode" /> is <see langword="FileMode.Truncate" /> or <see langword="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 langword="FileMode.CreateNew" /> when the file specified by <paramref name="path" /> already exists, occurred.
+        ///  -or-
+        ///  The stream has been closed.
+        ///  -or-
+        ///  The disk was full (when <see cref="System.IO.FileStreamOptions.PreallocationSize" /> was provided and <paramref name="path" /> was pointing to a regular file).
+        ///  -or-
+        ///  The file was too large (when <see cref="System.IO.FileStreamOptions.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 <see cref="System.IO.FileStreamOptions.Access" /> requested is not permitted by the operating system for the specified <paramref name="path" />, such as when <see cref="System.IO.FileStreamOptions.Access" />  is <see cref="System.IO.FileAccess.Write" /> or <see cref="System.IO.FileAccess.ReadWrite" /> and the file or directory is set for read-only access.
+        ///  -or-
+        /// <see cref="F:System.IO.FileOptions.Encrypted" /> is specified for <see cref="System.IO.FileStreamOptions.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 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 @@ namespace System.IO
             {
                 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 @@ namespace System.IO
                 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 (file)
index 0000000..c7dccfd
--- /dev/null
@@ -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
+    {
+        /// <summary>
+        /// One of the enumeration values that determines how to open or create the file.
+        /// </summary>
+        public FileMode Mode { get; set; }
+        /// <summary>
+        /// A bitwise combination of the enumeration values that determines how the file can be accessed by the <see cref="FileStream" /> object. This also determines the values returned by the <see cref="System.IO.FileStream.CanRead" /> and <see cref="System.IO.FileStream.CanWrite" /> properties of the <see cref="FileStream" /> object.
+        /// </summary>
+        public FileAccess Access { get; set; } = FileAccess.Read;
+        /// <summary>
+        /// A bitwise combination of the enumeration values that determines how the file will be shared by processes. The default value is <see cref="System.IO.FileShare.Read" />.
+        /// </summary>
+        public FileShare Share { get; set; } = FileStream.DefaultShare;
+        /// <summary>
+        /// A bitwise combination of the enumeration values that specifies additional file options. The default value is <see cref="System.IO.FileOptions.None" />, which indicates synchronous IO.
+        /// </summary>
+        public FileOptions Options { get; set; }
+        /// <summary>
+        /// 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 <see cref="FileStream" /> constructor throws an <see cref="ArgumentOutOfRangeException" />.
+        /// In other cases (including the default 0 value), it's ignored.
+        /// </summary>
+        public long PreallocationSize { get; set; }
+    }
+}
index 561bd2de7e97c014058adaf7760727c87fa25993..6719dd9389566aaeb9ccf5735f3f80cd4ec383e5 100644 (file)
@@ -17,8 +17,8 @@ namespace System.IO.Strategies
         {
         }
 
-        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)
         {
         }
 
index 0d2c5df52be3aa6d9bd407b7dd6f00e1720af4bc..176429b41fe501ca3f016538c0091fce768b5957 100644 (file)
@@ -17,10 +17,10 @@ namespace System.IO.Strategies
         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 @@ namespace System.IO.Strategies
                 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);
         }
 
index f33e47b0fe15c46b868697c05169a7e52538371f..13dcc7cfb17e4675953c4a652fde59bb7235f744 100644 (file)
@@ -5,6 +5,7 @@ using System.Buffers;
 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 @@ namespace System.IO.Strategies
             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 @@ namespace System.IO.Strategies
             }
 
             // 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)
index 1ca2e563998caae8c3d3528244b994bbf85c1c31..7dfaf7fe52dc73cd3121ca330074ed21ffd9733c 100644 (file)
@@ -12,8 +12,8 @@ namespace System.IO.Strategies
         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 @@ namespace System.IO.Strategies
             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
     }
 }
index eb1cebc37a7f89f606a78e0cce1056472bf88b6c..f039c020433812240cf791a9fffbc9bfd436dbcd 100644 (file)
@@ -39,7 +39,8 @@ namespace System.IO.Strategies
         /// <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>
-        private void Init(FileMode mode, FileShare share, string originalPath, FileOptions options)
+        /// <param name="preallocationSize">passed to posix_fallocate</param>
+        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 @@ namespace System.IO.Strategies
                     }
                 }
             }
+
+            // 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));
+                }
+            }
         }
 
         /// <summary>Initializes a stream from an already open file handle (file descriptor).</summary>
index fead01218fb0c33e4e94dda4d783e0005f68ab7f..8b51b2597fed0e6358f1c366814bba82fd947fba 100644 (file)
@@ -46,7 +46,7 @@ namespace System.IO.Strategies
         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);
 
index 03ca3533e717b7afdb3152847086b54b6cf5c0e6..d257df97722da80bb3fe9c5270a5c08e469c46cc 100644 (file)
@@ -78,7 +78,7 @@ namespace System.IO.Strategies
             _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 @@ namespace System.IO.Strategies
             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
             {
index ad96cdb4967b44e3e1181a25822a410929a02fe2..30499b660ddf00dc0d8b72a6f03f71556beb7d46 100644 (file)
@@ -15,8 +15,8 @@ namespace System.IO.Strategies
         {
         }
 
-        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)
         {
         }
 
index 94686aa9c05326c3eaf4f38a21c160ba8b942ad0..a5ebdd6f5da59986d98086973895a03ebe4772e3 100644 (file)
@@ -37,7 +37,7 @@ namespace System.IO.Strategies
             _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 @@ namespace System.IO.Strategies
             _access = access;
             _share = share;
 
-            _fileHandle = FileStreamHelpers.OpenHandle(fullPath, mode, access, share, options);
+            _fileHandle = FileStreamHelpers.OpenHandle(fullPath, mode, access, share, options, preallocationSize);
 
             try
             {
index c87b750b7445913e10f72815581fb899b8691b37..12c4068842242bbc06dac4ef221826efa38a37f8 100644 (file)
@@ -7299,6 +7299,15 @@ namespace System.IO
         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 @@ namespace System.IO
         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; } }
index 56fcfb62c407eeaae1b81d45ce52c8a7bd98db45..4c9b0a3aaf9a0af2c7351245bdcf27ac66586125 100644 (file)
     <Compile Include="System\Security\Principal\WindowsPrincipal.cs" />
     <Compile Include="$(CommonPath)Interop\Windows\Interop.Libraries.cs"
              Link="Common\Interop\Windows\Interop.Libraries.cs" />
+    <Compile Include="$(CommonPath)Interop\Windows\Interop.BOOLEAN.cs"
+             Link="Common\Interop\Windows\Advapi32\Interop.BOOLEAN.cs" />
+    <Compile Include="$(CommonPath)Interop\Windows\Interop.SECURITY_QUALITY_OF_SERVICE.cs"
+             Link="Common\Interop\Windows\Advapi32\Interop.SECURITY_QUALITY_OF_SERVICE.cs" />
     <Compile Include="$(CommonPath)Interop\Windows\Interop.UNICODE_STRING.cs"
              Link="Common\Interop\Windows\Advapi32\Interop.UNICODE_STRING.cs" />
     <Compile Include="$(CommonPath)Interop\Windows\Interop.OBJECT_ATTRIBUTES.cs"