NTFS Hard Links, Directory Junctions, and Windows Shortcuts

来源:互联网 发布:济南中学生学编程 编辑:程序博客网 时间:2024/06/03 13:51

NTFS Hard Links, Directory Junctions, and Windows Shortcuts

  • NTFS Hard Links
  • Reparse Points and Directory Junctions
  • Windows Shortcuts
  • Downloads

A link is basically an alternate name for a file or a directory. In many casescreating a link is preferrable to making several copies of the file.Links save disk space and more important, if the file should be changed afterwards,you will not have to edit a bunch of copies - all the links point to the same file.

If you are familiar with Unix-family operating systems, you must have knownwhat the ln command is. Any BSD or Linux disk contains a lot of links,and the ln command is one of the most commonly used. Windows also supportsboth hard and soft links, but surprisingly, it provides no command line tool forcreating links.

In this article we will show how to create symbolic and hard links programmatically.If you are not interested in programming and just want to get theutility, skip directly to the download section.


NTFS Hard Links

Hard link uses the same MFT entry as the original file. Adding a hard link createsa new name attribute and increases the hard link count (for a newly created filethis count equals to one). Deleting a hard link removes the appropriate name and decreasesthe hard link count. When the count goes to zero, the system deletes the file, freeing upits allocated disk space and releasing its MFT record. All the name attributes are independent,so deleting, moving, or renaming the file don't affect other hard links.

In order to get the value of the hard link count, open the file in theFlexHEXeditor and select the File Propertiespage in the Streams pane (the lower left one). If there is any hard link to the file,the Hard Links value will be greater than one (the normal directory recordis also counted, so one means no hard links).

Because all the links must refer to the same MFT entry, you cannot create a hard link ona different drive. Another limitation is that only files can be hard-linked, not directories(however soft links to directories are allowed).

Programming Considerations

To get the number of links use the Win32 API function GetFileInformationByHandle.The nNumberOfLinks member of the returned BY_HANDLE_FILE_INFORMATIONstructure contains the total number of links (a value of 1 means there are no hard links).

Creating Hard Link

Creating a hard link is simple - just call the Win32 API function CreateHardLink(this function is available in Windows 2000 and later systems).

BOOL CreateHardLink(LPCTSTR lpFileName,               // Link path
LPCTSTR lpExistingFileName,
LPSECURITY_ATTRIBUTES lpSecurityAttributes);

While the first two arguments are fairly obvious, the third one may be somewhat misleading.Security descriptors belong to files, not to hard links, so if you specify a securityattributes in a CreateHardLink call, this will change security settings for thefile itself (and for all the file links). You should also keep in mind thatcalling CreateHardLink with a non-NULL lpSecurityAttributes argument mostlikely will fail if the user is not the owner of the file or a system administrator.It is not clear what reasons did Microsoft have for adding that argument - modifying filesecurity the usual way, using the SetSecurityInfo function, would be a better practice.

Deleting Hard Link

In order to delete a hard link, call the Windows API function DeleteFile, specifyingthe link path. The DeleteFile function removes the link and decreases the link count inthe file's MFT record. If the link count becomes zero, the system deletes the file, that isfrees its MFT entry and disk space.

Note that there is no difference between the original file name and an additional hard link.Both are just pointers to the file's MFT entry.


Reparse Points and Directory Junctions

Reparse points provide another way of creating links. If a file or a directory has a reparsepoint attached, the system calls a file system filter, indicated by the reparse point tag.The filter may implement any method of accessing the actual data, including quitecomplicated ones. HSM (Hierarchical Storage Management) is a good example how useful reparsepoints can be.

Unfortunately, although we can link to directories using the reparse points mechanism (such linksare called junctions), there is no way of linking to files short of writing a customfile system filter driver. Even if using reparse points is a natural way of implementing soft links,the gain is hardly worth the pain.

However reparse points are not all that useless for our purpose: junctions can supplement NTFSfile-only hard links. Just keep in mind that they are, in fact, soft links, and if you move or delete thereferred directory, the junction will point to nowhere. On the other hand, being a soft link,a junction is not limited to objects residing on the same drive; it can point to a directoryon a different drive, or even to the drive itself (although a junction is not allowed to pointto a mapped network object).

Programming Considerations

In order to improve readability, the code examples below don't include any error processing.You should add some error checks if you want to use this code in your program. See the sourcesin the download section for an example of error handling.

Aside from a couple of overview articles, reparse points are very scantily documented, and thePlatform SDK does not include the required definitions:

#define REPARSE_MOUNTPOINT_HEADER_SIZE   8

