]> granicus.if.org Git - php/commitdiff
Modernize realpath and integrate quick variant into virtual_file_ex
authorAnatol Belski <ab@php.net>
Fri, 15 Dec 2017 12:23:34 +0000 (13:23 +0100)
committerAnatol Belski <ab@php.net>
Fri, 15 Dec 2017 12:23:34 +0000 (13:23 +0100)
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.

TSRM/tsrm_config.w32.h
TSRM/tsrm_win32.c
TSRM/tsrm_win32.h
Zend/zend_virtual_cwd.c
ext/opcache/Optimizer/zend_func_info.c
ext/standard/basic_functions.c
ext/standard/file.c
ext/standard/tests/file/realpath_cache_win32.phpt
win32/ioutil.c
win32/ioutil.h

index a58d47517ce208bcfe179f4c0fe7b8fa9b6df4ae..ab451411797c6a807bb950a7c72afd977d08d688 100644 (file)
@@ -6,7 +6,6 @@
 
 #define HAVE_UTIME 1
 #define HAVE_ALLOCA 1
-#define HAVE_REALPATH 1
 
 #include <malloc.h>
 #include <stdlib.h>
index 902a693cccb76b110a072b3f96e50afbe2394146..4de7acbfe363264b898b5857ebff94ae426da96e 100644 (file)
@@ -766,15 +766,6 @@ TSRM_API int shmctl(int key, int cmd, struct shmid_ds *buf)
        }
 }/*}}}*/
 
