xref: /PHP-7.2/main/streams/plain_wrapper.c (revision b864abfe)
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 #ifndef PHP_WIN32
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->offset > data->sb.st_size) {
703 							range->offset = data->sb.st_size;
704 						}
705 						if (range->length == 0 ||
706 								range->length > data->sb.st_size - range->offset) {
707 							range->length = data->sb.st_size - range->offset;
708 						}
709 						switch (range->mode) {
710 							case PHP_STREAM_MAP_MODE_READONLY:
711 								prot = PROT_READ;
712 								flags = MAP_PRIVATE;
713 								break;
714 							case PHP_STREAM_MAP_MODE_READWRITE:
715 								prot = PROT_READ | PROT_WRITE;
716 								flags = MAP_PRIVATE;
717 								break;
718 							case PHP_STREAM_MAP_MODE_SHARED_READONLY:
719 								prot = PROT_READ;
720 								flags = MAP_SHARED;
721 								break;
722 							case PHP_STREAM_MAP_MODE_SHARED_READWRITE:
723 								prot = PROT_READ | PROT_WRITE;
724 								flags = MAP_SHARED;
725 								break;
726 							default:
727 								return PHP_STREAM_OPTION_RETURN_ERR;
728 						}
729 						range->mapped = (char*)mmap(NULL, range->length, prot, flags, fd, range->offset);
730 						if (range->mapped == (char*)MAP_FAILED) {
731 							range->mapped = NULL;
732 							return PHP_STREAM_OPTION_RETURN_ERR;
733 						}
734 						/* remember the mapping */
735 						data->last_mapped_addr = range->mapped;
736 						data->last_mapped_len = range->length;
737 						return PHP_STREAM_OPTION_RETURN_OK;
738 
739 					case PHP_STREAM_MMAP_UNMAP:
740 						if (data->last_mapped_addr) {
741 							munmap(data->last_mapped_addr, data->last_mapped_len);
742 							data->last_mapped_addr = NULL;
743 
744 							return PHP_STREAM_OPTION_RETURN_OK;
745 						}
746 						return PHP_STREAM_OPTION_RETURN_ERR;
747 				}
748 			}
749 #elif defined(PHP_WIN32)
750 			{
751 				php_stream_mmap_range *range = (php_stream_mmap_range*)ptrparam;
752 				HANDLE hfile = (HANDLE)_get_osfhandle(fd);
753 				DWORD prot, acc, loffs = 0, delta = 0;
754 
755 				switch (value) {
756 					case PHP_STREAM_MMAP_SUPPORTED:
757 						return hfile == INVALID_HANDLE_VALUE ? PHP_STREAM_OPTION_RETURN_ERR : PHP_STREAM_OPTION_RETURN_OK;
758 
759 					case PHP_STREAM_MMAP_MAP_RANGE:
760 						switch (range->mode) {
761 							case PHP_STREAM_MAP_MODE_READONLY:
762 								prot = PAGE_READONLY;
763 								acc = FILE_MAP_READ;
764 								break;
765 							case PHP_STREAM_MAP_MODE_READWRITE:
766 								prot = PAGE_READWRITE;
767 								acc = FILE_MAP_READ | FILE_MAP_WRITE;
768 								break;
769 							case PHP_STREAM_MAP_MODE_SHARED_READONLY:
770 								prot = PAGE_READONLY;
771 								acc = FILE_MAP_READ;
772 								/* TODO: we should assign a name for the mapping */
773 								break;
774 							case PHP_STREAM_MAP_MODE_SHARED_READWRITE:
775 								prot = PAGE_READWRITE;
776 								acc = FILE_MAP_READ | FILE_MAP_WRITE;
777 								/* TODO: we should assign a name for the mapping */
778 								break;
779 							default:
780 								return PHP_STREAM_OPTION_RETURN_ERR;
781 						}
782 
783 						/* create a mapping capable of viewing the whole file (this costs no real resources) */
784 						data->file_mapping = CreateFileMapping(hfile, NULL, prot, 0, 0, NULL);
785 
786 						if (data->file_mapping == NULL) {
787 							return PHP_STREAM_OPTION_RETURN_ERR;
788 						}
789 
790 						size = GetFileSize(hfile, NULL);
791 						if (range->length == 0 && range->offset > 0 && range->offset < size) {
792 							range->length = size - range->offset;
793 						}
794 						if (range->length == 0 || range->length > size) {
795 							range->length = size;
796 						}
797 						if (range->offset >= size) {
798 							range->offset = size;
799 							range->length = 0;
800 						}
801 
802 						/* figure out how big a chunk to map to be able to view the part that we need */
803 						if (range->offset != 0) {
804 							SYSTEM_INFO info;
805 							DWORD gran;
806 
807 							GetSystemInfo(&info);
808 							gran = info.dwAllocationGranularity;
809 							loffs = ((DWORD)range->offset / gran) * gran;
810 							delta = (DWORD)range->offset - loffs;
811 						}
812 
813 						data->last_mapped_addr = MapViewOfFile(data->file_mapping, acc, 0, loffs, range->length + delta);
814 
815 						if (data->last_mapped_addr) {
816 							/* give them back the address of the start offset they requested */
817 							range->mapped = data->last_mapped_addr + delta;
818 							return PHP_STREAM_OPTION_RETURN_OK;
819 						}
820 
821 						CloseHandle(data->file_mapping);
822 						data->file_mapping = NULL;
823 
824 						return PHP_STREAM_OPTION_RETURN_ERR;
825 
826 					case PHP_STREAM_MMAP_UNMAP:
827 						if (data->last_mapped_addr) {
828 							UnmapViewOfFile(data->last_mapped_addr);
829 							data->last_mapped_addr = NULL;
830 							CloseHandle(data->file_mapping);
831 							data->file_mapping = NULL;
832 							return PHP_STREAM_OPTION_RETURN_OK;
833 						}
834 						return PHP_STREAM_OPTION_RETURN_ERR;
835 
836 					default:
837 						return PHP_STREAM_OPTION_RETURN_ERR;
838 				}
839 			}
840 
841 #endif
842 			return PHP_STREAM_OPTION_RETURN_NOTIMPL;
843 
844 		case PHP_STREAM_OPTION_TRUNCATE_API:
845 			switch (value) {
846 				case PHP_STREAM_TRUNCATE_SUPPORTED:
847 					return fd == -1 ? PHP_STREAM_OPTION_RETURN_ERR : PHP_STREAM_OPTION_RETURN_OK;
848 
849 				case PHP_STREAM_TRUNCATE_SET_SIZE: {
850 					ptrdiff_t new_size = *(ptrdiff_t*)ptrparam;
851 					if (new_size < 0) {
852 						return PHP_STREAM_OPTION_RETURN_ERR;
853 					}
854 					return ftruncate(fd, new_size) == 0 ? PHP_STREAM_OPTION_RETURN_OK : PHP_STREAM_OPTION_RETURN_ERR;
855 				}
856 			}
857 
858 #ifdef PHP_WIN32
859 		case PHP_STREAM_OPTION_PIPE_BLOCKING:
860 			data->is_pipe_blocking = value;
861 			return PHP_STREAM_OPTION_RETURN_OK;
862 #endif
863 		case PHP_STREAM_OPTION_META_DATA_API:
864 			if (fd == -1)
865 				return -1;
866 #ifdef O_NONBLOCK
867 			flags = fcntl(fd, F_GETFL, 0);
868 
869 			add_assoc_bool((zval*)ptrparam, "timed_out", 0);
870 			add_assoc_bool((zval*)ptrparam, "blocked", (flags & O_NONBLOCK)? 0 : 1);
871 			add_assoc_bool((zval*)ptrparam, "eof", stream->eof);
872 
873 			return PHP_STREAM_OPTION_RETURN_OK;
874 #endif
875 			return -1;
876 		default:
877 			return PHP_STREAM_OPTION_RETURN_NOTIMPL;
878 	}
879 }
880 
881 PHPAPI php_stream_ops	php_stream_stdio_ops = {
882 	php_stdiop_write, php_stdiop_read,
883 	php_stdiop_close, php_stdiop_flush,
884 	"STDIO",
885 	php_stdiop_seek,
886 	php_stdiop_cast,
887 	php_stdiop_stat,
888 	php_stdiop_set_option
889 };
890 /* }}} */
891 
892 /* {{{ plain files opendir/readdir implementation */
php_plain_files_dirstream_read(php_stream * stream,char * buf,size_t count)893 static size_t php_plain_files_dirstream_read(php_stream *stream, char *buf, size_t count)
894 {
895 	DIR *dir = (DIR*)stream->abstract;
896 	/* avoid libc5 readdir problems */
897 	char entry[sizeof(struct dirent)+MAXPATHLEN];
898 	struct dirent *result = (struct dirent *)&entry;
899 	php_stream_dirent *ent = (php_stream_dirent*)buf;
900 
901 	/* avoid problems if someone mis-uses the stream */
902 	if (count != sizeof(php_stream_dirent))
903 		return 0;
904 
905 	if (php_readdir_r(dir, (struct dirent *)entry, &result) == 0 && result) {
906 		PHP_STRLCPY(ent->d_name, result->d_name, sizeof(ent->d_name), strlen(result->d_name));
907 		return sizeof(php_stream_dirent);
908 	}
909 	return 0;
910 }
911 
php_plain_files_dirstream_close(php_stream * stream,int close_handle)912 static int php_plain_files_dirstream_close(php_stream *stream, int close_handle)
913 {
914 	return closedir((DIR *)stream->abstract);
915 }
916 
php_plain_files_dirstream_rewind(php_stream * stream,zend_off_t offset,int whence,zend_off_t * newoffs)917 static int php_plain_files_dirstream_rewind(php_stream *stream, zend_off_t offset, int whence, zend_off_t *newoffs)
918 {
919 	rewinddir((DIR *)stream->abstract);
920 	return 0;
921 }
922 
923 static php_stream_ops	php_plain_files_dirstream_ops = {
924 	NULL, php_plain_files_dirstream_read,
925 	php_plain_files_dirstream_close, NULL,
926 	"dir",
927 	php_plain_files_dirstream_rewind,
928 	NULL, /* cast */
929 	NULL, /* stat */
930 	NULL  /* set_option */
931 };
932 
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)933 static php_stream *php_plain_files_dir_opener(php_stream_wrapper *wrapper, const char *path, const char *mode,
934 		int options, zend_string **opened_path, php_stream_context *context STREAMS_DC)
935 {
936 	DIR *dir = NULL;
937 	php_stream *stream = NULL;
938 
939 #ifdef HAVE_GLOB
940 	if (options & STREAM_USE_GLOB_DIR_OPEN) {
941 		return php_glob_stream_wrapper.wops->dir_opener(&php_glob_stream_wrapper, path, mode, options, opened_path, context STREAMS_REL_CC);
942 	}
943 #endif
944 
945 	if (((options & STREAM_DISABLE_OPEN_BASEDIR) == 0) && php_check_open_basedir(path)) {
946 		return NULL;
947 	}
948 
949 	dir = VCWD_OPENDIR(path);
950 
951 #ifdef PHP_WIN32
952 	if (!dir) {
953 		php_win32_docref2_from_error(GetLastError(), path, path);
954 	}
955 
956 	if (dir && dir->finished) {
957 		closedir(dir);
958 		dir = NULL;
959 	}
960 #endif
961 	if (dir) {
962 		stream = php_stream_alloc(&php_plain_files_dirstream_ops, dir, 0, mode);
963 		if (stream == NULL)
964 			closedir(dir);
965 	}
966 
967 	return stream;
968 }
969 /* }}} */
970 
971 /* {{{ php_stream_fopen */
_php_stream_fopen(const char * filename,const char * mode,zend_string ** opened_path,int options STREAMS_DC)972 PHPAPI php_stream *_php_stream_fopen(const char *filename, const char *mode, zend_string **opened_path, int options STREAMS_DC)
973 {
974 	char realpath[MAXPATHLEN];
975 	int open_flags;
976 	int fd;
977 	php_stream *ret;
978 	int persistent = options & STREAM_OPEN_PERSISTENT;
979 	char *persistent_id = NULL;
980 
981 	if (FAILURE == php_stream_parse_fopen_modes(mode, &open_flags)) {
982 		if (options & REPORT_ERRORS) {
983 			php_error_docref(NULL, E_WARNING, "`%s' is not a valid mode for fopen", mode);
984 		}
985 		return NULL;
986 	}
987 
988 	if (options & STREAM_ASSUME_REALPATH) {
989 		strlcpy(realpath, filename, sizeof(realpath));
990 	} else {
991 		if (expand_filepath(filename, realpath) == NULL) {
992 			return NULL;
993 		}
994 	}
995 
996 	if (persistent) {
997 		spprintf(&persistent_id, 0, "streams_stdio_%d_%s", open_flags, realpath);
998 		switch (php_stream_from_persistent_id(persistent_id, &ret)) {
999 			case PHP_STREAM_PERSISTENT_SUCCESS:
1000 				if (opened_path) {
1001 					//TODO: avoid reallocation???
1002 					*opened_path = zend_string_init(realpath, strlen(realpath), 0);
1003 				}
1004 				/* fall through */
1005 
1006 			case PHP_STREAM_PERSISTENT_FAILURE:
1007 				efree(persistent_id);
1008 				return ret;
1009 		}
1010 	}
1011 #ifdef PHP_WIN32
1012 	fd = php_win32_ioutil_open(realpath, open_flags, 0666);
1013 #else
1014 	fd = open(realpath, open_flags, 0666);
1015 #endif
1016 	if (fd != -1)	{
1017 
1018 		if (options & STREAM_OPEN_FOR_INCLUDE) {
1019 			ret = php_stream_fopen_from_fd_int_rel(fd, mode, persistent_id);
1020 		} else {
1021 			ret = php_stream_fopen_from_fd_rel(fd, mode, persistent_id);
1022 		}
1023 
1024 		if (ret)	{
1025 			if (opened_path) {
1026 				*opened_path = zend_string_init(realpath, strlen(realpath), 0);
1027 			}
1028 			if (persistent_id) {
1029 				efree(persistent_id);
1030 			}
1031 
1032 			/* WIN32 always set ISREG flag */
1033 #ifndef PHP_WIN32
1034 			/* sanity checks for include/require.
1035 			 * We check these after opening the stream, so that we save
1036 			 * on fstat() syscalls */
1037 			if (options & STREAM_OPEN_FOR_INCLUDE) {
1038 				php_stdio_stream_data *self = (php_stdio_stream_data*)ret->abstract;
1039 				int r;
1040 
1041 				r = do_fstat(self, 0);
1042 				if ((r == 0 && !S_ISREG(self->sb.st_mode))) {
1043 					if (opened_path) {
1044 						zend_string_release(*opened_path);
1045 						*opened_path = NULL;
1046 					}
1047 					php_stream_close(ret);
1048 					return NULL;
1049 				}
1050 			}
1051 
1052 			if (options & STREAM_USE_BLOCKING_PIPE) {
1053 				php_stdio_stream_data *self = (php_stdio_stream_data*)ret->abstract;
1054 				self->is_pipe_blocking = 1;
1055 			}
1056 #endif
1057 
1058 			return ret;
1059 		}
1060 		close(fd);
1061 	}
1062 	if (persistent_id) {
1063 		efree(persistent_id);
1064 	}
1065 	return NULL;
1066 }
1067 /* }}} */
1068 
1069 
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)1070 static php_stream *php_plain_files_stream_opener(php_stream_wrapper *wrapper, const char *path, const char *mode,
1071 		int options, zend_string **opened_path, php_stream_context *context STREAMS_DC)
1072 {
1073 	if (((options & STREAM_DISABLE_OPEN_BASEDIR) == 0) && php_check_open_basedir(path)) {
1074 		return NULL;
1075 	}
1076 
1077 	return php_stream_fopen_rel(path, mode, opened_path, options);
1078 }
1079 
php_plain_files_url_stater(php_stream_wrapper * wrapper,const char * url,int flags,php_stream_statbuf * ssb,php_stream_context * context)1080 static int php_plain_files_url_stater(php_stream_wrapper *wrapper, const char *url, int flags, php_stream_statbuf *ssb, php_stream_context *context)
1081 {
1082 	if (strncasecmp(url, "file://", sizeof("file://") - 1) == 0) {
1083 		url += sizeof("file://") - 1;
1084 	}
1085 
1086 	if (php_check_open_basedir_ex(url, (flags & PHP_STREAM_URL_STAT_QUIET) ? 0 : 1)) {
1087 		return -1;
1088 	}
1089 
1090 #ifdef PHP_WIN32
1091 	if (flags & PHP_STREAM_URL_STAT_LINK) {
1092 		return VCWD_LSTAT(url, &ssb->sb);
1093 	}
1094 #else
1095 # ifdef HAVE_SYMLINK
1096 	if (flags & PHP_STREAM_URL_STAT_LINK) {
1097 		return VCWD_LSTAT(url, &ssb->sb);
1098 	} else
1099 # endif
1100 #endif
1101 		return VCWD_STAT(url, &ssb->sb);
1102 }
1103 
php_plain_files_unlink(php_stream_wrapper * wrapper,const char * url,int options,php_stream_context * context)1104 static int php_plain_files_unlink(php_stream_wrapper *wrapper, const char *url, int options, php_stream_context *context)
1105 {
1106 	int ret;
1107 
1108 	if (strncasecmp(url, "file://", sizeof("file://") - 1) == 0) {
1109 		url += sizeof("file://") - 1;
1110 	}
1111 
1112 	if (php_check_open_basedir(url)) {
1113 		return 0;
1114 	}
1115 
1116 	ret = VCWD_UNLINK(url);
1117 	if (ret == -1) {
1118 		if (options & REPORT_ERRORS) {
1119 			php_error_docref1(NULL, url, E_WARNING, "%s", strerror(errno));
1120 		}
1121 		return 0;
1122 	}
1123 
1124 	/* Clear stat cache (and realpath cache) */
1125 	php_clear_stat_cache(1, NULL, 0);
1126 
1127 	return 1;
1128 }
1129 
php_plain_files_rename(php_stream_wrapper * wrapper,const char * url_from,const char * url_to,int options,php_stream_context * context)1130 static int php_plain_files_rename(php_stream_wrapper *wrapper, const char *url_from, const char *url_to, int options, php_stream_context *context)
1131 {
1132 	int ret;
1133 
1134 	if (!url_from || !url_to) {
1135 		return 0;
1136 	}
1137 
1138 #ifdef PHP_WIN32
1139 	if (!php_win32_check_trailing_space(url_from, strlen(url_from))) {
1140 		php_win32_docref2_from_error(ERROR_INVALID_NAME, url_from, url_to);
1141 		return 0;
1142 	}
1143 	if (!php_win32_check_trailing_space(url_to, strlen(url_to))) {
1144 		php_win32_docref2_from_error(ERROR_INVALID_NAME, url_from, url_to);
1145 		return 0;
1146 	}
1147 #endif
1148 
1149 	if (strncasecmp(url_from, "file://", sizeof("file://") - 1) == 0) {
1150 		url_from += sizeof("file://") - 1;
1151 	}
1152 
1153 	if (strncasecmp(url_to, "file://", sizeof("file://") - 1) == 0) {
1154 		url_to += sizeof("file://") - 1;
1155 	}
1156 
1157 	if (php_check_open_basedir(url_from) || php_check_open_basedir(url_to)) {
1158 		return 0;
1159 	}
1160 
1161 	ret = VCWD_RENAME(url_from, url_to);
1162 
1163 	if (ret == -1) {
1164 #ifndef PHP_WIN32
1165 # ifdef EXDEV
1166 		if (errno == EXDEV) {
1167 			zend_stat_t sb;
1168 # if !defined(ZTS) && !defined(TSRM_WIN32)
1169             /* not sure what to do in ZTS case, umask is not thread-safe */
1170 			int oldmask = umask(077);
1171 # endif
1172 			int success = 0;
1173 			if (php_copy_file(url_from, url_to) == SUCCESS) {
1174 				if (VCWD_STAT(url_from, &sb) == 0) {
1175 					success = 1;
1176 #  if !defined(TSRM_WIN32)
1177 					/*
1178 					 * Try to set user and permission info on the target.
1179 					 * If we're not root, then some of these may fail.
1180 					 * We try chown first, to set proper group info, relying
1181 					 * on the system environment to have proper umask to not allow
1182 					 * access to the file in the meantime.
1183 					 */
1184 					if (VCWD_CHOWN(url_to, sb.st_uid, sb.st_gid)) {
1185 						php_error_docref2(NULL, url_from, url_to, E_WARNING, "%s", strerror(errno));
1186 						if (errno != EPERM) {
1187 							success = 0;
1188 						}
1189 					}
1190 
1191 					if (success) {
1192 						if (VCWD_CHMOD(url_to, sb.st_mode)) {
1193 							php_error_docref2(NULL, url_from, url_to, E_WARNING, "%s", strerror(errno));
1194 							if (errno != EPERM) {
1195 								success = 0;
1196 							}
1197 						}
1198 					}
1199 #  endif
1200 					if (success) {
1201 						VCWD_UNLINK(url_from);
1202 					}
1203 				} else {
1204 					php_error_docref2(NULL, url_from, url_to, E_WARNING, "%s", strerror(errno));
1205 				}
1206 			} else {
1207 				php_error_docref2(NULL, url_from, url_to, E_WARNING, "%s", strerror(errno));
1208 			}
1209 #  if !defined(ZTS) && !defined(TSRM_WIN32)
1210 			umask(oldmask);
1211 #  endif
1212 			return success;
1213 		}
1214 # endif
1215 #endif
1216 
1217 #ifdef PHP_WIN32
1218 		php_win32_docref2_from_error(GetLastError(), url_from, url_to);
1219 #else
1220 		php_error_docref2(NULL, url_from, url_to, E_WARNING, "%s", strerror(errno));
1221 #endif
1222 		return 0;
1223 	}
1224 
1225 	/* Clear stat cache (and realpath cache) */
1226 	php_clear_stat_cache(1, NULL, 0);
1227 
1228 	return 1;
1229 }
1230 
php_plain_files_mkdir(php_stream_wrapper * wrapper,const char * dir,int mode,int options,php_stream_context * context)1231 static int php_plain_files_mkdir(php_stream_wrapper *wrapper, const char *dir, int mode, int options, php_stream_context *context)
1232 {
1233 	int ret, recursive = options & PHP_STREAM_MKDIR_RECURSIVE;
1234 	char *p;
1235 
1236 	if (strncasecmp(dir, "file://", sizeof("file://") - 1) == 0) {
1237 		dir += sizeof("file://") - 1;
1238 	}
1239 
1240 	if (!recursive) {
1241 		ret = php_mkdir(dir, mode);
1242 	} else {
1243 		/* we look for directory separator from the end of string, thus hopefuly reducing our work load */
1244 		char *e;
1245 		zend_stat_t sb;
1246 		size_t dir_len = strlen(dir), offset = 0;
1247 		char buf[MAXPATHLEN];
1248 
1249 		if (!expand_filepath_with_mode(dir, buf, NULL, 0, CWD_EXPAND )) {
1250 			php_error_docref(NULL, E_WARNING, "Invalid path");
1251 			return 0;
1252 		}
1253 
1254 		e = buf +  strlen(buf);
1255 
1256 		if ((p = memchr(buf, DEFAULT_SLASH, dir_len))) {
1257 			offset = p - buf + 1;
1258 		}
1259 
1260 		if (p && dir_len == 1) {
1261 			/* buf == "DEFAULT_SLASH" */
1262 		}
1263 		else {
1264 			/* find a top level directory we need to create */
1265 			while ( (p = strrchr(buf + offset, DEFAULT_SLASH)) || (offset != 1 && (p = strrchr(buf, DEFAULT_SLASH))) ) {
1266 				int n = 0;
1267 
1268 				*p = '\0';
1269 				while (p > buf && *(p-1) == DEFAULT_SLASH) {
1270 					++n;
1271 					--p;
1272 					*p = '\0';
1273 				}
1274 				if (VCWD_STAT(buf, &sb) == 0) {
1275 					while (1) {
1276 						*p = DEFAULT_SLASH;
1277 						if (!n) break;
1278 						--n;
1279 						++p;
1280 					}
1281 					break;
1282 				}
1283 			}
1284 		}
1285 
1286 		if (p == buf) {
1287 			ret = php_mkdir(dir, mode);
1288 		} else if (!(ret = php_mkdir(buf, mode))) {
1289 			if (!p) {
1290 				p = buf;
1291 			}
1292 			/* create any needed directories if the creation of the 1st directory worked */
1293 			while (++p != e) {
1294 				if (*p == '\0') {
1295 					*p = DEFAULT_SLASH;
1296 					if ((*(p+1) != '\0') &&
1297 						(ret = VCWD_MKDIR(buf, (mode_t)mode)) < 0) {
1298 						if (options & REPORT_ERRORS) {
1299 							php_error_docref(NULL, E_WARNING, "%s", strerror(errno));
1300 						}
1301 						break;
1302 					}
1303 				}
1304 			}
1305 		}
1306 	}
1307 	if (ret < 0) {
1308 		/* Failure */
1309 		return 0;
1310 	} else {
1311 		/* Success */
1312 		return 1;
1313 	}
1314 }
1315 
php_plain_files_rmdir(php_stream_wrapper * wrapper,const char * url,int options,php_stream_context * context)1316 static int php_plain_files_rmdir(php_stream_wrapper *wrapper, const char *url, int options, php_stream_context *context)
1317 {
1318 	if (strncasecmp(url, "file://", sizeof("file://") - 1) == 0) {
1319 		url += sizeof("file://") - 1;
1320 	}
1321 
1322 	if (php_check_open_basedir(url)) {
1323 		return 0;
1324 	}
1325 
1326 #ifdef PHP_WIN32
1327 	if (!php_win32_check_trailing_space(url, strlen(url))) {
1328 		php_error_docref1(NULL, url, E_WARNING, "%s", strerror(ENOENT));
1329 		return 0;
1330 	}
1331 #endif
1332 
1333 	if (VCWD_RMDIR(url) < 0) {
1334 		php_error_docref1(NULL, url, E_WARNING, "%s", strerror(errno));
1335 		return 0;
1336 	}
1337 
1338 	/* Clear stat cache (and realpath cache) */
1339 	php_clear_stat_cache(1, NULL, 0);
1340 
1341 	return 1;
1342 }
1343 
php_plain_files_metadata(php_stream_wrapper * wrapper,const char * url,int option,void * value,php_stream_context * context)1344 static int php_plain_files_metadata(php_stream_wrapper *wrapper, const char *url, int option, void *value, php_stream_context *context)
1345 {
1346 	struct utimbuf *newtime;
1347 #ifndef PHP_WIN32
1348 	uid_t uid;
1349 	gid_t gid;
1350 #endif
1351 	mode_t mode;
1352 	int ret = 0;
1353 
1354 #ifdef PHP_WIN32
1355 	if (!php_win32_check_trailing_space(url, strlen(url))) {
1356 		php_error_docref1(NULL, url, E_WARNING, "%s", strerror(ENOENT));
1357 		return 0;
1358 	}
1359 #endif
1360 
1361 	if (strncasecmp(url, "file://", sizeof("file://") - 1) == 0) {
1362 		url += sizeof("file://") - 1;
1363 	}
1364 
1365 	if (php_check_open_basedir(url)) {
1366 		return 0;
1367 	}
1368 
1369 	switch(option) {
1370 		case PHP_STREAM_META_TOUCH:
1371 			newtime = (struct utimbuf *)value;
1372 			if (VCWD_ACCESS(url, F_OK) != 0) {
1373 				FILE *file = VCWD_FOPEN(url, "w");
1374 				if (file == NULL) {
1375 					php_error_docref1(NULL, url, E_WARNING, "Unable to create file %s because %s", url, strerror(errno));
1376 					return 0;
1377 				}
1378 				fclose(file);
1379 			}
1380 
1381 			ret = VCWD_UTIME(url, newtime);
1382 			break;
1383 #ifndef PHP_WIN32
1384 		case PHP_STREAM_META_OWNER_NAME:
1385 		case PHP_STREAM_META_OWNER:
1386 			if(option == PHP_STREAM_META_OWNER_NAME) {
1387 				if(php_get_uid_by_name((char *)value, &uid) != SUCCESS) {
1388 					php_error_docref1(NULL, url, E_WARNING, "Unable to find uid for %s", (char *)value);
1389 					return 0;
1390 				}
1391 			} else {
1392 				uid = (uid_t)*(long *)value;
1393 			}
1394 			ret = VCWD_CHOWN(url, uid, -1);
1395 			break;
1396 		case PHP_STREAM_META_GROUP:
1397 		case PHP_STREAM_META_GROUP_NAME:
1398 			if(option == PHP_STREAM_META_GROUP_NAME) {
1399 				if(php_get_gid_by_name((char *)value, &gid) != SUCCESS) {
1400 					php_error_docref1(NULL, url, E_WARNING, "Unable to find gid for %s", (char *)value);
1401 					return 0;
1402 				}
1403 			} else {
1404 				gid = (gid_t)*(long *)value;
1405 			}
1406 			ret = VCWD_CHOWN(url, -1, gid);
1407 			break;
1408 #endif
1409 		case PHP_STREAM_META_ACCESS:
1410 			mode = (mode_t)*(zend_long *)value;
1411 			ret = VCWD_CHMOD(url, mode);
1412 			break;
1413 		default:
1414 			php_error_docref1(NULL, url, E_WARNING, "Unknown option %d for stream_metadata", option);
1415 			return 0;
1416 	}
1417 	if (ret == -1) {
1418 		php_error_docref1(NULL, url, E_WARNING, "Operation failed: %s", strerror(errno));
1419 		return 0;
1420 	}
1421 	php_clear_stat_cache(0, NULL, 0);
1422 	return 1;
1423 }
1424 
1425 
1426 static php_stream_wrapper_ops php_plain_files_wrapper_ops = {
1427 	php_plain_files_stream_opener,
1428 	NULL,
1429 	NULL,
1430 	php_plain_files_url_stater,
1431 	php_plain_files_dir_opener,
1432 	"plainfile",
1433 	php_plain_files_unlink,
1434 	php_plain_files_rename,
1435 	php_plain_files_mkdir,
1436 	php_plain_files_rmdir,
1437 	php_plain_files_metadata
1438 };
1439 
1440 PHPAPI php_stream_wrapper php_plain_files_wrapper = {
1441 	&php_plain_files_wrapper_ops,
1442 	NULL,
1443 	0
1444 };
1445 
1446 /* {{{ 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)1447 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)
1448 {
1449 	/* code ripped off from fopen_wrappers.c */
1450 	char *pathbuf, *end;
1451 	const char *ptr;
1452 	char trypath[MAXPATHLEN];
1453 	php_stream *stream;
1454 	size_t filename_length;
1455 	zend_string *exec_filename;
1456 
1457 	if (opened_path) {
1458 		*opened_path = NULL;
1459 	}
1460 
1461 	if(!filename) {
1462 		return NULL;
1463 	}
1464 
1465 	filename_length = strlen(filename);
1466 #ifndef PHP_WIN32
1467 	(void) filename_length;
1468 #endif
1469 
1470 	/* Relative path open */
1471 	if (*filename == '.' && (IS_SLASH(filename[1]) || filename[1] == '.')) {
1472 		/* further checks, we could have ....... filenames */
1473 		ptr = filename + 1;
1474 		if (*ptr == '.') {
1475 			while (*(++ptr) == '.');
1476 			if (!IS_SLASH(*ptr)) { /* not a relative path after all */
1477 				goto not_relative_path;
1478 			}
1479 		}
1480 
1481 
1482 		if (((options & STREAM_DISABLE_OPEN_BASEDIR) == 0) && php_check_open_basedir(filename)) {
1483 			return NULL;
1484 		}
1485 
1486 		return php_stream_fopen_rel(filename, mode, opened_path, options);
1487 	}
1488 
1489 not_relative_path:
1490 
1491 	/* Absolute path open */
1492 	if (IS_ABSOLUTE_PATH(filename, filename_length)) {
1493 
1494 		if (((options & STREAM_DISABLE_OPEN_BASEDIR) == 0) && php_check_open_basedir(filename)) {
1495 			return NULL;
1496 		}
1497 
1498 		return php_stream_fopen_rel(filename, mode, opened_path, options);
1499 	}
1500 
1501 #ifdef PHP_WIN32
1502 	if (IS_SLASH(filename[0])) {
1503 		size_t cwd_len;
1504 		char *cwd;
1505 		cwd = virtual_getcwd_ex(&cwd_len);
1506 		/* getcwd() will return always return [DRIVE_LETTER]:/) on windows. */
1507 		*(cwd+3) = '\0';
1508 
1509 		if (snprintf(trypath, MAXPATHLEN, "%s%s", cwd, filename) >= MAXPATHLEN) {
1510 			php_error_docref(NULL, E_NOTICE, "%s/%s path was truncated to %d", cwd, filename, MAXPATHLEN);
1511 		}
1512 
1513 		efree(cwd);
1514 
1515 		if (((options & STREAM_DISABLE_OPEN_BASEDIR) == 0) && php_check_open_basedir(trypath)) {
1516 			return NULL;
1517 		}
1518 
1519 		return php_stream_fopen_rel(trypath, mode, opened_path, options);
1520 	}
1521 #endif
1522 
1523 	if (!path || !*path) {
1524 		return php_stream_fopen_rel(filename, mode, opened_path, options);
1525 	}
1526 
1527 	/* check in provided path */
1528 	/* append the calling scripts' current working directory
1529 	 * as a fall back case
1530 	 */
1531 	if (zend_is_executing() &&
1532 	    (exec_filename = zend_get_executed_filename_ex()) != NULL) {
1533 		const char *exec_fname = ZSTR_VAL(exec_filename);
1534 		size_t exec_fname_length = ZSTR_LEN(exec_filename);
1535 
1536 		while ((--exec_fname_length < SIZE_MAX) && !IS_SLASH(exec_fname[exec_fname_length]));
1537 		if (exec_fname_length<=0) {
1538 			/* no path */
1539 			pathbuf = estrdup(path);
1540 		} else {
1541 			size_t path_length = strlen(path);
1542 
1543 			pathbuf = (char *) emalloc(exec_fname_length + path_length +1 +1);
1544 			memcpy(pathbuf, path, path_length);
1545 			pathbuf[path_length] = DEFAULT_DIR_SEPARATOR;
1546 			memcpy(pathbuf+path_length+1, exec_fname, exec_fname_length);
1547 			pathbuf[path_length + exec_fname_length +1] = '\0';
1548 		}
1549 	} else {
1550 		pathbuf = estrdup(path);
1551 	}
1552 
1553 	ptr = pathbuf;
1554 
1555 	while (ptr && *ptr) {
1556 		end = strchr(ptr, DEFAULT_DIR_SEPARATOR);
1557 		if (end != NULL) {
1558 			*end = '\0';
1559 			end++;
1560 		}
1561 		if (*ptr == '\0') {
1562 			goto stream_skip;
1563 		}
1564 		if (snprintf(trypath, MAXPATHLEN, "%s/%s", ptr, filename) >= MAXPATHLEN) {
1565 			php_error_docref(NULL, E_NOTICE, "%s/%s path was truncated to %d", ptr, filename, MAXPATHLEN);
1566 		}
1567 
1568 		if (((options & STREAM_DISABLE_OPEN_BASEDIR) == 0) && php_check_open_basedir_ex(trypath, 0)) {
1569 			goto stream_skip;
1570 		}
1571 
1572 		stream = php_stream_fopen_rel(trypath, mode, opened_path, options);
1573 		if (stream) {
1574 			efree(pathbuf);
1575 			return stream;
1576 		}
1577 stream_skip:
1578 		ptr = end;
1579 	} /* end provided path */
1580 
1581 	efree(pathbuf);
1582 	return NULL;
1583 
1584 }
1585 /* }}} */
1586 
1587 /*
1588  * Local variables:
1589  * tab-width: 4
1590  * c-basic-offset: 4
1591  * End:
1592  * vim600: noet sw=4 ts=4 fdm=marker
1593  * vim<600: noet sw=4 ts=4
1594  */
1595