]> granicus.if.org Git - php/commitdiff
Fix bc break in Windows readlink
authorjohnstevenson <john-stevenson@blueyonder.co.uk>
Wed, 13 Feb 2019 11:34:00 +0000 (11:34 +0000)
committerAnatol Belski <ab@php.net>
Sun, 10 Mar 2019 14:48:31 +0000 (15:48 +0100)
GetFinalPathNameByHandleW is given a file handle to a symbolic link,
rather than one to the target, and therefore returns an incorrect path.

Fix symlink with relative path and add test

ext/standard/tests/file/windows_links/readlink_compat.phpt [new file with mode: 0644]
win32/ioutil.c
win32/ioutil.h
win32/winutil.c

diff --git a/ext/standard/tests/file/windows_links/readlink_compat.phpt b/ext/standard/tests/file/windows_links/readlink_compat.phpt
new file mode 100644 (file)
index 0000000..e119473
--- /dev/null
@@ -0,0 +1,89 @@
+--TEST--
+Test readlink bc behaviour on Windows
+--DESCRIPTION--
+Checks readlink is backward-compatible with PHP-7.3 and below
+--SKIPIF--
+<?php
+if (substr(PHP_OS, 0, 3) != 'WIN') {
+    die('skip windows only test');
+}
+exec('fltmc', $output, $exitCode);
+if ($exitCode !== 0) {
+    die('skip administrator privileges required');
+}
+?>
+--FILE--
+<?php
+$tmpDir = __DIR__ . '\\mnt';
+mkdir($tmpDir);
+
+// mounted volume
+$volume = trim(exec('mountvol C: /L'));
+exec(sprintf('mountvol "%s" %s', $tmpDir, $volume));
+var_dump(readlink($tmpDir)); 
+exec(sprintf('mountvol "%s" /D', $tmpDir));
+
+mkdir($tmpDir . '\\test\\directory', 0777, true);
+chdir($tmpDir . '\\test');
+
+// junction to a volume (same as a mounted volume)
+$link = $tmpDir . '\\test\\volume_junction';
+exec(sprintf('mklink /J "%s" %s', $link, $volume));
+var_dump(readlink($link));
+rmdir($link);
+
+// junction to a directory
+$link = $tmpDir . '\\test\\directory_junction';
+$target = $tmpDir . '\\test\\directory';
+exec(sprintf('mklink /J "%s" "%s"', $link, $target)); 
+var_dump(readlink($link));
+rmdir($link);
+
+// symlink to a directory (absolute and relative)
+$link = $tmpDir . '\\test\\directory_symlink';
+$target = $tmpDir . '\\test\\directory';
+exec(sprintf('mklink /D "%s" "%s"', $link, $target));
+var_dump(readlink($link));
+rmdir($link);
+exec(sprintf('mklink /D "%s" directory', $link));
+var_dump(readlink($link));
+rmdir($link);
+
+// create a file to link to
+$filename = $tmpDir . '\\test\\directory\\a.php';
+$fh = fopen($filename, 'w');
+fclose($fh);
+
+// symlink to a file (absolute and relative)
+$link = $tmpDir . '\\test\\file_symlink';
+exec(sprintf('mklink "%s" "%s"', $link, $filename)); 
+var_dump(readlink($link));
+unlink($link);
+exec(sprintf('mklink "%s" directory\\a.php', $link));
+var_dump(readlink($link));
+unlink($link);
+
+// unexpected behaviour
+echo "\n*** Unexpected behaviour when not a reparse point\n"; 
+var_dump(readlink($tmpDir . '\\test\\directory'));
+var_dump(readlink($filename));
+
+unlink($filename);
+
+chdir(__DIR__);
+rmdir($tmpDir . '\\test\\directory');
+rmdir($tmpDir . '\\test');
+rmdir($tmpDir);
+?>
+--EXPECTF--
+string(3) "C:\"
+string(3) "C:\"
+string(%d) "%s\mnt\test\directory"
+string(%d) "%s\mnt\test\directory"
+string(%d) "%s\mnt\test\directory"
+string(%d) "%s\mnt\test\directory\a.php"
+string(%d) "%s\mnt\test\directory\a.php"
+
+*** Unexpected behaviour when not a reparse point
+string(%d) "%s\mnt\test\directory"
+string(%d) "%s\mnt\test\directory\a.php"
index 7288ea353a67c17bfc4eadc2ce4b603195fdecaa..287a9c117c94aa32ccd3caa2bf39b40d2521a71d 100644 (file)
@@ -1004,6 +1004,13 @@ static ssize_t php_win32_ioutil_readlink_int(HANDLE h, wchar_t *buf, size_t buf_
 
        if (reparse_data->ReparseTag == IO_REPARSE_TAG_SYMLINK) {
                /* Real symlink */
+
+               /* BC - relative links are shown as absolute */
+               if (reparse_data->SymbolicLinkReparseBuffer.Flags & SYMLINK_FLAG_RELATIVE) {
+                       SET_ERRNO_FROM_WIN32_CODE(ERROR_SYMLINK_NOT_SUPPORTED);
+                       return -1;
+               }
+
                reparse_target = reparse_data->SymbolicLinkReparseBuffer.ReparseTarget +
                        (reparse_data->SymbolicLinkReparseBuffer.SubstituteNameOffset /
                        sizeof(wchar_t));
@@ -1095,6 +1102,7 @@ PW32IO ssize_t php_win32_ioutil_readlink_w(const wchar_t *path, wchar_t *buf, si
        HANDLE h;
        ssize_t ret;
 
+       /* Get a handle to the symbolic link (if path is a symbolic link) */
        h = CreateFileW(path,
                                        0,
                                        0,
@@ -1110,11 +1118,27 @@ PW32IO ssize_t php_win32_ioutil_readlink_w(const wchar_t *path, wchar_t *buf, si
 
        ret = php_win32_ioutil_readlink_int(h, buf, buf_len);
 
-       if (ret < 0) {
-               /* BC */
-               wchar_t target[PHP_WIN32_IOUTIL_MAXPATHLEN];
-               size_t offset = 0,
-                          target_len = GetFinalPathNameByHandleW(h, target, PHP_WIN32_IOUTIL_MAXPATHLEN, VOLUME_NAME_DOS);
+       if (ret < 0) {          
+               wchar_t target[PHP_WIN32_IOUTIL_MAXPATHLEN];            
+               size_t target_len;
+               size_t offset = 0;
+
+               /* BC - get a handle to the target (if path is a symbolic link) */
+               CloseHandle(h);
+               h = CreateFileW(path,
+                                               0,
+                                               0,
+                                               NULL,
+                                               OPEN_EXISTING,
+                                               FILE_FLAG_BACKUP_SEMANTICS,
+                                               NULL);
+
+               if (h == INVALID_HANDLE_VALUE) {
+                       SET_ERRNO_FROM_WIN32_CODE(GetLastError());
+                       return -1;
+               }
+
+               target_len = GetFinalPathNameByHandleW(h, target, PHP_WIN32_IOUTIL_MAXPATHLEN, VOLUME_NAME_DOS);
 
                if(target_len >= buf_len || target_len >= PHP_WIN32_IOUTIL_MAXPATHLEN || target_len == 0) {
                        CloseHandle(h);
index e2906c65e09d4d0b7c94653be090652595d58f83..34104a3f459ea5afcf7dcb2c7da42260fcc32314 100644 (file)
@@ -88,6 +88,11 @@ typedef unsigned short mode_t;
 #define F_OK 0x00
 #endif
 
+/* from ntifs.h */
+#ifndef SYMLINK_FLAG_RELATIVE
+#define SYMLINK_FLAG_RELATIVE 0x01
+#endif
+
 typedef struct {
        DWORD access;
        DWORD share;
index cb962fb537e166e409df136d08c24fe3018bb820..366a48bb11cd663a89c124cb56bf0bd34c340a20 100644 (file)
@@ -398,6 +398,8 @@ PHP_WINUTIL_API int php_win32_code_to_errno(unsigned long w32Err)
         /* 1314 */   ,  {   ERROR_PRIVILEGE_NOT_HELD        ,   EACCES          }
         /* 1816 */  ,   {   ERROR_NOT_ENOUGH_QUOTA          ,   ENOMEM          }
                                        ,   {   ERROR_ABANDONED_WAIT_0          ,   EIO }
+               /* 1464 */      ,       {       ERROR_SYMLINK_NOT_SUPPORTED             ,       EINVAL                  }
+               /* 4390 */      ,       {       ERROR_NOT_A_REPARSE_POINT               ,       EINVAL                  }
     };
 
     for(i = 0; i < sizeof(errmap)/sizeof(struct code_to_errno_map); ++i)