Coverage Report

Created: 2026-10-28 06:20

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
2
#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
2
# 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
11
{
76
11
  int flags;
77
78
11
  switch (mode[0]) {
79
11
    case 'r':
80
11
      flags = 0;
81
11
      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
11
  }
98
99
11
  if (strchr(mode, '+')) {
100
0
    flags |= O_RDWR;
101
11
  } else if (flags) {
102
0
    flags |= O_WRONLY;
103
11
  } else {
104
11
    flags |= O_RDONLY;
105
11
  }
106
107
11
#if defined(O_CLOEXEC)
108
11
  if (strchr(mode, 'e')) {
109
0
    flags |= O_CLOEXEC;
110
0
  }
111
11
#endif
112
113
11
#if defined(O_NONBLOCK)
114
11
  if (strchr(mode, 'n')) {
115
0
    flags |= O_NONBLOCK;
116
0
  }
117
11
#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
11
  *open_flags = flags;
128
11
  return SUCCESS;
129
11
}
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
4
#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
4
{
181
4
  if (!d->cached_fstat || (force && !d->no_forced_fstat)) {
182
2
    int fd;
183
2
    int r;
184
185
2
    PHP_STDIOP_GET_FD(fd, d);
186
2
    r = zend_fstat(fd, &d->sb);
187
2
    d->cached_fstat = r == 0;
188
189
2
    return r;
190
2
  }
191
2
  return 0;
192
4
}
193
194
static php_stream *_php_stream_fopen_from_fd_int(int fd, const char *mode, const char *persistent_id STREAMS_DC)
195
2
{
196
2
  php_stdio_stream_data *self;
197
198
2
  self = pemalloc_rel_orig(sizeof(*self), persistent_id);
199
2
  memset(self, 0, sizeof(*self));
200
2
  self->file = NULL;
201
2
  self->is_seekable = 1;
202
2
  self->is_pipe = 0;
203
2
  self->lock_flag = LOCK_UN;
204
2
  self->is_process_pipe = 0;
205
2
  self->temp_name = NULL;
206
2
  self->fd = fd;
207
#ifdef PHP_WIN32
208
  self->is_pipe_blocking = 0;
209
#endif
210
211
2
  return php_stream_alloc_rel(&php_stream_stdio_ops, self, persistent_id, mode);
212
2
}
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
0
static void detect_is_seekable(php_stdio_stream_data *self) {
273
0
#if defined(S_ISFIFO) && defined(S_ISCHR)
274
0
  if (self->fd >= 0 && do_fstat(self, 0) == 0) {
275
0
#ifdef __linux__
276
0
    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
0
    } else {
292
0
      self->is_seekable = !S_ISFIFO(self->sb.st_mode);
293
0
    }
294
#else
295
    self->is_seekable = !(S_ISFIFO(self->sb.st_mode) || S_ISCHR(self->sb.st_mode));
296
#endif
297
0
    self->is_pipe = S_ISFIFO(self->sb.st_mode);
298
0
  }
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
0
}
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
0
{
318
0
  php_stream *stream = php_stream_fopen_from_fd_int_rel(fd, mode, persistent_id);
319
320
0
  if (stream) {
321
0
    php_stdio_stream_data *self = (php_stdio_stream_data*)stream->abstract;
322
323
0
    detect_is_seekable(self);
324
0
    if (!self->is_seekable) {
325
0
      stream->flags |= PHP_STREAM_FLAG_NO_SEEK;
326
0
      stream->position = -1;
327
0
    } else if (zero_position) {
328
0
      ZEND_ASSERT(zend_lseek(self->fd, 0, SEEK_CUR) == 0);
329
0
      stream->position = 0;
330
0
    } 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
0
  }
341
342
0
  return stream;
343
0
}
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
2
{
437
2
  php_stdio_stream_data *data = (php_stdio_stream_data*)stream->abstract;
438
2
  ssize_t ret;
439
440
2
  assert(data != NULL);
441
442
2
  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
2
    ret = read(data->fd, buf,  PLAIN_WRAP_BUF_SIZE(count));
472
473
2
    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
2
    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
2
    } else if (ret == 0) {
500
2
      stream->eof = 1;
501
2
    }
502
503
2
  } 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
2
  if (EG(active)) {
516
    /* clear stat cache as atime got changed */
517
2
    php_clear_stat_cache(0, NULL, 0);
518
2
  }
519
520
2
  return ret;
521
2
}
522
523
static int php_stdiop_close(php_stream *stream, int close_handle)
524
2
{
525
2
  int ret;
526
2
  php_stdio_stream_data *data = (php_stdio_stream_data*)stream->abstract;
527
528
2
  assert(data != NULL);
529
530
2
#ifdef HAVE_MMAP
531
2
  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
2
  if (close_handle) {
547
2
    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
2
    } else if (data->fd != -1) {
562
2
      ret = close(data->fd);
563
2
      data->fd = -1;
564
2
    } else {
565
0
      return 0; /* everything should be closed already -> success */
566
0
    }
567
2
    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
2
  } else {
578
0
    ret = 0;
579
0
    data->file = NULL;
580
0
    data->fd = -1;
581
0
  }
582
583
2
  pefree(data, stream->is_persistent);
584
585
2
  return ret;
586
2
}
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
    case PHP_STREAM_AS_FD_FOR_POLL:
693
0
      PHP_STDIOP_GET_FD(fd, data);
694
0
      if (SOCK_ERR == fd) {
695
0
        return FAILURE;
696
0
      }
697
0
      if (ret) {
698
0
        *(php_socket_t *)ret = fd;
699
0
      }
700
0
      return SUCCESS;
701
702
0
    case PHP_STREAM_AS_FD:
703
0
      PHP_STDIOP_GET_FD(fd, data);
704
0
      if (SOCK_ERR == fd) {
705
0
        return FAILURE;
706
0
      }
707
0
      if (data->file) {
708
0
        fflush(data->file);
709
0
      }
710
0
      if (ret) {
711
0
        *(php_socket_t *)ret = fd;
712
0
      }
713
0
      return SUCCESS;
714
715
0
    case PHP_STREAM_AS_FD_FOR_COPY:
716
      /* stdio may read ahead, so use the buffered fallback for FILE* streams */
717
0
      if (data->file) {
718
0
        return FAILURE;
719
0
      }
