diff options
author | johnstevenson <john-stevenson@blueyonder.co.uk> | 2019-02-13 11:34:00 +0000 |
---|---|---|
committer | Anatol Belski <ab@php.net> | 2019-03-10 15:48:31 +0100 |
commit | b37a6aafce721d289cfa004bfc8120796e01de60 (patch) | |
tree | 0acfd791653f9c0b03151534508492e40d27ab3e | |
parent | 44b7126c6d86a4cde6d3c2b81161041cf0173743 (diff) | |
download | php-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.phpt | 89 | ||||
-rw-r--r-- | win32/ioutil.c | 34 | ||||
-rw-r--r-- | win32/ioutil.h | 5 | ||||
-rw-r--r-- | win32/winutil.c | 2 |
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) |