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