xref: /php-src/Zend/zend_virtual_cwd.c (revision 927adfb1)
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    | Authors: Andi Gutmans <andi@php.net>                                 |
14    |          Sascha Schumann <sascha@schumann.cx>                        |
15    |          Pierre Joye <pierre@php.net>                                |
16    +----------------------------------------------------------------------+
17 */
18 
19 #include <sys/types.h>
20 #include <sys/stat.h>
21 #include <string.h>
22 #include <stdio.h>
23 #include <limits.h>
24 #include <errno.h>
25 #include <stdlib.h>
26 #include <fcntl.h>
27 #include <time.h>
28 
29 #include "zend.h"
30 #include "zend_virtual_cwd.h"
31 
32 #ifdef ZEND_WIN32
33 #include <io.h>
34 #include "tsrm_win32.h"
35 # ifndef IO_REPARSE_TAG_SYMLINK
36 #  define IO_REPARSE_TAG_SYMLINK 0xA000000C
37 # endif
38 
39 # ifndef IO_REPARSE_TAG_DEDUP
40 #  define IO_REPARSE_TAG_DEDUP   0x80000013
41 # endif
42 
43 # ifndef IO_REPARSE_TAG_CLOUD
44 #  define IO_REPARSE_TAG_CLOUD    (0x9000001AL)
45 # endif
46 /* IO_REPARSE_TAG_CLOUD_1 through IO_REPARSE_TAG_CLOUD_F have values of 0x9000101AL
47    to 0x9000F01AL, they can be checked against the mask. */
48 #ifndef IO_REPARSE_TAG_CLOUD_MASK
49 #define IO_REPARSE_TAG_CLOUD_MASK (0x0000F000L)
50 #endif
51 
52 #ifndef IO_REPARSE_TAG_ONEDRIVE
53 #define IO_REPARSE_TAG_ONEDRIVE   (0x80000021L)
54 #endif
55 
56 # ifndef IO_REPARSE_TAG_ACTIVISION_HSM
57 #  define IO_REPARSE_TAG_ACTIVISION_HSM (0x00000047L)
58 # endif
59 
60 # ifndef IO_REPARSE_TAG_PROJFS
61 #  define IO_REPARSE_TAG_PROJFS (0x9000001CL)
62 # endif
63 
64 # ifndef VOLUME_NAME_NT
65 #  define VOLUME_NAME_NT 0x2
66 # endif
67 
68 # ifndef VOLUME_NAME_DOS
69 #  define VOLUME_NAME_DOS 0x0
70 # endif
71 
72 # include <winioctl.h>
73 # include <winnt.h>
74 #endif
75 
76 #define VIRTUAL_CWD_DEBUG 0
77 
78 #include "TSRM.h"
79 
80 /* Only need mutex for popen() in Windows because it doesn't chdir() on UNIX */
81 #if defined(ZEND_WIN32) && defined(ZTS)
82 MUTEX_T cwd_mutex;
83 #endif
84 
85 #ifdef ZTS
86 ts_rsrc_id cwd_globals_id;
87 size_t     cwd_globals_offset;
88 #else
89 virtual_cwd_globals cwd_globals;
90 #endif
91 
92 static cwd_state main_cwd_state; /* True global */
93 
94 #ifndef ZEND_WIN32
95 #include <unistd.h>
96 #else
97 #include <direct.h>
98 #include "zend_globals.h"
99 #include "zend_globals_macros.h"
100 #endif
101 
102 #define CWD_STATE_COPY(d, s)				\
103 	(d)->cwd_length = (s)->cwd_length;		\
104 	(d)->cwd = (char *) emalloc((s)->cwd_length+1);	\
105 	memcpy((d)->cwd, (s)->cwd, (s)->cwd_length+1);
106 
107 #define CWD_STATE_FREE(s)			\
108 	efree((s)->cwd); \
109 	(s)->cwd_length = 0;
110 
111 #ifdef ZEND_WIN32
112 # define CWD_STATE_FREE_ERR(state) do { \
113 		DWORD last_error = GetLastError(); \
114 		CWD_STATE_FREE(state); \
115 		SetLastError(last_error); \
116 	} while (0)
117 #else
118 # define CWD_STATE_FREE_ERR(state) CWD_STATE_FREE(state)
119 #endif
120 
php_is_dir_ok(const cwd_state * state)121 static int php_is_dir_ok(const cwd_state *state)  /* {{{ */
122 {
123 	zend_stat_t buf = {0};
124 
125 	if (php_sys_stat(state->cwd, &buf) == 0 && S_ISDIR(buf.st_mode))
126 		return (0);
127 
128 	return (1);
129 }
130 /* }}} */
131 
php_is_file_ok(const cwd_state * state)132 static int php_is_file_ok(const cwd_state *state)  /* {{{ */
133 {
134 	zend_stat_t buf = {0};
135 
136 	if (php_sys_stat(state->cwd, &buf) == 0 && S_ISREG(buf.st_mode))
137 		return (0);
138 
139 	return (1);
140 }
141 /* }}} */
142 
cwd_globals_ctor(virtual_cwd_globals * cwd_g)143 static void cwd_globals_ctor(virtual_cwd_globals *cwd_g) /* {{{ */
144 {
145 	CWD_STATE_COPY(&cwd_g->cwd, &main_cwd_state);
146 	cwd_g->realpath_cache_size = 0;
147 	cwd_g->realpath_cache_size_limit = REALPATH_CACHE_SIZE;
148 	cwd_g->realpath_cache_ttl = REALPATH_CACHE_TTL;
149 	memset(cwd_g->realpath_cache, 0, sizeof(cwd_g->realpath_cache));
150 }
151 /* }}} */
152 
realpath_cache_clean_helper(uint32_t max_entries,realpath_cache_bucket ** cache,zend_long * cache_size)153 static void realpath_cache_clean_helper(uint32_t max_entries, realpath_cache_bucket **cache, zend_long *cache_size)
154 {
155 	uint32_t i;
156 
157 	for (i = 0; i < max_entries; i++) {
158 		realpath_cache_bucket *p = cache[i];
159 		while (p != NULL) {
160 			realpath_cache_bucket *r = p;
161 			p = p->next;
162 			free(r);
163 		}
164 		cache[i] = NULL;
165 	}
166 	*cache_size = 0;
167 }
168 
cwd_globals_dtor(virtual_cwd_globals * cwd_g)169 static void cwd_globals_dtor(virtual_cwd_globals *cwd_g) /* {{{ */
170 {
171 	realpath_cache_clean_helper(sizeof(cwd_g->realpath_cache)/sizeof(cwd_g->realpath_cache[0]), cwd_g->realpath_cache, &cwd_g->realpath_cache_size);
172 }
173 /* }}} */
174 
virtual_cwd_main_cwd_init(uint8_t reinit)175 void virtual_cwd_main_cwd_init(uint8_t reinit) /* {{{ */
176 {
177 	char cwd[MAXPATHLEN];
178 	char *result;
179 
180 	if (reinit) {
181 		free(main_cwd_state.cwd);
182 	}
183 
184 #ifdef ZEND_WIN32
185 	ZeroMemory(&cwd, sizeof(cwd));
186 	result = php_win32_ioutil_getcwd(cwd, sizeof(cwd));
187 #else
188 	result = getcwd(cwd, sizeof(cwd));
189 #endif
190 
191 	if (!result) {
192 		cwd[0] = '\0';
193 	}
194 
195 	main_cwd_state.cwd_length = strlen(cwd);
196 #ifdef ZEND_WIN32
197 	if (main_cwd_state.cwd_length >= 2 && cwd[1] == ':') {
198 		cwd[0] = toupper(cwd[0]);
199 	}
200 #endif
201 	main_cwd_state.cwd = strdup(cwd);
202 }
203 /* }}} */
204 
virtual_cwd_startup(void)205 CWD_API void virtual_cwd_startup(void) /* {{{ */
206 {
207 	virtual_cwd_main_cwd_init(0);
208 #ifdef ZTS
209 	ts_allocate_fast_id(&cwd_globals_id, &cwd_globals_offset, sizeof(virtual_cwd_globals), (ts_allocate_ctor) cwd_globals_ctor, (ts_allocate_dtor) cwd_globals_dtor);
210 #else
211 	cwd_globals_ctor(&cwd_globals);
212 #endif
213 
214 #if (defined(ZEND_WIN32)) && defined(ZTS)
215 	cwd_mutex = tsrm_mutex_alloc();
216 #endif
217 }
218 /* }}} */
219 
virtual_cwd_shutdown(void)220 CWD_API void virtual_cwd_shutdown(void) /* {{{ */
221 {
222 #ifndef ZTS
223 	cwd_globals_dtor(&cwd_globals);
224 #endif
225 #if (defined(ZEND_WIN32)) && defined(ZTS)
226 	tsrm_mutex_free(cwd_mutex);
227 #endif
228 
229 	free(main_cwd_state.cwd); /* Don't use CWD_STATE_FREE because the non global states will probably use emalloc()/efree() */
230 }
231 /* }}} */
232 
virtual_cwd_activate(void)233 CWD_API int virtual_cwd_activate(void) /* {{{ */
234 {
235 	if (CWDG(cwd).cwd == NULL) {
236 		CWD_STATE_COPY(&CWDG(cwd), &main_cwd_state);
237 	}
238 	return 0;
239 }
240 /* }}} */
241 
virtual_cwd_deactivate(void)242 CWD_API int virtual_cwd_deactivate(void) /* {{{ */
243 {
244 	if (CWDG(cwd).cwd != NULL) {
245 		CWD_STATE_FREE(&CWDG(cwd));
246 		CWDG(cwd).cwd = NULL;
247 	}
248 	return 0;
249 }
250 /* }}} */
251 
virtual_getcwd_ex(size_t * length)252 CWD_API char *virtual_getcwd_ex(size_t *length) /* {{{ */
253 {
254 	cwd_state *state;
255 
256 	state = &CWDG(cwd);
257 
258 	if (state->cwd_length == 0) {
259 		char *retval;
260 
261 		*length = 1;
262 		retval = (char *) emalloc(2);
263 		retval[0] = DEFAULT_SLASH;
264 		retval[1] = '\0';
265 		return retval;
266 	}
267 
268 #ifdef ZEND_WIN32
269 	/* If we have something like C: */
270 	if (state->cwd_length == 2 && state->cwd[state->cwd_length-1] == ':') {
271 		char *retval;
272 
273 		*length = state->cwd_length+1;
274 		retval = (char *) emalloc(*length+1);
275 		memcpy(retval, state->cwd, *length);
276 		retval[0] = toupper(retval[0]);
277 		retval[*length-1] = DEFAULT_SLASH;
278 		retval[*length] = '\0';
279 		return retval;
280 	}
281 #endif
282 	if (!state->cwd) {
283 		*length = 0;
284 		return NULL;
285 	}
286 
287 	*length = state->cwd_length;
288 	return estrdup(state->cwd);
289 }
290 /* }}} */
291 
292 /* Same semantics as UNIX getcwd() */
virtual_getcwd(char * buf,size_t size)293 CWD_API char *virtual_getcwd(char *buf, size_t size) /* {{{ */
294 {
295 	size_t length;
296 	char *cwd;
297 
298 	cwd = virtual_getcwd_ex(&length);
299 
300 	if (buf == NULL) {
301 		return cwd;
302 	}
303 	if (length > size-1) {
304 		efree(cwd);
305 		errno = ERANGE; /* Is this OK? */
306 		return NULL;
307 	}
308 	if (!cwd) {
309 		return NULL;
310 	}
311 	memcpy(buf, cwd, length+1);
312 	efree(cwd);
313 	return buf;
314 }
315 /* }}} */
316 
317 #ifdef ZEND_WIN32
realpath_cache_key(const char * path,size_t path_len)318 static inline zend_ulong realpath_cache_key(const char *path, size_t path_len) /* {{{ */
319 {
320 	zend_ulong h;
321 	size_t bucket_key_len;
322 	const char *bucket_key_start = tsrm_win32_get_path_sid_key(path, path_len, &bucket_key_len);
323 	const char *bucket_key = bucket_key_start;
324 	const char *e;
325 
326 	if (!bucket_key) {
327 		return 0;
328 	}
329 
330 	e = bucket_key + bucket_key_len;
331 	for (h = Z_UL(2166136261); bucket_key < e;) {
332 		h *= Z_UL(16777619);
333 		h ^= *bucket_key++;
334 	}
335 	if (bucket_key_start != path) {
336 		HeapFree(GetProcessHeap(), 0, (LPVOID)bucket_key_start);
337 	}
338 	return h;
339 }
340 /* }}} */
341 #else
realpath_cache_key(const char * path,size_t path_len)342 static inline zend_ulong realpath_cache_key(const char *path, size_t path_len) /* {{{ */
343 {
344 	zend_ulong h;
345 	const char *e = path + path_len;
346 
347 	for (h = Z_UL(2166136261); path < e;) {
348 		h *= Z_UL(16777619);
349 		h ^= *path++;
350 	}
351 
352 	return h;
353 }
354 /* }}} */
355 #endif /* defined(ZEND_WIN32) */
356 
realpath_cache_clean(void)357 CWD_API void realpath_cache_clean(void) /* {{{ */
358 {
359 	realpath_cache_clean_helper(sizeof(CWDG(realpath_cache))/sizeof(CWDG(realpath_cache)[0]), CWDG(realpath_cache), &CWDG(realpath_cache_size));
360 }
361 /* }}} */
362 
realpath_cache_del(const char * path,size_t path_len)363 CWD_API void realpath_cache_del(const char *path, size_t path_len) /* {{{ */
364 {
365 	zend_ulong key = realpath_cache_key(path, path_len);
366 	zend_ulong n = key % (sizeof(CWDG(realpath_cache)) / sizeof(CWDG(realpath_cache)[0]));
367 	realpath_cache_bucket **bucket = &CWDG(realpath_cache)[n];
368 
369 	while (*bucket != NULL) {
370 		if (key == (*bucket)->key && path_len == (*bucket)->path_len &&
371 					memcmp(path, (*bucket)->path, path_len) == 0) {
372 			realpath_cache_bucket *r = *bucket;
373 			*bucket = (*bucket)->next;
374 
375 			/* if the pointers match then only subtract the length of the path */
376 		   	if(r->path == r->realpath) {
377 				CWDG(realpath_cache_size) -= sizeof(realpath_cache_bucket) + r->path_len + 1;
378 			} else {
379 				CWDG(realpath_cache_size) -= sizeof(realpath_cache_bucket) + r->path_len + 1 + r->realpath_len + 1;
380 			}
381 
382 			free(r);
383 			return;
384 		} else {
385 			bucket = &(*bucket)->next;
386 		}
387 	}
388 }
389 /* }}} */
390 
realpath_cache_add(const char * path,size_t path_len,const char * realpath,size_t realpath_len,int is_dir,time_t t)391 static inline void realpath_cache_add(const char *path, size_t path_len, const char *realpath, size_t realpath_len, int is_dir, time_t t) /* {{{ */
392 {
393 	zend_long size = sizeof(realpath_cache_bucket) + path_len + 1;
394 	int same = 1;
395 
396 	if (realpath_len != path_len ||
397 		memcmp(path, realpath, path_len) != 0) {
398 		size += realpath_len + 1;
399 		same = 0;
400 	}
401 
402 	if (CWDG(realpath_cache_size) + size <= CWDG(realpath_cache_size_limit)) {
403 		realpath_cache_bucket *bucket = malloc(size);
404 		zend_ulong n;
405 
406 		if (bucket == NULL) {
407 			return;
408 		}
409 
410 		bucket->key = realpath_cache_key(path, path_len);
411 		bucket->path = (char*)bucket + sizeof(realpath_cache_bucket);
412 		memcpy(bucket->path, path, path_len+1);
413 		bucket->path_len = path_len;
414 		if (same) {
415 			bucket->realpath = bucket->path;
416 		} else {
417 			bucket->realpath = bucket->path + (path_len + 1);
418 			memcpy(bucket->realpath, realpath, realpath_len+1);
419 		}
420 		bucket->realpath_len = realpath_len;
421 		bucket->is_dir = is_dir > 0;
422 #ifdef ZEND_WIN32
423 		bucket->is_rvalid   = 0;
424 		bucket->is_readable = 0;
425 		bucket->is_wvalid   = 0;
426 		bucket->is_writable = 0;
427 #endif
428 		bucket->expires = t + CWDG(realpath_cache_ttl);
429 		n = bucket->key % (sizeof(CWDG(realpath_cache)) / sizeof(CWDG(realpath_cache)[0]));
430 		bucket->next = CWDG(realpath_cache)[n];
431 		CWDG(realpath_cache)[n] = bucket;
432 		CWDG(realpath_cache_size) += size;
433 	}
434 }
435 /* }}} */
436 
realpath_cache_find(const char * path,size_t path_len,time_t t)437 static inline realpath_cache_bucket* realpath_cache_find(const char *path, size_t path_len, time_t t) /* {{{ */
438 {
439 	zend_ulong key = realpath_cache_key(path, path_len);
440 	zend_ulong n = key % (sizeof(CWDG(realpath_cache)) / sizeof(CWDG(realpath_cache)[0]));
441 	realpath_cache_bucket **bucket = &CWDG(realpath_cache)[n];
442 
443 	while (*bucket != NULL) {
444 		if (CWDG(realpath_cache_ttl) && (*bucket)->expires < t) {
445 			realpath_cache_bucket *r = *bucket;
446 			*bucket = (*bucket)->next;
447 
448 			/* if the pointers match then only subtract the length of the path */
449 		   	if(r->path == r->realpath) {
450 				CWDG(realpath_cache_size) -= sizeof(realpath_cache_bucket) + r->path_len + 1;
451 			} else {
452 				CWDG(realpath_cache_size) -= sizeof(realpath_cache_bucket) + r->path_len + 1 + r->realpath_len + 1;
453 			}
454 			free(r);
455 		} else if (key == (*bucket)->key && path_len == (*bucket)->path_len &&
456 					memcmp(path, (*bucket)->path, path_len) == 0) {
457 			return *bucket;
458 		} else {
459 			bucket = &(*bucket)->next;
460 		}
461 	}
462 	return NULL;
463 }
464 /* }}} */
465 
realpath_cache_lookup(const char * path,size_t path_len,time_t t)466 CWD_API realpath_cache_bucket* realpath_cache_lookup(const char *path, size_t path_len, time_t t) /* {{{ */
467 {
468 	return realpath_cache_find(path, path_len, t);
469 }
470 /* }}} */
471 
realpath_cache_size(void)472 CWD_API zend_long realpath_cache_size(void)
473 {
474 	return CWDG(realpath_cache_size);
475 }
476 
realpath_cache_max_buckets(void)477 CWD_API zend_long realpath_cache_max_buckets(void)
478 {
479 	return (sizeof(CWDG(realpath_cache)) / sizeof(CWDG(realpath_cache)[0]));
480 }
481 
realpath_cache_get_buckets(void)482 CWD_API realpath_cache_bucket** realpath_cache_get_buckets(void)
483 {
484 	return CWDG(realpath_cache);
485 }
486 
487 
488 #undef LINK_MAX
489 #define LINK_MAX 32
490 
tsrm_realpath_r(char * path,size_t start,size_t len,int * ll,time_t * t,int use_realpath,bool is_dir,int * link_is_dir)491 static size_t tsrm_realpath_r(char *path, size_t start, size_t len, int *ll, time_t *t, int use_realpath, bool is_dir, int *link_is_dir) /* {{{ */
492 {
493 	size_t i, j;
494 	int directory = 0, save;
495 #ifdef ZEND_WIN32
496 	WIN32_FIND_DATAW dataw;
497 	HANDLE hFind = INVALID_HANDLE_VALUE;
498 	ALLOCA_FLAG(use_heap_large)
499 	wchar_t *pathw = NULL;
500 	int may_retry_reparse_point;
501 #define FREE_PATHW() \
502 	do { free(pathw); } while(0);
503 
504 #else
505 	zend_stat_t st = {0};
506 #endif
507 	realpath_cache_bucket *bucket;
508 	char *tmp;
509 	ALLOCA_FLAG(use_heap)
510 
511 	while (1) {
512 		if (len <= start) {
513 			if (link_is_dir) {
514 				*link_is_dir = 1;
515 			}
516 			return start;
517 		}
518 
519 		i = len;
520 		while (i > start && !IS_SLASH(path[i-1])) {
521 			i--;
522 		}
523 		assert(i < MAXPATHLEN);
524 
525 		if (i == len ||
526 			(i + 1 == len && path[i] == '.')) {
527 			/* remove double slashes and '.' */
528 			len = EXPECTED(i > 0) ? i - 1 : 0;
529 			is_dir = 1;
530 			continue;
531 		} else if (i + 2 == len && path[i] == '.' && path[i+1] == '.') {
532 			/* remove '..' and previous directory */
533 			is_dir = 1;
534 			if (link_is_dir) {
535 				*link_is_dir = 1;
536 			}
537 			if (i <= start + 1) {
538 				return start ? start : len;
539 			}
540 			j = tsrm_realpath_r(path, start, i-1, ll, t, use_realpath, 1, NULL);
541 			if (j > start && j != (size_t)-1) {
542 				j--;
543 				assert(i < MAXPATHLEN);
544 				while (j > start && !IS_SLASH(path[j])) {
545 					j--;
546 				}
547 				assert(i < MAXPATHLEN);
548 				if (!start) {
549 					/* leading '..' must not be removed in case of relative path */
550 					if (j == 0 && path[0] == '.' && path[1] == '.' &&
551 							IS_SLASH(path[2])) {
552 						path[3] = '.';
553 						path[4] = '.';
554 						path[5] = DEFAULT_SLASH;
555 						j = 5;
556 					} else if (j > 0 &&
557 							path[j+1] == '.' && path[j+2] == '.' &&
558 							IS_SLASH(path[j+3])) {
559 						j += 4;
560 						path[j++] = '.';
561 						path[j++] = '.';
562 						path[j] = DEFAULT_SLASH;
563 					}
564 				}
565 			} else if (!start && !j) {
566 				/* leading '..' must not be removed in case of relative path */
567 				path[0] = '.';
568 				path[1] = '.';
569 				path[2] = DEFAULT_SLASH;
570 				j = 2;
571 			}
572 			return j;
573 		}
574 
575 		path[len] = 0;
576 
577 		save = (use_realpath != CWD_EXPAND);
578 
579 		if (start && save && CWDG(realpath_cache_size_limit)) {
580 			/* cache lookup for absolute path */
581 			if (!*t) {
582 				*t = time(0);
583 			}
584 			if ((bucket = realpath_cache_find(path, len, *t)) != NULL) {
585 				if (is_dir && !bucket->is_dir) {
586 					/* not a directory */
587 					return (size_t)-1;
588 				} else {
589 					if (link_is_dir) {
590 						*link_is_dir = bucket->is_dir;
591 					}
592 					memcpy(path, bucket->realpath, bucket->realpath_len + 1);
593 					return bucket->realpath_len;
594 				}
595 			}
596 		}
597 
598 #ifdef ZEND_WIN32
599 retry_reparse_point:
600 		may_retry_reparse_point = 0;
601 		if (save) {
602 			pathw = php_win32_ioutil_any_to_w(path);
603 			if (!pathw) {
604 				return (size_t)-1;
605 			}
606 			PHP_WIN32_IOUTIL_CHECK_PATH_W(pathw, (size_t)-1, 1);
607 			hFind = FindFirstFileExW(pathw, FindExInfoBasic, &dataw, FindExSearchNameMatch, NULL, 0);
608 			if (INVALID_HANDLE_VALUE == hFind) {
609 				if (use_realpath == CWD_REALPATH) {
610 					/* file not found */
611 					FREE_PATHW()
612 					return (size_t)-1;
613 				}
614 				/* continue resolution anyway but don't save result in the cache */
615 				save = 0;
616 			} else {
617 				FindClose(hFind);
618 			}
619 		}
620 
621 		tmp = do_alloca(len+1, use_heap);
622 		memcpy(tmp, path, len+1);
623 
624 retry_reparse_tag_cloud:
625 		if(save &&
626 				!(IS_UNC_PATH(path, len) && len >= 3 && path[2] != '?') &&
627                                (dataw.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)
628 				) {
629 			/* File is a reparse point. Get the target */
630 			HANDLE hLink = NULL;
631 			PHP_WIN32_IOUTIL_REPARSE_DATA_BUFFER * pbuffer;
632 			DWORD retlength = 0;
633 			size_t bufindex = 0;
634 			uint8_t isabsolute = 0;
635 			wchar_t * reparsetarget;
636 			BOOL isVolume = FALSE;
637 #if VIRTUAL_CWD_DEBUG
638 			char *printname = NULL;
639 #endif
640 			char *substitutename = NULL;
641 			size_t substitutename_len;
642 			size_t substitutename_off = 0;
643 			wchar_t tmpsubstname[MAXPATHLEN];
644 
645 			if(++(*ll) > LINK_MAX) {
646 				free_alloca(tmp, use_heap);
647 				FREE_PATHW()
648 				return (size_t)-1;
649 			}
650 
651 			hLink = CreateFileW(pathw,
652 					0,
653 					PHP_WIN32_IOUTIL_DEFAULT_SHARE_MODE,
654 					NULL,
655 					OPEN_EXISTING,
656 					FILE_FLAG_OPEN_REPARSE_POINT|FILE_FLAG_BACKUP_SEMANTICS,
657 					NULL);
658 			if(hLink == INVALID_HANDLE_VALUE) {
659 				free_alloca(tmp, use_heap);
660 				FREE_PATHW()
661 				return (size_t)-1;
662 			}
663 
664 			pbuffer = (PHP_WIN32_IOUTIL_REPARSE_DATA_BUFFER *)do_alloca(MAXIMUM_REPARSE_DATA_BUFFER_SIZE, use_heap_large);
665 			if (pbuffer == NULL) {
666 				CloseHandle(hLink);
667 				free_alloca(tmp, use_heap);
668 				FREE_PATHW()
669 				return (size_t)-1;
670 			}
671 			if(!DeviceIoControl(hLink, FSCTL_GET_REPARSE_POINT, NULL, 0, pbuffer,  MAXIMUM_REPARSE_DATA_BUFFER_SIZE, &retlength, NULL)) {
672 				BY_HANDLE_FILE_INFORMATION fileInformation;
673 
674 				free_alloca(pbuffer, use_heap_large);
675 				if ((dataw.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) &&
676 						(dataw.dwReserved0 & ~IO_REPARSE_TAG_CLOUD_MASK) == IO_REPARSE_TAG_CLOUD &&
677 						EG(windows_version_info).dwMajorVersion >= 10 &&
678 						EG(windows_version_info).dwMinorVersion == 0 &&
679 						EG(windows_version_info).dwBuildNumber >= 18362 &&
680 						GetFileInformationByHandle(hLink, &fileInformation) &&
681 						!(fileInformation.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)) {
682 					dataw.dwFileAttributes = fileInformation.dwFileAttributes;
683 					CloseHandle(hLink);
684 					(*ll)--;
685 					goto retry_reparse_tag_cloud;
686 				}
687 				free_alloca(tmp, use_heap);
688 				CloseHandle(hLink);
689 				FREE_PATHW()
690 				return (size_t)-1;
691 			}
692 
693 			CloseHandle(hLink);
694 
695 			if(pbuffer->ReparseTag == IO_REPARSE_TAG_SYMLINK) {
696 				may_retry_reparse_point = 1;
697 				reparsetarget = pbuffer->SymbolicLinkReparseBuffer.ReparseTarget;
698 				isabsolute = pbuffer->SymbolicLinkReparseBuffer.Flags == 0;
699 #if VIRTUAL_CWD_DEBUG
700 				printname = php_win32_ioutil_w_to_any(reparsetarget + pbuffer->MountPointReparseBuffer.PrintNameOffset  / sizeof(WCHAR));
701 				if (!printname) {
702 					free_alloca(pbuffer, use_heap_large);
703 					free_alloca(tmp, use_heap);
704 					FREE_PATHW()
705 					return (size_t)-1;
706 				}
707 #endif
708 
709 				substitutename_len = pbuffer->MountPointReparseBuffer.SubstituteNameLength / sizeof(WCHAR);
710 				if (substitutename_len >= MAXPATHLEN) {
711 					free_alloca(pbuffer, use_heap_large);
712 					free_alloca(tmp, use_heap);
713 					FREE_PATHW()
714 					return (size_t)-1;
715 				}
716 				memmove(tmpsubstname, reparsetarget + pbuffer->MountPointReparseBuffer.SubstituteNameOffset / sizeof(WCHAR), pbuffer->MountPointReparseBuffer.SubstituteNameLength);
717 				tmpsubstname[substitutename_len] = L'\0';
718 				substitutename = php_win32_cp_conv_w_to_any(tmpsubstname, substitutename_len, &substitutename_len);
719 				if (!substitutename || substitutename_len >= MAXPATHLEN) {
720 					free_alloca(pbuffer, use_heap_large);
721 					free_alloca(tmp, use_heap);
722 					free(substitutename);
723 #if VIRTUAL_CWD_DEBUG
724 					free(printname);
725 #endif
726 					FREE_PATHW()
727 					return (size_t)-1;
728 				}
729 			}
730 			else if(pbuffer->ReparseTag == IO_REPARSE_TAG_MOUNT_POINT) {
731 				isabsolute = 1;
732 				reparsetarget = pbuffer->MountPointReparseBuffer.ReparseTarget;
733 #if VIRTUAL_CWD_DEBUG
734 				printname = php_win32_ioutil_w_to_any(reparsetarget + pbuffer->MountPointReparseBuffer.PrintNameOffset  / sizeof(WCHAR));
735 				if (!printname) {
736 					free_alloca(pbuffer, use_heap_large);
737 					free_alloca(tmp, use_heap);
738 					FREE_PATHW()
739 					return (size_t)-1;
740 				}
741 #endif
742 
743 
744 				substitutename_len = pbuffer->MountPointReparseBuffer.SubstituteNameLength / sizeof(WCHAR);
745 				if (substitutename_len >= MAXPATHLEN) {
746 					free_alloca(pbuffer, use_heap_large);
747 					free_alloca(tmp, use_heap);
748 					FREE_PATHW()
749 					return (size_t)-1;
750 				}
751 				memmove(tmpsubstname, reparsetarget + pbuffer->MountPointReparseBuffer.SubstituteNameOffset / sizeof(WCHAR), pbuffer->MountPointReparseBuffer.SubstituteNameLength);
752 				tmpsubstname[substitutename_len] = L'\0';
753 				substitutename = php_win32_cp_conv_w_to_any(tmpsubstname, substitutename_len, &substitutename_len);
754 				if (!substitutename || substitutename_len >= MAXPATHLEN) {
755 					free_alloca(pbuffer, use_heap_large);
756 					free_alloca(tmp, use_heap);
757 					free(substitutename);
758 #if VIRTUAL_CWD_DEBUG
759 					free(printname);
760 #endif
761 					FREE_PATHW()
762 					return (size_t)-1;
763 				}
764 			}
765 			else if (pbuffer->ReparseTag == IO_REPARSE_TAG_DEDUP ||
766 					/* Starting with 1709. */
767 					(pbuffer->ReparseTag & ~IO_REPARSE_TAG_CLOUD_MASK) == IO_REPARSE_TAG_CLOUD ||
768 					IO_REPARSE_TAG_ONEDRIVE == pbuffer->ReparseTag ||
769 					IO_REPARSE_TAG_ACTIVISION_HSM == pbuffer->ReparseTag ||
770 					IO_REPARSE_TAG_PROJFS == pbuffer->ReparseTag) {
771 				isabsolute = 1;
772 				substitutename = malloc((len + 1) * sizeof(char));
773 				if (!substitutename) {
774 					free_alloca(pbuffer, use_heap_large);
775 					free_alloca(tmp, use_heap);
776 					FREE_PATHW()
777 					return (size_t)-1;
778 				}
779 				memcpy(substitutename, path, len + 1);
780 				substitutename_len = len;
781 			} else {
782 				/* XXX this might be not the end, restart handling with REPARSE_GUID_DATA_BUFFER should be implemented. */
783 				free_alloca(pbuffer, use_heap_large);
784 				free_alloca(tmp, use_heap);
785 				FREE_PATHW()
786 				return (size_t)-1;
787 			}
788 
789 			if(isabsolute && substitutename_len > 4) {
790 				/* Do not resolve volumes (for now). A mounted point can
791 				   target a volume without a drive, it is not certain that
792 				   all IO functions we use in php and its deps support
793 				   path with volume GUID instead of the DOS way, like:
794 				   d:\test\mnt\foo
795 				   \\?\Volume{62d1c3f8-83b9-11de-b108-806e6f6e6963}\foo
796 				*/
797 				if (strncmp(substitutename, "\\??\\Volume{",11) == 0
798 					|| strncmp(substitutename, "\\\\?\\Volume{",11) == 0
799 					|| strncmp(substitutename, "\\??\\UNC\\", 8) == 0
800 					) {
801 					isVolume = TRUE;
802 					substitutename_off = 0;
803 				} else
804 					/* do not use the \??\ and \\?\ prefix*/
805 					if (strncmp(substitutename, "\\??\\", 4) == 0
806 						|| strncmp(substitutename, "\\\\?\\", 4) == 0) {
807 					substitutename_off = 4;
808 				}
809 			}
810 
811 			if (!isVolume) {
812 				char * tmp2 = substitutename + substitutename_off;
813 				for (bufindex = 0; bufindex + substitutename_off < substitutename_len; bufindex++) {
814 					*(path + bufindex) = *(tmp2 + bufindex);
815 				}
816 
817 				*(path + bufindex) = 0;
818 				j = bufindex;
819 			} else {
820 				j = len;
821 			}
822 
823 
824 #if VIRTUAL_CWD_DEBUG
825 			fprintf(stderr, "reparse: print: %s ", printname);
826 			fprintf(stderr, "sub: %s ", substitutename);
827 			fprintf(stderr, "resolved: %s ", path);
828 			free(printname);
829 #endif
830 			free_alloca(pbuffer, use_heap_large);
831 			free(substitutename);
832 
833 			if (may_retry_reparse_point) {
834 				DWORD attrs;
835 
836 				FREE_PATHW()
837 				pathw = php_win32_ioutil_any_to_w(path);
838 				if (!pathw) {
839 					return (size_t)-1;
840 				}
841 				attrs = GetFileAttributesW(pathw);
842 				if (!isVolume && attrs != INVALID_FILE_ATTRIBUTES && (attrs & FILE_ATTRIBUTE_REPARSE_POINT)) {
843 					free_alloca(tmp, use_heap);
844 					FREE_PATHW()
845 					goto retry_reparse_point;
846 				}
847 			}
848 
849 			if(isabsolute == 1) {
850 				if (!((j == 3) && (path[1] == ':') && (path[2] == '\\'))) {
851 					/* use_realpath is 0 in the call below coz path is absolute*/
852 					j = tsrm_realpath_r(path, 0, j, ll, t, 0, is_dir, &directory);
853 					if(j == (size_t)-1) {
854 						free_alloca(tmp, use_heap);
855 						FREE_PATHW()
856 						return (size_t)-1;
857 					}
858 				}
859 			}
860 			else {
861 				if(i + j >= MAXPATHLEN - 1) {
862 					free_alloca(tmp, use_heap);
863 					FREE_PATHW()
864 					return (size_t)-1;
865 				}
866 
867 				memmove(path+i, path, j+1);
868 				memcpy(path, tmp, i-1);
869 				path[i-1] = DEFAULT_SLASH;
870 				j  = tsrm_realpath_r(path, start, i + j, ll, t, use_realpath, is_dir, &directory);
871 				if(j == (size_t)-1) {
872 					free_alloca(tmp, use_heap);
873 					FREE_PATHW()
874 					return (size_t)-1;
875 				}
876 			}
877 			directory = (dataw.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY);
878 
879 			if(link_is_dir) {
880 				*link_is_dir = directory;
881 			}
882 		}
883 		else {
884 			if (save) {
885 				directory = (dataw.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
886 				if (is_dir && !directory) {
887 					/* not a directory */
888 					free_alloca(tmp, use_heap);
889 					FREE_PATHW()
890 					return (size_t)-1;
891 				}
892 			}
893 #else
894 		if (save && php_sys_lstat(path, &st) < 0) {
895 			if (use_realpath == CWD_REALPATH) {
896 				/* file not found */
897 				return (size_t)-1;
898 			}
899 			/* continue resolution anyway but don't save result in the cache */
900 			save = 0;
901 		}
902 
903 		tmp = do_alloca(len+1, use_heap);
904 		memcpy(tmp, path, len+1);
905 
906 		if (save && S_ISLNK(st.st_mode)) {
907 			if (++(*ll) > LINK_MAX || (j = (size_t)php_sys_readlink(tmp, path, MAXPATHLEN)) == (size_t)-1) {
908 				/* too many links or broken symlinks */
909 				free_alloca(tmp, use_heap);
910 				return (size_t)-1;
911 			}
912 			path[j] = 0;
913 			if (IS_ABSOLUTE_PATH(path, j)) {
914 				j = tsrm_realpath_r(path, 1, j, ll, t, use_realpath, is_dir, &directory);
915 				if (j == (size_t)-1) {
916 					free_alloca(tmp, use_heap);
917 					return (size_t)-1;
918 				}
919 			} else {
920 				if (i + j >= MAXPATHLEN-1) {
921 					free_alloca(tmp, use_heap);
922 					return (size_t)-1; /* buffer overflow */
923 				}
924 				memmove(path+i, path, j+1);
925 				memcpy(path, tmp, i-1);
926 				path[i-1] = DEFAULT_SLASH;
927 				j = tsrm_realpath_r(path, start, i + j, ll, t, use_realpath, is_dir, &directory);
928 				if (j == (size_t)-1) {
929 					free_alloca(tmp, use_heap);
930 					return (size_t)-1;
931 				}
932 			}
933 			if (link_is_dir) {
934 				*link_is_dir = directory;
935 			}
936 		} else {
937 			if (save) {
938 				directory = S_ISDIR(st.st_mode);
939 				if (link_is_dir) {
940 					*link_is_dir = directory;
941 				}
942 				if (is_dir && !directory) {
943 					/* not a directory */
944 					free_alloca(tmp, use_heap);
945 					return (size_t)-1;
946 				}
947 			}
948 #endif
949 			if (i <= start + 1) {
950 				j = start;
951 			} else {
952 				/* some leading directories may be inaccessible */
953 				j = tsrm_realpath_r(path, start, i-1, ll, t, save ? CWD_FILEPATH : use_realpath, 1, NULL);
954 				if (j > start && j != (size_t)-1) {
955 					path[j++] = DEFAULT_SLASH;
956 				}
957 			}
958 #ifdef ZEND_WIN32
959 			if (j == (size_t)-1 || j + len >= MAXPATHLEN - 1 + i) {
960 				free_alloca(tmp, use_heap);
961 				FREE_PATHW()
962 				return (size_t)-1;
963 			}
964 			if (save) {
965 				size_t sz;
966 				char *tmp_path = php_win32_ioutil_conv_w_to_any(dataw.cFileName, PHP_WIN32_CP_IGNORE_LEN, &sz);
967 				if (!tmp_path) {
968 					free_alloca(tmp, use_heap);
969 					FREE_PATHW()
970 					return (size_t)-1;
971 				}
972 				i = sz;
973 				memcpy(path+j, tmp_path, i+1);
974 				free(tmp_path);
975 				j += i;
976 			} else {
977 				/* use the original file or directory name as it wasn't found */
978 				memcpy(path+j, tmp+i, len-i+1);
979 				j += (len-i);
980 			}
981 		}
982 #else
983 			if (j == (size_t)-1 || j + len >= MAXPATHLEN - 1 + i) {
984 				free_alloca(tmp, use_heap);
985 				return (size_t)-1;
986 			}
987 			memcpy(path+j, tmp+i, len-i+1);
988 			j += (len-i);
989 		}
990 #endif
991 
992 		if (save && start && CWDG(realpath_cache_size_limit)) {
993 			/* save absolute path in the cache */
994 			realpath_cache_add(tmp, len, path, j, directory, *t);
995 		}
996 
997 		free_alloca(tmp, use_heap);
998 #ifdef ZEND_WIN32
999 		FREE_PATHW()
1000 #undef FREE_PATHW
1001 #endif
1002 		return j;
1003 	}
1004 }
1005 /* }}} */
1006 
1007 /* Resolve path relatively to state and put the real path into state */
1008 /* returns 0 for ok, 1 for error, -1 if (path_length >= MAXPATHLEN-1) */
virtual_file_ex(cwd_state * state,const char * path,verify_path_func verify_path,int use_realpath)1009 CWD_API int virtual_file_ex(cwd_state *state, const char *path, verify_path_func verify_path, int use_realpath) /* {{{ */
1010 {
1011 	size_t path_length = strlen(path);
1012 	char resolved_path[MAXPATHLEN];
1013 	size_t start = 1;
1014 	int ll = 0;
1015 	time_t t;
1016 	int ret;
1017 	bool add_slash;
1018 	void *tmp;
1019 
1020 	if (!path_length || path_length >= MAXPATHLEN-1) {
1021 #ifdef ZEND_WIN32
1022 		SET_ERRNO_FROM_WIN32_CODE(ERROR_INVALID_PARAMETER);
1023 #else
1024 		errno = EINVAL;
1025 #endif
1026 		return 1;
1027 	}
1028 
1029 #if VIRTUAL_CWD_DEBUG
1030 	fprintf(stderr,"cwd = %s path = %s\n", state->cwd, path);
1031 #endif
1032 
1033 	/* cwd_length can be 0 when getcwd() fails.
1034 	 * This can happen under solaris when a dir does not have read permissions
1035 	 * but *does* have execute permissions */
1036 	if (!IS_ABSOLUTE_PATH(path, path_length)) {
1037 		if (state->cwd_length == 0) {
1038 			/* resolve relative path */
1039 			start = 0;
1040 			memcpy(resolved_path , path, path_length + 1);
1041 		} else {
1042 			size_t state_cwd_length = state->cwd_length;
1043 
1044 #ifdef ZEND_WIN32
1045 			if (IS_SLASH(path[0])) {
1046 				if (state->cwd[1] == ':') {
1047 					/* Copy only the drive name */
1048 					state_cwd_length = 2;
1049 				} else if (IS_UNC_PATH(state->cwd, state->cwd_length)) {
1050 					/* Copy only the share name */
1051 					state_cwd_length = 2;
1052 					while (IS_SLASH(state->cwd[state_cwd_length])) {
1053 						state_cwd_length++;
1054 					}
1055 					while (state->cwd[state_cwd_length] &&
1056 							!IS_SLASH(state->cwd[state_cwd_length])) {
1057 						state_cwd_length++;
1058 					}
1059 					while (IS_SLASH(state->cwd[state_cwd_length])) {
1060 						state_cwd_length++;
1061 					}
1062 					while (state->cwd[state_cwd_length] &&
1063 							!IS_SLASH(state->cwd[state_cwd_length])) {
1064 						state_cwd_length++;
1065 					}
1066 				}
1067 			}
1068 #endif
1069 			if (path_length + state_cwd_length + 1 >= MAXPATHLEN-1) {
1070 #ifdef ZEND_WIN32
1071 				SET_ERRNO_FROM_WIN32_CODE(ERROR_BUFFER_OVERFLOW);
1072 #else
1073 				errno = ENAMETOOLONG;
1074 #endif
1075 				return 1;
1076 			}
1077 			memcpy(resolved_path, state->cwd, state_cwd_length);
1078 			if (resolved_path[state_cwd_length-1] == DEFAULT_SLASH) {
1079 				memcpy(resolved_path + state_cwd_length, path, path_length + 1);
1080 				path_length += state_cwd_length;
1081 			} else {
1082 				resolved_path[state_cwd_length] = DEFAULT_SLASH;
1083 				memcpy(resolved_path + state_cwd_length + 1, path, path_length + 1);
1084 				path_length += state_cwd_length + 1;
1085 			}
1086 		}
1087 	} else {
1088 #ifdef ZEND_WIN32
1089 		if (path_length > 2 && path[1] == ':' && !IS_SLASH(path[2])) {
1090 			resolved_path[0] = path[0];
1091 			resolved_path[1] = ':';
1092 			resolved_path[2] = DEFAULT_SLASH;
1093 			memcpy(resolved_path + 3, path + 2, path_length - 1);
1094 			path_length++;
1095 		} else
1096 #endif
1097 		memcpy(resolved_path, path, path_length + 1);
1098 	}
1099 
1100 #ifdef ZEND_WIN32
1101 	if (memchr(resolved_path, '*', path_length) ||
1102 		memchr(resolved_path, '?', path_length)) {
1103 		SET_ERRNO_FROM_WIN32_CODE(ERROR_INVALID_NAME);
1104 		return 1;
1105 	}
1106 #endif
1107 
1108 #ifdef ZEND_WIN32
1109 	if (IS_UNC_PATH(resolved_path, path_length)) {
1110 		/* skip UNC name */
1111 		resolved_path[0] = DEFAULT_SLASH;
1112 		resolved_path[1] = DEFAULT_SLASH;
1113 		start = 2;
1114 		while (!IS_SLASH(resolved_path[start])) {
1115 			if (resolved_path[start] == 0) {
1116 				goto verify;
1117 			}
1118 			resolved_path[start] = toupper(resolved_path[start]);
1119 			start++;
1120 		}
1121 		resolved_path[start++] = DEFAULT_SLASH;
1122 		while (!IS_SLASH(resolved_path[start])) {
1123 			if (resolved_path[start] == 0) {
1124 				goto verify;
1125 			}
1126 			resolved_path[start] = toupper(resolved_path[start]);
1127 			start++;
1128 		}
1129 		resolved_path[start++] = DEFAULT_SLASH;
1130 	} else if (IS_ABSOLUTE_PATH(resolved_path, path_length)) {
1131 		/* skip DRIVE name */
1132 		resolved_path[0] = toupper(resolved_path[0]);
1133 		resolved_path[2] = DEFAULT_SLASH;
1134 		if (path_length == 2) {
1135 			resolved_path[3] = '\0';
1136 		}
1137 		start = 3;
1138 	}
1139 #endif
1140 
1141 	add_slash = (use_realpath != CWD_REALPATH) && path_length > 0 && IS_SLASH(resolved_path[path_length-1]);
1142 	t = CWDG(realpath_cache_ttl) ? 0 : -1;
1143 	path_length = tsrm_realpath_r(resolved_path, start, path_length, &ll, &t, use_realpath, 0, NULL);
1144 
1145 	if (path_length == (size_t)-1) {
1146 #ifdef ZEND_WIN32
1147 		if (errno != EACCES) {
1148 			errno = ENOENT;
1149 		}
1150 #else
1151 		errno = ENOENT;
1152 #endif
1153 		return 1;
1154 	}
1155 
1156 	if (!start && !path_length) {
1157 		resolved_path[path_length++] = '.';
1158 	}
1159 
1160 	if (add_slash && path_length && !IS_SLASH(resolved_path[path_length-1])) {
1161 		if (path_length >= MAXPATHLEN-1) {
1162 			return -1;
1163 		}
1164 		resolved_path[path_length++] = DEFAULT_SLASH;
1165 	}
1166 	resolved_path[path_length] = 0;
1167 
1168 #ifdef ZEND_WIN32
1169 verify:
1170 #endif
1171 	if (verify_path) {
1172 		cwd_state old_state;
1173 
1174 		CWD_STATE_COPY(&old_state, state);
1175 		state->cwd_length = path_length;
1176 
1177 		tmp = erealloc(state->cwd, state->cwd_length+1);
1178 		state->cwd = (char *) tmp;
1179 
1180 		memcpy(state->cwd, resolved_path, state->cwd_length+1);
1181 		if (verify_path(state)) {
1182 			CWD_STATE_FREE(state);
1183 			*state = old_state;
1184 			ret = 1;
1185 		} else {
1186 			CWD_STATE_FREE(&old_state);
1187 			ret = 0;
1188 		}
1189 	} else {
1190 		state->cwd_length = path_length;
1191 		tmp = erealloc(state->cwd, state->cwd_length+1);
1192 		state->cwd = (char *) tmp;
1193 
1194 		memcpy(state->cwd, resolved_path, state->cwd_length+1);
1195 		ret = 0;
1196 	}
1197 
1198 #if VIRTUAL_CWD_DEBUG
1199 	fprintf (stderr, "virtual_file_ex() = %s\n",state->cwd);
1200 #endif
1201 	return (ret);
1202 }
1203 /* }}} */
1204 
virtual_chdir(const char * path)1205 CWD_API zend_result virtual_chdir(const char *path) /* {{{ */
1206 {
1207 	return virtual_file_ex(&CWDG(cwd), path, php_is_dir_ok, CWD_REALPATH) ? FAILURE : SUCCESS;
1208 }
1209 /* }}} */
1210 
1211 
1212 /* returns 0 for ok, 1 for empty string, -1 on error */
virtual_chdir_file(const char * path,int (* p_chdir)(const char * path))1213 CWD_API int virtual_chdir_file(const char *path, int (*p_chdir)(const char *path)) /* {{{ */
1214 {
1215 	size_t length = strlen(path);
1216 	char *temp;
1217 	int retval;
1218 	ALLOCA_FLAG(use_heap)
1219 
1220 	if (length == 0) {
1221 		return 1; /* Can't cd to empty string */
1222 	}
1223 	while(--length < SIZE_MAX && !IS_SLASH(path[length])) {
1224 	}
1225 
1226 	if (length == SIZE_MAX) {
1227 		/* No directory only file name */
1228 		errno = ENOENT;
1229 		return -1;
1230 	}
1231 
1232 	if (length == COPY_WHEN_ABSOLUTE(path) && IS_ABSOLUTE_PATH(path, length+1)) { /* Also use trailing slash if this is absolute */
1233 		length++;
1234 	}
1235 	temp = (char *) do_alloca(length+1, use_heap);
1236 	memcpy(temp, path, length);
1237 	temp[length] = 0;
1238 #if VIRTUAL_CWD_DEBUG
1239 	fprintf (stderr, "Changing directory to %s\n", temp);
1240 #endif
1241 	retval = p_chdir(temp);
1242 	free_alloca(temp, use_heap);
1243 	return retval;
1244 }
1245 /* }}} */
1246 
virtual_realpath(const char * path,char * real_path)1247 CWD_API char *virtual_realpath(const char *path, char *real_path) /* {{{ */
1248 {
1249 	cwd_state new_state;
1250 	char *retval;
1251 	char cwd[MAXPATHLEN];
1252 
1253 	/* realpath("") returns CWD */
1254 	if (!*path) {
1255 		new_state.cwd = (char*)emalloc(1);
1256 		new_state.cwd[0] = '\0';
1257 		new_state.cwd_length = 0;
1258 		if (VCWD_GETCWD(cwd, MAXPATHLEN)) {
1259 			path = cwd;
1260 		}
1261 	} else if (!IS_ABSOLUTE_PATH(path, strlen(path))) {
1262 		CWD_STATE_COPY(&new_state, &CWDG(cwd));
1263 	} else {
1264 		new_state.cwd = (char*)emalloc(1);
1265 		new_state.cwd[0] = '\0';
1266 		new_state.cwd_length = 0;
1267 	}
1268 
1269 	if (virtual_file_ex(&new_state, path, NULL, CWD_REALPATH)==0) {
1270 		size_t len = new_state.cwd_length>MAXPATHLEN-1?MAXPATHLEN-1:new_state.cwd_length;
1271 
1272 		memcpy(real_path, new_state.cwd, len);
1273 		real_path[len] = '\0';
1274 		retval = real_path;
1275 	} else {
1276 		retval = NULL;
1277 	}
1278 
1279 	CWD_STATE_FREE(&new_state);
1280 	return retval;
1281 }
1282 /* }}} */
1283 
1284 /* returns 0 for ok, 1 for error, -1 if (path_length >= MAXPATHLEN-1) */
virtual_filepath_ex(const char * path,char ** filepath,verify_path_func verify_path)1285 CWD_API int virtual_filepath_ex(const char *path, char **filepath, verify_path_func verify_path) /* {{{ */
1286 {
1287 	cwd_state new_state;
1288 	int retval;
1289 
1290 	CWD_STATE_COPY(&new_state, &CWDG(cwd));
1291 	retval = virtual_file_ex(&new_state, path, verify_path, CWD_FILEPATH);
1292 
1293 	*filepath = new_state.cwd;
1294 
1295 	return retval;
1296 
1297 }
1298 /* }}} */
1299 
1300 /* returns 0 for ok, 1 for error, -1 if (path_length >= MAXPATHLEN-1) */
virtual_filepath(const char * path,char ** filepath)1301 CWD_API int virtual_filepath(const char *path, char **filepath) /* {{{ */
1302 {
1303 	return virtual_filepath_ex(path, filepath, php_is_file_ok);
1304 }
1305 /* }}} */
1306 
virtual_fopen(const char * path,const char * mode)1307 CWD_API FILE *virtual_fopen(const char *path, const char *mode) /* {{{ */
1308 {
1309 	cwd_state new_state;
1310 	FILE *f;
1311 
1312 	if (path[0] == '\0') { /* Fail to open empty path */
1313 		return NULL;
1314 	}
1315 
1316 	CWD_STATE_COPY(&new_state, &CWDG(cwd));
1317 	if (virtual_file_ex(&new_state, path, NULL, CWD_EXPAND)) {
1318 		CWD_STATE_FREE_ERR(&new_state);
1319 		return NULL;
1320 	}
1321 
1322 #ifdef ZEND_WIN32
1323 	f = php_win32_ioutil_fopen(new_state.cwd, mode);
1324 #else
1325 	f = fopen(new_state.cwd, mode);
1326 #endif
1327 
1328 	CWD_STATE_FREE_ERR(&new_state);
1329 
1330 	return f;
1331 }
1332 /* }}} */
1333 
virtual_access(const char * pathname,int mode)1334 CWD_API int virtual_access(const char *pathname, int mode) /* {{{ */
1335 {
1336 	cwd_state new_state;
1337 	int ret;
1338 
1339 	CWD_STATE_COPY(&new_state, &CWDG(cwd));
1340 	if (virtual_file_ex(&new_state, pathname, NULL, CWD_REALPATH)) {
1341 		CWD_STATE_FREE_ERR(&new_state);
1342 		return -1;
1343 	}
1344 
1345 #if defined(ZEND_WIN32)
1346 	ret = tsrm_win32_access(new_state.cwd, mode);
1347 #else
1348 	ret = access(new_state.cwd, mode);
1349 #endif
1350 
1351 	CWD_STATE_FREE_ERR(&new_state);
1352 
1353 	return ret;
1354 }
1355 /* }}} */
1356 
1357 #if HAVE_UTIME
virtual_utime(const char * filename,struct utimbuf * buf)1358 CWD_API int virtual_utime(const char *filename, struct utimbuf *buf) /* {{{ */
1359 {
1360 	cwd_state new_state;
1361 	int ret;
1362 
1363 	CWD_STATE_COPY(&new_state, &CWDG(cwd));
1364 	if (virtual_file_ex(&new_state, filename, NULL, CWD_REALPATH)) {
1365 		CWD_STATE_FREE_ERR(&new_state);
1366 		return -1;
1367 	}
1368 
1369 #ifdef ZEND_WIN32
1370 	ret = win32_utime(new_state.cwd, buf);
1371 #else
1372 	ret = utime(new_state.cwd, buf);
1373 #endif
1374 
1375 	CWD_STATE_FREE_ERR(&new_state);
1376 	return ret;
1377 }
1378 /* }}} */
1379 #endif
1380 
virtual_chmod(const char * filename,mode_t mode)1381 CWD_API int virtual_chmod(const char *filename, mode_t mode) /* {{{ */
1382 {
1383 	cwd_state new_state;
1384 	int ret;
1385 
1386 	CWD_STATE_COPY(&new_state, &CWDG(cwd));
1387 	if (virtual_file_ex(&new_state, filename, NULL, CWD_REALPATH)) {
1388 		CWD_STATE_FREE_ERR(&new_state);
1389 		return -1;
1390 	}
1391 
1392 #ifdef ZEND_WIN32
1393 	{
1394 		mode_t _tmp = mode;
1395 
1396 		mode = 0;
1397 
1398 		if (_tmp & _S_IREAD) {
1399 			mode |= _S_IREAD;
1400 		}
1401 		if (_tmp & _S_IWRITE) {
1402 			mode |= _S_IWRITE;
1403 		}
1404 		ret = php_win32_ioutil_chmod(new_state.cwd, mode);
1405 	}
1406 #else
1407 	ret = chmod(new_state.cwd, mode);
1408 #endif
1409 
1410 	CWD_STATE_FREE_ERR(&new_state);
1411 	return ret;
1412 }
1413 /* }}} */
1414 
1415 #if !defined(ZEND_WIN32)
virtual_chown(const char * filename,uid_t owner,gid_t group,int link)1416 CWD_API int virtual_chown(const char *filename, uid_t owner, gid_t group, int link) /* {{{ */
1417 {
1418 	cwd_state new_state;
1419 	int ret;
1420 
1421 	CWD_STATE_COPY(&new_state, &CWDG(cwd));
1422 	if (virtual_file_ex(&new_state, filename, NULL, CWD_REALPATH)) {
1423 		CWD_STATE_FREE_ERR(&new_state);
1424 		return -1;
1425 	}
1426 
1427 	if (link) {
1428 #if HAVE_LCHOWN
1429 		ret = lchown(new_state.cwd, owner, group);
1430 #else
1431 		ret = -1;
1432 #endif
1433 	} else {
1434 		ret = chown(new_state.cwd, owner, group);
1435 	}
1436 
1437 	CWD_STATE_FREE_ERR(&new_state);
1438 	return ret;
1439 }
1440 /* }}} */
1441 #endif
1442 
virtual_open(const char * path,int flags,...)1443 CWD_API int virtual_open(const char *path, int flags, ...) /* {{{ */
1444 {
1445 	cwd_state new_state;
1446 	int f;
1447 
1448 	CWD_STATE_COPY(&new_state, &CWDG(cwd));
1449 	if (virtual_file_ex(&new_state, path, NULL, CWD_FILEPATH)) {
1450 		CWD_STATE_FREE_ERR(&new_state);
1451 		return -1;
1452 	}
1453 
1454 	if (flags & O_CREAT) {
1455 		mode_t mode;
1456 		va_list arg;
1457 
1458 		va_start(arg, flags);
1459 		mode = (mode_t) va_arg(arg, int);
1460 		va_end(arg);
1461 
1462 #ifdef ZEND_WIN32
1463 		f = php_win32_ioutil_open(new_state.cwd, flags, mode);
1464 #else
1465 		f = open(new_state.cwd, flags, mode);
1466 #endif
1467 	} else {
1468 #ifdef ZEND_WIN32
1469 		f = php_win32_ioutil_open(new_state.cwd, flags);
1470 #else
1471 		f = open(new_state.cwd, flags);
1472 #endif
1473 	}
1474 	CWD_STATE_FREE_ERR(&new_state);
1475 	return f;
1476 }
1477 /* }}} */
1478 
virtual_creat(const char * path,mode_t mode)1479 CWD_API int virtual_creat(const char *path, mode_t mode) /* {{{ */
1480 {
1481 	cwd_state new_state;
1482 	int f;
1483 
1484 	CWD_STATE_COPY(&new_state, &CWDG(cwd));
1485 	if (virtual_file_ex(&new_state, path, NULL, CWD_FILEPATH)) {
1486 		CWD_STATE_FREE_ERR(&new_state);
1487 		return -1;
1488 	}
1489 
1490 	f = creat(new_state.cwd,  mode);
1491 
1492 	CWD_STATE_FREE_ERR(&new_state);
1493 	return f;
1494 }
1495 /* }}} */
1496 
virtual_rename(const char * oldname,const char * newname)1497 CWD_API int virtual_rename(const char *oldname, const char *newname) /* {{{ */
1498 {
1499 	cwd_state old_state;
1500 	cwd_state new_state;
1501 	int retval;
1502 
1503 	CWD_STATE_COPY(&old_state, &CWDG(cwd));
1504 	if (virtual_file_ex(&old_state, oldname, NULL, CWD_EXPAND)) {
1505 		CWD_STATE_FREE_ERR(&old_state);
1506 		return -1;
1507 	}
1508 	oldname = old_state.cwd;
1509 
1510 	CWD_STATE_COPY(&new_state, &CWDG(cwd));
1511 	if (virtual_file_ex(&new_state, newname, NULL, CWD_EXPAND)) {
1512 		CWD_STATE_FREE_ERR(&old_state);
1513 		CWD_STATE_FREE_ERR(&new_state);
1514 		return -1;
1515 	}
1516 	newname = new_state.cwd;
1517 
1518 	/* rename on windows will fail if newname already exists.
1519 	   MoveFileEx has to be used */
1520 #ifdef ZEND_WIN32
1521 	/* MoveFileEx returns 0 on failure, other way 'round for this function */
1522 	retval = php_win32_ioutil_rename(oldname, newname);
1523 #else
1524 	retval = rename(oldname, newname);
1525 #endif
1526 
1527 	CWD_STATE_FREE_ERR(&old_state);
1528 	CWD_STATE_FREE_ERR(&new_state);
1529 
1530 	return retval;
1531 }
1532 /* }}} */
1533 
virtual_stat(const char * path,zend_stat_t * buf)1534 CWD_API int virtual_stat(const char *path, zend_stat_t *buf) /* {{{ */
1535 {
1536 	cwd_state new_state;
1537 	int retval;
1538 
1539 	CWD_STATE_COPY(&new_state, &CWDG(cwd));
1540 	if (virtual_file_ex(&new_state, path, NULL, CWD_REALPATH)) {
1541 		CWD_STATE_FREE_ERR(&new_state);
1542 		return -1;
1543 	}
1544 
1545 	retval = php_sys_stat(new_state.cwd, buf);
1546 
1547 	CWD_STATE_FREE_ERR(&new_state);
1548 	return retval;
1549 }
1550 /* }}} */
1551 
virtual_lstat(const char * path,zend_stat_t * buf)1552 CWD_API int virtual_lstat(const char *path, zend_stat_t *buf) /* {{{ */
1553 {
1554 	cwd_state new_state;
1555 	int retval;
1556 
1557 	CWD_STATE_COPY(&new_state, &CWDG(cwd));
1558 	if (virtual_file_ex(&new_state, path, NULL, CWD_EXPAND)) {
1559 		CWD_STATE_FREE_ERR(&new_state);
1560 		return -1;
1561 	}
1562 
1563 	retval = php_sys_lstat(new_state.cwd, buf);
1564 
1565 	CWD_STATE_FREE_ERR(&new_state);
1566 	return retval;
1567 }
1568 /* }}} */
1569 
virtual_unlink(const char * path)1570 CWD_API int virtual_unlink(const char *path) /* {{{ */
1571 {
1572 	cwd_state new_state;
1573 	int retval;
1574 
1575 	CWD_STATE_COPY(&new_state, &CWDG(cwd));
1576 	if (virtual_file_ex(&new_state, path, NULL, CWD_EXPAND)) {
1577 		CWD_STATE_FREE_ERR(&new_state);
1578 		return -1;
1579 	}
1580 
1581 #ifdef ZEND_WIN32
1582 	retval = php_win32_ioutil_unlink(new_state.cwd);
1583 #else
1584 	retval = unlink(new_state.cwd);
1585 #endif
1586 
1587 	CWD_STATE_FREE_ERR(&new_state);
1588 	return retval;
1589 }
1590 /* }}} */
1591 
virtual_mkdir(const char * pathname,mode_t mode)1592 CWD_API int virtual_mkdir(const char *pathname, mode_t mode) /* {{{ */
1593 {
1594 	cwd_state new_state;
1595 	int retval;
1596 
1597 	CWD_STATE_COPY(&new_state, &CWDG(cwd));
1598 	if (virtual_file_ex(&new_state, pathname, NULL, CWD_FILEPATH)) {
1599 		CWD_STATE_FREE_ERR(&new_state);
1600 		return -1;
1601 	}
1602 
1603 #ifdef ZEND_WIN32
1604 	retval = php_win32_ioutil_mkdir(new_state.cwd, mode);
1605 #else
1606 	retval = mkdir(new_state.cwd, mode);
1607 #endif
1608 	CWD_STATE_FREE_ERR(&new_state);
1609 	return retval;
1610 }
1611 /* }}} */
1612 
virtual_rmdir(const char * pathname)1613 CWD_API int virtual_rmdir(const char *pathname) /* {{{ */
1614 {
1615 	cwd_state new_state;
1616 	int retval;
1617 
1618 	CWD_STATE_COPY(&new_state, &CWDG(cwd));
1619 	if (virtual_file_ex(&new_state, pathname, NULL, CWD_EXPAND)) {
1620 		CWD_STATE_FREE_ERR(&new_state);
1621 		return -1;
1622 	}
1623 
1624 #ifdef ZEND_WIN32
1625 	retval = php_win32_ioutil_rmdir(new_state.cwd);
1626 #else
1627 	retval = rmdir(new_state.cwd);
1628 #endif
1629 	CWD_STATE_FREE_ERR(&new_state);
1630 	return retval;
1631 }
1632 /* }}} */
1633 
1634 #ifdef ZEND_WIN32
1635 DIR *opendir(const char *name);
1636 #endif
1637 
virtual_opendir(const char * pathname)1638 CWD_API DIR *virtual_opendir(const char *pathname) /* {{{ */
1639 {
1640 	cwd_state new_state;
1641 	DIR *retval;
1642 
1643 	CWD_STATE_COPY(&new_state, &CWDG(cwd));
1644 	if (virtual_file_ex(&new_state, pathname, NULL, CWD_REALPATH)) {
1645 		CWD_STATE_FREE_ERR(&new_state);
1646 		return NULL;
1647 	}
1648 
1649 	retval = opendir(new_state.cwd);
1650 
1651 	CWD_STATE_FREE_ERR(&new_state);
1652 	return retval;
1653 }
1654 /* }}} */
1655 
1656 #ifdef ZEND_WIN32
virtual_popen(const char * command,const char * type)1657 CWD_API FILE *virtual_popen(const char *command, const char *type) /* {{{ */
1658 {
1659 	return popen_ex(command, type, CWDG(cwd).cwd, NULL);
1660 }
1661 /* }}} */
1662 #else /* Unix */
virtual_popen(const char * command,const char * type)1663 CWD_API FILE *virtual_popen(const char *command, const char *type) /* {{{ */
1664 {
1665 	size_t command_length;
1666 	int dir_length, extra = 0;
1667 	char *command_line;
1668 	char *ptr, *dir;
1669 	FILE *retval;
1670 
1671 	command_length = strlen(command);
1672 
1673 	dir_length = CWDG(cwd).cwd_length;
1674 	dir = CWDG(cwd).cwd;
1675 	while (dir_length > 0) {
1676 		if (*dir == '\'') extra+=3;
1677 		dir++;
1678 		dir_length--;
1679 	}
1680 	dir_length = CWDG(cwd).cwd_length;
1681 	dir = CWDG(cwd).cwd;
1682 
1683 	ptr = command_line = (char *) emalloc(command_length + sizeof("cd '' ; ") + dir_length + extra+1+1);
1684 	ptr = zend_mempcpy(ptr, "cd ", sizeof("cd ") - 1);
1685 
1686 	if (CWDG(cwd).cwd_length == 0) {
1687 		*ptr++ = DEFAULT_SLASH;
1688 	} else {
1689 		*ptr++ = '\'';
1690 		while (dir_length > 0) {
1691 			switch (*dir) {
1692 			case '\'':
1693 				*ptr++ = '\'';
1694 				*ptr++ = '\\';
1695 				*ptr++ = '\'';
1696 				ZEND_FALLTHROUGH;
1697 			default:
1698 				*ptr++ = *dir;
1699 			}
1700 			dir++;
1701 			dir_length--;
1702 		}
1703 		*ptr++ = '\'';
1704 	}
1705 
1706 	*ptr++ = ' ';
1707 	*ptr++ = ';';
1708 	*ptr++ = ' ';
1709 
1710 	memcpy(ptr, command, command_length+1);
1711 	retval = popen(command_line, type);
1712 
1713 	efree(command_line);
1714 	return retval;
1715 }
1716 /* }}} */
1717 #endif
1718 
tsrm_realpath(const char * path,char * real_path)1719 CWD_API char *tsrm_realpath(const char *path, char *real_path) /* {{{ */
1720 {
1721 	cwd_state new_state;
1722 	char cwd[MAXPATHLEN];
1723 
1724 	/* realpath("") returns CWD */
1725 	if (!*path) {
1726 		new_state.cwd = (char*)emalloc(1);
1727 		new_state.cwd[0] = '\0';
1728 		new_state.cwd_length = 0;
1729 		if (VCWD_GETCWD(cwd, MAXPATHLEN)) {
1730 			path = cwd;
1731 		}
1732 	} else if (!IS_ABSOLUTE_PATH(path, strlen(path)) &&
1733 					VCWD_GETCWD(cwd, MAXPATHLEN)) {
1734 		new_state.cwd = estrdup(cwd);
1735 		new_state.cwd_length = strlen(cwd);
1736 	} else {
1737 		new_state.cwd = (char*)emalloc(1);
1738 		new_state.cwd[0] = '\0';
1739 		new_state.cwd_length = 0;
1740 	}
1741 
1742 	if (virtual_file_ex(&new_state, path, NULL, CWD_REALPATH)) {
1743 		efree(new_state.cwd);
1744 		return NULL;
1745 	}
1746 
1747 	if (real_path) {
1748 		size_t copy_len = new_state.cwd_length>MAXPATHLEN-1 ? MAXPATHLEN-1 : new_state.cwd_length;
1749 		memcpy(real_path, new_state.cwd, copy_len);
1750 		real_path[copy_len] = '\0';
1751 		efree(new_state.cwd);
1752 		return real_path;
1753 	} else {
1754 		return new_state.cwd;
1755 	}
1756 }
1757 /* }}} */
1758