check_stat($tmpfile1, $tmpfile2, "check directory and link stat are the same");
ok(unlink($tmpfile2), "and we can unlink the symlink (rather than only rmdir)");
+# test our various name based directory tests
+{
+ use Win32API::File qw(GetFileAttributes FILE_ATTRIBUTE_DIRECTORY
+ INVALID_FILE_ATTRIBUTES);
+ # we can't use lstat() here, since the directory && symlink state
+ # can't be preserved in it's result, and normal stat would
+ # follow the link (which is broken for most of these)
+ # GetFileAttributes() doesn't follow the link and can present the
+ # directory && symlink state
+ my @tests =
+ (
+ "x:",
+ "x:\\",
+ "x:/",
+ "unknown\\",
+ "unknown/",
+ ".",
+ "..",
+ );
+ for my $path (@tests) {
+ ok(symlink($path, $tmpfile2), "symlink $path");
+ my $attr = GetFileAttributes($tmpfile2);
+ ok($attr != INVALID_FILE_ATTRIBUTES && ($attr & FILE_ATTRIBUTE_DIRECTORY) != 0,
+ "symlink $path: treated as a directory");
+ unlink($tmpfile2);
+ }
+}
+
# to check the unlink code for symlinks isn't mis-handling non-symlink
# directories
ok(!unlink($tmpfile1), "we can't unlink the original directory");
win32_symlink(const char *oldfile, const char *newfile)
{
dTHX;
- const char *dest_path = oldfile;
- char szTargetName[MAX_PATH+1];
size_t oldfile_len = strlen(oldfile);
pCreateSymbolicLinkA_t pCreateSymbolicLinkA =
(pCreateSymbolicLinkA_t)GetProcAddress(GetModuleHandle("kernel32.dll"), "CreateSymbolicLinkA");
- DWORD dest_attr;
DWORD create_flags = 0;
/* this flag can be used only on Windows 10 1703 or newer */
/* are we linking to a directory?
CreateSymlinkA() needs to know if the target is a directory,
- if the oldfile is relative we need to make a relative path
- based on the newfile
+ If it looks like a directory name:
+ - ends in slash
+ - is just . or ..
+ - ends in /. or /.. (with either slash)
+ - is a simple drive letter
+ assume it's a directory.
+
+ Otherwise if the oldfile is relative we need to make a relative path
+ based on the newfile to check if the target is a directory.
*/
+ if ((oldfile_len >= 1 && isSLASH(oldfile[oldfile_len-1])) ||
+ strEQ(oldfile, "..") ||
+ strEQ(oldfile, ".") ||
+ (isSLASH(oldfile[oldfile_len-2]) && oldfile[oldfile_len-1] == '.') ||
+ strEQ(oldfile+oldfile_len-3, "\\..") ||
+ strEQ(oldfile+oldfile_len-3, "/..") ||
+ (oldfile_len == 2 && oldfile[1] == ':')) {
+ create_flags |= SYMBOLIC_LINK_FLAG_DIRECTORY;
+ }
+ else { /* indent in a second commit */
+ DWORD dest_attr;
+ const char *dest_path = oldfile;
+ char szTargetName[MAX_PATH+1];
if (oldfile_len >= 3 && oldfile[1] == ':' && oldfile[2] != '\\' && oldfile[2] != '/') {
/* relative to current directory on a drive */
/* dest_path = oldfile; already done */
if (dest_attr != (DWORD)-1 && (dest_attr & FILE_ATTRIBUTE_DIRECTORY)) {
create_flags |= SYMBOLIC_LINK_FLAG_DIRECTORY;
}
+ }
if (!pCreateSymbolicLinkA(newfile, oldfile, create_flags)) {
translate_to_errno();