Jesse Lactin
Jesse Lactin

Reputation: 311

Perl - Detecting symbolic links under Windows 10

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.

Upvotes: 1

Views: 1148

Answers (2)

zdim
zdim

Reputation: 66891

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.

Upvotes: 0

ikegami
ikegami

Reputation: 385916

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).

Upvotes: 3

Related Questions