ioutil.c   [plain text]


/*
   +----------------------------------------------------------------------+
   | PHP Version 7                                                        |
   +----------------------------------------------------------------------+
   | Copyright (c) 1997-2017 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.               |
   +----------------------------------------------------------------------+
   | Author: Anatol Belski <ab@php.net>                                   |
   +----------------------------------------------------------------------+
*/

/* This file integrates several modified parts from the libuv project, which
 * is copyrighted to
 *
 * Copyright Joyent, Inc. and other Node contributors. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 */ 

#include <assert.h>
#include <stdlib.h>
#include <direct.h>
#include <errno.h>
#include <fcntl.h>
#include <io.h>
#include <limits.h>
#include <sys/stat.h>
#include <sys/utime.h>
#include <stdio.h>

#include "php.h"
#include "SAPI.h"
#include "win32/winutil.h"
#include "win32/time.h"
#include "win32/ioutil.h"
#include "win32/codepage.h"

#include <pathcch.h>

/*
#undef NONLS
#undef _WINNLS_
#include <winnls.h>
*/

typedef HRESULT (__stdcall *MyPathCchCanonicalizeEx)(wchar_t *pszPathOut, size_t cchPathOut, const wchar_t *pszPathIn, unsigned long dwFlags);

static MyPathCchCanonicalizeEx canonicalize_path_w = NULL;

PW32IO BOOL php_win32_ioutil_posix_to_open_opts(int flags, mode_t mode, php_ioutil_open_opts *opts)
{/*{{{*/
	int current_umask;

	opts->attributes = 0;

	/* Obtain the active umask. umask() never fails and returns the previous */
	/* umask. */
	current_umask = umask(0);
	umask(current_umask);

	/* convert flags and mode to CreateFile parameters */
	switch (flags & (_O_RDONLY | _O_WRONLY | _O_RDWR)) {
		case _O_RDONLY:
			opts->access = FILE_GENERIC_READ;
			/* XXX not opening dirs yet, see also at the bottom of this function. Should be evaluated properly. */
			/*opts->attributes |= FILE_FLAG_BACKUP_SEMANTICS;*/
			break;
		case _O_WRONLY:
			opts->access = FILE_GENERIC_WRITE;
			break;
		case _O_RDWR:
			opts->access = FILE_GENERIC_READ | FILE_GENERIC_WRITE;
			break;
		default:
			goto einval;
	}

	if (flags & _O_APPEND) {
		/* XXX this might look wrong, but i just leave it here. Disabling FILE_WRITE_DATA prevents the current truncate behaviors for files opened with "a". */
		/* access &= ~FILE_WRITE_DATA;*/
		opts->access |= FILE_APPEND_DATA;
		opts->attributes &= ~FILE_FLAG_BACKUP_SEMANTICS;
	}

	/*
	* Here is where we deviate significantly from what CRT's _open()
	* does. We indiscriminately use all the sharing modes, to match
	* UNIX semantics. In particular, this ensures that the file can
	* be deleted even whilst it's open.
	*/
	/* opts->share = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE; */
	/* XXX No UINX behavior  Good to know it's doable. 
	   Not being done as this means a behavior change. Should be evaluated properly. */
	opts->share = FILE_SHARE_READ | FILE_SHARE_WRITE;

	switch (flags & (_O_CREAT | _O_EXCL | _O_TRUNC)) {
		case 0:
		case _O_EXCL:
			opts->disposition = OPEN_EXISTING;
			break;
		case _O_CREAT:
			opts->disposition = OPEN_ALWAYS;
			break;
		case _O_CREAT | _O_EXCL:
		case _O_CREAT | _O_TRUNC | _O_EXCL:
			opts->disposition = CREATE_NEW;
			break;
		case _O_TRUNC:
		case _O_TRUNC | _O_EXCL:
			opts->disposition = TRUNCATE_EXISTING;
			break;
		case _O_CREAT | _O_TRUNC:
			opts->disposition = CREATE_ALWAYS;
			break;
		default:
			goto einval;
	}

	opts->attributes |= FILE_ATTRIBUTE_NORMAL;
	if (flags & _O_CREAT) {
		if (!((mode & ~current_umask) & _S_IWRITE)) {
			opts->attributes |= FILE_ATTRIBUTE_READONLY;
		}
	}

	if (flags & _O_TEMPORARY ) {
		opts->attributes |= FILE_FLAG_DELETE_ON_CLOSE | FILE_ATTRIBUTE_TEMPORARY;
		opts->access |= DELETE;
	}

	if (flags & _O_SHORT_LIVED) {
		opts->attributes |= FILE_ATTRIBUTE_TEMPORARY;
	}

	switch (flags & (_O_SEQUENTIAL | _O_RANDOM)) {
		case 0:
			break;
		case _O_SEQUENTIAL:
			opts->attributes |= FILE_FLAG_SEQUENTIAL_SCAN;
			break;
		case _O_RANDOM:
			opts->attributes |= FILE_FLAG_RANDOM_ACCESS;
			break;
		default:
			goto einval;
	}

	/* Very compat options */
	/*if (flags & O_ASYNC) {
		opts->attributes |= FILE_FLAG_OVERLAPPED;
	} else if (flags & O_SYNC) {
		opts->attributes &= ~FILE_FLAG_OVERLAPPED;
	}*/

	/* Setting this flag makes it possible to open a directory. */
	/* XXX not being done as this means a behavior change. Should be evaluated properly. */
	/* opts->attributes |= FILE_FLAG_BACKUP_SEMANTICS; */

	return 1;

einval:
	_set_errno(EINVAL);
	return 0;
}/*}}}*/

