xref: /PHP-7.1/main/streams/plain_wrapper.c (revision e3133e4d)
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: Wez Furlong <wez@thebrainroom.com>                          |
16    +----------------------------------------------------------------------+
17  */
18 
19 /* $Id$ */
20 
21 #include "php.h"
22 #include "php_globals.h"
23 #include "php_network.h"
24 #include "php_open_temporary_file.h"
25 #include "ext/standard/file.h"
26 #include "ext/standard/flock_compat.h"
27 #include "ext/standard/php_filestat.h"
28 #include <stddef.h>
29 #include <fcntl.h>
30 #if HAVE_SYS_WAIT_H
31 #include <sys/wait.h>
32 #endif
33 #if HAVE_SYS_FILE_H
34 #include <sys/file.h>
35 #endif
36 #ifdef HAVE_SYS_MMAN_H
37 #include <sys/mman.h>
38 #endif
39 #include "SAPI.h"
40 
41 #include "php_streams_int.h"
42 #ifdef PHP_WIN32
43 # include "win32/winutil.h"
44 # include "win32/time.h"
45 # include "win32/ioutil.h"
46 # include "win32/readdir.h"
47 #endif
48 
49 #define php_stream_fopen_from_fd_int(fd, mode, persistent_id)	_php_stream_fopen_from_fd_int((fd), (mode), (persistent_id) STREAMS_CC)
50 #define php_stream_fopen_from_fd_int_rel(fd, mode, persistent_id)	 _php_stream_fopen_from_fd_int((fd), (mode), (persistent_id) STREAMS_REL_CC)
51 #define php_stream_fopen_from_file_int(file, mode)	_php_stream_fopen_from_file_int((file), (mode) STREAMS_CC)
52 #define php_stream_fopen_from_file_int_rel(file, mode)	 _php_stream_fopen_from_file_int((file), (mode) STREAMS_REL_CC)
53 
54 #if !defined(WINDOWS) && !defined(NETWARE)
55 extern int php_get_uid_by_name(const char *name, uid_t *uid);
56 extern int php_get_gid_by_name(const char *name, gid_t *gid);
57 #endif
58 
59 #if defined(PHP_WIN32)
60 # define PLAIN_WRAP_BUF_SIZE(st) (((st) > UINT_MAX) ? UINT_MAX : (unsigned int)(st))
61 #else
62 # define PLAIN_WRAP_BUF_SIZE(st) (st)
63 #endif
64 
65 /* parse standard "fopen" modes into open() flags */
php_stream_parse_fopen_modes(const char * mode,int * open_flags)66 PHPAPI int php_stream_parse_fopen_modes(const char *mode, int *open_flags)
67 {
68 	int flags;
69 
70 	switch (mode[0]) {
71 		case 'r':
72 			flags = 0;
73 			break;
74 		case 'w':
75 			flags = O_TRUNC|O_CREAT;
76 			break;
77 		case 'a':
78 			flags = O_CREAT|O_APPEND;
79 			break;
80 		case 'x':
81 			flags = O_CREAT|O_EXCL;
82 			break;
83 		case 'c':
84 			flags = O_CREAT;
85 			break;
86 		default:
87 			/* unknown mode */
88 			return FAILURE;
89 	}
90 
91 	if (strchr(mode, '+')) {
92 		flags |= O_RDWR;
93 	} else if (flags) {
94 		flags |= O_WRONLY;
95 	} else {
96 		flags |= O_RDONLY;
97 	}
98 
99 #if defined(O_CLOEXEC)
100 	if (strchr(mode, 'e')) {
101 		flags |= O_CLOEXEC;
102 	}
103 #endif
104 
105 #if defined(O_NONBLOCK)
106 	if (strchr(mode, 'n')) {
107 		flags |= O_NONBLOCK;
108 	}
109 #endif
110 
111 #if defined(_O_TEXT) && defined(O_BINARY)
112 	if (strchr(mode, 't')) {
113 		flags |= _O_TEXT;
114 	} else {
115 		flags |= O_BINARY;
116 	}
117 #endif
118 
119 	*open_flags = flags;
120 	return SUCCESS;
121 }
122 
123 
124 /* {{{ ------- STDIO stream implementation -------*/
125 
126 typedef struct {
127 	FILE *file;
128 	int fd;					/* underlying file descriptor */
129 	unsigned is_process_pipe:1;	/* use pclose instead of fclose */
130 	unsigned is_pipe:1;			/* don't try and seek */
131 	unsigned cached_fstat:1;	/* sb is valid */
132 	unsigned is_pipe_blocking:1; /* allow blocking read() on pipes, currently Windows only */
133 	unsigned _reserved:28;
134 
135 	int lock_flag;			/* stores the lock state */
136 	zend_string *temp_name;	/* if non-null, this is the path to a temporary file that
137 							 * is to be deleted when the stream is closed */
138 #if HAVE_FLUSHIO
139 	char last_op;
140 #endif
141 
142 #if HAVE_MMAP
143 	char *last_mapped_addr;
144 	size_t last_mapped_len;
145 #endif
146 #ifdef PHP_WIN32
147 	char *last_mapped_addr;
148 	HANDLE file_mapping;
149 #endif
150 
151 	zend_stat_t sb;
152 } php_stdio_stream_data;
153 #define PHP_STDIOP_GET_FD(anfd, data)	anfd = (data)->file ? fileno((data)->file) : (data)->fd
154 
do_fstat(php_stdio_stream_data * d,int force)155 static int do_fstat(php_stdio_stream_data *d, int force)
156 {
157 	if (!d->cached_fstat || force) {
158 		int fd;
159 		int r;
160 
161 		PHP_STDIOP_GET_FD(fd, d);
162 		r = zend_fstat(fd, &d->sb);
163 		d->cached_fstat = r == 0;
164 
165 		return r;
166 	}
167 	return 0;
168 }
169 
_php_stream_fopen_from_fd_int(int fd,const char * mode,const char * persistent_id STREAMS_DC)170 static php_stream *_php_stream_fopen_from_fd_int(int fd, const char *mode, const char *persistent_id STREAMS_DC)
171 {
172 	php_stdio_stream_data *self;
173 
174 	self = pemalloc_rel_orig(sizeof(*self), persistent_id);
175 	memset(self, 0, sizeof(*self));
176 	self->file = NULL;
177 	self->is_pipe = 0;
178 	self->lock_flag = LOCK_UN;
179 	self->is_process_pipe = 0;
180 	self->temp_name = NULL;
181 	self->fd = fd;
182 #ifdef PHP_WIN32
183 	self->is_pipe_blocking = 0;
184 #endif
185 
186 	return php_stream_alloc_rel(&php_stream_stdio_ops, self, persistent_id, mode);
187 }
188 
_php_stream_fopen_from_file_int(FILE * file,const char * mode STREAMS_DC)189 static php_stream *_php_stream_fopen_from_file_int(FILE *file, const char *mode STREAMS_DC)
190 {
191 	php_stdio_stream_data *self;
192 
193 	self = emalloc_rel_orig(sizeof(*self));
194 	memset(self, 0, sizeof(*self));
195 	self->file = file;
196 	self->is_pipe = 0;
197 	self->lock_flag = LOCK_UN;
198 	self->is_process_pipe = 0;
199 	self->temp_name = NULL;
200 	self->fd = fileno(file);
201 #ifdef PHP_WIN32
202 	self->is_pipe_blocking = 0;
203 #endif
204 
205 	return php_stream_alloc_rel(&php_stream_stdio_ops, self, 0, mode);
206 }
207 
_php_stream_fopen_temporary_file(const char * dir,const char * pfx,zend_string ** opened_path_ptr STREAMS_DC)208 PHPAPI php_stream *_php_stream_fopen_temporary_file(const char *dir, const char *pfx, zend_string **opened_path_ptr STREAMS_DC)
209 {
210 	zend_string *opened_path = NULL;
211 	int fd;
212 
213 	fd = php_open_temporary_fd(dir, pfx, &opened_path);
214 	if (fd != -1)	{
215 		php_stream *stream;
216 
217 		if (opened_path_ptr) {
218 			*opened_path_ptr = opened_path;
219 		}
220 
221 		stream = php_stream_fopen_from_fd_int_rel(fd, "r+b", NULL);
222 		if (stream) {
223 			php_stdio_stream_data *self = (php_stdio_stream_data*)stream->abstract;
224 			stream->wrapper = &php_plain_files_wrapper;
225 			stream->orig_path = estrndup(ZSTR_VAL(opened_path), ZSTR_LEN(opened_path));
226 
227 			self->temp_name = opened_path;
228 			self->lock_flag = LOCK_UN;
229 
230 			return stream;
231 		}
232 		close(fd);
233 
234 		php_error_docref(NULL, E_WARNING, "unable to allocate stream");
235 
236 		return NULL;
237 	}
238 	return NULL;
239 }
240 
_php_stream_fopen_tmpfile(int dummy STREAMS_DC)241 PHPAPI php_stream *_php_stream_fopen_tmpfile(int dummy STREAMS_DC)
242 {
243 	return php_stream_fopen_temporary_file(NULL, "php", NULL);
244 }
245 
_php_stream_fopen_from_fd(int fd,const char * mode,const char * persistent_id STREAMS_DC)246 PHPAPI php_stream *_php_stream_fopen_from_fd(int fd, const char *mode, const char *persistent_id STREAMS_DC)
247 {
248 	php_stream *stream = php_stream_fopen_from_fd_int_rel(fd, mode, persistent_id);
249 
250 	if (stream) {
251 		php_stdio_stream_data *self = (php_stdio_stream_data*)stream->abstract;
252 
253 #ifdef S_ISFIFO
254 		/* detect if this is a pipe */
255 		if (self->fd >= 0) {
256 			self->is_pipe = (do_fstat(self, 0) == 0 && S_ISFIFO(self->sb.st_mode)) ? 1 : 0;
257 		}
258 #elif defined(PHP_WIN32)
259 		{
260 			zend_uintptr_t handle = _get_osfhandle(self->fd);
261 
262 			if (handle != (zend_uintptr_t)INVALID_HANDLE_VALUE) {
263 				self->is_pipe = GetFileType((HANDLE)handle) == FILE_TYPE_PIPE;
264 			}
265 		}
266 #endif
267 
268 		if (self->is_pipe) {
269 			stream->flags |= PHP_STREAM_FLAG_NO_SEEK;
270 		} else {
271 			stream->position = zend_lseek(self->fd, 0, SEEK_CUR);
272 #ifdef ESPIPE
273 			if (stream->position == (zend_off_t)-1 && errno == ESPIPE) {
274 				stream->position = 0;
275 				stream->flags |= PHP_STREAM_FLAG_NO_SEEK;
276 				self->is_pipe = 1;
277 			}
278 #endif
279 		}
280 	}
281 
282 	return stream;
283 }
284 
_php_stream_fopen_from_file(FILE * file,const char * mode STREAMS_DC)285 PHPAPI php_stream *_php_stream_fopen_from_file(FILE *file, const char *mode STREAMS_DC)
286 {
287 	php_stream *stream = php_stream_fopen_from_file_int_rel(file, mode);
288 
289 	if (stream) {
290 		php_stdio_stream_data *self = (php_stdio_stream_data*)stream->abstract;
291 
292 #ifdef S_ISFIFO
293 		/* detect if this is a pipe */
294 		if (self->fd >= 0) {
295 			self->is_pipe = (do_fstat(self, 0) == 0 && S_ISFIFO(self->sb.st_mode)) ? 1 : 0;
296 		}
297 #elif defined(PHP_WIN32)
298 		{
299 			zend_uintptr_t handle = _get_osfhandle(self->fd);
300 
301 			if (handle != (zend_uintptr_t)INVALID_HANDLE_VALUE) {
302 				self->is_pipe = GetFileType((HANDLE)handle) == FILE_TYPE_PIPE;
303 			}
304 		}
305 #endif
306 
307 		if (self->is_pipe) {
308 			stream->flags |= PHP_STREAM_FLAG_NO_SEEK;
309 		} else {
310 			stream->position = zend_ftell(file);
311 		}
312 	}
313 
314 	return stream;
315 }
316 
_php_stream_fopen_from_pipe(FILE * file,const char * mode STREAMS_DC)317 PHPAPI php_stream *_php_stream_fopen_from_pipe(FILE *file, const char *mode STREAMS_DC)
318 {
319 	php_stdio_stream_data *self;
320 	php_stream *stream;
321 
322 	self = emalloc_rel_orig(sizeof(*self));
323 	memset(self, 0, sizeof(*self));
324 	self->file = file;
325 	self->is_pipe = 1;
326 	self->lock_flag = LOCK_UN;
327 	self->is_process_pipe = 1;
328 	self->fd = fileno(file);
329 	self->temp_name = NULL;
330 #ifdef PHP_WIN32
331 	self->is_pipe_blocking = 0;
332 #endif
333 
334 	stream = php_stream_alloc_rel(&php_stream_stdio_ops, self, 0, mode);
335 	stream->flags |= PHP_STREAM_FLAG_NO_SEEK;
336 	return stream;
337 }
338 
php_stdiop_write(php_stream * stream,const char * buf,size_t count)339 static size_t php_stdiop_write(php_stream *stream, const char *buf, size_t count)
340 {
341 	php_stdio_stream_data *data = (php_stdio_stream_data*)stream->abstract;
342 
343 	assert(data != NULL);
344 
345 	if (data->fd >= 0) {
346 #ifdef PHP_WIN32
347 		int bytes_written;
348 		if (ZEND_SIZE_T_UINT_OVFL(count)) {
349 			count = UINT_MAX;
350 		}
351 		bytes_written = _write(data->fd, buf, (unsigned int)count);
352 #else
353 		int bytes_written = write(data->fd, buf, count);
354 #endif
355 		if (bytes_written < 0) return 0;
356 		return (size_t) bytes_written;
357 	} else {
358 
359 #if HAVE_FLUSHIO
360 		if (!data->is_pipe && data->last_op == 'r') {
361 			zend_fseek(data->file, 0, SEEK_CUR);
362 		}
363 		data->last_op = 'w';
364 #endif
365 
366 		return fwrite(buf, 1, count, data->file);
367 	}
368 }
369 
php_stdiop_read(php_stream * stream,char * buf,size_t count)370 static size_t php_stdiop_read(php_stream *stream, char *buf, size_t count)
371 {
372 	php_stdio_stream_data *data = (php_stdio_stream_data*)stream->abstract;
373 	size_t ret;
374 
375 	assert(data != NULL);
376 
377 	if (data->fd >= 0) {
378 #ifdef PHP_WIN32
379 		php_stdio_stream_data *self = (php_stdio_stream_data*)stream->abstract;
380 
381 		if ((self->is_pipe || self->is_process_pipe) && !self->is_pipe_blocking) {
382 			HANDLE ph = (HANDLE)_get_osfhandle(data->fd);
383 			int retry = 0;
384 			DWORD avail_read = 0;
385 
386 			do {
387 				/* Look ahead to get the available data amount to read. Do the same
388 					as read() does, however not blocking forever. In case it failed,
389 					no data will be read (better than block). */
390 				if (!PeekNamedPipe(ph, NULL, 0, NULL, &avail_read, NULL)) {
391 					break;
392 				}
393 				/* If there's nothing to read, wait in 10ms periods. */
394 				if (0 == avail_read) {
395 					usleep(10);
396 				}
397 			} while (0 == avail_read && retry++ < 3200000);
398 
399 			/* Reduce the required data amount to what is available, otherwise read()
400 				will block.*/
401 			if (avail_read < count) {
402 				count = avail_read;
403 			}
404 		}
405 #endif
406 		ret = read(data->fd, buf,  PLAIN_WRAP_BUF_SIZE(count));
407 
408 		if (ret == (size_t)-1 && errno == EINTR) {
409 			/* Read was interrupted, retry once,
410 			   If read still fails, giveup with feof==0
411 			   so script can retry if desired */
412 			ret = read(data->fd, buf,  PLAIN_WRAP_BUF_SIZE(count));
413 		}
414 
415 		stream->eof = (ret == 0 || (ret == (size_t)-1 && errno != EWOULDBLOCK && errno != EINTR && errno != EBADF));
416 
417 	} else {
418 #if HAVE_FLUSHIO
419 		if (!data->is_pipe && data->last_op == 'w')
420 			zend_fseek(data->file, 0, SEEK_CUR);
421 		data->last_op = 'r';
422 #endif
423 
424 		ret = fread(buf, 1, count, data->file);
425 
426 		stream->eof = feof(data->file);
427 	}
428 	return ret;
429 }
430 
php_stdiop_close(php_stream * stream,int close_handle)431 static int php_stdiop_close(php_stream *stream, int close_handle)
432 {
433 	int ret;
434 	php_stdio_stream_data *data = (php_stdio_stream_data*)stream->abstract;
435 
436 	assert(data != NULL);
437 
438 #if HAVE_MMAP
439 	if (data->last_mapped_addr) {
440 		munmap(data->last_mapped_addr, data->last_mapped_len);
441 		data->last_mapped_addr = NULL;
442 	}
443 #elif defined(PHP_WIN32)
444 	if (data->last_mapped_addr) {
445 		UnmapViewOfFile(data->last_mapped_addr);
446 		data->last_mapped_addr = NULL;
447 	}
448 	if (data->file_mapping) {
449 		CloseHandle(data->file_mapping);
450 		data->file_mapping = NULL;
451 	}
452 #endif
453 
454 	if (close_handle) {
455 		if (data->file) {
456 			if (data->is_process_pipe) {
457 				errno = 0;
458 				ret = pclose(data->file);
459 
460 #if HAVE_SYS_WAIT_H
461 				if (WIFEXITED(ret)) {
462 					ret = WEXITSTATUS(ret);
463 				}
464 #endif
465 			} else {
466 				ret = fclose(data->file);
467 				data->file = NULL;
468 			}
469 		} else if (data->fd != -1) {
470 			ret = close(data->fd);
471 			data->fd = -1;
472 		} else {
473 			return 0; /* everything should be closed already -> success */
474 		}
475 		if (data->temp_name) {
476 #ifdef PHP_WIN32
477 			php_win32_ioutil_unlink(ZSTR_VAL(data->temp_name));
478 #else
479 			unlink(ZSTR_VAL(data->temp_name));
480 #endif
481 			/* temporary streams are never persistent */
482 			zend_string_release(data->temp_name);
483 			data->temp_name = NULL;
484 		}
485 	} else {
486 		ret = 0;
487 		data->file = NULL;
488 		data->fd = -1;
489 	}
490 
491 	pefree(data, stream->is_persistent);
492 
493 	return ret;
494 }
495 
php_stdiop_flush(php_stream * stream)496 static int php_stdiop_flush(php_stream *stream)
497 {
498 	php_stdio_stream_data *data = (php_stdio_stream_data*)stream->abstract;
499 
500 	assert(data != NULL);
501 
502 	/*
503 	 * stdio buffers data in user land. By calling fflush(3), this
504 	 * data is send to the kernel using write(2). fsync'ing is
505 	 * something completely different.
506 	 */
507 	if (data->file) {
508 		return fflush(data->file);
509 	}
510 	return 0;
511 }
512 
php_stdiop_seek(php_stream * stream,zend_off_t offset,int whence,zend_off_t * newoffset)513 static int php_stdiop_seek(php_stream *stream, zend_off_t offset, int whence, zend_off_t *newoffset)
514 {
515 	php_stdio_stream_data *data = (php_stdio_stream_data*)stream->abstract;
516 	int ret;
517 
518 	assert(data != NULL);
519 
520 	if (data->is_pipe) {
521 		php_error_docref(NULL, E_WARNING, "cannot seek on a pipe");
522 		return -1;
523 	}
524 
525 	if (data->fd >= 0) {
526 		zend_off_t result;
527 
528 		result = zend_lseek(data->fd, offset, whence);
529 		if (result == (zend_off_t)-1)
530 			return -1;
531 
532 		*newoffset = result;
533 		return 0;
534 
535 	} else {
536 		ret = zend_fseek(data->file, offset, whence);
537 		*newoffset = zend_ftell(data->file);
538 		return ret;
539 	}
540 }
541 
php_stdiop_cast(php_stream * stream,int castas,void ** ret)542 static int php_stdiop_cast(php_stream *stream, int castas, void **ret)
543 {
544 	php_socket_t fd;
545 	php_stdio_stream_data *data = (php_stdio_stream_data*) stream->abstract;
546 
547 	assert(data != NULL);
548 
549 	/* as soon as someone touches the stdio layer, buffering may ensue,
550 	 * so we need to stop using the fd directly in that case */
551 
552 	switch (castas)	{
553 		case PHP_STREAM_AS_STDIO:
554 			if (ret) {
555 
556 				if (data->file == NULL) {
557 					/* we were opened as a plain file descriptor, so we
558 					 * need fdopen now */
559 					char fixed_mode[5];
560 					php_stream_mode_sanitize_fdopen_fopencookie(stream, fixed_mode);
561 					data->file = fdopen(data->fd, fixed_mode);
562 					if (data->file == NULL) {
563 						return FAILURE;
564 					}
565 				}
566 
567 				*(FILE**)ret = data->file;
568 				data->fd = SOCK_ERR;
569 			}
570 			return SUCCESS;
571 
572 		case PHP_STREAM_AS_FD_FOR_SELECT:
573 			PHP_STDIOP_GET_FD(fd, data);
574 			if (SOCK_ERR == fd) {
575 				return FAILURE;
576 			}
577 			if (ret) {
578 				*(php_socket_t *)ret = fd;
579 			}
580 			return SUCCESS;
581 
582 		case PHP_STREAM_AS_FD:
583 			PHP_STDIOP_GET_FD(fd, data);
584 
585 			if (SOCK_ERR == fd) {
586 				return FAILURE;
587 			}
588 			if (data->file) {
589 				fflush(data->file);
590 			}
591 			if (ret) {
592 				*(php_socket_t *)ret = fd;
593 			}
594 			return SUCCESS;
595 		default:
596 			return FAILURE;
597 	}
598 }
599 
php_stdiop_stat(php_stream * stream,php_stream_statbuf * ssb)600 static int php_stdiop_stat(php_stream *stream, php_stream_statbuf *ssb)
601 {
602 	int ret;
603 	php_stdio_stream_data *data = (php_stdio_stream_data*) stream->abstract;
604 
605 	assert(data != NULL);
606 	if((ret = do_fstat(data, 1)) == 0) {
607 		memcpy(&ssb->sb, &data->sb, sizeof(ssb->sb));
608 	}
609 
610 	return ret;
611 }
612 
php_stdiop_set_option(php_stream * stream,int option,int value,void * ptrparam)613 static int php_stdiop_set_option(php_stream *stream, int option, int value, void *ptrparam)
614 {
615 	php_stdio_stream_data *data = (php_stdio_stream_data*) stream->abstract;
616 	size_t size;
617 	int fd;
618 #ifdef O_NONBLOCK
619 	/* FIXME: make this work for win32 */
620 	int flags;
621 	int oldval;
622 #endif
623 
624 	PHP_STDIOP_GET_FD(fd, data);
625 
626 	switch(option) {
627 		case PHP_STREAM_OPTION_BLOCKING:
628 			if (fd == -1)
629 				return -1;
630 #ifdef O_NONBLOCK
631 			flags = fcntl(fd, F_GETFL, 0);
632 			oldval = (flags & O_NONBLOCK) ? 0 : 1;
633 			if (value)
634 				flags &= ~O_NONBLOCK;
635 			else
636 				flags |= O_NONBLOCK;
637 
638 			if (-1 == fcntl(fd, F_SETFL, flags))
639 				return -1;
640 			return oldval;
641 #else
642 			return -1; /* not yet implemented */
643 #endif
644 
645 		case PHP_STREAM_OPTION_WRITE_BUFFER:
646 
647 			if (data->file == NULL) {
648 				return -1;
649 			}
650 
651 			if (ptrparam)
652 				size = *(size_t *)ptrparam;
653 			else
654 				size = BUFSIZ;
655 
656 			switch(value) {
657 				case PHP_STREAM_BUFFER_NONE:
658 					return setvbuf(data->file, NULL, _IONBF, 0);
659 
660 				case PHP_STREAM_BUFFER_LINE:
661 					return setvbuf(data->file, NULL, _IOLBF, size);
662 
663 				case PHP_STREAM_BUFFER_FULL:
664 					return setvbuf(data->file, NULL, _IOFBF, size);
665 
666 				default:
667 					return -1;
668 			}
669 			break;
670 
671 		case PHP_STREAM_OPTION_LOCKING:
672 			if (fd == -1) {
673 				return -1;
674 			}
675 
676 			if ((zend_uintptr_t) ptrparam == PHP_STREAM_LOCK_SUPPORTED) {
677 				return 0;
678 			}
679 
680 			if (!flock(fd, value)) {
681 				data->lock_flag = value;
682 				return 0;
683 			} else {
684 				return -1;
685 			}
686 			break;
687 
688 		case PHP_STREAM_OPTION_MMAP_API:
689 #if HAVE_MMAP
690 			{
691 				php_stream_mmap_range *range = (php_stream_mmap_range*)ptrparam;
692 				int prot, flags;
693 
694 				switch (value) {
695 					case PHP_STREAM_MMAP_SUPPORTED:
696 						return fd == -1 ? PHP_STREAM_OPTION_RETURN_ERR : PHP_STREAM_OPTION_RETURN_OK;
697 
698 					case PHP_STREAM_MMAP_MAP_RANGE:
699 						if(do_fstat(data, 1) != 0) {
700 							return PHP_STREAM_OPTION_RETURN_ERR;
701 						}
702 						if (range->length == 0 && range->offset > 0 && range->offset < data->sb.st_size) {
703 							range->length = data->sb.st_size - range->offset;
704 						}
705 						if (range->length == 0 || range->length > data->sb.st_size) {
706 							range->length = data->sb.st_size;
707 						}
708 						if (range->offset >= data->sb.st_size) {
709 							range->offset = data->sb.st_size;
710 							range->length = 0;
711 						}
712 						switch (range->mode) {
713 							case PHP_STREAM_MAP_MODE_READONLY:
714 								prot = PROT_READ;
715 								flags = MAP_PRIVATE;
716 								break;
717 							case PHP_STREAM_MAP_MODE_READWRITE:
718 								prot = PROT_READ | PROT_WRITE;
719 								flags = MAP_PRIVATE;
720 								break;
721 							case PHP_STREAM_MAP_MODE_SHARED_READONLY:
722 								prot = PROT_READ;
723 								flags = MAP_SHARED;
724 								break;
725 							case PHP_STREAM_MAP_MODE_SHARED_READWRITE:
726 								prot = PROT_READ | PROT_WRITE;
727 								flags = MAP_SHARED;
728 								break;
729 							default:
730 								return PHP_STREAM_OPTION_RETURN_ERR;
731 						}
732 						range->mapped = (char*)mmap(NULL, range->length, prot, flags, fd, range->offset);
733 						if (range->mapped == (char*)MAP_FAILED) {
734 							range->mapped = NULL;
735 							return PHP_STREAM_OPTION_RETURN_ERR;
736 						}
737 						/* remember the mapping */
738 						data->last_mapped_addr = range->mapped;
739 						data->last_mapped_len = range->length;
740 						return PHP_STREAM_OPTION_RETURN_OK;
741 
742 					case PHP_STREAM_MMAP_UNMAP:
743 						if (data->last_mapped_addr) {
744 							munmap(data->last_mapped_addr, data->last_mapped_len);
745 							data->last_mapped_addr = NULL;
746 
747 							return PHP_STREAM_OPTION_RETURN_OK;
748 						}
749 						return PHP_STREAM_OPTION_RETURN_ERR;
750 				}
751 			}
752 #elif defined(PHP_WIN32)
753 			{
754 				php_stream_mmap_range *range = (php_stream_mmap_range*)ptrparam;
755 				HANDLE hfile = (HANDLE)_get_osfhandle(fd);
756 				DWORD prot, acc, loffs = 0, delta = 0;
757 
758 				switch (value) {
759 					case PHP_STREAM_MMAP_SUPPORTED:
760 						return hfile == INVALID_HANDLE_VALUE ? PHP_STREAM_OPTION_RETURN_ERR : PHP_STREAM_OPTION_RETURN_OK;
761 
762 					case PHP_STREAM_MMAP_MAP_RANGE:
763 						switch (range->mode) {
764 							case PHP_STREAM_MAP_MODE_READONLY:
765 								prot = PAGE_READONLY;
766 								acc = FILE_MAP_READ;
767 								break;
768 							case PHP_STREAM_MAP_MODE_READWRITE:
769 								prot = PAGE_READWRITE;
770 								acc = FILE_MAP_READ | FILE_MAP_WRITE;
771 								break;
772 							case PHP_STREAM_MAP_MODE_SHARED_READONLY:
773 								prot = PAGE_READONLY;
774 								acc = FILE_MAP_READ;
775 								/* TODO: we should assign a name for the mapping */
776 								break;
777 							case PHP_STREAM_MAP_MODE_SHARED_READWRITE:
778 								prot = PAGE_READWRITE;
779 								acc = FILE_MAP_READ | FILE_MAP_WRITE;
780 								/* TODO: we should assign a name for the mapping */
781 								break;
782 							default:
783 								return PHP_STREAM_OPTION_RETURN_ERR;
784 						}
785 
786 						/* create a mapping capable of viewing the whole file (this costs no real resources) */
787 						data->file_mapping = CreateFileMapping(hfile, NULL, prot, 0, 0, NULL);
788 
789 						if (data->file_mapping == NULL) {
790 							return PHP_STREAM_OPTION_RETURN_ERR;
791 						}
792 
793 						size = GetFileSize(hfile, NULL);
794 						if (range->length == 0 && range->offset > 0 && range->offset < size) {
795 							range->length = size - range->offset;
796 						}
797 						if (range->length == 0 || range->length > size) {
798 							range->length = size;
799 						}
800 						if (range->offset >= size) {
801 							range->offset = size;
802 							range->length = 0;
803 						}
804 
805 						/* figure out how big a chunk to map to be able to view the part that we need */
806 						if (range->offset != 0) {
807 							SYSTEM_INFO info;
808 							DWORD gran;
809 
810 							GetSystemInfo(&info);
811 							gran = info.dwAllocationGranularity;
812 							loffs = ((DWORD)range->offset / gran) * gran;
813 							delta = (DWORD)range->offset - loffs;
814 						}
815 
816 						data->last_mapped_addr = MapViewOfFile(data->file_mapping, acc, 0, loffs, range->length + delta);
817 
818 						if (data->last_mapped_addr) {
819 							/* give them back the address of the start offset they requested */
820 							range->mapped = data->last_mapped_addr + delta;
821 							return PHP_STREAM_OPTION_RETURN_OK;
822 						}
823 
824 						CloseHandle(data->file_mapping);
825 						data->file_mapping = NULL;
826 
827 						return PHP_STREAM_OPTION_RETURN_ERR;
828 
829 					case PHP_STREAM_MMAP_UNMAP:
830 						if (data->last_mapped_addr) {
831 							UnmapViewOfFile(data->last_mapped_addr);
832 							data->last_mapped_addr = NULL;
833 							CloseHandle(data->file_mapping);
834 							data->file_mapping = NULL;
835 							return PHP_STREAM_OPTION_RETURN_OK;
836 						}
837 						return PHP_STREAM_OPTION_RETURN_ERR;
838 
839 					default:
840 						return PHP_STREAM_OPTION_RETURN_ERR;
841 				}
842 			}
843 
844 #endif
845 			return PHP_STREAM_OPTION_RETURN_NOTIMPL;
846 
847 		case PHP_STREAM_OPTION_TRUNCATE_API:
848 			switch (value) {
849 				case PHP_STREAM_TRUNCATE_SUPPORTED:
850 					return fd == -1 ? PHP_STREAM_OPTION_RETURN_ERR : PHP_STREAM_OPTION_RETURN_OK;
851 
852 				case PHP_STREAM_TRUNCATE_SET_SIZE: {
853 					ptrdiff_t new_size = *(ptrdiff_t*)ptrparam;
854 					if (new_size < 0) {
855 						return PHP_STREAM_OPTION_RETURN_ERR;
856 					}
857 					return ftruncate(fd, new_size) == 0 ? PHP_STREAM_OPTION_RETURN_OK : PHP_STREAM_OPTION_RETURN_ERR;
858 				}
859 			}
860 
861 #ifdef PHP_WIN32
862 		case PHP_STREAM_OPTION_PIPE_BLOCKING:
863 			data->is_pipe_blocking = value;
864 			return PHP_STREAM_OPTION_RETURN_OK;
865 #endif
866 		case PHP_STREAM_OPTION_META_DATA_API:
867 			if (fd == -1)
868 				return -1;
869 #ifdef O_NONBLOCK
870 			flags = fcntl(fd, F_GETFL, 0);
871 
872 			add_assoc_bool((zval*)ptrparam, "timed_out", 0);
873 			add_assoc_bool((zval*)ptrparam, "blocked", (flags & O_NONBLOCK)? 0 : 1);
874 			add_assoc_bool((zval*)ptrparam, "eof", stream->eof);
875 
876 			return PHP_STREAM_OPTION_RETURN_OK;
877 #endif
878 			return -1;
879 		default:
880 			return PHP_STREAM_OPTION_RETURN_NOTIMPL;
881 	}
882 }
883 
884 PHPAPI php_stream_ops	php_stream_stdio_ops = {
885 	php_stdiop_write, php_stdiop_read,
886 	php_stdiop_close, php_stdiop_flush,
887 	"STDIO",
888 	php_stdiop_seek,
889 	php_stdiop_cast,
890 	php_stdiop_stat,
891 	php_stdiop_set_option
892 };
893 /* }}} */
894 
895 /* {{{ plain files opendir/readdir implementation */
php_plain_files_dirstream_read(php_stream * stream,char * buf,size_t count)896 static size_t php_plain_files_dirstream_read(php_stream *stream, char *buf, size_t count)
897 {
898 	DIR *dir = (DIR*)stream->abstract;
899 	/* avoid libc5 readdir problems */
900 	char entry[sizeof(struct dirent)+MAXPATHLEN];
901 	struct dirent *result = (struct dirent *)&entry;
902 	php_stream_dirent *ent = (php_stream_dirent*)buf;
903 
904 	/* avoid problems if someone mis-uses the stream */
905 	if (count != sizeof(php_stream_dirent))
906 		return 0;
907 
908 	if (php_readdir_r(dir, (struct dirent *)entry, &result) == 0 && result) {
909 		PHP_STRLCPY(ent->d_name, result->d_name, sizeof(ent->d_name), strlen(result->d_name));
910 		return sizeof(php_stream_dirent);
911 	}
912 	return 0;
913 }
914 
php_plain_files_dirstream_close(php_stream * stream,int close_handle)915 static int php_plain_files_dirstream_close(php_stream *stream, int close_handle)
916 {
917 	return closedir((DIR *)stream->abstract);
918 }
919 
php_plain_files_dirstream_rewind(php_stream * stream,zend_off_t offset,int whence,zend_off_t * newoffs)920 static int php_plain_files_dirstream_rewind(php_stream *stream, zend_off_t offset, int whence, zend_off_t *newoffs)
921 {
922 	rewinddir((DIR *)stream->abstract);
923 	return 0;
924 }
925 
926 static php_stream_ops	php_plain_files_dirstream_ops = {
927 	NULL, php_plain_files_dirstream_read,
928 	php_plain_files_dirstream_close, NULL,
929 	"dir",
930 	php_plain_files_dirstream_rewind,
931 	NULL, /* cast */
932 	NULL, /* stat */
933 	NULL  /* set_option */
934 };
935 
php_plain_files_dir_opener(php_stream_wrapper * wrapper,const char * path,const char * mode,int options,zend_string ** opened_path,php_stream_context * context STREAMS_DC)936 static php_stream *php_plain_files_dir_opener(php_stream_wrapper *wrapper, const char *path, const char *mode,
937 		int options, zend_string **opened_path, php_stream_context *context STREAMS_DC)
938 {
939 	DIR *dir = NULL;
940 	php_stream *stream = NULL;
941 
942 #ifdef HAVE_GLOB
943 	if (options & STREAM_USE_GLOB_DIR_OPEN) {
944 		return php_glob_stream_wrapper.wops->dir_opener(&php_glob_stream_wrapper, path, mode, options, opened_path, context STREAMS_REL_CC);
945 	}
946 #endif
947 
948 	if (((options & STREAM_DISABLE_OPEN_BASEDIR) == 0) && php_check_open_basedir(path)) {
949 		return NULL;
950 	}
951 
952 	dir = VCWD_OPENDIR(path);
953 
954 #ifdef PHP_WIN32
955 	if (!dir) {
956 		php_win32_docref2_from_error(GetLastError(), path, path);
957 	}
958 
959 	if (dir && dir->finished) {
960 		closedir(dir);
961 		dir = NULL;
962 	}
963 #endif
964 	if (dir) {
965 		stream = php_stream_alloc(&php_plain_files_dirstream_ops, dir, 0, mode);
966 		if (stream == NULL)
967 			closedir(dir);
968 	}
969 
970 	return stream;
971 }
972 /* }}} */
973 
974 /* {{{ php_stream_fopen */
_php_stream_fopen(const char * filename,const char * mode,zend_string ** opened_path,int options STREAMS_DC)975 PHPAPI php_stream *_php_stream_fopen(const char *filename, const char *mode, zend_string **opened_path, int options STREAMS_DC)
976 {
977 	char realpath[MAXPATHLEN];
978 	int open_flags;
979 	int fd;
980 	php_stream *ret;
981 	int persistent = options & STREAM_OPEN_PERSISTENT;
982 	char *persistent_id = NULL;
983 
984 	if (FAILURE == php_stream_parse_fopen_modes(mode, &open_flags)) {
985 		if (options & REPORT_ERRORS) {
986 			php_error_docref(NULL, E_WARNING, "`%s' is not a valid mode for fopen", mode);
987 		}
988 		return NULL;
989 	}
990 
991 	if (options & STREAM_ASSUME_REALPATH) {
992 		strlcpy(realpath, filename, sizeof(realpath));
993 	} else {
994 		if (expand_filepath(filename, realpath) == NULL) {
995 			return NULL;
996 		}
997 	}
998 
999 	if (persistent) {
1000 		spprintf(&persistent_id, 0, "streams_stdio_%d_%s", open_flags, realpath);
1001 		switch (php_stream_from_persistent_id(persistent_id, &ret)) {
1002 			case PHP_STREAM_PERSISTENT_SUCCESS:
1003 				if (opened_path) {
1004 					//TODO: avoid reallocation???
1005 					*opened_path = zend_string_init(realpath, strlen(realpath), 0);
1006 				}
1007 				/* fall through */
1008 
1009 			case PHP_STREAM_PERSISTENT_FAILURE:
1010 				efree(persistent_id);;
1011 				return ret;
1012 		}
1013 	}
1014 #ifdef PHP_WIN32
1015 	fd = php_win32_ioutil_open(realpath, open_flags, 0666);
1016 #else
1017 	fd = open(realpath, open_flags, 0666);
1018 #endif
1019 	if (fd != -1)	{
1020 
1021 		if (options & STREAM_OPEN_FOR_INCLUDE) {
1022 			ret = php_stream_fopen_from_fd_int_rel(fd, mode, persistent_id);
1023 		} else {
1024 			ret = php_stream_fopen_from_fd_rel(fd, mode, persistent_id);
1025 		}
1026 
1027 		if (ret)	{
1028 			if (opened_path) {
1029 				*opened_path = zend_string_init(realpath, strlen(realpath), 0);
1030 			}
1031 			if (persistent_id) {
1032 				efree(persistent_id);
1033 			}
1034 
1035 			/* WIN32 always set ISREG flag */
1036 #ifndef PHP_WIN32
1037 			/* sanity checks for include/require.
1038 			 * We check these after opening the stream, so that we save
1039 			 * on fstat() syscalls */
1040 			if (options & STREAM_OPEN_FOR_INCLUDE) {
1041 				php_stdio_stream_data *self = (php_stdio_stream_data*)ret->abstract;
1042 				int r;
1043 
1044 				r = do_fstat(self, 0);
1045 				if ((r == 0 && !S_ISREG(self->sb.st_mode))) {
1046 					if (opened_path) {
1047 						zend_string_release(*opened_path);
1048 						*opened_path = NULL;
1049 					}
1050 					php_stream_close(ret);
1051 					return NULL;
1052 				}
1053 			}
1054 
1055 			if (options & STREAM_USE_BLOCKING_PIPE) {
1056 				php_stdio_stream_data *self = (php_stdio_stream_data*)ret->abstract;
1057 				self->is_pipe_blocking = 1;
1058 			}
1059 #endif
1060 
1061 			return ret;
1062 		}
1063 		close(fd);
1064 	}
1065 	if (persistent_id) {
1066 		efree(persistent_id);
1067 	}
1068 	return NULL;
1069 }
1070 /* }}} */
1071 
1072 
php_plain_files_stream_opener(php_stream_wrapper * wrapper,const char * path,const char * mode,int options,zend_string ** opened_path,php_stream_context * context STREAMS_DC)1073 static php_stream *php_plain_files_stream_opener(php_stream_wrapper *wrapper, const char *path, const char *mode,
1074 		int options, zend_string **opened_path, php_stream_context *context STREAMS_DC)
1075 {
1076 	if (((options & STREAM_DISABLE_OPEN_BASEDIR) == 0) && php_check_open_basedir(path)) {
1077 		return NULL;
1078 	}
1079 
1080 	return php_stream_fopen_rel(path, mode, opened_path, options);
1081 }
1082 
php_plain_files_url_stater(php_stream_wrapper * wrapper,const char * url,int flags,php_stream_statbuf * ssb,php_stream_context * context)1083 static int php_plain_files_url_stater(php_stream_wrapper *wrapper, const char *url, int flags, php_stream_statbuf *ssb, php_stream_context *context)
1084 {
1085 	if (strncasecmp(url, "file://", sizeof("file://") - 1) == 0) {
1086 		url += sizeof("file://") - 1;
1087 	}
1088 
1089 	if (php_check_open_basedir_ex(url, (flags & PHP_STREAM_URL_STAT_QUIET) ? 0 : 1)) {
1090 		return -1;
1091 	}
1092 
1093 #ifdef PHP_WIN32
1094 	if (flags & PHP_STREAM_URL_STAT_LINK) {
1095 		return VCWD_LSTAT(url, &ssb->sb);
1096 	}
1097 #else
1098 # ifdef HAVE_SYMLINK
1099 	if (flags & PHP_STREAM_URL_STAT_LINK) {
1100 		return VCWD_LSTAT(url, &ssb->sb);
1101 	} else
1102 # endif
1103 #endif
1104 		return VCWD_STAT(url, &ssb->sb);
1105 }
1106 
php_plain_files_unlink(php_stream_wrapper * wrapper,const char * url,int options,php_stream_context * context)1107 static int php_plain_files_unlink(php_stream_wrapper *wrapper, const char *url, int options, php_stream_context *context)
1108 {
1109 	int ret;
1110 
1111 	if (strncasecmp(url, "file://", sizeof("file://") - 1) == 0) {
1112 		url += sizeof("file://") - 1;
1113 	}
1114 
1115 	if (php_check_open_basedir(url)) {
1116 		return 0;
1117 	}
1118 
1119 	ret = VCWD_UNLINK(url);
1120 	if (ret == -1) {
1121 		if (options & REPORT_ERRORS) {
1122 			php_error_docref1(NULL, url, E_WARNING, "%s", strerror(errno));
1123 		}
1124 		return 0;
1125 	}
1126 
1127 	/* Clear stat cache (and realpath cache) */
1128 	php_clear_stat_cache(1, NULL, 0);
1129 
1130 	return 1;
1131 }
1132 
php_plain_files_rename(php_stream_wrapper * wrapper,const char * url_from,const char * url_to,int options,php_stream_context * context)1133 static int php_plain_files_rename(php_stream_wrapper *wrapper, const char *url_from, const char *url_to, int options, php_stream_context *context)
1134 {
1135 	int ret;
1136 
1137 	if (!url_from || !url_to) {
1138 		return 0;
1139 	}
1140 
1141 #ifdef PHP_WIN32
1142 	if (!php_win32_check_trailing_space(url_from, (int)strlen(url_from))) {
1143 		php_win32_docref2_from_error(ERROR_INVALID_NAME, url_from, url_to);
1144 		return 0;
1145 	}
1146 	if (!php_win32_check_trailing_space(url_to, (int)strlen(url_to))) {
1147 		php_win32_docref2_from_error(ERROR_INVALID_NAME, url_from, url_to);
1148 		return 0;
1149 	}
1150 #endif
1151 
1152 	if (strncasecmp(url_from, "file://", sizeof("file://") - 1) == 0) {
1153 		url_from += sizeof("file://") - 1;
1154 	}
1155 
1156 	if (strncasecmp(url_to, "file://", sizeof("file://") - 1) == 0) {
1157 		url_to += sizeof("file://") - 1;
1158 	}
1159 
1160 	if (php_check_open_basedir(url_from) || php_check_open_basedir(url_to)) {
1161 		return 0;
1162 	}
1163 
1164 	ret = VCWD_RENAME(url_from, url_to);
1165 
1166 	if (ret == -1) {
1167 #ifndef PHP_WIN32
1168 # ifdef EXDEV
1169 		if (errno == EXDEV) {
1170 			zend_stat_t sb;
1171 # if !defined(ZTS) && !defined(TSRM_WIN32) && !defined(NETWARE)
1172 			/* not sure what to do in ZTS case, umask is not thread-safe */
1173 			int oldmask = umask(077);
1174 # endif
1175 			int success = 0;
1176 			if (php_copy_file(url_from, url_to) == SUCCESS) {
1177 				if (VCWD_STAT(url_from, &sb) == 0) {
1178 					success = 1;
1179 #  if !defined(TSRM_WIN32) && !defined(NETWARE)
1180 					/*
1181 					 * Try to set user and permission info on the target.
1182 					 * If we're not root, then some of these may fail.
1183 					 * We try chown first, to set proper group info, relying
1184 					 * on the system environment to have proper umask to not allow
1185 					 * access to the file in the meantime.
1186 					 */
1187 					if (VCWD_CHOWN(url_to, sb.st_uid, sb.st_gid)) {
1188 						php_error_docref2(NULL, url_from, url_to, E_WARNING, "%s", strerror(errno));
1189 						if (errno != EPERM) {
1190 							success = 0;
1191 						}
1192 					}
1193 
1194 					if (success) {
1195 						if (VCWD_CHMOD(url_to, sb.st_mode)) {
1196 							php_error_docref2(NULL, url_from, url_to, E_WARNING, "%s", strerror(errno));
1197 							if (errno != EPERM) {
1198 								success = 0;
1199 							}
1200 						}
1201 					}
1202 #  endif
1203 					if (success) {
1204 						VCWD_UNLINK(url_from);
1205 					}
1206 				} else {
1207 					php_error_docref2(NULL, url_from, url_to, E_WARNING, "%s", strerror(errno));
1208 				}
1209 			} else {
1210 				php_error_docref2(NULL, url_from, url_to, E_WARNING, "%s", strerror(errno));
1211 			}
1212 #  if !defined(ZTS) && !defined(TSRM_WIN32) && !defined(NETWARE)
1213 			umask(oldmask);
1214 #  endif
1215 			return success;
1216 		}
1217 # endif
1218 #endif
1219 
1220 #ifdef PHP_WIN32
1221 		php_win32_docref2_from_error(GetLastError(), url_from, url_to);
1222 #else
1223 		php_error_docref2(NULL, url_from, url_to, E_WARNING, "%s", strerror(errno));
1224 #endif
1225 		return 0;
1226 	}
1227 
1228 	/* Clear stat cache (and realpath cache) */
1229 	php_clear_stat_cache(1, NULL, 0);
1230 
1231 	return 1;
1232 }
1233 
php_plain_files_mkdir(php_stream_wrapper * wrapper,const char * dir,int mode,int options,php_stream_context * context)1234 static int php_plain_files_mkdir(php_stream_wrapper *wrapper, const char *dir, int mode, int options, php_stream_context *context)
1235 {
1236 	int ret, recursive = options & PHP_STREAM_MKDIR_RECURSIVE;
1237 	char *p;
1238 
1239 	if (strncasecmp(dir, "file://", sizeof("file://") - 1) == 0) {
1240 		dir += sizeof("file://") - 1;
1241 	}
1242 
1243 	if (!recursive) {
1244 		ret = php_mkdir(dir, mode);
1245 	} else {
1246 		/* we look for directory separator from the end of string, thus hopefuly reducing our work load */
1247 		char *e;
1248 		zend_stat_t sb;
1249 		int dir_len = (int)strlen(dir);
1250 		int offset = 0;
1251 		char buf[MAXPATHLEN];
1252 
1253 		if (!expand_filepath_with_mode(dir, buf, NULL, 0, CWD_EXPAND )) {
1254 			php_error_docref(NULL, E_WARNING, "Invalid path");
1255 			return 0;
1256 		}
1257 
1258 		e = buf +  strlen(buf);
1259 
1260 		if ((p = memchr(buf, DEFAULT_SLASH, dir_len))) {
1261 			offset = p - buf + 1;
1262 		}
1263 
1264 		if (p && dir_len == 1) {
1265 			/* buf == "DEFAULT_SLASH" */
1266 		}
1267 		else {
1268 			/* find a top level directory we need to create */
1269 			while ( (p = strrchr(buf + offset, DEFAULT_SLASH)) || (offset != 1 && (p = strrchr(buf, DEFAULT_SLASH))) ) {
1270 				int n = 0;
1271 
1272 				*p = '\0';
1273 				while (p > buf && *(p-1) == DEFAULT_SLASH) {
1274 					++n;
1275 					--p;
1276 					*p = '\0';
1277 				}
1278 				if (VCWD_STAT(buf, &sb) == 0) {
1279 					while (1) {
1280 						*p = DEFAULT_SLASH;
1281 						if (!n) break;
1282 						--n;
1283 						++p;
1284 					}
1285 					break;
1286 				}
1287 			}
1288 		}
1289 
1290 		if (p == buf) {
1291 			ret = php_mkdir(dir, mode);
1292 		} else if (!(ret = php_mkdir(buf, mode))) {
1293 			if (!p) {
1294 				p = buf;
1295 			}
1296 			/* create any needed directories if the creation of the 1st directory worked */
1297 			while (++p != e) {
1298 				if (*p == '\0') {
1299 					*p = DEFAULT_SLASH;
1300 					if ((*(p+1) != '\0') &&
1301 						(ret = VCWD_MKDIR(buf, (mode_t)mode)) < 0) {
1302 						if (options & REPORT_ERRORS) {
1303 							php_error_docref(NULL, E_WARNING, "%s", strerror(errno));
1304 						}
1305 						break;
1306 					}
1307 				}
1308 			}
1309 		}
1310 	}
1311 	if (ret < 0) {
1312 		/* Failure */
1313 		return 0;
1314 	} else {
1315 		/* Success */
1316 		return 1;
1317 	}
1318 }
1319 
php_plain_files_rmdir(php_stream_wrapper * wrapper,const char * url,int options,php_stream_context * context)1320 static int php_plain_files_rmdir(php_stream_wrapper *wrapper, const char *url, int options, php_stream_context *context)
1321 {
1322 	if (strncasecmp(url, "file://", sizeof("file://") - 1) == 0) {
1323 		url += sizeof("file://") - 1;
1324 	}
1325 
1326 	if (php_check_open_basedir(url)) {
1327 		return 0;
1328 	}
1329 
1330 #ifdef PHP_WIN32
1331 	if (!php_win32_check_trailing_space(url, (int)strlen(url))) {
1332 		php_error_docref1(NULL, url, E_WARNING, "%s", strerror(ENOENT));
1333 		return 0;
1334 	}
1335 #endif
1336 
1337 	if (VCWD_RMDIR(url) < 0) {
1338 		php_error_docref1(NULL, url, E_WARNING, "%s", strerror(errno));
1339 		return 0;
1340 	}
1341 
1342 	/* Clear stat cache (and realpath cache) */
1343 	php_clear_stat_cache(1, NULL, 0);
1344 
1345 	return 1;
1346 }
1347 
php_plain_files_metadata(php_stream_wrapper * wrapper,const char * url,int option,void * value,php_stream_context * context)1348 static int php_plain_files_metadata(php_stream_wrapper *wrapper, const char *url, int option, void *value, php_stream_context *context)
1349 {
1350 	struct utimbuf *newtime;
1351 #if !defined(WINDOWS) && !defined(NETWARE)
1352 	uid_t uid;
1353 	gid_t gid;
1354 #endif
1355 	mode_t mode;
1356 	int ret = 0;
1357 #ifdef PHP_WIN32
1358 	int url_len = (int)strlen(url);
1359 #endif
1360 
1361 #ifdef PHP_WIN32
1362 	if (!php_win32_check_trailing_space(url, url_len)) {
1363 		php_error_docref1(NULL, url, E_WARNING, "%s", strerror(ENOENT));
1364 		return 0;
1365 	}
1366 #endif
1367 
1368 	if (strncasecmp(url, "file://", sizeof("file://") - 1) == 0) {
1369 		url += sizeof("file://") - 1;
1370 	}
1371 
1372 	if (php_check_open_basedir(url)) {
1373 		return 0;
1374 	}
1375 
1376 	switch(option) {
1377 		case PHP_STREAM_META_TOUCH:
1378 			newtime = (struct utimbuf *)value;
1379 			if (VCWD_ACCESS(url, F_OK) != 0) {
1380 				FILE *file = VCWD_FOPEN(url, "w");
1381 				if (file == NULL) {
1382 					php_error_docref1(NULL, url, E_WARNING, "Unable to create file %s because %s", url, strerror(errno));
1383 					return 0;
1384 				}
1385 				fclose(file);
1386 			}
1387 
1388 			ret = VCWD_UTIME(url, newtime);
1389 			break;
1390 #if !defined(WINDOWS) && !defined(NETWARE)
1391 		case PHP_STREAM_META_OWNER_NAME:
1392 		case PHP_STREAM_META_OWNER:
1393 			if(option == PHP_STREAM_META_OWNER_NAME) {
1394 				if(php_get_uid_by_name((char *)value, &uid) != SUCCESS) {
1395 					php_error_docref1(NULL, url, E_WARNING, "Unable to find uid for %s", (char *)value);
1396 					return 0;
1397 				}
1398 			} else {
1399 				uid = (uid_t)*(long *)value;
1400 			}
1401 			ret = VCWD_CHOWN(url, uid, -1);
1402 			break;
1403 		case PHP_STREAM_META_GROUP:
1404 		case PHP_STREAM_META_GROUP_NAME:
1405 			if(option == PHP_STREAM_META_GROUP_NAME) {
1406 				if(php_get_gid_by_name((char *)value, &gid) != SUCCESS) {
1407 					php_error_docref1(NULL, url, E_WARNING, "Unable to find gid for %s", (char *)value);
1408 					return 0;
1409 				}
1410 			} else {
1411 				gid = (gid_t)*(long *)value;
1412 			}
1413 			ret = VCWD_CHOWN(url, -1, gid);
1414 			break;
1415 #endif
1416 		case PHP_STREAM_META_ACCESS:
1417 			mode = (mode_t)*(zend_long *)value;
1418 			ret = VCWD_CHMOD(url, mode);
1419 			break;
1420 		default:
1421 			php_error_docref1(NULL, url, E_WARNING, "Unknown option %d for stream_metadata", option);
1422 			return 0;
1423 	}
1424 	if (ret == -1) {
1425 		php_error_docref1(NULL, url, E_WARNING, "Operation failed: %s", strerror(errno));
1426 		return 0;
1427 	}
1428 	php_clear_stat_cache(0, NULL, 0);
1429 	return 1;
1430 }
1431 
1432 
1433 static php_stream_wrapper_ops php_plain_files_wrapper_ops = {
1434 	php_plain_files_stream_opener,
1435 	NULL,
1436 	NULL,
1437 	php_plain_files_url_stater,
1438 	php_plain_files_dir_opener,
1439 	"plainfile",
1440 	php_plain_files_unlink,
1441 	php_plain_files_rename,
1442 	php_plain_files_mkdir,
1443 	php_plain_files_rmdir,
1444 	php_plain_files_metadata
1445 };
1446 
1447 PHPAPI php_stream_wrapper php_plain_files_wrapper = {
1448 	&php_plain_files_wrapper_ops,
1449 	NULL,
1450 	0
1451 };
1452 
1453 /* {{{ php_stream_fopen_with_path */
_php_stream_fopen_with_path(const char * filename,const char * mode,const char * path,zend_string ** opened_path,int options STREAMS_DC)1454 PHPAPI php_stream *_php_stream_fopen_with_path(const char *filename, const char *mode, const char *path, zend_string **opened_path, int options STREAMS_DC)
1455 {
1456 	/* code ripped off from fopen_wrappers.c */
1457 	char *pathbuf, *end;
1458 	const char *ptr;
1459 	char trypath[MAXPATHLEN];
1460 	php_stream *stream;
1461 	int filename_length;
1462 	zend_string *exec_filename;
1463 
1464 	if (opened_path) {
1465 		*opened_path = NULL;
1466 	}
1467 
1468 	if(!filename) {
1469 		return NULL;
1470 	}
1471 
1472 	filename_length = (int)strlen(filename);
1473 #ifndef PHP_WIN32
1474 	(void) filename_length;
1475 #endif
1476 
1477 	/* Relative path open */
1478 	if (*filename == '.' && (IS_SLASH(filename[1]) || filename[1] == '.')) {
1479 		/* further checks, we could have ....... filenames */
1480 		ptr = filename + 1;
1481 		if (*ptr == '.') {
1482 			while (*(++ptr) == '.');
1483 			if (!IS_SLASH(*ptr)) { /* not a relative path after all */
1484 				goto not_relative_path;
1485 			}
1486 		}
1487 
1488 
1489 		if (((options & STREAM_DISABLE_OPEN_BASEDIR) == 0) && php_check_open_basedir(filename)) {
1490 			return NULL;
1491 		}
1492 
1493 		return php_stream_fopen_rel(filename, mode, opened_path, options);
1494 	}
1495 
1496 not_relative_path:
1497 
1498 	/* Absolute path open */
1499 	if (IS_ABSOLUTE_PATH(filename, filename_length)) {
1500 
1501 		if (((options & STREAM_DISABLE_OPEN_BASEDIR) == 0) && php_check_open_basedir(filename)) {
1502 			return NULL;
1503 		}
1504 
1505 		return php_stream_fopen_rel(filename, mode, opened_path, options);
1506 	}
1507 
1508 #ifdef PHP_WIN32
1509 	if (IS_SLASH(filename[0])) {
1510 		size_t cwd_len;
1511 		char *cwd;
1512 		cwd = virtual_getcwd_ex(&cwd_len);
1513 		/* getcwd() will return always return [DRIVE_LETTER]:/) on windows. */
1514 		*(cwd+3) = '\0';
1515 
1516 		if (snprintf(trypath, MAXPATHLEN, "%s%s", cwd, filename) >= MAXPATHLEN) {
1517 			php_error_docref(NULL, E_NOTICE, "%s/%s path was truncated to %d", cwd, filename, MAXPATHLEN);
1518 		}
1519 
1520 		efree(cwd);
1521 
1522 		if (((options & STREAM_DISABLE_OPEN_BASEDIR) == 0) && php_check_open_basedir(trypath)) {
1523 			return NULL;
1524 		}
1525 
1526 		return php_stream_fopen_rel(trypath, mode, opened_path, options);
1527 	}
1528 #endif
1529 
1530 	if (!path || !*path) {
1531 		return php_stream_fopen_rel(filename, mode, opened_path, options);
1532 	}
1533 
1534 	/* check in provided path */
1535 	/* append the calling scripts' current working directory
1536 	 * as a fall back case
1537 	 */
1538 	if (zend_is_executing() &&
1539 	    (exec_filename = zend_get_executed_filename_ex()) != NULL) {
1540 		const char *exec_fname = ZSTR_VAL(exec_filename);
1541 		size_t exec_fname_length = ZSTR_LEN(exec_filename);
1542 
1543 		while ((--exec_fname_length < SIZE_MAX) && !IS_SLASH(exec_fname[exec_fname_length]));
1544 		if (exec_fname_length<=0) {
1545 			/* no path */
1546 			pathbuf = estrdup(path);
1547 		} else {
1548 			size_t path_length = strlen(path);
1549 
1550 			pathbuf = (char *) emalloc(exec_fname_length + path_length +1 +1);
1551 			memcpy(pathbuf, path, path_length);
1552 			pathbuf[path_length] = DEFAULT_DIR_SEPARATOR;
1553 			memcpy(pathbuf+path_length+1, exec_fname, exec_fname_length);
1554 			pathbuf[path_length + exec_fname_length +1] = '\0';
1555 		}
1556 	} else {
1557 		pathbuf = estrdup(path);
1558 	}
1559 
1560 	ptr = pathbuf;
1561 
1562 	while (ptr && *ptr) {
1563 		end = strchr(ptr, DEFAULT_DIR_SEPARATOR);
1564 		if (end != NULL) {
1565 			*end = '\0';
1566 			end++;
1567 		}
1568 		if (*ptr == '\0') {
1569 			goto stream_skip;
1570 		}
1571 		if (snprintf(trypath, MAXPATHLEN, "%s/%s", ptr, filename) >= MAXPATHLEN) {
1572 			php_error_docref(NULL, E_NOTICE, "%s/%s path was truncated to %d", ptr, filename, MAXPATHLEN);
1573 		}
1574 
1575 		if (((options & STREAM_DISABLE_OPEN_BASEDIR) == 0) && php_check_open_basedir_ex(trypath, 0)) {
1576 			goto stream_skip;
1577 		}
1578 
1579 		stream = php_stream_fopen_rel(trypath, mode, opened_path, options);
1580 		if (stream) {
1581 			efree(pathbuf);
1582 			return stream;
1583 		}
1584 stream_skip:
1585 		ptr = end;
1586 	} /* end provided path */
1587 
1588 	efree(pathbuf);
1589 	return NULL;
1590 
1591 }
1592 /* }}} */
1593 
1594 /*
1595  * Local variables:
1596  * tab-width: 4
1597  * c-basic-offset: 4
1598  * End:
1599  * vim600: noet sw=4 ts=4 fdm=marker
1600  * vim<600: noet sw=4 ts=4
1601  */
1602