Using FileGetSymLinkTarget in Delphi XE5 does not return the network address to which it points

StackOverflow https://stackoverflow.com/questions/23409775

  •  13-07-2023
  •  | 
  •  

Question

I am using Delphi XE5 and XE6 and creating a number of directory links programmatically by shelling a process that prompts elevation and uses the Delphi function:

FileCreateSymLink( sLinkPath, sTargetPath )

From this point on, my use of sLinkPath is automatically vectored to sTarget path by the file system. This all works fine. At other times I also need to interrogate such a link to see (a) if it is a link and (b) where it points. To do this I call the Delphi function

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

With a successfully created link that points to another folder on my local hard disk, this works fine. However when I create a link that points to a network location such as

\\SERVER\Working\scratch\BJF\test

the link works perfectly at the file system level but Delphi calls to FileGetSymLinkTarget return false and a null target string. Stepping into SysUtils.pas reveals a call to “InternalGetFileNameFromSymLink” which in turn reveals a lot of hand waving to 'try' various calls to get sensible target information. I notice that inside this routine the one attempt that succeeds is the call to

GetObjectInfoName(Handle)

which reutrns

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

(close!) but which is then obliterated by ExpandVolumeName, probably because of the prefix, to a null string.

So, my question is:

Is this likely to be a bug in XE5 and XE6? Are there other ways of reading a link's target without using SysUtils?

LATER ADDITION BASED ON ACCEPTED ANSWER:

I have created a routine as follows based on Sertac's example that returns correct symlink paths for local drives and network paths. Although I am now calling this routine in place of SysUtils.FileGetSymLinkTarget, it may be useful to call SysUtils.FileGetSymLinkTarget first and only use my routine if the returned target is empty, perhaps to cope with redirections that I have not tried.

  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;
Was it helpful?

Solution

The following works in my test setup, which use 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;

The target path is displayed as \\?\UNC\Server\Share\Folder\SubFolder\. You can test the left-most side agains \\?\UNC and replace it with \ if you like.

You can also substitute VOLUME_NAME_NONE in place of FILE_NAME_NORMALIZED to have the target path as \Server\Share\Folder\SubFolder\.

The RTL use the same function in one of its tries with VOLUME_NAME_NT as result type which returns a path like Device\Mup\.., and then tries to match the starting part of the string with one of the local logical volumes (GetLogicalDriveStrings). When there's no match, since the path points to a network drive, it returns an empty string as you have noted.


Do note the comment in the RTL sources regarding symbolic links across machine boundary though:

The access rights of symlinks are unpredictable over network drives. It is therefore not recommended to create symlinks over a network drive. To enable remote access of symlinks under Windows Vista and Windows 7 use the command: "fsutil behavior set SymlinkEvaluation R2R:1 R2L:1"

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top