PW32IO int php_win32_ioutil_open_w(const wchar_t *path, int flags, ...)
{/*{{{*/
	php_ioutil_open_opts open_opts;
	HANDLE file;
	int fd;
	mode_t mode = 0;

	PHP_WIN32_IOUTIL_CHECK_PATH_W(path, -1, 0)

	if (flags & O_CREAT) {
		va_list arg;

		va_start(arg, flags);
		mode = (mode_t) va_arg(arg, int);
		va_end(arg);
	}

	if (!php_win32_ioutil_posix_to_open_opts(flags, mode, &open_opts)) {
		goto einval;
	}

	/* XXX care about security attributes here if needed, see tsrm_win32_access() */
	file = CreateFileW(path,
		open_opts.access,
		open_opts.share,
		NULL,
		open_opts.disposition,
		open_opts.attributes,
		NULL);

	if (file == INVALID_HANDLE_VALUE) {
		DWORD error = GetLastError();

		if (error == ERROR_FILE_EXISTS && (flags & _O_CREAT) &&
			!(flags & _O_EXCL)) {
			/* Special case: when ERROR_FILE_EXISTS happens and O_CREAT was */
			/* specified, it means the path referred to a directory. */
			_set_errno(EISDIR);
		} else {
			SET_ERRNO_FROM_WIN32_CODE(error);
		}
		return -1;
	}

	fd = _open_osfhandle((intptr_t) file, flags);
	if (fd < 0) {
		DWORD error = GetLastError();

		/* The only known failure mode for _open_osfhandle() is EMFILE, in which
		 * case GetLastError() will return zero. However we'll try to handle other
		 * errors as well, should they ever occur.
		 */
		if (errno == EMFILE) {
			_set_errno(EMFILE);
		} else if (error != ERROR_SUCCESS) {
			SET_ERRNO_FROM_WIN32_CODE(error);
		}
		CloseHandle(file);
		return -1;
	}

	if (flags & _O_TEXT) {
		_setmode(fd, _O_TEXT);
	} else if (flags & _O_BINARY) {
		_setmode(fd, _O_BINARY);
	}

	return fd;

	einval:
		_set_errno(EINVAL);
		return -1;
}/*}}}*/