720
0
      PHP_STDIOP_GET_FD(fd, data);
721
0
      if (SOCK_ERR == fd) {
722
0
        return FAILURE;
723
0
      }
724
0
      if (ret) {
725
0
        php_io_fd *copy_fd = (php_io_fd *) ret;
726
0
        copy_fd->fd = fd;
727
0
        copy_fd->fd_type = data->is_pipe ? PHP_IO_FD_PIPE : PHP_IO_FD_FILE;
728
0
        copy_fd->timeout.tv_sec = 0;
729
0
        copy_fd->timeout.tv_usec = 0;
730
0
        copy_fd->is_blocked = 0;
731
0
      }
732
0
      return SUCCESS;
733
734
0
    default:
735
0
      return FAILURE;
736
0
  }
737
0
}
738
739
static int php_stdiop_stat(php_stream *stream, php_stream_statbuf *ssb)
740
2
{
741
2
  int ret;
742
2
  php_stdio_stream_data *data = (php_stdio_stream_data*) stream->abstract;
743
744
2
  assert(data != NULL);
745
2
  if((ret = do_fstat(data, 1)) == 0) {
746
2
    memcpy(&ssb->sb, &data->sb, sizeof(ssb->sb));
747
2
  }
748
749
2
  return ret;
750
2
}
751
752
static int php_stdiop_set_option(php_stream *stream, int option, int value, void *ptrparam)
753
2
{
754
2
  php_stdio_stream_data *data = (php_stdio_stream_data*) stream->abstract;
755
2
  size_t size;
756
2
  int fd;
757
2
#ifdef O_NONBLOCK
758
  /* FIXME: make this work for win32 */
759
2
  int flags;
760
2
  int oldval;
761
2
#endif
762
763
2
  PHP_STDIOP_GET_FD(fd, data);
764
765
2
  switch(option) {
766
0
    case PHP_STREAM_OPTION_BLOCKING:
767
0
      if (fd == -1)
768
0
        return -1;
769
0
#ifdef O_NONBLOCK
770
0
      flags = fcntl(fd, F_GETFL, 0);
771
0
      oldval = (flags & O_NONBLOCK) ? 0 : 1;
772
0
      if (value)
773
0
        flags &= ~O_NONBLOCK;
774
0
      else
775
0
        flags |= O_NONBLOCK;
776
777
0
      if (-1 == fcntl(fd, F_SETFL, flags))
778
0
        return -1;
779
0
      return oldval;
780
#else
781
      return -1; /* not yet implemented */
782
#endif
783
784
0
    case PHP_STREAM_OPTION_WRITE_BUFFER:
785
786
0
      if (data->file == NULL) {
787
0
        return -1;
788
0
      }
789
790
0
      if (ptrparam)
791
0
        size = *(size_t *)ptrparam;
792
0
      else
793
0
        size = BUFSIZ;
794
795
0
      switch(value) {
796
0
        case PHP_STREAM_BUFFER_NONE:
797
0
          return setvbuf(data->file, NULL, _IONBF, 0);
798
799
0
        case PHP_STREAM_BUFFER_LINE:
800
0
          return setvbuf(data->file, NULL, _IOLBF, size);
801
802
0
        case PHP_STREAM_BUFFER_FULL:
803
0
          return setvbuf(data->file, NULL, _IOFBF, size);
804
805
0
        default:
806
0
          return -1;
807
0
      }
808
0
      break;
809
810
0
    case PHP_STREAM_OPTION_LOCKING:
811
0
      if (fd == -1) {
812
0
        return -1;
813
0
      }
814
815
0
      if ((uintptr_t) ptrparam == PHP_STREAM_LOCK_SUPPORTED) {
816
0
        return 0;
817
0
      }
818
819
0
      if (!flock(fd, value)) {
820
0
        data->lock_flag = value;
821
0
        return 0;
822
0
      } else {
823
0
        return -1;
824
0
      }
825
0
      break;
826
827
0
    case PHP_STREAM_OPTION_MMAP_API:
828
0
#ifdef HAVE_MMAP
829
0
      {
830
0
        php_stream_mmap_range *range = (php_stream_mmap_range*)ptrparam;
831
0
        int prot, flags;
832
833
0
        switch (value) {
834
0
          case PHP_STREAM_MMAP_SUPPORTED:
835
0
            return fd == -1 ? PHP_STREAM_OPTION_RETURN_ERR : PHP_STREAM_OPTION_RETURN_OK;
836
837
0
          case PHP_STREAM_MMAP_MAP_RANGE:
838
0
            if (do_fstat(data, 1) != 0) {
839
0
              return PHP_STREAM_OPTION_RETURN_ERR;
840
0
            }
841
0
            if (range->offset > data->sb.st_size) {
842
0
              range->offset = data->sb.st_size;
843
0
            }
844
0
            if (range->length == 0 ||
845
0
                range->length > data->sb.st_size - range->offset) {
846
0
              range->length = data->sb.st_size - range->offset;
847
0
            }
848
0
            switch (range->mode) {
849
0
              case PHP_STREAM_MAP_MODE_READONLY:
850
0
                prot = PROT_READ;
851
0
                flags = MAP_PRIVATE;
852
0
                break;
853
0
              case PHP_STREAM_MAP_MODE_READWRITE:
854
0
                prot = PROT_READ | PROT_WRITE;
855
0
                flags = MAP_PRIVATE;
856
0
                break;
857
0
              case PHP_STREAM_MAP_MODE_SHARED_READONLY:
858
0
                prot = PROT_READ;
859
0
                flags = MAP_SHARED;
860
0
                break;
861
0
              case PHP_STREAM_MAP_MODE_SHARED_READWRITE:
862
0
                prot = PROT_READ | PROT_WRITE;
863
0
                flags = MAP_SHARED;
864
0
                break;
865
0
              default:
866
0
                return PHP_STREAM_OPTION_RETURN_ERR;
867
0
            }
868
0
            range->mapped = (char*)mmap(NULL, range->length, prot, flags, fd, range->offset);
869
0
            if (range->mapped == (char*)MAP_FAILED) {
870
0
              range->mapped = NULL;
871
0
              return PHP_STREAM_OPTION_RETURN_ERR;
872
0
            }
873
            /* remember the mapping */
874
0
            data->last_mapped_addr = range->mapped;
875
0
            data->last_mapped_len = range->length;
876
0
            return PHP_STREAM_OPTION_RETURN_OK;
877
878
0
          case PHP_STREAM_MMAP_UNMAP:
879
0
            if (data->last_mapped_addr) {
880
0
              munmap(data->last_mapped_addr, data->last_mapped_len);
881
0
              data->last_mapped_addr = NULL;
882
883
0
              return PHP_STREAM_OPTION_RETURN_OK;
884
0
            }
885
0
            return PHP_STREAM_OPTION_RETURN_ERR;
886
0
        }
887
0
      }
888
#elif defined(PHP_WIN32)
889
      {
890
        php_stream_mmap_range *range = (php_stream_mmap_range*)ptrparam;
891
        HANDLE hfile = (HANDLE)_get_osfhandle(fd);
892
        DWORD prot, acc, loffs = 0, hoffs = 0, delta = 0;
893
        LARGE_INTEGER file_size;
894
895
        switch (value) {
896
          case PHP_STREAM_MMAP_SUPPORTED:
897
            return hfile == INVALID_HANDLE_VALUE ? PHP_STREAM_OPTION_RETURN_ERR : PHP_STREAM_OPTION_RETURN_OK;
898
899
          case PHP_STREAM_MMAP_MAP_RANGE:
900
            switch (range->mode) {
901
              case PHP_STREAM_MAP_MODE_READONLY:
902
                prot = PAGE_READONLY;
903
                acc = FILE_MAP_READ;
904
                break;
905
              case PHP_STREAM_MAP_MODE_READWRITE:
906
                prot = PAGE_READWRITE;
907
                acc = FILE_MAP_READ | FILE_MAP_WRITE;
908
                break;
909
              case PHP_STREAM_MAP_MODE_SHARED_READONLY:
910
                prot = PAGE_READONLY;
911
                acc = FILE_MAP_READ;
912
                /* TODO: we should assign a name for the mapping */
913
                break;
914
              case PHP_STREAM_MAP_MODE_SHARED_READWRITE:
915
                prot = PAGE_READWRITE;
916
                acc = FILE_MAP_READ | FILE_MAP_WRITE;
917
                /* TODO: we should assign a name for the mapping */
918
                break;
919
              default:
920
                return PHP_STREAM_OPTION_RETURN_ERR;
921
            }
922
923
            /* create a mapping capable of viewing the whole file (this costs no real resources) */
924
            data->file_mapping = CreateFileMapping(hfile, NULL, prot, 0, 0, NULL);
925
926
            if (data->file_mapping == NULL) {
927
              return PHP_STREAM_OPTION_RETURN_ERR;
928
            }
929
930
            if (!GetFileSizeEx(hfile, &file_size)) {
931
              CloseHandle(data->file_mapping);
932
              data->file_mapping = NULL;
933
              return PHP_STREAM_OPTION_RETURN_ERR;
934
            }
935
# if defined(_WIN64)
936
            size = file_size.QuadPart;
937
# else
938
            if (file_size.HighPart) {
939
              CloseHandle(data->file_mapping);
940
              data->file_mapping = NULL;
941
              return PHP_STREAM_OPTION_RETURN_ERR;
942
            } else {
943
              size = file_size.LowPart;
944
            }
945
# endif
946
            if (range->offset > size) {
947
              range->offset = size;
948
            }
949
            if (range->length == 0 || range->length > size - range->offset) {
950
              range->length = size - range->offset;
951
            }
952
953
            /* figure out how big a chunk to map to be able to view the part that we need */
954
            if (range->offset != 0) {
955
              SYSTEM_INFO info;
956
              DWORD gran;
957
958
              GetSystemInfo(&info);
959
              gran = info.dwAllocationGranularity;
960
              ZEND_ASSERT(gran != 0 && (gran & (gran - 1)) == 0);
961
              size_t rounded_offset = (range->offset / gran) * gran;
962
              delta = range->offset - rounded_offset;
963
              loffs = (DWORD)rounded_offset;
964
#ifdef _WIN64
965
              hoffs = (DWORD)(rounded_offset >> 32);
966
#else
967
              hoffs = 0;
968
#endif
969
            }
970
971
            /* MapViewOfFile()ing zero bytes would map to the end of the file; match *nix behavior instead */
972
            if (range->length + delta == 0) {
973
              return PHP_STREAM_OPTION_RETURN_ERR;
974
            }
975
976
            data->last_mapped_addr = MapViewOfFile(data->file_mapping, acc, hoffs, loffs, range->length + delta);
977
978
            if (data->last_mapped_addr) {
979
              /* give them back the address of the start offset they requested */
980
              range->mapped = data->last_mapped_addr + delta;
981
              return PHP_STREAM_OPTION_RETURN_OK;
982
            }
983
984
            CloseHandle(data->file_mapping);
985
            data->file_mapping = NULL;
986
987
            return PHP_STREAM_OPTION_RETURN_ERR;
988
989
          case PHP_STREAM_MMAP_UNMAP:
990
            if (data->last_mapped_addr) {
991
              UnmapViewOfFile(data->last_mapped_addr);
992
              data->last_mapped_addr = NULL;
993
              CloseHandle(data->file_mapping);
994
              data->file_mapping = NULL;
995
              return PHP_STREAM_OPTION_RETURN_OK;
996
            }
997
            return PHP_STREAM_OPTION_RETURN_ERR;
998
999
          default:
1000
            return PHP_STREAM_OPTION_RETURN_ERR;
1001
        }
1002
      }
1003
1004
#endif
1005
0
      return PHP_STREAM_OPTION_RETURN_NOTIMPL;
1006
1007
0
    case PHP_STREAM_OPTION_SYNC_API:
1008
0
      switch (value) {
1009
0
        case PHP_STREAM_SYNC_SUPPORTED:
1010
0
          return fd == -1 ? PHP_STREAM_OPTION_RETURN_ERR : PHP_STREAM_OPTION_RETURN_OK;
1011
0
        case PHP_STREAM_SYNC_FSYNC:
1012
0
          return php_stdiop_sync(stream, 0) == 0 ? PHP_STREAM_OPTION_RETURN_OK : PHP_STREAM_OPTION_RETURN_ERR;
1013
0
        case PHP_STREAM_SYNC_FDSYNC:
1014
0
          return php_stdiop_sync(stream, 1) == 0 ? PHP_STREAM_OPTION_RETURN_OK : PHP_STREAM_OPTION_RETURN_ERR;
1015
0
      }
1016
      /* Invalid option passed */
1017
0
      return PHP_STREAM_OPTION_RETURN_ERR;
1018
1019
0
    case PHP_STREAM_OPTION_TRUNCATE_API:
1020
0
      switch (value) {
1021
0
        case PHP_STREAM_TRUNCATE_SUPPORTED:
1022
0
          return fd == -1 ? PHP_STREAM_OPTION_RETURN_ERR : PHP_STREAM_OPTION_RETURN_OK;
1023
1024
0
        case PHP_STREAM_TRUNCATE_SET_SIZE: {
1025
0
          ptrdiff_t new_size = *(ptrdiff_t*)ptrparam;
1026
0
          if (new_size < 0) {
1027
0
            return PHP_STREAM_OPTION_RETURN_ERR;
1028
0
          }
1029
#ifdef PHP_WIN32
1030
          HANDLE h = (HANDLE) _get_osfhandle(fd);
1031
          if (INVALID_HANDLE_VALUE == h) {
1032
            return PHP_STREAM_OPTION_RETURN_ERR;
1033
          }
1034
1035
          LARGE_INTEGER sz, old_sz;
1036
          sz.QuadPart = 0;
1037
1038
          if (!SetFilePointerEx(h, sz, &old_sz, FILE_CURRENT)) {
1039
            return PHP_STREAM_OPTION_RETURN_ERR;
1040
          }
1041
1042
#ifdef _WIN64
1043
          sz.QuadPart = new_size;
1044
#else
1045
          sz.HighPart = 0;
1046
          sz.LowPart = new_size;
1047
#endif
1048
          if (!SetFilePointerEx(h, sz, NULL, FILE_BEGIN)) {
1049
            return PHP_STREAM_OPTION_RETURN_ERR;
1050
          }
1051
          if (0 == SetEndOfFile(h)) {
1052
            return PHP_STREAM_OPTION_RETURN_ERR;
1053
          }
1054
          if (!SetFilePointerEx(h, old_sz, NULL, FILE_BEGIN)) {
1055
            return PHP_STREAM_OPTION_RETURN_ERR;
1056
          }
1057
          return PHP_STREAM_OPTION_RETURN_OK;
1058
#else
1059
0
          return ftruncate(fd, new_size) == 0 ? PHP_STREAM_OPTION_RETURN_OK : PHP_STREAM_OPTION_RETURN_ERR;
1060
0
#endif
1061
0
        }
1062
0
      }
1063
0
      return PHP_STREAM_OPTION_RETURN_NOTIMPL;
1064
1065
#ifdef PHP_WIN32
1066
    case PHP_STREAM_OPTION_PIPE_BLOCKING:
1067
      data->is_pipe_blocking = value;
1068
      return PHP_STREAM_OPTION_RETURN_OK;
1069
#endif
1070
0
    case PHP_STREAM_OPTION_META_DATA_API:
1071
0
      if (fd == -1)
1072
0
        return -1;
1073
0
#ifdef O_NONBLOCK
1074
0
      flags = fcntl(fd, F_GETFL, 0);
1075
1076
0
      add_assoc_bool((zval*)ptrparam, "timed_out", 0);
1077
0
      add_assoc_bool((zval*)ptrparam, "blocked", (flags & O_NONBLOCK)? 0 : 1);
1078
0
      add_assoc_bool((zval*)ptrparam, "eof", stream->eof);
1079
1080
0
      return PHP_STREAM_OPTION_RETURN_OK;
1081
0
#endif
1082
0
      return -1;
1083
2
    default:
1084
2
      return PHP_STREAM_OPTION_RETURN_NOTIMPL;
1085
2
  }
1086
2
}
1087
1088
/* This should be "const", but phpdbg overwrite it */
1089
PHPAPI php_stream_ops php_stream_stdio_ops = {
1090
  php_stdiop_write, php_stdiop_read,
1091
  php_stdiop_close, php_stdiop_flush,
1092
  "STDIO",
1093
  php_stdiop_seek,
1094
  php_stdiop_cast,
1095
  php_stdiop_stat,
1096
  php_stdiop_set_option
1097
};
1098
/* }}} */
1099
1100
/* {{{ plain files opendir/readdir implementation */
1101
static ssize_t php_plain_files_dirstream_read(php_stream *stream, char *buf, size_t count)
1102
0
{
1103
0
  DIR *dir = (DIR*)stream->abstract;
1104
0
  struct dirent *result;
1105
0
  php_stream_dirent *ent = (php_stream_dirent*)buf;
1106
1107
  /* avoid problems if someone mis-uses the stream */
1108
0
  if (count != sizeof(php_stream_dirent))
1109
0
    return -1;
1110
1111
0
  result = readdir(dir);
1112
0
  if (result) {
1113
0
    size_t len = strlen(result->d_name);
1114
0
    if (UNEXPECTED(len >= sizeof(ent->d_name))) {
1115
0
      return -1;
1116
0
    }
1117
    /* Include null byte */
1118
0
    memcpy(ent->d_name, result->d_name, len+1);
1119
0
#ifdef _DIRENT_HAVE_D_TYPE
1120
0
    ent->d_type = result->d_type;
1121
#else
1122
    ent->d_type = DT_UNKNOWN;
1123
#endif
1124
0
    return sizeof(php_stream_dirent);
1125
0
  }
1126
0
  return 0;
1127
0
}
1128
1129
static int php_plain_files_dirstream_close(php_stream *stream, int close_handle)
1130
0
{
1131
0
  return closedir((DIR *)stream->abstract);
1132
0
}
1133
1134
static int php_plain_files_dirstream_rewind(php_stream *stream, zend_off_t offset, int whence, zend_off_t *newoffs)
1135
0
{
1136
0
  rewinddir((DIR *)stream->abstract);
1137
0
  return 0;
1138
0
}
1139
1140
static const php_stream_ops php_plain_files_dirstream_ops = {
1141
  NULL, php_plain_files_dirstream_read,
1142
  php_plain_files_dirstream_close, NULL,
1143
  "dir",
1144
  php_plain_files_dirstream_rewind,
1145
  NULL, /* cast */
1146
  NULL, /* stat */
1147
  NULL  /* set_option */
1148
};
1149
1150
static php_stream *php_plain_files_dir_opener(php_stream_wrapper *wrapper, const char *path, const char *mode,
1151
    int options, zend_string **opened_path, php_stream_context *context STREAMS_DC)
