今日碰到有程序在我们产品系统环境下无法正常运行某些功能,使用Process Monitor加反复测试发现,与产品中创建的symlink(软链接)有关。具体来讲,symlink文件是一个软链接文件,它的链接目标可以是任意盘上的文件。当访问软链接文件时,Windows文件系统(NTFS)会对该文件进行重解析(reparse),使其打开真正的目标文件。这里产生一个问题,并非所有的Win32 API都实现访问软链接文件时就自动指向目标文件,因为Windows要保证既能处理软链接文件也能处理目标文件。
这里特别要指出的一个Win32 API是GetFileAttributeEx,用它去读取软链接文件属性时(比较便捷,可一次性获取文件的大小、时间和属性),并不会自动获取到目标文件的属性,软链接文件的大小和属性一般必然与目标文件的不同,如果不做处理,可能获取到的并非期望的文件属性并产生错误的判断!前面讲到的有些程序无法正常运行就与此有关。我们来模拟一下看看,先创建一个软链接:
在target.txt中随便输入些字符,保存。然后用资源管理器看link.txt的属性:
再看target.txt的属性:
可以看到两个文件的真实属性完全不一样。一般程序如果不是明确意图要操作软链接文件本身,则应该访问的是目标文件。但GetFileAttibutesEx是众多仅访问软链接文件的函数之一,如果要获取目标文件的属性,必须对软链接的文件属性做一个判断,如果确认是软链接,再进一步访问目标文件。这里就利用到CreateFile不指定reparse标志的情况下默认打开目标文件的特性(Windows是利用CreateFile来做重解析,所以最好使用其打开句柄访问文件才能保证访问的是目标文件)。直接上代码:
DWORD GetRealAttr(LPCTSTR file_name, WIN32_FILE_ATTRIBUTE_DATA& output_attr)
{
DWORD err = ERROR_SUCCESS;
WIN32_FILE_ATTRIBUTE_DATA attr_data { 0 };
//AdjustPrivilege(SE_CREATE_SYMBOLIC_LINK_NAME, TRUE);
if (::GetFileAttributesEx(file_name, GetFileExInfoStandard, &attr_data)) {
//_tprintf(_T("link file size=%I64d\n"), ((LONGLONG)attr_data.nFileSizeHigh << 32) | attr_data.nFileSizeLow);
if (attr_data.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) {
HANDLE hFile = ::CreateFile(file_name,
FILE_READ_DATA,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (INVALID_HANDLE_VALUE != hFile) {
BY_HANDLE_FILE_INFORMATION target_info{ 0 };
if (GetFileInformationByHandle(hFile, &target_info)) {
output_attr.dwFileAttributes = target_info.dwFileAttributes;
output_attr.ftCreationTime = target_info.ftCreationTime;
output_attr.ftLastAccessTime = target_info.ftLastAccessTime;
output_attr.ftLastWriteTime = target_info.ftLastWriteTime;
output_attr.nFileSizeHigh = target_info.nFileSizeHigh;
output_attr.nFileSizeLow = target_info.nFileSizeLow;
//_tprintf(_T("target file size=%I64d\n"), ((LONGLONG)target_info.nFileSizeHigh << 32) | target_info.nFileSizeLow);
}
else {
err = ::GetLastError();
}
::CloseHandle(hFile);
}
else {
err = ::GetLastError();
}
}
else {
memcpy(&output_attr, &attr_data, sizeof(WIN32_FILE_ATTRIBUTE_DATA));
}
}
else {
err = ::GetLastError();
}
return err;
}
最后我们看实际读取到的link.txt和target.txt的文件大小: