}
/* }}} */
-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];
}
/* }}} */
-static inline void realpath_cache_add(const char *path, int path_len, const char *realpath, int realpath_len, time_t t TSRMLS_DC) /* {{{ */
+static inline void realpath_cache_add(const char *path, int path_len, const char *realpath, int realpath_len, int is_dir, 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;
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->is_dir = is_dir;
bucket->expires = t + CWDG(realpath_cache_ttl);
n = bucket->key % (sizeof(CWDG(realpath_cache)) / sizeof(CWDG(realpath_cache)[0]));
bucket->next = CWDG(realpath_cache)[n];
}
/* }}} */
-/* 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_realpath_r(char *path, int start, int len, int *ll, time_t *t, int use_realpath, int is_dir 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;
+ int directory = 0;
#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 (i > start && !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] == '.')) {
+ /* remove double slashes and '.' */
+ len = i - 1;
+ is_dir = 1;
+ continue;
+ } else if (i == len - 2 && path[i] == '.' && path[i+1] == '.') {
+ /* remove '..' and previous directory */
+ if (i - 1 <= start) {
+ return start ? start : len;
}
-#endif
- orig_path_len = path_length + state_cwd_length + 1;
- if (orig_path_len >= MAXPATHLEN) {
- return 1;
+ j = tsrm_realpath_r(path, start, i-1, ll, t, use_realpath, 1 TSRMLS_CC);
+ if (j > start) {
+ j--;
+ while (j > start && !IS_SLASH(path[j])) {
+ j--;
+ }
+ if (!start) {
+ /* leading '..' must not be removed in case of relative path */
+ if (j == 0 && path[0] == '.' && path[1] == '.' &&
+ IS_SLASH(path[2])) {
+ path[3] = '.';
+ path[4] = '.';
+ path[5] = DEFAULT_SLASH;
+ j = 5;
+ } else if (j > 0 &&
+ path[j+1] == '.' && path[j+2] == '.' &&
+ IS_SLASH(path[j+3])) {
+ j += 4;
+ path[j++] = '.';
+ path[j++] = '.';
+ path[j] = DEFAULT_SLASH;
+ }
+ }
+ } else if (!start && !j) {
+ /* leading '..' must not be removed in case of relative path */
+ path[0] = '.';
+ path[1] = '.';
+ path[2] = DEFAULT_SLASH;
+ j = 2;
}
- 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;
}
- }
+
+ path[len] = 0;
- 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;
-
- 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;
+ save = (use_realpath != CWD_EXPAND);
+
+ if (start && save && CWDG(realpath_cache_size_limit)) {
+ /* cache lookup for absolute path */
+ if (!*t) {
+ *t = time(0);
}
+ if ((bucket = realpath_cache_find(path, len, *t TSRMLS_CC)) != NULL) {
+ if (is_dir && !bucket->is_dir) {
+ /* not a directory */
+ return -1;
+ } else {
+ 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];
-
- if (!realpath(path, resolved_path)) { /* Note: Not threadsafe on older *BSD's */
+#ifdef TSRM_WIN32
+ if (save && (hFind = FindFirstFile(path, &data)) == INVALID_HANDLE_VALUE) {
if (use_realpath == CWD_REALPATH) {
- return 1;
+ /* file not found */
+ return -1;
}
- goto no_realpath;
+ /* continue resolution anyway but don't save result in the cache */
+ 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);
+ if (save) {
+ directory = (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
+ if (is_dir && !directory) {
+ /* not a directory */
+ FindClose(hFind);
+ return -1;
+ }
+ }
+ tmp = tsrm_do_alloca(len+1, use_heap);
+ memcpy(tmp, path, len+1);
+#elif defined(NETWARE)
+ save = 0;
+ 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 (save && lstat(path, &st) < 0) {
+ if (use_realpath == CWD_REALPATH) {
+ /* file not found */
+ return -1;
+ }
+ /* continue resolution anyway but don't save result in the cache */
+ 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) {
+ /* too many links or broken symlinks */
+ tsrm_free_alloca(tmp, use_heap);
+ return -1;
+ }
+ path[j] = 0;
+ if (IS_ABSOLUTE_PATH(path, j)) {
+ j = tsrm_realpath_r(path, 1, j, ll, t, use_realpath, is_dir TSRMLS_CC);
+ if (j < 0) {
+ tsrm_free_alloca(tmp, use_heap);
+ return -1;
+ }
+ } else {
+ if (i + j >= MAXPATHLEN-1) {
+ 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_realpath_r(path, start, i + j, ll, t, use_realpath, is_dir TSRMLS_CC);
+ if (j < 0) {
+ tsrm_free_alloca(tmp, use_heap);
+ return -1;
+ }
+ }
} else {
+ if (save) {
+ directory = S_ISDIR(st.st_mode);
+ if (is_dir && !directory) {
+ /* not a directory */
+ return -1;
+ }
+ }
#endif
- state->cwd = (char *) realloc(state->cwd, 1);
- state->cwd[0] = '\0';
- state->cwd_length = 0;
+ if (i - 1 <= start) {
+ j = start;
+ } else {
+ /* some leading directories may be unaccessable */
+ j = tsrm_realpath_r(path, start, i-1, ll, t, save ? CWD_FILEPATH : use_realpath, 1 TSRMLS_CC);
+ if (j > start) {
+ path[j++] = DEFAULT_SLASH;
+ }
+ }
#ifdef TSRM_WIN32
+ if (j < 0 || j + len - i >= MAXPATHLEN-1) {
+ tsrm_free_alloca(tmp, use_heap);
+ if (save) FindClose(hFind);
+ return -1;
+ }
+ if (save) {
+ memcpy(path+j, data.cFileName, len-i+1);
+ FindClose(hFind);
+ } else {
+ /* use the original file or directory name as it wasn't found */
+ memcpy(path+j, tmp+i, len-i+1);
+ }
+ j += (len-i);
+#else
+ if (j < 0 || j + len - i >= MAXPATHLEN-1) {
+ tsrm_free_alloca(tmp, use_heap);
+ return -1;
+ }
+ 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 (use_relative_path) {
- CWD_STATE_FREE(state);
- *state = old_state;
- return 1;
- }
- save = DEFAULT_SLASH;
+ if (save && start && CWDG(realpath_cache_size_limit)) {
+ /* save absolute path in the cache */
+ realpath_cache_add(tmp, len, path, j, directory, *t TSRMLS_CC);
+ }
-#define PREVIOUS state->cwd[state->cwd_length - 1]
+ tsrm_free_alloca(tmp, use_heap);
+ return j;
+ }
+}
+/* }}} */
- while (IS_ABSOLUTE_PATH(state->cwd, state->cwd_length) &&
- !IS_SLASH(PREVIOUS)) {
- save = PREVIOUS;
- PREVIOUS = '\0';
- state->cwd_length--;
- }
+/* 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);
+ char resolved_path[MAXPATHLEN];
+ int start = 1;
+ int ll = 0;
+ time_t t;
+ int ret;
+ int add_slash;
+ TSRMLS_FETCH();
- if (!IS_ABSOLUTE_PATH(state->cwd, state->cwd_length)) {
- state->cwd[state->cwd_length++] = save;
- state->cwd[state->cwd_length] = '\0';
- } else {
- PREVIOUS = '\0';
- state->cwd_length--;
- }
- } 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;
+ if (path_length == 0 || path_length >= MAXPATHLEN-1) {
+ return 1;
+ }
+
+#if VIRTUAL_CWD_DEBUG
+ fprintf(stderr,"cwd = %s path = %s\n", state->cwd, path);
#endif
- }
- memcpy(&state->cwd[state->cwd_length], ptr, ptr_length+1);
+
+ /* 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) {
+ /* resolve relative path */
+ start = 0;
+ memcpy(resolved_path , path, path_length + 1);
+ } else {
+ int state_cwd_length = state->cwd_length;
#ifdef TSRM_WIN32
- if (use_realpath != CWD_EXPAND) {
- WIN32_FIND_DATA data;
- HANDLE hFind;
-
- if ((hFind = FindFirstFile(state->cwd, &data)) != INVALID_HANDLE_VALUE) {
- int length = strlen(data.cFileName);
-
- 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 (IS_SLASH(path[0])) {
+ state_cwd_length = 2;
+ }
#endif
-
- state->cwd_length += ptr_length;
+ if (path_length + state_cwd_length + 1 >= MAXPATHLEN-1) {
+ return 1;
}
- ptr = tsrm_strtok_r(NULL, TOKENIZER_STRING, &tok);
+ memcpy(resolved_path, state->cwd, state_cwd_length);
+ resolved_path[state_cwd_length] = DEFAULT_SLASH;
+ memcpy(resolved_path + state_cwd_length + 1, path, path_length + 1);
+ path_length += state_cwd_length + 1;
}
+ } else {
+ memcpy(resolved_path , path, path_length + 1);
+ }
- free(free_path);
+#ifdef TSRM_WIN32
+ if (memchr(resolved_path, '*', path_length) ||
+ memchr(resolved_path, '?', path_length)) {
+ return 1;
+ }
+#endif
- if (use_realpath == CWD_REALPATH) {
- if (ret) {
- CWD_STATE_FREE(state);
- *state = old_state;
- return 1;
+#ifdef TSRM_WIN32
+ if (IS_UNC_PATH(resolved_path, path_length)) {
+ /* skip UNC name */
+ resolved_path[0] = DEFAULT_SLASH;
+ resolved_path[1] = DEFAULT_SLASH;
+ start = 2;
+ while (!IS_SLASH(resolved_path[start])) {
+ if (resolved_path[start] == 0) {
+ goto verify;
}
- } 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;
+ resolved_path[start] = toupper(resolved_path[start]);
+ start++;
+ }
+ resolved_path[start++] = DEFAULT_SLASH;
+ while (!IS_SLASH(resolved_path[start])) {
+ if (resolved_path[start] == 0) {
+ goto verify;
}
+ resolved_path[start] = toupper(resolved_path[start]);
+ start++;
}
+ resolved_path[start++] = DEFAULT_SLASH;
+ } else if (IS_ABSOLUTE_PATH(resolved_path, path_length)) {
+ /* skip DRIVE name */
+ resolved_path[0] = toupper(resolved_path[0]);
+ resolved_path[2] = DEFAULT_SLASH;
+ start = 3;
+ }
+#elif defined(NETWARE)
+ if (IS_ABSOLUTE_PATH(resolved_path, path_length)) {
+ /* skip VOLUME name */
+ start = 0;
+ while (start != ':') {
+ if (resolved_path[start] == 0) return -1;
+ start++;
+ }
+ start++;
+ if (!IS_SLASH(resolved_path[start])) return -1;
+ resolved_path[start++] = DEFAULT_SLASH;
+ }
+#endif
- 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++;
+ add_slash = (use_realpath != CWD_REALPATH) && path_length > 0 && IS_SLASH(resolved_path[path_length-1]);
+ t = CWDG(realpath_cache_ttl) ? 0 : -1;
+ path_length = tsrm_realpath_r(resolved_path, start, path_length, &ll, &t, use_realpath, 0 TSRMLS_CC);
+
+ if (path_length < 0) {
+ return 1;
+ }
+
+ 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;
}
+ resolved_path[path_length++] = DEFAULT_SLASH;
}
+ resolved_path[path_length] = 0;
- /* Store existent file in realpath cache. */
#ifdef TSRM_WIN32
- if (use_cache && !is_unc && exists) {
-#else
- if (use_cache && (use_realpath == CWD_REALPATH)) {
+verify:
#endif
- realpath_cache_add(path, path_length, state->cwd, state->cwd_length, t TSRMLS_CC);
- }
+ if (verify_path) {
+ cwd_state old_state;
- if (verify_path && verify_path(state)) {
- CWD_STATE_FREE(state);
- *state = old_state;
- ret = 1;
+ CWD_STATE_COPY(&old_state, state);
+ state->cwd_length = path_length;
+ state->cwd = (char *) realloc(state->cwd, state->cwd_length+1);
+ memcpy(state->cwd, resolved_path, state->cwd_length+1);
+ if (verify_path(state)) {
+ CWD_STATE_FREE(state);
+ *state = old_state;
+ ret = 1;
+ } else {
+ CWD_STATE_FREE(&old_state);
+ ret = 0;
+ }
} else {
- CWD_STATE_FREE(&old_state);
+ state->cwd_length = path_length;
+ state->cwd = (char *) realloc(state->cwd, state->cwd_length+1);
+ memcpy(state->cwd, resolved_path, state->cwd_length+1);
ret = 0;
}