Coverage Report

Created: 2025-11-16 06:23

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