typedef struct {
DWORD ReparseTag;
DWORD ReparseDataLength;
WORD Reserved;
WORD ReparseTargetLength;
WORD ReparseTargetMaximumLength;
WORD Reserved1;
WCHAR ReparseTarget[1];
} REPARSE_MOUNTPOINT_DATA_BUFFER, *PREPARSE_MOUNTPOINT_DATA_BUFFER;

The header file WinNT.h included in the VC++ 6.0 distributive containsanother definition (Microsoft removed it in the later versions of the Platform SDK):

typedef struct _REPARSE_DATA_BUFFER {
DWORD ReparseTag;
WORD ReparseDataLength;
WORD Reserved;
union {
struct {
WORD SubstituteNameOffset;
WORD SubstituteNameLength;
WORD PrintNameOffset;
WORD PrintNameLength;
WCHAR PathBuffer[1];
} SymbolicLinkReparseBuffer;
struct {
WORD SubstituteNameOffset;
WORD SubstituteNameLength;
WORD PrintNameOffset;
WORD PrintNameLength;
WCHAR PathBuffer[1];
} MountPointReparseBuffer;
struct {
BYTE DataBuffer[1];
} GenericReparseBuffer;
};
} REPARSE_DATA_BUFFER, *PREPARSE_DATA_BUFFER;

#define REPARSE_DATA_BUFFER_HEADER_SIZE /
FIELD_OFFSET(REPARSE_DATA_BUFFER, GenericReparseBuffer)

We will use the former structure as defined in the Junctions example on theSystems Internals Web site.

Helper Functions

We will need a couple of helper functions. First one opens a directory and returns its handle:

// Returns directory handle or INVALID_HANDLE_VALUE if failed to open.
// To get extended error information, call GetLastError.

HANDLE OpenDirectory(LPCTSTR pszPath, BOOL bReadWrite) {
// Obtain backup/restore privilege in case we don't have it
HANDLE hToken;
TOKEN_PRIVILEGES tp;
::OpenProcessToken(::GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken);
::LookupPrivilegeValue(NULL,
(bReadWrite ? SE_RESTORE_NAME : SE_BACKUP_NAME),
&tp.Privileges[0].Luid);
tp.PrivilegeCount = 1;
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
::AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), NULL, NULL);
::CloseHandle(hToken);

// Open the directory
DWORD dwAccess = bReadWrite ? (GENERIC_READ | GENERIC_WRITE) : GENERIC_READ;
HANDLE hDir = ::CreateFile(pszPath, dwAccess, 0, NULL, OPEN_EXISTING,
FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, NULL);

return hDir;
}

The second helper function checks if the directory has an associated junction point:

#define DIR_ATTR  (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_REPARSE_POINT)

BOOL IsDirectoryJunction(LPCTSTR pszDir) {
DWORD dwAttr = ::GetFileAttributes(pszDir);
if (dwAttr == -1) return FALSE; // Not exists
if ((dwAttr & DIR_ATTR) != DIR_ATTR) return FALSE; // Not dir or no reparse point

HANDLE hDir = OpenDirectory(pszDir, FALSE);
if (hDir == INVALID_HANDLE_VALUE) return FALSE; // Failed to open directory

BYTE buf[MAXIMUM_REPARSE_DATA_BUFFER_SIZE];
REPARSE_MOUNTPOINT_DATA_BUFFER& ReparseBuffer = (REPARSE_MOUNTPOINT_DATA_BUFFER&)buf;
DWORD dwRet;
BOOL br = ::DeviceIoControl(hDir, FSCTL_GET_REPARSE_POINT, NULL, 0, &ReparseBuffer,
MAXIMUM_REPARSE_DATA_BUFFER_SIZE, &dwRet, NULL);
::CloseHandle(hDir);
return br ? (ReparseBuffer.ReparseTag == IO_REPARSE_TAG_MOUNT_POINT) : FALSE;
}

Creating Directory Junction

The first step is creating a directory if it does not exists. If the directory exists, it must beempty - you cannot associate a reparse point with a non-empty directory.

::CreateDirectory(szJunction, NULL)

The next step is obtaining the directory handle:

// Open for reading and writing (see OpenDirectory definition above)
HANDLE hDir = OpenDirectory(szJunction, TRUE);

And the last step is the black magic:

// Take note that buf and ReparseBuffer occupy the same space
BYTE buf[sizeof(REPARSE_MOUNTPOINT_DATA_BUFFER) + MAX_PATH * sizeof(WCHAR)];
REPARSE_MOUNTPOINT_DATA_BUFFER& ReparseBuffer = (REPARSE_MOUNTPOINT_DATA_BUFFER&)buf;

