Click here to Skip to main content
Click here to Skip to main content
Go to top

Windows 2000 Junction Points

, 5 Jan 2000
Rate this:
Please Sign up or sign in to vote.
Explains how reparse points are used to create filesystem links

If you're not interested in the way to the solution, simply skip to the section The Solution.

Background

Windows 2000 includes a new version of NTFS dubbed "Version 5".

This version of NTFS includes something called reparse points. Reparse points can, among other things, be used to implement a type of softlinks, seen in other operating systems for the last decades.

  • A reparse point that points to a Volume is called Volume mount point.
  • A reparse point that points to a Directory is called Junction Point.

Volume Mount Points

The first steps to try these out was by using diskmgmt.msc which is an MMC "Snap-in" replacement for the Disk Administrator in NT4.

I created a directory \mnt\s and tried to mount S: on this directory.

It worked! I now had access to my S: through \mnt\s.

OK, can I now remove the driveletter from this drive and still have it working? Yes! Finally, you don't need all those driveletters anymore (except for the boot- and system-drive). You can simply remove e.g. S: and mount the Volume under \mnt\s.

This might not seem like a big deal to some people, but it can remove a lot of clutter. It also helps a lot when moving programs from one place to another, since just about every program in the Windows world expects to never be moved from the directory it was installed in.

E.g. moving your "Program Files" directory to another drive, and linking the original "Program Files" directory to this new location.

A Volume mount point basically contains the Unicode string

"\??\Volume{ GUID }\"
and is a representation that Windows 2000 uses to identify individual Volumes.

You can list accessible Volume GUIDs by typing MountVol.

For a quick look at where these are used, start RegEdit and look in the key

HKLM\SYSTEMS\MountedDevices

Note that a Volume isn't the Media, but rather the logical "device", since a Volume can refer to a floppy drive with no media in it.

Directory Junction Points

Reading a bit more revealed that reparse points should also be able to point at another directory. Ahhh, finally, I thought.

While looking for a way to create Directory junction points, I found a reference to a tool called "linkd.exe" in the Windows help. Hunting high and low for this tool I ended up empty handed.

Perhaps the most important evolution of NTFS ever, and they didn't supply the documented tool to use it! (It's apparently supposed to appear in Windows 2000 Resource Kit)

What's even more bothering is that junction points API usage is undocumented.

The Search

Starting to work up some steam over this issue, I got going on writing a tool that could create and manipulate Directory junction points (i.e. softlinks).

Now, how do you write code with completely undocumented structures? As usual in this world, by disassembling, trial-and-error, and searching old documentation and SDKs.

The the Windows 2000 SDK documentation that mentions Reparse Points points you to the struct REPARSE_GUID_DATA_BUFFER.

typedef struct _REPARSE_GUID_DATA_BUFFER {
    DWORD  ReparseTag;
    WORD   ReparseDataLength;
    WORD   Reserved;
    GUID   ReparseGuid;
    struct {
        BYTE   DataBuffer[1];
    } GenericReparseBuffer;
} REPARSE_GUID_DATA_BUFFER, *PREPARSE_GUID_DATA_BUFFER;
This struct is nothing more than i bit-bucket for the real data that any particular reparse point contains. In the case of Volume mount points and Junction points, it's both close to useless and completely wrong. Trying to parse the deata from a Junction Points using the ReparseGuid data member would only result in jibberish.

No help here.

There are three FSCTLs defined in WinIoCtl.h to manipulate reparse points using DeviceIoControl:

  • FSCTL_SET_REPARSE_POINT
  • FSCTL_GET_REPARSE_POINT
  • FSCTL_DELETE_REPARSE_POINT

Looking at the definition of these, you find that all three of them has a comment // REPARSE_DATA_BUFFER. This comment is still present in the Windows 2000 SDK, but the structure definition is nowhere to be found.

At this time my cursing started to approach a level not suitable for printing, and I decided that it was a long night with a lot of coffe and disassemly ahead.

But, I had a vague memory of this structure in the VC6 header. A contents search in the include directory for REPARSE_DATA_BUFFER displayed that it indeed existed in WinNT.h from VC6.