-TSRM_API char *realpath(char *orig_path, char *buffer)
-{/*{{{*/
-       int ret = GetFullPathName(orig_path, _MAX_PATH, buffer, NULL);
-       if(!ret || ret > _MAX_PATH) {
-               return NULL;
-       }
-       return buffer;
-}/*}}}*/
-
 #if HAVE_UTIME
 static zend_always_inline void UnixTimeToFileTime(time_t t, LPFILETIME pft) /* {{{ */
 {
index b90791cd59934ced650938d0c2aa07f06f5bdfc9..3edef1dd58276e4199275669f73140a3c3fd56fb 100644 (file)
@@ -110,8 +110,6 @@ TSRM_API int shmget(key_t key, size_t size, int flags);
 TSRM_API void *shmat(int key, const void *shmaddr, int flags);
 TSRM_API int shmdt(const void *shmaddr);
 TSRM_API int shmctl(int key, int cmd, struct shmid_ds *buf);
-
-TSRM_API char *realpath(char *orig_path, char *buffer);
 #endif
 
 /*
index 32c71a6149b4eae2a450c8490d514244e4b23867..a05d1c58ac14a54783741725d3211b45bae5f682 100644 (file)
@@ -1209,12 +1209,59 @@ static size_t tsrm_realpath_r(char *path, size_t start, size_t len, int *ll, tim
 }
 /* }}} */
 
+#ifdef ZEND_WIN32
+static size_t tsrm_win32_realpath_quick(char *path, size_t len, time_t *t) /* {{{ */
+{
+       char tmp_resolved_path[MAXPATHLEN];
+       int tmp_resolved_path_len;
+       BY_HANDLE_FILE_INFORMATION info;
+       realpath_cache_bucket *bucket;
+
+       if (!*t) {
+               *t = time(0);
+       }
+
+       if (CWDG(realpath_cache_size_limit) && (bucket = realpath_cache_find(path, len, *t)) != NULL) {
+               memcpy(path, bucket->realpath, bucket->realpath_len + 1);
+               return bucket->realpath_len;
+       }
+
+#if 0
+               if (!php_win32_ioutil_realpath_ex0(resolved_path, tmp_resolved_path, &info)) {
+                       if (CWD_REALPATH == use_realpath) {
+                               DWORD err = GetLastError();
+                               SET_ERRNO_FROM_WIN32_CODE(err);
+                               return 1;
+                       } else {
+                               /* Fallback to expand only to retain BC. */
+                               path_length = tsrm_realpath_r(resolved_path, start, path_length, &ll, &t, use_realpath, 0, NULL);
+                       }
+               } else {
+#endif
+
+       if (!php_win32_ioutil_realpath_ex0(path, tmp_resolved_path, &info)) {
+               DWORD err = GetLastError();
+               SET_ERRNO_FROM_WIN32_CODE(err);
+               return (size_t)-1;
+       }
+
+       tmp_resolved_path_len = strlen(tmp_resolved_path);
+       if (CWDG(realpath_cache_size_limit)) {
+               realpath_cache_add(path, len, tmp_resolved_path, tmp_resolved_path_len, info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY, *t);
+       }
+       memmove(path, tmp_resolved_path, tmp_resolved_path_len + 1);
+
+       return tmp_resolved_path_len;
+}
+/* }}} */
+#endif
+
 /* Resolve path relatively to state and put the real path into state */
 /* returns 0 for ok, 1 for error */
 CWD_API int virtual_file_ex(cwd_state *state, const char *path, verify_path_func verify_path, int use_realpath) /* {{{ */
 {
        size_t path_length = strlen(path);
-       char resolved_path[MAXPATHLEN];
+       char resolved_path[MAXPATHLEN] = {0};
        size_t start = 1;
        int ll = 0;
        time_t t;
@@ -1336,7 +1383,27 @@ CWD_API int virtual_file_ex(cwd_state *state, const char *path, verify_path_func
 
        add_slash = (use_realpath != CWD_REALPATH) && path_length > 0 && IS_SLASH(resolved_path[path_length-1]);
        t = CWDG(realpath_cache_ttl) ? 0 : -1;
+#ifdef ZEND_WIN32
+       if (CWD_EXPAND != use_realpath) {
+               size_t tmp_len = tsrm_win32_realpath_quick(resolved_path, path_length, &t);
+               if ((size_t)-1 != tmp_len) {
+                       path_length = tmp_len;
+               } else {
+                       DWORD err = GetLastError();
+                       /* The access denied error can mean something completely else,
+                               fallback to complicated way. */
+                       if (CWD_REALPATH == use_realpath && ERROR_ACCESS_DENIED != err) {
+                               SET_ERRNO_FROM_WIN32_CODE(err);
+                               return 1;
+                       }
+                       path_length = tsrm_realpath_r(resolved_path, start, path_length, &ll, &t, use_realpath, 0, NULL);
+               }
+       } else {
+               path_length = tsrm_realpath_r(resolved_path, start, path_length, &ll, &t, use_realpath, 0, NULL);
+       }
+#else
        path_length = tsrm_realpath_r(resolved_path, start, path_length, &ll, &t, use_realpath, 0, NULL);
+#endif
 
        if (path_length == (size_t)-1) {
                errno = ENOENT;
@@ -1346,6 +1413,7 @@ CWD_API int virtual_file_ex(cwd_state *state, const char *path, verify_path_func
        if (!start && !path_length) {
                resolved_path[path_length++] = '.';
        }
+
        if (add_slash && path_length && !IS_SLASH(resolved_path[path_length-1])) {
                if (path_length >= MAXPATHLEN-1) {
                        return -1;
index 8b4f12829978cc739bb7aa9c7a9725f29520f5b9..13f69d9e556509c55581ff57c98513ec579decf5 100644 (file)
@@ -28,6 +28,9 @@
 #include "zend_call_graph.h"
 #include "zend_func_info.h"
 #include "zend_inference.h"
+#ifdef _WIN32
+#include "win32/ioutil.h"
+#endif
 
 typedef uint32_t (*info_func_t)(const zend_call_info *call_info, const zend_ssa *ssa);
 
index ed150534a55c3f9f49d443ba8aee810b9d8d9b8a..846ffdbc667c5818b8fb74010b951972c754b355 100644 (file)
@@ -40,6 +40,7 @@
 #ifdef PHP_WIN32
 #include "win32/php_win32_globals.h"
 #include "win32/time.h"
+#include "win32/ioutil.h"
 #endif
 
 typedef struct yy_buffer_state *YY_BUFFER_STATE;
index e88197d95f2bf44803f6d07046a07d419c8f8e57..099508019a913f8a830b245fbcb82a5c86d8f7d6 100644 (file)
@@ -50,6 +50,7 @@
 # include "win32/param.h"
 # include "win32/winutil.h"
 # include "win32/fnmatch.h"
+# include "win32/ioutil.h"
 #else
 # if HAVE_SYS_PARAM_H
 #  include <sys/param.h>
index e74a6565a7aba34958af7ba8d7470855ff1f0bc7..fa5f22c3b59a66b1cf47c294424cc39b2fa01ba1 100644 (file)
@@ -11,7 +11,7 @@ if (substr(PHP_OS, 0, 3) != 'WIN') {
 
 var_dump(realpath_cache_size());
 $data = realpath_cache_get();
-var_dump($data[__DIR__]);
+var_dump($data[__FILE__]);
 
 echo "Done\n";
 ?>
@@ -21,9 +21,9 @@ array(8) {
   ["key"]=>
   %s(%d%s)
   ["is_dir"]=>
-  bool(true)
+  bool(false)
   ["realpath"]=>
-  string(%d) "%sfile"
+  string(%d) "%sphp"
   ["expires"]=>
   int(%d)
   ["is_rvalid"]=>
index 3f56a44acb6fc65d312091d3b7c9fc916a50f1c0..11761ce9d9b8c11714ce5deedb21b0562a06524a 100644 (file)
@@ -719,6 +719,95 @@ PW32IO FILE *php_win32_ioutil_fopen_w(const wchar_t *path, const wchar_t *mode)
        return ret;
 }/*}}}*/
 
+static size_t php_win32_ioutil_realpath_h(HANDLE *h, wchar_t **resolved)
+{/*{{{*/
+       wchar_t ret[PHP_WIN32_IOUTIL_MAXPATHLEN], *ret_real;
+       DWORD ret_len, ret_real_len;
+
+       ret_len = GetFinalPathNameByHandleW(h, ret, PHP_WIN32_IOUTIL_MAXPATHLEN-1, FILE_NAME_NORMALIZED | VOLUME_NAME_DOS);
+       if (0 == ret_len) {
+               DWORD err = GetLastError();
+               SET_ERRNO_FROM_WIN32_CODE(err);
+               return (size_t)-1;
+       } else if (ret_len > PHP_WIN32_IOUTIL_MAXPATHLEN) {
+               SET_ERRNO_FROM_WIN32_CODE(ERROR_INVALID_PARAMETER);
+               return (size_t)-1;
+       }
+
+       if (NULL == *resolved) {
+               /* ret is expected to be either NULL or a buffer of capable size. */
+               *resolved = (wchar_t *) malloc((ret_len + 1)*sizeof(wchar_t));
+               if (!*resolved) {
+                       SET_ERRNO_FROM_WIN32_CODE(ERROR_NOT_ENOUGH_MEMORY);
+                       return (size_t)-1;
+               }
+       }
+
+       ret_real = ret;
+       ret_real_len = ret_len;
+       if (0 == wcsncmp(ret, PHP_WIN32_IOUTIL_UNC_PATH_PREFIXW, PHP_WIN32_IOUTIL_UNC_PATH_PREFIX_LENW)) {
+               ret_real += (PHP_WIN32_IOUTIL_UNC_PATH_PREFIX_LENW - 2);
+               ret_real[0] = L'\\';
+               ret_real_len -= (PHP_WIN32_IOUTIL_UNC_PATH_PREFIX_LENW - 2);
+       } else if (PHP_WIN32_IOUTIL_IS_LONG_PATHW(ret, ret_len)) {
+               ret_real += PHP_WIN32_IOUTIL_LONG_PATH_PREFIX_LENW;
+               ret_real_len -= PHP_WIN32_IOUTIL_LONG_PATH_PREFIX_LENW;
+       }
+       memmove(*resolved, ret_real, (ret_real_len+1)*sizeof(wchar_t));
+
+       return ret_real_len;
+}/*}}}*/
+
+PW32IO wchar_t *php_win32_ioutil_realpath_w(const wchar_t *path, wchar_t *resolved)
+{/*{{{*/
+       return php_win32_ioutil_realpath_w_ex0(path, resolved, NULL);
+}/*}}}*/
+
+PW32IO wchar_t *php_win32_ioutil_realpath_w_ex0(const wchar_t *path, wchar_t *resolved, PBY_HANDLE_FILE_INFORMATION info)
+{/*{{{*/
+       HANDLE h;
+       size_t ret_len;
+
+       PHP_WIN32_IOUTIL_CHECK_PATH_W(path, NULL, 0)
+
+       h = CreateFileW(path,
+                                       0,
+                                       0,
+                                       NULL,
+                                       OPEN_EXISTING,
+                                       FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS,
+                                       NULL);
+       if (INVALID_HANDLE_VALUE == h) {
+               DWORD err = GetLastError();
+               SET_ERRNO_FROM_WIN32_CODE(err);
+               return NULL;
+       }
+
+       ret_len = php_win32_ioutil_realpath_h(h, &resolved);
+       if ((size_t)-1 == ret_len) {
+               DWORD err = GetLastError();
+               CloseHandle(h);
+               SET_ERRNO_FROM_WIN32_CODE(err);
+               return NULL;
+       }
+
+       if (NULL != info && !GetFileInformationByHandle(h, info)) {
+               DWORD err = GetLastError();
+               CloseHandle(h);
+               SET_ERRNO_FROM_WIN32_CODE(err);
+               return NULL;
+       }
+
+       CloseHandle(h);
+
+       return resolved;
+}/*}}}*/
+
+PW32IO char *realpath(const char *path, char *resolved)
+{/*{{{*/
+       return php_win32_ioutil_realpath(path, resolved);
+}/*}}}*/
+
 /*
  * Local variables:
  * tab-width: 4
index 488304d73d9a5dad7e2def82b20d04b9b26efa00..301566ddda8c0703712a97cbd69a7f4c5d18d48d 100644 (file)
@@ -258,6 +258,8 @@ PW32IO int php_win32_ioutil_unlink_w(const wchar_t *path);
 PW32IO int php_win32_ioutil_access_w(const wchar_t *path, mode_t mode);
 PW32IO int php_win32_ioutil_mkdir_w(const wchar_t *path, mode_t mode);
 PW32IO FILE *php_win32_ioutil_fopen_w(const wchar_t *path, const wchar_t *mode);
+PW32IO wchar_t *php_win32_ioutil_realpath_w(const wchar_t *path, wchar_t *resolved);
+PW32IO wchar_t *php_win32_ioutil_realpath_w_ex0(const wchar_t *path, wchar_t *resolved, PBY_HANDLE_FILE_INFORMATION info);
 
 __forceinline static int php_win32_ioutil_access(const char *path, mode_t mode)
 {/*{{{*/
@@ -582,6 +584,59 @@ __forceinline static int php_win32_ioutil_mkdir(const char *path, mode_t mode)
        return ret;
 }/*}}}*/
 
+#define HAVE_REALPATH 1
+PW32IO char *realpath(const char *path, char *resolved);
+
+__forceinline static char *php_win32_ioutil_realpath_ex0(const char *path, char *resolved, PBY_HANDLE_FILE_INFORMATION info)
+{/*{{{*/
+       wchar_t retw[PHP_WIN32_IOUTIL_MAXPATHLEN];
+       char *reta;
+       size_t reta_len;
+
+       PHP_WIN32_IOUTIL_INIT_W(path)
+       if (!pathw) {
+               SET_ERRNO_FROM_WIN32_CODE(ERROR_INVALID_PARAMETER);
+               return NULL;
+       }
+
+       if (NULL == php_win32_ioutil_realpath_w_ex0(pathw, retw, info)) {
+               DWORD err = GetLastError();
+               PHP_WIN32_IOUTIL_CLEANUP_W()
+               SET_ERRNO_FROM_WIN32_CODE(err);
+               return NULL;
+       }
+
+       reta = php_win32_cp_conv_w_to_any(retw, PHP_WIN32_CP_IGNORE_LEN, &reta_len);
+       if (!reta || reta_len > PHP_WIN32_IOUTIL_MAXPATHLEN) {
+               DWORD err = GetLastError();
+               PHP_WIN32_IOUTIL_CLEANUP_W()
+               SET_ERRNO_FROM_WIN32_CODE(err);
+               return NULL;
+       }
+
+       if (NULL == resolved) {
+               /* ret is expected to be either NULL or a buffer of capable size. */
+               resolved = (char *) malloc(reta_len + 1);
+               if (!resolved) {
+                       free(reta);
+                       PHP_WIN32_IOUTIL_CLEANUP_W()
+                       SET_ERRNO_FROM_WIN32_CODE(ERROR_NOT_ENOUGH_MEMORY);
+                       return NULL;
+               }
+       }
+       memmove(resolved, reta, reta_len+1);
+
+       PHP_WIN32_IOUTIL_CLEANUP_W()
+       free(reta);
+
+       return resolved;
+}/*}}}*/
+
+__forceinline static char *php_win32_ioutil_realpath(const char *path, char *resolved)
+{/*{{{*/
+       return php_win32_ioutil_realpath_ex0(path, resolved, NULL);
+}/*}}}*/
+
 #ifdef __cplusplus
 }
 #endif