|
|
![]() |
|
||||||||||||||||||||||||||||||||||||||||||
![]() Advertisement
If you're not interested in the way to the solution, simply skip to the section The Solution. Background![]() Advertisement
Windows 2000 includes a new version of NTFS dubbed "Version 5". ![]() Advertisement
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.
Volume Mount PointsThe first steps to try these out was by usingdiskmgmt.msc which
is an MMC "Snap-in" replacement for the Disk Administrator in NT4.
I created a directory It worked! I now had access to my 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. 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 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 PointsReading 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 SearchStarting 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 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
Looking at the definition of these, you find that all three of them has
a comment 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 // 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.
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
The
Strange, was my first thought. But still I tried to use the Volume GUID
with an appended directory name through 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
The Solution
According to "\??\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:
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 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. SummaryAs 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 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. CreateJunctionPointThis function allows you to create or overwrite an existing junction point.
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. DeleteJunctionPointThis 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
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 notesThe 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:
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. :-) BTW:
... "\\?\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 Mike Nordell
Other popular Windows 2000 / XP articles:
|
|
All Topics, MFC / C++ >> Windows 2000 / XP >> General
Updated: 6 Jan 2000 Editor: Chris Maunder |
Article content copyright Mike Nordell, 2000 everything else Copyright � CodeProject, 1999-2004. Advertise on The Code Project | Privacy |
![]() |
MSDN Communities | ASPAlliance � Developer Fusion � DevelopersDex � DevGuru � Programmers Heaven � SitePoint � Tek-Tips Forums � TopXML � VisualBuilder � ZVON � Search Us! |