Apparently this needed structure was removed from the Windows 2000 SDK. Go figure...

Since I've now mentioned REPARSE_DATA_BUFFER, I think it's fair to show you the definition of it.

// slightly edited for displaying purposes
struct REPARSE_DATA_BUFFER {
    DWORD  ReparseTag;
    WORD   ReparseDataLength;
    WORD   Reserved;
    struct {
        WORD   SubstituteNameOffset;
        WORD   SubstituteNameLength;
        WORD   PrintNameOffset;
        WORD   PrintNameLength;
        WCHAR  PathBuffer[1];
    } SymbolicLinkReparseBuffer;
};

Armed with this struct, and a little knowledge of how Volume mount point strings looked, I went of to write some code.

I had no success whatsoever in using the SET FSCTL. DeviceIoControl only returned an error.

Now, I could GET a Volume mount point, that are easily created with the Disk Administrator equivalent or MountVol.exe, but I couldn't SET it back!

Suddenly a thought struck me, what if the definition of the SET macro changed?!

Comparing the definitions of these macros from the new SDK with the VC6 version confirmed my suspicions. The VC6 version looks like

#define FSCTL_SET_REPARSE_POINT CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 41, 
        METHOD_BUFFERED, FILE_WRITE_DATA) // REPARSE_DATA_BUFFER,
#define FSCTL_GET_REPARSE_POINT     CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 42, 
        METHOD_BUFFERED, FILE_ANY_ACCESS) // , REPARSE_DATA_BUFFER
#define FSCTL_DELETE_REPARSE_POINT  CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 43, 
        METHOD_BUFFERED, FILE_WRITE_DATA) // REPARSE_DATA_BUFFER,
but in the Windows 2000 SDK, it had been changed to
// Windows 2000 SDK
#define FSCTL_SET_REPARSE_POINT     CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 41, 
        METHOD_BUFFERED, FILE_SPECIAL_ACCESS) // REPARSE_DATA_BUFFER,
#define FSCTL_GET_REPARSE_POINT     CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 42, 
        METHOD_BUFFERED, FILE_ANY_ACCESS) // REPARSE_DATA_BUFFER
#define FSCTL_DELETE_REPARSE_POINT  CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 43, 
        METHOD_BUFFERED, FILE_SPECIAL_ACCESS) // REPARSE_DATA_BUFFER,

The new FILE_SPECIAL_ACCESS is defined as:

#define FILE_SPECIAL_ACCESS (FILE_ANY_ACCESS)
Aha, they changed the access protection for SET and DELETE! That might explain why nothing worked.

Actually, the change in access protection makes some sense. A SET or DELETE operation on a reparse point doesn't need write access to the Directory it's used on. It only needs access to the NTFS Attributes for that directory.

As a sidenote I might add the following snippet from WinIoCtl.h from the windows 2000 SDK.

// FILE_SPECIAL_ACCESS is checked
// by the NT I/O system the same as FILE_ANY_ACCESS.
// The file systems, however, may add additional access checks
// for I/O and FS controls
// that use this value.
Interesting: It mentions that
"The file systems, however, may add additional access checks...".

I wonder how they are supposed to do that, since both ANY and SPECIAL access are defined to be zero.

Getting Closer

Finally I could both GET and SET a Volume mount point using DeviceIoControl, and thereby get some info of how this struct should be filled in.
The Volume mount points PathBuffer in REPARSE_DATA_BUFFER is a Unicode string that looks like

"\??\Volume{9424a4a2-bbb6-11d3-a640-806d6172696f}\"

The SubstituteNameLength tells how many bytes the PathBuffer contains. By disassembling SetVolumeMountPoint from kernel32.dll, I found out that it only accepts 96 or 98 bytes as buffer length.

Strange, was my first thought. But still I tried to use the Volume GUID with an appended directory name through DeviceIoControl, in the hope that it wouldn't have the same restrictions and only get resolved during access. Right?

Wrong. Why make it orthogonal when you can make it "cumbersome"?

