/* +----------------------------------------------------------------------+ | Copyright (c) The PHP Group | +----------------------------------------------------------------------+ | This source file is subject to version 3.01 of the PHP license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.php.net/license/3_01.txt | | If you did not receive a copy of the PHP license and are unable to | | obtain it through the world-wide-web, please send a note to | | license@php.net so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Authors: Andi Gutmans | | Sascha Schumann | | Pierre Joye | +----------------------------------------------------------------------+ */ #include #include #include #include #include #include #include #include #include #include "zend.h" #include "zend_virtual_cwd.h" #ifdef ZEND_WIN32 #include #include "tsrm_win32.h" # ifndef IO_REPARSE_TAG_SYMLINK # define IO_REPARSE_TAG_SYMLINK 0xA000000C # endif # ifndef IO_REPARSE_TAG_DEDUP # define IO_REPARSE_TAG_DEDUP 0x80000013 # endif # ifndef IO_REPARSE_TAG_CLOUD # define IO_REPARSE_TAG_CLOUD (0x9000001AL) # endif /* IO_REPARSE_TAG_CLOUD_1 through IO_REPARSE_TAG_CLOUD_F have values of 0x9000101AL to 0x9000F01AL, they can be checked against the mask. */ #ifndef IO_REPARSE_TAG_CLOUD_MASK #define IO_REPARSE_TAG_CLOUD_MASK (0x0000F000L) #endif #ifndef IO_REPARSE_TAG_ONEDRIVE #define IO_REPARSE_TAG_ONEDRIVE (0x80000021L) #endif # ifndef IO_REPARSE_TAG_ACTIVISION_HSM # define IO_REPARSE_TAG_ACTIVISION_HSM (0x00000047L) # endif # ifndef IO_REPARSE_TAG_PROJFS # define IO_REPARSE_TAG_PROJFS (0x9000001CL) # endif # ifndef VOLUME_NAME_NT # define VOLUME_NAME_NT 0x2 # endif # ifndef VOLUME_NAME_DOS # define VOLUME_NAME_DOS 0x0 # endif # include # include #endif #define VIRTUAL_CWD_DEBUG 0 #include "TSRM.h" /* Only need mutex for popen() in Windows because it doesn't chdir() on UNIX */ #if defined(ZEND_WIN32) && defined(ZTS) MUTEX_T cwd_mutex; #endif #ifdef ZTS ts_rsrc_id cwd_globals_id; size_t cwd_globals_offset; #else virtual_cwd_globals cwd_globals; #endif static cwd_state main_cwd_state; /* True global */ #ifndef ZEND_WIN32 #include #else #include #include "zend_globals.h" #include "zend_globals_macros.h" #endif #define CWD_STATE_COPY(d, s) \ (d)->cwd_length = (s)->cwd_length; \ (d)->cwd = (char *) emalloc((s)->cwd_length+1); \ memcpy((d)->cwd, (s)->cwd, (s)->cwd_length+1); #define CWD_STATE_FREE(s) \ efree((s)->cwd); \ (s)->cwd_length = 0; #ifdef ZEND_WIN32 # define CWD_STATE_FREE_ERR(state) do { \ DWORD last_error = GetLastError(); \ CWD_STATE_FREE(state); \ SetLastError(last_error); \ } while (0) #else # define CWD_STATE_FREE_ERR(state) CWD_STATE_FREE(state) #endif static int php_is_dir_ok(const cwd_state *state) /* {{{ */ { zend_stat_t buf; if (php_sys_stat(state->cwd, &buf) == 0 && S_ISDIR(buf.st_mode)) return (0); return (1); } /* }}} */ static int php_is_file_ok(const cwd_state *state) /* {{{ */ { zend_stat_t buf; if (php_sys_stat(state->cwd, &buf) == 0 && S_ISREG(buf.st_mode)) return (0); return (1); } /* }}} */ static void cwd_globals_ctor(virtual_cwd_globals *cwd_g) /* {{{ */ { CWD_STATE_COPY(&cwd_g->cwd, &main_cwd_state); cwd_g->realpath_cache_size = 0; cwd_g->realpath_cache_size_limit = REALPATH_CACHE_SIZE; cwd_g->realpath_cache_ttl = REALPATH_CACHE_TTL; memset(cwd_g->realpath_cache, 0, sizeof(cwd_g->realpath_cache)); } /* }}} */ static void cwd_globals_dtor(virtual_cwd_globals *cwd_g) /* {{{ */ { realpath_cache_clean(); } /* }}} */ void virtual_cwd_main_cwd_init(uint8_t reinit) /* {{{ */ { char cwd[MAXPATHLEN]; char *result; if (reinit) { free(main_cwd_state.cwd); } #ifdef ZEND_WIN32 ZeroMemory(&cwd, sizeof(cwd)); result = php_win32_ioutil_getcwd(cwd, sizeof(cwd)); #else result = getcwd(cwd, sizeof(cwd)); #endif if (!result) { cwd[0] = '\0'; } main_cwd_state.cwd_length = strlen(cwd); #ifdef ZEND_WIN32 if (main_cwd_state.cwd_length >= 2 && cwd[1] == ':') { cwd[0] = toupper(cwd[0]); } #endif main_cwd_state.cwd = strdup(cwd); } /* }}} */ CWD_API void virtual_cwd_startup(void) /* {{{ */ { virtual_cwd_main_cwd_init(0); #ifdef ZTS ts_allocate_fast_id(&cwd_globals_id, &cwd_globals_offset, sizeof(virtual_cwd_globals), (ts_allocate_ctor) cwd_globals_ctor, (ts_allocate_dtor) cwd_globals_dtor); #else cwd_globals_ctor(&cwd_globals); #endif #if (defined(ZEND_WIN32)) && defined(ZTS) cwd_mutex = tsrm_mutex_alloc(); #endif } /* }}} */ CWD_API void virtual_cwd_shutdown(void) /* {{{ */ { #ifndef ZTS cwd_globals_dtor(&cwd_globals); #endif #if (defined(ZEND_WIN32)) && defined(ZTS) tsrm_mutex_free(cwd_mutex); #endif free(main_cwd_state.cwd); /* Don't use CWD_STATE_FREE because the non global states will probably use emalloc()/efree() */ } /* }}} */ CWD_API int virtual_cwd_activate(void) /* {{{ */ { if (CWDG(cwd).cwd == NULL) { CWD_STATE_COPY(&CWDG(cwd), &main_cwd_state); } return 0; } /* }}} */ CWD_API int virtual_cwd_deactivate(void) /* {{{ */ { if (CWDG(cwd).cwd != NULL) { CWD_STATE_FREE(&CWDG(cwd)); CWDG(cwd).cwd = NULL; } return 0; } /* }}} */ CWD_API char *virtual_getcwd_ex(size_t *length) /* {{{ */ { cwd_state *state; state = &CWDG(cwd); if (state->cwd_length == 0) { char *retval; *length = 1; retval = (char *) emalloc(2); retval[0] = DEFAULT_SLASH; retval[1] = '\0'; return retval; } #ifdef ZEND_WIN32 /* If we have something like C: */ if (state->cwd_length == 2 && state->cwd[state->cwd_length-1] == ':') { char *retval; *length = state->cwd_length+1; retval = (char *) emalloc(*length+1); memcpy(retval, state->cwd, *length); retval[0] = toupper(retval[0]); retval[*length-1] = DEFAULT_SLASH; retval[*length] = '\0'; return retval; } #endif if (!state->cwd) { *length = 0; return NULL; } *length = state->cwd_length; return estrdup(state->cwd); } /* }}} */ /* Same semantics as UNIX getcwd() */ CWD_API char *virtual_getcwd(char *buf, size_t size) /* {{{ */ { size_t length; char *cwd; cwd = virtual_getcwd_ex(&length); if (buf == NULL) { return cwd; } if (length > size-1) { efree(cwd); errno = ERANGE; /* Is this OK? */ return NULL; } if (!cwd) { return NULL; } memcpy(buf, cwd, length+1); efree(cwd); return buf; } /* }}} */ #ifdef ZEND_WIN32 static inline zend_ulong realpath_cache_key(const char *path, size_t path_len) /* {{{ */ { register zend_ulong h; size_t bucket_key_len; const char *bucket_key_start = tsrm_win32_get_path_sid_key(path, path_len, &bucket_key_len); const char *bucket_key = bucket_key_start; const char *e; if (!bucket_key) { return 0; } e = bucket_key + bucket_key_len; for (h = Z_UL(2166136261); bucket_key < e;) { h *= Z_UL(16777619); h ^= *bucket_key++; } if (bucket_key_start != path) { HeapFree(GetProcessHeap(), 0, (LPVOID)bucket_key_start); } return h; } /* }}} */ #else static inline zend_ulong realpath_cache_key(const char *path, size_t path_len) /* {{{ */ { register zend_ulong h; const char *e = path + path_len; for (h = Z_UL(2166136261); path < e;) { h *= Z_UL(16777619); h ^= *path++; } return h; } /* }}} */ #endif /* defined(ZEND_WIN32) */ CWD_API void realpath_cache_clean(void) /* {{{ */ { uint32_t i; for (i = 0; i < sizeof(CWDG(realpath_cache))/sizeof(CWDG(realpath_cache)[0]); i++) { realpath_cache_bucket *p = CWDG(realpath_cache)[i]; while (p != NULL) { realpath_cache_bucket *r = p; p = p->next; free(r); } CWDG(realpath_cache)[i] = NULL; } CWDG(realpath_cache_size) = 0; } /* }}} */ CWD_API void realpath_cache_del(const char *path, size_t path_len) /* {{{ */ { zend_ulong key = realpath_cache_key(path, path_len); zend_ulong n = key % (sizeof(CWDG(realpath_cache)) / sizeof(CWDG(realpath_cache)[0])); realpath_cache_bucket **bucket = &CWDG(realpath_cache)[n]; while (*bucket != NULL) { if (key == (*bucket)->key && path_len == (*bucket)->path_len && memcmp(path, (*bucket)->path, path_len) == 0) { realpath_cache_bucket *r = *bucket; *bucket = (*bucket)->next; /* if the pointers match then only subtract the length of the path */ if(r->path == r->realpath) { CWDG(realpath_cache_size) -= sizeof(realpath_cache_bucket) + r->path_len + 1; } else { CWDG(realpath_cache_size) -= sizeof(realpath_cache_bucket) + r->path_len + 1 + r->realpath_len + 1; } free(r); return; } else { bucket = &(*bucket)->next; } } } /* }}} */ static inline void realpath_cache_add(const char *path, size_t path_len, const char *realpath, size_t realpath_len, int is_dir, time_t t) /* {{{ */ { zend_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); zend_ulong n; if (bucket == NULL) { return; } bucket->key = realpath_cache_key(path, path_len); bucket->path = (char*)bucket + sizeof(realpath_cache_bucket); memcpy(bucket->path, path, path_len+1); bucket->path_len = path_len; 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 > 0; #ifdef ZEND_WIN32 bucket->is_rvalid = 0; bucket->is_readable = 0; bucket->is_wvalid = 0; bucket->is_writable = 0; #endif 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]; CWDG(realpath_cache)[n] = bucket; CWDG(realpath_cache_size) += size; } } /* }}} */ static inline realpath_cache_bucket* realpath_cache_find(const char *path, size_t path_len, time_t t) /* {{{ */ { zend_ulong key = realpath_cache_key(path, path_len); zend_ulong n = key % (sizeof(CWDG(realpath_cache)) / sizeof(CWDG(realpath_cache)[0])); realpath_cache_bucket **bucket = &CWDG(realpath_cache)[n]; while (*bucket != NULL) { if (CWDG(realpath_cache_ttl) && (*bucket)->expires < t) { realpath_cache_bucket *r = *bucket; *bucket = (*bucket)->next; /* if the pointers match then only subtract the length of the path */ if(r->path == r->realpath) { CWDG(realpath_cache_size) -= sizeof(realpath_cache_bucket) + r->path_len + 1; } else { CWDG(realpath_cache_size) -= sizeof(realpath_cache_bucket) + r->path_len + 1 + r->realpath_len + 1; } free(r); } else if (key == (*bucket)->key && path_len == (*bucket)->path_len && memcmp(path, (*bucket)->path, path_len) == 0) { return *bucket; } else { bucket = &(*bucket)->next; } } return NULL; } /* }}} */ CWD_API realpath_cache_bucket* realpath_cache_lookup(const char *path, size_t path_len, time_t t) /* {{{ */ { return realpath_cache_find(path, path_len, t); } /* }}} */ CWD_API zend_long realpath_cache_size(void) { return CWDG(realpath_cache_size); } CWD_API zend_long realpath_cache_max_buckets(void) { return (sizeof(CWDG(realpath_cache)) / sizeof(CWDG(realpath_cache)[0])); } CWD_API realpath_cache_bucket** realpath_cache_get_buckets(void) { return CWDG(realpath_cache); } #undef LINK_MAX #define LINK_MAX 32 static size_t tsrm_realpath_r(char *path, size_t start, size_t len, int *ll, time_t *t, int use_realpath, bool is_dir, int *link_is_dir) /* {{{ */ { size_t i, j; int directory = 0, save; #ifdef ZEND_WIN32 WIN32_FIND_DATAW dataw; HANDLE hFind = INVALID_HANDLE_VALUE; ALLOCA_FLAG(use_heap_large) wchar_t *pathw = NULL; int may_retry_reparse_point; #define FREE_PATHW() \ do { free(pathw); } while(0); #else zend_stat_t st; #endif realpath_cache_bucket *bucket; char *tmp; ALLOCA_FLAG(use_heap) while (1) { if (len <= start) { if (link_is_dir) { *link_is_dir = 1; } return start; } i = len; while (i > start && !IS_SLASH(path[i-1])) { i--; } assert(i < MAXPATHLEN); if (i == len || (i + 1 == len && path[i] == '.')) { /* remove double slashes and '.' */ len = EXPECTED(i > 0) ? i - 1 : 0; is_dir = 1; continue; } else if (i + 2 == len && path[i] == '.' && path[i+1] == '.') { /* remove '..' and previous directory */ is_dir = 1; if (link_is_dir) { *link_is_dir = 1; } if (i <= start + 1) { return start ? start : len; } j = tsrm_realpath_r(path, start, i-1, ll, t, use_realpath, 1, NULL); if (j > start && j != (size_t)-1) { j--; assert(i < MAXPATHLEN); while (j > start && !IS_SLASH(path[j])) { j--; } assert(i < MAXPATHLEN); 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; } return j; } path[len] = 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)) != NULL) { if (is_dir && !bucket->is_dir) { /* not a directory */ return (size_t)-1; } else { if (link_is_dir) { *link_is_dir = bucket->is_dir; } memcpy(path, bucket->realpath, bucket->realpath_len + 1); return bucket->realpath_len; } } } #ifdef ZEND_WIN32 retry_reparse_point: may_retry_reparse_point = 0; if (save) { pathw = php_win32_ioutil_any_to_w(path); if (!pathw) { return (size_t)-1; } hFind = FindFirstFileExW(pathw, FindExInfoBasic, &dataw, FindExSearchNameMatch, NULL, 0); if (INVALID_HANDLE_VALUE == hFind) { if (use_realpath == CWD_REALPATH) { /* file not found */ FREE_PATHW() return (size_t)-1; } /* continue resolution anyway but don't save result in the cache */ save = 0; } else { FindClose(hFind); } } tmp = do_alloca(len+1, use_heap); memcpy(tmp, path, len+1); retry_reparse_tag_cloud: if(save && !(IS_UNC_PATH(path, len) && len >= 3 && path[2] != '?') && (dataw.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) ) { /* File is a reparse point. Get the target */ HANDLE hLink = NULL; PHP_WIN32_IOUTIL_REPARSE_DATA_BUFFER * pbuffer; DWORD retlength = 0; size_t bufindex = 0; uint8_t isabsolute = 0; wchar_t * reparsetarget; BOOL isVolume = FALSE; #if VIRTUAL_CWD_DEBUG char *printname = NULL; #endif char *substitutename = NULL; size_t substitutename_len; size_t substitutename_off = 0; wchar_t tmpsubstname[MAXPATHLEN]; if(++(*ll) > LINK_MAX) { free_alloca(tmp, use_heap); FREE_PATHW() return (size_t)-1; } hLink = CreateFileW(pathw, 0, PHP_WIN32_IOUTIL_DEFAULT_SHARE_MODE, NULL, OPEN_EXISTING, FILE_FLAG_OPEN_REPARSE_POINT|FILE_FLAG_BACKUP_SEMANTICS, NULL); if(hLink == INVALID_HANDLE_VALUE) { free_alloca(tmp, use_heap); FREE_PATHW() return (size_t)-1; } pbuffer = (PHP_WIN32_IOUTIL_REPARSE_DATA_BUFFER *)do_alloca(MAXIMUM_REPARSE_DATA_BUFFER_SIZE, use_heap_large); if (pbuffer == NULL) { CloseHandle(hLink); free_alloca(tmp, use_heap); FREE_PATHW() return (size_t)-1; } 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); 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_reparse_tag_cloud; } free_alloca(tmp, use_heap); CloseHandle(hLink); FREE_PATHW() return (size_t)-1; } CloseHandle(hLink); if(pbuffer->ReparseTag == IO_REPARSE_TAG_SYMLINK) { may_retry_reparse_point = 1; reparsetarget = pbuffer->SymbolicLinkReparseBuffer.ReparseTarget; isabsolute = pbuffer->SymbolicLinkReparseBuffer.Flags == 0; #if VIRTUAL_CWD_DEBUG printname = php_win32_ioutil_w_to_any(reparsetarget + pbuffer->MountPointReparseBuffer.PrintNameOffset / sizeof(WCHAR)); if (!printname) { free_alloca(pbuffer, use_heap_large); free_alloca(tmp, use_heap); FREE_PATHW() return (size_t)-1; } #endif substitutename_len = pbuffer->MountPointReparseBuffer.SubstituteNameLength / sizeof(WCHAR); if (substitutename_len >= MAXPATHLEN) { free_alloca(pbuffer, use_heap_large); free_alloca(tmp, use_heap); FREE_PATHW() return (size_t)-1; } memmove(tmpsubstname, reparsetarget + pbuffer->MountPointReparseBuffer.SubstituteNameOffset / sizeof(WCHAR), pbuffer->MountPointReparseBuffer.SubstituteNameLength); tmpsubstname[substitutename_len] = L'\0'; substitutename = php_win32_cp_conv_w_to_any(tmpsubstname, substitutename_len, &substitutename_len); if (!substitutename || substitutename_len >= MAXPATHLEN) { free_alloca(pbuffer, use_heap_large); free_alloca(tmp, use_heap); free(substitutename); #if VIRTUAL_CWD_DEBUG free(printname); #endif FREE_PATHW() return (size_t)-1; } } else if(pbuffer->ReparseTag == IO_REPARSE_TAG_MOUNT_POINT) { isabsolute = 1; reparsetarget = pbuffer->MountPointReparseBuffer.ReparseTarget; #if VIRTUAL_CWD_DEBUG printname = php_win32_ioutil_w_to_any(reparsetarget + pbuffer->MountPointReparseBuffer.PrintNameOffset / sizeof(WCHAR)); if (!printname) { free_alloca(pbuffer, use_heap_large); free_alloca(tmp, use_heap); FREE_PATHW() return (size_t)-1; } #endif substitutename_len = pbuffer->MountPointReparseBuffer.SubstituteNameLength / sizeof(WCHAR); if (substitutename_len >= MAXPATHLEN) { free_alloca(pbuffer, use_heap_large); free_alloca(tmp, use_heap); FREE_PATHW() return (size_t)-1; } memmove(tmpsubstname, reparsetarget + pbuffer->MountPointReparseBuffer.SubstituteNameOffset / sizeof(WCHAR), pbuffer->MountPointReparseBuffer.SubstituteNameLength); tmpsubstname[substitutename_len] = L'\0'; substitutename = php_win32_cp_conv_w_to_any(tmpsubstname, substitutename_len, &substitutename_len); if (!substitutename || substitutename_len >= MAXPATHLEN) { free_alloca(pbuffer, use_heap_large); free_alloca(tmp, use_heap); free(substitutename); #if VIRTUAL_CWD_DEBUG free(printname); #endif FREE_PATHW() return (size_t)-1; } } else if (pbuffer->ReparseTag == IO_REPARSE_TAG_DEDUP || /* Starting with 1709. */ (pbuffer->ReparseTag & ~IO_REPARSE_TAG_CLOUD_MASK) == IO_REPARSE_TAG_CLOUD || IO_REPARSE_TAG_ONEDRIVE == pbuffer->ReparseTag || IO_REPARSE_TAG_ACTIVISION_HSM == pbuffer->ReparseTag || IO_REPARSE_TAG_PROJFS == pbuffer->ReparseTag) { isabsolute = 1; substitutename = malloc((len + 1) * sizeof(char)); if (!substitutename) { free_alloca(pbuffer, use_heap_large); free_alloca(tmp, use_heap); FREE_PATHW() return (size_t)-1; } memcpy(substitutename, path, len + 1); substitutename_len = len; } else { /* XXX this might be not the end, restart handling with REPARSE_GUID_DATA_BUFFER should be implemented. */ free_alloca(pbuffer, use_heap_large); free_alloca(tmp, use_heap); FREE_PATHW() return (size_t)-1; } if(isabsolute && substitutename_len > 4) { /* Do not resolve volumes (for now). A mounted point can target a volume without a drive, it is not certain that all IO functions we use in php and its deps support path with volume GUID instead of the DOS way, like: d:\test\mnt\foo \\?\Volume{62d1c3f8-83b9-11de-b108-806e6f6e6963}\foo */ if (strncmp(substitutename, "\\??\\Volume{",11) == 0 || strncmp(substitutename, "\\\\?\\Volume{",11) == 0 || strncmp(substitutename, "\\??\\UNC\\", 8) == 0 ) { isVolume = TRUE; substitutename_off = 0; } else /* do not use the \??\ and \\?\ prefix*/ if (strncmp(substitutename, "\\??\\", 4) == 0 || strncmp(substitutename, "\\\\?\\", 4) == 0) { substitutename_off = 4; } } if (!isVolume) { char * tmp2 = substitutename + substitutename_off; for (bufindex = 0; bufindex + substitutename_off < substitutename_len; bufindex++) { *(path + bufindex) = *(tmp2 + bufindex); } *(path + bufindex) = 0; j = bufindex; } else { j = len; } #if VIRTUAL_CWD_DEBUG fprintf(stderr, "reparse: print: %s ", printname); fprintf(stderr, "sub: %s ", substitutename); fprintf(stderr, "resolved: %s ", path); free(printname); #endif free_alloca(pbuffer, use_heap_large); free(substitutename); if (may_retry_reparse_point) { DWORD attrs; FREE_PATHW() pathw = php_win32_ioutil_any_to_w(path); if (!pathw) { return (size_t)-1; } attrs = GetFileAttributesW(pathw); if (!isVolume && attrs != INVALID_FILE_ATTRIBUTES && (attrs & FILE_ATTRIBUTE_REPARSE_POINT)) { free_alloca(tmp, use_heap); FREE_PATHW() goto retry_reparse_point; } } if(isabsolute == 1) { if (!((j == 3) && (path[1] == ':') && (path[2] == '\\'))) { /* use_realpath is 0 in the call below coz path is absolute*/ j = tsrm_realpath_r(path, 0, j, ll, t, 0, is_dir, &directory); if(j == (size_t)-1) { free_alloca(tmp, use_heap); FREE_PATHW() return (size_t)-1; } } } else { if(i + j >= MAXPATHLEN - 1) { free_alloca(tmp, use_heap); FREE_PATHW() return (size_t)-1; } 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, &directory); if(j == (size_t)-1) { free_alloca(tmp, use_heap); FREE_PATHW() return (size_t)-1; } } directory = (dataw.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY); if(link_is_dir) { *link_is_dir = directory; } } else { if (save) { directory = (dataw.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0; if (is_dir && !directory) { /* not a directory */ free_alloca(tmp, use_heap); FREE_PATHW() return (size_t)-1; } } #else if (save && php_sys_lstat(path, &st) < 0) { if (use_realpath == CWD_REALPATH) { /* file not found */ return (size_t)-1; } /* continue resolution anyway but don't save result in the cache */ save = 0; } tmp = do_alloca(len+1, use_heap); memcpy(tmp, path, len+1); if (save && S_ISLNK(st.st_mode)) { if (++(*ll) > LINK_MAX || (j = (size_t)php_sys_readlink(tmp, path, MAXPATHLEN)) == (size_t)-1) { /* too many links or broken symlinks */ free_alloca(tmp, use_heap); return (size_t)-1; } path[j] = 0; if (IS_ABSOLUTE_PATH(path, j)) { j = tsrm_realpath_r(path, 1, j, ll, t, use_realpath, is_dir, &directory); if (j == (size_t)-1) { free_alloca(tmp, use_heap); return (size_t)-1; } } else { if (i + j >= MAXPATHLEN-1) { free_alloca(tmp, use_heap); return (size_t)-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, &directory); if (j == (size_t)-1) { free_alloca(tmp, use_heap); return (size_t)-1; } } if (link_is_dir) { *link_is_dir = directory; } } else { if (save) { directory = S_ISDIR(st.st_mode); if (link_is_dir) { *link_is_dir = directory; } if (is_dir && !directory) { /* not a directory */ free_alloca(tmp, use_heap); return (size_t)-1; } } #endif if (i <= start + 1) { 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, NULL); if (j > start && j != (size_t)-1) { path[j++] = DEFAULT_SLASH; } } #ifdef ZEND_WIN32 if (j == (size_t)-1 || j + len >= MAXPATHLEN - 1 + i) { free_alloca(tmp, use_heap); FREE_PATHW() return (size_t)-1; } if (save) { size_t sz; char *tmp_path = php_win32_ioutil_conv_w_to_any(dataw.cFileName, PHP_WIN32_CP_IGNORE_LEN, &sz); if (!tmp_path) { free_alloca(tmp, use_heap); FREE_PATHW() return (size_t)-1; } i = sz; memcpy(path+j, tmp_path, i+1); free(tmp_path); j += i; } 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 == (size_t)-1 || j + len >= MAXPATHLEN - 1 + i) { free_alloca(tmp, use_heap); return (size_t)-1; } memcpy(path+j, tmp+i, len-i+1); j += (len-i); } #endif if (save && start && CWDG(realpath_cache_size_limit)) { /* save absolute path in the cache */ realpath_cache_add(tmp, len, path, j, directory, *t); } free_alloca(tmp, use_heap); #ifdef ZEND_WIN32 FREE_PATHW() #undef FREE_PATHW #endif return j; } } /* }}} */ /* Resolve path relatively to state and put the real path into state */ /* returns 0 for ok, 1 for error, -1 if (path_length >= MAXPATHLEN-1) */ 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] = {0}; size_t start = 1; int ll = 0; time_t t; int ret; bool add_slash; void *tmp; if (!path_length || path_length >= MAXPATHLEN-1) { #ifdef ZEND_WIN32 SET_ERRNO_FROM_WIN32_CODE(ERROR_INVALID_PARAMETER); #else errno = EINVAL; #endif 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) { /* resolve relative path */ start = 0; memcpy(resolved_path , path, path_length + 1); } else { size_t state_cwd_length = state->cwd_length; #ifdef ZEND_WIN32 if (IS_SLASH(path[0])) { if (state->cwd[1] == ':') { /* Copy only the drive name */ state_cwd_length = 2; } else if (IS_UNC_PATH(state->cwd, state->cwd_length)) { /* Copy only the share name */ state_cwd_length = 2; while (IS_SLASH(state->cwd[state_cwd_length])) { state_cwd_length++; } while (state->cwd[state_cwd_length] && !IS_SLASH(state->cwd[state_cwd_length])) { state_cwd_length++; } while (IS_SLASH(state->cwd[state_cwd_length])) { state_cwd_length++; } while (state->cwd[state_cwd_length] && !IS_SLASH(state->cwd[state_cwd_length])) { state_cwd_length++; } } } #endif if (path_length + state_cwd_length + 1 >= MAXPATHLEN-1) { return 1; } memcpy(resolved_path, state->cwd, state_cwd_length); if (resolved_path[state_cwd_length-1] == DEFAULT_SLASH) { memcpy(resolved_path + state_cwd_length, path, path_length + 1); path_length += state_cwd_length; } else { 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 { #ifdef ZEND_WIN32 if (path_length > 2 && path[1] == ':' && !IS_SLASH(path[2])) { resolved_path[0] = path[0]; resolved_path[1] = ':'; resolved_path[2] = DEFAULT_SLASH; memcpy(resolved_path + 3, path + 2, path_length - 1); path_length++; } else #endif memcpy(resolved_path, path, path_length + 1); } #ifdef ZEND_WIN32 if (memchr(resolved_path, '*', path_length) || memchr(resolved_path, '?', path_length)) { return 1; } #endif #ifdef ZEND_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; } 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; } #endif 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, NULL); if (path_length == (size_t)-1) { errno = ENOENT; 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; #ifdef ZEND_WIN32 verify: #endif if (verify_path) { cwd_state old_state; CWD_STATE_COPY(&old_state, state); state->cwd_length = path_length; tmp = erealloc(state->cwd, state->cwd_length+1); state->cwd = (char *) tmp; 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 { state->cwd_length = path_length; tmp = erealloc(state->cwd, state->cwd_length+1); state->cwd = (char *) tmp; memcpy(state->cwd, resolved_path, state->cwd_length+1); ret = 0; } #if VIRTUAL_CWD_DEBUG fprintf (stderr, "virtual_file_ex() = %s\n",state->cwd); #endif return (ret); } /* }}} */ CWD_API zend_result virtual_chdir(const char *path) /* {{{ */ { return virtual_file_ex(&CWDG(cwd), path, php_is_dir_ok, CWD_REALPATH) ? FAILURE : SUCCESS; } /* }}} */ /* returns 0 for ok, 1 for empty string, -1 on error */ CWD_API int virtual_chdir_file(const char *path, int (*p_chdir)(const char *path)) /* {{{ */ { size_t length = strlen(path); char *temp; int retval; ALLOCA_FLAG(use_heap) if (length == 0) { return 1; /* Can't cd to empty string */ } while(--length < SIZE_MAX && !IS_SLASH(path[length])) { } if (length == SIZE_MAX) { /* No directory only file name */ errno = ENOENT; return -1; } if (length == COPY_WHEN_ABSOLUTE(path) && IS_ABSOLUTE_PATH(path, length+1)) { /* Also use trailing slash if this is absolute */ length++; } temp = (char *) do_alloca(length+1, use_heap); memcpy(temp, path, length); temp[length] = 0; #if VIRTUAL_CWD_DEBUG fprintf (stderr, "Changing directory to %s\n", temp); #endif retval = p_chdir(temp); free_alloca(temp, use_heap); return retval; } /* }}} */ CWD_API char *virtual_realpath(const char *path, char *real_path) /* {{{ */ { cwd_state new_state; char *retval; char cwd[MAXPATHLEN]; /* realpath("") returns CWD */ if (!*path) { new_state.cwd = (char*)emalloc(1); new_state.cwd[0] = '\0'; new_state.cwd_length = 0; if (VCWD_GETCWD(cwd, MAXPATHLEN)) { path = cwd; } } else if (!IS_ABSOLUTE_PATH(path, strlen(path))) { CWD_STATE_COPY(&new_state, &CWDG(cwd)); } else { new_state.cwd = (char*)emalloc(1); new_state.cwd[0] = '\0'; new_state.cwd_length = 0; } if (virtual_file_ex(&new_state, path, NULL, CWD_REALPATH)==0) { size_t len = new_state.cwd_length>MAXPATHLEN-1?MAXPATHLEN-1:new_state.cwd_length; memcpy(real_path, new_state.cwd, len); real_path[len] = '\0'; retval = real_path; } else { retval = NULL; } CWD_STATE_FREE(&new_state); return retval; } /* }}} */ /* returns 0 for ok, 1 for error, -1 if (path_length >= MAXPATHLEN-1) */ CWD_API int virtual_filepath_ex(const char *path, char **filepath, verify_path_func verify_path) /* {{{ */ { cwd_state new_state; int retval; CWD_STATE_COPY(&new_state, &CWDG(cwd)); retval = virtual_file_ex(&new_state, path, verify_path, CWD_FILEPATH); *filepath = new_state.cwd; return retval; } /* }}} */ /* returns 0 for ok, 1 for error, -1 if (path_length >= MAXPATHLEN-1) */ CWD_API int virtual_filepath(const char *path, char **filepath) /* {{{ */ { return virtual_filepath_ex(path, filepath, php_is_file_ok); } /* }}} */ CWD_API FILE *virtual_fopen(const char *path, const char *mode) /* {{{ */ { cwd_state new_state; FILE *f; if (path[0] == '\0') { /* Fail to open empty path */ return NULL; } CWD_STATE_COPY(&new_state, &CWDG(cwd)); if (virtual_file_ex(&new_state, path, NULL, CWD_EXPAND)) { CWD_STATE_FREE_ERR(&new_state); return NULL; } #ifdef ZEND_WIN32 f = php_win32_ioutil_fopen(new_state.cwd, mode); #else f = fopen(new_state.cwd, mode); #endif CWD_STATE_FREE_ERR(&new_state); return f; } /* }}} */ CWD_API int virtual_access(const char *pathname, int mode) /* {{{ */ { cwd_state new_state; int ret; CWD_STATE_COPY(&new_state, &CWDG(cwd)); if (virtual_file_ex(&new_state, pathname, NULL, CWD_REALPATH)) { CWD_STATE_FREE_ERR(&new_state); return -1; } #if defined(ZEND_WIN32) ret = tsrm_win32_access(new_state.cwd, mode); #else ret = access(new_state.cwd, mode); #endif CWD_STATE_FREE_ERR(&new_state); return ret; } /* }}} */ #if HAVE_UTIME CWD_API int virtual_utime(const char *filename, struct utimbuf *buf) /* {{{ */ { cwd_state new_state; int ret; CWD_STATE_COPY(&new_state, &CWDG(cwd)); if (virtual_file_ex(&new_state, filename, NULL, CWD_REALPATH)) { CWD_STATE_FREE_ERR(&new_state); return -1; } #ifdef ZEND_WIN32 ret = win32_utime(new_state.cwd, buf); #else ret = utime(new_state.cwd, buf); #endif CWD_STATE_FREE_ERR(&new_state); return ret; } /* }}} */ #endif CWD_API int virtual_chmod(const char *filename, mode_t mode) /* {{{ */ { cwd_state new_state; int ret; CWD_STATE_COPY(&new_state, &CWDG(cwd)); if (virtual_file_ex(&new_state, filename, NULL, CWD_REALPATH)) { CWD_STATE_FREE_ERR(&new_state); return -1; } #ifdef ZEND_WIN32 { mode_t _tmp = mode; mode = 0; if (_tmp & _S_IREAD) { mode |= _S_IREAD; } if (_tmp & _S_IWRITE) { mode |= _S_IWRITE; } ret = php_win32_ioutil_chmod(new_state.cwd, mode); } #else ret = chmod(new_state.cwd, mode); #endif CWD_STATE_FREE_ERR(&new_state); return ret; } /* }}} */ #if !defined(ZEND_WIN32) CWD_API int virtual_chown(const char *filename, uid_t owner, gid_t group, int link) /* {{{ */ { cwd_state new_state; int ret; CWD_STATE_COPY(&new_state, &CWDG(cwd)); if (virtual_file_ex(&new_state, filename, NULL, CWD_REALPATH)) { CWD_STATE_FREE_ERR(&new_state); return -1; } if (link) { #if HAVE_LCHOWN ret = lchown(new_state.cwd, owner, group); #else ret = -1; #endif } else { ret = chown(new_state.cwd, owner, group); } CWD_STATE_FREE_ERR(&new_state); return ret; } /* }}} */ #endif CWD_API int virtual_open(const char *path, int flags, ...) /* {{{ */ { cwd_state new_state; int f; CWD_STATE_COPY(&new_state, &CWDG(cwd)); if (virtual_file_ex(&new_state, path, NULL, CWD_FILEPATH)) { CWD_STATE_FREE_ERR(&new_state); return -1; } if (flags & O_CREAT) { mode_t mode; va_list arg; va_start(arg, flags); mode = (mode_t) va_arg(arg, int); va_end(arg); #ifdef ZEND_WIN32 f = php_win32_ioutil_open(new_state.cwd, flags, mode); #else f = open(new_state.cwd, flags, mode); #endif } else { #ifdef ZEND_WIN32 f = php_win32_ioutil_open(new_state.cwd, flags); #else f = open(new_state.cwd, flags); #endif } CWD_STATE_FREE_ERR(&new_state); return f; } /* }}} */ CWD_API int virtual_creat(const char *path, mode_t mode) /* {{{ */ { cwd_state new_state; int f; CWD_STATE_COPY(&new_state, &CWDG(cwd)); if (virtual_file_ex(&new_state, path, NULL, CWD_FILEPATH)) { CWD_STATE_FREE_ERR(&new_state); return -1; } f = creat(new_state.cwd, mode); CWD_STATE_FREE_ERR(&new_state); return f; } /* }}} */ CWD_API int virtual_rename(const char *oldname, const char *newname) /* {{{ */ { cwd_state old_state; cwd_state new_state; int retval; CWD_STATE_COPY(&old_state, &CWDG(cwd)); if (virtual_file_ex(&old_state, oldname, NULL, CWD_EXPAND)) { CWD_STATE_FREE_ERR(&old_state); return -1; } oldname = old_state.cwd; CWD_STATE_COPY(&new_state, &CWDG(cwd)); if (virtual_file_ex(&new_state, newname, NULL, CWD_EXPAND)) { CWD_STATE_FREE_ERR(&old_state); CWD_STATE_FREE_ERR(&new_state); return -1; } newname = new_state.cwd; /* rename on windows will fail if newname already exists. MoveFileEx has to be used */ #ifdef ZEND_WIN32 /* MoveFileEx returns 0 on failure, other way 'round for this function */ retval = php_win32_ioutil_rename(oldname, newname); #else retval = rename(oldname, newname); #endif CWD_STATE_FREE_ERR(&old_state); CWD_STATE_FREE_ERR(&new_state); return retval; } /* }}} */ CWD_API int virtual_stat(const char *path, zend_stat_t *buf) /* {{{ */ { cwd_state new_state; int retval; CWD_STATE_COPY(&new_state, &CWDG(cwd)); if (virtual_file_ex(&new_state, path, NULL, CWD_REALPATH)) { CWD_STATE_FREE_ERR(&new_state); return -1; } retval = php_sys_stat(new_state.cwd, buf); CWD_STATE_FREE_ERR(&new_state); return retval; } /* }}} */ CWD_API int virtual_lstat(const char *path, zend_stat_t *buf) /* {{{ */ { cwd_state new_state; int retval; CWD_STATE_COPY(&new_state, &CWDG(cwd)); if (virtual_file_ex(&new_state, path, NULL, CWD_EXPAND)) { CWD_STATE_FREE_ERR(&new_state); return -1; } retval = php_sys_lstat(new_state.cwd, buf); CWD_STATE_FREE_ERR(&new_state); return retval; } /* }}} */ CWD_API int virtual_unlink(const char *path) /* {{{ */ { cwd_state new_state; int retval; CWD_STATE_COPY(&new_state, &CWDG(cwd)); if (virtual_file_ex(&new_state, path, NULL, CWD_EXPAND)) { CWD_STATE_FREE_ERR(&new_state); return -1; } #ifdef ZEND_WIN32 retval = php_win32_ioutil_unlink(new_state.cwd); #else retval = unlink(new_state.cwd); #endif CWD_STATE_FREE_ERR(&new_state); return retval; } /* }}} */ CWD_API int virtual_mkdir(const char *pathname, mode_t mode) /* {{{ */ { cwd_state new_state; int retval; CWD_STATE_COPY(&new_state, &CWDG(cwd)); if (virtual_file_ex(&new_state, pathname, NULL, CWD_FILEPATH)) { CWD_STATE_FREE_ERR(&new_state); return -1; } #ifdef ZEND_WIN32 retval = php_win32_ioutil_mkdir(new_state.cwd, mode); #else retval = mkdir(new_state.cwd, mode); #endif CWD_STATE_FREE_ERR(&new_state); return retval; } /* }}} */ CWD_API int virtual_rmdir(const char *pathname) /* {{{ */ { cwd_state new_state; int retval; CWD_STATE_COPY(&new_state, &CWDG(cwd)); if (virtual_file_ex(&new_state, pathname, NULL, CWD_EXPAND)) { CWD_STATE_FREE_ERR(&new_state); return -1; } #ifdef ZEND_WIN32 retval = php_win32_ioutil_rmdir(new_state.cwd); #else retval = rmdir(new_state.cwd); #endif CWD_STATE_FREE_ERR(&new_state); return retval; } /* }}} */ #ifdef ZEND_WIN32 DIR *opendir(const char *name); #endif CWD_API DIR *virtual_opendir(const char *pathname) /* {{{ */ { cwd_state new_state; DIR *retval; CWD_STATE_COPY(&new_state, &CWDG(cwd)); if (virtual_file_ex(&new_state, pathname, NULL, CWD_REALPATH)) { CWD_STATE_FREE_ERR(&new_state); return NULL; } retval = opendir(new_state.cwd); CWD_STATE_FREE_ERR(&new_state); return retval; } /* }}} */ #ifdef ZEND_WIN32 CWD_API FILE *virtual_popen(const char *command, const char *type) /* {{{ */ { return popen_ex(command, type, CWDG(cwd).cwd, NULL); } /* }}} */ #else /* Unix */ CWD_API FILE *virtual_popen(const char *command, const char *type) /* {{{ */ { size_t command_length; int dir_length, extra = 0; char *command_line; char *ptr, *dir; FILE *retval; command_length = strlen(command); dir_length = CWDG(cwd).cwd_length; dir = CWDG(cwd).cwd; while (dir_length > 0) { if (*dir == '\'') extra+=3; dir++; dir_length--; } dir_length = CWDG(cwd).cwd_length; dir = CWDG(cwd).cwd; ptr = command_line = (char *) emalloc(command_length + sizeof("cd '' ; ") + dir_length + extra+1+1); memcpy(ptr, "cd ", sizeof("cd ")-1); ptr += sizeof("cd ")-1; if (CWDG(cwd).cwd_length == 0) { *ptr++ = DEFAULT_SLASH; } else { *ptr++ = '\''; while (dir_length > 0) { switch (*dir) { case '\'': *ptr++ = '\''; *ptr++ = '\\'; *ptr++ = '\''; /* fall-through */ default: *ptr++ = *dir; } dir++; dir_length--; } *ptr++ = '\''; } *ptr++ = ' '; *ptr++ = ';'; *ptr++ = ' '; memcpy(ptr, command, command_length+1); retval = popen(command_line, type); efree(command_line); return retval; } /* }}} */ #endif CWD_API char *tsrm_realpath(const char *path, char *real_path) /* {{{ */ { cwd_state new_state; char cwd[MAXPATHLEN]; /* realpath("") returns CWD */ if (!*path) { new_state.cwd = (char*)emalloc(1); new_state.cwd[0] = '\0'; new_state.cwd_length = 0; if (VCWD_GETCWD(cwd, MAXPATHLEN)) { path = cwd; } } else if (!IS_ABSOLUTE_PATH(path, strlen(path)) && VCWD_GETCWD(cwd, MAXPATHLEN)) { new_state.cwd = estrdup(cwd); new_state.cwd_length = strlen(cwd); } else { new_state.cwd = (char*)emalloc(1); new_state.cwd[0] = '\0'; new_state.cwd_length = 0; } if (virtual_file_ex(&new_state, path, NULL, CWD_REALPATH)) { efree(new_state.cwd); return NULL; } if (real_path) { size_t copy_len = new_state.cwd_length>MAXPATHLEN-1 ? MAXPATHLEN-1 : new_state.cwd_length; memcpy(real_path, new_state.cwd, copy_len); real_path[copy_len] = '\0'; efree(new_state.cwd); return real_path; } else { return new_state.cwd; } } /* }}} */