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