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