xref: /PHP-7.4/win32/ioutil.c (revision 6ec40569)
1 /*
2    +----------------------------------------------------------------------+
3    | PHP Version 7                                                        |
4    +----------------------------------------------------------------------+
5    | Copyright (c) The PHP Group                                          |
6    +----------------------------------------------------------------------+
7    | This source file is subject to version 3.01 of the PHP license,      |
8    | that is bundled with this package in the file LICENSE, and is        |
9    | available through the world-wide-web at the following url:           |
10    | http://www.php.net/license/3_01.txt                                  |
11    | If you did not receive a copy of the PHP license and are unable to   |
12    | obtain it through the world-wide-web, please send a note to          |
13    | license@php.net so we can mail you a copy immediately.               |
14    +----------------------------------------------------------------------+
15    | Author: Anatol Belski <ab@php.net>                                   |
16    +----------------------------------------------------------------------+
17 */
18 
19 /* This file integrates several modified parts from the libuv project, which
20  * is copyrighted to
21  *
22  * Copyright Joyent, Inc. and other Node contributors. All rights reserved.
23  *
24  * Permission is hereby granted, free of charge, to any person obtaining a copy
25  * of this software and associated documentation files (the "Software"), to
26  * deal in the Software without restriction, including without limitation the
27  * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
28  * sell copies of the Software, and to permit persons to whom the Software is
29  * furnished to do so, subject to the following conditions:
30  *
31  * The above copyright notice and this permission notice shall be included in
32  * all copies or substantial portions of the Software.
33  *
34  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
35  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
36  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
37  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
38  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
39  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
40  * IN THE SOFTWARE.
41  */
42 
43 #include <assert.h>
44 #include <stdlib.h>
45 #include <direct.h>
46 #include <errno.h>
47 #include <fcntl.h>
48 #include <io.h>
49 #include <limits.h>
50 #include <sys/stat.h>
51 #include <sys/utime.h>
52 #include <stdio.h>
53 
54 #include "php.h"
55 #include "SAPI.h"
56 #include "win32/winutil.h"
57 #include "win32/time.h"
58 #include "win32/ioutil.h"
59 #include "win32/codepage.h"
60 #include "main/streams/php_stream_plain_wrapper.h"
61 
62 #include <pathcch.h>
63 #include <winioctl.h>
64 #include <winnt.h>
65 
66 /*
67 #undef NONLS
68 #undef _WINNLS_
69 #include <winnls.h>
70 */
71 
72 typedef HRESULT (__stdcall *MyPathCchCanonicalizeEx)(wchar_t *pszPathOut, size_t cchPathOut, const wchar_t *pszPathIn, unsigned long dwFlags);
73 
74 static MyPathCchCanonicalizeEx canonicalize_path_w = NULL;
75 
76 PW32IO BOOL php_win32_ioutil_posix_to_open_opts(int flags, mode_t mode, php_ioutil_open_opts *opts)
77 {/*{{{*/
78 	int current_umask;
79 
80 	opts->attributes = 0;
81 
82 	/* Obtain the active umask. umask() never fails and returns the previous */
83 	/* umask. */
84 	current_umask = umask(0);
85 	umask(current_umask);
86 
87 	/* convert flags and mode to CreateFile parameters */
88 	switch (flags & (_O_RDONLY | _O_WRONLY | _O_RDWR)) {
89 		case _O_RDONLY:
90 			opts->access = FILE_GENERIC_READ;
91 			/* XXX not opening dirs yet, see also at the bottom of this function. Should be evaluated properly. */
92 			/*opts->attributes |= FILE_FLAG_BACKUP_SEMANTICS;*/
93 			break;
94 		case _O_WRONLY:
95 			opts->access = FILE_GENERIC_WRITE;
96 			break;
97 		case _O_RDWR:
98 			opts->access = FILE_GENERIC_READ | FILE_GENERIC_WRITE;
99 			break;
100 		default:
101 			goto einval;
102 	}
103 
104 	if (flags & _O_APPEND) {
105 		/* 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". */
106 		/* access &= ~FILE_WRITE_DATA;*/
107 		opts->access |= FILE_APPEND_DATA;
108 		opts->attributes &= ~FILE_FLAG_BACKUP_SEMANTICS;
109 	}
110 
111 	/*
112 	* Here is where we deviate significantly from what CRT's _open()
113 	* does. We indiscriminately use all the sharing modes, to match
114 	* UNIX semantics. In particular, this ensures that the file can
115 	* be deleted even whilst it's open.
116 	*/
117 	opts->share = PHP_WIN32_IOUTIL_DEFAULT_SHARE_MODE;
118 
119 	switch (flags & (_O_CREAT | _O_EXCL | _O_TRUNC)) {
120 		case 0:
121 		case _O_EXCL:
122 			opts->disposition = OPEN_EXISTING;
123 			break;
124 		case _O_CREAT:
125 			opts->disposition = OPEN_ALWAYS;
126 			break;
127 		case _O_CREAT | _O_EXCL:
128 		case _O_CREAT | _O_TRUNC | _O_EXCL:
129 			opts->disposition = CREATE_NEW;
130 			break;
131 		case _O_TRUNC:
132 		case _O_TRUNC | _O_EXCL:
133 			opts->disposition = TRUNCATE_EXISTING;
134 			break;
135 		case _O_CREAT | _O_TRUNC:
136 			opts->disposition = CREATE_ALWAYS;
137 			break;
138 		default:
139 			goto einval;
140 	}
141 
142 	opts->attributes |= FILE_ATTRIBUTE_NORMAL;
143 	if (flags & _O_CREAT) {
144 		if (!((mode & ~current_umask) & _S_IWRITE)) {
145 			opts->attributes |= FILE_ATTRIBUTE_READONLY;
146 		}
147 	}
148 
149 	if (flags & _O_TEMPORARY ) {
150 		opts->attributes |= FILE_FLAG_DELETE_ON_CLOSE | FILE_ATTRIBUTE_TEMPORARY;
151 		opts->access |= DELETE;
152 	}
153 
154 	if (flags & _O_SHORT_LIVED) {
155 		opts->attributes |= FILE_ATTRIBUTE_TEMPORARY;
156 	}
157 
158 	switch (flags & (_O_SEQUENTIAL | _O_RANDOM)) {
159 		case 0:
160 			break;
161 		case _O_SEQUENTIAL:
162 			opts->attributes |= FILE_FLAG_SEQUENTIAL_SCAN;
163 			break;
164 		case _O_RANDOM:
165 			opts->attributes |= FILE_FLAG_RANDOM_ACCESS;
166 			break;
167 		default:
168 			goto einval;
169 	}
170 
171 	/* Very compat options */
172 	/*if (flags & O_ASYNC) {
173 		opts->attributes |= FILE_FLAG_OVERLAPPED;
174 	} else if (flags & O_SYNC) {
175 		opts->attributes &= ~FILE_FLAG_OVERLAPPED;
176 	}*/
177 
178 	/* Setting this flag makes it possible to open a directory. */
179 	/* XXX not being done as this means a behavior change. Should be evaluated properly. */
180 	/* opts->attributes |= FILE_FLAG_BACKUP_SEMANTICS; */
181 
182 	return 1;
183 
184 einval:
185 	_set_errno(EINVAL);
186 	return 0;
187 }/*}}}*/
188 
189 PW32IO int php_win32_ioutil_open_w(const wchar_t *path, int flags, ...)
190 {/*{{{*/
191 	php_ioutil_open_opts open_opts;
192 	HANDLE file;
193 	int fd;
194 	mode_t mode = 0;
195 
196 	PHP_WIN32_IOUTIL_CHECK_PATH_W(path, -1, 0)
197 
198 	if (flags & O_CREAT) {
199 		va_list arg;
200 
201 		va_start(arg, flags);
202 		mode = (mode_t) va_arg(arg, int);
203 		va_end(arg);
204 	}
205 
206 	if (!php_win32_ioutil_posix_to_open_opts(flags, mode, &open_opts)) {
207 		goto einval;
208 	}
209 
210 	/* XXX care about security attributes here if needed, see tsrm_win32_access() */
211 	file = CreateFileW(path,
212 		open_opts.access,
213 		open_opts.share,
214 		NULL,
215 		open_opts.disposition,
216 		open_opts.attributes,
217 		NULL);
218 
219 	if (file == INVALID_HANDLE_VALUE) {
220 		DWORD error = GetLastError();
221 
222 		if (error == ERROR_FILE_EXISTS && (flags & _O_CREAT) &&
223 			!(flags & _O_EXCL)) {
224 			/* Special case: when ERROR_FILE_EXISTS happens and O_CREAT was */
225 			/* specified, it means the path referred to a directory. */
226 			_set_errno(EISDIR);
227 		} else {
228 			SET_ERRNO_FROM_WIN32_CODE(error);
229 		}
230 		return -1;
231 	}
232 
233 	fd = _open_osfhandle((intptr_t) file, flags);
234 	if (fd < 0) {
235 		DWORD error = GetLastError();
236 
237 		/* The only known failure mode for _open_osfhandle() is EMFILE, in which
238 		 * case GetLastError() will return zero. However we'll try to handle other
239 		 * errors as well, should they ever occur.
240 		 */
241 		if (errno == EMFILE) {
242 			_set_errno(EMFILE);
243 		} else if (error != ERROR_SUCCESS) {
244 			SET_ERRNO_FROM_WIN32_CODE(error);
245 		}
246 		CloseHandle(file);
247 		return -1;
248 	}
249 
250 	if (flags & _O_TEXT) {
251 		_setmode(fd, _O_TEXT);
252 	} else if (flags & _O_BINARY) {
253 		_setmode(fd, _O_BINARY);
254 	}
255 
256 	return fd;
257 
258 	einval:
259 		_set_errno(EINVAL);
260 		return -1;
261 }/*}}}*/
262 
263 PW32IO int php_win32_ioutil_close(int fd)
264 {/*{{{*/
265 	int result = -1;
266 
267 	if (-1 == fd) {
268 		_set_errno(EBADF);
269 		return result;
270 	}
271 
272 	if (fd > 2) {
273 		result = _close(fd);
274 	} else {
275 		result = 0;
276 	}
277 
278 	/* _close doesn't set _doserrno on failure, but it does always set errno
279 	* to EBADF on failure.
280 	*/
281 	if (result == -1) {
282 		_set_errno(EBADF);
283 	}
284 
285 	return result;
286 }/*}}}*/
287 
288 PW32IO int php_win32_ioutil_mkdir_w(const wchar_t *path, mode_t mode)
289 {/*{{{*/
290 	size_t path_len;
291 	const wchar_t *my_path;
292 
293 	if (!path) {
294 		SET_ERRNO_FROM_WIN32_CODE(ERROR_INVALID_PARAMETER);
295 		return -1;
296 	}
297 
298 	PHP_WIN32_IOUTIL_CHECK_PATH_W(path, -1, 0)
299 
300 	path_len = wcslen(path);
301 	if (path_len < _MAX_PATH && path_len >= _MAX_PATH - 12) {
302 		/* Special case here. From the doc:
303 
304 		 "When using an API to create a directory, the specified path cannot be
305 		 so long that you cannot append an 8.3 file name ..."
306 
307 		 Thus, if the directory name length happens to be in this range, it
308 		 already needs to be a long path. The given path is already normalized
309 		 and prepared, need only to prefix it.
310 		 */
311 		wchar_t *tmp = (wchar_t *) malloc((path_len + 1) * sizeof(wchar_t));
312 		if (!tmp) {
313 			SET_ERRNO_FROM_WIN32_CODE(ERROR_NOT_ENOUGH_MEMORY);
314 			return -1;
315 		}
316 		memmove(tmp, path, (path_len + 1) * sizeof(wchar_t));
317 
318 		if (PHP_WIN32_IOUTIL_NORM_FAIL == php_win32_ioutil_normalize_path_w(&tmp, path_len, &path_len)) {
319 			free(tmp);
320 			return -1;
321 		}
322 
323 		if (!PHP_WIN32_IOUTIL_IS_LONG_PATHW(tmp, path_len)) {
324 			wchar_t *_tmp = (wchar_t *) malloc((path_len + PHP_WIN32_IOUTIL_LONG_PATH_PREFIX_LENW + 1) * sizeof(wchar_t));
325 			wchar_t *src, *dst;
326 			if (!_tmp) {
327 				SET_ERRNO_FROM_WIN32_CODE(ERROR_NOT_ENOUGH_MEMORY);
328 				free(tmp);
329 				return -1;
330 			}
331 			memmove(_tmp, PHP_WIN32_IOUTIL_LONG_PATH_PREFIXW, PHP_WIN32_IOUTIL_LONG_PATH_PREFIX_LENW * sizeof(wchar_t));
332 			src = tmp;
333 			dst = _tmp + PHP_WIN32_IOUTIL_LONG_PATH_PREFIX_LENW;
334 			while (src < tmp + path_len) {
335 				if (*src == PHP_WIN32_IOUTIL_FW_SLASHW) {
336 					*dst++ = PHP_WIN32_IOUTIL_DEFAULT_SLASHW;
337 					src++;
338 				} else {
339 					*dst++ = *src++;
340 				}
341 			}
342 			path_len += PHP_WIN32_IOUTIL_LONG_PATH_PREFIX_LENW;
343 			_tmp[path_len] = L'\0';
344 			free(tmp);
345 			tmp = _tmp;
346 		}
347 
348 		my_path = tmp;
349 
350 	} else {
351 		my_path = path;
352 	}
353 
354 	if (!CreateDirectoryW(my_path, NULL)) {
355 		DWORD err = GetLastError();
356 		if (my_path != path) {
357 			free((void *)my_path);
358 		}
359 		SET_ERRNO_FROM_WIN32_CODE(err);
360 		return -1;
361 	}
362 
363 	if (my_path != path) {
364 		free((void *)my_path);
365 	}
366 
367 	return 0;
368 }/*}}}*/
369 
370 PW32IO int php_win32_ioutil_unlink_w(const wchar_t *path)
371 {/*{{{*/
372 	DWORD err = 0;
373 	HANDLE h;
374 	BY_HANDLE_FILE_INFORMATION info;
375 	FILE_DISPOSITION_INFO disposition;
376 	BOOL status;
377 
378 	PHP_WIN32_IOUTIL_CHECK_PATH_W(path, -1, 0)
379 
380 	h = CreateFileW(path,
381 					FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES | DELETE,
382 					PHP_WIN32_IOUTIL_DEFAULT_SHARE_MODE,
383 					NULL,
384 					OPEN_EXISTING,
385 					FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS,
386 					NULL);
387 
388 	if (INVALID_HANDLE_VALUE == h) {
389 		err = GetLastError();
390 		SET_ERRNO_FROM_WIN32_CODE(err);
391 		return -1;
392 	}
393 
394 	if (!GetFileInformationByHandle(h, &info)) {
395 		err = GetLastError();
396 		CloseHandle(h);
397 		SET_ERRNO_FROM_WIN32_CODE(err);
398 		return -1;
399 	}
400 
401 	if (info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
402 		/* TODO Handle possible reparse point. */
403 		CloseHandle(h);
404 		SET_ERRNO_FROM_WIN32_CODE(ERROR_DIRECTORY_NOT_SUPPORTED);
405 		return -1;
406 	}
407 
408 #if 0
409 	/* XXX BC breach! */
410 	if (info.dwFileAttributes & FILE_ATTRIBUTE_READONLY) {
411 		/* Remove read-only attribute */
412 		FILE_BASIC_INFO basic = { 0 };
413 
414 		basic.FileAttributes = info.dwFileAttributes & ~(FILE_ATTRIBUTE_READONLY);
415 
416 		status = SetFileInformationByHandle(h, FileBasicInfo, &basic, sizeof basic);
417 		if (!status) {
418 			err = GetLastError();
419 			SET_ERRNO_FROM_WIN32_CODE(err);
420 			CloseHandle(h);
421 			return -1;
422 		}
423 	}
424 #endif
425 
426 	/* Try to set the delete flag. */
427 	disposition.DeleteFile = TRUE;
428 	status = SetFileInformationByHandle(h, FileDispositionInfo, &disposition, sizeof disposition);
429 	if (!status) {
430 		err = GetLastError();
431 		CloseHandle(h);
432 		SET_ERRNO_FROM_WIN32_CODE(err);
433 		return -1;
434 	}
435 
436 	CloseHandle(h);
437 
438 	return 0;
439 }/*}}}*/
440 
441 PW32IO int php_win32_ioutil_rmdir_w(const wchar_t *path)
442 {/*{{{*/
443 	int ret = 0;
444 
445 	PHP_WIN32_IOUTIL_CHECK_PATH_W(path, -1, 0)
446 
447 	if (!RemoveDirectoryW(path)) {
448 		DWORD err = GetLastError();
449 		ret = -1;
450 		SET_ERRNO_FROM_WIN32_CODE(err);
451 	}
452 
453 	return ret;
454 }/*}}}*/
455 
456 PW32IO int php_win32_ioutil_chdir_w(const wchar_t *path)
457 {/*{{{*/
458 	int ret = 0;
459 
460 	if (!SetCurrentDirectoryW(path)) {
461 		DWORD err = GetLastError();
462 		ret = -1;
463 		SET_ERRNO_FROM_WIN32_CODE(err);
464 	}
465 
466 	return ret;
467 }/*}}}*/
468 
469 PW32IO int php_win32_ioutil_rename_w(const wchar_t *oldname, const wchar_t *newname)
470 {/*{{{*/
471 	int ret = 0;
472 
473 	PHP_WIN32_IOUTIL_CHECK_PATH_W(oldname, -1, 0)
474 	PHP_WIN32_IOUTIL_CHECK_PATH_W(newname, -1, 0)
475 
476 
477 	if (!MoveFileExW(oldname, newname, MOVEFILE_REPLACE_EXISTING|MOVEFILE_COPY_ALLOWED)) {
478 		DWORD err = GetLastError();
479 		ret = -1;
480 		SET_ERRNO_FROM_WIN32_CODE(err);
481 	}
482 
483 	return ret;
484 }/*}}}*/
485 
486 PW32IO wchar_t *php_win32_ioutil_getcwd_w(wchar_t *buf, size_t len)
487 {/*{{{*/
488 	DWORD err = 0;
489 	wchar_t *tmp_buf = NULL;
490 	DWORD tmp_len = (DWORD)len;
491 
492 	/* If buf was NULL, the result has to be freed outside here. */
493 	if (!buf) {
494 		tmp_len = GetCurrentDirectoryW(0, NULL) + 1;
495 		if (!tmp_len) {
496 			err = GetLastError();
497 			SET_ERRNO_FROM_WIN32_CODE(err);
498 			return NULL;
499 		} else if (tmp_len > len) {
500 			SET_ERRNO_FROM_WIN32_CODE(ERROR_INSUFFICIENT_BUFFER);
501 			return NULL;
502 		}
503 
504 		tmp_buf = (wchar_t *)malloc((tmp_len)*sizeof(wchar_t));
505 		if (!tmp_buf) {
506 			SET_ERRNO_FROM_WIN32_CODE(ERROR_NOT_ENOUGH_MEMORY);
507 			return NULL;
508 		}
509 		buf = tmp_buf;
510 	}
511 
512 	if (!GetCurrentDirectoryW(tmp_len, buf)) {
513 		err = GetLastError();
514 		SET_ERRNO_FROM_WIN32_CODE(err);
515 		free(tmp_buf);
516 		return NULL;
517 	}
518 
519 	return (wchar_t *)buf;
520 }/*}}}*/
521 
522 /* based on zend_dirname(). */
523 PW32IO size_t php_win32_ioutil_dirname(char *path, size_t len)
524 {/*{{{*/
525 	char *ret = NULL, *start;
526 	size_t ret_len, len_adjust = 0, pathw_len;
527 	wchar_t *endw, *pathw, *startw;
528 
529 	if (len == 0) {
530 		return 0;
531 	}
532 
533 	start = path;
534 
535 	/* Don't really care about the path normalization, pure parsing here. */
536 	startw = pathw = php_win32_cp_conv_any_to_w(path, len, &pathw_len);
537 	if (!pathw) {
538 		return 0;
539 	}
540 
541 	endw = pathw + pathw_len - 1;
542 
543 	if ((2 <= pathw_len) && iswalpha((wint_t)(pathw)[0]) && (L':' == pathw[1])) {
544 		pathw += 2;
545 		path += 2;
546 		len_adjust += 2;
547 		if (2 == len) {
548 			free(startw);
549 			return len;
550 		}
551 	}
552 
553 	/* Strip trailing slashes */
554 	while (endw >= pathw && PHP_WIN32_IOUTIL_IS_SLASHW(*endw)) {
555 		endw--;
556 	}
557 	if (endw < pathw) {
558 		free(startw);
559 		/* The path only contained slashes */
560 		path[0] = PHP_WIN32_IOUTIL_DEFAULT_SLASH;
561 		path[1] = '\0';
562 		return 1 + len_adjust;
563 	}
564 
565 	/* Strip filename */
566 	while (endw >= pathw && !PHP_WIN32_IOUTIL_IS_SLASHW(*endw)) {
567 		endw--;
568 	}
569 	if (endw < pathw) {
570 		free(startw);
571 		path[0] = '.';
572 		path[1] = '\0';
573 		return 1 + len_adjust;
574 	}
575 
576 	/* Strip slashes which came before the file name */
577 	while (endw >= pathw && PHP_WIN32_IOUTIL_IS_SLASHW(*endw)) {
578 		endw--;
579 	}
580 	if (endw < pathw) {
581 		free(startw);
582 		path[0] = PHP_WIN32_IOUTIL_DEFAULT_SLASH;
583 		path[1] = '\0';
584 		return 1 + len_adjust;
585 	}
586 	*(endw+1) = L'\0';
587 
588 	ret_len = (endw + 1 - startw);
589 	if (PHP_WIN32_IOUTIL_IS_LONG_PATHW(startw, ret_len)) {
590 		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);
591 	} else {
592 		ret = php_win32_ioutil_conv_w_to_any(startw, ret_len, &ret_len);
593 	}
594 	memmove(start, ret, ret_len+1);
595 	assert(start[ret_len] == '\0');
596 	free(ret);
597 	free(startw);
598 
599 	return ret_len;
600 }/*}}}*/
601 
602 /* Partial normalization can still be acceptable, explicit fail has to be caught. */
603 PW32IO php_win32_ioutil_normalization_result php_win32_ioutil_normalize_path_w(wchar_t **buf, size_t len, size_t *new_len)
604 {/*{{{*/
605 	wchar_t *idx = *buf, canonicalw[MAXPATHLEN], _tmp[MAXPATHLEN], *pos = _tmp;
606 	size_t ret_len = len;
607 
608 	if (len >= MAXPATHLEN) {
609 		SET_ERRNO_FROM_WIN32_CODE(ERROR_BAD_LENGTH);
610 		*new_len = 0;
611 		return PHP_WIN32_IOUTIL_NORM_FAIL;
612 	}
613 
614 	for (; (size_t)(idx - *buf) <= len; idx++, pos++) {
615 		*pos = *idx;
616 		if (PHP_WIN32_IOUTIL_FW_SLASHW == *pos) {
617 			*pos = PHP_WIN32_IOUTIL_DEFAULT_SLASHW;
618 		}
619 		while (PHP_WIN32_IOUTIL_IS_SLASHW(*idx) && PHP_WIN32_IOUTIL_IS_SLASHW(*(idx+1))) {
620 			idx++;
621 		}
622 	}
623 
624 	if (S_OK != canonicalize_path_w(canonicalw, MAXPATHLEN, _tmp, PATHCCH_ALLOW_LONG_PATHS)) {
625 		/* Length unchanged. */
626 		*new_len = len;
627 		return PHP_WIN32_IOUTIL_NORM_PARTIAL;
628 	}
629 	ret_len = wcslen(canonicalw);
630 	if (ret_len != len) {
631 		if (ret_len > len) {
632 			wchar_t *tmp = realloc(*buf, (ret_len + 1) * sizeof(wchar_t));
633 			if (!tmp) {
634 				SET_ERRNO_FROM_WIN32_CODE(ERROR_NOT_ENOUGH_MEMORY);
635 				/* Length unchanged. */
636 				*new_len = len;
637 				return PHP_WIN32_IOUTIL_NORM_PARTIAL;
638 			}
639 			*buf = tmp;
640 		}
641 		memmove(*buf, canonicalw, (ret_len + 1) * sizeof(wchar_t));
642 	}
643 	*new_len = ret_len;
644 
645 	return PHP_WIN32_IOUTIL_NORM_OK;
646 }/*}}}*/
647 
648 static HRESULT __stdcall MyPathCchCanonicalizeExFallback(wchar_t *pszPathOut, size_t cchPathOut, const wchar_t *pszPathIn, unsigned long dwFlags)
649 {/*{{{*/
650 	return -42;
651 }/*}}}*/
652 
653 BOOL php_win32_ioutil_init(void)
654 {/*{{{*/
655 	HMODULE hMod = GetModuleHandle("api-ms-win-core-path-l1-1-0");
656 
657 	if (hMod) {
658 		canonicalize_path_w = (MyPathCchCanonicalizeEx)GetProcAddress(hMod, "PathCchCanonicalizeEx");
659 		if (!canonicalize_path_w) {
660 			canonicalize_path_w = (MyPathCchCanonicalizeEx)MyPathCchCanonicalizeExFallback;
661 		}
662 	} else {
663 		canonicalize_path_w = (MyPathCchCanonicalizeEx)MyPathCchCanonicalizeExFallback;
664 	}
665 
666 	return TRUE;
667 }/*}}}*/
668 
669 PW32IO int php_win32_ioutil_access_w(const wchar_t *path, mode_t mode)
670 {/*{{{*/
671 	DWORD attr;
672 
673 	if ((mode & X_OK) == X_OK) {
674 		DWORD type;
675 		return GetBinaryTypeW(path, &type) ? 0 : -1;
676 	}
677 
678 	attr = GetFileAttributesW(path);
679 	if (attr == INVALID_FILE_ATTRIBUTES) {
680 		DWORD err = GetLastError();
681 		SET_ERRNO_FROM_WIN32_CODE(err);
682 		return -1;
683 	}
684 
685 	if (F_OK == mode) {
686 		return 0;
687 	}
688 
689 	if ((mode &W_OK) == W_OK && (attr & FILE_ATTRIBUTE_READONLY) == FILE_ATTRIBUTE_READONLY) {
690 		SET_ERRNO_FROM_WIN32_CODE(ERROR_ACCESS_DENIED);
691 		return -1;
692 	}
693 
694 	return 0;
695 }/*}}}*/
696 
697 PW32IO FILE *php_win32_ioutil_fopen_w(const wchar_t *path, const wchar_t *mode)
698 {/*{{{*/
699 	FILE *ret;
700 	char modea[16] = {0};
701 	int err = 0, fd, flags, i = 0;
702 
703 	PHP_WIN32_IOUTIL_CHECK_PATH_W(path, NULL, 0)
704 
705 	/* Using the converter from streams, char only. */
706 	while (i < sizeof(modea)-1 && mode[i]) {
707 		modea[i] = (char)mode[i];
708 		i++;
709 	}
710 	if (SUCCESS != php_stream_parse_fopen_modes(modea, &flags)) {
711 		SET_ERRNO_FROM_WIN32_CODE(ERROR_INVALID_PARAMETER);
712 		return NULL;
713 	}
714 
715 	fd = php_win32_ioutil_open_w(path, flags, 0666);
716 	if (0 > fd) {
717 		err = GetLastError();
718 		SET_ERRNO_FROM_WIN32_CODE(err);
719 		return NULL;
720 	}
721 
722 	ret = _wfdopen(fd, mode);
723 	if (!ret) {
724 		err = GetLastError();
725 		php_win32_ioutil_close(fd);
726 		SET_ERRNO_FROM_WIN32_CODE(err);
727 		return NULL;
728 	}
729 
730 	return ret;
731 }/*}}}*/
732 
733 static size_t php_win32_ioutil_realpath_h(HANDLE *h, wchar_t **resolved)
734 {/*{{{*/
735 	wchar_t ret[PHP_WIN32_IOUTIL_MAXPATHLEN], *ret_real;
736 	DWORD ret_len, ret_real_len;
737 
738 	ret_len = GetFinalPathNameByHandleW(h, ret, PHP_WIN32_IOUTIL_MAXPATHLEN-1, FILE_NAME_NORMALIZED | VOLUME_NAME_DOS);
739 	if (0 == ret_len) {
740 		DWORD err = GetLastError();
741 		SET_ERRNO_FROM_WIN32_CODE(err);
742 		return (size_t)-1;
743 	} else if (ret_len > PHP_WIN32_IOUTIL_MAXPATHLEN) {
744 		SET_ERRNO_FROM_WIN32_CODE(ERROR_INVALID_PARAMETER);
745 		return (size_t)-1;
746 	}
747 
748 	if (NULL == *resolved) {
749 		/* ret is expected to be either NULL or a buffer of capable size. */
750 		*resolved = (wchar_t *) malloc((ret_len + 1)*sizeof(wchar_t));
751 		if (!*resolved) {
752 			SET_ERRNO_FROM_WIN32_CODE(ERROR_NOT_ENOUGH_MEMORY);
753 			return (size_t)-1;
754 		}
755 	}
756 
757 	ret_real = ret;
758 	ret_real_len = ret_len;
759 	if (0 == wcsncmp(ret, PHP_WIN32_IOUTIL_UNC_PATH_PREFIXW, PHP_WIN32_IOUTIL_UNC_PATH_PREFIX_LENW)) {
760 		ret_real += (PHP_WIN32_IOUTIL_UNC_PATH_PREFIX_LENW - 2);
761 		ret_real[0] = L'\\';
762 		ret_real_len -= (PHP_WIN32_IOUTIL_UNC_PATH_PREFIX_LENW - 2);
763 	} else if (PHP_WIN32_IOUTIL_IS_LONG_PATHW(ret, ret_len)) {
764 		ret_real += PHP_WIN32_IOUTIL_LONG_PATH_PREFIX_LENW;
765 		ret_real_len -= PHP_WIN32_IOUTIL_LONG_PATH_PREFIX_LENW;
766 	}
767 	memmove(*resolved, ret_real, (ret_real_len+1)*sizeof(wchar_t));
768 
769 	return ret_real_len;
770 }/*}}}*/
771 
772 PW32IO wchar_t *php_win32_ioutil_realpath_w(const wchar_t *path, wchar_t *resolved)
773 {/*{{{*/
774 	return php_win32_ioutil_realpath_w_ex0(path, resolved, NULL);
775 }/*}}}*/
776 
777 PW32IO wchar_t *php_win32_ioutil_realpath_w_ex0(const wchar_t *path, wchar_t *resolved, PBY_HANDLE_FILE_INFORMATION info)
778 {/*{{{*/
779 	HANDLE h;
780 	size_t ret_len;
781 
782 	PHP_WIN32_IOUTIL_CHECK_PATH_W(path, NULL, 0)
783 
784 	h = CreateFileW(path,
785 					0,
786 					0,
787 					NULL,
788 					OPEN_EXISTING,
789 					FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS,
790 					NULL);
791 	if (INVALID_HANDLE_VALUE == h) {
792 		DWORD err = GetLastError();
793 		SET_ERRNO_FROM_WIN32_CODE(err);
794 		return NULL;
795 	}
796 
797 	ret_len = php_win32_ioutil_realpath_h(h, &resolved);
798 	if ((size_t)-1 == ret_len) {
799 		DWORD err = GetLastError();
800 		CloseHandle(h);
801 		SET_ERRNO_FROM_WIN32_CODE(err);
802 		return NULL;
803 	}
804 
805 	if (NULL != info && !GetFileInformationByHandle(h, info)) {
806 		DWORD err = GetLastError();
807 		CloseHandle(h);
808 		SET_ERRNO_FROM_WIN32_CODE(err);
809 		return NULL;
810 	}
811 
812 	CloseHandle(h);
813 
814 	return resolved;
815 }/*}}}*/
816 
817 PW32IO char *realpath(const char *path, char *resolved)
818 {/*{{{*/
819 	return php_win32_ioutil_realpath(path, resolved);
820 }/*}}}*/
821 
822 PW32IO int php_win32_ioutil_symlink_w(const wchar_t *target, const wchar_t *link)
823 {/*{{{*/
824 	DWORD attr;
825 	BOOLEAN res;
826 
827 	if ((attr = GetFileAttributesW(target)) == INVALID_FILE_ATTRIBUTES) {
828 		SET_ERRNO_FROM_WIN32_CODE(GetLastError());
829 		return -1;
830 	}
831 
832 	res = CreateSymbolicLinkW(link, target, (attr & FILE_ATTRIBUTE_DIRECTORY ? 1 : 0));
833 	if (!res) {
834 		SET_ERRNO_FROM_WIN32_CODE(GetLastError());
835 		return -1;
836 	}
837 
838 	return 0;
839 }/*}}}*/
840 
841 PW32IO int php_win32_ioutil_link_w(const wchar_t *target, const wchar_t *link)
842 {/*{{{*/
843 	BOOL res;
844 
845 	res = CreateHardLinkW(link, target, NULL);
846 	if (!res) {
847 		SET_ERRNO_FROM_WIN32_CODE(GetLastError());
848 		return -1;
849 	}
850 
851 	return 0;
852 }/*}}}*/
853 
854 #define FILETIME_TO_UINT(filetime)                                          \
855    (*((uint64_t*) &(filetime)) - 116444736000000000ULL)
856 
857 #define FILETIME_TO_TIME_T(filetime)                                        \
858    (time_t)(FILETIME_TO_UINT(filetime) / 10000000ULL)
859 
860 static int php_win32_ioutil_fstat_int(HANDLE h, php_win32_ioutil_stat_t *buf, const wchar_t *pathw, size_t pathw_len, PBY_HANDLE_FILE_INFORMATION dp)
861 {/*{{{*/
862 	BY_HANDLE_FILE_INFORMATION d;
863 	PBY_HANDLE_FILE_INFORMATION data;
864 	LARGE_INTEGER t;
865 	wchar_t mypath[MAXPATHLEN];
866 	uint8_t is_dir;
867 
868 	data = !dp ? &d : dp;
869 
870 	if(!GetFileInformationByHandle(h, data)) {
871 		if (INVALID_HANDLE_VALUE != h) {
872 			/* Perhaps it's a fileless stream like stdio, reuse the normal stat info. */
873 			struct __stat64 _buf;
874 			if (_fstat64(_open_osfhandle((intptr_t)h, 0), &_buf)) {
875 				return -1;
876 			}
877 			buf->st_dev = _buf.st_dev;
878 			buf->st_ino = _buf.st_ino;
879 			buf->st_mode = _buf.st_mode;
880 			buf->st_nlink = _buf.st_nlink;
881 			buf->st_uid = _buf.st_uid;
882 			buf->st_gid = _buf.st_gid;
883 			buf->st_rdev = _buf.st_rdev;
884 			buf->st_size = _buf.st_size;
885 			buf->st_atime = _buf.st_atime;
886 			buf->st_mtime = _buf.st_mtime;
887 			buf->st_ctime = _buf.st_ctime;
888 			return 0;
889 		} else if(h == INVALID_HANDLE_VALUE && pathw_len > 0) {
890 			/* An abnormal situation it is. For example, the user is the file
891 				owner, but the file has an empty DACL. In that case, it is
892 				possible CreateFile would fail, but the attributes still can
893 				be read. Some info is still going to be missing. */
894 			WIN32_FILE_ATTRIBUTE_DATA _data;
895 			if (!GetFileAttributesExW(pathw, GetFileExInfoStandard, &_data)) {
896 				DWORD err = GetLastError();
897 				SET_ERRNO_FROM_WIN32_CODE(err);
898 				return -1;
899 			}
900 			data->dwFileAttributes = _data.dwFileAttributes;
901 			data->ftCreationTime = _data.ftCreationTime;
902 			data->ftLastAccessTime = _data.ftLastAccessTime;
903 			data->ftLastWriteTime = _data.ftLastWriteTime;
904 			data->nFileSizeHigh = _data.nFileSizeHigh;
905 			data->nFileSizeLow = _data.nFileSizeLow;
906 			data->dwVolumeSerialNumber = 0;
907 			data->nNumberOfLinks = 1;
908 			data->nFileIndexHigh = 0;
909 			data->nFileIndexLow = 0;
910 		} else {
911 			DWORD err = GetLastError();
912 			SET_ERRNO_FROM_WIN32_CODE(err);
913 			return -1;
914 		}
915 	}
916 
917 	is_dir = (data->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == FILE_ATTRIBUTE_DIRECTORY;
918 
919 	buf->st_dev = data->dwVolumeSerialNumber;
920 
921 	buf->st_rdev = buf->st_uid = buf->st_gid = 0;
922 
923 	buf->st_ino = (((uint64_t)data->nFileIndexHigh) << 32) + data->nFileIndexLow;
924 
925 	buf->st_mode = 0;
926 
927 	if (!is_dir) {
928 		if (pathw_len >= 4 &&
929 			pathw[pathw_len-4] == L'.') {
930 			if (_wcsnicmp(pathw+pathw_len-3, L"exe", 3) == 0 ||
931 			_wcsnicmp(pathw+pathw_len-3, L"com", 3) == 0) {
932 				DWORD type;
933 				if (GetBinaryTypeW(pathw, &type)) {
934 					buf->st_mode  |= (S_IEXEC|(S_IEXEC>>3)|(S_IEXEC>>6));
935 				}
936 			/* The below is actually incorrect, but keep for BC. */
937 			} else if (_wcsnicmp(pathw+pathw_len-3, L"bat", 3) == 0 ||
938 				_wcsnicmp(pathw+pathw_len-3, L"cmd", 3) == 0) {
939 				buf->st_mode  |= (S_IEXEC|(S_IEXEC>>3)|(S_IEXEC>>6));
940 			}
941 		}
942 	}
943 
944 	if ((data->dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) == 0) {
945 		if (is_dir) {
946 			buf->st_mode |= (S_IFDIR|S_IEXEC|(S_IEXEC>>3)|(S_IEXEC>>6));
947 		} else {
948 			switch (GetFileType(h)) {
949 				case FILE_TYPE_CHAR:
950 					buf->st_mode |= S_IFCHR;
951 					break;
952 				case FILE_TYPE_PIPE:
953 					buf->st_mode |= S_IFIFO;
954 					break;
955 				default:
956 					buf->st_mode |= S_IFREG;
957 			}
958 		}
959 		buf->st_mode |= (data->dwFileAttributes & FILE_ATTRIBUTE_READONLY) ? (S_IREAD|(S_IREAD>>3)|(S_IREAD>>6)) : (S_IREAD|(S_IREAD>>3)|(S_IREAD>>6)|S_IWRITE|(S_IWRITE>>3)|(S_IWRITE>>6));
960 	}
961 
962 	buf->st_nlink = data->nNumberOfLinks;
963 	t.HighPart = data->nFileSizeHigh;
964 	t.LowPart = data->nFileSizeLow;
965 	/* It's an overflow on 32 bit, however it won't fix as long
966 	as zend_long is 32 bit. */
967 	buf->st_size = (zend_long)t.QuadPart;
968 	buf->st_atime = FILETIME_TO_TIME_T(data->ftLastAccessTime);
969 	buf->st_ctime = FILETIME_TO_TIME_T(data->ftCreationTime);
970 	buf->st_mtime = FILETIME_TO_TIME_T(data->ftLastWriteTime);
971 
972 	return 0;
973 }/*}}}*/
974 
975 PW32IO int php_win32_ioutil_stat_ex_w(const wchar_t *path, size_t path_len, php_win32_ioutil_stat_t *buf, int lstat)
976 {/*{{{*/
977 	BY_HANDLE_FILE_INFORMATION data;
978 	HANDLE hLink = NULL;
979 	DWORD flags_and_attrs = lstat ? FILE_FLAG_BACKUP_SEMANTICS|FILE_FLAG_OPEN_REPARSE_POINT : FILE_FLAG_BACKUP_SEMANTICS;
980 	int ret;
981 	ALLOCA_FLAG(use_heap_large)
982 
983 	hLink = CreateFileW(path,
984 			FILE_READ_ATTRIBUTES,
985 			PHP_WIN32_IOUTIL_DEFAULT_SHARE_MODE,
986 			NULL,
987 			OPEN_EXISTING,
988 			flags_and_attrs,
989 			NULL
990 	);
991 
992 	ret = php_win32_ioutil_fstat_int(hLink, buf, path, path_len, &data);
993 
994 	if (lstat && data.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) {
995 		/* File is a reparse point. Get the target */
996 		PHP_WIN32_IOUTIL_REPARSE_DATA_BUFFER * pbuffer;
997 		DWORD retlength = 0;
998 
999 		pbuffer = (PHP_WIN32_IOUTIL_REPARSE_DATA_BUFFER *)do_alloca(MAXIMUM_REPARSE_DATA_BUFFER_SIZE, use_heap_large);
1000 		if(!DeviceIoControl(hLink, FSCTL_GET_REPARSE_POINT, NULL, 0, pbuffer,  MAXIMUM_REPARSE_DATA_BUFFER_SIZE, &retlength, NULL)) {
1001 			free_alloca(pbuffer, use_heap_large);
1002 			CloseHandle(hLink);
1003 			return -1;
1004 		}
1005 
1006 		if(pbuffer->ReparseTag == IO_REPARSE_TAG_SYMLINK) {
1007 			buf->st_mode = S_IFLNK;
1008 			buf->st_mode |= (data.dwFileAttributes & FILE_ATTRIBUTE_READONLY) ? (S_IREAD|(S_IREAD>>3)|(S_IREAD>>6)) : (S_IREAD|(S_IREAD>>3)|(S_IREAD>>6)|S_IWRITE|(S_IWRITE>>3)|(S_IWRITE>>6));
1009 		}
1010 
1011 #if 0 /* Not used yet */
1012 		else if(pbuffer->ReparseTag == IO_REPARSE_TAG_MOUNT_POINT) {
1013 			buf->st_mode |=;
1014 		}
1015 #endif
1016 		free_alloca(pbuffer, use_heap_large);
1017 	}
1018 
1019 	CloseHandle(hLink);
1020 
1021 	return ret;
1022 
1023 }/*}}}*/
1024 
1025 PW32IO int php_win32_ioutil_fstat(int fd, php_win32_ioutil_stat_t *buf)
1026 {/*{{{*/
1027 	return php_win32_ioutil_fstat_int((HANDLE)_get_osfhandle(fd), buf, NULL, 0, NULL);
1028 }/*}}}*/
1029 
1030 static ssize_t php_win32_ioutil_readlink_int(HANDLE h, wchar_t *buf, size_t buf_len)
1031 {/*{{{*/
1032 	char buffer[MAXIMUM_REPARSE_DATA_BUFFER_SIZE];
1033 	PHP_WIN32_IOUTIL_REPARSE_DATA_BUFFER *reparse_data = (PHP_WIN32_IOUTIL_REPARSE_DATA_BUFFER*) buffer;
1034 	wchar_t* reparse_target;
1035 	DWORD reparse_target_len;
1036 	DWORD bytes;
1037 
1038 	if (!DeviceIoControl(h,
1039 		FSCTL_GET_REPARSE_POINT,
1040 		NULL,
1041 		0,
1042 		buffer,
1043 		sizeof buffer,
1044 		&bytes,
1045 		NULL)) {
1046 		SET_ERRNO_FROM_WIN32_CODE(GetLastError());
1047 		return -1;
1048 	}
1049 
1050 	if (reparse_data->ReparseTag == IO_REPARSE_TAG_SYMLINK) {
1051 		/* Real symlink */
1052 
1053 		/* BC - relative links are shown as absolute */
1054 		if (reparse_data->SymbolicLinkReparseBuffer.Flags & SYMLINK_FLAG_RELATIVE) {
1055 			SET_ERRNO_FROM_WIN32_CODE(ERROR_SYMLINK_NOT_SUPPORTED);
1056 			return -1;
1057 		}
1058 
1059 		reparse_target = reparse_data->SymbolicLinkReparseBuffer.ReparseTarget +
1060 			(reparse_data->SymbolicLinkReparseBuffer.SubstituteNameOffset /
1061 			sizeof(wchar_t));
1062 		reparse_target_len =
1063 			reparse_data->SymbolicLinkReparseBuffer.SubstituteNameLength /
1064 			sizeof(wchar_t);
1065 
1066 		/* Real symlinks can contain pretty much everything, but the only thing we
1067 		* really care about is undoing the implicit conversion to an NT namespaced
1068 		* path that CreateSymbolicLink will perform on absolute paths. If the path
1069 		* is win32-namespaced then the user must have explicitly made it so, and
1070 		* we better just return the unmodified reparse data. */
1071 		if (reparse_target_len >= 4 &&
1072 		reparse_target[0] == L'\\' &&
1073 		reparse_target[1] == L'?' &&
1074 		reparse_target[2] == L'?' &&
1075 		reparse_target[3] == L'\\') {
1076 			/* Starts with \??\ */
1077 			if (reparse_target_len >= 6 &&
1078 				((reparse_target[4] >= L'A' && reparse_target[4] <= L'Z') ||
1079 				(reparse_target[4] >= L'a' && reparse_target[4] <= L'z')) &&
1080 				reparse_target[5] == L':' &&
1081 				(reparse_target_len == 6 || reparse_target[6] == L'\\')) {
1082 				/* \??\<drive>:\ */
1083 				reparse_target += 4;
1084 				reparse_target_len -= 4;
1085 
1086 			} else if (reparse_target_len >= 8 &&
1087 				(reparse_target[4] == L'U' || reparse_target[4] == L'u') &&
1088 				(reparse_target[5] == L'N' || reparse_target[5] == L'n') &&
1089 				(reparse_target[6] == L'C' || reparse_target[6] == L'c') &&
1090 				reparse_target[7] == L'\\') {
1091 				/* \??\UNC\<server>\<share>\ - make sure the final path looks like
1092 				* \\<server>\<share>\ */
1093 				reparse_target += 6;
1094 				reparse_target[0] = L'\\';
1095 				reparse_target_len -= 6;
1096 			}
1097 		}
1098 
1099 	} else if (reparse_data->ReparseTag == IO_REPARSE_TAG_MOUNT_POINT) {
1100 		/* Junction. */
1101 		reparse_target = reparse_data->MountPointReparseBuffer.ReparseTarget +
1102 			(reparse_data->MountPointReparseBuffer.SubstituteNameOffset /
1103 			sizeof(wchar_t));
1104 		reparse_target_len = reparse_data->MountPointReparseBuffer.SubstituteNameLength / sizeof(wchar_t);
1105 
1106 		/* Only treat junctions that look like \??\<drive>:\ as symlink. Junctions
1107 		* can also be used as mount points, like \??\Volume{<guid>}, but that's
1108 		* confusing for programs since they wouldn't be able to actually
1109 		* understand such a path when returned by uv_readlink(). UNC paths are
1110 		* never valid for junctions so we don't care about them. */
1111 		if (!(reparse_target_len >= 6 &&
1112 			reparse_target[0] == L'\\' &&
1113 			reparse_target[1] == L'?' &&
1114 			reparse_target[2] == L'?' &&
1115 			reparse_target[3] == L'\\' &&
1116 			((reparse_target[4] >= L'A' && reparse_target[4] <= L'Z') ||
1117 			(reparse_target[4] >= L'a' && reparse_target[4] <= L'z')) &&
1118 			reparse_target[5] == L':' &&
1119 			(reparse_target_len == 6 || reparse_target[6] == L'\\'))) {
1120 			SET_ERRNO_FROM_WIN32_CODE(ERROR_SYMLINK_NOT_SUPPORTED);
1121 			return -1;
1122 		}
1123 
1124 		/* Remove leading \??\ */
1125 		reparse_target += 4;
1126 		reparse_target_len -= 4;
1127 
1128 	} else {
1129 		/* Reparse tag does not indicate a symlink. */
1130 		SET_ERRNO_FROM_WIN32_CODE(ERROR_SYMLINK_NOT_SUPPORTED);
1131 		return -1;
1132 	}
1133 
1134 	if (reparse_target_len >= buf_len) {
1135 		SET_ERRNO_FROM_WIN32_CODE(ERROR_NOT_ENOUGH_MEMORY);
1136 		return -1;
1137 	}
1138 
1139 	memcpy(buf, reparse_target, reparse_target_len*sizeof(wchar_t));
1140 	buf[reparse_target_len] = L'\0';
1141 
1142 	return reparse_target_len;
1143 }/*}}}*/
1144 
1145 PW32IO ssize_t php_win32_ioutil_readlink_w(const wchar_t *path, wchar_t *buf, size_t buf_len)
1146 {/*{{{*/
1147 	HANDLE h;
1148 	ssize_t ret;
1149 
1150 	/* Get a handle to the symbolic link (if path is a symbolic link) */
1151 	h = CreateFileW(path,
1152 					0,
1153 					0,
1154 					NULL,
1155 					OPEN_EXISTING,
1156 					FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS,
1157 					NULL);
1158 
1159 	if (h == INVALID_HANDLE_VALUE) {
1160 		SET_ERRNO_FROM_WIN32_CODE(GetLastError());
1161 		return -1;
1162 	}
1163 
1164 	ret = php_win32_ioutil_readlink_int(h, buf, buf_len);
1165 
1166 	if (ret < 0) {
1167 		wchar_t target[PHP_WIN32_IOUTIL_MAXPATHLEN];
1168 		size_t target_len;
1169 		size_t offset = 0;
1170 
1171 		/* BC - get a handle to the target (if path is a symbolic link) */
1172 		CloseHandle(h);
1173 		h = CreateFileW(path,
1174 						0,
1175 						0,
1176 						NULL,
1177 						OPEN_EXISTING,
1178 						FILE_FLAG_BACKUP_SEMANTICS,
1179 						NULL);
1180 
1181 		if (h == INVALID_HANDLE_VALUE) {
1182 			SET_ERRNO_FROM_WIN32_CODE(GetLastError());
1183 			return -1;
1184 		}
1185 
1186 		target_len = GetFinalPathNameByHandleW(h, target, PHP_WIN32_IOUTIL_MAXPATHLEN, VOLUME_NAME_DOS);
1187 
1188 		if(target_len >= buf_len || target_len >= PHP_WIN32_IOUTIL_MAXPATHLEN || target_len == 0) {
1189 			CloseHandle(h);
1190 			return -1;
1191 		}
1192 
1193 		if(target_len > 4) {
1194 			/* Skip first 4 characters if they are "\\?\" */
1195 			if(target[0] == L'\\' && target[1] == L'\\' && target[2] == L'?' && target[3] ==  L'\\') {
1196 				offset = 4;
1197 
1198 				/* \\?\UNC\ */
1199 				if (target_len > 7 && target[4] == L'U' && target[5] == L'N' && target[6] == L'C') {
1200 					offset += 2;
1201 					target[offset] = L'\\';
1202 				}
1203 			}
1204 		}
1205 
1206 		ret = target_len - offset;
1207 		memcpy(buf, target + offset, (ret + 1)*sizeof(wchar_t));
1208 	}
1209 
1210 	CloseHandle(h);
1211 
1212 	return ret;
1213 }/*}}}*/
1214