PW32IO int php_win32_ioutil_close(int fd)
{/*{{{*/
	int result = -1;

	if (-1 == fd) {
		_set_errno(EBADF);
		return result;
	}

	if (fd > 2) {
		result = _close(fd);
	} else {
		result = 0;
	}

	/* _close doesn't set _doserrno on failure, but it does always set errno
	* to EBADF on failure.
	*/
	if (result == -1) {
		_set_errno(EBADF);
	}

	return result;
}/*}}}*/

#if 0
PW32IO int php_win32_ioutil_mkdir_w(const wchar_t *path, mode_t mode)
{/*{{{*/
	int ret = 0;
	DWORD err = 0;

	PHP_WIN32_IOUTIL_CHECK_PATH_W(path, -1, 0)

	/* TODO extend with mode usage */
	if (!CreateDirectoryW(path, NULL)) {
		err = GetLastError();
		ret = -1;
		SET_ERRNO_FROM_WIN32_CODE(err);
	}

	return ret;
}/*}}}*/
#endif

PW32IO int php_win32_ioutil_mkdir(const char *path, mode_t mode)
{/*{{{*/
	wchar_t *pathw = php_win32_ioutil_any_to_w(path);
	int ret = 0;
	DWORD err = 0;

	/* TODO extend with mode usage */
	if (!pathw) {
		SET_ERRNO_FROM_WIN32_CODE(ERROR_INVALID_PARAMETER);
		return -1;
	}

	PHP_WIN32_IOUTIL_CHECK_PATH_W(pathw, -1, 1)

	if (!CreateDirectoryW(pathw, NULL)) {
		err = GetLastError();
		ret = -1;
	}
	free(pathw);

	if (0 > ret) {
		SET_ERRNO_FROM_WIN32_CODE(err);
	}

	return ret;
}/*}}}*/

PW32IO int php_win32_ioutil_unlink_w(const wchar_t *path)
{/*{{{*/
	int ret = 0;
	DWORD err = 0;

	PHP_WIN32_IOUTIL_CHECK_PATH_W(path, -1, 0)

	if (!DeleteFileW(path)) {
		err = GetLastError();
		ret = -1;
		SET_ERRNO_FROM_WIN32_CODE(err);
	}

	return ret;
}/*}}}*/

PW32IO int php_win32_ioutil_rmdir_w(const wchar_t *path)
{/*{{{*/
	int ret = 0;
	DWORD err = 0;

	PHP_WIN32_IOUTIL_CHECK_PATH_W(path, -1, 0)

	if (!RemoveDirectoryW(path)) {
		err = GetLastError();
		ret = -1;
		SET_ERRNO_FROM_WIN32_CODE(err);
	}

	return ret;
}/*}}}*/

PW32IO int php_win32_ioutil_chdir_w(const wchar_t *path)
{/*{{{*/
	int ret = 0;
	DWORD err = 0;
	
	if (!SetCurrentDirectoryW(path)) {
		err = GetLastError();
		ret = -1;
		SET_ERRNO_FROM_WIN32_CODE(err);
	}

	return ret;
}/*}}}*/

PW32IO int php_win32_ioutil_rename_w(const wchar_t *oldname, const wchar_t *newname)
{/*{{{*/
	int ret = 0;
	DWORD err = 0;
	
	PHP_WIN32_IOUTIL_CHECK_PATH_W(oldname, -1, 0)
	PHP_WIN32_IOUTIL_CHECK_PATH_W(newname, -1, 0)


	if (!MoveFileExW(oldname, newname, MOVEFILE_REPLACE_EXISTING|MOVEFILE_COPY_ALLOWED)) {
		err = GetLastError();
		ret = -1;
		SET_ERRNO_FROM_WIN32_CODE(err);
	}

	return ret;
}/*}}}*/

