xref: /PHP-7.3/win32/ioutil.c (revision bb735c9e)
1 /*
2    +----------------------------------------------------------------------+
3    | PHP Version 7                                                        |
4    +----------------------------------------------------------------------+
5    | Copyright (c) 1997-2018 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 
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 
820 /*
821  * Local variables:
822  * tab-width: 4
823  * c-basic-offset: 4
824  * End:
825  * vim600: sw=4 ts=4 fdm=marker
826  * vim<600: sw=4 ts=4
827  */
828