diff --git a/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.FILE_END_OF_FILE_INFO.cs b/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.FILE_END_OF_FILE_INFO.cs new file mode 100644 index 00000000000000..5683bb20653c08 --- /dev/null +++ b/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.FILE_END_OF_FILE_INFO.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. + +internal static partial class Interop +{ + internal static partial class Kernel32 + { + // From FILE_INFO_BY_HANDLE_CLASS + // Use for SetFileInformationByHandle + internal const int FileEndOfFileInfo = 6; + + internal struct FILE_END_OF_FILE_INFO + { + internal long EndOfFile; + } + } +} diff --git a/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.SetEndOfFile.cs b/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.SetEndOfFile.cs deleted file mode 100644 index 7ba5014d62feb8..00000000000000 --- a/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.SetEndOfFile.cs +++ /dev/null @@ -1,14 +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 Microsoft.Win32.SafeHandles; -using System.Runtime.InteropServices; - -internal static partial class Interop -{ - internal static partial class Kernel32 - { - [DllImport(Libraries.Kernel32, SetLastError = true)] - internal static extern bool SetEndOfFile(SafeFileHandle hFile); - } -} diff --git a/src/libraries/System.IO.FileSystem/tests/ManualTests/ManualTests.cs b/src/libraries/System.IO.FileSystem/tests/ManualTests/ManualTests.cs index 0e8ae5b968044c..c363e654e73ac9 100644 --- a/src/libraries/System.IO.FileSystem/tests/ManualTests/ManualTests.cs +++ b/src/libraries/System.IO.FileSystem/tests/ManualTests/ManualTests.cs @@ -75,5 +75,66 @@ public static void Throw_FileStreamDispose_WhenRemoteMountRunsOutOfSpace() destinationStream.Dispose(); }); } + + + const long InitialFileSize = 1024; + + [ConditionalFact(nameof(ManualTestsEnabled))] + [PlatformSpecific(TestPlatforms.Windows)] + public static void SetLength_DoesNotAlterPositionWhenNativeCallFails() + { + /* This test verifies that Position is not altered when SetLength fails when a "disk out of space" error occurs. + + Setup environment to have a drive with less than 1k available space: + - Create an 8mb fixed size VHD. + - Open Computer Management -> Storage -> Disk Management + - Follow these instructions: + https://docs.microsoft.com/en-us/windows-server/storage/disk-management/manage-virtual-hard-disks + + - Restrict the space available in the VHD. + - Create a 512 bytes quota in the VHD created above using cmd: + fsutil quota modify E: 512 512 SYSTEM + fsutil quota modify E: 512 512 YourUser + + - Run the test. If configured correctly, the SetLength operation should fail at least once. + */ + + using FileStream fs = File.Open("E:/dummy_file.txt", FileMode.OpenOrCreate); + + // Position was less than new Length; should remain the same. + fs.Seek(0, SeekOrigin.Begin); + VerifySetLength(fs); + Assert.Equal(0, fs.Position); + Assert.True(fs.Position < fs.Length); + + // Position was larger than new Length; should be adjusted to the Length. + fs.Seek(InitialFileSize + 1, SeekOrigin.Begin); + VerifySetLength(fs); + Assert.Equal(fs.Length, fs.Position); + } + + private static void VerifySetLength(FileStream fs) + { + long originalPosition = fs.Position; + bool success = false; + long size = InitialFileSize; + + while (!success) + { + try + { + Console.WriteLine($"Attempting to write {size} bytes..."); + fs.SetLength(size); + Console.WriteLine("Success!"); + success = true; + } + catch (IOException) + { + Console.WriteLine("Failed."); + Assert.Equal(originalPosition, fs.Position); + size = (long)(size * 0.9); + } + } + } } } 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 4ac94b06136feb..561518834ad44d 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 @@ -1307,6 +1307,9 @@ Common\Interop\Windows\Kernel32\Interop.ExpandEnvironmentStrings.cs + + Common\Interop\Windows\Kernel32\Interop.FILE_END_OF_FILE_INFO.cs + Common\Interop\Windows\Kernel32\Interop.FILE_STANDARD_INFO.cs @@ -1475,8 +1478,8 @@ Common\Interop\Windows\Kernel32\Interop.SetCurrentDirectory.cs - - Common\Interop\Windows\Kernel32\Interop.SetEndOfFile.cs + + Common\Interop\Windows\Kernel32\Interop.SetFileInformationByHandle.cs Common\Interop\Windows\Kernel32\Interop.SetFilePointerEx.cs diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/FileStream.Windows.cs b/src/libraries/System.Private.CoreLib/src/System/IO/FileStream.Windows.cs index 3cfdf8de38f0b9..85b6e7794fae26 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/FileStream.Windows.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/FileStream.Windows.cs @@ -379,28 +379,31 @@ private void SetLengthInternal(long value) // We absolutely need this method broken out so that WriteInternalCoreAsync can call // a method without having to go through buffering code that might call FlushWrite. - private void SetLengthCore(long value) + private unsafe void SetLengthCore(long value) { Debug.Assert(value >= 0, "value >= 0"); - long origPos = _filePosition; - VerifyOSHandlePosition(); - if (_filePosition != value) - SeekCore(_fileHandle, value, SeekOrigin.Begin); - if (!Interop.Kernel32.SetEndOfFile(_fileHandle)) + + var eofInfo = new Interop.Kernel32.FILE_END_OF_FILE_INFO + { + EndOfFile = value + }; + + if (!Interop.Kernel32.SetFileInformationByHandle( + _fileHandle, + Interop.Kernel32.FileEndOfFileInfo, + &eofInfo, + (uint)sizeof(Interop.Kernel32.FILE_END_OF_FILE_INFO))) { int errorCode = Marshal.GetLastWin32Error(); if (errorCode == Interop.Errors.ERROR_INVALID_PARAMETER) throw new ArgumentOutOfRangeException(nameof(value), SR.ArgumentOutOfRange_FileLengthTooBig); throw Win32Marshal.GetExceptionForWin32Error(errorCode, _path); } - // Return file pointer to where it was before setting length - if (origPos != value) + + if (_filePosition > value) { - if (origPos < value) - SeekCore(_fileHandle, origPos, SeekOrigin.Begin); - else - SeekCore(_fileHandle, 0, SeekOrigin.End); + SeekCore(_fileHandle, 0, SeekOrigin.End); } }