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