71

How can I check if IOException is a "Not enough disk space" exception type?

At the moment I check to see if the message matches something like "Not enough disk space", but I know that this won't work if the OS language is not English.

abatishchev
  • 98,240
  • 88
  • 296
  • 433
jotbek
  • 1,479
  • 3
  • 14
  • 22
  • 1
    Why does your code care what the cause of the IOException was? – CodesInChaos Feb 15 '12 at 12:55
  • 3
    I need to tell user when something does not work and that this is the reason and he should clean some disk space. I have external dependencies that operates on file system and get such exception from them. – jotbek Feb 15 '12 at 13:00
  • The exception message already tells the user what the cause is. – CodesInChaos Feb 15 '12 at 13:46
  • 9
    But if you develop english language application, you cannot show message in OS language (e.g. german) when it is not-english language operating system. – jotbek Feb 15 '12 at 13:55

5 Answers5

82

You need to check the HResult and test against ERROR_DISK_FULL (0x70) and ERROR_HANDLE_DISK_FULL (0x27), which can be converted to HResults by OR'ing with 0x80070000.

For .Net Framework 4.5 and above, you can use the Exception.HResult property:

static bool IsDiskFull(Exception ex)
{
    const int HR_ERROR_HANDLE_DISK_FULL = unchecked((int)0x80070027);
    const int HR_ERROR_DISK_FULL = unchecked((int)0x80070070);

    return ex.HResult == HR_ERROR_HANDLE_DISK_FULL 
        || ex.HResult == HR_ERROR_DISK_FULL;
}

For older versions, you can use Marshal.GetHRForException to get back the HResult, but this has significant side-effects and is not recommended:

static bool IsDiskFull(Exception ex)
{
    const int ERROR_HANDLE_DISK_FULL = 0x27;
    const int ERROR_DISK_FULL = 0x70;

    int win32ErrorCode = Marshal.GetHRForException(ex) & 0xFFFF;
    return win32ErrorCode == ERROR_HANDLE_DISK_FULL || win32ErrorCode == ERROR_DISK_FULL;
}

From the MSDN documentation:

Note that the GetHRForException method sets the IErrorInfo of the current thread. This can cause unexpected results for methods like the ThrowExceptionForHR methods that default to using the IErrorInfo of the current thread if it is set.

See also How do I determine the HResult for a System.IO.IOException?

Community
  • 1
  • 1
Justin
  • 84,773
  • 49
  • 224
  • 367
  • 2
    Looks a bit cleaner than Hans's solution. – CodesInChaos Feb 15 '12 at 13:49
  • 3
    On the other hand `Marshal.GetHRForException` having a side-effect is extremely ugly. – CodesInChaos Feb 15 '12 at 13:50
  • I think this is the cleanest soultion you can find. I will wait a little bit more but I think that will mark it as answer. :) Thanks! – jotbek Feb 15 '12 at 13:54
  • 3
    Starting from .Net-Framework 4.5 the getter for Exception.HResult is no longer protected. I think this could replace the GetHRForException call and get rid of the mentioned side effect. – mklein Jun 19 '15 at 08:55
  • 1
    I strongly recommend you do not use GetHRForException. It caused a very strange problem that took us months to understand: https://stackoverflow.com/a/40242031/5844190 – Alsty Oct 25 '16 at 14:04
  • There is a more complete `HRESULT`-specific documentation page now, at https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-erref/705fb797-2175-4a90-b5a3-3918024b10b8. It has all the Win32 codes, converted to `HRESULT` (i.e. with facility `7` in the high word), and result codes from other facilities as well. In the same doc section, there is even a page that explains the bit layout of the `HRESULT` value. – Peter Duniho Mar 13 '19 at 01:12
21

In .NET 4.5, the HResult property getter is now Public, so you do not have to use Marshal.GetHRForException (along with its side affects) anymore.

http://msdn.microsoft.com/en-us/library/system.exception.hresult(v=vs.110).aspx states "Starting with the .NET Framework 4.5, the HResult property's setter is protected, whereas its getter is public. In previous versions of the .NET Framework, both getter and setter are protected"

So you can use Justin's answer, but replace Marshal.GetHRForException(ex) with ex.HResult.

BateTech
  • 5,780
  • 3
  • 20
  • 31
13

Most simple inline solution (min .NET 4.5 and C# 6):

try
{
    //...
}
catch (IOException ex) when ((ex.HResult & 0xFFFF) == 0x27 || (ex.HResult & 0xFFFF) == 0x70)
{
    //...
}
Dominik Palo
  • 2,873
  • 4
  • 29
  • 52
13

Well, it's a bit hacky, but here we go.

First thing to do is to get the HResult from the exception. As it's a protected member, we need a bit of reflection to get the value. Here's an extension method to will do the trick:

public static class ExceptionExtensions
{
    public static int HResultPublic(this Exception exception)
    {
        var hResult = exception.GetType().GetProperties(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance).Where(z => z.Name.Equals("HResult")).First();
        return (int)hResult.GetValue(exception, null);
    }
}

Now, in your catch scope, you can get the HResult:

catch (Exception ex)
{
    int hResult = ex.HResultPublic();
}

From here, you'll have to interpret the HResult. You'll need this link.

We need to get the ErrorCode which is stored in the 16 first bits of the value, so here's some bit operation:

int errorCode = (int)(hResult & 0x0000FFFF);

Now, refer to the list of system error codes and here we are:

ERROR_DISK_FULL
112 (0x70)

So test it using:

switch (errorCode)
{
    case 112:
        // Disk full
}

Maybe there are some "higher level" functions to get all this stuff, but at least it works.

ken2k
  • 48,145
  • 10
  • 116
  • 176
  • 1
    +1, as actually (since .NET 4.5) it is not _that_ messy anymore because `HResult` was promoted `public`. The only thing left is _maybe_ to cache the `GetProperties` call in a static member - that would than make a nice wrapper to have a "backwards compatible" wrapper (provided you use both `Public` and `NonPublic` binding flags - which you did :-)). Albeit, in the face of exception halding, the performance gain of a cached member is probably moot. – Christian.K Dec 22 '15 at 17:07
  • 1
    To make it perfectly clean, I'm casting `Exception.HResult` to my `HResultErrorCodes` enum based on ushort which singlehandedly applies the 0xFFFF mask and removes the need for magic numbers or constants in the calling code. – jnm2 Dec 22 '15 at 17:16
1

System.IOException has a number of derived Exception types, however none of these derived type sound like your exception. You can look at the HResult or the Data property of the exception, perhaps this will have a more specific error code detailed. According to MSDN both those properties are part of that exception type. Just make sure you are try catching the specific exception type, not just the base Exception type.

abatishchev
  • 98,240
  • 88
  • 296
  • 433
AaronHS
  • 1,334
  • 12
  • 29
  • To use HResult I would need to use reflection like here: http://www.dotnetspider.com/forum/101158-Disk-full-C.aspx maybe it's better than to use message but still a hack. I'am not sure where can I find some specification what is in public Data Property in this case. – jotbek Feb 15 '12 at 13:19
  • true, didnt realise it was proteted. – AaronHS Feb 15 '12 at 22:29