Hi, The attached patch is going to fix the problem. It implements its own realpath() function, so we won't depend on system anymore. It also improve realpath cache usage by caching intermediate results.
I tested it on Linux and Windows only and it seems to work without problems. It breaks one test related to clearstatcache() function, but this break is expected. Could you please test it. Does it really fix the bug on FreeBSD? Thanks. Dmitry. Hannes Magnusson wrote: > On Thu, Aug 7, 2008 at 22:37, Arnaud Le Blanc <[EMAIL PROTECTED]> wrote: >> virtual_file_ex() assumes that realpath() returns NULL when the file does not >> exists. But BSD's realpath() will not return NULL if all >> components *but the last* exist. So realpath(/foo/bar) will return /foo/bar >> even if bar does not exists. > > And that behavior is intended, but is sadly overwritten in ZTS :( > > -Hannes
Index: TSRM/tsrm_virtual_cwd.c =================================================================== RCS file: /repository/TSRM/tsrm_virtual_cwd.c,v retrieving revision 1.74.2.9.2.35.2.7 diff -u -p -d -r1.74.2.9.2.35.2.7 tsrm_virtual_cwd.c --- TSRM/tsrm_virtual_cwd.c 20 May 2008 07:41:35 -0000 1.74.2.9.2.35.2.7 +++ TSRM/tsrm_virtual_cwd.c 8 Aug 2008 15:33:03 -0000 @@ -257,22 +257,6 @@ static void cwd_globals_dtor(virtual_cwd } /* }}} */ -static char *tsrm_strndup(const char *s, size_t length) /* {{{ */ -{ - char *p; - - p = (char *) malloc(length+1); - if (!p) { - return (char *)NULL; - } - if (length) { - memcpy(p,s,length); - } - p[length]=0; - return p; -} -/* }}} */ - CWD_API void virtual_cwd_startup(void) /* {{{ */ { char cwd[MAXPATHLEN]; @@ -433,7 +417,15 @@ CWD_API void realpath_cache_del(const ch static inline void realpath_cache_add(const char *path, int path_len, const char *realpath, int realpath_len, time_t t TSRMLS_DC) /* {{{ */ { - long size = sizeof(realpath_cache_bucket) + path_len + 1 + realpath_len + 1; + long size = sizeof(realpath_cache_bucket) + path_len + 1; + int same = 1; + + if (realpath_len != path_len || + memcmp(path, realpath, path_len) != 0) { + size += realpath_len + 1; + same = 0; + } + if (CWDG(realpath_cache_size) + size <= CWDG(realpath_cache_size_limit)) { realpath_cache_bucket *bucket = malloc(size); unsigned long n; @@ -442,8 +434,12 @@ static inline void realpath_cache_add(co bucket->path = (char*)bucket + sizeof(realpath_cache_bucket); memcpy(bucket->path, path, path_len+1); bucket->path_len = path_len; - bucket->realpath = bucket->path + (path_len + 1); - memcpy(bucket->realpath, realpath, realpath_len+1); + if (same) { + bucket->realpath = bucket->path; + } else { + bucket->realpath = bucket->path + (path_len + 1); + memcpy(bucket->realpath, realpath, realpath_len+1); + } bucket->realpath_len = realpath_len; bucket->expires = t + CWDG(realpath_cache_ttl); n = bucket->key % (sizeof(CWDG(realpath_cache)) / sizeof(CWDG(realpath_cache)[0])); @@ -477,283 +473,447 @@ static inline realpath_cache_bucket* rea } /* }}} */ -/* 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) /* {{{ */ +#define LINK_MAX 32 + +static int tsrm_rpath_r(char *path, int start, int len, int *ll, time_t *t, int use_realpath TSRMLS_DC) { - int path_length = strlen(path); - cwd_state old_state; - char orig_path[MAXPATHLEN]; - realpath_cache_bucket *bucket; - time_t t = 0; - int ret; - int use_cache; - int use_relative_path = 0; + int i, j, save; #ifdef TSRM_WIN32 - int is_unc; - int exists; + WIN32_FIND_DATA data; + HANDLE hFind; +#else + struct stat st; #endif - TSRMLS_FETCH(); - - use_cache = ((use_realpath != CWD_EXPAND) && CWDG(realpath_cache_size_limit)); - - if (path_length == 0) - return (1); - if (path_length >= MAXPATHLEN) - return (1); + realpath_cache_bucket *bucket; + char *tmp; + TSRM_ALLOCA_FLAG(use_heap); -#if VIRTUAL_CWD_DEBUG - fprintf(stderr,"cwd = %s path = %s\n", state->cwd, path); -#endif + while (1) { + if (len <= start) { + return start; + } - /* cwd_length can be 0 when getcwd() fails. - * This can happen under solaris when a dir does not have read permissions - * but *does* have execute permissions */ - if (!IS_ABSOLUTE_PATH(path, path_length)) { - if (state->cwd_length == 0) { - use_cache = 0; - use_relative_path = 1; - } else { - int orig_path_len; - int state_cwd_length = state->cwd_length; + i = len; + while (!IS_SLASH(path[i-1])) { + i--; + } -#ifdef TSRM_WIN32 - if (IS_SLASH(path[0])) { - state_cwd_length = 2; + if (i == len || + (i == len - 1 && path[i] == '.')) { + len = i - 1; + continue; + } else if (i == len - 2 && path[i] == '.' && path[i+1] == '.') { + if (i - 1 <= start) { + return start; } -#endif - orig_path_len = path_length + state_cwd_length + 1; - if (orig_path_len >= MAXPATHLEN) { - return 1; + j = tsrm_rpath_r(path, start, i-1, ll, t, use_realpath TSRMLS_CC); + if (j > start) { + j--; + while (j > start && !IS_SLASH(path[j])) { + j--; + } } - memcpy(orig_path, state->cwd, state_cwd_length); - orig_path[state_cwd_length] = DEFAULT_SLASH; - memcpy(orig_path + state_cwd_length + 1, path, path_length + 1); - path = orig_path; - path_length = orig_path_len; + return j; } - } - - if (use_cache) { - t = CWDG(realpath_cache_ttl)?time(0):0; - if ((bucket = realpath_cache_find(path, path_length, t TSRMLS_CC)) != NULL) { - int len = bucket->realpath_len; + + path[len] = 0; - CWD_STATE_COPY(&old_state, state); - state->cwd = (char *) realloc(state->cwd, len+1); - memcpy(state->cwd, bucket->realpath, len+1); - state->cwd_length = len; - if (verify_path && verify_path(state)) { - CWD_STATE_FREE(state); - *state = old_state; - return 1; - } else { - CWD_STATE_FREE(&old_state); - return 0; + if (CWDG(realpath_cache_size_limit)) { + if (!*t) { + *t = time(0); } + if ((bucket = realpath_cache_find(path, len, *t TSRMLS_CC)) != NULL) { + memcpy(path, bucket->realpath, bucket->realpath_len + 1); + return bucket->realpath_len; + } } - } - if (use_realpath != CWD_EXPAND) { -#if !defined(TSRM_WIN32) && !defined(NETWARE) - char resolved_path[MAXPATHLEN]; + save = 1; - if (!realpath(path, resolved_path)) { /* Note: Not threadsafe on older *BSD's */ +#ifdef TSRM_WIN32 + if ((hFind = FindFirstFile(path, &data)) == INVALID_HANDLE_VALUE) { if (use_realpath == CWD_REALPATH) { - return 1; + return -1; } - goto no_realpath; + save = 0; } - use_realpath = CWD_REALPATH; - CWD_STATE_COPY(&old_state, state); - - state->cwd_length = strlen(resolved_path); - state->cwd = (char *) realloc(state->cwd, state->cwd_length+1); - memcpy(state->cwd, resolved_path, state->cwd_length+1); + tmp = tsrm_do_alloca(len+1, use_heap); + memcpy(tmp, path, len+1); #else - goto no_realpath; -#endif - } else { - char *ptr, *path_copy, *free_path; - char *tok; - int ptr_length; -no_realpath: - -#ifdef TSRM_WIN32 - if (memchr(path, '*', path_length) || - memchr(path, '?', path_length)) { - return 1; + if (lstat(path, &st) < 0) { + if (use_realpath == CWD_REALPATH) { + return -1; + } + save = 0; } -#endif - free_path = path_copy = tsrm_strndup(path, path_length); - CWD_STATE_COPY(&old_state, state); + tmp = tsrm_do_alloca(len+1, use_heap); + memcpy(tmp, path, len+1); -#ifdef TSRM_WIN32 - exists = (use_realpath != CWD_EXPAND); - ret = 0; - is_unc = 0; - if (path_length >= 2 && path[1] == ':') { - state->cwd = (char *) realloc(state->cwd, 2 + 1); - state->cwd[0] = toupper(path[0]); - state->cwd[1] = ':'; - state->cwd[2] = '\0'; - state->cwd_length = 2; - path_copy += 2; - } else if (IS_UNC_PATH(path, path_length)) { - state->cwd = (char *) realloc(state->cwd, 1 + 1); - state->cwd[0] = DEFAULT_SLASH; - state->cwd[1] = '\0'; - state->cwd_length = 1; - path_copy += 2; - is_unc = 2; + if (save && S_ISLNK(st.st_mode)) { + if (++(*ll) > LINK_MAX || (j = readlink(tmp, path, MAXPATHLEN)) < 0) { + tsrm_free_alloca(tmp, use_heap); + return -1; + } + path[j] = 0; + if (IS_ABSOLUTE_PATH(path, j)) { + j = tsrm_rpath_r(path, 1, j, ll, t, use_realpath TSRMLS_CC); + if (j < 0) { + tsrm_free_alloca(tmp, use_heap); + return -1; + } + } else { + if (i + j >= MAXPATHLEN) { + tsrm_free_alloca(tmp, use_heap); + return -1; /* buffer overflow */ + } + memmove(path+i, path, j+1); + memcpy(path, tmp, i-1); + path[i-1] = DEFAULT_SLASH; + j = tsrm_rpath_r(path, start, i + j, ll, t, use_realpath TSRMLS_CC); + if (j < 0) { + tsrm_free_alloca(tmp, use_heap); + return -1; + } + } } else { #endif - state->cwd = (char *) realloc(state->cwd, 1); - state->cwd[0] = '\0'; - state->cwd_length = 0; + if (i - 1 <= start) { + j = start; + } else { + j = tsrm_rpath_r(path, start, i-1, ll, t, use_realpath TSRMLS_CC); + if (j < 0) { + tsrm_free_alloca(tmp, use_heap); + return j; + } + if (j > start) { + path[j++] = DEFAULT_SLASH; + } + } + if (j + len - i >= MAXPATHLEN) { + tsrm_free_alloca(tmp, use_heap); + return -1; /* buffer overflow */ + } #ifdef TSRM_WIN32 + if (save) { + memcpy(path+j, data.cFileName, len-i+1); + } else { + memcpy(path+j, tmp+i, len-i+1); + } + j += (len-i); +#else + memcpy(path+j, tmp+i, len-i+1); + j += (len-i); } #endif - - tok = NULL; - ptr = tsrm_strtok_r(path_copy, TOKENIZER_STRING, &tok); - while (ptr) { - ptr_length = strlen(ptr); - if (IS_DIRECTORY_UP(ptr, ptr_length)) { - char save; + if (save && CWDG(realpath_cache_size_limit)) { + realpath_cache_add(tmp, len, path, j, *t TSRMLS_CC); + } - if (use_relative_path) { - CWD_STATE_FREE(state); - *state = old_state; - return 1; - } + tsrm_free_alloca(tmp, use_heap); + return j; + } +} - save = DEFAULT_SLASH; +static char *tsrm_rpath(const char *from, int len, char *to, int use_realpath TSRMLS_DC) +{ + int i; + int start = 1; + int ll = 0; + time_t t = CWDG(realpath_cache_ttl) ? 0 : -1; -#define PREVIOUS state->cwd[state->cwd_length - 1] + if (!IS_ABSOLUTE_PATH(from, len)) { + /* add current directory to relative path */ + if (!getcwd(to, MAXPATHLEN)) { + return NULL; + } + i = strlen(to); + if (i + len >= MAXPATHLEN-1) { + /*buffer overflow */ + return NULL; + } + if (!IS_SLASH(to[i-1])) { + to[i++] = DEFAULT_SLASH; + } + memcpy(to + i, from, len + 1); + len += i; + } else { + memcpy(to, from, len + 1); + } - while (IS_ABSOLUTE_PATH(state->cwd, state->cwd_length) && - !IS_SLASH(PREVIOUS)) { - save = PREVIOUS; - PREVIOUS = '\0'; - state->cwd_length--; - } +#ifdef TSRM_WIN32 + if (IS_UNC_PATH(to, len)) { + /* skip UNC name */ + to[0] = DEFAULT_SLASH; + to[1] = DEFAULT_SLASH; + start = 2; + while (!IS_SLASH(to[start])) { + if (to[start] == 0) { + return to; + } + start++; + } + to[start] = DEFAULT_SLASH; + start++; + while (!IS_SLASH(to[start])) { + if (to[start] == 0) { + return to; + } + start++; + } + to[start] = DEFAULT_SLASH; + start++; + } else if (!IS_SLASH(to[0])) { + to[0] = toupper(to[0]); + to[2] = DEFAULT_SLASH; + start = 3; + } +#endif - if (!IS_ABSOLUTE_PATH(state->cwd, state->cwd_length)) { - state->cwd[state->cwd_length++] = save; - state->cwd[state->cwd_length] = '\0'; + i = tsrm_rpath_r(to, start, len, &ll, &t, use_realpath TSRMLS_CC); + if (i < 0) { + return NULL; + } else { + to[i] = 0; + return to; + } +#if 0 + *ret = 0; + + while (1) { + /* remove trailing slashes '.' and '..' */ + j = 0; + while (1) { + if (len <= PATH_START + 1) { + if (ret == buf + sizeof(buf) - 1) { + to[PATH_START + 1] = 0; + return to; } else { - PREVIOUS = '\0'; - state->cwd_length--; + memcpy(to + PATH_START, ret, sizeof(buf) - (ret - buf)); + return to; } - } else if (!IS_DIRECTORY_CURRENT(ptr, ptr_length)) { - if (use_relative_path) { - state->cwd = (char *) realloc(state->cwd, state->cwd_length+ptr_length+1); - use_relative_path = 0; - } else { - state->cwd = (char *) realloc(state->cwd, state->cwd_length+ptr_length+1+1); -#ifdef TSRM_WIN32 - /* Windows 9x will consider C:\\Foo as a network path. Avoid it. */ - if (state->cwd_length < 2 || - (state->cwd[state->cwd_length-1]!='\\' && state->cwd[state->cwd_length-1]!='/') || - IsDBCSLeadByte(state->cwd[state->cwd_length-2])) { - state->cwd[state->cwd_length++] = DEFAULT_SLASH; - } -#elif defined(NETWARE) - /* - Below code keeps appending to state->cwd a File system seperator - cases where this appending should not happen is given below, - a) sys: should just be left as it is - b) sys:system should just be left as it is, - Colon is allowed only in the first token as volume names alone can have the : in their names. - Files and Directories cannot have : in their names - So the check goes like this, - For second token and above simply append the DEFAULT_SLASH to the state->cwd. - For first token check for the existence of : - if it exists don't append the DEFAULT_SLASH to the state->cwd. - */ - if(((state->cwd_length == 0) && (strchr(ptr, ':') == NULL)) || (state->cwd_length > 0)) { - state->cwd[state->cwd_length++] = DEFAULT_SLASH; - } -#else - state->cwd[state->cwd_length++] = DEFAULT_SLASH; -#endif + } + if (IS_SLASH(to[len-1])) { + /* skip slash */ + len -= 1; + continue; + } else if (to[len-1] == '.') { + if (len > 1 && IS_SLASH(to[len-2])) { + /* skip '.' */ + len -= 2; + continue; + } else if (len > 2 && to[len-2] == '.' && IS_SLASH(to[len-3])) { + /* skip '..' */ + len -= 3; + j++; + continue; } - memcpy(&state->cwd[state->cwd_length], ptr, ptr_length+1); - -#ifdef TSRM_WIN32 - if (use_realpath != CWD_EXPAND) { - WIN32_FIND_DATA data; - HANDLE hFind; + } else if (j > 0) { + /* skip a directory because of the following '..' */ + j--; + while (!IS_SLASH(to[--len])) { + } + continue; + } + to[len] = 0; + break; + } - if ((hFind = FindFirstFile(state->cwd, &data)) != INVALID_HANDLE_VALUE) { - int length = strlen(data.cFileName); + if (use_realpath == CWD_EXPAND) { + goto next_dir; + } - if (length != ptr_length) { - state->cwd = (char *) realloc(state->cwd, state->cwd_length+length+1); - } - memcpy(&state->cwd[state->cwd_length], data.cFileName, length+1); - ptr_length = length; - FindClose(hFind); - ret = 0; - } else { - if (is_unc) { - /* skip share name */ - is_unc--; - ret = 0; - } else { - exists = 0; - if (use_realpath == CWD_REALPATH) { - ret = 1; - } - } - } + if (CWDG(realpath_cache_size_limit)) { + if (!t) { + t = time(0); + } + if ((bucket = realpath_cache_find(to, len, t TSRMLS_CC)) != NULL) { + if (len == bucket->realpath_len && + memcmp(to, bucket->realpath, len) == 0) { + goto next_dir; + } else { + len = bucket->realpath_len; + memcpy(to, bucket->realpath, len + 1); + continue; } -#endif + } + } - state->cwd_length += ptr_length; +#ifdef TSRM_WIN32 + if ((hFind = FindFirstFile(to, &data)) != INVALID_HANDLE_VALUE) { +fprintf(stderr, "1 %s (%d)\n", to, len); + j = strlen(data.cFileName); + + i = len; + while (!IS_SLASH(to[i-1])) { + i--; + } + if (i + j >= MAXPATHLEN-1) { + /*buffer overflow */ + return NULL; + } + if (CWDG(realpath_cache_size_limit)) { + memcpy(tmp, to, i); + memcpy(tmp + i, data.cFileName, j + 1); + realpath_cache_add(to, len, tmp, i + j, t TSRMLS_CC); + } + ret -= (j + 1); + if (ret < buf) { + /* buffer overflow */ + return NULL; + } + *ret = DEFAULT_SLASH; + memcpy(ret+1, data.cFileName, j); + FindClose(hFind); + len = i - 1; + to[len] = 0; +fprintf(stderr, "2 %s - %s\n", to, ret); + continue; + } else { + if (use_realpath == CWD_REALPATH) { + return NULL; + } else { + goto next_dir; } - ptr = tsrm_strtok_r(NULL, TOKENIZER_STRING, &tok); } +#else + if (lstat(to, &st) < 0) { + if (use_realpath == CWD_REALPATH) { + return NULL; + } else { + goto next_dir; + } + } + if (S_ISLNK(st.st_mode)) { + if (++ll > LINK_MAX || (j = readlink(to, link, MAXPATHLEN)) < 0) { + return NULL; + } + link[j] = 0; - free(free_path); + if (!IS_ABSOLUTE_PATH(link, j)) { + /* symlink is a relative path, take dir from the current path */ + i = len; + while (!IS_SLASH(to[i-1])) { + i--; + } + if (i + j >= MAXPATHLEN-1) { + /*buffer overflow */ + return NULL; + } + if (CWDG(realpath_cache_size_limit)) { + memcpy(tmp, to, i); + memcpy(tmp + i, link, j + 1); + realpath_cache_add(to, len, tmp, i + j, t TSRMLS_CC); + } + memcpy(to + i, link, j + 1); + + len = i + j; + continue; + } else { + /* symlink is an absolute path */ + if (CWDG(realpath_cache_size_limit)) { + realpath_cache_add(to, len, link, j, t TSRMLS_CC); + } + memcpy(to, link, j + 1); + len = j; + continue; + } + } +#endif + if (CWDG(realpath_cache_size_limit)) { + realpath_cache_add(to, len, to, len, t TSRMLS_CC); + } - if (use_realpath == CWD_REALPATH) { - if (ret) { - CWD_STATE_FREE(state); - *state = old_state; - return 1; +next_dir: + i = len - 1; + while (!IS_SLASH(to[i])) { + i--; + } + if (i == PATH_START) { + if (ret - buf < len) { + /*buffer overflow */ + return NULL; } + memcpy(to + len, ret, sizeof(buf) - (ret - buf)); + return to; } else { -#if defined(TSRM_WIN32) || defined(NETWARE) - if (path[path_length-1] == '\\' || path[path_length-1] == '/') { -#else - if (path[path_length-1] == '/') { -#endif - state->cwd = (char*)realloc(state->cwd, state->cwd_length + 2); - state->cwd[state->cwd_length++] = DEFAULT_SLASH; - state->cwd[state->cwd_length] = 0; + /* save already resolved suffix of the patch */ + ret -= (len - i); + if (ret < buf) { + /* buffer overflow */ + return NULL; } - } + memcpy(ret, to + i, len - i); - if (state->cwd_length == COPY_WHEN_ABSOLUTE(state->cwd)) { - state->cwd = (char *) realloc(state->cwd, state->cwd_length+1+1); - state->cwd[state->cwd_length] = DEFAULT_SLASH; - state->cwd[state->cwd_length+1] = '\0'; - state->cwd_length++; + /* resolve the prefix */ + len = i; + to[len] = 0; + continue; } } +#endif +} - /* Store existent file in realpath cache. */ +/* 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) /* {{{ */ +{ + int path_length = strlen(path); + cwd_state old_state; + char orig_path[MAXPATHLEN]; + char resolved_path[MAXPATHLEN]; + int ret; + int use_relative_path = 0; #ifdef TSRM_WIN32 - if (use_cache && !is_unc && exists) { -#else - if (use_cache && (use_realpath == CWD_REALPATH)) { + int is_unc; + int exists; #endif - realpath_cache_add(path, path_length, state->cwd, state->cwd_length, t TSRMLS_CC); + TSRMLS_FETCH(); + + if (path_length == 0) + return (1); + if (path_length >= MAXPATHLEN) + return (1); + +#if VIRTUAL_CWD_DEBUG + fprintf(stderr,"cwd = %s path = %s\n", state->cwd, path); +#endif + + /* cwd_length can be 0 when getcwd() fails. + * This can happen under solaris when a dir does not have read permissions + * but *does* have execute permissions */ + if (!IS_ABSOLUTE_PATH(path, path_length)) { + if (state->cwd_length == 0) { + use_relative_path = 1; + } else { + int orig_path_len; + int state_cwd_length = state->cwd_length; + +#ifdef TSRM_WIN32 + if (IS_SLASH(path[0])) { + state_cwd_length = 2; + } +#endif + orig_path_len = path_length + state_cwd_length + 1; + if (orig_path_len >= MAXPATHLEN) { + return 1; + } + memcpy(orig_path, state->cwd, state_cwd_length); + orig_path[state_cwd_length] = DEFAULT_SLASH; + memcpy(orig_path + state_cwd_length + 1, path, path_length + 1); + path = orig_path; + path_length = orig_path_len; + } + } + + if (!tsrm_rpath(path, path_length, resolved_path, use_realpath TSRMLS_CC)) { + return 1; } + CWD_STATE_COPY(&old_state, state); + state->cwd_length = strlen(resolved_path); + state->cwd = (char *) realloc(state->cwd, state->cwd_length+1); + memcpy(state->cwd, resolved_path, state->cwd_length+1); if (verify_path && verify_path(state)) { CWD_STATE_FREE(state);
-- PHP Internals - PHP Runtime Development Mailing List To unsubscribe, visit: http://www.php.net/unsub.php