1152
0
{
1153
0
  DIR *dir = NULL;
1154
0
  php_stream *stream = NULL;
1155
1156
0
  if (options & STREAM_USE_GLOB_DIR_OPEN) {
1157
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);
1158
0
  }
1159
1160
0
  if (((options & STREAM_DISABLE_OPEN_BASEDIR) == 0) && php_check_open_basedir(path)) {
1161
0
    return NULL;
1162
0
  }
1163
1164
0
  dir = VCWD_OPENDIR(path);
1165
1166
#ifdef PHP_WIN32
1167
  if (!dir) {
1168
    php_win32_stream_wrapper_warn_error(wrapper, context, options, PHP_STREAM_EC(OpenFailed), GetLastError());
1169
  }
1170
1171
  if (dir && dir->finished) {
1172
    closedir(dir);
1173
    dir = NULL;
1174
  }
1175
#endif
1176
0
  if (dir) {
1177
0
    stream = php_stream_alloc(&php_plain_files_dirstream_ops, dir, 0, mode);
1178
0
    if (stream == NULL)
1179
0
      closedir(dir);
1180
0
  }
1181
1182
0
  return stream;
1183
0
}
1184
/* }}} */
1185
1186
/* {{{ php_stream_fopen */
1187
PHPAPI php_stream *_php_stream_fopen(const char *filename, const char *mode, zend_string **opened_path, int options STREAMS_DC)
1188
11
{
1189
11
  char realpath[MAXPATHLEN];
1190
11
  int open_flags;
1191
11
  int fd;
1192
11
  php_stream *ret;
1193
11
  int persistent = options & STREAM_OPEN_PERSISTENT;
1194
11
  char *persistent_id = NULL;
1195
1196
11
  if (FAILURE == php_stream_parse_fopen_modes(mode, &open_flags)) {
1197
0
    php_stream_wrapper_log_warn(&php_plain_files_wrapper, NULL, options,
1198
0
        InvalidMode, "`%s' is not a valid mode for fopen", mode);
1199
0
    return NULL;
1200
0
  }
1201
1202
11
  if (options & STREAM_ASSUME_REALPATH) {
1203
2
    strlcpy(realpath, filename, sizeof(realpath));
1204
9
  } else {
1205
9
    if (expand_filepath(filename, realpath) == NULL) {
1206
0
      return NULL;
1207
0
    }
1208
9
  }
1209
1210
11
  if (persistent) {
1211
0
    spprintf(&persistent_id, 0, "streams_stdio_%d_%s", open_flags, realpath);
1212
0
    switch (php_stream_from_persistent_id(persistent_id, &ret)) {
1213
0
      case PHP_STREAM_PERSISTENT_SUCCESS:
1214
0
        if (opened_path) {
1215
          //TODO: avoid reallocation???
1216
0
          *opened_path = zend_string_init(realpath, strlen(realpath), 0);
1217
0
        }
1218
0
        ZEND_FALLTHROUGH;
1219
1220
0
      case PHP_STREAM_PERSISTENT_FAILURE:
1221
0
        efree(persistent_id);
1222
0
        return ret;
1223
0
    }
1224
0
  }
1225
#ifdef PHP_WIN32
1226
  fd = php_win32_ioutil_open(realpath, open_flags, 0666);
1227
#else
1228
11
  fd = open(realpath, open_flags, 0666);
1229
11
#endif
1230
11
  if (fd != -1)  {
1231
1232
2
    if (options & STREAM_OPEN_FOR_INCLUDE) {
1233
2
      ret = php_stream_fopen_from_fd_int_rel(fd, mode, persistent_id);
1234
2
    } else {
1235
      /* skip the lseek(SEEK_CUR) system call to
1236
       * determine the current offset because we
1237
       * know newly opened files are at offset zero
1238
       * (unless the file has been opened in
1239
       * O_APPEND mode) */
1240
0
      ret = php_stream_fopen_from_fd_rel(fd, mode, persistent_id, (open_flags & O_APPEND) == 0);
1241
0
    }
1242
1243
2
    if (EG(active)) {
1244
      /* clear stat cache as mtime and ctime might got changed - phar can use stream before
1245
       * cache is initialized so we need to check if the execution is active. */
1246
2
      php_clear_stat_cache(0, NULL, 0);
1247
2
    }
1248
1249
2
    if (ret) {
1250
2
      if (opened_path) {
1251
2
        *opened_path = zend_string_init(realpath, strlen(realpath), 0);
1252
2
      }
1253
2
      if (persistent_id) {
1254
0
        efree(persistent_id);
1255
0
      }
1256
1257
      /* WIN32 always set ISREG flag */
1258
2
#ifndef PHP_WIN32
1259
      /* sanity checks for include/require.
1260
       * We check these after opening the stream, so that we save
1261
       * on fstat() syscalls */
1262
2
      if (options & STREAM_OPEN_FOR_INCLUDE) {
1263
2
        php_stdio_stream_data *self = (php_stdio_stream_data*)ret->abstract;
1264
2
        int r;
1265
1266
2
        r = do_fstat(self, 0);
1267
2
        if ((r == 0 && !S_ISREG(self->sb.st_mode))) {
1268
0
          if (opened_path) {
1269
0
            zend_string_release_ex(*opened_path, 0);
1270
0
            *opened_path = NULL;
1271
0
          }
1272
0
          php_stream_close(ret);
1273
0
          return NULL;
1274
0
        }
1275
1276
        /* Make sure the fstat result is reused when we later try to get the
1277
         * file size. */
1278
2
        self->no_forced_fstat = 1;
1279
2
      }
1280
1281
2
      if (options & STREAM_USE_BLOCKING_PIPE) {
1282
0
        php_stdio_stream_data *self = (php_stdio_stream_data*)ret->abstract;
1283
0
        self->is_pipe_blocking = 1;
1284
0
      }
1285
2
#endif
1286
1287
2
      return ret;
1288
2
    }
1289
0
    close(fd);
1290
0
  }
1291
9
  if (persistent_id) {
1292
0
    efree(persistent_id);
1293
0
  }
1294
9
  return NULL;
1295
11
}
1296
/* }}} */
1297
1298
1299
static php_stream *php_plain_files_stream_opener(php_stream_wrapper *wrapper, const char *path, const char *mode,
1300
    int options, zend_string **opened_path, php_stream_context *context STREAMS_DC)