PW32IO wchar_t *php_win32_ioutil_getcwd_w(const wchar_t *buf, int len)
{/*{{{*/
	DWORD err = 0;
	wchar_t *tmp_buf = NULL;

	/* If buf was NULL, the result has to be freed outside here. */
	if (!buf) {
		DWORD tmp_len = GetCurrentDirectoryW(0, NULL) + 1;
		if (!tmp_len) {
			err = GetLastError();
			SET_ERRNO_FROM_WIN32_CODE(err);
			return NULL;
		} else if (tmp_len > len) {
			SET_ERRNO_FROM_WIN32_CODE(ERROR_INSUFFICIENT_BUFFER);
			return NULL;
		}

		len = tmp_len;

		tmp_buf = (wchar_t *)malloc((len)*sizeof(wchar_t));
		if (!tmp_buf) {
			SET_ERRNO_FROM_WIN32_CODE(ERROR_NOT_ENOUGH_MEMORY);
			return NULL;
		}
		buf = tmp_buf;
	}
	
	if (!GetCurrentDirectoryW(len, buf)) {
		err = GetLastError();
		SET_ERRNO_FROM_WIN32_CODE(err);
		free(tmp_buf);
		return NULL;
	}

	return (wchar_t *)buf;
}/*}}}*/

/* based on zend_dirname(). */
PW32IO size_t php_win32_ioutil_dirname(char *path, size_t len)
{/*{{{*/
	char *ret = NULL, *start;
	size_t ret_len, len_adjust = 0, pathw_len;
	wchar_t *endw, *pathw, *startw;

	if (len == 0) {
		return 0;
	}
	
	start = path;

	/* Don't really care about the path normalization, pure parsing here. */
	startw = pathw = php_win32_cp_conv_any_to_w(path, len, &pathw_len);
	if (!pathw) {
		return 0;
	}

	endw = pathw + pathw_len - 1;

	if ((2 <= len) && isalpha((int)((unsigned char *)path)[0]) && (':' == path[1])) {
		pathw += 2;
		path += 2;
		len_adjust += 2;
		if (2 == len) {
			free(startw);
			return len;
		}
	}

	/* Strip trailing slashes */
	while (endw >= pathw && PHP_WIN32_IOUTIL_IS_SLASHW(*endw)) {
		endw--;
	}
	if (endw < pathw) {
		free(startw);
		/* The path only contained slashes */
		path[0] = PHP_WIN32_IOUTIL_DEFAULT_SLASH;
		path[1] = '\0';
		return 1 + len_adjust;
	}

	/* Strip filename */
	while (endw >= pathw && !PHP_WIN32_IOUTIL_IS_SLASHW(*endw)) {
		endw--;
	}
	if (endw < pathw) {
		free(startw);
		path[0] = '.';
		path[1] = '\0';
		return 1 + len_adjust;
	}

	/* Strip slashes which came before the file name */
	while (endw >= pathw && PHP_WIN32_IOUTIL_IS_SLASHW(*endw)) {
		endw--;
	}
	if (endw < pathw) {
		free(startw);
		path[0] = PHP_WIN32_IOUTIL_DEFAULT_SLASH;
		path[1] = '\0';
		return 1 + len_adjust;
	}
	*(endw+1) = L'\0';

	ret_len = (endw + 1 - startw);
	if (PHP_WIN32_IOUTIL_IS_LONG_PATHW(startw, ret_len)) {
		ret = php_win32_ioutil_conv_w_to_any(startw + PHP_WIN32_IOUTIL_LONG_PATH_PREFIX_LENW, ret_len - PHP_WIN32_IOUTIL_LONG_PATH_PREFIX_LENW, &ret_len);
	} else {
		ret = php_win32_ioutil_conv_w_to_any(startw, ret_len, &ret_len);
	}
	memmove(start, ret, ret_len+1);
	assert(start[ret_len] == '\0');
	free(ret);
	free(startw);

	return ret_len;
}/*}}}*/

