Quick background to this question as I'm sure it'll raise a few eyebrows: I'm developing a command line tool in C for making backups, and I am implementing incremental backups using NTFS hard links. Thus, if symbolic links exists in a prior backup, I must be able to point to the symbolic links themselves, not the target.
Unfortunately, the page for CreateHardLink clearly states:
Symbolic link behavior—If the path points to a symbolic link, the function creates a hard link to the target.
Now I'm stuck wondering, what's the solution to this? How can I create a hardlink that points to a symbolic link itself as opposed to the target? I did notice Windows' internal command MKLINK
appears to be able to create hardlinks to symlinks. So theoretically, I guess I could just use the system
function in C, but to be honest, it feels lazy and I tend to avoid it. Is there possibly a solution using only the Win32 API?
I also came across some code snippets from a Google developer ([1] [2]), with some details on the implementation of CreateHardLink
and whatnot, but it seemed a little too low level for me to make any real sense out of it. Also, (and I could be wrong about this) the functions provided in the GitHub repo seem to only be compatible with Windows 10 and later, but I'd hope to at least support Windows 7 as well.
CodePudding user response:
CreateHardLink
create hard link to the symbolic link (reparse point) themselves, not to the target. so documentation simply is wrong. the lpExistingFileName
opened with option FILE_OPEN_REPARSE_POINT
so you can simply use CreateHardLink
as is and nothing more need todo. visa versa - if you want create hard link to target, you need custom implementation of CreateHardLink
and not use FILE_OPEN_REPARSE_POINT
(if you will use NtOpenFile
) or FILE_FLAG_OPEN_REPARSE_POINT
if you use CreatFileW
)
I did notice Windows' internal command MKLINK appears to be able to create hardlinks to symlinks.
if you debug cmd.exe with mklink
command you easy can notice that also simply CreateHardLinkW
api called (set breakpoint to it)
after you create hardlink to symlink file you can view in explorer that type of file is .symlink . for test we can remove link from target file ( by use FSCTL_DELETE_REPARSE_POINT
) if hardlink point to target - any operation with symlink not affect hardlink. but if we created hardlink to symlink intself - after break symlink - hard link will also be breaked:
void TestCreateHardLink(PCWSTR lpFileName, PCWSTR lpSymlinkFileName, PCWSTR lpExistingFileName)
{
if (CreateSymbolicLinkW(lpSymlinkFileName, lpExistingFileName, 0))
{
if (CreateHardLinkW(lpFileName, lpSymlinkFileName, 0))
{
HANDLE hFile = CreateFileW(lpSymlinkFileName, FILE_WRITE_ATTRIBUTES, FILE_SHARE_DELETE, 0, OPEN_EXISTING,
FILE_FLAG_OPEN_REPARSE_POINT, 0);
if (hFile != INVALID_HANDLE_VALUE)
{
REPARSE_DATA_BUFFER rdb = { IO_REPARSE_TAG_SYMLINK };
OVERLAPPED ov {};
if (DeviceIoControl(hFile, FSCTL_DELETE_REPARSE_POINT, &rdb, sizeof(rdb), 0, 0, 0, &ov))
{
MessageBoxW(0, 0, 0, 0);
}
CloseHandle(hFile);
}
DeleteFileW(lpFileName);
}
DeleteFileW(lpSymlinkFileName);
}
}
we want more flexible implementation of hardlink create (set target) , can use next code:
HRESULT CreateHardLinkExW(PCWSTR lpFileName, PCWSTR lpExistingFileName, BOOLEAN ReplaceIfExisting, BOOLEAN bToTarget)
{
HANDLE hFile = CreateFileW(lpExistingFileName, 0, FILE_SHARE_READ|FILE_SHARE_WRITE, 0, OPEN_EXISTING,
bToTarget ? FILE_FLAG_BACKUP_SEMANTICS : FILE_FLAG_BACKUP_SEMANTICS|FILE_FLAG_OPEN_REPARSE_POINT, 0);
if (hFile == INVALID_HANDLE_VALUE)
{
return HRESULT_FROM_WIN32(GetLastError());
}
UNICODE_STRING NtName;
NTSTATUS status = RtlDosPathNameToNtPathName_U_WithStatus(lpFileName, &NtName, 0, 0);
if (0 <= status)
{
ULONG Length = FIELD_OFFSET(FILE_LINK_INFORMATION, FileName) NtName.Length;
if (PFILE_LINK_INFORMATION LinkInfo = (PFILE_LINK_INFORMATION)_malloca(Length))
{
LinkInfo->ReplaceIfExists = ReplaceIfExisting;
LinkInfo->RootDirectory = 0;
LinkInfo->FileNameLength = NtName.Length;
memcpy(LinkInfo->FileName, NtName.Buffer, NtName.Length);
IO_STATUS_BLOCK iosb;
status = NtSetInformationFile(hFile, &iosb, LinkInfo, Length, FileLinkInformation);
}
else
{
status = STATUS_NO_MEMORY;
}
RtlFreeUnicodeString(&NtName);
}
CloseHandle(hFile);
return 0 > status ? HRESULT_FROM_NT(status) : S_OK;
}