Getting File System Information

How do I use WinAPI functions like GetVolumeInformation to get information about my system?

Note:There's a demo application available for this Articel

Now and then a user comes up with a question that intrigues me so much that I end up creating a full-blown application as well as article around the question. Such is the case with this article.

A user sent me an e-mail asking how to employ the WinAPI call GetVolumeInformation. I knew about this function, but had never really used it. But when I referenced the function in the help file, which I was supposed to use as material to answer the original question, I ended up following the various branches of hyperlinks off of the topic, struck by the thought that with a couple of other functions, I could create a program that could provide me with practically instant information about my file system. And that's what I did. Figure 1 shows the program in action.

Picture of VOL_INFO.EXE

The information that's displayed is the result of three WinAPI calls. Table 1 describes what was used and for what purpose:

Function Usage
GetVolumeInformation This handy function fills some variables passed as formal parameters to give you information such as the disk volume name, the file system employed, and various things the drive supports such as compression and long file names.
GetDiskFreeSpace GetDiskFreeSpace fills four variables passed as formal parameters which provide information on the following: Sectors/Cluster, Bytes/Cluster, Free Clusters, and Total Clusters. A little arithmetic will get you the total and free number of megabytes on the hard disk.
GetDriveType This returns a DWORD (4-byte assembler symbol that can be read as a short but is typically used as a bit array) that indicates the type of drive of the drive passed to it as a formal parameter.

These functions provide the most common information about your file system and the drive for which you want to get information. But since they're WinAPI calls, it's not quite readily apparent how to use them.

The WinAPI Dilemma

If you've followed this column for a while, and have read my articles dealing with WinAPI calls, you know how I feel about it. I have a love-hate relationship with the WinAPI. I love it because there's so much buried within it, yet I hate it, because it's big and employing its functions is not the easiest thing because of the type conversions and C language conventions. But for low-level Windows stuff, the WinAPI is the way to go. Remember, the VCL is pretty much one big wrapper for the WinAPI.

I've said the following before as well: Be prepared to have some references handy when using the WinAPI. Specifically, these are the WINDOWS.PAS WinAPI wrapper file and the online help. The help file is very important because it will give you explanations of the various formal parameters and return values (if any) for the function. It will also tell you what structures you might need to initialize prior to calling the function. You'll want to have the WINDOWS.PAS file on hand because when you look up function in the help file, you'll notice that it is described in C/C++ conventions. What you have to do is locate the function in WINDOWS.PAS and see what the Object Pascal/Delphi convention is for calling the function. For example, a WinAPI function may define one of its formal parameters as type LPSTR or LPCSTR. If you didn't have WINDOWS.PAS on hand, you'd never know these types translate to PChar. That said, let's look at how I've employed the functions I've used for getting file system information.

Getting at the Information

I used a single method in the main form of the program to get the volume and file system information to display in the various labels and the memo on the main form. Here's a listing of the method; below it, we'll discuss particulars.

procedure TForm1.GetVolInfo;
  //Volume Information Variables
  nVNameSer   : PDWORD;
  drv         : String;
  pVolName    : PChar;
  maxCmpLen   : DWord;
  I           : Integer;
  pFSBuf       : PChar;

  //Drive Information Variables;
  dType       : TDrvType;
  TotCls      : DWord;

  //initialize vars
  drv := DriveComboBox1.Drive + ':\';
  GetMem(pVolName, MAX_PATH);
  GetMem(pFSBuf, MAX_PATH);
  GetMem(nVNameSer, MAX_PATH);

  //Do some preliminary preparation stuff

  //Now, get the volume information
  GetVolumeInformation(PChar(drv), pVolName, MAX_PATH, nVNameSer,
                       maxCmpLen, FSSysFlags, pFSBuf, MAX_PATH);

  //Get descriptions for File System Flags
  for I := 0 to 5 do begin
    //do an AND bitwise operation to see if I is in the mask
    if ((FSSysFlags AND I) <> 0) then
      case I of
        Ord(fsCaseIsPreserved)     :
          Memo1.Lines.Add('...preserves case with file names');
        Ord(fsCaseSensitive)       :
          Memo1.Lines.Add('...supports case sensitive file names');
        Ord(fsUnicodeStoredOnDisk) :
          Memo1.Lines.Add('...stores Unicodes as on disk');
        Ord(fsPersistentAcls)      :
          Memo1.Lines.Add('...preserves and enforces ACLs');
        Ord(fsFileCompression)     :
          Memo1.Lines.Add('...supports file-based compression');
        Ord(fsVolumeIsCompressed)  :
          Memo1.Lines.Add('...resides on a compressed volume');

  //determine if system supports long file names
  if (maxCmpLen > 8.3) then
    Memo1.Lines.Add('...supports long file names');

  Label6.Caption := StrPas(pVolName);
  Label3.Caption := IntToStr(nVNameSer^);
  Label4.Caption := StrPas(pFSBuf);

  //Get the Drive Type information
  dType := TDrvType(GetDriveType(PChar(drv)));
  case dType of
    dtNotDetermined : Label10.Caption := 'Unable to Determine';
    dtNonExistent   : Label10.Caption := 'Does not exist';
    dtRemoveable    : Label10.Caption := 'Removable Drive (Floppy)';
    dtFixed         : Label10.Caption := 'Fixed Disk';
    dtRemote        : Label10.Caption := 'Remote or Network Drive';
    dtCDROM         : Label10.Caption := 'CD-ROM Drive';
    dtRamDrive      : Label10.Caption := 'RAM Drive';

  //Get the total and free space on selected drive and
  //display in MBs
  GetDiskFreeSpace(PChar(drv), SectPerCls, BytesPerCls, FreeCls, TotCls);
  Label11.Caption := FormatFloat('0.00', (SectPerCls * BytesPerCls *
                                          TotCls)/1000000) + ' MB';
  Label12.Caption := FormatFloat('0.00', (SectPerCls * BytesPerCls *
                                          FreeCls)/1000000) + ' MB';

  //Get rid of pointer resources
  FreeMem(pVolName, MAX_PATH);
  FreeMem(pFSBuf, MAX_PATH);
  FreeMem(nVNameSer, MAX_PATH);

