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