在Delphi XE5中使用FileGetSymLinkTarget无法返回指向的网络地址。

3

我正在使用Delphi XE5和XE6,通过启动一个需要管理员权限的进程并使用Delphi函数来编写多个目录链接。

FileCreateSymLink( sLinkPath, sTargetPath )

从这一点开始,我的sLinkPath使用会自动被文件系统定向到sTarget路径。这个功能运作良好。有时我还需要查询这样的链接,以了解它是否为链接以及指向何处。为此,我调用Delphi函数。

function FileGetSymLinkTarget(const FileName: string; var TargetName: string): Boolean;

如果我成功地创建了一个指向本地硬盘上另一个文件夹的链接,这很好。但是当我创建一个指向网络位置(例如)的链接时

\\SERVER\Working\scratch\BJF\test

该链接在文件系统级别上运行良好,但Delphi调用FileGetSymLinkTarget返回false和空目标字符串。进入SysUtils.pas会发现调用了"InternalGetFileNameFromSymLink",这又揭示出使用“try”尝试各种调用来获取有意义的目标信息的许多手势。我注意到,在此例程内部,成功的唯一尝试是调用

GetObjectInfoName(Handle)

返回

\Device\Mup\SERVER\Working\scratch\BJF\test

(关闭!)但是,由于前缀的原因,ExpandVolumeName将其转换为空字符串。

因此,我的问题是:

这可能是XE5和XE6中的一个错误吗? 是否有其他方法可以读取链接的目标而不使用SysUtils?

基于Sertac的示例,我创建了一个程序,它返回本地驱动器和网络路径的正确符号链接路径。虽然我现在调用这个程序来代替SysUtils.FileGetSymLinkTarget,但如果返回的目标为空,也许是为了应对我尚未尝试的重定向,可以先调用SysUtils.FileGetSymLinkTarget,然后仅使用我的程序。

  function MyFileGetSymLinkTarget( const APathToLink : string; var ATarget : string ) : boolean;
  var
    LinkHandle: THandle;
    TargetName: array [0..OFS_MAXPATHNAME-1] of Char;
  begin
    ATarget := '';
    LinkHandle := CreateFile( PChar(APathToLink), 0, FILE_SHARE_READ, nil,
        OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0);
    Win32Check(LinkHandle <> INVALID_HANDLE_VALUE);
    try
      Result := GetFinalPathNameByHandle(LinkHandle, TargetName, OFS_MAXPATHNAME, FILE_NAME_NORMALIZED) > 0;
      if Result then
        begin
        ATarget := TargetName;
        if Pos( '\\?\UNC\', ATarget ) = 1 then
           begin
           Delete( ATarget, 1, 8 );
           Insert( '\\', ATarget, 1 );
           end
          else
          if Pos( '\\?\', ATarget ) = 1 then
             Delete( ATarget, 1, 4 );
        end;
    finally
      CloseHandle(LinkHandle);
    end;
  end;

ExpandVolumeName() 将对象前缀转换为本地驱动器字母。由于您的目标不是映射到本地驱动器字母而是映射到 UNC 路径,因此它返回一个空字符串。FileGetSymLinkTarget() 使用未记录的 ObjectNameInformation 标志调用 NTQueryObject() 来获取对象名称,这就是为什么您会得到系统定义的名称。 - Remy Lebeau
1
符号链接在Windows上是作为重分析点实现的,Microsoft有用于处理重分析点的API,例如FSCTL_GET_REPARSE_POINT。我很惊讶RTL竟然采用手动hack而不使用它们。 - Remy Lebeau
感谢您的评论,Remy。看起来RTL的运作方式确实有些奇怪。我将与EMB提出一个问题,也许会有人去看一下……?! - Brian Frost
请使用(Ansi)StartsText()来代替使用Pos()=1。当删除\\?\UNC\时,请单独使用Delete(ATarget, 3, 6),而不是分别使用Delete()Insert() - Remy Lebeau
Remy提出的建议很好,谢谢。 - Brian Frost
1个回答

3
在我的测试环境中,以下内容有效,它使用了GetFinalPathNameByHandle函数。
var
  LinkHandle: THandle;
  TargetName: array [0..512] of Char;
begin
  LinkHandle := CreateFile('[path to sym link]', 0, FILE_SHARE_READ, nil,
      OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0);
  Win32Check(LinkHandle <> INVALID_HANDLE_VALUE);
  try
    if GetFinalPathNameByHandle(LinkHandle, TargetName, 512,
        FILE_NAME_NORMALIZED) > 0 then
      ShowMessage(TargetName)
    else
      RaiseLastOSError;
  finally
    CloseHandle(LinkHandle);
  end;

end;

目标路径显示为\\?\UNC\Server\Share\Folder\SubFolder\。如果您愿意,可以针对左侧再次进行测试,并将其替换为\
您还可以在FILE_NAME_NORMALIZED的位置使用VOLUME_NAME_NONE进行替换,以使目标路径为\Server\Share\Folder\SubFolder\
RTL在其中一次尝试中使用相同的函数,以VOLUME_NAME_NT作为结果类型,返回类似Device\Mup\..的路径,然后尝试将字符串的起始部分与一个本地逻辑卷匹配(GetLogicalDriveStrings)。 当没有匹配时,因为路径指向网络驱动器,它将返回一个空字符串,正如您所注意到的那样。
请注意RTL源中关于跨机器边界的符号链接的注释:

符号链接的访问权限在网络驱动器上是不可预测的。 因此,不建议在网络驱动器上创建符号链接。 要在Windows Vista和Windows 7下启用符号链接的远程访问,请使用命令:“fsutil行为设置SymlinkEvaluation R2R:1 R2L:1”


这个完美地运行了,谢谢。我已经根据您上面的代码编辑了我的问题,并展示了我的最终解决方案,现在可以在本地和网络驱动器上工作。我看到了关于符号链接和网络驱动器的RTL注意事项,但我只会在本地创建它们并(可能)指向网络位置,所以我认为这对我来说是可以的。 - Brian Frost
@Brian - 欢迎。你可能是对的,这个评论可能不适用于你的情况。如果你需要在你的情况下更改行为,那么你需要使用的参数是 L2RL2L,如 fsutil 的文档所指定的那样。 - Sertac Akyuz

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接