Coverage Report

Created: 2026-07-25 06:39

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