You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
In Windows, NTFS supports something called a reparse point (also known as a junction). They always point to directories and are similar to hard links in Unix. Unfortunately, when the visit method encounters a junction, it tries to open it as a regular directory and dies:
Error opendir on '/Users/LushKava/Application Data': Invalid argument at backup line 121.
This is particularly awkward when traversing one's home directory because Windows makes extensive use of junctions, for reasons of backward compatibility. There are two potential solutions:
Follow junctions as if they were directory symlinks (where follow_symlinks => 1)
Ignore junctions altogether
It is rare for a Windows user to create a junction of his or her own accord, especially given that NTFS also supports genuine file/directory symlinks. In practice, junctions exist only as a form of backward compatibility goop. Therefore, I don't think that ignoring them would be so bad. In fact, I needed to do exactly that in a backup tool that I am writing.
Note that, while Win32::Symlink exists in CPAN, it appears to have some serious flaws. Firstly, despite the name, it doesn't appear to support actual symlinks. Secondly, it often returns undef when using the readlink method on perfectly valid junctions.
So, I decided to go for the second option. I did it by creating a function that can determine whether a given path is actually a junction.
use Path::Tiny;
formy$p (glob'C:/Users/LushKava/*') {
print is_junction($p) ? "* $p\n" : "$p\n";
}
subis_junction {
my ($dir) = @_;
state $last_parent;;
state $junction_by;
my$path = path($dir);
if (! $path->is_dir || $path->is_rootdir) {
return 0;
}
if (! defined$last_parent || $path->parent ne$last_parent) {
$junction_by = { map { $_=> 1 } list_junctions($path->parent) };
$last_parent = $path->parent;
}
returnexists$junction_by->{$path->basename};
}
sublist_junctions {
my ($dir) = @_;
my$path = path($dir);
if (! $path->is_dir) {
return ();
}
my$cmd = sprintf'dir /AL /B "%s" 2>&1', $path->canonpath;
my@lines = `$cmd`;
chomp@lines;
if ($? >> 8) {
if ($lines[0] eq'File Not Found') {
return ();
} else {
die"Failed to execute: $cmd";
}
}
return@lines;
}
There are a few things to note here. Firstly, the /AL switch ensures that only junctions and symlinks are listed. Secondly, if none are listed, it is normal for the command to return an exit status of 1 and print "File Not Found" to STDERR. Thirdly, is_junction caches the results of list_junctions, only updating the cache if asked to check a path whose parent is different from the last time that it was called. This speeds it up significantly during recursion, without unduly wasting memory.
Further, it would be trivial to adapt this code so as to map the targets of the junctions/links, in case one wanted to properly support the follow_symlinks option.
In any case, the above approach is working reliably for me. Here is some sample output:
Let me see if I understand: on Win32 NTFS, a directory junction has -d true, and -l false, but can be resolved with readlink?
I wouldn't want to shell out to find reparse points. I'm more inclined to add Win32API::File as a prerequisite on Win32. I think it ships with ActiveState and Strawberry (would need to check that), so it's effectively a "core" Win32 module. It has GetFileAttributes that can detect a reparse point.
In my quick reading about directory junctions, they seem more like symbolic links, so I'd like to treat them that way.
Either as an alternative to, or in addition to the above, possibly iterator and visit could get an ignore_errors option that would skip directories that can't be opened.
A junction has -d true and -l false and cannot be resolved with readlink, as provided by the Strawberry Perl core. As mentioned, Win32::Symlink is unreliable and its own readlink method is not going to be a soution.
Alas, the same appears to be true of symlinks. I suppose that could be considered as a bug in Strawberry (I haven't tested ActiveState yet).
I don't know why I forgot about the existence of Win32API::File. It seems ideal so I'm going to test it on both symlinks and junctions and will let you know how that pans out.
While an ignore_errors option might have its own utility, I would not to mask errors in order to silence this particular issue.
In Windows, NTFS supports something called a reparse point (also known as a junction). They always point to directories and are similar to hard links in Unix. Unfortunately, when the visit method encounters a junction, it tries to open it as a regular directory and dies:
Error opendir on '/Users/LushKava/Application Data': Invalid argument at backup line 121.
This is particularly awkward when traversing one's home directory because Windows makes extensive use of junctions, for reasons of backward compatibility. There are two potential solutions:
It is rare for a Windows user to create a junction of his or her own accord, especially given that NTFS also supports genuine file/directory symlinks. In practice, junctions exist only as a form of backward compatibility goop. Therefore, I don't think that ignoring them would be so bad. In fact, I needed to do exactly that in a backup tool that I am writing.
Note that, while
Win32::Symlink
exists in CPAN, it appears to have some serious flaws. Firstly, despite the name, it doesn't appear to support actual symlinks. Secondly, it often returns undef when using the readlink method on perfectly valid junctions.So, I decided to go for the second option. I did it by creating a function that can determine whether a given path is actually a junction.
There are a few things to note here. Firstly, the
/AL
switch ensures that only junctions and symlinks are listed. Secondly, if none are listed, it is normal for the command to return an exit status of 1 and print "File Not Found" to STDERR. Thirdly,is_junction
caches the results oflist_junctions
, only updating the cache if asked to check a path whose parent is different from the last time that it was called. This speeds it up significantly during recursion, without unduly wasting memory.Further, it would be trivial to adapt this code so as to map the targets of the junctions/links, in case one wanted to properly support the follow_symlinks option.
In any case, the above approach is working reliably for me. Here is some sample output:
Ideally, I'd like to see something like this implemented as part of the visit method in Path::Tiny, so that I can go back to using it in Windows.
The text was updated successfully, but these errors were encountered: