Coverage Report

Created: 2025-07-23 06:33

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