Skip to content

Commit 6115ca2

Browse files
Align preallocationSize behavior (#58726)
Co-authored-by: Stephen Toub <stoub@microsoft.com>
1 parent 9035d94 commit 6115ca2

File tree

12 files changed

+108
-160
lines changed

12 files changed

+108
-160
lines changed

src/libraries/Common/src/Interop/Windows/Kernel32/Interop.FILE_ALLOCATION_INFO.cs

Lines changed: 0 additions & 16 deletions
This file was deleted.

src/libraries/System.IO.FileSystem/tests/FileStream/ctor_options_as.Browser.cs

Lines changed: 0 additions & 16 deletions
This file was deleted.

src/libraries/System.IO.FileSystem/tests/FileStream/ctor_options_as.Unix.cs

Lines changed: 0 additions & 21 deletions
This file was deleted.

src/libraries/System.IO.FileSystem/tests/FileStream/ctor_options_as.Windows.cs

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,16 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System.IO.Pipes;
45
using System.Runtime.InteropServices;
56
using System.Text;
7+
using System.Threading.Tasks;
68
using Xunit;
79

810
namespace System.IO.Tests
911
{
10-
public partial class FileStream_ctor_options_as : FileStream_ctor_options_as_base
12+
public partial class FileStream_ctor_options_as
1113
{
12-
protected override long PreallocationSize => 10;
13-
14-
protected override long InitialLength => 0; // Windows modifies AllocationSize, but not EndOfFile (file length)
15-
16-
private long GetExpectedFileLength(long preallocationSize) => 0; // Windows modifies AllocationSize, but not EndOfFile (file length)
17-
18-
private unsafe long GetActualPreallocationSize(FileStream fileStream)
19-
{
20-
Interop.Kernel32.FILE_STANDARD_INFO info;
21-
22-
Assert.True(Interop.Kernel32.GetFileInformationByHandleEx(fileStream.SafeFileHandle, Interop.Kernel32.FileStandardInfo, &info, (uint)sizeof(Interop.Kernel32.FILE_STANDARD_INFO)));
23-
24-
return info.AllocationSize;
25-
}
26-
2714
[Theory]
2815
[InlineData(@"\\?\")]
2916
[InlineData(@"\??\")]
@@ -36,7 +23,24 @@ public void ExtendedPathsAreSupported(string prefix)
3623

3724
using (var fs = new FileStream(filePath, GetOptions(FileMode.CreateNew, FileAccess.Write, FileShare.None, FileOptions.None, preallocationSize)))
3825
{
39-
Assert.True(GetActualPreallocationSize(fs) >= preallocationSize, $"Provided {preallocationSize}, actual: {GetActualPreallocationSize(fs)}");
26+
Assert.Equal(preallocationSize, fs.Length);
27+
}
28+
}
29+
30+
[Fact]
31+
public async Task PreallocationSizeIsIgnoredForNonSeekableFiles()
32+
{
33+
string pipeName = GetNamedPipeServerStreamName();
34+
string pipePath = Path.GetFullPath($@"\\.\pipe\{pipeName}");
35+
36+
FileStreamOptions options = new() { Mode = FileMode.Open, Access = FileAccess.Write, Share = FileShare.None, PreallocationSize = 123 };
37+
38+
using (var server = new NamedPipeServerStream(pipeName, PipeDirection.In))
39+
using (var clienStream = new FileStream(pipePath, options))
40+
{
41+
await server.WaitForConnectionAsync();
42+
43+
Assert.False(clienStream.CanSeek);
4044
}
4145
}
4246

src/libraries/System.IO.FileSystem/tests/FileStream/ctor_options_as.cs

Lines changed: 57 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System.Linq;
5+
using System.Security.Cryptography;
46
using Xunit;
57

68
namespace System.IO.Tests
@@ -68,6 +70,10 @@ public partial class NoParallelTests { }
6870
[Collection("NoParallelTests")]
6971
public partial class FileStream_ctor_options_as : FileStream_ctor_options_as_base
7072
{
73+
protected override long PreallocationSize => 10;
74+
75+
protected override long InitialLength => 10;
76+
7177
[Fact]
7278
public virtual void NegativePreallocationSizeThrows()
7379
{
@@ -80,57 +86,68 @@ public virtual void NegativePreallocationSizeThrows()
8086
[InlineData(FileMode.Create, 0L)]
8187
[InlineData(FileMode.CreateNew, 0L)]
8288
[InlineData(FileMode.OpenOrCreate, 0L)]
83-
public void WhenFileIsCreatedWithoutPreallocationSizeSpecifiedThePreallocationSizeIsNotSet(FileMode mode, long preallocationSize)
89+
public void WhenFileIsCreatedWithoutPreallocationSizeSpecifiedItsLengthIsZero(FileMode mode, long preallocationSize)
8490
{
8591
using (var fs = new FileStream(GetPathToNonExistingFile(), GetOptions(mode, FileAccess.Write, FileShare.None, FileOptions.None, preallocationSize)))
8692
{
87-
Assert.Equal(0, GetActualPreallocationSize(fs));
8893
Assert.Equal(0, fs.Length);
8994
Assert.Equal(0, fs.Position);
9095
}
9196
}
9297

9398
[Theory]
94-
[InlineData(FileMode.Open, 0L)]
95-
[InlineData(FileMode.Open, 1L)]
96-
[InlineData(FileMode.OpenOrCreate, 0L)]
97-
[InlineData(FileMode.OpenOrCreate, 1L)]
98-
[InlineData(FileMode.Append, 0L)]
99-
[InlineData(FileMode.Append, 1L)]
100-
public void WhenExistingFileIsBeingOpenedWithPreallocationSizeSpecifiedThePreallocationSizeIsNotChanged(FileMode mode, long preallocationSize)
99+
[InlineData(FileMode.Open, 20L)]
100+
[InlineData(FileMode.Open, 5L)]
101+
[InlineData(FileMode.Append, 20L)]
102+
[InlineData(FileMode.Append, 5L)]
103+
public void PreallocationSizeIsIgnoredForFileModeOpenAndAppend(FileMode mode, long preallocationSize)
101104
{
102-
const int initialSize = 1;
105+
const int initialSize = 10;
103106
string filePath = GetPathToNonExistingFile();
104107
File.WriteAllBytes(filePath, new byte[initialSize]);
105-
long initialPreallocationSize;
106108

107-
using (var fs = new FileStream(filePath, GetOptions(mode, FileAccess.Write, FileShare.None, FileOptions.None, 0))) // preallocationSize NOT provided
109+
using (var fs = new FileStream(filePath, GetOptions(mode, FileAccess.Write, FileShare.None, FileOptions.None, preallocationSize)))
108110
{
109-
initialPreallocationSize = GetActualPreallocationSize(fs); // just read it to ensure it's not being changed
111+
Assert.Equal(initialSize, fs.Length); // it has NOT been changed
112+
Assert.Equal(mode == FileMode.Append ? initialSize : 0, fs.Position);
110113
}
114+
}
111115

112-
using (var fs = new FileStream(filePath, GetOptions(mode, FileAccess.Write, FileShare.None, FileOptions.None, preallocationSize)))
116+
[Theory]
117+
[InlineData(FileMode.OpenOrCreate, 20L)] // preallocationSize > initialSize
118+
[InlineData(FileMode.OpenOrCreate, 5L)] // preallocationSize < initialSize
119+
public void WhenExistingFileIsBeingOpenedWithOpenOrCreateModeTheLengthRemainsUnchanged(FileMode mode, long preallocationSize)
120+
{
121+
const int initialSize = 10;
122+
string filePath = GetPathToNonExistingFile();
123+
byte[] initialData = RandomNumberGenerator.GetBytes(initialSize);
124+
File.WriteAllBytes(filePath, initialData);
125+
126+
using (var fs = new FileStream(filePath, GetOptions(mode, FileAccess.ReadWrite, FileShare.None, FileOptions.None, preallocationSize)))
113127
{
114-
Assert.Equal(initialPreallocationSize, GetActualPreallocationSize(fs)); // it has NOT been changed
115-
Assert.Equal(initialSize, fs.Length);
116-
Assert.Equal(mode == FileMode.Append ? initialSize : 0, fs.Position);
128+
Assert.Equal(initialSize, fs.Length); // it was not changed
129+
Assert.Equal(0, fs.Position);
130+
131+
byte[] actualContent = new byte[initialData.Length];
132+
Assert.Equal(actualContent.Length, fs.Read(actualContent));
133+
AssertExtensions.SequenceEqual(initialData, actualContent); // the initial content was not changed
117134
}
118135
}
119136

120137
[Theory]
121138
[InlineData(FileMode.Create)]
122139
[InlineData(FileMode.CreateNew)]
123140
[InlineData(FileMode.OpenOrCreate)]
124-
public void WhenFileIsCreatedWithPreallocationSizeSpecifiedThePreallocationSizeIsSet(FileMode mode)
141+
public void WhenFileIsCreatedWithPreallocationSizeSpecifiedTheLengthIsSetAndTheContentIsZeroed(FileMode mode)
125142
{
126143
const long preallocationSize = 123;
127144

128-
using (var fs = new FileStream(GetPathToNonExistingFile(), GetOptions(mode, FileAccess.Write, FileShare.None, FileOptions.None, preallocationSize)))
145+
using (var fs = new FileStream(GetPathToNonExistingFile(), GetOptions(mode, FileAccess.ReadWrite, FileShare.None, FileOptions.None, preallocationSize)))
129146
{
130-
// OS might allocate MORE than we have requested
131-
Assert.True(GetActualPreallocationSize(fs) >= preallocationSize, $"Provided {preallocationSize}, actual: {GetActualPreallocationSize(fs)}");
132-
Assert.Equal(GetExpectedFileLength(preallocationSize), fs.Length);
147+
Assert.Equal(preallocationSize, fs.Length);
133148
Assert.Equal(0, fs.Position);
149+
150+
AssertFileContentHasBeenZeroed(0, (int)fs.Length, fs);
134151
}
135152
}
136153

@@ -153,7 +170,7 @@ public void WhenDiskIsFullTheErrorMessageContainsAllDetails(FileMode mode)
153170
Assert.Contains(filePath, ex.Message);
154171
Assert.Contains(tooMuch.ToString(), ex.Message);
155172

156-
// ensure it was NOT created (provided OOTB by Windows, emulated on Unix)
173+
// ensure it was NOT created
157174
bool exists = File.Exists(filePath);
158175
if (exists)
159176
{
@@ -163,37 +180,20 @@ public void WhenDiskIsFullTheErrorMessageContainsAllDetails(FileMode mode)
163180
}
164181

165182
[Fact]
166-
public void WhenFileIsTruncatedWithoutPreallocationSizeSpecifiedThePreallocationSizeIsNotSet()
167-
{
168-
const int initialSize = 10_000;
169-
170-
string filePath = GetPathToNonExistingFile();
171-
File.WriteAllBytes(filePath, new byte[initialSize]);
172-
173-
using (var fs = new FileStream(filePath, GetOptions(FileMode.Truncate, FileAccess.Write, FileShare.None, FileOptions.None, 0)))
174-
{
175-
Assert.Equal(0, GetActualPreallocationSize(fs));
176-
Assert.Equal(0, fs.Length);
177-
Assert.Equal(0, fs.Position);
178-
}
179-
}
180-
181-
[Fact]
182-
public void WhenFileIsTruncatedWithPreallocationSizeSpecifiedThePreallocationSizeIsSet()
183+
public void WhenFileIsTruncatedWithPreallocationSizeSpecifiedTheLengthIsSetAndTheContentIsZeroed()
183184
{
184185
const int initialSize = 10_000; // this must be more than 4kb which seems to be minimum allocation size on Windows
185186
const long preallocationSize = 100;
186187

187188
string filePath = GetPathToNonExistingFile();
188-
File.WriteAllBytes(filePath, new byte[initialSize]);
189+
File.WriteAllBytes(filePath, Enumerable.Repeat((byte)1, initialSize).ToArray());
189190

190-
using (var fs = new FileStream(filePath, GetOptions(FileMode.Truncate, FileAccess.Write, FileShare.None, FileOptions.None, preallocationSize)))
191+
using (var fs = new FileStream(filePath, GetOptions(FileMode.Truncate, FileAccess.ReadWrite, FileShare.None, FileOptions.None, preallocationSize)))
191192
{
192-
Assert.True(GetActualPreallocationSize(fs) >= preallocationSize, $"Provided {preallocationSize}, actual: {GetActualPreallocationSize(fs)}");
193-
// less than initial file size (file got truncated)
194-
Assert.True(GetActualPreallocationSize(fs) < initialSize, $"initialSize {initialSize}, actual: {GetActualPreallocationSize(fs)}");
195-
Assert.Equal(GetExpectedFileLength(preallocationSize), fs.Length);
193+
Assert.Equal(preallocationSize, fs.Length);
196194
Assert.Equal(0, fs.Position);
195+
196+
AssertFileContentHasBeenZeroed(0, (int)fs.Length, fs);
197197
}
198198
}
199199

@@ -208,5 +208,16 @@ private string GetPathToNonExistingFile()
208208

209209
return filePath;
210210
}
211+
212+
private static void AssertFileContentHasBeenZeroed(int from, int to, FileStream fs)
213+
{
214+
int expectedByteCount = to - from;
215+
int extraByteCount = 1;
216+
byte[] content = Enumerable.Repeat((byte)1, expectedByteCount + extraByteCount).ToArray();
217+
fs.Position = from;
218+
Assert.Equal(expectedByteCount, fs.Read(content));
219+
Assert.All(content.SkipLast(extraByteCount), @byte => Assert.Equal(0, @byte));
220+
Assert.Equal(to, fs.Position);
221+
}
211222
}
212223
}

src/libraries/System.IO.FileSystem/tests/Net5CompatTests/System.IO.FileSystem.Net5Compat.Tests.csproj

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<Project Sdk="Microsoft.NET.Sdk">
1+
<Project Sdk="Microsoft.NET.Sdk">
22
<PropertyGroup>
33
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
44
<IncludeRemoteExecutor>true</IncludeRemoteExecutor>
@@ -14,7 +14,6 @@
1414
<Compile Remove="..\**\*.Windows.cs" />
1515
<Compile Remove="..\**\*.Browser.cs" />
1616
<Compile Include="$(CommonPath)Interop\Unix\Interop.Libraries.cs" Link="Interop\Unix\Interop.Libraries.cs" />
17-
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.Stat.cs" Link="Interop\Unix\System.Native\Interop.Stat.cs" />
1817
</ItemGroup>
1918
<ItemGroup Condition="'$(TargetsWindows)' == 'true'">
2019
<Compile Remove="..\**\*.Unix.cs" />
@@ -24,8 +23,6 @@
2423
<Compile Include="$(CommonPath)Interop\Windows\Interop.BOOL.cs" Link="Common\Interop\Windows\Interop.BOOL.cs" />
2524
<Compile Include="$(CommonPath)Interop\Windows\Interop.Libraries.cs" Link="Common\Interop\Windows\Interop.Libraries.cs" />
2625
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.CreateFile.cs" Link="Common\Interop\Windows\Interop.CreateFile.cs" />
27-
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.FILE_STANDARD_INFO.cs" Link="Common\Interop\Windows\Interop.FILE_STANDARD_INFO.cs" />
28-
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.GetFileInformationByHandleEx.cs" Link="Common\Interop\Windows\Interop.GetFileInformationByHandleEx.cs" />
2926
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.GetFinalPathNameByHandle.cs" Link="Common\Interop\Windows\Interop.GetFinalPathNameByHandle.cs" />
3027
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.MemOptions.cs" Link="Common\Interop\Windows\Interop.MemOptions.cs" />
3128
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.SECURITY_ATTRIBUTES.cs" Link="Common\Interop\Windows\Interop.SECURITY_ATTRIBUTES.cs" />

src/libraries/System.IO.FileSystem/tests/System.IO.FileSystem.Tests.csproj

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -70,9 +70,7 @@
7070
<ItemGroup Condition="'$(TargetsUnix)' == 'true'">
7171
<Compile Include="Base\SymbolicLinks\BaseSymbolicLinks.Unix.cs" />
7272
<Compile Include="FileSystemTest.Unix.cs" />
73-
<Compile Include="FileStream\ctor_options_as.Unix.cs" />
7473
<Compile Include="$(CommonPath)Interop\Unix\Interop.Libraries.cs" Link="Interop\Unix\Interop.Libraries.cs" />
75-
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.Stat.cs" Link="Interop\Unix\System.Native\Interop.Stat.cs" />
7674
</ItemGroup>
7775
<ItemGroup Condition="'$(TargetsWindows)' == 'true'">
7876
<Compile Include="Base\SymbolicLinks\BaseSymbolicLinks.Windows.cs" />
@@ -88,8 +86,6 @@
8886
<Compile Include="$(CommonPath)Interop\Windows\Interop.BOOL.cs" Link="Common\Interop\Windows\Interop.BOOL.cs" />
8987
<Compile Include="$(CommonPath)Interop\Windows\Interop.Libraries.cs" Link="Common\Interop\Windows\Interop.Libraries.cs" />
9088
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.CreateFile.cs" Link="Common\Interop\Windows\Interop.CreateFile.cs" />
91-
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.FILE_STANDARD_INFO.cs" Link="Common\Interop\Windows\Interop.FILE_STANDARD_INFO.cs" />
92-
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.GetFileInformationByHandleEx.cs" Link="Common\Interop\Windows\Interop.GetFileInformationByHandleEx.cs" />
9389
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.GetFinalPathNameByHandle.cs" Link="Common\Interop\Windows\Interop.GetFinalPathNameByHandle.cs" />
9490
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.MemOptions.cs" Link="Common\Interop\Windows\Interop.MemOptions.cs" />
9591
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.SECURITY_ATTRIBUTES.cs" Link="Common\Interop\Windows\Interop.SECURITY_ATTRIBUTES.cs" />
@@ -103,7 +99,6 @@
10399
<ItemGroup Condition="'$(TargetsBrowser)' == 'true'">
104100
<Compile Include="Base\SymbolicLinks\BaseSymbolicLinks.Unix.cs" />
105101
<Compile Include="FileSystemTest.Browser.cs" />
106-
<Compile Include="FileStream\ctor_options_as.Browser.cs" />
107102
</ItemGroup>
108103
<ItemGroup>
109104
<!-- Rewritten -->

src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Unix.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -324,7 +324,7 @@ private void Init(string path, FileMode mode, FileAccess access, FileShare share
324324
}
325325

326326
// If preallocationSize has been provided for a creatable and writeable file
327-
if (FileStreamHelpers.ShouldPreallocate(preallocationSize, access, mode))
327+
if (FileStreamHelpers.ShouldPreallocate(preallocationSize, access, mode, this))
328328
{
329329
int fallocateResult = Interop.Sys.PosixFAllocate(this, 0, preallocationSize);
330330
if (fallocateResult != 0)

0 commit comments

Comments
 (0)