INFO: Understanding and Using GetDiskFreeSpace and GetDiskFreeSpaceEx

ID: Q231497


The information in this article applies to:


SUMMARY

Win32 programs use GetDiskFreeSpaceEx or GetDiskFreeSpace to determine the total size of a volume and how much free space can be allocated by the caller. Because some Windows platforms have features such as enforceable disk quotas while others don't, applications that use these APIs must interpret the returned sizes correctly. In addition, because most Windows platforms support volumes larger than 4 gigabytes (GB), Win32 programs must use 64-bit integer math with these APIs. This article explains how to use GetDiskFreeSpaceEx and GetDiskFreeSpace in a way that works on all Win32 platforms.


MORE INFORMATION

Generally, Win32 programs should use GetDiskFreeSpaceEx to determine the total size of a volume and the amount of free space that the caller can use. The one case where this isn't possible is when the program runs on retail Windows 95 (build 950.6) because GetDiskFreeSpaceEx was introduced to Windows 95 in OEM Service Release 2 (OSR2).

If your program can run on retail Windows 95, you should not directly call GetDiskFreeSpaceEx because this function is not implemented in retail Windows 95, and calling it directly will prevent your program from loading. Instead, you should dynamically link to it via GetProcAddress. If the returned pointer is non-NULL, then your application can safely call GetDiskFreeSpaceEx through the pointer; if the returned pointer is NULL, then you should revert to GetDiskFreeSpace. The example code later in this article demonstrates how to do this.

Drive and UNC Name Parameter

GetDiskFreeSpace on Windows 95 and 98 requires a trailing backslash for both drive letters and UNC names. Although Windows NT does not require the trailing backslash, you should add one if your program can run on both platforms.

Understanding Returned Sizes

On all versions of Windows 95 and Windows 98, GetDiskFreeSpace returns a maximum total size and maximum free size of 2GB. For example, if you have a 6GB volume and 5GB are free, GetDiskFreeSpace will report that the drive's total size is 2GB and 2GB are free. This limitation originated because the first version of Windows 95 only supports volumes of up to 2GB in size. Windows 95 OSR2 and later versions, including Windows 98, support volumes larger than 4GB. GetDiskFreeSpaceEx does not have this 2GB limitation, the major reason why it is preferred over GetDiskFreeSpace.

On Windows NT and Windows 2000, GetDiskFreeSpace is not limited to 2GB; it will report the full size of the drive and the full amount of free space. Windows 2000 supports disk quotas. When quota hard limits are being enforced, GetDiskFreeSpace will return the quota size for the user as the total disk space and will return the user's remaining unused quota for the free space. This is done so that setup programs can determine how much disk space they can actually use at the time of installation. GetDiskFreeSpaceEx will return values subject to enforced quota limits also, but specifically returns the number of bytes available to the user who is running the program.

Using Returned Sizes

GetDiskFreeSpaceEx returns three 64-bit values; Win32 programs should be careful to retain all 64 bits in order to properly handle drive sizes larger than 4GB. To determine how much disk space you can use, use the lpFreeBytesAvailableToCaller member because it takes into account disk quotas, to which the program's user may be limited.

GetDiskFreeSpace returns four 32-bit values that Win32 programs must multiply together to get the total size of the volume and the free space on the volume. While it seems understandable to use 32-bit integer multiply operations to get the sizes, doing so will lead to incorrect results when the drive is larger than 2GB. The proper way to multiply the returned values of GetDiskFreeSpace is to use 64-bit math.

Sample Code

