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 #ifdef HAVE_SYS_WAIT_H
27 #include <sys/wait.h>
28 #endif
29 #ifdef 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 if (php_check_open_basedir(dir)) {
1378 return 0;
1379 }
1380
1381 int ret = VCWD_MKDIR(dir, (mode_t)mode);
1382 if (ret < 0 && (options & REPORT_ERRORS)) {
1383 php_error_docref(NULL, E_WARNING, "%s", strerror(errno));
1384 return 0;
1385 }
1386
1387 return 1;
1388 }
1389
1390 char buf[MAXPATHLEN];
1391 if (!expand_filepath_with_mode(dir, buf, NULL, 0, CWD_EXPAND)) {
1392 php_error_docref(NULL, E_WARNING, "Invalid path");
1393 return 0;
1394 }
1395
1396 if (php_check_open_basedir(buf)) {
1397 return 0;
1398 }
1399
1400 /* we look for directory separator from the end of string, thus hopefully reducing our work load */
1401 char *p;
1402 zend_stat_t sb;
1403 size_t dir_len = strlen(dir), offset = 0;
1404 char *e = buf + strlen(buf);
1405
1406 if ((p = memchr(buf, DEFAULT_SLASH, dir_len))) {
1407 offset = p - buf + 1;
1408 }
1409
1410 if (p && dir_len == 1) {
1411 /* buf == "DEFAULT_SLASH" */
1412 }
1413 else {
1414 /* find a top level directory we need to create */
1415 while ( (p = strrchr(buf + offset, DEFAULT_SLASH)) || (offset != 1 && (p = strrchr(buf, DEFAULT_SLASH))) ) {
1416 int n = 0;
1417
1418 *p = '\0';
1419 while (p > buf && *(p-1) == DEFAULT_SLASH) {
1420 ++n;
1421 --p;
1422 *p = '\0';
1423 }
1424 if (VCWD_STAT(buf, &sb) == 0) {
1425 while (1) {
1426 *p = DEFAULT_SLASH;
1427 if (!n) break;
1428 --n;
1429 ++p;
1430 }
1431 break;
1432 }
1433 }
1434 }
1435
1436 if (!p) {
1437 p = buf;
1438 }
1439 while (true) {
1440 int ret = VCWD_MKDIR(buf, (mode_t) mode);
1441 if (ret < 0 && errno != EEXIST) {
1442 if (options & REPORT_ERRORS) {
1443 php_error_docref(NULL, E_WARNING, "%s", strerror(errno));
1444 }
1445 return 0;
1446 }
1447
1448 bool replaced_slash = false;
1449 while (++p != e) {
1450 if (*p == '\0') {
1451 replaced_slash = true;
1452 *p = DEFAULT_SLASH;
1453 if (*(p+1) != '\0') {
1454 break;
1455 }
1456 }
1457 }
1458 if (p == e || !replaced_slash) {
1459 /* No more directories to create */
1460 /* issue a warning to client when the last directory was created failed */
1461 if (ret < 0) {
1462 if (options & REPORT_ERRORS) {
1463 php_error_docref(NULL, E_WARNING, "%s", strerror(errno));
1464 }
1465 return 0;
1466 }
1467 return 1;
1468 }
1469 }
1470 }
1471
php_plain_files_rmdir(php_stream_wrapper * wrapper,const char * url,int options,php_stream_context * context)1472 static int php_plain_files_rmdir(php_stream_wrapper *wrapper, const char *url, int options, php_stream_context *context)
1473 {
1474 if (strncasecmp(url, "file://", sizeof("file://") - 1) == 0) {
1475 url += sizeof("file://") - 1;
1476 }
1477
1478 if (php_check_open_basedir(url)) {
1479 return 0;
1480 }
1481
1482 #ifdef PHP_WIN32
1483 if (!php_win32_check_trailing_space(url, strlen(url))) {
1484 php_error_docref1(NULL, url, E_WARNING, "%s", strerror(ENOENT));
1485 return 0;
1486 }
1487 #endif
1488
1489 if (VCWD_RMDIR(url) < 0) {
1490 php_error_docref1(NULL, url, E_WARNING, "%s", strerror(errno));
1491 return 0;
1492 }
1493
1494 /* Clear stat cache (and realpath cache) */
1495 php_clear_stat_cache(1, NULL, 0);
1496
1497 return 1;
1498 }
1499
php_plain_files_metadata(php_stream_wrapper * wrapper,const char * url,int option,void * value,php_stream_context * context)1500 static int php_plain_files_metadata(php_stream_wrapper *wrapper, const char *url, int option, void *value, php_stream_context *context)
1501 {
1502 struct utimbuf *newtime;
1503 #ifndef PHP_WIN32
1504 uid_t uid;
1505 gid_t gid;
1506 #endif
1507 mode_t mode;
1508 int ret = 0;
1509
1510 #ifdef PHP_WIN32
1511 if (!php_win32_check_trailing_space(url, strlen(url))) {
1512 php_error_docref1(NULL, url, E_WARNING, "%s", strerror(ENOENT));
1513 return 0;
1514 }
1515 #endif
1516
1517 if (strncasecmp(url, "file://", sizeof("file://") - 1) == 0) {
1518 url += sizeof("file://") - 1;
1519 }
1520
1521 if (php_check_open_basedir(url)) {
1522 return 0;
1523 }
1524
1525 switch(option) {
1526 case PHP_STREAM_META_TOUCH:
1527 newtime = (struct utimbuf *)value;
1528 if (VCWD_ACCESS(url, F_OK) != 0) {
1529 FILE *file = VCWD_FOPEN(url, "w");
1530 if (file == NULL) {
1531 php_error_docref1(NULL, url, E_WARNING, "Unable to create file %s because %s", url, strerror(errno));
1532 return 0;
1533 }
1534 fclose(file);
1535 }
1536
1537 ret = VCWD_UTIME(url, newtime);
1538 break;
1539 #ifndef PHP_WIN32
1540 case PHP_STREAM_META_OWNER_NAME:
1541 case PHP_STREAM_META_OWNER:
1542 if(option == PHP_STREAM_META_OWNER_NAME) {
1543 if(php_get_uid_by_name((char *)value, &uid) != SUCCESS) {
1544 php_error_docref1(NULL, url, E_WARNING, "Unable to find uid for %s", (char *)value);
1545 return 0;
1546 }
1547 } else {
1548 uid = (uid_t)*(long *)value;
1549 }
1550 ret = VCWD_CHOWN(url, uid, -1);
1551 break;
1552 case PHP_STREAM_META_GROUP:
1553 case PHP_STREAM_META_GROUP_NAME:
1554 if(option == PHP_STREAM_META_GROUP_NAME) {
1555 if(php_get_gid_by_name((char *)value, &gid) != SUCCESS) {
1556 php_error_docref1(NULL, url, E_WARNING, "Unable to find gid for %s", (char *)value);
1557 return 0;
1558 }
1559 } else {
1560 gid = (gid_t)*(long *)value;
1561 }
1562 ret = VCWD_CHOWN(url, -1, gid);
1563 break;
1564 #endif
1565 case PHP_STREAM_META_ACCESS:
1566 mode = (mode_t)*(zend_long *)value;
1567 ret = VCWD_CHMOD(url, mode);
1568 break;
1569 default:
1570 zend_value_error("Unknown option %d for stream_metadata", option);
1571 return 0;
1572 }
1573 if (ret == -1) {
1574 php_error_docref1(NULL, url, E_WARNING, "Operation failed: %s", strerror(errno));
1575 return 0;
1576 }
1577 php_clear_stat_cache(0, NULL, 0);
1578 return 1;
1579 }
1580
1581
1582 static const php_stream_wrapper_ops php_plain_files_wrapper_ops = {
1583 php_plain_files_stream_opener,
1584 NULL,
1585 NULL,
1586 php_plain_files_url_stater,
1587 php_plain_files_dir_opener,
1588 "plainfile",
1589 php_plain_files_unlink,
1590 php_plain_files_rename,
1591 php_plain_files_mkdir,
1592 php_plain_files_rmdir,
1593 php_plain_files_metadata
1594 };
1595
1596 /* TODO: We have to make php_plain_files_wrapper writable to support SWOOLE */
1597 PHPAPI /*const*/ php_stream_wrapper php_plain_files_wrapper = {
1598 &php_plain_files_wrapper_ops,
1599 NULL,
1600 0
1601 };
1602
1603 /* {{{ 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)1604 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)
1605 {
1606 /* code ripped off from fopen_wrappers.c */
1607 char *pathbuf, *end;
1608 const char *ptr;
1609 char trypath[MAXPATHLEN];
1610 php_stream *stream;
1611 size_t filename_length;
1612 zend_string *exec_filename;
1613
1614 if (opened_path) {
1615 *opened_path = NULL;
1616 }
1617
1618 if(!filename) {
1619 return NULL;
1620 }
1621
1622 filename_length = strlen(filename);
1623 #ifndef PHP_WIN32
1624 (void) filename_length;
1625 #endif
1626
1627 /* Relative path open */
1628 if (*filename == '.' && (IS_SLASH(filename[1]) || filename[1] == '.')) {
1629 /* further checks, we could have ....... filenames */
1630 ptr = filename + 1;
1631 if (*ptr == '.') {
1632 while (*(++ptr) == '.');
1633 if (!IS_SLASH(*ptr)) { /* not a relative path after all */
1634 goto not_relative_path;
1635 }
1636 }
1637
1638
1639 if (((options & STREAM_DISABLE_OPEN_BASEDIR) == 0) && php_check_open_basedir(filename)) {
1640 return NULL;
1641 }
1642
1643 return php_stream_fopen_rel(filename, mode, opened_path, options);
1644 }
1645
1646 not_relative_path:
1647
1648 /* Absolute path open */
1649 if (IS_ABSOLUTE_PATH(filename, filename_length)) {
1650
1651 if (((options & STREAM_DISABLE_OPEN_BASEDIR) == 0) && php_check_open_basedir(filename)) {
1652 return NULL;
1653 }
1654
1655 return php_stream_fopen_rel(filename, mode, opened_path, options);
1656 }
1657
1658 #ifdef PHP_WIN32
1659 if (IS_SLASH(filename[0])) {
1660 size_t cwd_len;
1661 char *cwd;
1662 cwd = virtual_getcwd_ex(&cwd_len);
1663 /* getcwd() will return always return [DRIVE_LETTER]:/) on windows. */
1664 *(cwd+3) = '\0';
1665
1666 if (snprintf(trypath, MAXPATHLEN, "%s%s", cwd, filename) >= MAXPATHLEN) {
1667 php_error_docref(NULL, E_NOTICE, "%s/%s path was truncated to %d", cwd, filename, MAXPATHLEN);
1668 }
1669
1670 efree(cwd);
1671
1672 if (((options & STREAM_DISABLE_OPEN_BASEDIR) == 0) && php_check_open_basedir(trypath)) {
1673 return NULL;
1674 }
1675
1676 return php_stream_fopen_rel(trypath, mode, opened_path, options);
1677 }
1678 #endif
1679
1680 if (!path || !*path) {
1681 return php_stream_fopen_rel(filename, mode, opened_path, options);
1682 }
1683
1684 /* check in provided path */
1685 /* append the calling scripts' current working directory
1686 * as a fallback case
1687 */
1688 if (zend_is_executing() &&
1689 (exec_filename = zend_get_executed_filename_ex()) != NULL) {
1690 const char *exec_fname = ZSTR_VAL(exec_filename);
1691 size_t exec_fname_length = ZSTR_LEN(exec_filename);
1692
1693 while ((--exec_fname_length < SIZE_MAX) && !IS_SLASH(exec_fname[exec_fname_length]));
1694 if (exec_fname_length<=0) {
1695 /* no path */
1696 pathbuf = estrdup(path);
1697 } else {
1698 size_t path_length = strlen(path);
1699
1700 pathbuf = (char *) emalloc(exec_fname_length + path_length +1 +1);
1701 memcpy(pathbuf, path, path_length);
1702 pathbuf[path_length] = DEFAULT_DIR_SEPARATOR;
1703 memcpy(pathbuf+path_length+1, exec_fname, exec_fname_length);
1704 pathbuf[path_length + exec_fname_length +1] = '\0';
1705 }
1706 } else {
1707 pathbuf = estrdup(path);
1708 }
1709
1710 ptr = pathbuf;
1711
1712 while (ptr && *ptr) {
1713 end = strchr(ptr, DEFAULT_DIR_SEPARATOR);
1714 if (end != NULL) {
1715 *end = '\0';
1716 end++;
1717 }
1718 if (*ptr == '\0') {
1719 goto stream_skip;
1720 }
1721 if (snprintf(trypath, MAXPATHLEN, "%s/%s", ptr, filename) >= MAXPATHLEN) {
1722 php_error_docref(NULL, E_NOTICE, "%s/%s path was truncated to %d", ptr, filename, MAXPATHLEN);
1723 }
1724
1725 if (((options & STREAM_DISABLE_OPEN_BASEDIR) == 0) && php_check_open_basedir_ex(trypath, 0)) {
1726 goto stream_skip;
1727 }
1728
1729 stream = php_stream_fopen_rel(trypath, mode, opened_path, options);
1730 if (stream) {
1731 efree(pathbuf);
1732 return stream;
1733 }
1734 stream_skip:
1735 ptr = end;
1736 } /* end provided path */
1737
1738 efree(pathbuf);
1739 return NULL;
1740
1741 }
1742 /* }}} */
1743