用 Win32 API 硬 link 到符号 link?

Hard link to a symbolic link with Win32 API?

这个问题的快速背景知识,因为我相信它会引起一些人的注意:我正在用 C 语言开发一个命令行工具来进行备份,并且我正在使用 NTFS 硬盘实现增量备份 links。因此,如果符号 links 存在于先前的备份中,我必须能够指向符号 links 本身,而不是目标。

不幸的是,CreateHardLink 的页面明确指出:

Symbolic link behavior—If the path points to a symbolic link, the function creates a hard link to the target.

现在我一直在想,解决这个问题的方法是什么?我如何创建一个指向符号 link 本身而不是目标的 hardlink?我确实注意到 Windows' 内部命令 MKLINK 似乎能够创建 hardlinks 到 symlinks。所以理论上,我想我可以在 C 中使用 system 函数,但说实话,它感觉很懒,我倾向于避免它。是否有仅使用 Win32 的解决方案 API?

我还看到了一些来自 Google 开发人员 ([1] [2]) 的代码片段,其中有一些关于 CreateHardLink 实现的细节等等,但似乎有点太低了让我真正理解它的水平。此外,(我对此可能是错误的)GitHub 存储库中提供的功能似乎只与 Windows 10 及更高版本兼容,但我希望至少支持 Windows 7也是。

CreateHardLink 创建硬 link 到符号 link(重新分析点)本身,而不是目标。所以文档是错误的。 lpExistingFileName 使用选项 FILE_OPEN_REPARSE_POINT 打开 所以你可以简单地使用 CreateHardLink 并且不需要做更多的事情。反之亦然 - 如果你想创建硬 link 目标,你需要 CreateHardLink 的自定义实现而不是使用 FILE_OPEN_REPARSE_POINT(如果你将使用 NtOpenFile)或 FILE_FLAG_OPEN_REPARSE_POINT 如果你使用 CreatFileW )

I did notice Windows' internal command MKLINK appears to be able to create hardlinks to symlinks.

如果你用 mklink 命令调试 cmd.exe 你会很容易注意到 CreateHardLinkW api 调用(设置断点)


在创建 hardlink 到 symlink 文件后,您可以在资源管理器中查看文件类型为 .symlink 。对于测试,如果 hardlink 指向目标,我们可以从目标文件中删除 link(通过使用 FSCTL_DELETE_REPARSE_POINT)——任何使用 symlink 的操作都不会影响 hardlink .但是如果我们自己创建 hardlink 到 symlink - 在 break symlink 之后 - hard link 也会被 break:

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);
    }
}

我们想要更灵活的实现hardlink create(set target),可以使用下一个代码:

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;
}