1

I have a problem with detecting symbolic links under Windows 10, which supports them. First I tried this:

if(! -l $import_filename) {
    print "$0: $import_filename is not a symlink";
}

That doesn't work. It gets executed when $import_filename is a symlink. Then I tried this:

use File::stat;
use Fcntl;

my $statbuf = lstat($import_filename);
if(!($statbuf->mode & S_ISLNK)) {
    print "$0: $import_filename is not a symlink";
}

And it seems to be a different way to say the same thing. As expected, is there any blessed way to do this under Windows versions with symlink/junction support? If there isn't, a command line tool is also an acceptable answer.

Jesse Lactin
  • 301
  • 3
  • 12

2 Answers2

3

Given

>mklink file_symlink file
symbolic link created for file_symlink <<===>> file

>mklink /d dir_symlink dir
symbolic link created for dir_symlink <<===>> dir

>mklink /h file_hardlink file
Hardlink created for file_hardlink <<===>> file

>mklink /j dir_hardlink dir
Junction created for dir_hardlink <<===>> dir

>dir
...
2018-05-09  12:59 AM    <JUNCTION>     dir_hardlink [C:\...\dir]
2018-05-09  12:58 AM    <SYMLINKD>     dir_symlink [dir]
2018-05-09  12:56 AM                 6 file_hardlink
2018-05-09  12:58 AM    <SYMLINK>      file_symlink [file]
...

You can use the following to detect file_symlink, dir_symlink and dir_hardlink (but not file_hardlink) as a link:

use Win32API::File qw( GetFileAttributes FILE_ATTRIBUTE_REPARSE_POINT );

my $is_link = GetFileAttributes($qfn) & FILE_ATTRIBUTE_REPARSE_POINT;

I don't know how to distinguish between hard links and symlinks (though differentiating between files and dirs can be done using & FILE_ATTRIBUTE_DIRECTORY).

ikegami
  • 367,544
  • 15
  • 269
  • 518
  • FILE_ATTRIBUTE_REPARSE_POINT can be other things than links, you need to also obtain the reparse point data and check if it is actually a symbolic link. So that $is_link code is, to the best of my knowledge, wrong. – E. T. Feb 01 '21 at 04:34
0

There seem to be not much Perl support for working with symlinks on Windows (if any). Neither of related builtins are implemented, according to perlport page for symlink and for readlink.

Most importantly for your direct question, lstat isn't implemented either so one can't have a filetest for symlink. The perlport for -X says that

-g, -k, -l, -u, -A are not particularly meaningful.

I haven't found anything on CPAN, other than using the Windows API.


Then you can go to the Windows command line, and look for <SYMLINK> in dir output

# Build $basename and $path from $import_filename if needed
if ( not grep { /<SYMLINK>.*$basename/ } qx(dir $path) ) {
    say "$0: $import_filename is not a symlink";
}

where $basename need be used since dir doesn't show the path. The components of a filename with full path can be obtained for example with the core module File::Spec

use File::Spec;
my ($vol, $path, $basename) = File::Spec->splitpath($import_filename);

If the filename has no path then $vol and $path are empty strings, which is OK for dir as it needs no argument for the current directory. If $import_filename by design refers to the current directory (has no path) then use it in the regex, with qx(dir) (no argument).

The dir output shows the target name as well, what can come in handy.

zdim
  • 64,580
  • 5
  • 52
  • 81