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