This is a live mirror of the Perl 5 development currently hosted at https://github.com/perl/perl5
lstat(), readlink() and unlink() treat directory junctions as symlinks
authorTony Cook <tony@develop-help.com>
Thu, 15 Oct 2020 04:11:13 +0000 (15:11 +1100)
committerTony Cook <tony@develop-help.com>
Tue, 1 Dec 2020 04:29:33 +0000 (15:29 +1100)
pod/perlport.pod
t/win32/stat.t
t/win32/symlink.t
win32/win32.c

index 42e178a..63869d5 100644 (file)
@@ -1526,6 +1526,9 @@ C<-x>, C<-o>.
 (Win32, VMS, S<RISC OS>)
 C<-g>, C<-k>, C<-l>, C<-u>, C<-A> are not particularly meaningful.
 
+(Win32)
+C<-l> returns true for both symlinks and directory junctions.
+
 (VMS, S<RISC OS>)
 C<-p> is not particularly meaningful.
 
@@ -1952,7 +1955,7 @@ but usually by no more than an hour.
 Not implemented.
 
 (Win32)
-Return values (especially for device and inode) may be bogus.
+Treats directory junctions as symlinks.
 
 =item msgctl
 
@@ -1982,9 +1985,13 @@ implications for your code.
 
 =item readlink
 
-(Win32, VMS, S<RISC OS>)
+(VMS, S<RISC OS>)
 Not implemented.
 
+(Win32)
+readlink() on a directory junction returns the object name, not a
+simple path.
+
 =item rename
 
 (Win32)
index 6046994..09f52ad 100644 (file)
@@ -112,10 +112,10 @@ if (system("mklink /d $tmpfile1 win32") == 0) {
     rmdir( $tmpfile1 );
 }
 
-# check a junction doesn't look like a symlink
+# check a junction looks like a symlink
 
 if (system("mklink /j $tmpfile1 win32") == 0) {
-    ok(!-l $tmpfile1, "lstat doesn't see a symlink on the directory junction");
+    ok(-l $tmpfile1, "lstat sees a symlink on the directory junction");
 
     rmdir( $tmpfile1 );
 }
index 9716f37..e9088cd 100644 (file)
@@ -59,9 +59,10 @@ ok(mkdir($tmpfile1), "make a directory");
 # this may only work in an admin shell
 # MKLINK [[/D] | [/H] | [/J]] Link Target
 if (system("mklink /j $tmpfile2 $tmpfile1") == 0) {
-    ok(!-l $tmpfile2, "junction doesn't look like a symlink");
-    ok(!unlink($tmpfile2), "no unlink magic for junctions");
-    rmdir($tmpfile2);
+    ok(-l $tmpfile2, "junction does look like a symlink");
+    like(readlink($tmpfile2), qr/\Q$tmpfile1\E$/,
+         "readlink() works on a junction");
+    ok(unlink($tmpfile2), "unlink magic for junctions");
 }
 rmdir($tmpfile1);
 
index eb11e14..65d154d 100644 (file)
@@ -1660,8 +1660,8 @@ https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/ntifs/ns-ntifs-_re
 Renamed to avoid conflicts, apparently some SDKs define this
 structure.
 
-Hoisted the symlink data into a new type to allow us to make a pointer
-to it, and to avoid C++ scoping issues.
+Hoisted the symlink and mount point data into a new type to allow us
+to make a pointer to it, and to avoid C++ scoping issues.
 
 */
 
