Coverage Report

Created: 2025-06-13 06:43

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