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