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