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