After many hours of trial-and-error, and even more cursing, I was about ready to give in, and admit defeat, when I got an idea. What if you instead of using a Volume GUID, look back on the CreateFile documentation?

The Solution

According to CreateFile documentation, you can enter a non-parsed path by prepending "\??\" to it. What if we used this approach and put in a "normal" full path like

"\??\C:\Program Files"

Type some code, build and test...Finally! It worked!

So, finally, to create a directory junction point, you must do the following:

  1. Create a directory, or use an existing empty directory.
  2. Open this directory with CreateFile, using the flags    FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT
  3. Create a filled-in REPARSE_DATA_BUFFER.
  4. Call
    DeviceIoControl(hDir,			// HANDLE to the directory
    		FSCTL_SET_REPARSE_POINT,
    		(LPVOID)&rdb,		// REPARSE_DATA_BUFFER
    		dwBytesToSet,
    		NULL,
    		0,
    		&dwBytes,
    		0);
    
  5. Done.

Well, almost. It's still that little matter of filling in this struct.

Filling in the REPARSE_DATA_BUFFER example

// quick 'n' dirty solution
wchar_t wszDestDir[] = "\\??\\C:\\Program Files\";
const int nDestBytes = lstrlenW(wszDestDir) * sizeof(wchar_t);

char szBuff[1024] = { 0 };
REPARSE_DATA_BUFFER& rdb = *(REPARSE_DATA_BUFFER*)szBuff;

rdb.ReparseTag        = IO_REPARSE_TAG_MOUNT_POINT;
rdb.ReparseDataLength = nDestBytes + 12;
rdb.SymbolicLinkReparseBuffer.SubstituteNameLength = nDestBytes;
rdb.SymbolicLinkReparseBuffer.PrintNameOffset      = nDestBytes + 2;

lstrcpyW(rdb.SymbolicLinkReparseBuffer.PathBuffer, wszDestDir);

const DWORD dwBytesToSet = // input buffer size to give to DeviceIoControl
	rdb.ReparseDataLength + REPARSE_DATA_BUFFER_HEADER_SIZE;

Ugly or what? I especially dislike the unnamed (i.e. it doesn't have a typename) struct SymbolicLinkReparseBuffer. Both that it's unnamed, and the length of its name makes the code quite unreadable.

I copied the definition of REPARSE_DATA_BUFFER from the VC6 header file to be able to use this even with the Windows 2000 SDK. I renamed it and removed the unnamed struct. In the process, it got some member functions to make its usage a lot easier.

Summary

As I said earlier in this article, possibly one of the most sought for features (and by that, looong overdue) in NTFS is "softlinks", and they didn't have the decency to neither document it, nor to provide any API whatsoever to use it.

I mean, get real; DeviceIoControl() to create a softlink?!

To make this a bit more usable, I wrote a little library that you can use in your own creations. The included program MakeLink.exe uses this library, and it's used to create, list and delete junction points. Just start MakeLink without arguments to see its usage.

The functions that IMO were missing from Microsofts API, and got implemented by this library (though in its own C++ namespace) are:

BOOL CreateJunctionPoint(LPCTSTR szMountDir, LPCTSTR szDestDir);
BOOL DeleteJunctionPoint(LPCTSTR szMountDir);
DWORD GetJunctionPointDestination(LPCTSTR szMountDir, LPTSTR szDestBuff, 
                                  DWORD dwBuffSize /* in TCHARs */);

These should be self explaining, but in the interest of completeness, here's some documentation.

CreateJunctionPoint

This function allows you to create or overwrite an existing junction point.

  • szMountDir must point to an empty directory.
  • szDestDir can either contain a path in the form "C:\Program Files" or "\??\C:\Program Files".
The first form will check if the directory exists before creating/overwriting the junction point. The second form allows you to enter just about any string as the destination.

Note that using the second form, you could create a Directory junction point that points to nowhere usable (e.g. "\??\foo:bar/baz").

If the function fails, the return value is FALSE. To get extended error information, call GetLastError.

Note: Strictly speaking, you can use this function as a replacement for the MountVol.exe command using the "\??\" form, but I think that Disk Admin is better suited for that purpose.

DeleteJunctionPoint

This function allows you to remove any Volume Mount Points or Directory Junction Points from the specified directory.

If the function fails, the return value is FALSE. To get extended error information, call GetLastError.

GetJunctionPointDestination

This function allows you to query any directory for its reparse point destination. Note that it will only work for reparse points of the type IO_REPARSE_TAG_MOUNT_POINT, but since this includes both Volume mount points and Directory junction points, it's fit for this library.

If the GetJunctionPointDestination succeeds, the return value is the the length, in TCHARs, of the string copied to szDestDir, not including the terminating null character.

If the szDestDir buffer is too small, the return value is the size of the buffer, in TCHARs, required to hold the path.

If the function fails, the return value is zero. To get extended error information, call GetLastError.

Final notes

The code is compilable as both ANSI and Unicode. It does not use MFC, standard C++ library, or any CRT memory management functions.

Writing this library and I had a few criterias in mind:

  • Make an easy API for people to use. I always use CreateFile as a comparison.
  • Create as few dependencies on other libraries as possible.
  • Don't use MFC. Not everybody uses MFC, and to use it for a utility (library) like this is IMO like using a sledgehammer to type on your keyboard.
  • Don't depend on the standard C++ library. Not everybody uses it, and though a correct implementation is good, PJP's Microsoft implementation is not correct, since their compilers can't handle a conforming implementation. Besides that:
  • Make it small.

The application MakeLink.exe is 5 632 bytes. It does however depend on MSVCRT.dll (Microsoft C Runtime Library), but I think the size criteria was met. Smile | :)

