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