xref: /PHP-8.2/main/streams/plain_wrapper.c (revision 8421cfda)
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 #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 # 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 	zend_uintptr_t handle = _get_osfhandle(self->fd);
263 
264 	if (handle != (zend_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 send 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 ((zend_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 		return sizeof(php_stream_dirent);
1033 	}
1034 	return 0;
1035 }
1036 
php_plain_files_dirstream_close(php_stream * stream,int close_handle)1037 static int php_plain_files_dirstream_close(php_stream *stream, int close_handle)
1038 {
1039 	return closedir((DIR *)stream->abstract);
1040 }
1041 
php_plain_files_dirstream_rewind(php_stream * stream,zend_off_t offset,int whence,zend_off_t * newoffs)1042 static int php_plain_files_dirstream_rewind(php_stream *stream, zend_off_t offset, int whence, zend_off_t *newoffs)
1043 {
1044 	rewinddir((DIR *)stream->abstract);
1045 	return 0;
1046 }
1047 
1048 static const php_stream_ops	php_plain_files_dirstream_ops = {
1049 	NULL, php_plain_files_dirstream_read,
1050 	php_plain_files_dirstream_close, NULL,
1051 	"dir",
1052 	php_plain_files_dirstream_rewind,
1053 	NULL, /* cast */
1054 	NULL, /* stat */
1055 	NULL  /* set_option */
1056 };
1057 
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)1058 static php_stream *php_plain_files_dir_opener(php_stream_wrapper *wrapper, const char *path, const char *mode,
1059 		int options, zend_string **opened_path, php_stream_context *context STREAMS_DC)
1060 {
1061 	DIR *dir = NULL;
1062 	php_stream *stream = NULL;
1063 
1064 #ifdef HAVE_GLOB
1065 	if (options & STREAM_USE_GLOB_DIR_OPEN) {
1066 		return php_glob_stream_wrapper.wops->dir_opener((php_stream_wrapper*)&php_glob_stream_wrapper, path, mode, options, opened_path, context STREAMS_REL_CC);
1067 	}
1068 #endif
1069 
1070 	if (((options & STREAM_DISABLE_OPEN_BASEDIR) == 0) && php_check_open_basedir(path)) {
1071 		return NULL;
1072 	}
1073 
1074 	dir = VCWD_OPENDIR(path);
1075 
1076 #ifdef PHP_WIN32
1077 	if (!dir) {
1078 		php_win32_docref1_from_error(GetLastError(), path);
1079 	}
1080 
1081 	if (dir && dir->finished) {
1082 		closedir(dir);
1083 		dir = NULL;
1084 	}
1085 #endif
1086 	if (dir) {
1087 		stream = php_stream_alloc(&php_plain_files_dirstream_ops, dir, 0, mode);
1088 		if (stream == NULL)
1089 			closedir(dir);
1090 	}
1091 
1092 	return stream;
1093 }
1094 /* }}} */
1095 
1096 /* {{{ php_stream_fopen */
_php_stream_fopen(const char * filename,const char * mode,zend_string ** opened_path,int options STREAMS_DC)1097 PHPAPI php_stream *_php_stream_fopen(const char *filename, const char *mode, zend_string **opened_path, int options STREAMS_DC)
1098 {
1099 	char realpath[MAXPATHLEN];
1100 	int open_flags;
1101 	int fd;
1102 	php_stream *ret;
1103 	int persistent = options & STREAM_OPEN_PERSISTENT;
1104 	char *persistent_id = NULL;
1105 
1106 	if (FAILURE == php_stream_parse_fopen_modes(mode, &open_flags)) {
1107 		php_stream_wrapper_log_error(&php_plain_files_wrapper, options, "`%s' is not a valid mode for fopen", mode);
1108 		return NULL;
1109 	}
1110 
1111 	if (options & STREAM_ASSUME_REALPATH) {
1112 		strlcpy(realpath, filename, sizeof(realpath));
1113 	} else {
1114 		if (expand_filepath(filename, realpath) == NULL) {
1115 			return NULL;
1116 		}
1117 	}
1118 
1119 	if (persistent) {
1120 		spprintf(&persistent_id, 0, "streams_stdio_%d_%s", open_flags, realpath);
1121 		switch (php_stream_from_persistent_id(persistent_id, &ret)) {
1122 			case PHP_STREAM_PERSISTENT_SUCCESS:
1123 				if (opened_path) {
1124 					//TODO: avoid reallocation???
1125 					*opened_path = zend_string_init(realpath, strlen(realpath), 0);
1126 				}
1127 				ZEND_FALLTHROUGH;
1128 
1129 			case PHP_STREAM_PERSISTENT_FAILURE:
1130 				efree(persistent_id);
1131 				return ret;
1132 		}
1133 	}
1134 #ifdef PHP_WIN32
1135 	fd = php_win32_ioutil_open(realpath, open_flags, 0666);
1136 #else
1137 	fd = open(realpath, open_flags, 0666);
1138 #endif
1139 	if (fd != -1)	{
1140 
1141 		if (options & STREAM_OPEN_FOR_INCLUDE) {
1142 			ret = php_stream_fopen_from_fd_int_rel(fd, mode, persistent_id);
1143 		} else {
1144 			/* skip the lseek(SEEK_CUR) system call to
1145 			 * determine the current offset because we
1146 			 * know newly opened files are at offset zero
1147 			 * (unless the file has been opened in
1148 			 * O_APPEND mode) */
1149 			ret = php_stream_fopen_from_fd_rel(fd, mode, persistent_id, (open_flags & O_APPEND) == 0);
1150 		}
1151 
1152 		if (ret)	{
1153 			if (opened_path) {
1154 				*opened_path = zend_string_init(realpath, strlen(realpath), 0);
1155 			}
1156 			if (persistent_id) {
1157 				efree(persistent_id);
1158 			}
1159 
1160 			/* WIN32 always set ISREG flag */
1161 #ifndef PHP_WIN32
1162 			/* sanity checks for include/require.
1163 			 * We check these after opening the stream, so that we save
1164 			 * on fstat() syscalls */
1165 			if (options & STREAM_OPEN_FOR_INCLUDE) {
1166 				php_stdio_stream_data *self = (php_stdio_stream_data*)ret->abstract;
1167 				int r;
1168 
1169 				r = do_fstat(self, 0);
1170 				if ((r == 0 && !S_ISREG(self->sb.st_mode))) {
1171 					if (opened_path) {
1172 						zend_string_release_ex(*opened_path, 0);
1173 						*opened_path = NULL;
1174 					}
1175 					php_stream_close(ret);
1176 					return NULL;
1177 				}
1178 
1179 				/* Make sure the fstat result is reused when we later try to get the
1180 				 * file size. */
1181 				self->no_forced_fstat = 1;
1182 			}
1183 
1184 			if (options & STREAM_USE_BLOCKING_PIPE) {
1185 				php_stdio_stream_data *self = (php_stdio_stream_data*)ret->abstract;
1186 				self->is_pipe_blocking = 1;
1187 			}
1188 #endif
1189 
1190 			return ret;
1191 		}
1192 		close(fd);
1193 	}
1194 	if (persistent_id) {
1195 		efree(persistent_id);
1196 	}
1197 	return NULL;
1198 }
1199 /* }}} */
1200 
1201 
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)1202 static php_stream *php_plain_files_stream_opener(php_stream_wrapper *wrapper, const char *path, const char *mode,
1203 		int options, zend_string **opened_path, php_stream_context *context STREAMS_DC)
1204 {
1205 	if (((options & STREAM_DISABLE_OPEN_BASEDIR) == 0) && php_check_open_basedir(path)) {
1206 		return NULL;
1207 	}
1208 
1209 	return php_stream_fopen_rel(path, mode, opened_path, options);
1210 }
1211 
php_plain_files_url_stater(php_stream_wrapper * wrapper,const char * url,int flags,php_stream_statbuf * ssb,php_stream_context * context)1212 static int php_plain_files_url_stater(php_stream_wrapper *wrapper, const char *url, int flags, php_stream_statbuf *ssb, php_stream_context *context)
1213 {
1214 	if (!(flags & PHP_STREAM_URL_STAT_IGNORE_OPEN_BASEDIR)) {
1215 		if (strncasecmp(url, "file://", sizeof("file://") - 1) == 0) {
1216 			url += sizeof("file://") - 1;
1217 		}
1218 
1219 		if (php_check_open_basedir_ex(url, (flags & PHP_STREAM_URL_STAT_QUIET) ? 0 : 1)) {
1220 			return -1;
1221 		}
1222 	}
1223 
1224 #ifdef PHP_WIN32
1225 	if (flags & PHP_STREAM_URL_STAT_LINK) {
1226 		return VCWD_LSTAT(url, &ssb->sb);
1227 	}
1228 #else
1229 # ifdef HAVE_SYMLINK
1230 	if (flags & PHP_STREAM_URL_STAT_LINK) {
1231 		return VCWD_LSTAT(url, &ssb->sb);
1232 	} else
1233 # endif
1234 #endif
1235 		return VCWD_STAT(url, &ssb->sb);
1236 }
1237 
php_plain_files_unlink(php_stream_wrapper * wrapper,const char * url,int options,php_stream_context * context)1238 static int php_plain_files_unlink(php_stream_wrapper *wrapper, const char *url, int options, php_stream_context *context)
1239 {
1240 	int ret;
1241 
1242 	if (strncasecmp(url, "file://", sizeof("file://") - 1) == 0) {
1243 		url += sizeof("file://") - 1;
1244 	}
1245 
1246 	if (php_check_open_basedir(url)) {
1247 		return 0;
1248 	}
1249 
1250 	ret = VCWD_UNLINK(url);
1251 	if (ret == -1) {
1252 		if (options & REPORT_ERRORS) {
1253 			php_error_docref1(NULL, url, E_WARNING, "%s", strerror(errno));
1254 		}
1255 		return 0;
1256 	}
1257 
1258 	/* Clear stat cache (and realpath cache) */
1259 	php_clear_stat_cache(1, NULL, 0);
1260 
1261 	return 1;
1262 }
1263 
php_plain_files_rename(php_stream_wrapper * wrapper,const char * url_from,const char * url_to,int options,php_stream_context * context)1264 static int php_plain_files_rename(php_stream_wrapper *wrapper, const char *url_from, const char *url_to, int options, php_stream_context *context)
1265 {
1266 	int ret;
1267 
1268 	if (!url_from || !url_to) {
1269 		return 0;
1270 	}
1271 
1272 #ifdef PHP_WIN32
1273 	if (!php_win32_check_trailing_space(url_from, strlen(url_from))) {
1274 		php_win32_docref2_from_error(ERROR_INVALID_NAME, url_from, url_to);
1275 		return 0;
1276 	}
1277 	if (!php_win32_check_trailing_space(url_to, strlen(url_to))) {
1278 		php_win32_docref2_from_error(ERROR_INVALID_NAME, url_from, url_to);
1279 		return 0;
1280 	}
1281 #endif
1282 
1283 	if (strncasecmp(url_from, "file://", sizeof("file://") - 1) == 0) {
1284 		url_from += sizeof("file://") - 1;
1285 	}
1286 
1287 	if (strncasecmp(url_to, "file://", sizeof("file://") - 1) == 0) {
1288 		url_to += sizeof("file://") - 1;
1289 	}
1290 
1291 	if (php_check_open_basedir(url_from) || php_check_open_basedir(url_to)) {
1292 		return 0;
1293 	}
1294 
1295 	ret = VCWD_RENAME(url_from, url_to);
1296 
1297 	if (ret == -1) {
1298 #ifndef PHP_WIN32
1299 # ifdef EXDEV
1300 		if (errno == EXDEV) {
1301 			zend_stat_t sb;
1302 # if !defined(ZTS) && !defined(TSRM_WIN32)
1303 			/* not sure what to do in ZTS case, umask is not thread-safe */
1304 			int oldmask = umask(077);
1305 # endif
1306 			int success = 0;
1307 			if (php_copy_file(url_from, url_to) == SUCCESS) {
1308 				if (VCWD_STAT(url_from, &sb) == 0) {
1309 					success = 1;
1310 #  ifndef TSRM_WIN32
1311 					/*
1312 					 * Try to set user and permission info on the target.
1313 					 * If we're not root, then some of these may fail.
1314 					 * We try chown first, to set proper group info, relying
1315 					 * on the system environment to have proper umask to not allow
1316 					 * access to the file in the meantime.
1317 					 */
1318 					if (VCWD_CHOWN(url_to, sb.st_uid, sb.st_gid)) {
1319 						php_error_docref2(NULL, url_from, url_to, E_WARNING, "%s", strerror(errno));
1320 						if (errno != EPERM) {
1321 							success = 0;
1322 						}
1323 					}
1324 
1325 					if (success) {
1326 						if (VCWD_CHMOD(url_to, sb.st_mode)) {
1327 							php_error_docref2(NULL, url_from, url_to, E_WARNING, "%s", strerror(errno));
1328 							if (errno != EPERM) {
1329 								success = 0;
1330 							}
1331 						}
1332 					}
1333 #  endif
1334 					if (success) {
1335 						VCWD_UNLINK(url_from);
1336 					}
1337 				} else {
1338 					php_error_docref2(NULL, url_from, url_to, E_WARNING, "%s", strerror(errno));
1339 				}
1340 			} else {
1341 				php_error_docref2(NULL, url_from, url_to, E_WARNING, "%s", strerror(errno));
1342 			}
1343 #  if !defined(ZTS) && !defined(TSRM_WIN32)
1344 			umask(oldmask);
1345 #  endif
1346 			return success;
1347 		}
1348 # endif
1349 #endif
1350 
1351 #ifdef PHP_WIN32
1352 		php_win32_docref2_from_error(GetLastError(), url_from, url_to);
1353 #else
1354 		php_error_docref2(NULL, url_from, url_to, E_WARNING, "%s", strerror(errno));
1355 #endif
1356 		return 0;
1357 	}
1358 
1359 	/* Clear stat cache (and realpath cache) */
1360 	php_clear_stat_cache(1, NULL, 0);
1361 
1362 	return 1;
1363 }
1364 
php_plain_files_mkdir(php_stream_wrapper * wrapper,const char * dir,int mode,int options,php_stream_context * context)1365 static int php_plain_files_mkdir(php_stream_wrapper *wrapper, const char *dir, int mode, int options, php_stream_context *context)
1366 {
1367 	if (strncasecmp(dir, "file://", sizeof("file://") - 1) == 0) {
1368 		dir += sizeof("file://") - 1;
1369 	}
1370 
1371 	if (!(options & PHP_STREAM_MKDIR_RECURSIVE)) {
1372 		return php_mkdir(dir, mode) == 0;
1373 	}
1374 
1375 	char buf[MAXPATHLEN];
1376 	if (!expand_filepath_with_mode(dir, buf, NULL, 0, CWD_EXPAND)) {
1377 		php_error_docref(NULL, E_WARNING, "Invalid path");
1378 		return 0;
1379 	}
1380 
1381 	if (php_check_open_basedir(buf)) {
1382 		return 0;
1383 	}
1384 
1385 	/* we look for directory separator from the end of string, thus hopefully reducing our work load */
1386 	char *p;
1387 	zend_stat_t sb;
1388 	size_t dir_len = strlen(dir), offset = 0;
1389 	char *e = buf +  strlen(buf);
1390 
1391 	if ((p = memchr(buf, DEFAULT_SLASH, dir_len))) {
1392 		offset = p - buf + 1;
1393 	}
1394 
1395 	if (p && dir_len == 1) {
1396 		/* buf == "DEFAULT_SLASH" */
1397 	}
1398 	else {
1399 		/* find a top level directory we need to create */
1400 		while ( (p = strrchr(buf + offset, DEFAULT_SLASH)) || (offset != 1 && (p = strrchr(buf, DEFAULT_SLASH))) ) {
1401 			int n = 0;
1402 
1403 			*p = '\0';
1404 			while (p > buf && *(p-1) == DEFAULT_SLASH) {
1405 				++n;
1406 				--p;
1407 				*p = '\0';
1408 			}
1409 			if (VCWD_STAT(buf, &sb) == 0) {
1410 				while (1) {
1411 					*p = DEFAULT_SLASH;
1412 					if (!n) break;
1413 					--n;
1414 					++p;
1415 				}
1416 				break;
1417 			}
1418 		}
1419 	}
1420 
1421 	if (!p) {
1422 		p = buf;
1423 	}
1424 	while (true) {
1425 		int ret = VCWD_MKDIR(buf, (mode_t) mode);
1426 		if (ret < 0 && errno != EEXIST) {
1427 			if (options & REPORT_ERRORS) {
1428 				php_error_docref(NULL, E_WARNING, "%s", strerror(errno));
1429 			}
1430 			return 0;
1431 		}
1432 
1433 		bool replaced_slash = false;
1434 		while (++p != e) {
1435 			if (*p == '\0') {
1436 				replaced_slash = true;
1437 				*p = DEFAULT_SLASH;
1438 				if (*(p+1) != '\0') {
1439 					break;
1440 				}
1441 			}
1442 		}
1443 		if (p == e || !replaced_slash) {
1444 			/* No more directories to create */
1445 			/* issue a warning to client when the last directory was created failed */
1446 			if (ret < 0) {
1447 				if (options & REPORT_ERRORS) {
1448 					php_error_docref(NULL, E_WARNING, "%s", strerror(errno));
1449 				}
1450 				return 0;
1451 			}
1452 			return 1;
1453 		}
1454 	}
1455 }
1456 
php_plain_files_rmdir(php_stream_wrapper * wrapper,const char * url,int options,php_stream_context * context)1457 static int php_plain_files_rmdir(php_stream_wrapper *wrapper, const char *url, int options, php_stream_context *context)
1458 {
1459 	if (strncasecmp(url, "file://", sizeof("file://") - 1) == 0) {
1460 		url += sizeof("file://") - 1;
1461 	}
1462 
1463 	if (php_check_open_basedir(url)) {
1464 		return 0;
1465 	}
1466 
1467 #ifdef PHP_WIN32
1468 	if (!php_win32_check_trailing_space(url, strlen(url))) {
1469 		php_error_docref1(NULL, url, E_WARNING, "%s", strerror(ENOENT));
1470 		return 0;
1471 	}
1472 #endif
1473 
1474 	if (VCWD_RMDIR(url) < 0) {
1475 		php_error_docref1(NULL, url, E_WARNING, "%s", strerror(errno));
1476 		return 0;
1477 	}
1478 
1479 	/* Clear stat cache (and realpath cache) */
1480 	php_clear_stat_cache(1, NULL, 0);
1481 
1482 	return 1;
1483 }
1484 
php_plain_files_metadata(php_stream_wrapper * wrapper,const char * url,int option,void * value,php_stream_context * context)1485 static int php_plain_files_metadata(php_stream_wrapper *wrapper, const char *url, int option, void *value, php_stream_context *context)
1486 {
1487 	struct utimbuf *newtime;
1488 #ifndef PHP_WIN32
1489 	uid_t uid;
1490 	gid_t gid;
1491 #endif
1492 	mode_t mode;
1493 	int ret = 0;
1494 
1495 #ifdef PHP_WIN32
1496 	if (!php_win32_check_trailing_space(url, strlen(url))) {
1497 		php_error_docref1(NULL, url, E_WARNING, "%s", strerror(ENOENT));
1498 		return 0;
1499 	}
1500 #endif
1501 
1502 	if (strncasecmp(url, "file://", sizeof("file://") - 1) == 0) {
1503 		url += sizeof("file://") - 1;
1504 	}
1505 
1506 	if (php_check_open_basedir(url)) {
1507 		return 0;
1508 	}
1509 
1510 	switch(option) {
1511 		case PHP_STREAM_META_TOUCH:
1512 			newtime = (struct utimbuf *)value;
1513 			if (VCWD_ACCESS(url, F_OK) != 0) {
1514 				FILE *file = VCWD_FOPEN(url, "w");
1515 				if (file == NULL) {
1516 					php_error_docref1(NULL, url, E_WARNING, "Unable to create file %s because %s", url, strerror(errno));
1517 					return 0;
1518 				}
1519 				fclose(file);
1520 			}
1521 
1522 			ret = VCWD_UTIME(url, newtime);
1523 			break;
1524 #ifndef PHP_WIN32
1525 		case PHP_STREAM_META_OWNER_NAME:
1526 		case PHP_STREAM_META_OWNER:
1527 			if(option == PHP_STREAM_META_OWNER_NAME) {
1528 				if(php_get_uid_by_name((char *)value, &uid) != SUCCESS) {
1529 					php_error_docref1(NULL, url, E_WARNING, "Unable to find uid for %s", (char *)value);
1530 					return 0;
1531 				}
1532 			} else {
1533 				uid = (uid_t)*(long *)value;
1534 			}
1535 			ret = VCWD_CHOWN(url, uid, -1);
1536 			break;
1537 		case PHP_STREAM_META_GROUP:
1538 		case PHP_STREAM_META_GROUP_NAME:
1539 			if(option == PHP_STREAM_META_GROUP_NAME) {
1540 				if(php_get_gid_by_name((char *)value, &gid) != SUCCESS) {
1541 					php_error_docref1(NULL, url, E_WARNING, "Unable to find gid for %s", (char *)value);
1542 					return 0;
1543 				}
1544 			} else {
1545 				gid = (gid_t)*(long *)value;
1546 			}
1547 			ret = VCWD_CHOWN(url, -1, gid);
1548 			break;
1549 #endif
1550 		case PHP_STREAM_META_ACCESS:
1551 			mode = (mode_t)*(zend_long *)value;
1552 			ret = VCWD_CHMOD(url, mode);
1553 			break;
1554 		default:
1555 			zend_value_error("Unknown option %d for stream_metadata", option);
1556 			return 0;
1557 	}
1558 	if (ret == -1) {
1559 		php_error_docref1(NULL, url, E_WARNING, "Operation failed: %s", strerror(errno));
1560 		return 0;
1561 	}
1562 	php_clear_stat_cache(0, NULL, 0);
1563 	return 1;
1564 }
1565 
1566 
1567 static const php_stream_wrapper_ops php_plain_files_wrapper_ops = {
1568 	php_plain_files_stream_opener,
1569 	NULL,
1570 	NULL,
1571 	php_plain_files_url_stater,
1572 	php_plain_files_dir_opener,
1573 	"plainfile",
1574 	php_plain_files_unlink,
1575 	php_plain_files_rename,
1576 	php_plain_files_mkdir,
1577 	php_plain_files_rmdir,
1578 	php_plain_files_metadata
1579 };
1580 
1581 /* TODO: We have to make php_plain_files_wrapper writable to support SWOOLE */
1582 PHPAPI /*const*/ php_stream_wrapper php_plain_files_wrapper = {
1583 	&php_plain_files_wrapper_ops,
1584 	NULL,
1585 	0
1586 };
1587 
1588 /* {{{ 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)1589 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)
1590 {
1591 	/* code ripped off from fopen_wrappers.c */
1592 	char *pathbuf, *end;
1593 	const char *ptr;
1594 	char trypath[MAXPATHLEN];
1595 	php_stream *stream;
1596 	size_t filename_length;
1597 	zend_string *exec_filename;
1598 
1599 	if (opened_path) {
1600 		*opened_path = NULL;
1601 	}
1602 
1603 	if(!filename) {
1604 		return NULL;
1605 	}
1606 
1607 	filename_length = strlen(filename);
1608 #ifndef PHP_WIN32
1609 	(void) filename_length;
1610 #endif
1611 
1612 	/* Relative path open */
1613 	if (*filename == '.' && (IS_SLASH(filename[1]) || filename[1] == '.')) {
1614 		/* further checks, we could have ....... filenames */
1615 		ptr = filename + 1;
1616 		if (*ptr == '.') {
1617 			while (*(++ptr) == '.');
1618 			if (!IS_SLASH(*ptr)) { /* not a relative path after all */
1619 				goto not_relative_path;
1620 			}
1621 		}
1622 
1623 
1624 		if (((options & STREAM_DISABLE_OPEN_BASEDIR) == 0) && php_check_open_basedir(filename)) {
1625 			return NULL;
1626 		}
1627 
1628 		return php_stream_fopen_rel(filename, mode, opened_path, options);
1629 	}
1630 
1631 not_relative_path:
1632 
1633 	/* Absolute path open */
1634 	if (IS_ABSOLUTE_PATH(filename, filename_length)) {
1635 
1636 		if (((options & STREAM_DISABLE_OPEN_BASEDIR) == 0) && php_check_open_basedir(filename)) {
1637 			return NULL;
1638 		}
1639 
1640 		return php_stream_fopen_rel(filename, mode, opened_path, options);
1641 	}
1642 
1643 #ifdef PHP_WIN32
1644 	if (IS_SLASH(filename[0])) {
1645 		size_t cwd_len;
1646 		char *cwd;
1647 		cwd = virtual_getcwd_ex(&cwd_len);
1648 		/* getcwd() will return always return [DRIVE_LETTER]:/) on windows. */
1649 		*(cwd+3) = '\0';
1650 
1651 		if (snprintf(trypath, MAXPATHLEN, "%s%s", cwd, filename) >= MAXPATHLEN) {
1652 			php_error_docref(NULL, E_NOTICE, "%s/%s path was truncated to %d", cwd, filename, MAXPATHLEN);
1653 		}
1654 
1655 		efree(cwd);
1656 
1657 		if (((options & STREAM_DISABLE_OPEN_BASEDIR) == 0) && php_check_open_basedir(trypath)) {
1658 			return NULL;
1659 		}
1660 
1661 		return php_stream_fopen_rel(trypath, mode, opened_path, options);
1662 	}
1663 #endif
1664 
1665 	if (!path || !*path) {
1666 		return php_stream_fopen_rel(filename, mode, opened_path, options);
1667 	}
1668 
1669 	/* check in provided path */
1670 	/* append the calling scripts' current working directory
1671 	 * as a fall back case
1672 	 */
1673 	if (zend_is_executing() &&
1674 	    (exec_filename = zend_get_executed_filename_ex()) != NULL) {
1675 		const char *exec_fname = ZSTR_VAL(exec_filename);
1676 		size_t exec_fname_length = ZSTR_LEN(exec_filename);
1677 
1678 		while ((--exec_fname_length < SIZE_MAX) && !IS_SLASH(exec_fname[exec_fname_length]));
1679 		if (exec_fname_length<=0) {
1680 			/* no path */
1681 			pathbuf = estrdup(path);
1682 		} else {
1683 			size_t path_length = strlen(path);
1684 
1685 			pathbuf = (char *) emalloc(exec_fname_length + path_length +1 +1);
1686 			memcpy(pathbuf, path, path_length);
1687 			pathbuf[path_length] = DEFAULT_DIR_SEPARATOR;
1688 			memcpy(pathbuf+path_length+1, exec_fname, exec_fname_length);
1689 			pathbuf[path_length + exec_fname_length +1] = '\0';
1690 		}
1691 	} else {
1692 		pathbuf = estrdup(path);
1693 	}
1694 
1695 	ptr = pathbuf;
1696 
1697 	while (ptr && *ptr) {
1698 		end = strchr(ptr, DEFAULT_DIR_SEPARATOR);
1699 		if (end != NULL) {
1700 			*end = '\0';
1701 			end++;
1702 		}
1703 		if (*ptr == '\0') {
1704 			goto stream_skip;
1705 		}
1706 		if (snprintf(trypath, MAXPATHLEN, "%s/%s", ptr, filename) >= MAXPATHLEN) {
1707 			php_error_docref(NULL, E_NOTICE, "%s/%s path was truncated to %d", ptr, filename, MAXPATHLEN);
1708 		}
1709 
1710 		if (((options & STREAM_DISABLE_OPEN_BASEDIR) == 0) && php_check_open_basedir_ex(trypath, 0)) {
1711 			goto stream_skip;
1712 		}
1713 
1714 		stream = php_stream_fopen_rel(trypath, mode, opened_path, options);
1715 		if (stream) {
1716 			efree(pathbuf);
1717 			return stream;
1718 		}
1719 stream_skip:
1720 		ptr = end;
1721 	} /* end provided path */
1722 
1723 	efree(pathbuf);
1724 	return NULL;
1725 
1726 }
1727 /* }}} */
1728