The following sample code demonstrates how to use GetDiskFreeSpaceEx and GetDiskFreeSpace on all Windows platforms. Important elements of the code include:


   /*
      Determines the amount of free space available for the caller.
      Runs on Windows 95 retail and later, and on Windows 4.0 and later.  
      Uses GetDiskFreeSpaceEx if available, otherwise reverts to
      GetDiskFreeSpace.

      To determine the amount of available space correctly:

       * Use 64-bit math with the return values of both GetDiskFreeSpace
         and GetDiskFreeSpaceEx so that you can determine the sizes of
         volumes that are larger than 2GB.

      Programs that need to determine how much free space the current user
      can have (such as whether there is enough space to complete an
      installation) have an additional requirement:

       * Use the lpFreeBytesAvailableToCaller value from
         GetDiskFreeSpaceEx rather than lpTotalNumberOfFreeBytes.  
         This is because Windows 2000 has disk quota management that
         administrators may use to limit the amount of disk space that
         users may use.
   */ 


   #include <windows.h>
   #include <stdio.h>


   typedef BOOL (WINAPI *P_GDFSE)(LPCTSTR, PULARGE_INTEGER, 
                                  PULARGE_INTEGER, PULARGE_INTEGER);

   void main (int argc, char **argv)
   {
      BOOL  fResult;

      char  *pszDrive  = NULL,
             szDrive[4];

      DWORD dwSectPerClust,
            dwBytesPerSect,
            dwFreeClusters,
            dwTotalClusters;

      P_GDFSE pGetDiskFreeSpaceEx = NULL;

      unsigned __int64 i64FreeBytesToCaller,
                       i64TotalBytes,
                       i64FreeBytes;

      /*
         Command line parsing.

         If the drive is a drive letter and not a UNC path, append a 
         trailing backslash to the drive letter and colon.  This is 
         required on Windows 95 and 98.
      */ 
      if (argc != 2)
      {
         printf ("usage:  %s <drive|UNC path>\n", argv[0]);
         printf ("\texample:  %s C:\\\n", argv[0]);
         return;
      }

      pszDrive = argv[1];

      if (pszDrive[1] == ':')
      {
         szDrive[0] = pszDrive[0];
         szDrive[1] = ':';
         szDrive[2] = '\\';
         szDrive[3] = '\0';

         pszDrive = szDrive;
      }

      /*
         Use GetDiskFreeSpaceEx if available; otherwise, use
         GetDiskFreeSpace.

         Note: Since GetDiskFreeSpaceEx is not in Windows 95 Retail, we
         dynamically link to it and only call it if it is present.  We 
         don't need to call LoadLibrary on KERNEL32.DLL because it is 
         already loaded into every Win32 process's address space.
      */ 
      pGetDiskFreeSpaceEx = (P_GDFSE)GetProcAddress (
                               GetModuleHandle ("kernel32.dll"),
                                                "GetDiskFreeSpaceExA");
      if (pGetDiskFreeSpaceEx)
      {
         fResult = pGetDiskFreeSpaceEx (pszDrive,
                                 (PULARGE_INTEGER)&i64FreeBytesToCaller,
                                 (PULARGE_INTEGER)&i64TotalBytes,
                                 (PULARGE_INTEGER)&i64FreeBytes);
         if (fResult)
         {
            printf ("\n\nGetDiskFreeSpaceEx reports\n\n");
            printf ("Available space to caller = %I64u MB\n",
                    i64FreeBytesToCaller / (1024*1024));
            printf ("Total space               = %I64u MB\n",
                    i64TotalBytes / (1024*1024));
            printf ("Free space on drive       = %I64u MB\n",
                    i64FreeBytes / (1024*1024));
         }
      }
      else
      {
         fResult = GetDiskFreeSpace (pszDrive, 
                                     &dwSectPerClust,
                                     &dwBytesPerSect, 
                                     &dwFreeClusters,
                                     &dwTotalClusters);
         if (fResult)
         {
            /* force 64-bit math */ 
            i64TotalBytes = (__int64)dwTotalClusters * dwSectPerClust *
                              dwBytesPerSect;
            i64FreeBytes = (__int64)dwFreeClusters * dwSectPerClust *
                              dwBytesPerSect;

            printf ("GetDiskFreeSpace reports\n\n");
            printf ("Free space  = %I64u MB\n", 
                    i64FreeBytes / (1024*1024));
            printf ("Total space = %I64u MB\n", 
                    i64TotalBytes / (1024*1024));
         }
      }

      if (!fResult)
         printf ("error: %lu:  could not get free space for \"%s\"\n",
                 GetLastError(), argv[1]);
   } 

Notes on 64-bit Integer Math

Microsoft Visual C++ versions 4.0 and later support a 64-bit integer type called __int64. The compiler generates code to do the 64-bit math because the Intel x86 family of microprocessors supports 8-bit, 16-bit, and 32-bit integer math, but not 64-bit integer math.

To perform a 64-bit integer multiply, one of the arguments must be 64-bit; the other can be either 32-bit or 64-bit. When functions such as GetDiskFreeSpace return only 32-bit integer values that will be multiplied together but you need to have a 64-bit integer to contain the product, cast one of the values to an __int 64 as follows:

i64TotalBytes = (__int64)dwTotalClusters * dwSectPerClust * 
                      dwBytesPerSect; 
The first multiply is of a 64-bit integer with a 32-bit integer; the result is a 64-bit integer, which is then multiplied by another 32-bit integer, resulting in a 64-bit product.

Many Win32 API functions that take a 64-bit quantity do so as two separate 32-bit quantities. Others, such as QueryPerformanceCounter, take a single 64-bit quantity. The LARGE_INTEGER union type defined in the Platform SDK WINNT.H header file manages these differing ways to handle 64-bit integers. There's a corresponding ULARGE_INTEGER for unsigned large integers.

The LARGE_INTEGER union consists of a 64-bit __int64 member (QuadPart) and two 32-bit values (HighPart and LowPart). Each of the two 32-bit values is one-half of the 64-bit integer. The HighPart member is a signed long integer, while the LowPart is an unsigned long integer. Since the LARGE_INTEGER.QuadPart member is an __int64, you can easily intermix LARGE_INTEGER variables with __int64 variables. To perform integer math with LARGE_INTEGER variables, always use the QuadPart member to treat the LARGE_INTEGER as the single 64-bit value it represents. Use the 32-bit HighPart and LowPart members when you must pass a LARGE_INTEGER to a function in two 32-bit parts.

An equivalent to the above example using LARGE_INTEGERs instead of __int64 variables is:

liTotalBytes.QuadPart = (__int64)dwTotalClusters * dwSectPerClust
                          * dwBytesPerSect; 

Additional query words: available drive volume unused setup empty


Keywords          : kbAPI kbFileIO kbKernBase kbSDKPlatform kbSDKWin32 
Version           : winnt:3.51,4.0,4.0 SP4
Platform          : winnt 
Issue type        : kbinfo 

Last Reviewed: May 21, 1999