diff --git a/Zend/zend_virtual_cwd.c b/Zend/zend_virtual_cwd.c index 079fab3eb6d..16734501ecb 100644 --- a/Zend/zend_virtual_cwd.c +++ b/Zend/zend_virtual_cwd.c @@ -113,78 +113,6 @@ static cwd_state main_cwd_state; /* True global */ # define CWD_STATE_FREE_ERR(state) CWD_STATE_FREE(state) #endif -#ifdef ZEND_WIN32 -CWD_API ssize_t php_sys_readlink(const char *link, char *target, size_t target_len){ /* {{{ */ - HANDLE hFile; - wchar_t *linkw = php_win32_ioutil_any_to_w(link), targetw[MAXPATHLEN]; - size_t ret_len, targetw_len, offset = 0; - char *ret; - - if (!linkw) { - return -1; - } - - if (!target_len) { - free(linkw); - return -1; - } - - hFile = CreateFileW(linkw, // file to open - 0, // query possible attributes - PHP_WIN32_IOUTIL_DEFAULT_SHARE_MODE, - NULL, // default security - OPEN_EXISTING, // existing file only - FILE_FLAG_BACKUP_SEMANTICS, // normal file - NULL); // no attr. template - if( hFile == INVALID_HANDLE_VALUE) { - free(linkw); - return -1; - } - - /* Despite MSDN has documented it won't to, the length returned by - GetFinalPathNameByHandleA includes the length of the - null terminator. This behavior is at least reproducible - with VS2012 and earlier, and seems not to be fixed till - now. Thus, correcting target_len so it's suddenly don't - overflown. */ - targetw_len = GetFinalPathNameByHandleW(hFile, targetw, MAXPATHLEN, VOLUME_NAME_DOS); - if(targetw_len >= target_len || targetw_len >= MAXPATHLEN || targetw_len == 0) { - free(linkw); - CloseHandle(hFile); - return -1; - } - - if(targetw_len > 4) { - /* Skip first 4 characters if they are "\\?\" */ - if(targetw[0] == L'\\' && targetw[1] == L'\\' && targetw[2] == L'?' && targetw[3] == L'\\') { - offset = 4; - - /* \\?\UNC\ */ - if (targetw_len > 7 && targetw[4] == L'U' && targetw[5] == L'N' && targetw[6] == L'C') { - offset += 2; - targetw[offset] = L'\\'; - } - } - } - - ret = php_win32_ioutil_conv_w_to_any(targetw + offset, targetw_len - offset, &ret_len); - if (!ret || ret_len >= MAXPATHLEN) { - CloseHandle(hFile); - free(linkw); - free(ret); - return -1; - } - memcpy(target, ret, ret_len + 1); - - free(ret); - CloseHandle(hFile); - free(linkw); - - return (ssize_t)ret_len; -} -/* }}} */ -#endif - static int php_is_dir_ok(const cwd_state *state) /* {{{ */ { zend_stat_t buf; diff --git a/Zend/zend_virtual_cwd.h b/Zend/zend_virtual_cwd.h index 9773ef1e48b..afee6ee7d44 100644 --- a/Zend/zend_virtual_cwd.h +++ b/Zend/zend_virtual_cwd.h @@ -118,7 +118,7 @@ typedef unsigned short mode_t; # define php_sys_stat php_win32_ioutil_stat # define php_sys_lstat php_win32_ioutil_lstat # define php_sys_fstat php_win32_ioutil_fstat -CWD_API ssize_t php_sys_readlink(const char *link, char *target, size_t target_len); +# define php_sys_readlink php_win32_ioutil_readlink # define php_sys_symlink php_win32_ioutil_symlink # define php_sys_link php_win32_ioutil_link #else diff --git a/win32/ftok.c b/win32/ftok.c index 469f2df59af..77de6a0449c 100644 --- a/win32/ftok.c +++ b/win32/ftok.c @@ -16,6 +16,7 @@ +----------------------------------------------------------------------+ */ +#include "php.h" #include "ipc.h" #include diff --git a/win32/ioutil.c b/win32/ioutil.c index 99eccd511fb..0ca01261a52 100644 --- a/win32/ioutil.c +++ b/win32/ioutil.c @@ -982,6 +982,138 @@ PW32IO int php_win32_ioutil_fstat(int fd, php_win32_ioutil_stat_t *buf) return php_win32_ioutil_fstat_int((HANDLE)_get_osfhandle(fd), buf, NULL, 0, NULL); }/*}}}*/ +static ssize_t php_win32_ioutil_readlink_int(HANDLE h, wchar_t *buf, size_t buf_len) +{/*{{{*/ + char buffer[MAXIMUM_REPARSE_DATA_BUFFER_SIZE]; + PHP_WIN32_IOUTIL_REPARSE_DATA_BUFFER *reparse_data = (PHP_WIN32_IOUTIL_REPARSE_DATA_BUFFER*) buffer; + wchar_t* reparse_target; + DWORD reparse_target_len; + DWORD bytes; + + if (!DeviceIoControl(h, + FSCTL_GET_REPARSE_POINT, + NULL, + 0, + buffer, + sizeof buffer, + &bytes, + NULL)) { + SET_ERRNO_FROM_WIN32_CODE(GetLastError()); + return -1; + } + + if (reparse_data->ReparseTag == IO_REPARSE_TAG_SYMLINK) { + /* Real symlink */ + reparse_target = reparse_data->SymbolicLinkReparseBuffer.ReparseTarget + + (reparse_data->SymbolicLinkReparseBuffer.SubstituteNameOffset / + sizeof(wchar_t)); + reparse_target_len = + reparse_data->SymbolicLinkReparseBuffer.SubstituteNameLength / + sizeof(wchar_t); + + /* Real symlinks can contain pretty much everything, but the only thing we + * really care about is undoing the implicit conversion to an NT namespaced + * path that CreateSymbolicLink will perform on absolute paths. If the path + * is win32-namespaced then the user must have explicitly made it so, and + * we better just return the unmodified reparse data. */ + if (reparse_target_len >= 4 && + reparse_target[0] == L'\\' && + reparse_target[1] == L'?' && + reparse_target[2] == L'?' && + reparse_target[3] == L'\\') { + /* Starts with \??\ */ + if (reparse_target_len >= 6 && + ((reparse_target[4] >= L'A' && reparse_target[4] <= L'Z') || + (reparse_target[4] >= L'a' && reparse_target[4] <= L'z')) && + reparse_target[5] == L':' && + (reparse_target_len == 6 || reparse_target[6] == L'\\')) { + /* \??\:\ */ + reparse_target += 4; + reparse_target_len -= 4; + + } else if (reparse_target_len >= 8 && + (reparse_target[4] == L'U' || reparse_target[4] == L'u') && + (reparse_target[5] == L'N' || reparse_target[5] == L'n') && + (reparse_target[6] == L'C' || reparse_target[6] == L'c') && + reparse_target[7] == L'\\') { + /* \??\UNC\\\ - make sure the final path looks like + * \\\\ */ + reparse_target += 6; + reparse_target[0] = L'\\'; + reparse_target_len -= 6; + } + } + + } else if (reparse_data->ReparseTag == IO_REPARSE_TAG_MOUNT_POINT) { + /* Junction. */ + reparse_target = reparse_data->MountPointReparseBuffer.ReparseTarget + + (reparse_data->MountPointReparseBuffer.SubstituteNameOffset / + sizeof(wchar_t)); + reparse_target_len = reparse_data->MountPointReparseBuffer.SubstituteNameLength / sizeof(wchar_t); + + /* Only treat junctions that look like \??\:\ as symlink. Junctions + * can also be used as mount points, like \??\Volume{}, but that's + * confusing for programs since they wouldn't be able to actually + * understand such a path when returned by uv_readlink(). UNC paths are + * never valid for junctions so we don't care about them. */ + if (!(reparse_target_len >= 6 && + reparse_target[0] == L'\\' && + reparse_target[1] == L'?' && + reparse_target[2] == L'?' && + reparse_target[3] == L'\\' && + ((reparse_target[4] >= L'A' && reparse_target[4] <= L'Z') || + (reparse_target[4] >= L'a' && reparse_target[4] <= L'z')) && + reparse_target[5] == L':' && + (reparse_target_len == 6 || reparse_target[6] == L'\\'))) { + SET_ERRNO_FROM_WIN32_CODE(ERROR_SYMLINK_NOT_SUPPORTED); + return -1; + } + + /* Remove leading \??\ */ + reparse_target += 4; + reparse_target_len -= 4; + + } else { + /* Reparse tag does not indicate a symlink. */ + SET_ERRNO_FROM_WIN32_CODE(ERROR_SYMLINK_NOT_SUPPORTED); + return -1; + } + + if (reparse_target_len >= buf_len) { + SET_ERRNO_FROM_WIN32_CODE(ERROR_NOT_ENOUGH_MEMORY); + return -1; + } + + memcpy(buf, reparse_target, (reparse_target_len + 1)*sizeof(wchar_t)); + + return reparse_target_len; +}/*}}}*/ + +PW32IO ssize_t php_win32_ioutil_readlink_w(const wchar_t *path, wchar_t *buf, size_t buf_len) +{/*{{{*/ + HANDLE h; + ssize_t ret; + + h = CreateFileW(path, + 0, + 0, + NULL, + OPEN_EXISTING, + FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, + NULL); + + if (h == INVALID_HANDLE_VALUE) { + SET_ERRNO_FROM_WIN32_CODE(GetLastError()); + return -1; + } + + ret = php_win32_ioutil_readlink_int(h, buf, buf_len); + + CloseHandle(h); + + return ret; +}/*}}}*/ + /* * Local variables: * tab-width: 4 diff --git a/win32/ioutil.h b/win32/ioutil.h index 9da518dbc42..db21c449e0f 100644 --- a/win32/ioutil.h +++ b/win32/ioutil.h @@ -751,6 +751,42 @@ __forceinline static int php_win32_ioutil_stat_ex(const char *path, php_win32_io #define php_win32_ioutil_stat(path, buf) php_win32_ioutil_stat_ex(path, buf, 0) #define php_win32_ioutil_lstat(path, buf) php_win32_ioutil_stat_ex(path, buf, 1) +PW32IO ssize_t php_win32_ioutil_readlink_w(const wchar_t *path, wchar_t *buf, size_t buf_len); + +__forceinline static ssize_t php_win32_ioutil_readlink(const char *path, char *buf, size_t buf_len) +{/*{{{*/ + size_t pathw_len, ret_buf_len; + wchar_t *pathw = php_win32_ioutil_conv_any_to_w(path, PHP_WIN32_CP_IGNORE_LEN, &pathw_len); + wchar_t retw[PHP_WIN32_IOUTIL_MAXPATHLEN]; + char *ret_buf; + ssize_t ret; + + if (!pathw) { + SET_ERRNO_FROM_WIN32_CODE(ERROR_INVALID_PARAMETER); + return -1; + } + + ret = php_win32_ioutil_readlink_w(pathw, retw, sizeof(retw)-1); + if (ret < 0) { + DWORD _err = GetLastError(); + free(pathw); + SET_ERRNO_FROM_WIN32_CODE(_err); + return ret; + } + + ret_buf = php_win32_ioutil_conv_w_to_any(retw, PHP_WIN32_CP_IGNORE_LEN, &ret_buf_len); + if (!ret_buf || ret_buf_len >= buf_len || ret_buf_len >= MAXPATHLEN) { + free(pathw); + SET_ERRNO_FROM_WIN32_CODE(ERROR_BAD_PATHNAME); + return -1; + } + memcpy(buf, ret_buf, ret_buf_len + 1); + + free(pathw); + + return ret; +}/*}}}*/ + #ifdef __cplusplus } #endif