--- /dev/null
+--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"
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));
HANDLE h;
ssize_t ret;
+ /* Get a handle to the symbolic link (if path is a symbolic link) */
h = CreateFileW(path,
0,
0,
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);