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