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