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.

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.

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?

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:

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 CRuntime Library), but I think the size criteria was met. :-)

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.

Comments and Discussions

I have a query about Junction/link to a folder.
How to identify junction in MFC in code?
Is there ant specific method?
If I make write some code to cab a folder which is a link(Junction) in other drive.
So when I restored it back,will the link/junction be also restored at the destination?
Kindly reply.
Awaiting for the same.