Skip to content

Commit f3f6cd2

Browse files
committed
Modernize realpath and integrate quick variant into virtual_file_ex
The slower I/O as a traditional bottleneck on Windows which is the target of this patch. The recursive path resolution, while being an allround solution, is expensive when it comes to the common case. Files with proper ACLs set can be resolved in one go by usage of specific API. Those are available since Vista, so actually can be called old. Those simpler api is used for the cases where no CWD_EXPAND is requested. For the cases where ACLs are improper, the existing solution based on FindFirstFile still does good job also partially providing quirks. Cases involing reparse tags and other non local filesystems are also partially server by new APIs. The approach uses both APIs - the quick one for the common case still integrating realpath cache, and the existing one as a fallback. The tests show the I/O load drop on the realpath resolution part due to less system calls for the sub part resolution of paths. In most case it is justified, as the sub parts were otherwise cached or unused as well. The realpath() implementation in ioutil is also closer to the POSIX.
1 parent 66a6041 commit f3f6cd2

File tree

10 files changed

+221
-16
lines changed

10 files changed

+221
-16
lines changed

TSRM/tsrm_config.w32.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66

77
#define HAVE_UTIME 1
88
#define HAVE_ALLOCA 1
9-
#define HAVE_REALPATH 1
109

1110
#include <malloc.h>
1211
#include <stdlib.h>

TSRM/tsrm_win32.c

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -766,15 +766,6 @@ TSRM_API int shmctl(int key, int cmd, struct shmid_ds *buf)
766766
}
767767
}/*}}}*/
768768