PW32IO php_win32_ioutil_normalization_result php_win32_ioutil_normalize_path_w(wchar_t **buf, size_t len, size_t *new_len)
{/*{{{*/
	wchar_t *pos, *idx = *buf, canonicalw[MAXPATHLEN];
	size_t ret_len = len;

	if (len >= MAXPATHLEN) {
		SET_ERRNO_FROM_WIN32_CODE(ERROR_BAD_LENGTH);
		return PHP_WIN32_IOUTIL_NORM_FAIL;
	}

	while (NULL != (pos = wcschr(idx, PHP_WIN32_IOUTIL_FW_SLASHW)) && idx - *buf <= len) {
		*pos = PHP_WIN32_IOUTIL_DEFAULT_SLASHW;
		idx = pos++;
	}

	if (S_OK != canonicalize_path_w(canonicalw, MAXPATHLEN, *buf, PATHCCH_ALLOW_LONG_PATHS)) {
		return PHP_WIN32_IOUTIL_NORM_PARTIAL;
	}
	ret_len = wcslen(canonicalw);
	if (ret_len != len) {
		if (ret_len > len) {
			wchar_t *tmp = realloc(*buf, (ret_len + 1) * sizeof(wchar_t));
			if (!tmp) {
				SET_ERRNO_FROM_WIN32_CODE(ERROR_NOT_ENOUGH_MEMORY);
				return PHP_WIN32_IOUTIL_NORM_PARTIAL;
			}
			*buf = tmp;
		}
		memmove(*buf, canonicalw, (ret_len + 1) * sizeof(wchar_t));
	}
	*new_len = ret_len;

	return PHP_WIN32_IOUTIL_NORM_OK;
}/*}}}*/

static HRESULT __stdcall MyPathCchCanonicalizeExFallback(wchar_t *pszPathOut, size_t cchPathOut, const wchar_t *pszPathIn, unsigned long dwFlags)
{/*{{{*/
	return -42;
}/*}}}*/

BOOL php_win32_ioutil_init(void)
{/*{{{*/
	HMODULE hMod = GetModuleHandle("api-ms-win-core-path-l1-1-0");

	if (hMod) {
		canonicalize_path_w = (MyPathCchCanonicalizeEx)GetProcAddress(hMod, "PathCchCanonicalizeEx");
		if (!canonicalize_path_w) {
			canonicalize_path_w = (MyPathCchCanonicalizeEx)MyPathCchCanonicalizeExFallback;
		}
	} else {
		canonicalize_path_w = (MyPathCchCanonicalizeEx)MyPathCchCanonicalizeExFallback;
	}

	return TRUE;
}/*}}}*/

/* an extended version could be implemented, for now direct functions can be used. */
#if 0
PW32IO int php_win32_ioutil_access_w(const wchar_t *path, mode_t mode)
{
	return _waccess(path, mode);
}
#endif

#if 0
PW32IO HANDLE php_win32_ioutil_findfirstfile_w(char *path, WIN32_FIND_DATA *data)
{
	HANDLE ret = INVALID_HANDLE_VALUE;
	DWORD err;

	if (!path) {
		SET_ERRNO_FROM_WIN32_CODE(ERROR_INVALID_PARAMETER);
		return ret;
	}

	pathw = php_win32_ioutil_any_to_w(path);

	if (!pathw) {
		err = GetLastError();
		SET_ERRNO_FROM_WIN32_CODE(ret);
		return ret;
	}

		ret = FindFirstFileW(pathw, data);
	
	if (INVALID_HANDLE_VALUE == ret && path) {
		ret = FindFirstFileA(path, data);
	}

	/* XXX set errno */
	return ret;
}
#endif

/*
 * Local variables:
 * tab-width: 4
 * c-basic-offset: 4
 * End:
 * vim600: sw=4 ts=4 fdm=marker
 * vim<600: sw=4 ts=4
 */