In many newsgroups/forums you will see the following approach to read the reparse data (for example the original directory of a junction):
- Adjust the Token and enable the SE_BACKUP_NAME privilege
- Open the diretory with “GENERIC_READ” and “FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT”
- Use DeviceIoControl with FSCTL_GET_REPARSE_POINT to get the data
This works very well under XP (but only if you are an admin). It does not work under Vista if UAC is enabled (even if you are admin). It only works if the process was started evelated (as real admin)!
But if you go to the command prompt and type “dir c:\ /AL”, you will see that in the command prompt the display is always possible, even if you are not an admin:
C:\>dir /AL
Volume in drive C has no label.
Volume Serial Number is B424-9F82
Directory of C:\
11/02/2006 02:00 PM Documents and Settings [C:\Users]
0 File(s) 0 bytes
1 Dir(s) 57,193,873,408 bytes free
So there must be a better way to read the reparse data.
First I tried to find an other function to get the data. But this had no success… then I remembered my research about “Shims” which are part of XP and later. These shims can be used to “fix” application compatibilities in news OSs. To activate a shim for an appliction you can use the “Application Compatibility Toolkit” from Microsoft. And also I remembered an Shim namaned “APILogger”. Ok, then I copied “cmd.exe” to a temp-dir and renamned it; created and installed the sdb-File with the APILogger for this new “testcmd.exe”; and executed the “dir c:\ /AL” command. Afterwards I could easily find the correct approach to get the reparse data (in a later blogpost I will explain how to use the APILogger-shim).
The key point is: You must open the directory only with “FILE_READ_EA” access!
Here is now the full working source-code for reading reparse-data:
#include "stdafx.h"
#include
typedef struct _REPARSE_DATA_BUFFER {
ULONG ReparseTag;
USHORT ReparseDataLength;
USHORT Reserved;
union {
struct {
USHORT SubstituteNameOffset;
USHORT SubstituteNameLength;
USHORT PrintNameOffset;
USHORT PrintNameLength;
ULONG Flags; // it seems that the docu is missing this entry (at least 2008-03-07)
WCHAR PathBuffer[1];
} SymbolicLinkReparseBuffer;
struct {
USHORT SubstituteNameOffset;
USHORT SubstituteNameLength;
USHORT PrintNameOffset;
USHORT PrintNameLength;
WCHAR PathBuffer[1];
} MountPointReparseBuffer;
struct {
UCHAR DataBuffer[1];
} GenericReparseBuffer;
};
} REPARSE_DATA_BUFFER, *PREPARSE_DATA_BUFFER;
#define REPARSE_DATA_BUFFER_HEADER_SIZE FIELD_OFFSET(REPARSE_DATA_BUFFER, GenericReparseBuffer)
#define MAXIMUM_REPARSE_DATA_BUFFER_SIZE ( 16 * 1024 )
int _tmain(int argc, _TCHAR* argv[])
{
HANDLE hFile;
LPCTSTR szMyFile = _T("C:\\Documents and Settings"); // Mount-Point (JUNCTION)
//LPCTSTR szMyFile = _T("C:\\Users\\All Users"); // Symbolic-Link (SYMLINKD)
hFile = CreateFile(szMyFile, FILE_READ_EA, FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, NULL);
if (hFile == INVALID_HANDLE_VALUE)
{
_tprintf(_T("Could not open dir '%s'; error: %d\n"), szMyFile, GetLastError());
return 1;
}
// Allocate the reparse data structure
DWORD dwBufSize = MAXIMUM_REPARSE_DATA_BUFFER_SIZE;
REPARSE_DATA_BUFFER* rdata;
rdata = (REPARSE_DATA_BUFFER*) malloc(dwBufSize);
// Query the reparse data
DWORD dwRetLen;
BOOL bRet = DeviceIoControl(hFile, FSCTL_GET_REPARSE_POINT, NULL, 0, rdata, dwBufSize, &dwRetLen, NULL);
if (bRet == FALSE)
{
_tprintf(_T("DeviceIoControl failed with error: %d\n"), GetLastError());
CloseHandle(hFile);
return 1;
}
CloseHandle(hFile);
if (IsReparseTagMicrosoft(rdata->ReparseTag))
{
if (rdata->ReparseTag == IO_REPARSE_TAG_SYMLINK)
{
printf("Symbolic-Link\n");
size_t slen = rdata->SymbolicLinkReparseBuffer.SubstituteNameLength / sizeof(WCHAR);
WCHAR *szSubName = new WCHAR[slen+1];
wcsncpy_s(szSubName, slen+1, &rdata->SymbolicLinkReparseBuffer.PathBuffer[rdata->SymbolicLinkReparseBuffer.SubstituteNameOffset / sizeof(WCHAR)], slen);
szSubName[slen] = 0;
printf("SubstitutionName (len: %d): '%S'\n", rdata->SymbolicLinkReparseBuffer.SubstituteNameLength, szSubName);
delete [] szSubName;
size_t plen = rdata->SymbolicLinkReparseBuffer.PrintNameLength / sizeof(WCHAR);
WCHAR *szPrintName = new WCHAR[plen+1];
wcsncpy_s(szPrintName, plen+1, &rdata->SymbolicLinkReparseBuffer.PathBuffer[rdata->SymbolicLinkReparseBuffer.PrintNameOffset / sizeof(WCHAR)], plen);
szPrintName[plen] = 0;
printf("PrintName (len: %d): '%S'\n", rdata->SymbolicLinkReparseBuffer.PrintNameLength, szPrintName);
delete [] szPrintName;
}
else if (rdata->ReparseTag == IO_REPARSE_TAG_MOUNT_POINT)
{
printf("Mount-Point\n");
size_t slen = rdata->MountPointReparseBuffer.SubstituteNameLength / sizeof(WCHAR);
WCHAR *szSubName = new WCHAR[slen+1];
wcsncpy_s(szSubName, slen+1, &rdata->MountPointReparseBuffer.PathBuffer[rdata->MountPointReparseBuffer.SubstituteNameOffset / sizeof(WCHAR)], slen);
szSubName[slen] = 0;
printf("SubstitutionName (len: %d): '%S'\n", rdata->MountPointReparseBuffer.SubstituteNameLength, szSubName);
delete [] szSubName;
size_t plen = rdata->MountPointReparseBuffer.PrintNameLength / sizeof(WCHAR);
WCHAR *szPrintName = new WCHAR[plen+1];
wcsncpy_s(szPrintName, plen+1, &rdata->MountPointReparseBuffer.PathBuffer[rdata->MountPointReparseBuffer.PrintNameOffset / sizeof(WCHAR)], plen);
szPrintName[plen] = 0;
printf("PrintName (len: %d): '%S'\n", rdata->MountPointReparseBuffer.PrintNameLength, szPrintName);
delete [] szPrintName;
}
else
{
printf("No Mount-Point or Symblic-Link...\n");
}
}
else
{
_tprintf(_T("Not a Microsoft-reparse point - could not query data!\n"));
}
free(rdata);
return 0;
}
ADD (2008-03-07): Thanks to the comment of Sergey, I now changed the sample code to correctly display JUNCTIONs (mount points) and SYMLINKD (symbolic-links). It seems that the documentation of REPARSE_DATA_BUFFER is incorrect, because the missed a field named “Flags”. I changed the structure to the correct definition which can be found in the DDK header file “ntifs.h”.
Two little remarks:
1. In my tests it works even without FILE_READ_EA, i.e. CreateFile(szMyFile, 0, …)
2. SymbolicLinkReparseBuffer definition misses “ULONG Flags;”. It should be
struct {
USHORT SubstituteNameOffset;
USHORT SubstituteNameLength;
USHORT PrintNameOffset;
USHORT PrintNameLength;
ULONG Flags;
WCHAR PathBuffer[1];
} SymbolicLinkReparseBuffer;
And you forgot to mention that extracting symbolic link info is left as exercise for the reader 🙂 It can be tested on _T(“c:\\Users\\All Users”);
Hi Sergey,
1. The “correct” (or MS) way is to use FILE_READ_EA; see my screencast about using APILogger-Shim:
http://blog.kalmbach-software.de/2008/03/06/screencast-api-logging-with-the-application-compatibility-layer/
2. I don’t understand; see documentation of REPARSE_DATA_BUFFER
http://msdn2.microsoft.com/en-us/library/ms791514.aspx
Ok, I found the correct definition in the ntfsi.h file from the DDK and corrected the example; also added support for SymLink and Junctions 😉
Thanks again!
Pingback: Jochen Kalmbach’s Blog » Blog Archive » Mal wieder ein Doku Fehler in der MSDN
Pingback: Broken and Ill-Documented API for Windows Mount-Points « Brainrack Aborning - Developing a Search Engine
Thanks, Jochen! Anyone trying to implement this, or something like it, should be aware of a few other little gotchas in the Microsoft API. I’ve detailed them at http://brainrack.wordpress.com/2008/05/28/broken-and-ill-documented-api-for-windows-mount-points/
Thanks for the article. This saved me a lot of time!
Getting a handle to a directory with FILE_FLAG_BACKUP_SEMANTICS does _not_ require the backup (and/or restore) privilege(s). GENERIC_READ will work all the same, although FILE_READ_EA is probably the minimum required (which is why MS uses it, but that’s not documented, it just happens to show up when you look at what their implementations do).
The actual point, however, is that the FILE_FLAG_BACKUP_SEMANTICS for directories does not require the privilege, whereas for files that is required.