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