Fix #78220: Can't access OneDrive folder

As of Windows 1903, when the OneDrive on-demand feature is enabled, the
OneDrive folder is reported as reparse point by `FindFirstFile()`, but
trying to get information about the reparse point using
`DeviceIoControl()` fails with `ERROR_NOT_A_REPARSE_POINT`.  We work
around this problem by falling back to `GetFileInformationByHandle()`
if that happens, but only if the reparse point is reported as cloud
reparse point, and only if PHP is running on Windows 1903 or later.

The patch has been developed in collaboration with ab@php.net.

We should keep an eye on the somewhat quirky OneDrive behavior, since
it might change again in a future Windows release.
This commit is contained in:
Christoph M. Becker 2019-08-19 19:44:37 +02:00
parent 725f439778
commit 81f52158b4
3 changed files with 35 additions and 2 deletions

1
NEWS
View file

@ -3,6 +3,7 @@ PHP NEWS
?? ??? 2019, PHP 7.2.23 ?? ??? 2019, PHP 7.2.23
- Core: - Core:
. Fixed bug #78220 (Can't access OneDrive folder). (cmb, ab)
. Fixed bug #78412 (Generator incorrectly reports non-releasable $this as GC . Fixed bug #78412 (Generator incorrectly reports non-releasable $this as GC
child). (Nikita) child). (Nikita)

View file

@ -91,6 +91,8 @@ cwd_state main_cwd_state; /* True global */
#include <unistd.h> #include <unistd.h>
#else #else
#include <direct.h> #include <direct.h>
#include "zend_globals.h"
#include "zend_globals_macros.h"
#endif #endif
#define CWD_STATE_COPY(d, s) \ #define CWD_STATE_COPY(d, s) \
@ -855,6 +857,7 @@ static int tsrm_realpath_r(char *path, int start, int len, int *ll, time_t *t, i
tmp = do_alloca(len+1, use_heap); tmp = do_alloca(len+1, use_heap);
memcpy(tmp, path, len+1); memcpy(tmp, path, len+1);
retry:
if(save && if(save &&
!(IS_UNC_PATH(path, len) && len >= 3 && path[2] != '?') && !(IS_UNC_PATH(path, len) && len >= 3 && path[2] != '?') &&
(dataw.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) (dataw.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)
@ -895,7 +898,21 @@ static int tsrm_realpath_r(char *path, int start, int len, int *ll, time_t *t, i
return -1; return -1;
} }
if(!DeviceIoControl(hLink, FSCTL_GET_REPARSE_POINT, NULL, 0, pbuffer, MAXIMUM_REPARSE_DATA_BUFFER_SIZE, &retlength, NULL)) { if(!DeviceIoControl(hLink, FSCTL_GET_REPARSE_POINT, NULL, 0, pbuffer, MAXIMUM_REPARSE_DATA_BUFFER_SIZE, &retlength, NULL)) {
BY_HANDLE_FILE_INFORMATION fileInformation;
free_alloca(pbuffer, use_heap_large); free_alloca(pbuffer, use_heap_large);
if ((dataw.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) &&
(dataw.dwReserved0 & ~IO_REPARSE_TAG_CLOUD_MASK) == IO_REPARSE_TAG_CLOUD &&
EG(windows_version_info).dwMajorVersion >= 10 &&
EG(windows_version_info).dwMinorVersion == 0 &&
EG(windows_version_info).dwBuildNumber >= 18362 &&
GetFileInformationByHandle(hLink, &fileInformation) &&
!(fileInformation.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)) {
dataw.dwFileAttributes = fileInformation.dwFileAttributes;
CloseHandle(hLink);
(*ll)--;
goto retry;
}
free_alloca(tmp, use_heap); free_alloca(tmp, use_heap);
CloseHandle(hLink); CloseHandle(hLink);
FREE_PATHW() FREE_PATHW()
@ -975,8 +992,7 @@ static int tsrm_realpath_r(char *path, int start, int len, int *ll, time_t *t, i
} }
else if (pbuffer->ReparseTag == IO_REPARSE_TAG_DEDUP || else if (pbuffer->ReparseTag == IO_REPARSE_TAG_DEDUP ||
/* Starting with 1709. */ /* Starting with 1709. */
(pbuffer->ReparseTag & IO_REPARSE_TAG_CLOUD_MASK) != 0 && 0x90001018L != pbuffer->ReparseTag || (pbuffer->ReparseTag & ~IO_REPARSE_TAG_CLOUD_MASK) == IO_REPARSE_TAG_CLOUD ||
IO_REPARSE_TAG_CLOUD == pbuffer->ReparseTag ||
IO_REPARSE_TAG_ONEDRIVE == pbuffer->ReparseTag) { IO_REPARSE_TAG_ONEDRIVE == pbuffer->ReparseTag) {
isabsolute = 1; isabsolute = 1;
substitutename = malloc((len + 1) * sizeof(char)); substitutename = malloc((len + 1) * sizeof(char));

View file

@ -0,0 +1,16 @@
--TEST--
Bug #78220 (Can't access OneDrive folder)
--SKIPIF--
<?php
if (substr(PHP_OS, 0, 3) != 'WIN') die("skip this test is for Windows platforms only");
?>
--FILE--
<?php
$onedrive_dirs = array_unique([getenv('OneDrive'), getenv('OneDriveCommercial')]);
foreach ($onedrive_dirs as $dir) {
if ($dir && scandir($dir) === FALSE) {
echo "can't scan $dir\n";
}
}
?>
--EXPECT--