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