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