1301
291
{
1302
291
  if (((options & STREAM_DISABLE_OPEN_BASEDIR) == 0) && php_check_open_basedir(path)) {
1303
280
    return NULL;
1304
280
  }
1305
1306
11
  return php_stream_fopen_rel(path, mode, opened_path, options);
1307
291
}
1308
1309
static int php_plain_files_url_stater(php_stream_wrapper *wrapper, const char *url, int flags, php_stream_statbuf *ssb, php_stream_context *context)
1310
0
{
1311
0
  if (!(flags & PHP_STREAM_URL_STAT_IGNORE_OPEN_BASEDIR)) {
1312
0
    if (strncasecmp(url, "file://", sizeof("file://") - 1) == 0) {
1313
0
      url += sizeof("file://") - 1;
1314
0
    }
1315
1316
0
    if (php_check_open_basedir_ex(url, (flags & PHP_STREAM_URL_STAT_QUIET) ? 0 : 1)) {
1317
0
      return -1;
1318
0
    }
1319
0
  }
1320
1321
#ifdef PHP_WIN32
1322
  if (flags & PHP_STREAM_URL_STAT_LINK) {
1323
    return VCWD_LSTAT(url, &ssb->sb);
1324
  }
1325
#else
1326
0
# ifdef HAVE_SYMLINK
1327
0
  if (flags & PHP_STREAM_URL_STAT_LINK) {
1328
0
    return VCWD_LSTAT(url, &ssb->sb);
1329
0
  } else
1330
0
# endif
1331
0
#endif
1332
0
    return VCWD_STAT(url, &ssb->sb);
1333
0
}
1334
1335
static int php_plain_files_unlink(php_stream_wrapper *wrapper, const char *url, int options, php_stream_context *context)
1336
0
{
1337
0
  int ret;
1338
1339
0
  if (strncasecmp(url, "file://", sizeof("file://") - 1) == 0) {
1340
0
    url += sizeof("file://") - 1;
1341
0
  }
1342
1343
0
  if (php_check_open_basedir(url)) {
1344
0
    return 0;
1345
0
  }
1346
1347
0
  ret = VCWD_UNLINK(url);
1348
0
  if (ret == -1) {
1349
0
    if (options & REPORT_ERRORS) {
1350
0
      char errstr[256];
1351
0
      php_stream_wrapper_warn(wrapper, context, options,
1352
0
          UnlinkFailed, "%s",
1353
0
          php_socket_strerror_s(errno, errstr, sizeof(errstr)));
1354
0
    }
1355
0
    return 0;
1356
0
  }
1357
1358
  /* Clear stat cache (and realpath cache) */
1359
0
  php_clear_stat_cache(1, NULL, 0);
1360
1361
0
  return 1;
1362
0
}
1363
1364
static int php_plain_files_rename(php_stream_wrapper *wrapper, const char *url_from, const char *url_to, int options, php_stream_context *context)
1365
0
{
1366
0
  int ret;
1367
1368
0
  if (!url_from || !url_to) {
1369
0
    return 0;
1370
0
  }
1371
1372
#ifdef PHP_WIN32
1373
  if (!php_win32_check_trailing_space(url_from, strlen(url_from))) {
1374
    php_win32_stream_wrapper_warn_error(wrapper, context, options, PHP_STREAM_EC(InvalidPath), ERROR_INVALID_NAME);
1375
    return 0;
1376
  }
1377
  if (!php_win32_check_trailing_space(url_to, strlen(url_to))) {
1378
    php_win32_stream_wrapper_warn_error(wrapper, context, options, PHP_STREAM_EC(InvalidPath), ERROR_INVALID_NAME);
1379
    return 0;
1380
  }
1381
#endif
1382
1383
0
  if (strncasecmp(url_from, "file://", sizeof("file://") - 1) == 0) {
1384
0
    url_from += sizeof("file://") - 1;
1385
0
  }
1386
1387
0
  if (strncasecmp(url_to, "file://", sizeof("file://") - 1) == 0) {
1388
0
    url_to += sizeof("file://") - 1;
1389
0
  }
1390
1391
0
  if (php_check_open_basedir(url_from) || php_check_open_basedir(url_to)) {
1392
0
    return 0;
1393
0
  }
1394
1395
0
  ret = VCWD_RENAME(url_from, url_to);
1396
1397
0
  if (ret == -1) {
1398
0
#ifndef PHP_WIN32
1399
0
    char errstr[256];
1400
0
# ifdef EXDEV
1401
0
    if (errno == EXDEV) {
1402
0
      zend_stat_t sb;
1403
0
# if !defined(ZTS) && !defined(TSRM_WIN32)
1404
      /* not sure what to do in ZTS case, umask is not thread-safe */
1405
0
      int oldmask = umask(077);
1406
0
# endif
1407
0
      int success = 0;
1408
0
      if (php_copy_file(url_from, url_to) == SUCCESS) {
1409
0
        if (VCWD_STAT(url_from, &sb) == 0) {
1410
0
          success = 1;
1411
0
#  ifndef TSRM_WIN32
1412
          /*
1413
           * Try to set user and permission info on the target.
1414
           * If we're not root, then some of these may fail.
1415
           * We try chown first, to set proper group info, relying
1416
           * on the system environment to have proper umask to not allow
1417
           * access to the file in the meantime.
1418
           */
1419
0
          if (VCWD_CHOWN(url_to, sb.st_uid, sb.st_gid)) {
1420
0
            if (errno != EPERM) {
1421
0
              success = 0;
1422
0
            }
1423
0
            php_stream_wrapper_error(wrapper, context, NULL, options, E_WARNING,
1424
0
                !success, PHP_STREAM_EC(ChownFailed),
1425
0
                "%s", php_socket_strerror_s(errno, errstr, sizeof(errstr)));
1426
0
          }
1427
1428
0
          if (success) {
1429
0
            if (VCWD_CHMOD(url_to, sb.st_mode)) {
1430
0
              if (errno != EPERM) {
1431
0
                success = 0;
1432
0
              }
1433
0
              php_stream_wrapper_error(wrapper, context, NULL, options, E_WARNING,
1434
0
                  !success, PHP_STREAM_EC(ChownFailed),
1435
0
                  "%s", php_socket_strerror_s(errno, errstr, sizeof(errstr)));
1436
0
            }
1437
0
          }
1438
0
#  endif
1439
0
          if (success) {
1440
0
            VCWD_UNLINK(url_from);
1441
0
          }
1442
0
        } else {
1443
0
          php_stream_wrapper_warn_nt(wrapper, context, options, StatFailed,
1444
0
              "%s", php_socket_strerror_s(errno, errstr, sizeof(errstr)));
1445
0
        }
1446
0
      } else {
1447
0
        php_stream_wrapper_warn_nt(wrapper, context, options, CopyFailed,
1448
0
            "%s", php_socket_strerror_s(errno, errstr, sizeof(errstr)));
1449
0
      }
1450
0
#  if !defined(ZTS) && !defined(TSRM_WIN32)
1451
0
      umask(oldmask);
1452
0
#  endif
1453
0
      return success;
1454
0
    }
1455
0
# endif
1456
0
#endif
1457
1458
#ifdef PHP_WIN32
1459
    php_win32_stream_wrapper_warn_error(wrapper, context, options, PHP_STREAM_EC(RenameFailed), GetLastError());
1460
#else
1461
0
    php_stream_wrapper_warn(wrapper, context, options,
1462
0
        RenameFailed,
1463
0
        "%s", php_socket_strerror_s(errno, errstr, sizeof(errstr)));
1464
0
#endif
1465
0
    return 0;
1466
0
  }
1467
1468
  /* Clear stat cache (and realpath cache) */
1469
0
  php_clear_stat_cache(1, NULL, 0);
1470
1471
0
  return 1;
1472
0
}
1473
1474
static int php_plain_files_mkdir(php_stream_wrapper *wrapper, const char *dir, int mode, int options, php_stream_context *context)
1475
0
{
1476
0
  if (strncasecmp(dir, "file://", sizeof("file://") - 1) == 0) {
1477
0
    dir += sizeof("file://") - 1;
1478
0
  }
1479
1480
0
  if (!(options & PHP_STREAM_MKDIR_RECURSIVE)) {
1481
0
    if (php_check_open_basedir(dir)) {
1482
0
      return 0;
1483
0
    }
1484
1485
0
    int ret = VCWD_MKDIR(dir, (mode_t)mode);
1486
0
    if (ret < 0) {
1487
0
      php_stream_wrapper_warn(wrapper, context, options,
1488
0
          MkdirFailed, "%s", strerror(errno));
1489
0
      return 0;
1490
0
    }
1491
1492
0
    return 1;
1493
0
  }
1494
1495
0
  char buf[MAXPATHLEN];
1496
0
  if (!expand_filepath_with_mode(dir, buf, NULL, 0, CWD_EXPAND)) {
1497
0
    php_stream_wrapper_warn(wrapper, context, options,
1498
0
        InvalidPath, "Invalid path");
1499
0
    return 0;
1500
0
  }
1501
1502
0
  if (php_check_open_basedir(buf)) {
1503
0
    return 0;
1504
0
  }
1505
1506
  /* we look for directory separator from the end of string, thus hopefully reducing our work load */
1507
0
  char *p;
1508
0
  zend_stat_t sb;
1509
0
  size_t dir_len = strlen(dir), offset = 0;
1510
0
  char *e = buf +  strlen(buf);
1511
1512
0
  if ((p = memchr(buf, DEFAULT_SLASH, dir_len))) {
1513
0
    offset = p - buf + 1;
1514
0
  }
1515
1516
0
  if (p && dir_len == 1) {
1517
    /* buf == "DEFAULT_SLASH" */
1518
0
  }
1519
0
  else {
1520
    /* find a top level directory we need to create */
1521
0
    while ( (p = strrchr(buf + offset, DEFAULT_SLASH)) || (offset != 1 && (p = strrchr(buf, DEFAULT_SLASH))) ) {
1522
0
      int n = 0;
1523
1524
0
      *p = '\0';
1525
0
      while (p > buf && *(p-1) == DEFAULT_SLASH) {
1526
0
        ++n;
1527
0
        --p;
1528
0
        *p = '\0';
1529
0
      }
1530
0
      if (VCWD_STAT(buf, &sb) == 0) {
1531
0
        while (1) {
1532
0
          *p = DEFAULT_SLASH;
1533
0
          if (!n) break;
1534
0
          --n;
1535
0
          ++p;
1536
0
        }
1537
0
        break;
1538
0
      }
1539
0
    }
1540
0
  }
1541
1542
0
  if (!p) {
1543
0
    p = buf;
1544
0
  }
1545
0
  char errstr[256];
1546
0
  while (true) {
1547
0
    int ret = VCWD_MKDIR(buf, (mode_t) mode);
1548
0
    if (ret < 0 && errno != EEXIST) {
1549
0
      if (options & REPORT_ERRORS) {
1550
0
        php_stream_wrapper_warn(wrapper, context, options,
1551
0
            MkdirFailed,
1552
0
            "%s", php_socket_strerror_s(errno, errstr, sizeof(errstr)));
1553
0
      }
1554
0
      return 0;
1555
0
    }
1556
1557
0
    bool replaced_slash = false;
1558
0
    while (++p != e) {
1559
0
      if (*p == '\0') {
1560
0
        replaced_slash = true;
1561
0
        *p = DEFAULT_SLASH;
1562
0
        if (*(p+1) != '\0') {
1563
0
          break;
1564
0
        }
1565
0
      }
1566
0
    }
1567
0
    if (p == e || !replaced_slash) {
1568
      /* No more directories to create */
1569
      /* issue a warning to client when the last directory was created failed */
1570
0
      if (ret < 0) {
1571
0
        if (options & REPORT_ERRORS) {
1572
0
          php_stream_wrapper_warn(wrapper, context, options,
1573
0
              MkdirFailed,
1574
0
              "%s", php_socket_strerror_s(errno, errstr, sizeof(errstr)));
1575
0
        }
1576
0
        return 0;
1577
0
      }
1578
0
      return 1;
1579
0
    }
1580
0
  }
1581
0
}
1582
1583
static int php_plain_files_rmdir(php_stream_wrapper *wrapper, const char *url, int options, php_stream_context *context)
1584
0
{
1585
0
  if (strncasecmp(url, "file://", sizeof("file://") - 1) == 0) {
1586
0
    url += sizeof("file://") - 1;
1587
0
  }
1588
1589
0
  if (php_check_open_basedir(url)) {
1590
0
    return 0;
1591
0
  }
1592
1593
0
  char errstr[256];
1594
#ifdef PHP_WIN32
1595
  if (!php_win32_check_trailing_space(url, strlen(url))) {
1596
    php_stream_wrapper_warn(wrapper, context, options,
1597
        NotFound, "%s",
1598
        php_socket_strerror_s(ENOENT, errstr, sizeof(errstr)));
1599
    return 0;
1600
  }
1601
#endif
1602
1603
0
  if (VCWD_RMDIR(url) < 0) {
1604
0
    php_stream_wrapper_warn(wrapper, context, options,
1605
0
        RmdirFailed, "%s",
1606
0
        php_socket_strerror_s(errno, errstr, sizeof(errstr)));
1607
0
    return 0;
1608
0
  }
1609
1610
  /* Clear stat cache (and realpath cache) */
1611
0
  php_clear_stat_cache(1, NULL, 0);
1612
1613
0
  return 1;
1614
0
}
1615
1616
static int php_plain_files_metadata(php_stream_wrapper *wrapper, const char *url, int option, void *value, php_stream_context *context)
1617
0
{
1618
0
  struct utimbuf *newtime;
1619
0
#ifndef PHP_WIN32
1620
0
  uid_t uid;
1621
0
  gid_t gid;
1622
0
#endif
1623
0
  mode_t mode;
1624
0
  int ret = 0;
1625
0
  char errstr[256];
1626
1627
#ifdef PHP_WIN32
1628
  if (!php_win32_check_trailing_space(url, strlen(url))) {
1629
    php_stream_wrapper_warn(wrapper, context, REPORT_ERRORS,
1630
        NotFound, "%s",
1631
        php_socket_strerror_s(ENOENT, errstr, sizeof(errstr)));
1632
    return 0;
1633
  }
1634
#endif
1635
1636
0
  if (strncasecmp(url, "file://", sizeof("file://") - 1) == 0) {
1637
0
    url += sizeof("file://") - 1;
1638
0
  }
1639
1640
0
  if (php_check_open_basedir(url)) {
1641
0
    return 0;
1642
0
  }
1643
1644
0
  switch(option) {
1645
0
    case PHP_STREAM_META_TOUCH:
1646
0
      newtime = (struct utimbuf *)value;
1647
0
      if (VCWD_ACCESS(url, F_OK) != 0) {
1648
0
        FILE *file = VCWD_FOPEN(url, "w");
1649
0
        if (file == NULL) {
1650
0
          php_stream_wrapper_warn(wrapper, context, REPORT_ERRORS,
1651
0
              PermissionDenied,
1652
0
              "Unable to create file %s because %s", url,
1653
0
              php_socket_strerror_s(errno, errstr, sizeof(errstr)));
1654
0
          return 0;
1655
0
        }
1656
0
        fclose(file);
1657
0
      }
1658
1659
0
      ret = VCWD_UTIME(url, newtime);
1660
0
      break;
1661
0
#ifndef PHP_WIN32
1662
0
    case PHP_STREAM_META_OWNER_NAME:
1663
0
    case PHP_STREAM_META_OWNER:
1664
0
      if(option == PHP_STREAM_META_OWNER_NAME) {
1665
0
        if(php_get_uid_by_name((char *)value, &uid) != SUCCESS) {
1666
0
          php_stream_wrapper_warn(wrapper, context, REPORT_ERRORS,
1667
0
              MetaFailed,
1668
0
              "Unable to find uid for %s", (char *)value);
1669
0
          return 0;
1670
0
        }
1671
0
      } else {
1672
0
        uid = (uid_t)*(long *)value;
1673
0
      }
1674
0
      ret = VCWD_CHOWN(url, uid, -1);
1675
0
      break;
1676
0
    case PHP_STREAM_META_GROUP:
1677
0
    case PHP_STREAM_META_GROUP_NAME:
1678
0
      if(option == PHP_STREAM_META_GROUP_NAME) {
1679
0
        if(php_get_gid_by_name((char *)value, &gid) != SUCCESS) {
1680
0
          php_stream_wrapper_warn(wrapper, context, REPORT_ERRORS,
1681
0
              MetaFailed,
1682
0
              "Unable to find gid for %s", (char *)value);
1683
0
          return 0;
1684
0
        }
1685
0
      } else {
1686
0
        gid = (gid_t)*(long *)value;
1687
0
      }
1688
0
      ret = VCWD_CHOWN(url, -1, gid);
1689
0
      break;
1690
0
#endif
1691
0
    case PHP_STREAM_META_ACCESS:
1692
0
      mode = (mode_t)*(zend_long *)value;
1693
0
      ret = VCWD_CHMOD(url, mode);
1694
0
      break;
1695
0
    default:
1696
0
      zend_value_error("Unknown option %d for stream_metadata", option);
1697
0
      return 0;
1698
0
  }
1699
0
  if (ret == -1) {
1700
0
    php_stream_wrapper_warn(wrapper, context, REPORT_ERRORS,
1701
0
        MetaFailed,
1702
0
        "Operation failed: %s", php_socket_strerror_s(errno, errstr, sizeof(errstr)));
1703
0
    return 0;
1704
0
  }
1705
0
  php_clear_stat_cache(0, NULL, 0);
1706
0
  return 1;
1707
0
}
1708
1709
1710
static const php_stream_wrapper_ops php_plain_files_wrapper_ops = {
1711
  php_plain_files_stream_opener,
1712
  NULL,
1713
  NULL,
1714
  php_plain_files_url_stater,
1715
  php_plain_files_dir_opener,
1716
  "plainfile",
1717
  php_plain_files_unlink,
1718
  php_plain_files_rename,
1719
  php_plain_files_mkdir,
1720
  php_plain_files_rmdir,
1721
  php_plain_files_metadata
1722
};
1723
1724
/* TODO: We have to make php_plain_files_wrapper writable to support SWOOLE */
1725
PHPAPI /*const*/ php_stream_wrapper php_plain_files_wrapper = {
1726
  &php_plain_files_wrapper_ops,
1727
  NULL,
1728
  0
1729
};