// Prepare reparse point data
memset(buf, 0, sizeof(buf));
ReparseBuffer.ReparseTag = IO_REPARSE_TAG_MOUNT_POINT;
int len = ::MultiByteToWideChar(CP_ACP, 0, szTarget, -1,
ReparseBuffer.ReparseTarget, MAX_PATH);
ReparseBuffer.ReparseTargetMaximumLength = (len--) * sizeof(WCHAR);
ReparseBuffer.ReparseTargetLength = len * sizeof(WCHAR);
ReparseBuffer.ReparseDataLength = ReparseBuffer.ReparseTargetLength + 12;

// Attach reparse point
DWORD dwRet;
::DeviceIoControl(hDir, FSCTL_SET_REPARSE_POINT, &ReparseBuffer,
ReparseBuffer.ReparseDataLength+REPARSE_MOUNTPOINT_HEADER_SIZE,
NULL, 0, &dwRet, NULL);

Note that szTarget string must contain the path prefixed with the "non-parsed"prefix "/??/", and terminated with the backslash character, for example "/??/C:/Some Dir/".

You can save some effort by using the CreateJunction function you can find inthe download section.

Deleting Directory Junction

If the directory is empty, you can use the RemoveDirectory function toremove both the directory and the associated junction point. Another way isto use DeviceIoControl, which does not require the directory to be emptyand removes the junction only, leaving the directory and its content intact.

if (!IsDirectoryJunction(szJunction)) {
// Error: no junction here
}

// Open for reading and writing (see OpenDirectory definition above)
HANDLE hDir = OpenDirectory(szJunction, TRUE);

BYTE buf[REPARSE_MOUNTPOINT_HEADER_SIZE];
REPARSE_MOUNTPOINT_DATA_BUFFER& ReparseBuffer = (REPARSE_MOUNTPOINT_DATA_BUFFER&)buf;
DWORD dwRet;

memset(buf, 0, sizeof(buf));
ReparseBuffer.ReparseTag = IO_REPARSE_TAG_MOUNT_POINT;

if (::DeviceIoControl(hDir, FSCTL_DELETE_REPARSE_POINT, &ReparseBuffer,
REPARSE_MOUNTPOINT_HEADER_SIZE, NULL, 0, &dwRet, NULL)) {
// Success
::CloseHandle(hDir);
}
else { // Error
DWORD dr = ::GetLastError();
::CloseHandle(hDir);
// Some error action (throw or MessageBox)
}

Querying Directory Junction

The following code returns the path the junction point is linked to:

TCHAR szPath[MAX_PATH];  // Buffer for returned path

if (!IsDirectoryJunction(szJunction)) {
// Error: no junction here
}

// Open for reading only (see OpenDirectory definition above)
HANDLE hDir = OpenDirectory(szJunction, FALSE);

BYTE buf[MAXIMUM_REPARSE_DATA_BUFFER_SIZE]; // We need a large buffer
REPARSE_MOUNTPOINT_DATA_BUFFER& ReparseBuffer = (REPARSE_MOUNTPOINT_DATA_BUFFER&)buf;
DWORD dwRet;

if (::DeviceIoControl(hDir, FSCTL_GET_REPARSE_POINT, NULL, 0, &ReparseBuffer,
MAXIMUM_REPARSE_DATA_BUFFER_SIZE, &dwRet, NULL)) {
// Success
::CloseHandle(hDir);

LPCWSTR pPath = ReparseBuffer.ReparseTarget;
if (wcsncmp(pPath, L"//??//", 4) == 0) pPath += 4; // Skip 'non-parsed' prefix
::WideCharToMultiByte(CP_ACP, 0, pPath, -1, szPath, MAX_PATH, NULL, NULL);
}
else { // Error
DWORD dr = ::GetLastError();
::CloseHandle(hDir);
// Some error action (throw or MessageBox)
}

Windows Shortcuts

A more commonly used name for Windows symbolic link is shortcut. Shortcutis a much more advanced version of Unix soft link - in addition to the basic link-to-filerelationship you can also set a description, an icon, and so on. In addition, shortcutscan point to non-file objects like printers.

A shortcut is actually a file, usually with the .lnk extension (any other extension canalso be registered as a shortcut). Like any other file it can be copied, renamed, or deleted;file operations on a shortcut don't affect the object being pointed to.

Programming Considerations

In order to improve readability, the code examples below don't include any error processing.You should add some error checks if you want to use this code in your program. See the sourcesin the download section for an example of error handling.

