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, GetVolumeHandleForFile
. 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.
The “GetVolumePathNameW function” page on Microsoft Docs looks damaged. Its examples are missing too.
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...
Are the Windows people still not using
std::array
in place of C-style arrays?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.
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 asize_t
, so you’re going to have to do avolumePath.data(), static_cast<DWORD>(volumePath.size())
, which is getting unwieldy.