diff --git a/src/libraries/Common/src/Interop/Unix/System.Native/Interop.PosixFAllocate.cs b/src/libraries/Common/src/Interop/Unix/System.Native/Interop.FAllocate.cs
similarity index 63%
rename from src/libraries/Common/src/Interop/Unix/System.Native/Interop.PosixFAllocate.cs
rename to src/libraries/Common/src/Interop/Unix/System.Native/Interop.FAllocate.cs
index 2866ed38935bdf..71155e0f35e67e 100644
--- a/src/libraries/Common/src/Interop/Unix/System.Native/Interop.PosixFAllocate.cs
+++ b/src/libraries/Common/src/Interop/Unix/System.Native/Interop.FAllocate.cs
@@ -9,9 +9,9 @@ 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.
+ /// Returns -1 on error, 0 on success.
///
- [DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_PosixFAllocate", SetLastError = false)]
- internal static extern int PosixFAllocate(SafeFileHandle fd, long offset, long length);
+ [DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_FAllocate", SetLastError = true)]
+ internal static extern int FAllocate(SafeFileHandle fd, long offset, long length);
}
}
diff --git a/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.FILE_ALLOCATION_INFO.cs b/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.FILE_ALLOCATION_INFO.cs
new file mode 100644
index 00000000000000..0233626ee73c62
--- /dev/null
+++ b/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.FILE_ALLOCATION_INFO.cs
@@ -0,0 +1,16 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+internal static partial class Interop
+{
+ internal static partial class Kernel32
+ {
+ // Value taken from https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-setfileinformationbyhandle#remarks:
+ internal const int FileAllocationInfo = 5;
+
+ internal struct FILE_ALLOCATION_INFO
+ {
+ internal long AllocationSize;
+ }
+ }
+}
diff --git a/src/libraries/Native/Unix/Common/pal_config.h.in b/src/libraries/Native/Unix/Common/pal_config.h.in
index d50c8d6f952976..9cffccbb0cc718 100644
--- a/src/libraries/Native/Unix/Common/pal_config.h.in
+++ b/src/libraries/Native/Unix/Common/pal_config.h.in
@@ -36,8 +36,7 @@
#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 HAVE_FALLOCATE
#cmakedefine01 HAVE_PREADV
#cmakedefine01 HAVE_PWRITEV
#cmakedefine01 PRIORITY_REQUIRES_INT_WHO
diff --git a/src/libraries/Native/Unix/System.Native/entrypoints.c b/src/libraries/Native/Unix/System.Native/entrypoints.c
index c196a734c509c1..e21cfb69884b18 100644
--- a/src/libraries/Native/Unix/System.Native/entrypoints.c
+++ b/src/libraries/Native/Unix/System.Native/entrypoints.c
@@ -92,7 +92,7 @@ static const Entry s_sysNative[] =
DllImportEntry(SystemNative_FTruncate)
DllImportEntry(SystemNative_Poll)
DllImportEntry(SystemNative_PosixFAdvise)
- DllImportEntry(SystemNative_PosixFAllocate)
+ DllImportEntry(SystemNative_FAllocate)
DllImportEntry(SystemNative_Read)
DllImportEntry(SystemNative_ReadLink)
DllImportEntry(SystemNative_Rename)
diff --git a/src/libraries/Native/Unix/System.Native/pal_io.c b/src/libraries/Native/Unix/System.Native/pal_io.c
index d86e8feb93584e..9d313dd6e99519 100644
--- a/src/libraries/Native/Unix/System.Native/pal_io.c
+++ b/src/libraries/Native/Unix/System.Native/pal_io.c
@@ -1008,83 +1008,33 @@ 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)
+int32_t SystemNative_FAllocate(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);
+#if HAVE_FALLOCATE // Linux
+ while ((result = fallocate(fileDescriptor, FALLOC_FL_KEEP_SIZE, (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_flags = F_ALLOCATEALL; // Allocate all requested space or no space at all.
+ fstore.fst_posmode = F_PEOFPOSMODE; // Allocate from the physical end of file.
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);
+#else
+ (void)offset; // unused
+ (void)length; // unused
+ result = -1;
+ errno = EOPNOTSUPP;
#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
+ assert(result == 0 || errno != EINVAL);
- // 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;
- }
+ return result;
}
int32_t SystemNative_Read(intptr_t fd, void* buffer, int32_t bufferSize)
diff --git a/src/libraries/Native/Unix/System.Native/pal_io.h b/src/libraries/Native/Unix/System.Native/pal_io.h
index 3f0db054d4368b..0e5d4cae2feb8f 100644
--- a/src/libraries/Native/Unix/System.Native/pal_io.h
+++ b/src/libraries/Native/Unix/System.Native/pal_io.h
@@ -620,11 +620,11 @@ 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.
+ * Preallocates disk space.
*
- * Returns -1 on ENOSPC, -2 on EFBIG. On success or ignorable error, 0 is returned.
+ * Returns 0 for success, -1 for failure. Sets errno on failure.
*/
-PALEXPORT int32_t SystemNative_PosixFAllocate(intptr_t fd, int64_t offset, int64_t length);
+PALEXPORT int32_t SystemNative_FAllocate(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 3500e54fc71834..1038d1b4c1f2f6 100644
--- a/src/libraries/Native/Unix/configure.cmake
+++ b/src/libraries/Native/Unix/configure.cmake
@@ -232,14 +232,9 @@ check_symbol_exists(
HAVE_POSIX_ADVISE)
check_symbol_exists(
- posix_fallocate
+ fallocate
fcntl.h
- HAVE_POSIX_FALLOCATE)
-
-check_symbol_exists(
- posix_fallocate64
- fcntl.h
- HAVE_POSIX_FALLOCATE64)
+ HAVE_FALLOCATE)
check_symbol_exists(
preadv
diff --git a/src/libraries/System.IO.FileSystem/src/Resources/Strings.resx b/src/libraries/System.IO.FileSystem/src/Resources/Strings.resx
index 3c391ca6bcde4a..583d1c9cc366aa 100644
--- a/src/libraries/System.IO.FileSystem/src/Resources/Strings.resx
+++ b/src/libraries/System.IO.FileSystem/src/Resources/Strings.resx
@@ -173,6 +173,12 @@
Append access can be requested only in write-only mode.
+
+ Preallocation size can be requested only in write mode.
+
+
+ Preallocation size can be requested only for new files.
+
Combining FileMode: {0} with FileAccess: {1} is invalid.
diff --git a/src/libraries/System.IO.FileSystem/tests/File/Open.cs b/src/libraries/System.IO.FileSystem/tests/File/Open.cs
index d014cf0efb94d8..bd9b38b121b886 100644
--- a/src/libraries/System.IO.FileSystem/tests/File/Open.cs
+++ b/src/libraries/System.IO.FileSystem/tests/File/Open.cs
@@ -42,15 +42,14 @@ protected override FileStream CreateFileStream(string path, FileMode mode, FileA
}
}
- public class File_Open_str_options_as : FileStream_ctor_options_as
+ public class File_Open_str_options : FileStream_ctor_options
{
protected override FileStream CreateFileStream(string path, FileMode mode)
{
return File.Open(path,
new FileStreamOptions {
Mode = mode,
- Access = mode == FileMode.Append ? FileAccess.Write : FileAccess.ReadWrite,
- PreallocationSize = PreallocationSize
+ Access = mode == FileMode.Append ? FileAccess.Write : FileAccess.ReadWrite
});
}
@@ -59,12 +58,23 @@ protected override FileStream CreateFileStream(string path, FileMode mode, FileA
return File.Open(path,
new FileStreamOptions {
Mode = mode,
- Access = access,
- PreallocationSize = PreallocationSize
+ Access = access
});
}
protected override FileStream CreateFileStream(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize, FileOptions options)
+ {
+ return File.Open(path,
+ new FileStreamOptions {
+ Mode = mode,
+ Access = access,
+ Share = share,
+ Options = options,
+ BufferSize = bufferSize
+ });
+ }
+
+ protected override FileStream CreateFileStream(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize, FileOptions options, long preallocationSize)
{
return File.Open(path,
new FileStreamOptions {
@@ -73,7 +83,7 @@ protected override FileStream CreateFileStream(string path, FileMode mode, FileA
Share = share,
Options = options,
BufferSize = bufferSize,
- PreallocationSize = PreallocationSize
+ PreallocationSize = preallocationSize
});
}
}
diff --git a/src/libraries/System.IO.FileSystem/tests/File/OpenHandle.cs b/src/libraries/System.IO.FileSystem/tests/File/OpenHandle.cs
index a8b3619fb51cf7..120ed9aec222ef 100644
--- a/src/libraries/System.IO.FileSystem/tests/File/OpenHandle.cs
+++ b/src/libraries/System.IO.FileSystem/tests/File/OpenHandle.cs
@@ -7,28 +7,24 @@
namespace System.IO.Tests
{
// to avoid a lot of code duplication, we reuse FileStream tests
- public class File_OpenHandle : FileStream_ctor_options_as
+ public class File_OpenHandle : FileStream_ctor_options
{
protected override string GetExpectedParamName(string paramName) => paramName;
protected override FileStream CreateFileStream(string path, FileMode mode)
{
FileAccess access = mode == FileMode.Append ? FileAccess.Write : FileAccess.ReadWrite;
- return new FileStream(File.OpenHandle(path, mode, access, preallocationSize: PreallocationSize), access);
+ return new FileStream(File.OpenHandle(path, mode, access), access);
}
protected override FileStream CreateFileStream(string path, FileMode mode, FileAccess access)
- => new FileStream(File.OpenHandle(path, mode, access, preallocationSize: PreallocationSize), access);
+ => new FileStream(File.OpenHandle(path, mode, access), access);
protected override FileStream CreateFileStream(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize, FileOptions options)
- => new FileStream(File.OpenHandle(path, mode, access, share, options, PreallocationSize), access, bufferSize, (options & FileOptions.Asynchronous) != 0);
+ => new FileStream(File.OpenHandle(path, mode, access, share, options), access, bufferSize, (options & FileOptions.Asynchronous) != 0);
- [Fact]
- public override void NegativePreallocationSizeThrows()
- {
- ArgumentOutOfRangeException ex = Assert.Throws(
- () => File.OpenHandle("validPath", FileMode.CreateNew, FileAccess.Write, FileShare.None, FileOptions.None, preallocationSize: -1));
- }
+ protected override FileStream CreateFileStream(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize, FileOptions options, long preallocationSize)
+ => new FileStream(File.OpenHandle(path, mode, access, share, options, preallocationSize), access, bufferSize, (options & FileOptions.Asynchronous) != 0);
[ActiveIssue("https://github.com/dotnet/runtime/issues/53432")]
[Theory, MemberData(nameof(StreamSpecifiers))]
diff --git a/src/libraries/System.IO.FileSystem/tests/FileInfo/Open.cs b/src/libraries/System.IO.FileSystem/tests/FileInfo/Open.cs
index 2324a92eafce4f..36fd39a6000564 100644
--- a/src/libraries/System.IO.FileSystem/tests/FileInfo/Open.cs
+++ b/src/libraries/System.IO.FileSystem/tests/FileInfo/Open.cs
@@ -74,15 +74,14 @@ protected override FileStream CreateFileStream(string path, FileMode mode, FileA
}
}
- public class FileInfo_Open_options_as : FileStream_ctor_options_as
+ public class FileInfo_Open_options : FileStream_ctor_options
{
protected override FileStream CreateFileStream(string path, FileMode mode)
{
return new FileInfo(path).Open(
new FileStreamOptions {
Mode = mode,
- Access = mode == FileMode.Append ? FileAccess.Write : FileAccess.ReadWrite,
- PreallocationSize = PreallocationSize
+ Access = mode == FileMode.Append ? FileAccess.Write : FileAccess.ReadWrite
});
}
@@ -91,12 +90,23 @@ protected override FileStream CreateFileStream(string path, FileMode mode, FileA
return new FileInfo(path).Open(
new FileStreamOptions {
Mode = mode,
- Access = access,
- PreallocationSize = PreallocationSize
+ Access = access
});
}
protected override FileStream CreateFileStream(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize, FileOptions options)
+ {
+ return new FileInfo(path).Open(
+ new FileStreamOptions {
+ Mode = mode,
+ Access = access,
+ Share = share,
+ Options = options,
+ BufferSize = bufferSize
+ });
+ }
+
+ protected override FileStream CreateFileStream(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize, FileOptions options, long preallocationSize)
{
return new FileInfo(path).Open(
new FileStreamOptions {
@@ -105,7 +115,7 @@ protected override FileStream CreateFileStream(string path, FileMode mode, FileA
Share = share,
Options = options,
BufferSize = bufferSize,
- PreallocationSize = PreallocationSize
+ PreallocationSize = preallocationSize
});
}
}
diff --git a/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_options.Browser.cs b/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_options.Browser.cs
new file mode 100644
index 00000000000000..45326d885c455d
--- /dev/null
+++ b/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_options.Browser.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.
+
+namespace System.IO.Tests
+{
+ public partial class FileStream_ctor_options
+ {
+ private static long GetAllocatedSize(FileStream fileStream)
+ {
+ return 0;
+ }
+
+ private static bool SupportsPreallocation => false;
+
+ private static bool IsGetAllocatedSizeImplemented => false;
+ }
+}
diff --git a/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_options.Unix.cs b/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_options.Unix.cs
new file mode 100644
index 00000000000000..c33c56a1fa5706
--- /dev/null
+++ b/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_options.Unix.cs
@@ -0,0 +1,36 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+
+namespace System.IO.Tests
+{
+ public partial class FileStream_ctor_options
+ {
+ private static long GetAllocatedSize(FileStream fileStream)
+ {
+ bool isOSX = RuntimeInformation.IsOSPlatform(OSPlatform.OSX);
+ // Call 'stat' to get the number of blocks, and size of blocks.
+ using var px = Process.Start(new ProcessStartInfo
+ {
+ FileName = "stat",
+ ArgumentList = { isOSX ? "-f" : "-c",
+ isOSX ? "%b %k" : "%b %B",
+ fileStream.Name },
+ RedirectStandardOutput = true
+ });
+ string stdout = px.StandardOutput.ReadToEnd();
+
+ string[] parts = stdout.Split(' ');
+ return long.Parse(parts[0]) * long.Parse(parts[1]);
+ }
+
+ private static bool SupportsPreallocation =>
+ RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ||
+ RuntimeInformation.IsOSPlatform(OSPlatform.OSX);
+
+ // Mobile platforms don't support Process.Start.
+ private static bool IsGetAllocatedSizeImplemented => !PlatformDetection.IsMobile;
+ }
+}
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.Windows.cs
similarity index 66%
rename from src/libraries/System.IO.FileSystem/tests/FileStream/ctor_options_as.Windows.cs
rename to src/libraries/System.IO.FileSystem/tests/FileStream/ctor_options.Windows.cs
index b716bcb3de25b5..2dca6baf22bd07 100644
--- a/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_options_as.Windows.cs
+++ b/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_options.Windows.cs
@@ -9,8 +9,21 @@
namespace System.IO.Tests
{
- public partial class FileStream_ctor_options_as
+ public partial class FileStream_ctor_options
{
+ private unsafe long GetAllocatedSize(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;
+ }
+
+ private static bool SupportsPreallocation => true;
+
+ private static bool IsGetAllocatedSizeImplemented => true;
+
[Theory]
[InlineData(@"\\?\")]
[InlineData(@"\??\")]
@@ -19,43 +32,26 @@ public void ExtendedPathsAreSupported(string prefix)
{
const long preallocationSize = 123;
- string filePath = prefix + Path.GetFullPath(GetPathToNonExistingFile());
+ string filePath = prefix + Path.GetFullPath(GetTestFilePath());
- using (var fs = new FileStream(filePath, GetOptions(FileMode.CreateNew, FileAccess.Write, FileShare.None, FileOptions.None, preallocationSize)))
+ using (var fs = CreateFileStream(filePath, FileMode.CreateNew, FileAccess.Write, FileShare.None, bufferSize: 4096, FileOptions.None, preallocationSize))
{
- Assert.Equal(preallocationSize, fs.Length);
- }
- }
-
- [Fact]
- public async Task PreallocationSizeIsIgnoredForNonSeekableFiles()
- {
- string pipeName = GetNamedPipeServerStreamName();
- string pipePath = Path.GetFullPath($@"\\.\pipe\{pipeName}");
-
- FileStreamOptions options = new() { Mode = FileMode.Open, Access = FileAccess.Write, Share = FileShare.None, PreallocationSize = 123 };
-
- using (var server = new NamedPipeServerStream(pipeName, PipeDirection.In))
- using (var clienStream = new FileStream(pipePath, options))
- {
- await server.WaitForConnectionAsync();
-
- Assert.False(clienStream.CanSeek);
+ Assert.Equal(0, fs.Length);
+ Assert.True(GetAllocatedSize(fs) >= preallocationSize);
}
}
[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();
+ string filePath = GetTestFilePath();
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)));
+ IOException ex = Assert.Throws(() => CreateFileStream(filePath, mode, FileAccess.Write, FileShare.None, bufferSize: 4096, FileOptions.None, tooMuch));
Assert.Contains(filePath, ex.Message);
Assert.Contains(tooMuch.ToString(), ex.Message);
diff --git a/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_options.cs b/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_options.cs
new file mode 100644
index 00000000000000..c4344de517449d
--- /dev/null
+++ b/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_options.cs
@@ -0,0 +1,172 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Linq;
+using System.Security.Cryptography;
+using Xunit;
+
+namespace System.IO.Tests
+{
+ // 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 : FileStream_ctor_str_fm_fa_fs_buffer_fo
+ {
+ protected override string GetExpectedParamName(string paramName) => "value";
+
+ protected override FileStream CreateFileStream(string path, FileMode mode)
+ => new FileStream(path,
+ new FileStreamOptions
+ {
+ Mode = mode,
+ Access = mode == FileMode.Append ? FileAccess.Write : FileAccess.ReadWrite
+ });
+
+ protected override FileStream CreateFileStream(string path, FileMode mode, FileAccess access)
+ => new FileStream(path,
+ new FileStreamOptions
+ {
+ Mode = mode,
+ Access = access
+ });
+
+ protected override FileStream CreateFileStream(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize, FileOptions options)
+ => new FileStream(path,
+ new FileStreamOptions
+ {
+ Mode = mode,
+ Access = access,
+ Share = share,
+ BufferSize = bufferSize,
+ Options = options
+ });
+
+ protected virtual FileStream CreateFileStream(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize, FileOptions options, long preallocationSize)
+ => new FileStream(path,
+ new FileStreamOptions
+ {
+ Mode = mode,
+ Access = access,
+ Share = share,
+ BufferSize = bufferSize,
+ Options = options,
+ PreallocationSize = preallocationSize
+ });
+
+ [Fact]
+ public virtual void NegativePreallocationSizeThrows()
+ {
+ string filePath = GetTestFilePath();
+ ArgumentOutOfRangeException ex = Assert.Throws(
+ () => CreateFileStream(filePath, FileMode.CreateNew, FileAccess.Write, FileShare.None, bufferSize: 1, FileOptions.None, preallocationSize: -1));
+ }
+
+ [Theory]
+ [InlineData(FileMode.Append)]
+ [InlineData(FileMode.Open)]
+ [InlineData(FileMode.OpenOrCreate)]
+ [InlineData(FileMode.Truncate)]
+ public void PreallocationSizeThrowsForFileModesThatOpenExistingFiles(FileMode mode)
+ {
+ Assert.Throws(
+ () => CreateFileStream(GetTestFilePath(), mode, FileAccess.Write, FileShare.None, bufferSize: 1, FileOptions.None, preallocationSize: 20));
+ }
+
+ [Theory]
+ [InlineData(FileMode.Create)]
+ [InlineData(FileMode.CreateNew)]
+ public void PreallocationSizeThrowsForReadOnlyAccess(FileMode mode)
+ {
+ Assert.Throws(
+ () => CreateFileStream(GetTestFilePath(), mode, FileAccess.Read, FileShare.None, bufferSize: 1, FileOptions.None, preallocationSize: 20));
+ }
+
+ [Theory]
+ [InlineData(FileMode.Create, false)]
+ [InlineData(FileMode.Create, true)]
+ [InlineData(FileMode.CreateNew, false)]
+ [InlineData(FileMode.Append, false)]
+ [InlineData(FileMode.Append, true)]
+ [InlineData(FileMode.Open, true)]
+ [InlineData(FileMode.OpenOrCreate, true)]
+ [InlineData(FileMode.OpenOrCreate, false)]
+ [InlineData(FileMode.Truncate, true)]
+ public void ZeroPreallocationSizeDoesNotAllocate(FileMode mode, bool createFile)
+ {
+ string filename = GetTestFilePath();
+
+ if (createFile)
+ {
+ File.WriteAllText(filename, "");
+ }
+
+ using (FileStream fs = CreateFileStream(filename, mode, FileAccess.Write, FileShare.None, bufferSize: 1, FileOptions.None, preallocationSize: 0))
+ {
+ Assert.Equal(0, fs.Length);
+ if (IsGetAllocatedSizeImplemented)
+ {
+ Assert.Equal(0, GetAllocatedSize(fs));
+ }
+ Assert.Equal(0, fs.Position);
+ }
+ }
+
+ [Theory]
+ [InlineData(FileAccess.Write, FileMode.Create)]
+ [InlineData(FileAccess.Write, FileMode.CreateNew)]
+ [InlineData(FileAccess.ReadWrite, FileMode.Create)]
+ [InlineData(FileAccess.ReadWrite, FileMode.CreateNew)]
+ public void PreallocationSize(FileAccess access, FileMode mode)
+ {
+ const long preallocationSize = 123;
+
+ using (var fs = CreateFileStream(GetTestFilePath(), mode, access, FileShare.None, bufferSize: 1, FileOptions.None, preallocationSize))
+ {
+ Assert.Equal(0, fs.Length);
+ if (IsGetAllocatedSizeImplemented)
+ {
+ if (SupportsPreallocation)
+ {
+ Assert.True(GetAllocatedSize(fs) >= preallocationSize);
+ }
+ else
+ {
+ Assert.Equal(0, GetAllocatedSize(fs));
+ }
+ }
+ 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)]
+ public void WhenDiskIsFullTheErrorMessageContainsAllDetails(FileMode mode)
+ {
+ const long tooMuch = 1024L * 1024L * 1024L * 1024L; // 1 TB
+
+ string filePath = GetTestFilePath();
+
+ IOException ex = Assert.Throws(() => CreateFileStream(filePath, mode, FileAccess.Write, FileShare.None, bufferSize: 1, FileOptions.None, tooMuch));
+ Assert.Contains(filePath, ex.Message);
+ Assert.Contains(tooMuch.ToString(), ex.Message);
+
+ // ensure it was NOT created
+ bool exists = File.Exists(filePath);
+ if (exists)
+ {
+ File.Delete(filePath);
+ }
+ Assert.False(exists);
+ }
+ }
+
+ [CollectionDefinition("NoParallelTests", DisableParallelization = true)]
+ public partial class NoParallelTests { }
+}
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
deleted file mode 100644
index b23ccb15739832..00000000000000
--- a/src/libraries/System.IO.FileSystem/tests/FileStream/ctor_options_as.cs
+++ /dev/null
@@ -1,223 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using System.Linq;
-using System.Security.Cryptography;
-using 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 string GetExpectedParamName(string paramName) => "value";
-
- 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 override FileStream CreateFileStream(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize, FileOptions options)
- => new FileStream(path,
- new FileStreamOptions
- {
- Mode = mode,
- Access = access,
- Share = share,
- BufferSize = bufferSize,
- Options = options,
- 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
- {
- protected override long PreallocationSize => 10;
-
- protected override long InitialLength => 10;
-
- [Fact]
- public virtual 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 WhenFileIsCreatedWithoutPreallocationSizeSpecifiedItsLengthIsZero(FileMode mode, long preallocationSize)
- {
- using (var fs = new FileStream(GetPathToNonExistingFile(), GetOptions(mode, FileAccess.Write, FileShare.None, FileOptions.None, preallocationSize)))
- {
- Assert.Equal(0, fs.Length);
- Assert.Equal(0, fs.Position);
- }
- }
-
- [Theory]
- [InlineData(FileMode.Open, 20L)]
- [InlineData(FileMode.Open, 5L)]
- [InlineData(FileMode.Append, 20L)]
- [InlineData(FileMode.Append, 5L)]
- public void PreallocationSizeIsIgnoredForFileModeOpenAndAppend(FileMode mode, long preallocationSize)
- {
- const int initialSize = 10;
- string filePath = GetPathToNonExistingFile();
- File.WriteAllBytes(filePath, new byte[initialSize]);
-
- using (var fs = new FileStream(filePath, GetOptions(mode, FileAccess.Write, FileShare.None, FileOptions.None, preallocationSize)))
- {
- Assert.Equal(initialSize, fs.Length); // it has NOT been changed
- Assert.Equal(mode == FileMode.Append ? initialSize : 0, fs.Position);
- }
- }
-
- [Theory]
- [InlineData(FileMode.OpenOrCreate, 20L)] // preallocationSize > initialSize
- [InlineData(FileMode.OpenOrCreate, 5L)] // preallocationSize < initialSize
- public void WhenExistingFileIsBeingOpenedWithOpenOrCreateModeTheLengthRemainsUnchanged(FileMode mode, long preallocationSize)
- {
- const int initialSize = 10;
- string filePath = GetPathToNonExistingFile();
- byte[] initialData = RandomNumberGenerator.GetBytes(initialSize);
- File.WriteAllBytes(filePath, initialData);
-
- using (var fs = new FileStream(filePath, GetOptions(mode, FileAccess.ReadWrite, FileShare.None, FileOptions.None, preallocationSize)))
- {
- Assert.Equal(initialSize, fs.Length); // it was not changed
- Assert.Equal(0, fs.Position);
-
- byte[] actualContent = new byte[initialData.Length];
- Assert.Equal(actualContent.Length, fs.Read(actualContent));
- AssertExtensions.SequenceEqual(initialData, actualContent); // the initial content was not changed
- }
- }
-
- [Theory]
- [InlineData(FileMode.Create)]
- [InlineData(FileMode.CreateNew)]
- [InlineData(FileMode.OpenOrCreate)]
- public void WhenFileIsCreatedWithPreallocationSizeSpecifiedTheLengthIsSetAndTheContentIsZeroed(FileMode mode)
- {
- const long preallocationSize = 123;
-
- using (var fs = new FileStream(GetPathToNonExistingFile(), GetOptions(mode, FileAccess.ReadWrite, FileShare.None, FileOptions.None, preallocationSize)))
- {
- Assert.Equal(preallocationSize, fs.Length);
- Assert.Equal(0, fs.Position);
-
- AssertFileContentHasBeenZeroed(0, (int)fs.Length, fs);
- }
- }
-
- [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
- bool exists = File.Exists(filePath);
- if (exists)
- {
- File.Delete(filePath);
- }
- Assert.False(exists);
- }
-
- [Fact]
- public void WhenFileIsTruncatedWithPreallocationSizeSpecifiedTheLengthIsSetAndTheContentIsZeroed()
- {
- 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, Enumerable.Repeat((byte)1, initialSize).ToArray());
-
- using (var fs = new FileStream(filePath, GetOptions(FileMode.Truncate, FileAccess.ReadWrite, FileShare.None, FileOptions.None, preallocationSize)))
- {
- Assert.Equal(preallocationSize, fs.Length);
- Assert.Equal(0, fs.Position);
-
- AssertFileContentHasBeenZeroed(0, (int)fs.Length, fs);
- }
- }
-
- private string GetPathToNonExistingFile()
- {
- string filePath = GetTestFilePath();
-
- if (File.Exists(filePath))
- {
- File.Delete(filePath);
- }
-
- return filePath;
- }
-
- private static void AssertFileContentHasBeenZeroed(int from, int to, FileStream fs)
- {
- int expectedByteCount = to - from;
- int extraByteCount = 1;
- byte[] content = Enumerable.Repeat((byte)1, expectedByteCount + extraByteCount).ToArray();
- fs.Position = from;
- Assert.Equal(expectedByteCount, fs.Read(content));
- Assert.All(content.SkipLast(extraByteCount), @byte => Assert.Equal(0, @byte));
- Assert.Equal(to, fs.Position);
- }
- }
-}
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 47e3b492373161..cd3b18da034228 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
@@ -13,8 +13,6 @@ protected virtual FileStream CreateFileStream(string path, FileMode mode)
return new FileStream(path, mode);
}
- protected virtual long InitialLength => 0;
-
protected virtual string GetExpectedParamName(string paramName) => paramName;
[Fact]
@@ -97,7 +95,7 @@ public void FileModeCreateExisting(string streamSpecifier)
using (FileStream fs = CreateFileStream(fileName, FileMode.Create))
{
// Ensure that the file was re-created
- Assert.Equal(InitialLength, fs.Length);
+ Assert.Equal(0, fs.Length);
Assert.Equal(0L, fs.Position);
Assert.True(fs.CanRead);
Assert.True(fs.CanWrite);
@@ -148,7 +146,7 @@ public void FileModeOpenExisting(string streamSpecifier)
using (FileStream fs = CreateFileStream(fileName, FileMode.Open))
{
// Ensure that the file was re-opened
- Assert.Equal(Math.Max(1L, InitialLength), fs.Length);
+ Assert.Equal(1, fs.Length);
Assert.Equal(0L, fs.Position);
Assert.True(fs.CanRead);
Assert.True(fs.CanWrite);
@@ -177,7 +175,7 @@ public void FileModeOpenOrCreateExisting(string streamSpecifier)
using (FileStream fs = CreateFileStream(fileName, FileMode.OpenOrCreate))
{
// Ensure that the file was re-opened
- Assert.Equal(Math.Max(1L, InitialLength), fs.Length);
+ Assert.Equal(1, fs.Length);
Assert.Equal(0L, fs.Position);
Assert.True(fs.CanRead);
Assert.True(fs.CanWrite);
@@ -204,7 +202,7 @@ public void FileModeTruncateExisting(string streamSpecifier)
using (FileStream fs = CreateFileStream(fileName, FileMode.Truncate))
{
// Ensure that the file was re-opened and truncated
- Assert.Equal(InitialLength, fs.Length);
+ Assert.Equal(0, fs.Length);
Assert.Equal(0L, fs.Position);
Assert.True(fs.CanRead);
Assert.True(fs.CanWrite);
@@ -246,7 +244,7 @@ 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(Math.Max(1L, InitialLength), fs.Length);
+ Assert.Equal(1, fs.Length);
long position = fs.Position;
Assert.Equal(fs.Length, position);
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 d9fcde23a91b79..54b9c2622937d7 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
@@ -23,6 +23,8 @@
+
+
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 a9bedb8bd312bc..ae57e79319a5f3 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
@@ -24,7 +24,7 @@
-
+
@@ -71,12 +71,13 @@
+
-
+
@@ -86,6 +87,8 @@
+
+
@@ -99,6 +102,7 @@
+
diff --git a/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Unix.cs b/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Unix.cs
index 777997444848fe..1edb65fe58afbb 100644
--- a/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Unix.cs
+++ b/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Unix.cs
@@ -323,20 +323,24 @@ private void Init(string path, FileMode mode, FileAccess access, FileShare share
}
}
- // If preallocationSize has been provided for a creatable and writeable file
- if (FileStreamHelpers.ShouldPreallocate(preallocationSize, access, mode, this))
+ if (preallocationSize > 0 && Interop.Sys.FAllocate(this, 0, preallocationSize) < 0)
{
- int fallocateResult = Interop.Sys.PosixFAllocate(this, 0, preallocationSize);
- if (fallocateResult != 0)
+ Interop.ErrorInfo errorInfo = Interop.Sys.GetLastErrorInfo();
+
+ // Only throw for errors that indicate there is not enough space.
+ if (errorInfo.Error == Interop.Error.EFBIG ||
+ errorInfo.Error == Interop.Error.ENOSPC)
{
Dispose();
- Interop.Sys.Unlink(path!); // remove the file to mimic Windows behaviour (atomic operation)
- Debug.Assert(fallocateResult == -1 || fallocateResult == -2);
- throw new IOException(SR.Format(
- fallocateResult == -1 ? SR.IO_DiskFull_Path_AllocationSize : SR.IO_FileTooLarge_Path_AllocationSize,
- path,
- preallocationSize));
+ // Delete the file we've created.
+ Debug.Assert(mode == FileMode.Create || mode == FileMode.CreateNew);
+ Interop.Sys.Unlink(path!);
+
+ throw new IOException(SR.Format(errorInfo.Error == Interop.Error.EFBIG
+ ? SR.IO_FileTooLarge_Path_AllocationSize
+ : SR.IO_DiskFull_Path_AllocationSize,
+ path, preallocationSize));
}
}
}
diff --git a/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Windows.cs b/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Windows.cs
index 3dde1e2e89a6b8..3352f0194e73b9 100644
--- a/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Windows.cs
+++ b/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Windows.cs
@@ -34,7 +34,7 @@ internal static unsafe SafeFileHandle Open(string fullPath, FileMode mode, FileA
// of converting DOS to NT file paths (RtlDosPathNameToRelativeNtPathName_U_WithStatus is not documented)
SafeFileHandle fileHandle = CreateFile(fullPath, mode, access, share, options);
- if (FileStreamHelpers.ShouldPreallocate(preallocationSize, access, mode, fileHandle))
+ if (preallocationSize > 0)
{
Preallocate(fullPath, preallocationSize, fileHandle);
}
@@ -108,18 +108,33 @@ private static unsafe SafeFileHandle CreateFile(string fullPath, FileMode mode,
private static unsafe void Preallocate(string fullPath, long preallocationSize, SafeFileHandle fileHandle)
{
- // preallocationSize must be ignored for non-seekable files, unsupported file systems
- // and other failures other than ERROR_DISK_FULL and ERROR_FILE_TOO_LARGE
- if (!FileStreamHelpers.TrySetFileLength(fileHandle, preallocationSize, out int errorCode)
- && errorCode == Interop.Errors.ERROR_DISK_FULL || errorCode == Interop.Errors.ERROR_FILE_TOO_LARGE)
- {
- // Windows does not modify the file size if the request can't be satisfied in atomic way.
- // posix_fallocate (Unix) implementation might consume all available disk space and fail after that.
- // To ensure that the behaviour is the same for every OS (no incomplete or empty file), we close the handle and delete the file.
- fileHandle.Dispose();
- Interop.Kernel32.DeleteFile(fullPath);
-
- throw new IOException(SR.Format(errorCode == Interop.Errors.ERROR_DISK_FULL ? SR.IO_DiskFull_Path_AllocationSize : SR.IO_FileTooLarge_Path_AllocationSize, fullPath, preallocationSize));
+ var allocationInfo = new Interop.Kernel32.FILE_ALLOCATION_INFO
+ {
+ AllocationSize = preallocationSize
+ };
+
+ if (!Interop.Kernel32.SetFileInformationByHandle(
+ fileHandle,
+ Interop.Kernel32.FileAllocationInfo,
+ &allocationInfo,
+ (uint)sizeof(Interop.Kernel32.FILE_ALLOCATION_INFO)))
+ {
+ int errorCode = Marshal.GetLastPInvokeError();
+
+ // Only throw for errors that indicate there is not enough space.
+ if (errorCode == Interop.Errors.ERROR_DISK_FULL ||
+ errorCode == Interop.Errors.ERROR_FILE_TOO_LARGE)
+ {
+ fileHandle.Dispose();
+
+ // Delete the file we've created.
+ Interop.Kernel32.DeleteFile(fullPath);
+
+ throw new IOException(SR.Format(errorCode == Interop.Errors.ERROR_DISK_FULL
+ ? SR.IO_DiskFull_Path_AllocationSize
+ : SR.IO_FileTooLarge_Path_AllocationSize,
+ fullPath, preallocationSize));
+ }
}
}
diff --git a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx
index 1e16e6b577abf7..c651da612d53c4 100644
--- a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx
+++ b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx
@@ -1092,6 +1092,12 @@
Append access can be requested only in write-only mode.
+
+ Preallocation size can be requested only in write mode.
+
+
+ Preallocation size can be requested only for new files.
+
Type of argument is not compatible with the generic comparer.
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 2a757e5f52589c..25f933a1e5b312 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
@@ -1457,6 +1457,9 @@
Common\Interop\Windows\Kernel32\Interop.FILE_BASIC_INFO.cs
+
+ Common\Interop\Windows\Kernel32\Interop.FILE_ALLOCATION_INFO.cs
+
Common\Interop\Windows\Kernel32\Interop.FILE_END_OF_FILE_INFO.cs
@@ -2036,8 +2039,8 @@
Common\Interop\Unix\System.Native\Interop.PosixFAdvise.cs
-
- Common\Interop\Unix\System.Native\Interop.PosixFAllocate.cs
+
+ Common\Interop\Unix\System.Native\Interop.FAllocate.cs
Common\Interop\Unix\System.Native\Interop.PRead.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 b60bb9d79e3a08..b97194b86c5f0b 100644
--- a/src/libraries/System.Private.CoreLib/src/System/IO/FileStream.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/IO/FileStream.cs
@@ -197,6 +197,11 @@ public FileStream(string path, FileStreamOptions options)
}
}
+ if (options.PreallocationSize > 0)
+ {
+ FileStreamHelpers.ValidateArgumentsForPreallocation(options.Mode, options.Access);
+ }
+
FileStreamHelpers.SerializationGuard(options.Access);
_strategy = FileStreamHelpers.ChooseStrategy(
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 981776da32b81d..3db2174d3fbd14 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
@@ -58,12 +58,6 @@ e is UnauthorizedAccessException ||
e is NotSupportedException ||
(e is ArgumentException && !(e is ArgumentNullException));
- internal static bool ShouldPreallocate(long preallocationSize, FileAccess access, FileMode mode, SafeFileHandle fileHandle)
- => preallocationSize > 0
- && (access & FileAccess.Write) != 0
- && mode != FileMode.Open && mode != FileMode.Append
- && (mode != FileMode.OpenOrCreate || (fileHandle.CanSeek && RandomAccess.GetFileLength(fileHandle) == 0)); // allow to extend only new files
-
internal static void ValidateArguments(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize, FileOptions options, long preallocationSize)
{
if (path == null)
@@ -126,9 +120,31 @@ internal static void ValidateArguments(string path, FileMode mode, FileAccess ac
throw new ArgumentException(SR.Argument_InvalidAppendMode, nameof(access));
}
+ if (preallocationSize > 0)
+ {
+ ValidateArgumentsForPreallocation(mode, access);
+ }
+
SerializationGuard(access);
}
+ internal static void ValidateArgumentsForPreallocation(FileMode mode, FileAccess access)
+ {
+ // The user will be writing into the preallocated space.
+ if ((access & FileAccess.Write) == 0)
+ {
+ throw new ArgumentException(SR.Argument_InvalidPreallocateAccess, nameof(access));
+ }
+
+ // Only allow preallocation for newly created/overwritten files.
+ // When we fail to preallocate, we'll remove the file.
+ if (mode != FileMode.Create &&
+ mode != FileMode.CreateNew)
+ {
+ throw new ArgumentException(SR.Argument_InvalidPreallocateMode, nameof(mode));
+ }
+ }
+
internal static void SerializationGuard(FileAccess access)
{
if ((access & FileAccess.Write) == FileAccess.Write)