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