769-
TSRM_API char *realpath(char *orig_path, char *buffer)
770-
{/*{{{*/
771-
int ret = GetFullPathName(orig_path, _MAX_PATH, buffer, NULL);
772-
if(!ret || ret > _MAX_PATH) {
773-
return NULL;
774-
}
775-
return buffer;
776-
}/*}}}*/
777-
778769
#if HAVE_UTIME
779770
static zend_always_inline void UnixTimeToFileTime(time_t t, LPFILETIME pft) /* {{{ */
780771
{

TSRM/tsrm_win32.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,8 +110,6 @@ TSRM_API int shmget(key_t key, size_t size, int flags);
110110
TSRM_API void *shmat(int key, const void *shmaddr, int flags);
111111
TSRM_API int shmdt(const void *shmaddr);
112112
TSRM_API int shmctl(int key, int cmd, struct shmid_ds *buf);
113-
114-
TSRM_API char *realpath(char *orig_path, char *buffer);
115113
#endif
116114

117115
/*

Zend/zend_virtual_cwd.c

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1209,12 +1209,59 @@ static size_t tsrm_realpath_r(char *path, size_t start, size_t len, int *ll, tim
12091209
}
12101210
/* }}} */
12111211

1212+
#ifdef ZEND_WIN32
1213+
static size_t tsrm_win32_realpath_quick(char *path, size_t len, time_t *t) /* {{{ */
1214+
{
1215+
char tmp_resolved_path[MAXPATHLEN];
1216+
int tmp_resolved_path_len;
1217+
BY_HANDLE_FILE_INFORMATION info;
1218+
realpath_cache_bucket *bucket;
1219+
1220+
if (!*t) {
1221+
*t = time(0);
1222+
}
1223+
1224+
if (CWDG(realpath_cache_size_limit) && (bucket = realpath_cache_find(path, len, *t)) != NULL) {
1225+
memcpy(path, bucket->realpath, bucket->realpath_len + 1);
1226+
return bucket->realpath_len;
1227+
}
1228+
1229+
#if 0
1230+
if (!php_win32_ioutil_realpath_ex0(resolved_path, tmp_resolved_path, &info)) {
1231+
if (CWD_REALPATH == use_realpath) {
1232+
DWORD err = GetLastError();
1233+
SET_ERRNO_FROM_WIN32_CODE(err);
1234+
return 1;
1235+
} else {
1236+
/* Fallback to expand only to retain BC. */
1237+
path_length = tsrm_realpath_r(resolved_path, start, path_length, &ll, &t, use_realpath, 0, NULL);
1238+
}
1239+
} else {
1240+
#endif
1241+
1242+
if (!php_win32_ioutil_realpath_ex0(path, tmp_resolved_path, &info)) {
1243+
DWORD err = GetLastError();
1244+
SET_ERRNO_FROM_WIN32_CODE(err);
1245+
return (size_t)-1;
1246+
}
1247+
1248+
tmp_resolved_path_len = strlen(tmp_resolved_path);
1249+
if (CWDG(realpath_cache_size_limit)) {
1250+
realpath_cache_add(path, len, tmp_resolved_path, tmp_resolved_path_len, info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY, *t);
1251+
}
1252+
memmove(path, tmp_resolved_path, tmp_resolved_path_len + 1);
1253+
1254+
return tmp_resolved_path_len;
1255+
}
1256+
/* }}} */
1257+
#endif
1258+
12121259
/* Resolve path relatively to state and put the real path into state */
12131260
/* returns 0 for ok, 1 for error */
12141261
CWD_API int virtual_file_ex(cwd_state *state, const char *path, verify_path_func verify_path, int use_realpath) /* {{{ */
12151262
{
12161263
size_t path_length = strlen(path);
1217-
char resolved_path[MAXPATHLEN];
1264+
char resolved_path[MAXPATHLEN] = {0};
12181265
size_t start = 1;
12191266
int ll = 0;
12201267
time_t t;
@@ -1336,7 +1383,27 @@ CWD_API int virtual_file_ex(cwd_state *state, const char *path, verify_path_func
13361383

13371384
add_slash = (use_realpath != CWD_REALPATH) && path_length > 0 && IS_SLASH(resolved_path[path_length-1]);
13381385
t = CWDG(realpath_cache_ttl) ? 0 : -1;
1386+
#ifdef ZEND_WIN32
1387+
if (CWD_EXPAND != use_realpath) {
1388+
size_t tmp_len = tsrm_win32_realpath_quick(resolved_path, path_length, &t);
1389+
if ((size_t)-1 != tmp_len) {
1390+
path_length = tmp_len;
1391+
} else {
1392+
DWORD err = GetLastError();
1393+
/* The access denied error can mean something completely else,
1394+
fallback to complicated way. */
1395+
if (CWD_REALPATH == use_realpath && ERROR_ACCESS_DENIED != err) {
1396+
SET_ERRNO_FROM_WIN32_CODE(err);
1397+
return 1;
1398+
}
1399+
path_length = tsrm_realpath_r(resolved_path, start, path_length, &ll, &t, use_realpath, 0, NULL);
1400+
}
1401+
} else {
1402+
path_length = tsrm_realpath_r(resolved_path, start, path_length, &ll, &t, use_realpath, 0, NULL);
1403+
}
1404+
#else
13391405
path_length = tsrm_realpath_r(resolved_path, start, path_length, &ll, &t, use_realpath, 0, NULL);
1406+
#endif
13401407

13411408
if (path_length == (size_t)-1) {
13421409
errno = ENOENT;
@@ -1346,6 +1413,7 @@ CWD_API int virtual_file_ex(cwd_state *state, const char *path, verify_path_func
13461413
if (!start && !path_length) {
13471414
resolved_path[path_length++] = '.';
13481415
}
1416+
13491417
if (add_slash && path_length && !IS_SLASH(resolved_path[path_length-1])) {
13501418
if (path_length >= MAXPATHLEN-1) {
13511419
return -1;

ext/opcache/Optimizer/zend_func_info.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@
2828
#include "zend_call_graph.h"
2929
#include "zend_func_info.h"
3030
#include "zend_inference.h"
31+
#ifdef _WIN32
32+
#include "win32/ioutil.h"
33+
#endif
3134

3235
typedef uint32_t (*info_func_t)(const zend_call_info *call_info, const zend_ssa *ssa);
3336

ext/standard/basic_functions.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
#ifdef PHP_WIN32
4141
#include "win32/php_win32_globals.h"
4242
#include "win32/time.h"
43+
#include "win32/ioutil.h"
4344
#endif
4445

4546
typedef struct yy_buffer_state *YY_BUFFER_STATE;

ext/standard/file.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
# include "win32/param.h"
5151
# include "win32/winutil.h"
5252
# include "win32/fnmatch.h"
53+
# include "win32/ioutil.h"
5354
#else
5455
# if HAVE_SYS_PARAM_H
5556
# include <sys/param.h>

ext/standard/tests/file/realpath_cache_win32.phpt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ if (substr(PHP_OS, 0, 3) != 'WIN') {
1111

1212
var_dump(realpath_cache_size());
1313
$data = realpath_cache_get();
14-
var_dump($data[__DIR__]);
14+
var_dump($data[__FILE__]);
1515

1616
echo "Done\n";
1717
?>
@@ -21,9 +21,9 @@ array(8) {
2121
["key"]=>
2222
%s(%d%s)
2323
["is_dir"]=>
24-
bool(true)
24+
bool(false)
2525
["realpath"]=>
26-
string(%d) "%sfile"
26+
string(%d) "%sphp"
2727
["expires"]=>
2828
int(%d)
2929
["is_rvalid"]=>

win32/ioutil.c

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -719,6 +719,95 @@ PW32IO FILE *php_win32_ioutil_fopen_w(const wchar_t *path, const wchar_t *mode)
719719
return ret;
720720
}/*}}}*/
721721

722+
static size_t php_win32_ioutil_realpath_h(HANDLE *h, wchar_t **resolved)
723+
{/*{{{*/
724+
wchar_t ret[PHP_WIN32_IOUTIL_MAXPATHLEN], *ret_real;
725+
DWORD ret_len, ret_real_len;
726+
727+
ret_len = GetFinalPathNameByHandleW(h, ret, PHP_WIN32_IOUTIL_MAXPATHLEN-1, FILE_NAME_NORMALIZED | VOLUME_NAME_DOS);
728+
if (0 == ret_len) {
729+
DWORD err = GetLastError();
730+
SET_ERRNO_FROM_WIN32_CODE(err);
731+
return (size_t)-1;
732+
} else if (ret_len > PHP_WIN32_IOUTIL_MAXPATHLEN) {
733+
SET_ERRNO_FROM_WIN32_CODE(ERROR_INVALID_PARAMETER);
734+
return (size_t)-1;
735+
}
736+
737+
if (NULL == *resolved) {
738+
/* ret is expected to be either NULL or a buffer of capable size. */
739+
*resolved = (wchar_t *) malloc((ret_len + 1)*sizeof(wchar_t));
740+
if (!*resolved) {
741+
SET_ERRNO_FROM_WIN32_CODE(ERROR_NOT_ENOUGH_MEMORY);
742+
return (size_t)-1;
743+
}
744+
}
745+
746+
ret_real = ret;
747+
ret_real_len = ret_len;
748+
if (0 == wcsncmp(ret, PHP_WIN32_IOUTIL_UNC_PATH_PREFIXW, PHP_WIN32_IOUTIL_UNC_PATH_PREFIX_LENW)) {
749+
ret_real += (PHP_WIN32_IOUTIL_UNC_PATH_PREFIX_LENW - 2);
750+
ret_real[0] = L'\\';
751+
ret_real_len -= (PHP_WIN32_IOUTIL_UNC_PATH_PREFIX_LENW - 2);
752+
} else if (PHP_WIN32_IOUTIL_IS_LONG_PATHW(ret, ret_len)) {
753+
ret_real += PHP_WIN32_IOUTIL_LONG_PATH_PREFIX_LENW;
754+
ret_real_len -= PHP_WIN32_IOUTIL_LONG_PATH_PREFIX_LENW;
755+
}
756+
memmove(*resolved, ret_real, (ret_real_len+1)*sizeof(wchar_t));
757+
758+
return ret_real_len;
759+
}/*}}}*/
760+
761+
PW32IO wchar_t *php_win32_ioutil_realpath_w(const wchar_t *path, wchar_t *resolved)
762+
{/*{{{*/
763+
return php_win32_ioutil_realpath_w_ex0(path, resolved, NULL);
764+
}/*}}}*/
765+
766+
PW32IO wchar_t *php_win32_ioutil_realpath_w_ex0(const wchar_t *path, wchar_t *resolved, PBY_HANDLE_FILE_INFORMATION info)
767+
{/*{{{*/
768+
HANDLE h;
769+
size_t ret_len;
770+
771+
PHP_WIN32_IOUTIL_CHECK_PATH_W(path, NULL, 0)
772+
773+
h = CreateFileW(path,
774+
0,
775+
0,
776+
NULL,
777+
OPEN_EXISTING,
778+
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS,
779+
NULL);
780+
if (INVALID_HANDLE_VALUE == h) {
781+
DWORD err = GetLastError();
782+
SET_ERRNO_FROM_WIN32_CODE(err);
783+
return NULL;
784+
}
785+
786+
ret_len = php_win32_ioutil_realpath_h(h, &resolved);
787+
if ((size_t)-1 == ret_len) {
788+
DWORD err = GetLastError();
789+
CloseHandle(h);
790+
SET_ERRNO_FROM_WIN32_CODE(err);
791+
return NULL;
792+
}
793+
794+
if (NULL != info && !GetFileInformationByHandle(h, info)) {
795+
DWORD err = GetLastError();
796+
CloseHandle(h);
797+
SET_ERRNO_FROM_WIN32_CODE(err);
798+
return NULL;
799+
}
800+
801+
CloseHandle(h);
802+
803+
return resolved;
804+
}/*}}}*/
805+
806+
PW32IO char *realpath(const char *path, char *resolved)
807+
{/*{{{*/
808+
return php_win32_ioutil_realpath(path, resolved);
809+
}/*}}}*/
810+
722811
/*
723812
* Local variables:
724813
* tab-width: 4

win32/ioutil.h

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,8 @@ PW32IO int php_win32_ioutil_unlink_w(const wchar_t *path);
258258
PW32IO int php_win32_ioutil_access_w(const wchar_t *path, mode_t mode);
259259
PW32IO int php_win32_ioutil_mkdir_w(const wchar_t *path, mode_t mode);
260260
PW32IO FILE *php_win32_ioutil_fopen_w(const wchar_t *path, const wchar_t *mode);
261+
PW32IO wchar_t *php_win32_ioutil_realpath_w(const wchar_t *path, wchar_t *resolved);
262+
PW32IO wchar_t *php_win32_ioutil_realpath_w_ex0(const wchar_t *path, wchar_t *resolved, PBY_HANDLE_FILE_INFORMATION info);
261263

262264
__forceinline static int php_win32_ioutil_access(const char *path, mode_t mode)
263265
{/*{{{*/
@@ -582,6 +584,59 @@ __forceinline static int php_win32_ioutil_mkdir(const char *path, mode_t mode)
582584
return ret;
583585
}/*}}}*/
584586

587+
#define HAVE_REALPATH 1
588+
PW32IO char *realpath(const char *path, char *resolved);
589+
590+
__forceinline static char *php_win32_ioutil_realpath_ex0(const char *path, char *resolved, PBY_HANDLE_FILE_INFORMATION info)
591+
{/*{{{*/
592+
wchar_t retw[PHP_WIN32_IOUTIL_MAXPATHLEN];
593+
char *reta;
594+
size_t reta_len;
595+
596+
PHP_WIN32_IOUTIL_INIT_W(path)
597+
if (!pathw) {
598+
SET_ERRNO_FROM_WIN32_CODE(ERROR_INVALID_PARAMETER);
599+
return NULL;
600+
}
601+
602+
if (NULL == php_win32_ioutil_realpath_w_ex0(pathw, retw, info)) {
603+
DWORD err = GetLastError();
604+
PHP_WIN32_IOUTIL_CLEANUP_W()
605+
SET_ERRNO_FROM_WIN32_CODE(err);
606+
return NULL;
607+
}
608+
609+
reta = php_win32_cp_conv_w_to_any(retw, PHP_WIN32_CP_IGNORE_LEN, &reta_len);
610+
if (!reta || reta_len > PHP_WIN32_IOUTIL_MAXPATHLEN) {
611+
DWORD err = GetLastError();
612+
PHP_WIN32_IOUTIL_CLEANUP_W()
613+
SET_ERRNO_FROM_WIN32_CODE(err);
614+
return NULL;
615+
}
616+
617+
if (NULL == resolved) {
618+
/* ret is expected to be either NULL or a buffer of capable size. */
619+
resolved = (char *) malloc(reta_len + 1);
620+
if (!resolved) {
621+
free(reta);
622+
PHP_WIN32_IOUTIL_CLEANUP_W()
623+
SET_ERRNO_FROM_WIN32_CODE(ERROR_NOT_ENOUGH_MEMORY);
624+
return NULL;
625+
}
626+
}
627+
memmove(resolved, reta, reta_len+1);
628+
629+
PHP_WIN32_IOUTIL_CLEANUP_W()
630+
free(reta);
631+
632+
return resolved;
633+
}/*}}}*/
634+
635+
__forceinline static char *php_win32_ioutil_realpath(const char *path, char *resolved)
636+
{/*{{{*/
637+
return php_win32_ioutil_realpath_ex0(path, resolved, NULL);
638+
}/*}}}*/
639+
585640
#ifdef __cplusplus
586641
}
587642
#endif

0 commit comments

Comments
 (0)