BTW:
While browsing the new documentation, in the documentation for SetVolumeMountPoint I found the following text

... "\\?\C:\myworld\private" is seen as "C:\myworld\private".
This initially led me to believe that I've done all this work for nothing!

Trying out this API (which according to its name is to mount Volumes only), I found out that they've only mentioned it, they don't implement this behaviour in SetVolumeMountPoint. Another point of interest is that the creator of this API apparently was completely unaware of the already documented approach of creating a non-parsed file system name "\??\", and charged ahead to invent "\\?\".

Happy Filesystem linking.

Mike Nordell - Nordell Consulting

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

Share

About the Author

Mike Nordell
Architect
Sweden Sweden
No Biography provided

Comments and Discussions

 
Generalsome wrong on 2t disk simulator Pinmembernhchmg15-Dec-10 22:55 
GeneralMy vote of 5 Pinmemberdemonkoryu13-Oct-10 23:58 
GeneralJunction Link Magic: Include Option to Select Folder Level Deep Pingroupjames.rapula22-Jul-10 13:43 
QuestionIs Link to Network Drive Possible? PinmemberJonny Hart5-Jun-08 22:26 
GeneralCab and restore a folder which is a Junction/Link in other drive Pinmemberkulkarniquiet7-Mar-07 22:32 
Questiontrailing backslash--bug? Pinmemberlarham28-Nov-06 8:59 
GeneralVolume Mount Points To Device PNP Pinmembersmclain23-Jun-06 19:07 
JokeLonghorn provides API for symlinks LOL Pinmembercurlyhands25-Mar-06 20:53 
GeneralShell Extension for junctions Pinmemberryltsov10-Feb-06 9:52 
GeneralDirectory junction points now available directly from linkd.exe PinsussMatthias Fripp29-Sep-05 12:39 
GeneralRe: Directory junction points now available directly from linkd.exe PinmemberScott Pascoe19-Nov-05 8:05 
QuestionWith p/Invoke Pinsussjonscheiding12-Sep-05 6:06 
AnswerRe: With p/Invoke PinmemberMike Nordell14-Sep-06 7:20 
GeneralTo get Information of localization of a resource in windows PinmemberAjay Movalia6-Nov-03 1:05 
QuestionIsReparseTagValid macro? PinmemberJerker Bäck30-Sep-03 10:25 
AnswerRe: IsReparseTagValid macro? PinmemberMike Nordell30-Sep-03 11:35 
GeneralRe: IsReparseTagValid macro? PinmemberJerker Bäck1-Oct-03 12:33 
GeneralRe: IsReparseTagValid macro? PinmemberJerker Bäck7-Nov-04 12:30 
GeneralAnother REPARSE_DATA_BUFFER PinmemberJerker Bäck30-Sep-03 7:52 
QuestionMulti target ? PinsussMadium3-Jul-03 9:59 
AnswerRe: Multi target ? Pinmemberrumburaky31-Jul-03 3:52 
GeneralJunction Pts for C:\Program Files and C:\Documents and Settings PinmemberWarrenW8-Jun-03 21:06 
GeneralRe: Junction Pts for C:\Program Files and C:\Documents and Settings PinmemberMike Nordell9-Jun-03 1:00 
GeneralRe: Junction Pts for C:\Program Files and C:\Documents and Settings PinmemberWarrenW9-Jun-03 13:53 
GeneralRe: Junction Pts for C:\Program Files and C:\Documents and Settings PinmemberJohn M. Dlugosz22-Feb-05 10:01 
GeneralCall me stupid, but.... Pinmemberhector santos1-Mar-03 0:23 
GeneralRe: Call me stupid, but.... PinmemberChristopher Lord15-Mar-03 12:09 
GeneralRe: Call me stupid, but.... PinmemberJerry Jeremiah16-Oct-06 15:25 
GeneralJunctions pointing at remote shares/volumes PinsussAnonymous3-Dec-02 2:26 
GeneralRe: Junctions pointing at remote shares/volumes PinmemberMike Nordell3-Dec-02 16:23 
GeneralRe: Junctions pointing at remote shares/volumes PinmemberSunemaster4-Dec-02 0:38 
GeneralRe: Junctions pointing at remote shares/volumes PinmemberMike Nordell11-Dec-02 12:40 
GeneralRe: Junctions pointing at remote shares/volumes PinmemberSunemaster12-Dec-02 0:20 
GeneralRe: Junctions pointing at remote shares/volumes Pinmemberrtroncy8-Jan-03 22:44 
GeneralRe: Junctions pointing at remote shares/volumes PinmemberMike Nordell9-Jan-03 13:49 
GeneralRe: Junctions pointing at remote shares/volumes PinmemberSpam Basket10-Feb-03 6:55 
GeneralRe: Junctions pointing at remote shares/volumes PinmemberMike Nordell10-Feb-03 7:30 
GeneralRe: Junctions pointing at remote shares/volumes Pinmemberphoton07-Apr-05 5:36 
GeneralRe: Junctions pointing at remote shares/volumes PinmemberalwaysZeroHour26-Apr-07 6:01 
GeneralRe: Junctions pointing at remote shares/volumes PinmemberArgon019-Sep-08 2:56 
GeneralHKEY_LOCAL_USER PinmemberHugo Hallman20-Oct-02 23:47 
GeneralRe: HKEY_LOCAL_USER PinmemberMike Nordell21-Oct-02 3:16 
GeneralANN: GUI version of MakeLink PinmemberJos Branders31-Mar-02 5:17 
GeneralRe: ANN: GUI version of MakeLink Pinmemberhector santos1-Mar-03 0:29 
GeneralRe: ANN: GUI version of MakeLink PinsussJos Branders1-Mar-03 21:04 
GeneralRe: ANN: GUI version of MakeLink Pinmemberhector santos1-Mar-03 0:33 
GeneralGreat! PinmemberKurt1-Sep-01 1:44 
GeneralCool util! Found a good use for it. PinmemberGossamer18-Jun-01 13:11 
Cool | :cool: Amazing article! I've been searching high and low for the linkd.exe utility but now I finally found your article, and it worked! I'm really impressed how you found out how it all works without having anything to go on.
 
I have my machine loaded with multiple logons for different networks (domains, etc), and now I can just make a junction for my user directory and not have to set everything up all over again for each user.
GeneralThank's PinmemberAnonymous27-Apr-01 11:10 
GeneralSystinernals junction PinmemberAnonymousx16-Apr-01 7:42 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web03 | 2.8.140916.1 | Last Updated 6 Jan 2000
Article Copyright 2000 by Mike Nordell
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid