October 20th, 2020

How do I get from a file path to the volume that holds it?

Say you have the path to a file and you want to access the volume that the file resides on.

Warning: All error checking is removed for expository purposes.

The first step on our journey is getting from the path to the volume mount point. This tells us where the root of the volume got inserted into the namespace.

TCHAR volumePath[MAX_PATH]; // for expository purposes
GetVolumePathName(filePath, volumePath, ARRAYSIZE(volumePath));

This information might be useful in its own right, but for us, it’s just a stepping stone to the next piece of information: The volume name.

TCHAR volumeName[MAX_PATH]; // for expository purposes
GetVolumeNameForVolumeMountPoint(volumePath, volumeName, ARRAYSIZE(volumeName));

The volume name is returned in the form \\?\Volume{guid}\, with a trailing backslash. Note that this call will fail if the path is not a local drive.

Now things get weird.

If you pass that path to the CreateFile function with the trailing backslash intact, then you are opening a handle to the root directory of the volume.

If you pass that path to the CreateFile function with the trailing backslash removed, then you are opening a handle to the volume itself.

Depending on which operation you want to perform on the volume, you either must have or must not have that trailing backslash.

In our case, we want to open a handle to the volume itself, so we need to remove that trailing backslash. The call to CreateFile looks like this:

HANDLE handle = CreateFile(volumeNameWithoutTrailingBackslash,
    0, /* no special access requested */
    FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
    nullptr, /* no custom security */
    OPEN_EXISTING,
    FILE_FLAG_BACKUP_SEMANTICS,
    nullptr); /* template */

Volume query operations do not require any specific level of access, so we’ll ask for no special access. Which is good, because regular non-elevated code doesn’t really have much in the way of special access to volumes. You won’t able to get GENERIC_READ, much less GENERIC_WRITE. (You’ll be able to get FILE_READ_ATTRIBUTES, if that’s any consolation.)

You also need to request backup semantics in order to open a volume.

We can put all of this together into a function called, say, Get­Volume­Handle­For­File. For RAII, I’m going to use wil.

wil::unique_hfile GetVolumeHandleForFile(PCWSTR filePath)
{
  wchar_t volumePath[MAX_PATH];
  THROW_IF_WIN32_BOOL_FALSE(GetVolumePathName(filePath,
                                volumePath, ARRAYSIZE(volumePath)));

  wchar_t volumeName[MAX_PATH];
  THROW_IF_WIN32_BOOL_FALSE(GetVolumeNameForVolumeMountPoint(volumePath,
                                volumeName, ARRAYSIZE(volumeName)));

  auto length = wcslen(volumeName);
  if (length && volumeName[length - 1] == L'\\')
  {
    volumeName[length - 1] = L'\0';
  }

  wil::unique_hfile result{ CreateFile(volumeName, 0,
                FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
                nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr) };
  THROW_LAST_ERROR_IF(!result);
  return result;
}

Now that you have the volume handle, you can ask the volume for information. We’ll look at that next time.

Topics
Code

Author

Raymond has been involved in the evolution of Windows for more than 30 years. In 2003, he began a Web site known as The Old New Thing which has grown in popularity far beyond his wildest imagination, a development which still gives him the heebie-jeebies. The Web site spawned a book, coincidentally also titled The Old New Thing (Addison Wesley 2007). He occasionally appears on the Windows Dev Docs Twitter account to tell stories which convey no useful information.

6 comments

Discussion is closed. Login to edit/delete existing comments.

  • Mystery Man

    The “GetVolumePathNameW function” page on Microsoft Docs looks damaged. Its examples are missing too.

  • Dan Bugglin

    I've tried to do this in .NET Framework before, and was surprised to find there seems to be no way to get the volume mount point for an arbitrary path, and DriveInfo only supports drive letters. I ended up wrapping some WinAPI calls with PInvoke.

    I used GetVolumePathName, but then I jumped right to GetVolumeInformation rather than getting the actual volume name and opening a handle. I'll be interested in seeing what you're doing and...

    Read more
  • 紅樓鍮

    Are the Windows people still not using std::array in place of C-style arrays?

    • Daniel Sturm

      If you can come up with a single advantage in the given code of using std::array instead of a normal array I’m all ears.

      Otherwise you simply make the code more complicated for no good reason. Most people would think this is not a good thing even if it allows you to play with a new toy.

      • 紅樓鍮

        you can call on an array to get its size

        I don't think is in any way more complicated than C-style arrays; it's the converse in fact. It has none of the surprising behaviors (decaying, not copy constructible/assignable) that C-style arrays have. I think most people will agree is more comprehensible than which involves both decaying and a non-standard macro.

        Read more
      • Raymond ChenMicrosoft employee Author

        I try to stick to C-friendly code, using C++ only where necessary (like for RAII), because it reduces confusion. Note also that .size() returns a size_t, so you’re going to have to do a volumePath.data(), static_cast<DWORD>(volumePath.size()), which is getting unwieldy.