HowTo: Correctly read reparse data in Vista

In many newsgroups/forums you will see the following approach to read the reparse data (for example the original directory of a junction):

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”.

8 thoughts on “HowTo: Correctly read reparse data in Vista

  1. Sergey Vlasov

    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”);

  2. jkalmbach Post author

    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!

  3. Pingback: Jochen Kalmbach’s Blog » Blog Archive » Mal wieder ein Doku Fehler in der MSDN

  4. Pingback: Broken and Ill-Documented API for Windows Mount-Points « Brainrack Aborning - Developing a Search Engine

  5. Oliver

    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.

Comments are closed.