Creating New Shortcut

In order to set up a Windows shortcut programmatically first create it usingthe IShellLink interface (don't forget to call CoInitializebefore calling any COM function).

IShellLink *pShellLink;
::CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_ALL,
IID_IShellLink, (void**)&pShellLink);
pShellLink->SetPath(szPath); // Path to the object we are referring to

If you want to create a shortcut for a non-file object, use the SetIDList functioninstead of SetPath. In this case you should use a pointer to the object'sITEMIDLIST structure, or PIDL. PIDL is a more universal way of object addressingso you can use the SetIDList function for both file and non-file objects. You can obtainPIDLes for the standard shell objects by calling the SHGetFolderLocation orSHGetSpecialFolderLocation function. To convert a PIDL to the corresponding file path usethe shell function SHGetPathFromIDList.

To set additional parameters like a descripton or an icon location use the appropriateIShellLink functions:

pShellLink->SetDescription(szDescr);

When the shortcut is set up completely, save it using the IPersistFile interface:

IPersistFile *pPersistFile;
pShellLink->QueryInterface(IID_IPersistFile, (void**)&pPersistFile);
pPersistFile->Save(wszLinkPath, TRUE);

Please note that unlike IShellLink::SetPath, and like most COM/OLE functions,IPersistFile::Save requires a UNICODE path string.

As a final stroke, commit I/O by releasing the interface pointers, andthe newly created shortcut is here!

pPersistFile->Release();
pShellLink->Release();

Modifying Existing Shortcut

An existing shortcut can be modified using the same code; the only difference is that youshould first read the shortcut using the IPersistFile::Load call. For example, a function for changing the path to the shortcut target would look as follows:

//  wszLinkPath - a UNICODE string containing the path to the shortcut (for
// example 'C:/Documents and Settings/All Users/Desktop/MyShortcut.lnk')
//
// szNewTarget - an ANSI string containing a new path to the target.

IShellLink *pShellLink;
IPersistFile *pPersistFile;

// Obtain IShellLink and IPersistFile pointers
::CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_ALL,
IID_IShellLink, (void**)&pShellLink);
pShellLink->QueryInterface(IID_IPersistFile, (void**)&pPersistFile);

// Load existing shortcut
pPersistFile->Load(wszLinkPath, STGM_READWRITE | STGM_SHARE_EXCLUSIVE);

// Modify shortcut (set a new target path)
pShellLink->SetPath(szNewTarget);

// Save the modified shortcut
pPersistFile->Save(wszLinkPath, TRUE);

// Commit I/O and release interface pointers
pPersistFile->Release();
pShellLink->Release();

Deleting Shortcut

Simply delete the shortcut file using the standard DeleteFile API function.

Desktop? Where Is It?

You will probably want to place your shiny new shortcut on the desktop or inthe Start menu. However the directories associated with the standard locationsmay vary for different Windows versions and localizations. If you want your codeto work on different computers, you should never assume that the Desktop directoryis C:/Document and Settings/All Users/Desktop or the Programs directory isC:/Program Files.

The Windows Shell API function SHGetSpecialFolderPath and the newerSHGetFolderPath retrieve the path of a special folder. However if acompatibility with older Windows versions is important, use the older functionSHGetSpecialFolderLocation, which is supported by all Windows versionsand does not require a redistributable.

TCHAR szDesktopPath[MAX_PATH];
LPITEMIDLIST pidl;
::SHGetSpecialFolderLocation(NULL, CSIDL_DESKTOPDIRECTORY, &pidl);
::SHGetPathFromIDList(pidl, szDesktopPath);

Remember to free the PIDL when it is no more needed:

LPMALLOC pMalloc;
::SHGetMalloc(&pMalloc);
pMalloc->Free(pidl);
pMalloc->Release();

Notifying the Explorer

After you created or deleted a shortcut, you may need to use the SHChangeNotifyfunction to notify the Windows Explorer about the change in the shortcut directory andto ensure the directory view is properly updated.


Downloads

All the content is provided on the "as is" basis and without any warranty, express or implied.You can use the supplied tools and examples for any commercial or non-commercial purpose withoutcharge. You can copy and redistribute these tools and examples freely, provided that you distributethe original umodified archives.

ln.zip - a command-line utility, similar to the Unix lncommand. See the enclosed readme.txt file for a description and usage examples.

links.zip - The complete Visual C++ source codefor the ln command-line utility. These sources supplement theProgramming Considerations sections and can be used as reference code.



From: http://www.flexhex.com/docs/articles/hard-links.phtml
原创粉丝点击