The first thing I did in the method was to initialize the variables that required initialization before usage. Whenever you use pointers of any type, they must be initialized and the system must be notified of how much memory will need to be reserved. For PChar this is a simple process of calling GetMem. After you've finished using a pointer, make sure to call FreeMem to release the resources used by the pointer.

Continuing on, after I initialized the variables, I made the call to GetVolumeInformation. WINDOWS.PAS declares the function as follows:

function GetVolumeInformation(lpRootPathName: PChar;
                              lpVolumeNameBuffer: PChar; 
                              nVolumeNameSize: DWORD; 
                              lpVolumeSerialNumber: PDWORD;
                          var lpMaximumComponentLength, 
                              lpFileSystemFlags: DWORD;
                              lpFileSystemNameBuffer: PChar; 
                              nFileSystemNameSize: DWORD): 
                              BOOL; stdcall;

As you can see there are quite a few formal parameters you have to fill in order to make this call. The first parameter, lpRootName, is a null-terminated string that holds the root directory of the drive or volume you want to get information on. As you can see in the code above, I've typecasted the value of the drv string variable, which is merely the value of the Drive property of a TDriveComboBox, plus a semi-colon and backslash, when making the call to GetVolumeInformation.

The second and third parameters have to do with the volume name. lpVolumeNameBuffer is a PChar that will be loaded with the name of the volume, and nVolumeNameSize is the size of the buffer. These two formal parameters are similar to the lpFileSystemNameBuffer and nFileSystemNameSize parameters in that they describe a destination buffer along with its size. Notice that I initialized both pointers' sizes to the numeric constant MAX_PATH and also used this value for nVolumeNameSize and nFileSystemNameSize. MAX_PATH's value is 260, which is probably overkill, but I wanted to use a common, globally defined system constant for initializing the size as opposed to a hard-coded value. In fact, you'll often find the Win32 help file stating that you should use a predefined constant to avoid differences in later versions of the compiler.

lpVolumeSerialNumber is the serial number of the disk on which the volume resides. This is a pointer to a DWORD, so you'll notice that I also initialized memory for it in addition to lpVolumeNameBuffer and lpFileSystemName with GetMem. lpMaximumComponentLength is a parameter you pass by reference; hence the var signfication associated with it. The number that gets returned is maximum file length of a file component, the characters between the backslashes of a path listing.

Finally, lpFileSystemFlags is a DWORD (four-byte Assembler type that is for all intents and purposes the same as a Short) that acts as a bit mask for specific system information. The way that works is there are predefined constants associated in the set of File System Flags. To determine if a particular flag is set in the bit mask, you do a bitwise AND against the flag value's bits. If the return value is one, then the bit is set and the flag is true. There are six file system flags. So in the code, I perform a loop to test each bit position. If a bit is set, I display some text in a memo box. Study the code above to see how I do this. In a nutshell, each successive bit represents a certain file system flag as defined in the online help. If a bit is set, I basically translate this into its associated definition and display it. (BTW, a big thanks goes to Steve Schafer of TeamBorland on CompuServe for his help with the bit mask operations.)


The calls to GetDriveType and GetDiskFreeSpace were far easier to make than GetVolumeInformation. With respect to the call to GetDriveType, notice that in the code listing above I enclosed it in a typecast of TDrvType. This was a custom enumerated type that I declared that held the definitions of the possible drive types that could be encountered when using this function. By performing the typecast, I ensure that the return type, which is a DWORD, will conform to one of my descriptors.

GetDiskFreeSpace was even easier to call than any of the other functions employed by my application. All you do with it is pass the root directory name and four DWORD variables representing the following: Sectors/Cluster, Bytes/Cluster, Free Clusters, and Total Clusters, respectively. Then with a little arithmetic (as shown above) you can get the total and free space available on current volume. That's a snap.

Will you ever use these functions? Probably not very often. However, for getting some quick information about your file system, these are the functions you use. Yes, the Windows API is a bit cumbersome, but hey! this is our environment of choice. And my hunch is, the more you know about it, the better your understanding will be, and the more effective you'll be at writing Windows applications.