Skip to content

Conversation

@karakasa
Copy link
Contributor

Close #100457

@ghost ghost added the area-System.IO label Nov 30, 2024
@dotnet-policy-service dotnet-policy-service bot added the community-contribution Indicates that the PR has been added by a community member label Nov 30, 2024
Copy link
Member

@adamsitnik adamsitnik left a 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.");
Copy link
Member

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:

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.

Copy link
Member

@adamsitnik adamsitnik Jan 8, 2025

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.

https://github.com/tmds/runtime/blob/ec9412be43707ca9450afd6519ded31b801ce02a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/FileStreamHelpers.Windows.cs#L129-L130

So we need to fix the error handling logic and treat Interop.Errors.ERROR_INVALID_PARAMETER as full disk as well.

Copy link
Contributor Author

@karakasa karakasa Jan 9, 2025

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.

https://github.com/tmds/runtime/blob/ec9412be43707ca9450afd6519ded31b801ce02a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/FileStreamHelpers.Windows.cs#L129-L130

So we need to fix the error handling logic and treat Interop.Errors.ERROR_INVALID_PARAMETER as 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.

@adamsitnik adamsitnik self-assigned this Jan 9, 2025
@adamsitnik adamsitnik added the needs-author-action An issue or pull request that requires more info or actions from the author. label Jan 9, 2025
@dotnet-policy-service dotnet-policy-service bot removed the needs-author-action An issue or pull request that requires more info or actions from the author. label Jan 9, 2025
Copy link
Member

@adamsitnik adamsitnik left a 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 !

@adamsitnik adamsitnik added this to the 10.0.0 milestone Jan 10, 2025
@adamsitnik adamsitnik merged commit 0df31bc into dotnet:main Jan 10, 2025
137 of 139 checks passed
@karakasa karakasa deleted the issue-100457 branch January 10, 2025 13:01
@github-actions github-actions bot locked and limited conversation to collaborators Feb 10, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-System.IO community-contribution Indicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

IOException thrown when disk is full does not have correct hresult

2 participants