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