xref: /PHP-8.1/main/streams/plain_wrapper.c (revision aec08cc8)
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 #endif
44 
45 #define php_stream_fopen_from_fd_int(fd, mode, persistent_id)	_php_stream_fopen_from_fd_int((fd), (mode), (persistent_id) STREAMS_CC)
46 #define php_stream_fopen_from_fd_int_rel(fd, mode, persistent_id)	 _php_stream_fopen_from_fd_int((fd), (mode), (persistent_id) STREAMS_REL_CC)
47 #define php_stream_fopen_from_file_int(file, mode)	_php_stream_fopen_from_file_int((file), (mode) STREAMS_CC)
48 #define php_stream_fopen_from_file_int_rel(file, mode)	 _php_stream_fopen_from_file_int((file), (mode) STREAMS_REL_CC)
49 
50 #ifndef PHP_WIN32
51 extern int php_get_uid_by_name(const char *name, uid_t *uid);
52 extern int php_get_gid_by_name(const char *name, gid_t *gid);
53 #endif
54 
55 #if defined(PHP_WIN32)
56 # define PLAIN_WRAP_BUF_SIZE(st) (((st) > UINT_MAX) ? UINT_MAX : (unsigned int)(st))
57 #define fsync _commit
58 #define fdatasync fsync
59 #else
60 # define PLAIN_WRAP_BUF_SIZE(st) (st)
61 # if !defined(HAVE_FDATASYNC)
62 #  define fdatasync fsync
63 # elif defined(__APPLE__)
64   // The symbol is present, however not in the headers
65   extern int fdatasync(int);
66 # endif
67 #endif
68 
69 /* parse standard "fopen" modes into open() flags */
php_stream_parse_fopen_modes(const char * mode,int * open_flags)70 PHPAPI int php_stream_parse_fopen_modes(const char *mode, int *open_flags)
71 {
72 	int flags;
73 
74 	switch (mode[0]) {
75 		case 'r':
76 			flags = 0;
77 			break;
78 		case 'w':
79 			flags = O_TRUNC|O_CREAT;
80 			break;
81 		case 'a':
82 			flags = O_CREAT|O_APPEND;
83 			break;
84 		case 'x':
85 			flags = O_CREAT|O_EXCL;
86 			break;
87 		case 'c':
88 			flags = O_CREAT;
89 			break;
90 		default:
91 			/* unknown mode */
92 			return FAILURE;
93 	}
94 
95 	if (strchr(mode, '+')) {
96 		flags |= O_RDWR;
97 	} else if (flags) {
98 		flags |= O_WRONLY;
99 	} else {
100 		flags |= O_RDONLY;
101 	}
102 
103 #if defined(O_CLOEXEC)
104 	if (strchr(mode, 'e')) {
105 		flags |= O_CLOEXEC;
106 	}
107 #endif
108 
109 #if defined(O_NONBLOCK)
110 	if (strchr(mode, 'n')) {
111 		flags |= O_NONBLOCK;
112 	}
113 #endif
114 
115 #if defined(_O_TEXT) && defined(O_BINARY)
116 	if (strchr(mode, 't')) {
117 		flags |= _O_TEXT;
118 	} else {
119 		flags |= O_BINARY;
120 	}
121 #endif
122 
123 	*open_flags = flags;
124 	return SUCCESS;
125 }
126 
127 
128 /* {{{ ------- STDIO stream implementation -------*/
129 
130 typedef struct {
131 	FILE *file;
132 	int fd;					/* underlying file descriptor */
133 	unsigned is_process_pipe:1;	/* use pclose instead of fclose */
134 	unsigned is_pipe:1;		/* stream is an actual pipe, currently Windows only*/
135 	unsigned cached_fstat:1;	/* sb is valid */
136 	unsigned is_pipe_blocking:1; /* allow blocking read() on pipes, currently Windows only */
137 	unsigned no_forced_fstat:1;  /* Use fstat cache even if forced */
138 	unsigned is_seekable:1;		/* don't try and seek, if not set */
139 	unsigned _reserved:26;
140 
141 	int lock_flag;			/* stores the lock state */
142 	zend_string *temp_name;	/* if non-null, this is the path to a temporary file that
143 							 * is to be deleted when the stream is closed */
144 #ifdef HAVE_FLUSHIO
145 	char last_op;
146 #endif
147 
148 #ifdef HAVE_MMAP
149 	char *last_mapped_addr;
150 	size_t last_mapped_len;
151 #endif
152 #ifdef PHP_WIN32
153 	char *last_mapped_addr;
154 	HANDLE file_mapping;
155 #endif
156 
157 	zend_stat_t sb;
158 } php_stdio_stream_data;
159 #define PHP_STDIOP_GET_FD(anfd, data)	anfd = (data)->file ? fileno((data)->file) : (data)->fd
160 
do_fstat(php_stdio_stream_data * d,int force)161 static int do_fstat(php_stdio_stream_data *d, int force)
162 {
163 	if (!d->cached_fstat || (force && !d->no_forced_fstat)) {
164 		int fd;
165 		int r;
166 
167 		PHP_STDIOP_GET_FD(fd, d);
168 		r = zend_fstat(fd, &d->sb);
169 		d->cached_fstat = r == 0;
170 
171 		return r;
172 	}
173 	return 0;
174 }
175 
_php_stream_fopen_from_fd_int(int fd,const char * mode,const char * persistent_id STREAMS_DC)176 static php_stream *_php_stream_fopen_from_fd_int(int fd, const char *mode, const char *persistent_id STREAMS_DC)
177 {
178 	php_stdio_stream_data *self;
179 
180 	self = pemalloc_rel_orig(sizeof(*self), persistent_id);
181 	memset(self, 0, sizeof(*self));
182 	self->file = NULL;
183 	self->is_seekable = 1;
184 	self->is_pipe = 0;
185 	self->lock_flag = LOCK_UN;
186 	self->is_process_pipe = 0;
187 	self->temp_name = NULL;
188 	self->fd = fd;
189 #ifdef PHP_WIN32
190 	self->is_pipe_blocking = 0;
191 #endif
192 
193 	return php_stream_alloc_rel(&php_stream_stdio_ops, self, persistent_id, mode);
194 }
195 
_php_stream_fopen_from_file_int(FILE * file,const char * mode STREAMS_DC)196 static php_stream *_php_stream_fopen_from_file_int(FILE *file, const char *mode STREAMS_DC)
197 {
198 	php_stdio_stream_data *self;
199 
200 	self = emalloc_rel_orig(sizeof(*self));
201 	memset(self, 0, sizeof(*self));
202 	self->file = file;
203 	self->is_seekable = 1;
204 	self->is_pipe = 0;
205 	self->lock_flag = LOCK_UN;
206 	self->is_process_pipe = 0;
207 	self->temp_name = NULL;
208 	self->fd = fileno(file);
209 #ifdef PHP_WIN32
210 	self->is_pipe_blocking = 0;
211 #endif
212 
213 	return php_stream_alloc_rel(&php_stream_stdio_ops, self, 0, mode);
214 }
215 
_php_stream_fopen_temporary_file(const char * dir,const char * pfx,zend_string ** opened_path_ptr STREAMS_DC)216 PHPAPI php_stream *_php_stream_fopen_temporary_file(const char *dir, const char *pfx, zend_string **opened_path_ptr STREAMS_DC)
217 {
218 	zend_string *opened_path = NULL;
219 	int fd;
220 
221 	fd = php_open_temporary_fd(dir, pfx, &opened_path);
222 	if (fd != -1)	{
223 		php_stream *stream;
224 
225 		if (opened_path_ptr) {
226 			*opened_path_ptr = opened_path;
227 		}
228 
229 		stream = php_stream_fopen_from_fd_int_rel(fd, "r+b", NULL);
230 		if (stream) {
231 			php_stdio_stream_data *self = (php_stdio_stream_data*)stream->abstract;
232 			stream->wrapper = (php_stream_wrapper*)&php_plain_files_wrapper;
233 			stream->orig_path = estrndup(ZSTR_VAL(opened_path), ZSTR_LEN(opened_path));
234 
235 			self->temp_name = opened_path;
236 			self->lock_flag = LOCK_UN;
237 
238 			return stream;
239 		}
240 		close(fd);
241 
242 		php_error_docref(NULL, E_WARNING, "Unable to allocate stream");
243 
244 		return NULL;
245 	}
246 	return NULL;
247 }
248 
_php_stream_fopen_tmpfile(int dummy STREAMS_DC)249 PHPAPI php_stream *_php_stream_fopen_tmpfile(int dummy STREAMS_DC)
250 {
251 	return php_stream_fopen_temporary_file(NULL, "php", NULL);
252 }
253 
detect_is_seekable(php_stdio_stream_data * self)254 static void detect_is_seekable(php_stdio_stream_data *self) {
255 #if defined(S_ISFIFO) && defined(S_ISCHR)
256 	if (self->fd >= 0 && do_fstat(self, 0) == 0) {
257 		self->is_seekable = !(S_ISFIFO(self->sb.st_mode) || S_ISCHR(self->sb.st_mode));
258 		self->is_pipe = S_ISFIFO(self->sb.st_mode);
259 	}
260 #elif defined(PHP_WIN32)
261 	zend_uintptr_t handle = _get_osfhandle(self->fd);
262 
263 	if (handle != (zend_uintptr_t)INVALID_HANDLE_VALUE) {
264 		DWORD file_type = GetFileType((HANDLE)handle);
265 
266 		self->is_seekable = !(file_type == FILE_TYPE_PIPE || file_type == FILE_TYPE_CHAR);
267 		self->is_pipe = file_type == FILE_TYPE_PIPE;
268 
269 		/* Additional check needed to distinguish between pipes and sockets. */
270 		if (self->is_pipe && !GetNamedPipeInfo((HANDLE) handle, NULL, NULL, NULL, NULL)) {
271 			self->is_pipe = 0;
272 		}
273 	}
274 #endif
275 }
276 
_php_stream_fopen_from_fd(int fd,const char * mode,const char * persistent_id STREAMS_DC)277 PHPAPI php_stream *_php_stream_fopen_from_fd(int fd, const char *mode, const char *persistent_id STREAMS_DC)
278 {
279 	php_stream *stream = php_stream_fopen_from_fd_int_rel(fd, mode, persistent_id);
280 
281 	if (stream) {
282 		php_stdio_stream_data *self = (php_stdio_stream_data*)stream->abstract;
283 
284 		detect_is_seekable(self);
285 		if (!self->is_seekable) {
286 			stream->flags |= PHP_STREAM_FLAG_NO_SEEK;
287 			stream->position = -1;
288 		} else {
289 			stream->position = zend_lseek(self->fd, 0, SEEK_CUR);
290 #ifdef ESPIPE
291 			/* FIXME: Is this code still needed? */
292 			if (stream->position == (zend_off_t)-1 && errno == ESPIPE) {
293 				stream->flags |= PHP_STREAM_FLAG_NO_SEEK;
294 				self->is_seekable = 0;
295 			}
296 #endif
297 		}
298 	}
299 
300 	return stream;
301 }
302 
_php_stream_fopen_from_file(FILE * file,const char * mode STREAMS_DC)303 PHPAPI php_stream *_php_stream_fopen_from_file(FILE *file, const char *mode STREAMS_DC)
304 {
305 	php_stream *stream = php_stream_fopen_from_file_int_rel(file, mode);
306 
307 	if (stream) {
308 		php_stdio_stream_data *self = (php_stdio_stream_data*)stream->abstract;
309 
310 		detect_is_seekable(self);
311 		if (!self->is_seekable) {
312 			stream->flags |= PHP_STREAM_FLAG_NO_SEEK;
313 			stream->position = -1;
314 		} else {
315 			stream->position = zend_ftell(file);
316 		}
317 	}
318 
319 	return stream;
320 }
321 
_php_stream_fopen_from_pipe(FILE * file,const char * mode STREAMS_DC)322 PHPAPI php_stream *_php_stream_fopen_from_pipe(FILE *file, const char *mode STREAMS_DC)
323 {
324 	php_stdio_stream_data *self;
325 	php_stream *stream;
326 
327 	self = emalloc_rel_orig(sizeof(*self));
328 	memset(self, 0, sizeof(*self));
329 	self->file = file;
330 	self->is_seekable = 0;
331 	self->is_pipe = 1;
332 	self->lock_flag = LOCK_UN;
333 	self->is_process_pipe = 1;
334 	self->fd = fileno(file);
335 	self->temp_name = NULL;
336 #ifdef PHP_WIN32
337 	self->is_pipe_blocking = 0;
338 #endif
339 
340 	stream = php_stream_alloc_rel(&php_stream_stdio_ops, self, 0, mode);
341 	stream->flags |= PHP_STREAM_FLAG_NO_SEEK;
342 	return stream;
343 }
344 
php_stdiop_write(php_stream * stream,const char * buf,size_t count)345 static ssize_t php_stdiop_write(php_stream *stream, const char *buf, size_t count)
346 {
347 	php_stdio_stream_data *data = (php_stdio_stream_data*)stream->abstract;
348 
349 	assert(data != NULL);
350 
351 	if (data->fd >= 0) {
352 #ifdef PHP_WIN32
353 		ssize_t bytes_written;
354 		if (ZEND_SIZE_T_UINT_OVFL(count)) {
355 			count = UINT_MAX;
356 		}
357 		bytes_written = _write(data->fd, buf, (unsigned int)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 							hoffs = (DWORD)(rounded_offset >> 32);
883 						}
884 
885 						/* MapViewOfFile()ing zero bytes would map to the end of the file; match *nix behavior instead */
886 						if (range->length + delta == 0) {
887 							return PHP_STREAM_OPTION_RETURN_ERR;
888 						}
889 
890 						data->last_mapped_addr = MapViewOfFile(data->file_mapping, acc, hoffs, loffs, range->length + delta);
891 
892 						if (data->last_mapped_addr) {
893 							/* give them back the address of the start offset they requested */
894 							range->mapped = data->last_mapped_addr + delta;
895 							return PHP_STREAM_OPTION_RETURN_OK;
896 						}
897 
898 						CloseHandle(data->file_mapping);
899 						data->file_mapping = NULL;
900 
901 						return PHP_STREAM_OPTION_RETURN_ERR;
902 
903 					case PHP_STREAM_MMAP_UNMAP:
904 						if (data->last_mapped_addr) {
905 							UnmapViewOfFile(data->last_mapped_addr);
906 							data->last_mapped_addr = NULL;
907 							CloseHandle(data->file_mapping);
908 							data->file_mapping = NULL;
909 							return PHP_STREAM_OPTION_RETURN_OK;
910 						}
911 						return PHP_STREAM_OPTION_RETURN_ERR;
912 
913 					default:
914 						return PHP_STREAM_OPTION_RETURN_ERR;
915 				}
916 			}
917 
918 #endif
919 			return PHP_STREAM_OPTION_RETURN_NOTIMPL;
920 
921 		case PHP_STREAM_OPTION_SYNC_API:
922 			switch (value) {
923 				case PHP_STREAM_SYNC_SUPPORTED:
924 					return fd == -1 ? PHP_STREAM_OPTION_RETURN_ERR : PHP_STREAM_OPTION_RETURN_OK;
925 				case PHP_STREAM_SYNC_FSYNC:
926 					return php_stdiop_sync(stream, 0) == 0 ? PHP_STREAM_OPTION_RETURN_OK : PHP_STREAM_OPTION_RETURN_ERR;
927 				case PHP_STREAM_SYNC_FDSYNC:
928 					return php_stdiop_sync(stream, 1) == 0 ? PHP_STREAM_OPTION_RETURN_OK : PHP_STREAM_OPTION_RETURN_ERR;
929 			}
930 			/* Invalid option passed */
931 			return PHP_STREAM_OPTION_RETURN_ERR;
932 
933 		case PHP_STREAM_OPTION_TRUNCATE_API:
934 			switch (value) {
935 				case PHP_STREAM_TRUNCATE_SUPPORTED:
936 					return fd == -1 ? PHP_STREAM_OPTION_RETURN_ERR : PHP_STREAM_OPTION_RETURN_OK;
937 
938 				case PHP_STREAM_TRUNCATE_SET_SIZE: {
939 					ptrdiff_t new_size = *(ptrdiff_t*)ptrparam;
940 					if (new_size < 0) {
941 						return PHP_STREAM_OPTION_RETURN_ERR;
942 					}
943 #ifdef PHP_WIN32
944 					HANDLE h = (HANDLE) _get_osfhandle(fd);
945 					if (INVALID_HANDLE_VALUE == h) {
946 						return PHP_STREAM_OPTION_RETURN_ERR;
947 					}
948 
949 					LARGE_INTEGER sz, old_sz;
950 					sz.QuadPart = 0;
951 
952 					if (!SetFilePointerEx(h, sz, &old_sz, FILE_CURRENT)) {
953 						return PHP_STREAM_OPTION_RETURN_ERR;
954 					}
955 
956 #ifdef _WIN64
957 					sz.QuadPart = new_size;
958 #else
959 					sz.HighPart = 0;
960 					sz.LowPart = new_size;
961 #endif
962 					if (!SetFilePointerEx(h, sz, NULL, FILE_BEGIN)) {
963 						return PHP_STREAM_OPTION_RETURN_ERR;
964 					}
965 					if (0 == SetEndOfFile(h)) {
966 						return PHP_STREAM_OPTION_RETURN_ERR;
967 					}
968 					if (!SetFilePointerEx(h, old_sz, NULL, FILE_BEGIN)) {
969 						return PHP_STREAM_OPTION_RETURN_ERR;
970 					}
971 					return PHP_STREAM_OPTION_RETURN_OK;
972 #else
973 					return ftruncate(fd, new_size) == 0 ? PHP_STREAM_OPTION_RETURN_OK : PHP_STREAM_OPTION_RETURN_ERR;
974 #endif
975 				}
976 			}
977 			return PHP_STREAM_OPTION_RETURN_NOTIMPL;
978 
979 #ifdef PHP_WIN32
980 		case PHP_STREAM_OPTION_PIPE_BLOCKING:
981 			data->is_pipe_blocking = value;
982 			return PHP_STREAM_OPTION_RETURN_OK;
983 #endif
984 		case PHP_STREAM_OPTION_META_DATA_API:
985 			if (fd == -1)
986 				return -1;
987 #ifdef O_NONBLOCK
988 			flags = fcntl(fd, F_GETFL, 0);
989 
990 			add_assoc_bool((zval*)ptrparam, "timed_out", 0);
991 			add_assoc_bool((zval*)ptrparam, "blocked", (flags & O_NONBLOCK)? 0 : 1);
992 			add_assoc_bool((zval*)ptrparam, "eof", stream->eof);
993 
994 			return PHP_STREAM_OPTION_RETURN_OK;
995 #endif
996 			return -1;
997 		default:
998 			return PHP_STREAM_OPTION_RETURN_NOTIMPL;
999 	}
1000 }
1001 
1002 /* This should be "const", but phpdbg overwrite it */
1003 PHPAPI php_stream_ops	php_stream_stdio_ops = {
1004 	php_stdiop_write, php_stdiop_read,
1005 	php_stdiop_close, php_stdiop_flush,
1006 	"STDIO",
1007 	php_stdiop_seek,
1008 	php_stdiop_cast,
1009 	php_stdiop_stat,
1010 	php_stdiop_set_option
1011 };
1012 /* }}} */
1013 
1014 /* {{{ plain files opendir/readdir implementation */
php_plain_files_dirstream_read(php_stream * stream,char * buf,size_t count)1015 static ssize_t php_plain_files_dirstream_read(php_stream *stream, char *buf, size_t count)
1016 {
1017 	DIR *dir = (DIR*)stream->abstract;
1018 	struct dirent *result;
1019 	php_stream_dirent *ent = (php_stream_dirent*)buf;
1020 
1021 	/* avoid problems if someone mis-uses the stream */
1022 	if (count != sizeof(php_stream_dirent))
1023 		return -1;
1024 
1025 	result = readdir(dir);
1026 	if (result) {
1027 		PHP_STRLCPY(ent->d_name, result->d_name, sizeof(ent->d_name), strlen(result->d_name));
1028 		return sizeof(php_stream_dirent);
1029 	}
1030 	return 0;
1031 }
1032 
php_plain_files_dirstream_close(php_stream * stream,int close_handle)1033 static int php_plain_files_dirstream_close(php_stream *stream, int close_handle)
1034 {
1035 	return closedir((DIR *)stream->abstract);
1036 }
1037 
php_plain_files_dirstream_rewind(php_stream * stream,zend_off_t offset,int whence,zend_off_t * newoffs)1038 static int php_plain_files_dirstream_rewind(php_stream *stream, zend_off_t offset, int whence, zend_off_t *newoffs)
1039 {
1040 	rewinddir((DIR *)stream->abstract);
1041 	return 0;
1042 }
1043 
1044 static const php_stream_ops	php_plain_files_dirstream_ops = {
1045 	NULL, php_plain_files_dirstream_read,
1046 	php_plain_files_dirstream_close, NULL,
1047 	"dir",
1048 	php_plain_files_dirstream_rewind,
1049 	NULL, /* cast */
1050 	NULL, /* stat */
1051 	NULL  /* set_option */
1052 };
1053 
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)1054 static php_stream *php_plain_files_dir_opener(php_stream_wrapper *wrapper, const char *path, const char *mode,
1055 		int options, zend_string **opened_path, php_stream_context *context STREAMS_DC)
1056 {
1057 	DIR *dir = NULL;
1058 	php_stream *stream = NULL;
1059 
1060 #ifdef HAVE_GLOB
1061 	if (options & STREAM_USE_GLOB_DIR_OPEN) {
1062 		return php_glob_stream_wrapper.wops->dir_opener((php_stream_wrapper*)&php_glob_stream_wrapper, path, mode, options, opened_path, context STREAMS_REL_CC);
1063 	}
1064 #endif
1065 
1066 	if (((options & STREAM_DISABLE_OPEN_BASEDIR) == 0) && php_check_open_basedir(path)) {
1067 		return NULL;
1068 	}
1069 
1070 	dir = VCWD_OPENDIR(path);
1071 
1072 #ifdef PHP_WIN32
1073 	if (!dir) {
1074 		php_win32_docref1_from_error(GetLastError(), path);
1075 	}
1076 
1077 	if (dir && dir->finished) {
1078 		closedir(dir);
1079 		dir = NULL;
1080 	}
1081 #endif
1082 	if (dir) {
1083 		stream = php_stream_alloc(&php_plain_files_dirstream_ops, dir, 0, mode);
1084 		if (stream == NULL)
1085 			closedir(dir);
1086 	}
1087 
1088 	return stream;
1089 }
1090 /* }}} */
1091 
1092 /* {{{ php_stream_fopen */
_php_stream_fopen(const char * filename,const char * mode,zend_string ** opened_path,int options STREAMS_DC)1093 PHPAPI php_stream *_php_stream_fopen(const char *filename, const char *mode, zend_string **opened_path, int options STREAMS_DC)
1094 {
1095 	char realpath[MAXPATHLEN];
1096 	int open_flags;
1097 	int fd;
1098 	php_stream *ret;
1099 	int persistent = options & STREAM_OPEN_PERSISTENT;
1100 	char *persistent_id = NULL;
1101 
1102 	if (FAILURE == php_stream_parse_fopen_modes(mode, &open_flags)) {
1103 		php_stream_wrapper_log_error(&php_plain_files_wrapper, options, "`%s' is not a valid mode for fopen", mode);
1104 		return NULL;
1105 	}
1106 
1107 	if (options & STREAM_ASSUME_REALPATH) {
1108 		strlcpy(realpath, filename, sizeof(realpath));
1109 	} else {
1110 		if (expand_filepath(filename, realpath) == NULL) {
1111 			return NULL;
1112 		}
1113 	}
1114 
1115 	if (persistent) {
1116 		spprintf(&persistent_id, 0, "streams_stdio_%d_%s", open_flags, realpath);
1117 		switch (php_stream_from_persistent_id(persistent_id, &ret)) {
1118 			case PHP_STREAM_PERSISTENT_SUCCESS:
1119 				if (opened_path) {
1120 					//TODO: avoid reallocation???
1121 					*opened_path = zend_string_init(realpath, strlen(realpath), 0);
1122 				}
1123 				ZEND_FALLTHROUGH;
1124 
1125 			case PHP_STREAM_PERSISTENT_FAILURE:
1126 				efree(persistent_id);
1127 				return ret;
1128 		}
1129 	}
1130 #ifdef PHP_WIN32
1131 	fd = php_win32_ioutil_open(realpath, open_flags, 0666);
1132 #else
1133 	fd = open(realpath, open_flags, 0666);
1134 #endif
1135 	if (fd != -1)	{
1136 
1137 		if (options & STREAM_OPEN_FOR_INCLUDE) {
1138 			ret = php_stream_fopen_from_fd_int_rel(fd, mode, persistent_id);
1139 		} else {
1140 			ret = php_stream_fopen_from_fd_rel(fd, mode, persistent_id);
1141 		}
1142 
1143 		if (ret)	{
1144 			if (opened_path) {
1145 				*opened_path = zend_string_init(realpath, strlen(realpath), 0);
1146 			}
1147 			if (persistent_id) {
1148 				efree(persistent_id);
1149 			}
1150 
1151 			/* WIN32 always set ISREG flag */
1152 #ifndef PHP_WIN32
1153 			/* sanity checks for include/require.
1154 			 * We check these after opening the stream, so that we save
1155 			 * on fstat() syscalls */
1156 			if (options & STREAM_OPEN_FOR_INCLUDE) {
1157 				php_stdio_stream_data *self = (php_stdio_stream_data*)ret->abstract;
1158 				int r;
1159 
1160 				r = do_fstat(self, 0);
1161 				if ((r == 0 && !S_ISREG(self->sb.st_mode))) {
1162 					if (opened_path) {
1163 						zend_string_release_ex(*opened_path, 0);
1164 						*opened_path = NULL;
1165 					}
1166 					php_stream_close(ret);
1167 					return NULL;
1168 				}
1169 
1170 				/* Make sure the fstat result is reused when we later try to get the
1171 				 * file size. */
1172 				self->no_forced_fstat = 1;
1173 			}
1174 
1175 			if (options & STREAM_USE_BLOCKING_PIPE) {
1176 				php_stdio_stream_data *self = (php_stdio_stream_data*)ret->abstract;
1177 				self->is_pipe_blocking = 1;
1178 			}
1179 #endif
1180 
1181 			return ret;
1182 		}
1183 		close(fd);
1184 	}
1185 	if (persistent_id) {
1186 		efree(persistent_id);
1187 	}
1188 	return NULL;
1189 }
1190 /* }}} */
1191 
1192 
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)1193 static php_stream *php_plain_files_stream_opener(php_stream_wrapper *wrapper, const char *path, const char *mode,
1194 		int options, zend_string **opened_path, php_stream_context *context STREAMS_DC)
1195 {
1196 	if (((options & STREAM_DISABLE_OPEN_BASEDIR) == 0) && php_check_open_basedir(path)) {
1197 		return NULL;
1198 	}
1199 
1200 	return php_stream_fopen_rel(path, mode, opened_path, options);
1201 }
1202 
php_plain_files_url_stater(php_stream_wrapper * wrapper,const char * url,int flags,php_stream_statbuf * ssb,php_stream_context * context)1203 static int php_plain_files_url_stater(php_stream_wrapper *wrapper, const char *url, int flags, php_stream_statbuf *ssb, php_stream_context *context)
1204 {
1205 	if (!(flags & PHP_STREAM_URL_STAT_IGNORE_OPEN_BASEDIR)) {
1206 		if (strncasecmp(url, "file://", sizeof("file://") - 1) == 0) {
1207 			url += sizeof("file://") - 1;
1208 		}
1209 
1210 		if (php_check_open_basedir_ex(url, (flags & PHP_STREAM_URL_STAT_QUIET) ? 0 : 1)) {
1211 			return -1;
1212 		}
1213 	}
1214 
1215 #ifdef PHP_WIN32
1216 	if (flags & PHP_STREAM_URL_STAT_LINK) {
1217 		return VCWD_LSTAT(url, &ssb->sb);
1218 	}
1219 #else
1220 # ifdef HAVE_SYMLINK
1221 	if (flags & PHP_STREAM_URL_STAT_LINK) {
1222 		return VCWD_LSTAT(url, &ssb->sb);
1223 	} else
1224 # endif
1225 #endif
1226 		return VCWD_STAT(url, &ssb->sb);
1227 }
1228 
php_plain_files_unlink(php_stream_wrapper * wrapper,const char * url,int options,php_stream_context * context)1229 static int php_plain_files_unlink(php_stream_wrapper *wrapper, const char *url, int options, php_stream_context *context)
1230 {
1231 	int ret;
1232 
1233 	if (strncasecmp(url, "file://", sizeof("file://") - 1) == 0) {
1234 		url += sizeof("file://") - 1;
1235 	}
1236 
1237 	if (php_check_open_basedir(url)) {
1238 		return 0;
1239 	}
1240 
1241 	ret = VCWD_UNLINK(url);
1242 	if (ret == -1) {
1243 		if (options & REPORT_ERRORS) {
1244 			php_error_docref1(NULL, url, E_WARNING, "%s", strerror(errno));
1245 		}
1246 		return 0;
1247 	}
1248 
1249 	/* Clear stat cache (and realpath cache) */
1250 	php_clear_stat_cache(1, NULL, 0);
1251 
1252 	return 1;
1253 }
1254 
php_plain_files_rename(php_stream_wrapper * wrapper,const char * url_from,const char * url_to,int options,php_stream_context * context)1255 static int php_plain_files_rename(php_stream_wrapper *wrapper, const char *url_from, const char *url_to, int options, php_stream_context *context)
1256 {
1257 	int ret;
1258 
1259 	if (!url_from || !url_to) {
1260 		return 0;
1261 	}
1262 
1263 #ifdef PHP_WIN32
1264 	if (!php_win32_check_trailing_space(url_from, strlen(url_from))) {
1265 		php_win32_docref2_from_error(ERROR_INVALID_NAME, url_from, url_to);
1266 		return 0;
1267 	}
1268 	if (!php_win32_check_trailing_space(url_to, strlen(url_to))) {
1269 		php_win32_docref2_from_error(ERROR_INVALID_NAME, url_from, url_to);
1270 		return 0;
1271 	}
1272 #endif
1273 
1274 	if (strncasecmp(url_from, "file://", sizeof("file://") - 1) == 0) {
1275 		url_from += sizeof("file://") - 1;
1276 	}
1277 
1278 	if (strncasecmp(url_to, "file://", sizeof("file://") - 1) == 0) {
1279 		url_to += sizeof("file://") - 1;
1280 	}
1281 
1282 	if (php_check_open_basedir(url_from) || php_check_open_basedir(url_to)) {
1283 		return 0;
1284 	}
1285 
1286 	ret = VCWD_RENAME(url_from, url_to);
1287 
1288 	if (ret == -1) {
1289 #ifndef PHP_WIN32
1290 # ifdef EXDEV
1291 		if (errno == EXDEV) {
1292 			zend_stat_t sb;
1293 # if !defined(ZTS) && !defined(TSRM_WIN32)
1294 			/* not sure what to do in ZTS case, umask is not thread-safe */
1295 			int oldmask = umask(077);
1296 # endif
1297 			int success = 0;
1298 			if (php_copy_file(url_from, url_to) == SUCCESS) {
1299 				if (VCWD_STAT(url_from, &sb) == 0) {
1300 					success = 1;
1301 #  ifndef TSRM_WIN32
1302 					/*
1303 					 * Try to set user and permission info on the target.
1304 					 * If we're not root, then some of these may fail.
1305 					 * We try chown first, to set proper group info, relying
1306 					 * on the system environment to have proper umask to not allow
1307 					 * access to the file in the meantime.
1308 					 */
1309 					if (VCWD_CHOWN(url_to, sb.st_uid, sb.st_gid)) {
1310 						php_error_docref2(NULL, url_from, url_to, E_WARNING, "%s", strerror(errno));
1311 						if (errno != EPERM) {
1312 							success = 0;
1313 						}
1314 					}
1315 
1316 					if (success) {
1317 						if (VCWD_CHMOD(url_to, sb.st_mode)) {
1318 							php_error_docref2(NULL, url_from, url_to, E_WARNING, "%s", strerror(errno));
1319 							if (errno != EPERM) {
1320 								success = 0;
1321 							}
1322 						}
1323 					}
1324 #  endif
1325 					if (success) {
1326 						VCWD_UNLINK(url_from);
1327 					}
1328 				} else {
1329 					php_error_docref2(NULL, url_from, url_to, E_WARNING, "%s", strerror(errno));
1330 				}
1331 			} else {
1332 				php_error_docref2(NULL, url_from, url_to, E_WARNING, "%s", strerror(errno));
1333 			}
1334 #  if !defined(ZTS) && !defined(TSRM_WIN32)
1335 			umask(oldmask);
1336 #  endif
1337 			return success;
1338 		}
1339 # endif
1340 #endif
1341 
1342 #ifdef PHP_WIN32
1343 		php_win32_docref2_from_error(GetLastError(), url_from, url_to);
1344 #else
1345 		php_error_docref2(NULL, url_from, url_to, E_WARNING, "%s", strerror(errno));
1346 #endif
1347 		return 0;
1348 	}
1349 
1350 	/* Clear stat cache (and realpath cache) */
1351 	php_clear_stat_cache(1, NULL, 0);
1352 
1353 	return 1;
1354 }
1355 
php_plain_files_mkdir(php_stream_wrapper * wrapper,const char * dir,int mode,int options,php_stream_context * context)1356 static int php_plain_files_mkdir(php_stream_wrapper *wrapper, const char *dir, int mode, int options, php_stream_context *context)
1357 {
1358 	if (strncasecmp(dir, "file://", sizeof("file://") - 1) == 0) {
1359 		dir += sizeof("file://") - 1;
1360 	}
1361 
1362 	if (!(options & PHP_STREAM_MKDIR_RECURSIVE)) {
1363 		return php_mkdir(dir, mode) == 0;
1364 	}
1365 
1366 	char buf[MAXPATHLEN];
1367 	if (!expand_filepath_with_mode(dir, buf, NULL, 0, CWD_EXPAND)) {
1368 		php_error_docref(NULL, E_WARNING, "Invalid path");
1369 		return 0;
1370 	}
1371 
1372 	if (php_check_open_basedir(buf)) {
1373 		return 0;
1374 	}
1375 
1376 	/* we look for directory separator from the end of string, thus hopefully reducing our work load */
1377 	char *p;
1378 	zend_stat_t sb;
1379 	size_t dir_len = strlen(dir), offset = 0;
1380 	char *e = buf +  strlen(buf);
1381 
1382 	if ((p = memchr(buf, DEFAULT_SLASH, dir_len))) {
1383 		offset = p - buf + 1;
1384 	}
1385 
1386 	if (p && dir_len == 1) {
1387 		/* buf == "DEFAULT_SLASH" */
1388 	}
1389 	else {
1390 		/* find a top level directory we need to create */
1391 		while ( (p = strrchr(buf + offset, DEFAULT_SLASH)) || (offset != 1 && (p = strrchr(buf, DEFAULT_SLASH))) ) {
1392 			int n = 0;
1393 
1394 			*p = '\0';
1395 			while (p > buf && *(p-1) == DEFAULT_SLASH) {
1396 				++n;
1397 				--p;
1398 				*p = '\0';
1399 			}
1400 			if (VCWD_STAT(buf, &sb) == 0) {
1401 				while (1) {
1402 					*p = DEFAULT_SLASH;
1403 					if (!n) break;
1404 					--n;
1405 					++p;
1406 				}
1407 				break;
1408 			}
1409 		}
1410 	}
1411 
1412 	if (!p) {
1413 		p = buf;
1414 	}
1415 	while (true) {
1416 		int ret = VCWD_MKDIR(buf, (mode_t) mode);
1417 		if (ret < 0 && errno != EEXIST) {
1418 			if (options & REPORT_ERRORS) {
1419 				php_error_docref(NULL, E_WARNING, "%s", strerror(errno));
1420 			}
1421 			return 0;
1422 		}
1423 
1424 		bool replaced_slash = false;
1425 		while (++p != e) {
1426 			if (*p == '\0') {
1427 				replaced_slash = true;
1428 				*p = DEFAULT_SLASH;
1429 				if (*(p+1) != '\0') {
1430 					break;
1431 				}
1432 			}
1433 		}
1434 		if (p == e || !replaced_slash) {
1435 			/* No more directories to create */
1436 			/* issue a warning to client when the last directory was created failed */
1437 			if (ret < 0) {
1438 				if (options & REPORT_ERRORS) {
1439 					php_error_docref(NULL, E_WARNING, "%s", strerror(errno));
1440 				}
1441 				return 0;
1442 			}
1443 			return 1;
1444 		}
1445 	}
1446 }
1447 
php_plain_files_rmdir(php_stream_wrapper * wrapper,const char * url,int options,php_stream_context * context)1448 static int php_plain_files_rmdir(php_stream_wrapper *wrapper, const char *url, int options, php_stream_context *context)
1449 {
1450 	if (strncasecmp(url, "file://", sizeof("file://") - 1) == 0) {
1451 		url += sizeof("file://") - 1;
1452 	}
1453 
1454 	if (php_check_open_basedir(url)) {
1455 		return 0;
1456 	}
1457 
1458 #ifdef PHP_WIN32
1459 	if (!php_win32_check_trailing_space(url, strlen(url))) {
1460 		php_error_docref1(NULL, url, E_WARNING, "%s", strerror(ENOENT));
1461 		return 0;
1462 	}
1463 #endif
1464 
1465 	if (VCWD_RMDIR(url) < 0) {
1466 		php_error_docref1(NULL, url, E_WARNING, "%s", strerror(errno));
1467 		return 0;
1468 	}
1469 
1470 	/* Clear stat cache (and realpath cache) */
1471 	php_clear_stat_cache(1, NULL, 0);
1472 
1473 	return 1;
1474 }
1475 
php_plain_files_metadata(php_stream_wrapper * wrapper,const char * url,int option,void * value,php_stream_context * context)1476 static int php_plain_files_metadata(php_stream_wrapper *wrapper, const char *url, int option, void *value, php_stream_context *context)
1477 {
1478 	struct utimbuf *newtime;
1479 #ifndef PHP_WIN32
1480 	uid_t uid;
1481 	gid_t gid;
1482 #endif
1483 	mode_t mode;
1484 	int ret = 0;
1485 
1486 #ifdef PHP_WIN32
1487 	if (!php_win32_check_trailing_space(url, strlen(url))) {
1488 		php_error_docref1(NULL, url, E_WARNING, "%s", strerror(ENOENT));
1489 		return 0;
1490 	}
1491 #endif
1492 
1493 	if (strncasecmp(url, "file://", sizeof("file://") - 1) == 0) {
1494 		url += sizeof("file://") - 1;
1495 	}
1496 
1497 	if (php_check_open_basedir(url)) {
1498 		return 0;
1499 	}
1500 
1501 	switch(option) {
1502 		case PHP_STREAM_META_TOUCH:
1503 			newtime = (struct utimbuf *)value;
1504 			if (VCWD_ACCESS(url, F_OK) != 0) {
1505 				FILE *file = VCWD_FOPEN(url, "w");
1506 				if (file == NULL) {
1507 					php_error_docref1(NULL, url, E_WARNING, "Unable to create file %s because %s", url, strerror(errno));
1508 					return 0;
1509 				}
1510 				fclose(file);
1511 			}
1512 
1513 			ret = VCWD_UTIME(url, newtime);
1514 			break;
1515 #ifndef PHP_WIN32
1516 		case PHP_STREAM_META_OWNER_NAME:
1517 		case PHP_STREAM_META_OWNER:
1518 			if(option == PHP_STREAM_META_OWNER_NAME) {
1519 				if(php_get_uid_by_name((char *)value, &uid) != SUCCESS) {
1520 					php_error_docref1(NULL, url, E_WARNING, "Unable to find uid for %s", (char *)value);
1521 					return 0;
1522 				}
1523 			} else {
1524 				uid = (uid_t)*(long *)value;
1525 			}
1526 			ret = VCWD_CHOWN(url, uid, -1);
1527 			break;
1528 		case PHP_STREAM_META_GROUP:
1529 		case PHP_STREAM_META_GROUP_NAME:
1530 			if(option == PHP_STREAM_META_GROUP_NAME) {
1531 				if(php_get_gid_by_name((char *)value, &gid) != SUCCESS) {
1532 					php_error_docref1(NULL, url, E_WARNING, "Unable to find gid for %s", (char *)value);
1533 					return 0;
1534 				}
1535 			} else {
1536 				gid = (gid_t)*(long *)value;
1537 			}
1538 			ret = VCWD_CHOWN(url, -1, gid);
1539 			break;
1540 #endif
1541 		case PHP_STREAM_META_ACCESS:
1542 			mode = (mode_t)*(zend_long *)value;
1543 			ret = VCWD_CHMOD(url, mode);
1544 			break;
1545 		default:
1546 			zend_value_error("Unknown option %d for stream_metadata", option);
1547 			return 0;
1548 	}
1549 	if (ret == -1) {
1550 		php_error_docref1(NULL, url, E_WARNING, "Operation failed: %s", strerror(errno));
1551 		return 0;
1552 	}
1553 	php_clear_stat_cache(0, NULL, 0);
1554 	return 1;
1555 }
1556 
1557 
1558 static const php_stream_wrapper_ops php_plain_files_wrapper_ops = {
1559 	php_plain_files_stream_opener,
1560 	NULL,
1561 	NULL,
1562 	php_plain_files_url_stater,
1563 	php_plain_files_dir_opener,
1564 	"plainfile",
1565 	php_plain_files_unlink,
1566 	php_plain_files_rename,
1567 	php_plain_files_mkdir,
1568 	php_plain_files_rmdir,
1569 	php_plain_files_metadata
1570 };
1571 
1572 /* TODO: We have to make php_plain_files_wrapper writable to support SWOOLE */
1573 PHPAPI /*const*/ php_stream_wrapper php_plain_files_wrapper = {
1574 	&php_plain_files_wrapper_ops,
1575 	NULL,
1576 	0
1577 };
1578 
1579 /* {{{ 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)1580 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)
1581 {
1582 	/* code ripped off from fopen_wrappers.c */
1583 	char *pathbuf, *end;
1584 	const char *ptr;
1585 	char trypath[MAXPATHLEN];
1586 	php_stream *stream;
1587 	size_t filename_length;
1588 	zend_string *exec_filename;
1589 
1590 	if (opened_path) {
1591 		*opened_path = NULL;
1592 	}
1593 
1594 	if(!filename) {
1595 		return NULL;
1596 	}
1597 
1598 	filename_length = strlen(filename);
1599 #ifndef PHP_WIN32
1600 	(void) filename_length;
1601 #endif
1602 
1603 	/* Relative path open */
1604 	if (*filename == '.' && (IS_SLASH(filename[1]) || filename[1] == '.')) {
1605 		/* further checks, we could have ....... filenames */
1606 		ptr = filename + 1;
1607 		if (*ptr == '.') {
1608 			while (*(++ptr) == '.');
1609 			if (!IS_SLASH(*ptr)) { /* not a relative path after all */
1610 				goto not_relative_path;
1611 			}
1612 		}
1613 
1614 
1615 		if (((options & STREAM_DISABLE_OPEN_BASEDIR) == 0) && php_check_open_basedir(filename)) {
1616 			return NULL;
1617 		}
1618 
1619 		return php_stream_fopen_rel(filename, mode, opened_path, options);
1620 	}
1621 
1622 not_relative_path:
1623 
1624 	/* Absolute path open */
1625 	if (IS_ABSOLUTE_PATH(filename, filename_length)) {
1626 
1627 		if (((options & STREAM_DISABLE_OPEN_BASEDIR) == 0) && php_check_open_basedir(filename)) {
1628 			return NULL;
1629 		}
1630 
1631 		return php_stream_fopen_rel(filename, mode, opened_path, options);
1632 	}
1633 
1634 #ifdef PHP_WIN32
1635 	if (IS_SLASH(filename[0])) {
1636 		size_t cwd_len;
1637 		char *cwd;
1638 		cwd = virtual_getcwd_ex(&cwd_len);
1639 		/* getcwd() will return always return [DRIVE_LETTER]:/) on windows. */
1640 		*(cwd+3) = '\0';
1641 
1642 		if (snprintf(trypath, MAXPATHLEN, "%s%s", cwd, filename) >= MAXPATHLEN) {
1643 			php_error_docref(NULL, E_NOTICE, "%s/%s path was truncated to %d", cwd, filename, MAXPATHLEN);
1644 		}
1645 
1646 		efree(cwd);
1647 
1648 		if (((options & STREAM_DISABLE_OPEN_BASEDIR) == 0) && php_check_open_basedir(trypath)) {
1649 			return NULL;
1650 		}
1651 
1652 		return php_stream_fopen_rel(trypath, mode, opened_path, options);
1653 	}
1654 #endif
1655 
1656 	if (!path || !*path) {
1657 		return php_stream_fopen_rel(filename, mode, opened_path, options);
1658 	}
1659 
1660 	/* check in provided path */
1661 	/* append the calling scripts' current working directory
1662 	 * as a fall back case
1663 	 */
1664 	if (zend_is_executing() &&
1665 	    (exec_filename = zend_get_executed_filename_ex()) != NULL) {
1666 		const char *exec_fname = ZSTR_VAL(exec_filename);
1667 		size_t exec_fname_length = ZSTR_LEN(exec_filename);
1668 
1669 		while ((--exec_fname_length < SIZE_MAX) && !IS_SLASH(exec_fname[exec_fname_length]));
1670 		if (exec_fname_length<=0) {
1671 			/* no path */
1672 			pathbuf = estrdup(path);
1673 		} else {
1674 			size_t path_length = strlen(path);
1675 
1676 			pathbuf = (char *) emalloc(exec_fname_length + path_length +1 +1);
1677 			memcpy(pathbuf, path, path_length);
1678 			pathbuf[path_length] = DEFAULT_DIR_SEPARATOR;
1679 			memcpy(pathbuf+path_length+1, exec_fname, exec_fname_length);
1680 			pathbuf[path_length + exec_fname_length +1] = '\0';
1681 		}
1682 	} else {
1683 		pathbuf = estrdup(path);
1684 	}
1685 
1686 	ptr = pathbuf;
1687 
1688 	while (ptr && *ptr) {
1689 		end = strchr(ptr, DEFAULT_DIR_SEPARATOR);
1690 		if (end != NULL) {
1691 			*end = '\0';
1692 			end++;
1693 		}
1694 		if (*ptr == '\0') {
1695 			goto stream_skip;
1696 		}
1697 		if (snprintf(trypath, MAXPATHLEN, "%s/%s", ptr, filename) >= MAXPATHLEN) {
1698 			php_error_docref(NULL, E_NOTICE, "%s/%s path was truncated to %d", ptr, filename, MAXPATHLEN);
1699 		}
1700 
1701 		if (((options & STREAM_DISABLE_OPEN_BASEDIR) == 0) && php_check_open_basedir_ex(trypath, 0)) {
1702 			goto stream_skip;
1703 		}
1704 
1705 		stream = php_stream_fopen_rel(trypath, mode, opened_path, options);
1706 		if (stream) {
1707 			efree(pathbuf);
1708 			return stream;
1709 		}
1710 stream_skip:
1711 		ptr = end;
1712 	} /* end provided path */
1713 
1714 	efree(pathbuf);
1715 	return NULL;
1716 
1717 }
1718 /* }}} */
1719