summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorjohnstevenson <john-stevenson@blueyonder.co.uk>2019-02-13 11:34:00 +0000
committerAnatol Belski <ab@php.net>2019-03-10 15:48:31 +0100
commitb37a6aafce721d289cfa004bfc8120796e01de60 (patch)
tree0acfd791653f9c0b03151534508492e40d27ab3e
parent44b7126c6d86a4cde6d3c2b81161041cf0173743 (diff)
downloadphp-git-b37a6aafce721d289cfa004bfc8120796e01de60.tar.gz
Fix bc break in Windows readlink
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
-rw-r--r--ext/standard/tests/file/windows_links/readlink_compat.phpt89
-rw-r--r--win32/ioutil.c34
-rw-r--r--win32/ioutil.h5
-rw-r--r--win32/winutil.c2
4 files changed, 125 insertions, 5 deletions
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
index 0000000000..e119473895
--- /dev/null
+++ b/ext/standard/tests/file/windows_links/readlink_compat.phpt
@@ -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"
diff --git a/win32/ioutil.c b/win32/ioutil.c
index 7288ea353a..287a9c117c 100644
--- a/win32/ioutil.c
+++ b/win32/ioutil.c
@@ -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);
diff --git a/win32/ioutil.h b/win32/ioutil.h
index e2906c65e0..34104a3f45 100644
--- a/win32/ioutil.h
+++ b/win32/ioutil.h
@@ -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;
diff --git a/win32/winutil.c b/win32/winutil.c
index cb962fb537..366a48bb11 100644
--- a/win32/winutil.c
+++ b/win32/winutil.c
@@ -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)