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