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