internal static partial class Sys
{
/// <summary>
- /// Returns -1 on ENOSPC, -2 on EFBIG. On success or ignorable error, 0 is returned.
+ /// Returns -1 on error, 0 on success.
/// </summary>
- [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);
}
}
#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
DllImportEntry(SystemNative_FTruncate)
DllImportEntry(SystemNative_Poll)
DllImportEntry(SystemNative_PosixFAdvise)
- DllImportEntry(SystemNative_PosixFAllocate)
+ DllImportEntry(SystemNative_FAllocate)
DllImportEntry(SystemNative_Read)
DllImportEntry(SystemNative_ReadLink)
DllImportEntry(SystemNative_Rename)
#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
+ int fileDescriptor = ToFileDescriptor(fd);
+ while ((result = fallocate(fileDescriptor, FALLOC_FL_KEEP_SIZE, (off_t)offset, (off_t)length)) == EINTR);
#elif defined(F_PREALLOCATE) // macOS
+ int fileDescriptor = ToFileDescriptor(fd);
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)fd; // unused
+ (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)
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.
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
}
}
- 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
});
}
return File.Open(path,
new FileStreamOptions {
Mode = mode,
- Access = access,
- PreallocationSize = PreallocationSize
+ Access = access
});
}
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 {
+ Mode = mode,
+ Access = access,
+ Share = share,
+ Options = options,
BufferSize = bufferSize,
- PreallocationSize = PreallocationSize
+ PreallocationSize = preallocationSize
});
}
}
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<ArgumentOutOfRangeException>(
- () => 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))]
}
}
- 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
});
}
return new FileInfo(path).Open(
new FileStreamOptions {
Mode = mode,
- Access = access,
- PreallocationSize = PreallocationSize
+ Access = access
});
}
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 {
+ Mode = mode,
+ Access = access,
+ Share = share,
+ Options = options,
BufferSize = bufferSize,
- PreallocationSize = PreallocationSize
+ PreallocationSize = preallocationSize
});
}
}
--- /dev/null
+// 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;
+ }
+}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.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;
+ }
+}
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+using System.IO.Pipes;
using System.Runtime.InteropServices;
using System.Text;
+using System.Threading.Tasks;
using Xunit;
namespace System.IO.Tests
{
- public partial class FileStream_ctor_options_as : FileStream_ctor_options_as_base
+ public partial class FileStream_ctor_options
{
- 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)
+ private unsafe long GetAllocatedSize(FileStream fileStream)
{
Interop.Kernel32.FILE_STANDARD_INFO info;
return info.AllocationSize;
}
+ private static bool SupportsPreallocation => true;
+
+ private static bool IsGetAllocatedSizeImplemented => true;
+
[Theory]
[InlineData(@"\\?\")]
[InlineData(@"\??\")]
{
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.True(GetActualPreallocationSize(fs) >= preallocationSize, $"Provided {preallocationSize}, actual: {GetActualPreallocationSize(fs)}");
+ 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<IOException>(() => new FileStream(filePath, GetOptions(mode, FileAccess.Write, FileShare.None, FileOptions.None, tooMuch)));
+ IOException ex = Assert.Throws<IOException>(() => CreateFileStream(filePath, mode, FileAccess.Write, FileShare.None, bufferSize: 4096, FileOptions.None, tooMuch));
Assert.Contains(filePath, ex.Message);
Assert.Contains(tooMuch.ToString(), ex.Message);
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.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 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<ArgumentOutOfRangeException>(
+ () => 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<ArgumentException>(
+ () => 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<ArgumentException>(
+ () => 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<IOException>(() => 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 { }
+}
+++ /dev/null
-// 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) => fileStream.Length;
- }
-}
+++ /dev/null
-// 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;
- }
- }
-}
+++ /dev/null
-// 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 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
- {
- [Fact]
- public virtual void NegativePreallocationSizeThrows()
- {
- string filePath = GetPathToNonExistingFile();
- ArgumentOutOfRangeException ex = Assert.Throws<ArgumentOutOfRangeException>(
- () => new FileStream(filePath, GetOptions(FileMode.CreateNew, FileAccess.Write, FileShare.None, FileOptions.None, -1)));
- }
-
- [Theory]
- [InlineData(FileMode.Create, 0L)]
- [InlineData(FileMode.CreateNew, 0L)]
- [InlineData(FileMode.OpenOrCreate, 0L)]
- public void WhenFileIsCreatedWithoutPreallocationSizeSpecifiedThePreallocationSizeIsNotSet(FileMode mode, long preallocationSize)
- {
- using (var fs = new FileStream(GetPathToNonExistingFile(), GetOptions(mode, FileAccess.Write, FileShare.None, FileOptions.None, preallocationSize)))
- {
- Assert.Equal(0, GetActualPreallocationSize(fs));
- Assert.Equal(0, fs.Length);
- Assert.Equal(0, fs.Position);
- }
- }
-
- [Theory]
- [InlineData(FileMode.Open, 0L)]
- [InlineData(FileMode.Open, 1L)]
- [InlineData(FileMode.OpenOrCreate, 0L)]
- [InlineData(FileMode.OpenOrCreate, 1L)]
- [InlineData(FileMode.Append, 0L)]
- [InlineData(FileMode.Append, 1L)]
- public void WhenExistingFileIsBeingOpenedWithPreallocationSizeSpecifiedThePreallocationSizeIsNotChanged(FileMode mode, long preallocationSize)
- {
- const int initialSize = 1;
- string filePath = GetPathToNonExistingFile();
- File.WriteAllBytes(filePath, new byte[initialSize]);
- long initialPreallocationSize;
-
- using (var fs = new FileStream(filePath, GetOptions(mode, FileAccess.Write, FileShare.None, FileOptions.None, 0))) // preallocationSize NOT provided
- {
- initialPreallocationSize = GetActualPreallocationSize(fs); // just read it to ensure it's not being changed
- }
-
- using (var fs = new FileStream(filePath, GetOptions(mode, FileAccess.Write, FileShare.None, FileOptions.None, preallocationSize)))
- {
- Assert.Equal(initialPreallocationSize, GetActualPreallocationSize(fs)); // it has NOT been changed
- Assert.Equal(initialSize, fs.Length);
- Assert.Equal(mode == FileMode.Append ? initialSize : 0, fs.Position);
- }
- }
-
- [Theory]
- [InlineData(FileMode.Create)]
- [InlineData(FileMode.CreateNew)]
- [InlineData(FileMode.OpenOrCreate)]
- public void WhenFileIsCreatedWithPreallocationSizeSpecifiedThePreallocationSizeIsSet(FileMode mode)
- {
- const long preallocationSize = 123;
-
- using (var fs = new FileStream(GetPathToNonExistingFile(), GetOptions(mode, FileAccess.Write, FileShare.None, FileOptions.None, preallocationSize)))
- {
- // OS might allocate MORE than we have requested
- Assert.True(GetActualPreallocationSize(fs) >= preallocationSize, $"Provided {preallocationSize}, actual: {GetActualPreallocationSize(fs)}");
- Assert.Equal(GetExpectedFileLength(preallocationSize), fs.Length);
- Assert.Equal(0, fs.Position);
- }
- }
-
- [OuterLoop("Might allocate 1 TB file if there is enough space on the disk")]
- // macOS fcntl doc does not mention ENOSPC error: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/fcntl.2.html
- // But depending on the OS version, it might actually return it.
- // Since we don't want to have unstable tests, it's better to not run it on macOS at all.
- [PlatformSpecific(TestPlatforms.Windows | TestPlatforms.Linux)]
- [Theory]
- [InlineData(FileMode.Create)]
- [InlineData(FileMode.CreateNew)]
- [InlineData(FileMode.OpenOrCreate)]
- public void WhenDiskIsFullTheErrorMessageContainsAllDetails(FileMode mode)
- {
- const long tooMuch = 1024L * 1024L * 1024L * 1024L; // 1 TB
-
- string filePath = GetPathToNonExistingFile();
-
- IOException ex = Assert.Throws<IOException>(() => new FileStream(filePath, GetOptions(mode, FileAccess.Write, FileShare.None, FileOptions.None, tooMuch)));
- Assert.Contains(filePath, ex.Message);
- Assert.Contains(tooMuch.ToString(), ex.Message);
-
- // ensure it was NOT created (provided OOTB by Windows, emulated on Unix)
- bool exists = File.Exists(filePath);
- if (exists)
- {
- File.Delete(filePath);
- }
- Assert.False(exists);
- }
-
- [Fact]
- public void WhenFileIsTruncatedWithoutPreallocationSizeSpecifiedThePreallocationSizeIsNotSet()
- {
- const int initialSize = 10_000;
-
- string filePath = GetPathToNonExistingFile();
- File.WriteAllBytes(filePath, new byte[initialSize]);
-
- using (var fs = new FileStream(filePath, GetOptions(FileMode.Truncate, FileAccess.Write, FileShare.None, FileOptions.None, 0)))
- {
- Assert.Equal(0, GetActualPreallocationSize(fs));
- Assert.Equal(0, fs.Length);
- Assert.Equal(0, fs.Position);
- }
- }
-
- [Fact]
- public void WhenFileIsTruncatedWithPreallocationSizeSpecifiedThePreallocationSizeIsSet()
- {
- const int initialSize = 10_000; // this must be more than 4kb which seems to be minimum allocation size on Windows
- const long preallocationSize = 100;
-
- string filePath = GetPathToNonExistingFile();
- File.WriteAllBytes(filePath, new byte[initialSize]);
-
- using (var fs = new FileStream(filePath, GetOptions(FileMode.Truncate, FileAccess.Write, FileShare.None, FileOptions.None, preallocationSize)))
- {
- Assert.True(GetActualPreallocationSize(fs) >= preallocationSize, $"Provided {preallocationSize}, actual: {GetActualPreallocationSize(fs)}");
- // less than initial file size (file got truncated)
- Assert.True(GetActualPreallocationSize(fs) < initialSize, $"initialSize {initialSize}, actual: {GetActualPreallocationSize(fs)}");
- Assert.Equal(GetExpectedFileLength(preallocationSize), fs.Length);
- Assert.Equal(0, fs.Position);
- }
- }
-
- private string GetPathToNonExistingFile()
- {
- string filePath = GetTestFilePath();
-
- if (File.Exists(filePath))
- {
- File.Delete(filePath);
- }
-
- return filePath;
- }
- }
-}
return new FileStream(path, mode);
}
- protected virtual long InitialLength => 0;
-
protected virtual string GetExpectedParamName(string paramName) => paramName;
[Fact]
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);
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);
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);
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);
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);
-<Project Sdk="Microsoft.NET.Sdk">
+<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<IncludeRemoteExecutor>true</IncludeRemoteExecutor>
<Compile Remove="..\**\*.Windows.cs" />
<Compile Remove="..\**\*.Browser.cs" />
<Compile Include="$(CommonPath)Interop\Unix\Interop.Libraries.cs" Link="Interop\Unix\Interop.Libraries.cs" />
- <Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.Stat.cs" Link="Interop\Unix\System.Native\Interop.Stat.cs" />
</ItemGroup>
<ItemGroup Condition="'$(TargetsWindows)' == 'true'">
<Compile Remove="..\**\*.Unix.cs" />
<Compile Include="FileInfo\IsReadOnly.cs" />
<Compile Include="FileInfo\Replace.cs" />
<Compile Include="FileInfo\SymbolicLinks.cs" />
- <Compile Include="FileStream\ctor_options_as.cs" />
+ <Compile Include="FileStream\ctor_options.cs" />
<Compile Include="FileStream\Handle.cs" />
<Compile Include="Directory\GetLogicalDrives.cs" />
<Compile Include="FileStream\LockUnlock.cs" />
<ItemGroup Condition="'$(TargetsUnix)' == 'true'">
<Compile Include="Base\SymbolicLinks\BaseSymbolicLinks.Unix.cs" />
<Compile Include="FileSystemTest.Unix.cs" />
- <Compile Include="FileStream\ctor_options_as.Unix.cs" />
<Compile Include="$(CommonPath)Interop\Unix\Interop.Libraries.cs" Link="Interop\Unix\Interop.Libraries.cs" />
- <Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.Stat.cs" Link="Interop\Unix\System.Native\Interop.Stat.cs" />
+ <Compile Include="FileStream\ctor_options.Unix.cs" />
</ItemGroup>
<ItemGroup Condition="'$(TargetsWindows)' == 'true'">
<Compile Include="Base\SymbolicLinks\BaseSymbolicLinks.Windows.cs" />
<Compile Include="Directory\Delete.Windows.cs" />
<Compile Include="FileSystemTest.Windows.cs" />
- <Compile Include="FileStream\ctor_options_as.Windows.cs" />
+ <Compile Include="FileStream\ctor_options.Windows.cs" />
<Compile Include="FileStream\FileStreamConformanceTests.Windows.cs" />
<Compile Include="Junctions.Windows.cs" />
<Compile Include="RandomAccess\Mixed.Windows.cs" />
<ItemGroup Condition="'$(TargetsBrowser)' == 'true'">
<Compile Include="Base\SymbolicLinks\BaseSymbolicLinks.Unix.cs" />
<Compile Include="FileSystemTest.Browser.cs" />
- <Compile Include="FileStream\ctor_options_as.Browser.cs" />
+ <Compile Include="FileStream\ctor_options.Browser.cs" />
</ItemGroup>
<ItemGroup>
<!-- Rewritten -->
}
}
- // If preallocationSize has been provided for a creatable and writeable file
- if (FileStreamHelpers.ShouldPreallocate(preallocationSize, access, mode))
+ 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));
}
}
}
// 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))
+ if (preallocationSize > 0)
{
Preallocate(fullPath, preallocationSize, fileHandle);
}
{
int errorCode = Marshal.GetLastPInvokeError();
- // we try to mimic the atomic NtCreateFile here:
- // if preallocation fails, close the handle and delete the file
- fileHandle.Dispose();
- Interop.Kernel32.DeleteFile(fullPath);
-
- switch (errorCode)
+ // 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)
{
- case Interop.Errors.ERROR_DISK_FULL:
- throw new IOException(SR.Format(SR.IO_DiskFull_Path_AllocationSize, fullPath, preallocationSize));
- case Interop.Errors.ERROR_FILE_TOO_LARGE:
- throw new IOException(SR.Format(SR.IO_FileTooLarge_Path_AllocationSize, fullPath, preallocationSize));
- default:
- throw Win32Marshal.GetExceptionForWin32Error(errorCode, fullPath);
+ 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));
}
}
}
<data name="Argument_InvalidAppendMode" xml:space="preserve">
<value>Append access can be requested only in write-only mode.</value>
</data>
+ <data name="Argument_InvalidPreallocateAccess" xml:space="preserve">
+ <value>Preallocation size can be requested only in write mode.</value>
+ </data>
+ <data name="Argument_InvalidPreallocateMode" xml:space="preserve">
+ <value>Preallocation size can be requested only for new files.</value>
+ </data>
<data name="Argument_InvalidArgumentForComparison" xml:space="preserve">
<value>Type of argument is not compatible with the generic comparer.</value>
</data>
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.PosixFAdvise.cs">
<Link>Common\Interop\Unix\System.Native\Interop.PosixFAdvise.cs</Link>
</Compile>
- <Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.PosixFAllocate.cs">
- <Link>Common\Interop\Unix\System.Native\Interop.PosixFAllocate.cs</Link>
+ <Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.FAllocate.cs">
+ <Link>Common\Interop\Unix\System.Native\Interop.FAllocate.cs</Link>
</Compile>
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.PRead.cs">
<Link>Common\Interop\Unix\System.Native\Interop.PRead.cs</Link>
}
}
+ if (options.PreallocationSize > 0)
+ {
+ FileStreamHelpers.ValidateArgumentsForPreallocation(options.Mode, options.Access);
+ }
+
FileStreamHelpers.SerializationGuard(options.Access);
_strategy = FileStreamHelpers.ChooseStrategy(
internal static unsafe void SetFileLength(SafeFileHandle handle, long length)
{
+ if (!TrySetFileLength(handle, length, out int errorCode))
+ {
+ throw errorCode == Interop.Errors.ERROR_INVALID_PARAMETER
+ ? new ArgumentOutOfRangeException(nameof(length), SR.ArgumentOutOfRange_FileLengthTooBig)
+ : Win32Marshal.GetExceptionForWin32Error(errorCode, handle.Path);
+ }
+ }
+
+ internal static unsafe bool TrySetFileLength(SafeFileHandle handle, long length, out int errorCode)
+ {
var eofInfo = new Interop.Kernel32.FILE_END_OF_FILE_INFO
{
EndOfFile = length
&eofInfo,
(uint)sizeof(Interop.Kernel32.FILE_END_OF_FILE_INFO)))
{
- int errorCode = Marshal.GetLastPInvokeError();
- if (errorCode == Interop.Errors.ERROR_INVALID_PARAMETER)
- throw new ArgumentOutOfRangeException(nameof(length), SR.ArgumentOutOfRange_FileLengthTooBig);
- throw Win32Marshal.GetExceptionForWin32Error(errorCode, handle.Path);
+ errorCode = Marshal.GetLastPInvokeError();
+ return false;
}
+
+ errorCode = Interop.Errors.ERROR_SUCCESS;
+ return true;
}
internal static unsafe int ReadFileNative(SafeFileHandle handle, Span<byte> bytes, NativeOverlapped* overlapped, out int errorCode)
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;
-
internal static void ValidateArguments(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize, FileOptions options, long preallocationSize)
{
if (path == null)
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)