1 /*
2 +----------------------------------------------------------------------+
3 | PHP Version 7 |
4 +----------------------------------------------------------------------+
5 | Copyright (c) 1997-2018 The PHP Group |
6 +----------------------------------------------------------------------+
7 | This source file is subject to version 3.01 of the PHP license, |
8 | that is bundled with this package in the file LICENSE, and is |
9 | available through the world-wide-web at the following url: |
10 | http://www.php.net/license/3_01.txt |
11 | If you did not receive a copy of the PHP license and are unable to |
12 | obtain it through the world-wide-web, please send a note to |
13 | license@php.net so we can mail you a copy immediately. |
14 +----------------------------------------------------------------------+
15 | Author: Anatol Belski <ab@php.net> |
16 +----------------------------------------------------------------------+
17 */
18
19 /* This file integrates several modified parts from the libuv project, which
20 * is copyrighted to
21 *
22 * Copyright Joyent, Inc. and other Node contributors. All rights reserved.
23 *
24 * Permission is hereby granted, free of charge, to any person obtaining a copy
25 * of this software and associated documentation files (the "Software"), to
26 * deal in the Software without restriction, including without limitation the
27 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
28 * sell copies of the Software, and to permit persons to whom the Software is
29 * furnished to do so, subject to the following conditions:
30 *
31 * The above copyright notice and this permission notice shall be included in
32 * all copies or substantial portions of the Software.
33 *
34 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
35 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
36 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
37 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
38 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
39 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
40 * IN THE SOFTWARE.
41 */
42
43 #include <assert.h>
44 #include <stdlib.h>
45 #include <direct.h>
46 #include <errno.h>
47 #include <fcntl.h>
48 #include <io.h>
49 #include <limits.h>
50 #include <sys/stat.h>
51 #include <sys/utime.h>
52 #include <stdio.h>
53
54 #include "php.h"
55 #include "SAPI.h"
56 #include "win32/winutil.h"
57 #include "win32/time.h"
58 #include "win32/ioutil.h"
59 #include "win32/codepage.h"
60 #include "main/streams/php_stream_plain_wrapper.h"
61
62 #include <pathcch.h>
63
64 /*
65 #undef NONLS
66 #undef _WINNLS_
67 #include <winnls.h>
68 */
69
70 typedef HRESULT (__stdcall *MyPathCchCanonicalizeEx)(wchar_t *pszPathOut, size_t cchPathOut, const wchar_t *pszPathIn, unsigned long dwFlags);
71
72 static MyPathCchCanonicalizeEx canonicalize_path_w = NULL;
73
php_win32_ioutil_posix_to_open_opts(int flags,mode_t mode,php_ioutil_open_opts * opts)74 PW32IO BOOL php_win32_ioutil_posix_to_open_opts(int flags, mode_t mode, php_ioutil_open_opts *opts)
75 {/*{{{*/
76 int current_umask;
77
78 opts->attributes = 0;
79
80 /* Obtain the active umask. umask() never fails and returns the previous */
81 /* umask. */
82 current_umask = umask(0);
83 umask(current_umask);
84
85 /* convert flags and mode to CreateFile parameters */
86 switch (flags & (_O_RDONLY | _O_WRONLY | _O_RDWR)) {
87 case _O_RDONLY:
88 opts->access = FILE_GENERIC_READ;
89 /* XXX not opening dirs yet, see also at the bottom of this function. Should be evaluated properly. */
90 /*opts->attributes |= FILE_FLAG_BACKUP_SEMANTICS;*/
91 break;
92 case _O_WRONLY:
93 opts->access = FILE_GENERIC_WRITE;
94 break;
95 case _O_RDWR:
96 opts->access = FILE_GENERIC_READ | FILE_GENERIC_WRITE;
97 break;
98 default:
99 goto einval;
100 }
101
102 if (flags & _O_APPEND) {
103 /* XXX this might look wrong, but i just leave it here. Disabling FILE_WRITE_DATA prevents the current truncate behaviors for files opened with "a". */
104 /* access &= ~FILE_WRITE_DATA;*/
105 opts->access |= FILE_APPEND_DATA;
106 opts->attributes &= ~FILE_FLAG_BACKUP_SEMANTICS;
107 }
108
109 /*
110 * Here is where we deviate significantly from what CRT's _open()
111 * does. We indiscriminately use all the sharing modes, to match
112 * UNIX semantics. In particular, this ensures that the file can
113 * be deleted even whilst it's open.
114 */
115 opts->share = PHP_WIN32_IOUTIL_DEFAULT_SHARE_MODE;
116
117 switch (flags & (_O_CREAT | _O_EXCL | _O_TRUNC)) {
118 case 0:
119 case _O_EXCL:
120 opts->disposition = OPEN_EXISTING;
121 break;
122 case _O_CREAT:
123 opts->disposition = OPEN_ALWAYS;
124 break;
125 case _O_CREAT | _O_EXCL:
126 case _O_CREAT | _O_TRUNC | _O_EXCL:
127 opts->disposition = CREATE_NEW;
128 break;
129 case _O_TRUNC:
130 case _O_TRUNC | _O_EXCL:
131 opts->disposition = TRUNCATE_EXISTING;
132 break;
133 case _O_CREAT | _O_TRUNC:
134 opts->disposition = CREATE_ALWAYS;
135 break;
136 default:
137 goto einval;
138 }
139
140 opts->attributes |= FILE_ATTRIBUTE_NORMAL;
141 if (flags & _O_CREAT) {
142 if (!((mode & ~current_umask) & _S_IWRITE)) {
143 opts->attributes |= FILE_ATTRIBUTE_READONLY;
144 }
145 }
146
147 if (flags & _O_TEMPORARY ) {
148 opts->attributes |= FILE_FLAG_DELETE_ON_CLOSE | FILE_ATTRIBUTE_TEMPORARY;
149 opts->access |= DELETE;
150 }
151
152 if (flags & _O_SHORT_LIVED) {
153 opts->attributes |= FILE_ATTRIBUTE_TEMPORARY;
154 }
155
156 switch (flags & (_O_SEQUENTIAL | _O_RANDOM)) {
157 case 0:
158 break;
159 case _O_SEQUENTIAL:
160 opts->attributes |= FILE_FLAG_SEQUENTIAL_SCAN;
161 break;
162 case _O_RANDOM:
163 opts->attributes |= FILE_FLAG_RANDOM_ACCESS;
164 break;
165 default:
166 goto einval;
167 }
168
169 /* Very compat options */
170 /*if (flags & O_ASYNC) {
171 opts->attributes |= FILE_FLAG_OVERLAPPED;
172 } else if (flags & O_SYNC) {
173 opts->attributes &= ~FILE_FLAG_OVERLAPPED;
174 }*/
175
176 /* Setting this flag makes it possible to open a directory. */
177 /* XXX not being done as this means a behavior change. Should be evaluated properly. */
178 /* opts->attributes |= FILE_FLAG_BACKUP_SEMANTICS; */
179
180 return 1;
181
182 einval:
183 _set_errno(EINVAL);
184 return 0;
185 }/*}}}*/
186
php_win32_ioutil_open_w(const wchar_t * path,int flags,...)187 PW32IO int php_win32_ioutil_open_w(const wchar_t *path, int flags, ...)
188 {/*{{{*/
189 php_ioutil_open_opts open_opts;
190 HANDLE file;
191 int fd;
192 mode_t mode = 0;
193
194 PHP_WIN32_IOUTIL_CHECK_PATH_W(path, -1, 0)
195
196 if (flags & O_CREAT) {
197 va_list arg;
198
199 va_start(arg, flags);
200 mode = (mode_t) va_arg(arg, int);
201 va_end(arg);
202 }
203
204 if (!php_win32_ioutil_posix_to_open_opts(flags, mode, &open_opts)) {
205 goto einval;
206 }
207
208 /* XXX care about security attributes here if needed, see tsrm_win32_access() */
209 file = CreateFileW(path,
210 open_opts.access,
211 open_opts.share,
212 NULL,
213 open_opts.disposition,
214 open_opts.attributes,
215 NULL);
216
217 if (file == INVALID_HANDLE_VALUE) {
218 DWORD error = GetLastError();
219
220 if (error == ERROR_FILE_EXISTS && (flags & _O_CREAT) &&
221 !(flags & _O_EXCL)) {
222 /* Special case: when ERROR_FILE_EXISTS happens and O_CREAT was */
223 /* specified, it means the path referred to a directory. */
224 _set_errno(EISDIR);
225 } else {
226 SET_ERRNO_FROM_WIN32_CODE(error);
227 }
228 return -1;
229 }
230
231 fd = _open_osfhandle((intptr_t) file, flags);
232 if (fd < 0) {
233 DWORD error = GetLastError();
234
235 /* The only known failure mode for _open_osfhandle() is EMFILE, in which
236 * case GetLastError() will return zero. However we'll try to handle other
237 * errors as well, should they ever occur.
238 */
239 if (errno == EMFILE) {
240 _set_errno(EMFILE);
241 } else if (error != ERROR_SUCCESS) {
242 SET_ERRNO_FROM_WIN32_CODE(error);
243 }
244 CloseHandle(file);
245 return -1;
246 }
247
248 if (flags & _O_TEXT) {
249 _setmode(fd, _O_TEXT);
250 } else if (flags & _O_BINARY) {
251 _setmode(fd, _O_BINARY);
252 }
253
254 return fd;
255
256 einval:
257 _set_errno(EINVAL);
258 return -1;
259 }/*}}}*/
260
php_win32_ioutil_close(int fd)261 PW32IO int php_win32_ioutil_close(int fd)
262 {/*{{{*/
263 int result = -1;
264
265 if (-1 == fd) {
266 _set_errno(EBADF);
267 return result;
268 }
269
270 if (fd > 2) {
271 result = _close(fd);
272 } else {
273 result = 0;
274 }
275
276 /* _close doesn't set _doserrno on failure, but it does always set errno
277 * to EBADF on failure.
278 */
279 if (result == -1) {
280 _set_errno(EBADF);
281 }
282
283 return result;
284 }/*}}}*/
285
php_win32_ioutil_mkdir_w(const wchar_t * path,mode_t mode)286 PW32IO int php_win32_ioutil_mkdir_w(const wchar_t *path, mode_t mode)
287 {/*{{{*/
288 size_t path_len;
289 const wchar_t *my_path;
290
291 if (!path) {
292 SET_ERRNO_FROM_WIN32_CODE(ERROR_INVALID_PARAMETER);
293 return -1;
294 }
295
296 PHP_WIN32_IOUTIL_CHECK_PATH_W(path, -1, 0)
297
298 path_len = wcslen(path);
299 if (path_len < _MAX_PATH && path_len >= _MAX_PATH - 12) {
300 /* Special case here. From the doc:
301
302 "When using an API to create a directory, the specified path cannot be
303 so long that you cannot append an 8.3 file name ..."
304
305 Thus, if the directory name length happens to be in this range, it
306 already needs to be a long path. The given path is already normalized
307 and prepared, need only to prefix it.
308 */
309 wchar_t *tmp = (wchar_t *) malloc((path_len + 1) * sizeof(wchar_t));
310 if (!tmp) {
311 SET_ERRNO_FROM_WIN32_CODE(ERROR_NOT_ENOUGH_MEMORY);
312 return -1;
313 }
314 memmove(tmp, path, (path_len + 1) * sizeof(wchar_t));
315
316 if (PHP_WIN32_IOUTIL_NORM_FAIL == php_win32_ioutil_normalize_path_w(&tmp, path_len, &path_len)) {
317 free(tmp);
318 return -1;
319 }
320
321 if (!PHP_WIN32_IOUTIL_IS_LONG_PATHW(tmp, path_len)) {
322 wchar_t *_tmp = (wchar_t *) malloc((path_len + PHP_WIN32_IOUTIL_LONG_PATH_PREFIX_LENW + 1) * sizeof(wchar_t));
323 wchar_t *src, *dst;
324 if (!_tmp) {
325 SET_ERRNO_FROM_WIN32_CODE(ERROR_NOT_ENOUGH_MEMORY);
326 free(tmp);
327 return -1;
328 }
329 memmove(_tmp, PHP_WIN32_IOUTIL_LONG_PATH_PREFIXW, PHP_WIN32_IOUTIL_LONG_PATH_PREFIX_LENW * sizeof(wchar_t));
330 src = tmp;
331 dst = _tmp + PHP_WIN32_IOUTIL_LONG_PATH_PREFIX_LENW;
332 while (src < tmp + path_len) {
333 if (*src == PHP_WIN32_IOUTIL_FW_SLASHW) {
334 *dst++ = PHP_WIN32_IOUTIL_DEFAULT_SLASHW;
335 src++;
336 } else {
337 *dst++ = *src++;
338 }
339 }
340 path_len += PHP_WIN32_IOUTIL_LONG_PATH_PREFIX_LENW;
341 _tmp[path_len] = L'\0';
342 free(tmp);
343 tmp = _tmp;
344 }
345
346 my_path = tmp;
347
348 } else {
349 my_path = path;
350 }
351
352 if (!CreateDirectoryW(my_path, NULL)) {
353 DWORD err = GetLastError();
354 if (my_path != path) {
355 free((void *)my_path);
356 }
357 SET_ERRNO_FROM_WIN32_CODE(err);
358 return -1;
359 }
360
361 if (my_path != path) {
362 free((void *)my_path);
363 }
364
365 return 0;
366 }/*}}}*/
367
php_win32_ioutil_unlink_w(const wchar_t * path)368 PW32IO int php_win32_ioutil_unlink_w(const wchar_t *path)
369 {/*{{{*/
370 DWORD err = 0;
371 HANDLE h;
372 BY_HANDLE_FILE_INFORMATION info;
373 FILE_DISPOSITION_INFO disposition;
374 BOOL status;
375
376 PHP_WIN32_IOUTIL_CHECK_PATH_W(path, -1, 0)
377
378 h = CreateFileW(path,
379 FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES | DELETE,
380 PHP_WIN32_IOUTIL_DEFAULT_SHARE_MODE,
381 NULL,
382 OPEN_EXISTING,
383 FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS,
384 NULL);
385
386 if (INVALID_HANDLE_VALUE == h) {
387 err = GetLastError();
388 SET_ERRNO_FROM_WIN32_CODE(err);
389 return -1;
390 }
391
392 if (!GetFileInformationByHandle(h, &info)) {
393 err = GetLastError();
394 CloseHandle(h);
395 SET_ERRNO_FROM_WIN32_CODE(err);
396 return -1;
397 }
398
399 if (info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
400 /* TODO Handle possible reparse point. */
401 CloseHandle(h);
402 SET_ERRNO_FROM_WIN32_CODE(ERROR_DIRECTORY_NOT_SUPPORTED);
403 return -1;
404 }
405
406 #if 0
407 /* XXX BC breach! */
408 if (info.dwFileAttributes & FILE_ATTRIBUTE_READONLY) {
409 /* Remove read-only attribute */
410 FILE_BASIC_INFO basic = { 0 };
411
412 basic.FileAttributes = info.dwFileAttributes & ~(FILE_ATTRIBUTE_READONLY);
413
414 status = SetFileInformationByHandle(h, FileBasicInfo, &basic, sizeof basic);
415 if (!status) {
416 err = GetLastError();
417 SET_ERRNO_FROM_WIN32_CODE(err);
418 CloseHandle(h);
419 return -1;
420 }
421 }
422 #endif
423
424 /* Try to set the delete flag. */
425 disposition.DeleteFile = TRUE;
426 status = SetFileInformationByHandle(h, FileDispositionInfo, &disposition, sizeof disposition);
427 if (!status) {
428 err = GetLastError();
429 CloseHandle(h);
430 SET_ERRNO_FROM_WIN32_CODE(err);
431 return -1;
432 }
433
434 CloseHandle(h);
435
436 return 0;
437 }/*}}}*/
438
php_win32_ioutil_rmdir_w(const wchar_t * path)439 PW32IO int php_win32_ioutil_rmdir_w(const wchar_t *path)
440 {/*{{{*/
441 int ret = 0;
442
443 PHP_WIN32_IOUTIL_CHECK_PATH_W(path, -1, 0)
444
445 if (!RemoveDirectoryW(path)) {
446 DWORD err = GetLastError();
447 ret = -1;
448 SET_ERRNO_FROM_WIN32_CODE(err);
449 }
450
451 return ret;
452 }/*}}}*/
453
php_win32_ioutil_chdir_w(const wchar_t * path)454 PW32IO int php_win32_ioutil_chdir_w(const wchar_t *path)
455 {/*{{{*/
456 int ret = 0;
457
458 if (!SetCurrentDirectoryW(path)) {
459 DWORD err = GetLastError();
460 ret = -1;
461 SET_ERRNO_FROM_WIN32_CODE(err);
462 }
463
464 return ret;
465 }/*}}}*/
466
php_win32_ioutil_rename_w(const wchar_t * oldname,const wchar_t * newname)467 PW32IO int php_win32_ioutil_rename_w(const wchar_t *oldname, const wchar_t *newname)
468 {/*{{{*/
469 int ret = 0;
470
471 PHP_WIN32_IOUTIL_CHECK_PATH_W(oldname, -1, 0)
472 PHP_WIN32_IOUTIL_CHECK_PATH_W(newname, -1, 0)
473
474
475 if (!MoveFileExW(oldname, newname, MOVEFILE_REPLACE_EXISTING|MOVEFILE_COPY_ALLOWED)) {
476 DWORD err = GetLastError();
477 ret = -1;
478 SET_ERRNO_FROM_WIN32_CODE(err);
479 }
480
481 return ret;
482 }/*}}}*/
483
php_win32_ioutil_getcwd_w(wchar_t * buf,size_t len)484 PW32IO wchar_t *php_win32_ioutil_getcwd_w(wchar_t *buf, size_t len)
485 {/*{{{*/
486 DWORD err = 0;
487 wchar_t *tmp_buf = NULL;
488 DWORD tmp_len = (DWORD)len;
489
490 /* If buf was NULL, the result has to be freed outside here. */
491 if (!buf) {
492 tmp_len = GetCurrentDirectoryW(0, NULL) + 1;
493 if (!tmp_len) {
494 err = GetLastError();
495 SET_ERRNO_FROM_WIN32_CODE(err);
496 return NULL;
497 } else if (tmp_len > len) {
498 SET_ERRNO_FROM_WIN32_CODE(ERROR_INSUFFICIENT_BUFFER);
499 return NULL;
500 }
501
502 tmp_buf = (wchar_t *)malloc((tmp_len)*sizeof(wchar_t));
503 if (!tmp_buf) {
504 SET_ERRNO_FROM_WIN32_CODE(ERROR_NOT_ENOUGH_MEMORY);
505 return NULL;
506 }
507 buf = tmp_buf;
508 }
509
510 if (!GetCurrentDirectoryW(tmp_len, buf)) {
511 err = GetLastError();
512 SET_ERRNO_FROM_WIN32_CODE(err);
513 free(tmp_buf);
514 return NULL;
515 }
516
517 return (wchar_t *)buf;
518 }/*}}}*/
519
520 /* based on zend_dirname(). */
php_win32_ioutil_dirname(char * path,size_t len)521 PW32IO size_t php_win32_ioutil_dirname(char *path, size_t len)
522 {/*{{{*/
523 char *ret = NULL, *start;
524 size_t ret_len, len_adjust = 0, pathw_len;
525 wchar_t *endw, *pathw, *startw;
526
527 if (len == 0) {
528 return 0;
529 }
530
531 start = path;
532
533 /* Don't really care about the path normalization, pure parsing here. */
534 startw = pathw = php_win32_cp_conv_any_to_w(path, len, &pathw_len);
535 if (!pathw) {
536 return 0;
537 }
538
539 endw = pathw + pathw_len - 1;
540
541 if ((2 <= pathw_len) && iswalpha((wint_t)(pathw)[0]) && (L':' == pathw[1])) {
542 pathw += 2;
543 path += 2;
544 len_adjust += 2;
545 if (2 == len) {
546 free(startw);
547 return len;
548 }
549 }
550
551 /* Strip trailing slashes */
552 while (endw >= pathw && PHP_WIN32_IOUTIL_IS_SLASHW(*endw)) {
553 endw--;
554 }
555 if (endw < pathw) {
556 free(startw);
557 /* The path only contained slashes */
558 path[0] = PHP_WIN32_IOUTIL_DEFAULT_SLASH;
559 path[1] = '\0';
560 return 1 + len_adjust;
561 }
562
563 /* Strip filename */
564 while (endw >= pathw && !PHP_WIN32_IOUTIL_IS_SLASHW(*endw)) {
565 endw--;
566 }
567 if (endw < pathw) {
568 free(startw);
569 path[0] = '.';
570 path[1] = '\0';
571 return 1 + len_adjust;
572 }
573
574 /* Strip slashes which came before the file name */
575 while (endw >= pathw && PHP_WIN32_IOUTIL_IS_SLASHW(*endw)) {
576 endw--;
577 }
578 if (endw < pathw) {
579 free(startw);
580 path[0] = PHP_WIN32_IOUTIL_DEFAULT_SLASH;
581 path[1] = '\0';
582 return 1 + len_adjust;
583 }
584 *(endw+1) = L'\0';
585
586 ret_len = (endw + 1 - startw);
587 if (PHP_WIN32_IOUTIL_IS_LONG_PATHW(startw, ret_len)) {
588 ret = php_win32_ioutil_conv_w_to_any(startw + PHP_WIN32_IOUTIL_LONG_PATH_PREFIX_LENW, ret_len - PHP_WIN32_IOUTIL_LONG_PATH_PREFIX_LENW, &ret_len);
589 } else {
590 ret = php_win32_ioutil_conv_w_to_any(startw, ret_len, &ret_len);
591 }
592 memmove(start, ret, ret_len+1);
593 assert(start[ret_len] == '\0');
594 free(ret);
595 free(startw);
596
597 return ret_len;
598 }/*}}}*/
599
600 /* Partial normalization can still be acceptable, explicit fail has to be caught. */
php_win32_ioutil_normalize_path_w(wchar_t ** buf,size_t len,size_t * new_len)601 PW32IO php_win32_ioutil_normalization_result php_win32_ioutil_normalize_path_w(wchar_t **buf, size_t len, size_t *new_len)
602 {/*{{{*/
603 wchar_t *idx = *buf, canonicalw[MAXPATHLEN], _tmp[MAXPATHLEN], *pos = _tmp;
604 size_t ret_len = len;
605
606 if (len >= MAXPATHLEN) {
607 SET_ERRNO_FROM_WIN32_CODE(ERROR_BAD_LENGTH);
608 *new_len = 0;
609 return PHP_WIN32_IOUTIL_NORM_FAIL;
610 }
611
612 for (; (size_t)(idx - *buf) <= len; idx++, pos++) {
613 *pos = *idx;
614 if (PHP_WIN32_IOUTIL_FW_SLASHW == *pos) {
615 *pos = PHP_WIN32_IOUTIL_DEFAULT_SLASHW;
616 }
617 while (PHP_WIN32_IOUTIL_IS_SLASHW(*idx) && PHP_WIN32_IOUTIL_IS_SLASHW(*(idx+1))) {
618 idx++;
619 }
620 }
621
622 if (S_OK != canonicalize_path_w(canonicalw, MAXPATHLEN, _tmp, PATHCCH_ALLOW_LONG_PATHS)) {
623 /* Length unchanged. */
624 *new_len = len;
625 return PHP_WIN32_IOUTIL_NORM_PARTIAL;
626 }
627 ret_len = wcslen(canonicalw);
628 if (ret_len != len) {
629 if (ret_len > len) {
630 wchar_t *tmp = realloc(*buf, (ret_len + 1) * sizeof(wchar_t));
631 if (!tmp) {
632 SET_ERRNO_FROM_WIN32_CODE(ERROR_NOT_ENOUGH_MEMORY);
633 /* Length unchanged. */
634 *new_len = len;
635 return PHP_WIN32_IOUTIL_NORM_PARTIAL;
636 }
637 *buf = tmp;
638 }
639 memmove(*buf, canonicalw, (ret_len + 1) * sizeof(wchar_t));
640 }
641 *new_len = ret_len;
642
643 return PHP_WIN32_IOUTIL_NORM_OK;
644 }/*}}}*/
645
MyPathCchCanonicalizeExFallback(wchar_t * pszPathOut,size_t cchPathOut,const wchar_t * pszPathIn,unsigned long dwFlags)646 static HRESULT __stdcall MyPathCchCanonicalizeExFallback(wchar_t *pszPathOut, size_t cchPathOut, const wchar_t *pszPathIn, unsigned long dwFlags)
647 {/*{{{*/
648 return -42;
649 }/*}}}*/
650
php_win32_ioutil_init(void)651 BOOL php_win32_ioutil_init(void)
652 {/*{{{*/
653 HMODULE hMod = GetModuleHandle("api-ms-win-core-path-l1-1-0");
654
655 if (hMod) {
656 canonicalize_path_w = (MyPathCchCanonicalizeEx)GetProcAddress(hMod, "PathCchCanonicalizeEx");
657 if (!canonicalize_path_w) {
658 canonicalize_path_w = (MyPathCchCanonicalizeEx)MyPathCchCanonicalizeExFallback;
659 }
660 } else {
661 canonicalize_path_w = (MyPathCchCanonicalizeEx)MyPathCchCanonicalizeExFallback;
662 }
663
664 return TRUE;
665 }/*}}}*/
666
php_win32_ioutil_access_w(const wchar_t * path,mode_t mode)667 PW32IO int php_win32_ioutil_access_w(const wchar_t *path, mode_t mode)
668 {/*{{{*/
669 DWORD attr;
670
671 if ((mode & X_OK) == X_OK) {
672 DWORD type;
673 return GetBinaryTypeW(path, &type) ? 0 : -1;
674 }
675
676 attr = GetFileAttributesW(path);
677 if (attr == INVALID_FILE_ATTRIBUTES) {
678 DWORD err = GetLastError();
679 SET_ERRNO_FROM_WIN32_CODE(err);
680 return -1;
681 }
682
683 if (F_OK == mode) {
684 return 0;
685 }
686
687 if ((mode &W_OK) == W_OK && (attr & FILE_ATTRIBUTE_READONLY) == FILE_ATTRIBUTE_READONLY) {
688 SET_ERRNO_FROM_WIN32_CODE(ERROR_ACCESS_DENIED);
689 return -1;
690 }
691
692 return 0;
693 }/*}}}*/
694
php_win32_ioutil_fopen_w(const wchar_t * path,const wchar_t * mode)695 PW32IO FILE *php_win32_ioutil_fopen_w(const wchar_t *path, const wchar_t *mode)
696 {/*{{{*/
697 FILE *ret;
698 char modea[16] = {0};
699 int err = 0, fd, flags, i = 0;
700
701 PHP_WIN32_IOUTIL_CHECK_PATH_W(path, NULL, 0)
702
703 /* Using the converter from streams, char only. */
704 while (i < sizeof(modea)-1 && mode[i]) {
705 modea[i] = (char)mode[i];
706 i++;
707 }
708 if (SUCCESS != php_stream_parse_fopen_modes(modea, &flags)) {
709 SET_ERRNO_FROM_WIN32_CODE(ERROR_INVALID_PARAMETER);
710 return NULL;
711 }
712
713 fd = php_win32_ioutil_open_w(path, flags, 0666);
714 if (0 > fd) {
715 err = GetLastError();
716 SET_ERRNO_FROM_WIN32_CODE(err);
717 return NULL;
718 }
719
720 ret = _wfdopen(fd, mode);
721 if (!ret) {
722 err = GetLastError();
723 php_win32_ioutil_close(fd);
724 SET_ERRNO_FROM_WIN32_CODE(err);
725 return NULL;
726 }
727
728 return ret;
729 }/*}}}*/
730
php_win32_ioutil_realpath_h(HANDLE * h,wchar_t ** resolved)731 static size_t php_win32_ioutil_realpath_h(HANDLE *h, wchar_t **resolved)
732 {/*{{{*/
733 wchar_t ret[PHP_WIN32_IOUTIL_MAXPATHLEN], *ret_real;
734 DWORD ret_len, ret_real_len;
735
736 ret_len = GetFinalPathNameByHandleW(h, ret, PHP_WIN32_IOUTIL_MAXPATHLEN-1, FILE_NAME_NORMALIZED | VOLUME_NAME_DOS);
737 if (0 == ret_len) {
738 DWORD err = GetLastError();
739 SET_ERRNO_FROM_WIN32_CODE(err);
740 return (size_t)-1;
741 } else if (ret_len > PHP_WIN32_IOUTIL_MAXPATHLEN) {
742 SET_ERRNO_FROM_WIN32_CODE(ERROR_INVALID_PARAMETER);
743 return (size_t)-1;
744 }
745
746 if (NULL == *resolved) {
747 /* ret is expected to be either NULL or a buffer of capable size. */
748 *resolved = (wchar_t *) malloc((ret_len + 1)*sizeof(wchar_t));
749 if (!*resolved) {
750 SET_ERRNO_FROM_WIN32_CODE(ERROR_NOT_ENOUGH_MEMORY);
751 return (size_t)-1;
752 }
753 }
754
755 ret_real = ret;
756 ret_real_len = ret_len;
757 if (0 == wcsncmp(ret, PHP_WIN32_IOUTIL_UNC_PATH_PREFIXW, PHP_WIN32_IOUTIL_UNC_PATH_PREFIX_LENW)) {
758 ret_real += (PHP_WIN32_IOUTIL_UNC_PATH_PREFIX_LENW - 2);
759 ret_real[0] = L'\\';
760 ret_real_len -= (PHP_WIN32_IOUTIL_UNC_PATH_PREFIX_LENW - 2);
761 } else if (PHP_WIN32_IOUTIL_IS_LONG_PATHW(ret, ret_len)) {
762 ret_real += PHP_WIN32_IOUTIL_LONG_PATH_PREFIX_LENW;
763 ret_real_len -= PHP_WIN32_IOUTIL_LONG_PATH_PREFIX_LENW;
764 }
765 memmove(*resolved, ret_real, (ret_real_len+1)*sizeof(wchar_t));
766
767 return ret_real_len;
768 }/*}}}*/
769
php_win32_ioutil_realpath_w(const wchar_t * path,wchar_t * resolved)770 PW32IO wchar_t *php_win32_ioutil_realpath_w(const wchar_t *path, wchar_t *resolved)
771 {/*{{{*/
772 return php_win32_ioutil_realpath_w_ex0(path, resolved, NULL);
773 }/*}}}*/
774
php_win32_ioutil_realpath_w_ex0(const wchar_t * path,wchar_t * resolved,PBY_HANDLE_FILE_INFORMATION info)775 PW32IO wchar_t *php_win32_ioutil_realpath_w_ex0(const wchar_t *path, wchar_t *resolved, PBY_HANDLE_FILE_INFORMATION info)
776 {/*{{{*/
777 HANDLE h;
778 size_t ret_len;
779
780 PHP_WIN32_IOUTIL_CHECK_PATH_W(path, NULL, 0)
781
782 h = CreateFileW(path,
783 0,
784 0,
785 NULL,
786 OPEN_EXISTING,
787 FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS,
788 NULL);
789 if (INVALID_HANDLE_VALUE == h) {
790 DWORD err = GetLastError();
791 SET_ERRNO_FROM_WIN32_CODE(err);
792 return NULL;
793 }
794
795 ret_len = php_win32_ioutil_realpath_h(h, &resolved);
796 if ((size_t)-1 == ret_len) {
797 DWORD err = GetLastError();
798 CloseHandle(h);
799 SET_ERRNO_FROM_WIN32_CODE(err);
800 return NULL;
801 }
802
803 if (NULL != info && !GetFileInformationByHandle(h, info)) {
804 DWORD err = GetLastError();
805 CloseHandle(h);
806 SET_ERRNO_FROM_WIN32_CODE(err);
807 return NULL;
808 }
809
810 CloseHandle(h);
811
812 return resolved;
813 }/*}}}*/
814
realpath(const char * path,char * resolved)815 PW32IO char *realpath(const char *path, char *resolved)
816 {/*{{{*/
817 return php_win32_ioutil_realpath(path, resolved);
818 }/*}}}*/
819
820 /*
821 * Local variables:
822 * tab-width: 4
823 * c-basic-offset: 4
824 * End:
825 * vim600: sw=4 ts=4 fdm=marker
826 * vim<600: sw=4 ts=4
827 */
828