@@ -1675,18 +1675,20 @@ typedef struct {
 } MY_SYMLINK_REPARSE_BUFFER, *PMY_SYMLINK_REPARSE_BUFFER;
 
 typedef struct {
+    USHORT SubstituteNameOffset;
+    USHORT SubstituteNameLength;
+    USHORT PrintNameOffset;
+    USHORT PrintNameLength;
+    WCHAR  PathBuffer[MAX_PATH*3];
+} MY_MOUNT_POINT_REPARSE_BUFFER;
+
+typedef struct {
   ULONG  ReparseTag;
   USHORT ReparseDataLength;
   USHORT Reserved;
   union {
     MY_SYMLINK_REPARSE_BUFFER SymbolicLinkReparseBuffer;
-    struct {
-      USHORT SubstituteNameOffset;
-      USHORT SubstituteNameLength;
-      USHORT PrintNameOffset;
-      USHORT PrintNameLength;
-      WCHAR  PathBuffer[1];
-    } MountPointReparseBuffer;
+    MY_MOUNT_POINT_REPARSE_BUFFER MountPointReparseBuffer;
     struct {
       UCHAR DataBuffer[1];
     } GenericReparseBuffer;
@@ -1705,7 +1707,8 @@ is_symlink(HANDLE h) {
     }
 
     if (linkdata_returned < offsetof(MY_REPARSE_DATA_BUFFER, Data.SymbolicLinkReparseBuffer.PathBuffer)
-        || linkdata.ReparseTag != IO_REPARSE_TAG_SYMLINK) {
+        || (linkdata.ReparseTag != IO_REPARSE_TAG_SYMLINK
+            && linkdata.ReparseTag != IO_REPARSE_TAG_MOUNT_POINT)) {
         /* some other type of reparse point */
         return FALSE;
     }
@@ -1731,8 +1734,6 @@ is_symlink_name(const char *name) {
 DllExport int
 win32_readlink(const char *pathname, char *buf, size_t bufsiz) {
     MY_REPARSE_DATA_BUFFER linkdata;
-    const MY_SYMLINK_REPARSE_BUFFER * const sd =
-        &linkdata.Data.SymbolicLinkReparseBuffer;
     HANDLE hlink;
     DWORD fileattr = GetFileAttributes(pathname);
     DWORD linkdata_returned;
@@ -1765,16 +1766,43 @@ win32_readlink(const char *pathname, char *buf, size_t bufsiz) {
     }
     CloseHandle(hlink);
 
-    if (linkdata_returned < offsetof(MY_REPARSE_DATA_BUFFER, Data.SymbolicLinkReparseBuffer.PathBuffer)
-        || linkdata.ReparseTag != IO_REPARSE_TAG_SYMLINK) {
+    switch (linkdata.ReparseTag) {
+    case IO_REPARSE_TAG_SYMLINK:
+        {
+            const MY_SYMLINK_REPARSE_BUFFER * const sd =
+                &linkdata.Data.SymbolicLinkReparseBuffer;
+            if (linkdata_returned < offsetof(MY_REPARSE_DATA_BUFFER, Data.SymbolicLinkReparseBuffer.PathBuffer)) {
+                errno = EINVAL;
+                return -1;
+            }
+            bytes_out =
+                WideCharToMultiByte(CP_ACP, WC_NO_BEST_FIT_CHARS,
+                                    sd->PathBuffer + sd->SubstituteNameOffset/2,
+                                    sd->SubstituteNameLength/2,
+                                    buf, (int)bufsiz, NULL, &used_default);
+        }
+        break;
+    case IO_REPARSE_TAG_MOUNT_POINT:
+        {
+            const MY_MOUNT_POINT_REPARSE_BUFFER * const rd =
+                &linkdata.Data.MountPointReparseBuffer;
+            if (linkdata_returned < offsetof(MY_REPARSE_DATA_BUFFER, Data.MountPointReparseBuffer.PathBuffer)) {
+                errno = EINVAL;
+                return -1;
+            }
+            bytes_out =
+                WideCharToMultiByte(CP_ACP, WC_NO_BEST_FIT_CHARS,
+                                    rd->PathBuffer + rd->SubstituteNameOffset/2,
+                                    rd->SubstituteNameLength/2,
+                                    buf, (int)bufsiz, NULL, &used_default);
+        }
+        break;
+
+    default:
         errno = EINVAL;
         return -1;
     }
 
-    bytes_out = WideCharToMultiByte(CP_ACP, WC_NO_BEST_FIT_CHARS,
-                                    sd->PathBuffer+sd->SubstituteNameOffset/2,
-                                    sd->SubstituteNameLength/2,
-                                    buf, bufsiz, NULL, &used_default);
     if (bytes_out == 0 || used_default) {
         /* failed conversion from unicode to ANSI or otherwise failed */
         errno = EINVAL;