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