Refactor php_sys_readlink

Also move the implementation into win32 where it belongs
This commit is contained in:
Anatol Belski 2018-10-03 18:56:08 +02:00
parent 97481fccb9
commit 91c905e83c
5 changed files with 170 additions and 73 deletions

View File

@ -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;

View File

@ -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

View File

@ -16,6 +16,7 @@
+----------------------------------------------------------------------+
*/
#include "php.h"
#include "ipc.h"
#include <windows.h>

View File

@ -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'\\')) {
/* \??\<drive>:\ */
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\<server>\<share>\ - make sure the final path looks like
* \\<server>\<share>\ */
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 \??\<drive>:\ as symlink. Junctions
* can also be used as mount points, like \??\Volume{<guid>}, 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

View File

@ -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