-
Notifications
You must be signed in to change notification settings - Fork 5.2k
Include correct HResult in IOException during preallocation when the disk is full
#110281
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
adamsitnik
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@karakasa big thanks for your contribution! Would you like to be interested in addressing my comment?
| try | ||
| { | ||
| using (File.OpenHandle(path, mode: FileMode.Create, access: FileAccess.ReadWrite, preallocationSize: VeryLargeFileSize)) { } | ||
| Assert.Fail("The test should fail due to failure to preallocate a very large file."); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can see that this test fails in the CI. I was able to reproduce it locally and debug it.
The sys-call for setting the pre-allocation size fails here:
Lines 130 to 142 in 06a6ea7
| 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(); |
but the error code I keep getting is 87 (ERROR_INVALID_PARAMETER). We need to dive deeper and find out why it's happening.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can see that before #59338 we were using a different sys-call that was most likely using given error codes (ERROR_DISK_FULL and ERROR_FILE_TOO_LARGE), after switching to SetFileInformationByHandle we no longer get those errors.
So we need to fix the error handling logic and treat Interop.Errors.ERROR_INVALID_PARAMETER as full disk as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can see that before #59338 we were using a different sys-call that was most likely using given error codes (ERROR_DISK_FULL and ERROR_FILE_TOO_LARGE), after switching to
SetFileInformationByHandlewe no longer get those errors.So we need to fix the error handling logic and treat
Interop.Errors.ERROR_INVALID_PARAMETERas full disk as well.
I will address the issue. Thanks for figuring it out!
It seems the syscall no longer fails with FILE_TOO_LARGE and we are unable to tell whether it's because the size is disallowed, or the disk is full. A possible solution is to change the exception message to "the disk is full OR the file size is disallowed."
My original testing code returns INVALID_PARAMETER beucase the allowed file size depends on cluster size. The default 4KB cluster allows a file of ~16TB.
new code
using Microsoft.Win32.SafeHandles;
using System.ComponentModel;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using Windows.Win32;
using Windows.Win32.Storage.FileSystem;
using Windows.Win32.Foundation;
using System.Runtime.Versioning;
public static partial class Program
{
public static unsafe void Main()
{
const long size1T = 1L * 1024 * 1024 * 1024 * 1024;
const long size15T = 15L * 1024 * 1024 * 1024 * 1024;
const long size16Tminus64K = 16L * 1024 * 1024 * 1024 * 1024 - 1024 * 64;
const long size128T = 128L * 1024 * 1024 * 1024 * 1024;
const long size5GB = 5L * 1024 * 1024 * 1024;
const long size12GB = 12L * 1024 * 1024 * 1024;
// On NTFS volume with <1T free space and 4KB cluster size
Console.WriteLine("NTFS: " + GetErrorCode(@"E:\whatever.bin", size1T)); // ERROR_DISK_FULL
Console.WriteLine("NTFS: " + GetErrorCode(@"E:\whatever.bin", size15T)); // ERROR_DISK_FULL
Console.WriteLine("NTFS: " + GetErrorCode(@"E:\whatever.bin", size16Tminus64K)); // ERROR_DISK_FULL
Console.WriteLine("NTFS: " + GetErrorCode(@"E:\whatever.bin", size16Tminus64K + 1)); // ERROR_INVALID_PARAMETER
Console.WriteLine("NTFS: " + GetErrorCode(@"E:\whatever.bin", size128T)); // ERROR_INVALID_PARAMETER
// On an empty 8GB FAT32 volume
Console.WriteLine("FAT32: " + GetErrorCode(@"F:\whatever.bin", size5GB)); // ERROR_DISK_FULL
Console.WriteLine("FAT32: " + GetErrorCode(@"F:\whatever.bin", size12GB)); // ERROR_DISK_FULL
}
private static unsafe int GetErrorCode(string path, long size)
{
try
{
using var hFile = PInvoke.CreateFile(path, (uint)GENERIC_ACCESS_RIGHTS.GENERIC_ALL, FILE_SHARE_MODE.FILE_SHARE_NONE, null, FILE_CREATION_DISPOSITION.CREATE_ALWAYS, 0, null);
FILE_ALLOCATION_INFO alloc;
alloc.AllocationSize = size;
if (PInvoke.SetFileInformationByHandle(hFile, FILE_INFO_BY_HANDLE_CLASS.FileAllocationInfo, &alloc, (uint)sizeof(FILE_ALLOCATION_INFO)))
{
return 0;
}
else
{
return Marshal.GetLastWin32Error();
}
}
finally
{
if (File.Exists(path)) File.Delete(path);
}
}
}We also need to update the document. AFAIK the behavior is consistent with Windows & Linux currently as what #59338 did.
src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Windows.cs
Outdated
Show resolved
Hide resolved
adamsitnik
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, thank you for your contribution @karakasa !
Close #100457