从symlink/reparse点获取直接目标路径
Get the immediate target path from symlink/reparse point
我知道 GetFinalPathNameByHandle 可用于获取符号 link 或重新分析点的目标,但在某些情况下不希望使用它:
- 如果目标不可用、不存在或无法打开,symlink 上的CreateFile 会失败,因此无法获取路径。
- 如果我将 symlink "a" 指向文件 "b" 并创建一个 symlink "b" 到文件 "c",函数跟随整个链,返回 "c".
- 当我手头已经有了实际 symlink 的句柄时,该函数就没有多大用处了。
似乎 DeviceIoControl 可以与 FSCTL_GET_REPARSE_POINT
一起使用来获取文件的实际重解析数据,但这让我得到了 REPARSE_DATA_BUFFER
,我将不得不解析那个。
我不知道系统实际上是如何处理重解析点的,但我认为目标位置是一条应该在某个时刻可用的信息。例如,dir
命令可以为任何重新分析点正确显示目标路径...好吧,我已经看到它只处理 symlinks 和挂载点(连接点)。
可以使用 REPARSE_DATA_BUFFER
instead. The MS open protocol specification 读取 Microsoft 重分析点也可能有用。
只有知道格式才能解析其他基于 GUID 的标签。
how the system actually processes reparse points
这是在文件系统和文件系统过滤驱动程序内部完成的。结果取决于调用 CreateFile
中使用的 FILE_FLAG_OPEN_REPARSE_POINT
选项(或 NT 调用中的 FILE_OPEN_REPARSE_POINT
)。
当指定 FILE_FLAG_OPEN_REPARSE_POINT
时 - 文件系统绕过文件的正常重分析点处理并尝试按原样直接打开重分析点文件。
如果未指定 FILE_OPEN_REPARSE_POINT
标志 - 文件系统尝试打开重分析点指向的文件(如果 fs 理解重分析点的格式 - 主要只有 Microsoft 重分析点)
reparse point中保存的数据格式为REPARSE_DATA_BUFFER
(Microsoft reparse point format) or REPARSE_GUID_DATA_BUFFER
- 需要在开始处查找ReparseTag
。
为了确定重分析点标记是否对应于 Microsoft 拥有的标记,我们使用 IsReparseTagMicrosoft
宏。
test/print 重解析点数据的代码:
volatile UCHAR guz;
ULONG TestReparsePoint(PCWSTR FileName)
{
HANDLE hFile = CreateFile(FileName, 0, FILE_SHARE_VALID_FLAGS, 0, OPEN_EXISTING,
FILE_FLAG_OPEN_REPARSE_POINT|FILE_FLAG_BACKUP_SEMANTICS, 0);
if (hFile == INVALID_HANDLE_VALUE)
{
return GetLastError();
}
union {
PVOID pv;
PULONG ReparseTag;
PREPARSE_DATA_BUFFER prdb;
PREPARSE_GUID_DATA_BUFFER prgdb;
};
PVOID stack = alloca(guz);
ULONG cb = 0, rcb = sizeof(REPARSE_DATA_BUFFER) + 0x100, BytesReturned;
ULONG dwError;
do
{
if (cb < rcb) cb = RtlPointerToOffset(pv = alloca(rcb - cb), stack);
if (DeviceIoControl(hFile, FSCTL_GET_REPARSE_POINT, 0, 0, pv, cb, &BytesReturned, 0))
{
dwError = NOERROR;
if (IsReparseTagMicrosoft(*ReparseTag))
{
char cc[16];
LPCSTR name;
switch (*ReparseTag)
{
case IO_REPARSE_TAG_SYMLINK:
name = " SYMLINK";
stack = prdb->SymbolicLinkReparseBuffer.PathBuffer;
break;
case IO_REPARSE_TAG_MOUNT_POINT:
name = " MOUNT_POINT";
stack = prdb->MountPointReparseBuffer.PathBuffer;
break;
default:
sprintf(cc, " %08x", prdb->ReparseTag);
name = cc;
}
DbgPrint(" %s->%.*S <%.*S>\n", name,
prdb->MountPointReparseBuffer.SubstituteNameLength >> 1,
RtlOffsetToPointer(stack, prdb->MountPointReparseBuffer.SubstituteNameOffset),
prdb->MountPointReparseBuffer.PrintNameLength >> 1,
RtlOffsetToPointer(stack, prdb->MountPointReparseBuffer.PrintNameOffset)
);
}
else
{
PGUID g = &prgdb->ReparseGuid;
DbgPrint(" tag=%x {%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x} size=%x\n", *ReparseTag,
g->Data1, g->Data2, g->Data3,
g->Data4[0],g->Data4[1],g->Data4[2],g->Data4[3],g->Data4[4],g->Data4[5],g->Data4[6],g->Data4[7],
prgdb->ReparseDataLength);
}
break;
}
rcb = IsReparseTagMicrosoft(*ReparseTag)
? REPARSE_DATA_BUFFER_HEADER_SIZE + prdb->ReparseDataLength
: REPARSE_GUID_DATA_BUFFER_HEADER_SIZE + prgdb->ReparseDataLength;
} while((dwError = GetLastError()) == ERROR_MORE_DATA);
CloseHandle(hFile);
return dwError;
}
我知道 GetFinalPathNameByHandle 可用于获取符号 link 或重新分析点的目标,但在某些情况下不希望使用它:
- 如果目标不可用、不存在或无法打开,symlink 上的CreateFile 会失败,因此无法获取路径。
- 如果我将 symlink "a" 指向文件 "b" 并创建一个 symlink "b" 到文件 "c",函数跟随整个链,返回 "c".
- 当我手头已经有了实际 symlink 的句柄时,该函数就没有多大用处了。
似乎 DeviceIoControl 可以与 FSCTL_GET_REPARSE_POINT
一起使用来获取文件的实际重解析数据,但这让我得到了 REPARSE_DATA_BUFFER
,我将不得不解析那个。
我不知道系统实际上是如何处理重解析点的,但我认为目标位置是一条应该在某个时刻可用的信息。例如,dir
命令可以为任何重新分析点正确显示目标路径...好吧,我已经看到它只处理 symlinks 和挂载点(连接点)。
可以使用 REPARSE_DATA_BUFFER
instead. The MS open protocol specification 读取 Microsoft 重分析点也可能有用。
只有知道格式才能解析其他基于 GUID 的标签。
how the system actually processes reparse points
这是在文件系统和文件系统过滤驱动程序内部完成的。结果取决于调用 CreateFile
中使用的 FILE_FLAG_OPEN_REPARSE_POINT
选项(或 NT 调用中的 FILE_OPEN_REPARSE_POINT
)。
当指定 FILE_FLAG_OPEN_REPARSE_POINT
时 - 文件系统绕过文件的正常重分析点处理并尝试按原样直接打开重分析点文件。
如果未指定 FILE_OPEN_REPARSE_POINT
标志 - 文件系统尝试打开重分析点指向的文件(如果 fs 理解重分析点的格式 - 主要只有 Microsoft 重分析点)
reparse point中保存的数据格式为REPARSE_DATA_BUFFER
(Microsoft reparse point format) or REPARSE_GUID_DATA_BUFFER
- 需要在开始处查找ReparseTag
。
为了确定重分析点标记是否对应于 Microsoft 拥有的标记,我们使用 IsReparseTagMicrosoft
宏。
test/print 重解析点数据的代码:
volatile UCHAR guz;
ULONG TestReparsePoint(PCWSTR FileName)
{
HANDLE hFile = CreateFile(FileName, 0, FILE_SHARE_VALID_FLAGS, 0, OPEN_EXISTING,
FILE_FLAG_OPEN_REPARSE_POINT|FILE_FLAG_BACKUP_SEMANTICS, 0);
if (hFile == INVALID_HANDLE_VALUE)
{
return GetLastError();
}
union {
PVOID pv;
PULONG ReparseTag;
PREPARSE_DATA_BUFFER prdb;
PREPARSE_GUID_DATA_BUFFER prgdb;
};
PVOID stack = alloca(guz);
ULONG cb = 0, rcb = sizeof(REPARSE_DATA_BUFFER) + 0x100, BytesReturned;
ULONG dwError;
do
{
if (cb < rcb) cb = RtlPointerToOffset(pv = alloca(rcb - cb), stack);
if (DeviceIoControl(hFile, FSCTL_GET_REPARSE_POINT, 0, 0, pv, cb, &BytesReturned, 0))
{
dwError = NOERROR;
if (IsReparseTagMicrosoft(*ReparseTag))
{
char cc[16];
LPCSTR name;
switch (*ReparseTag)
{
case IO_REPARSE_TAG_SYMLINK:
name = " SYMLINK";
stack = prdb->SymbolicLinkReparseBuffer.PathBuffer;
break;
case IO_REPARSE_TAG_MOUNT_POINT:
name = " MOUNT_POINT";
stack = prdb->MountPointReparseBuffer.PathBuffer;
break;
default:
sprintf(cc, " %08x", prdb->ReparseTag);
name = cc;
}
DbgPrint(" %s->%.*S <%.*S>\n", name,
prdb->MountPointReparseBuffer.SubstituteNameLength >> 1,
RtlOffsetToPointer(stack, prdb->MountPointReparseBuffer.SubstituteNameOffset),
prdb->MountPointReparseBuffer.PrintNameLength >> 1,
RtlOffsetToPointer(stack, prdb->MountPointReparseBuffer.PrintNameOffset)
);
}
else
{
PGUID g = &prgdb->ReparseGuid;
DbgPrint(" tag=%x {%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x} size=%x\n", *ReparseTag,
g->Data1, g->Data2, g->Data3,
g->Data4[0],g->Data4[1],g->Data4[2],g->Data4[3],g->Data4[4],g->Data4[5],g->Data4[6],g->Data4[7],
prgdb->ReparseDataLength);
}
break;
}
rcb = IsReparseTagMicrosoft(*ReparseTag)
? REPARSE_DATA_BUFFER_HEADER_SIZE + prdb->ReparseDataLength
: REPARSE_GUID_DATA_BUFFER_HEADER_SIZE + prgdb->ReparseDataLength;
} while((dwError = GetLastError()) == ERROR_MORE_DATA);
CloseHandle(hFile);
return dwError;
}