Coverage Report

Created: 2026-06-02 06:36

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/php-src/Zend/zend_stream.c
Line
Count
Source
1
/*
2
   +----------------------------------------------------------------------+
3
   | Zend Engine                                                          |
4
   +----------------------------------------------------------------------+
5
   | Copyright © Zend Technologies Ltd., a subsidiary company of          |
6
   |     Perforce Software, Inc., and Contributors.                       |
7
   +----------------------------------------------------------------------+
8
   | This source file is subject to the Modified BSD License that is      |
9
   | bundled with this package in the file LICENSE, and is available      |
10
   | through the World Wide Web at <https://www.php.net/license/>.        |
11
   |                                                                      |
12
   | SPDX-License-Identifier: BSD-3-Clause                                |
13
   +----------------------------------------------------------------------+
14
   | Authors: Wez Furlong <wez@thebrainroom.com>                          |
15
   |          Scott MacVicar <scottmac@php.net>                           |
16
   |          Nuno Lopes <nlopess@php.net>                                |
17
   |          Marcus Boerger <helly@php.net>                              |
18
   +----------------------------------------------------------------------+
19
*/
20
21
#include "zend.h"
22
#include "zend_compile.h"
23
#include "zend_stream.h"
24
25
ZEND_DLIMPORT int isatty(int fd);
26
27
static ssize_t zend_stream_stdio_reader(void *handle, char *buf, size_t len) /* {{{ */
28
0
{
29
0
  return fread(buf, 1, len, (FILE*)handle);
30
0
} /* }}} */
31
32
static void zend_stream_stdio_closer(void *handle) /* {{{ */
33
0
{
34
0
  if (handle && (FILE*)handle != stdin) {
35
0
    fclose((FILE*)handle);
36
0
  }
37
0
} /* }}} */
38
39
static size_t zend_stream_stdio_fsizer(void *handle) /* {{{ */
40
0
{
41
0
  zend_stat_t buf = {0};
42
0
  if (handle && zend_fstat(fileno((FILE*)handle), &buf) == 0) {
43
0
#ifdef S_ISREG
44
0
    if (!S_ISREG(buf.st_mode)) {
45
0
      return 0;
46
0
    }
47
0
#endif
48
0
    return buf.st_size;
49
0
  }
50
0
  return -1;
51
0
} /* }}} */
52
53
static size_t zend_stream_fsize(zend_file_handle *file_handle) /* {{{ */
54
0
{
55
0
  ZEND_ASSERT(file_handle->type == ZEND_HANDLE_STREAM);
56
0
  if (file_handle->handle.stream.isatty) {
57
0
    return 0;
58
0
  }
59
0
  return file_handle->handle.stream.fsizer(file_handle->handle.stream.handle);
60
0
} /* }}} */
61
62
0
ZEND_API void zend_stream_init_fp(zend_file_handle *handle, FILE *fp, const char *filename) {
63
0
  memset(handle, 0, sizeof(zend_file_handle));
64
0
  handle->type = ZEND_HANDLE_FP;
65
0
  handle->handle.fp = fp;
66
0
  handle->filename = filename ? zend_string_init(filename, strlen(filename), 0) : NULL;
67
0
}
68
69
0
ZEND_API void zend_stream_init_filename(zend_file_handle *handle, const char *filename) {
70
0
  memset(handle, 0, sizeof(zend_file_handle));
71
0
  handle->type = ZEND_HANDLE_FILENAME;
72
0
  handle->filename = filename ? zend_string_init(filename, strlen(filename), 0) : NULL;
73
0
}
74
75
0
ZEND_API void zend_stream_init_filename_ex(zend_file_handle *handle, zend_string *filename) {
76
0
  memset(handle, 0, sizeof(zend_file_handle));
77
0
  handle->type = ZEND_HANDLE_FILENAME;
78
0
  handle->filename = zend_string_copy(filename);
79
0
}
80
81
ZEND_API zend_result zend_stream_open(zend_file_handle *handle) /* {{{ */
82
0
{
83
0
  zend_string *opened_path;
84
85
0
  ZEND_ASSERT(handle->type == ZEND_HANDLE_FILENAME);
86
0
  if (zend_stream_open_function) {
87
0
    return zend_stream_open_function(handle);
88
0
  }
89
90
0
  handle->handle.fp = zend_fopen(handle->filename, &opened_path);
91
0
  if (!handle->handle.fp) {
92
0
    return FAILURE;
93
0
  }
94
0
  handle->type = ZEND_HANDLE_FP;
95
0
  return SUCCESS;
96
0
} /* }}} */
97
98
static int zend_stream_getc(zend_file_handle *file_handle) /* {{{ */
99
0
{
100
0
  char buf;
101
102
0
  if (file_handle->handle.stream.reader(file_handle->handle.stream.handle, &buf, sizeof(buf))) {
103
0
    return (int)buf;
104
0
  }
105
0
  return EOF;
106
0
} /* }}} */
107
108
static ssize_t zend_stream_read(zend_file_handle *file_handle, char *buf, size_t len) /* {{{ */
109
0
{
110
0
  if (file_handle->handle.stream.isatty) {
111
0
    int c = '*';
112
0
    size_t n;
113
114
0
    for (n = 0; n < len && (c = zend_stream_getc(file_handle)) != EOF && c != '\n'; ++n)  {
115
0
      buf[n] = (char)c;
116
0
    }
117
0
    if (c == '\n') {
118
0
      buf[n++] = (char)c;
119
0
    }
120
121
0
    return n;
122
0
  }
123
0
  return file_handle->handle.stream.reader(file_handle->handle.stream.handle, buf, len);
124
0
} /* }}} */
125
126
ZEND_API zend_result zend_stream_fixup(zend_file_handle *file_handle, char **buf, size_t *len) /* {{{ */
127
0
{
128
0
  size_t file_size;
129
130
0
  if (file_handle->buf) {
131
0
    *buf = file_handle->buf;
132
0
    *len = file_handle->len;
133
0
    return SUCCESS;
134
0
  }
135
136
0
  if (file_handle->type == ZEND_HANDLE_FILENAME) {
137
0
    if (zend_stream_open(file_handle) == FAILURE) {
138
0
      return FAILURE;
139
0
    }
140
0
  }
141
142
0
  if (file_handle->type == ZEND_HANDLE_FP) {
143
0
    if (!file_handle->handle.fp) {
144
0
      return FAILURE;
145
0
    }
146
147
0
    file_handle->type = ZEND_HANDLE_STREAM;
148
0
    file_handle->handle.stream.handle = file_handle->handle.fp;
149
0
    file_handle->handle.stream.isatty = isatty(fileno((FILE *)file_handle->handle.stream.handle));
150
0
    file_handle->handle.stream.reader = (zend_stream_reader_t)zend_stream_stdio_reader;
151
0
    file_handle->handle.stream.closer = (zend_stream_closer_t)zend_stream_stdio_closer;
152
0
    file_handle->handle.stream.fsizer = (zend_stream_fsizer_t)zend_stream_stdio_fsizer;
153
0
  }
154
155
0
  file_size = zend_stream_fsize(file_handle);
156
0
  if (file_size == (size_t)-1) {
157
0
    return FAILURE;
158
0
  }
159
160
0
  if (file_size) {
161
0
    ssize_t read;
162
0
    size_t size = 0;
163
0
    *buf = safe_emalloc(1, file_size, ZEND_MMAP_AHEAD);
164
0
    while ((read = zend_stream_read(file_handle, *buf + size, file_size - size)) > 0) {
165
0
      size += read;
166
0
    }
167
0
    if (read < 0) {
168
0
      efree(*buf);
169
0
      return FAILURE;
170
0
    }
171
0
    file_handle->buf = *buf;
172
0
    file_handle->len = size;
173
0
  } else {
174
0
    size_t size = 0, remain = 4*1024;
175
0
    ssize_t read;
176
0
    *buf = emalloc(remain);
177
178
0
    while ((read = zend_stream_read(file_handle, *buf + size, remain)) > 0) {
179
0
      size   += read;
180
0
      remain -= read;
181
182
0
      if (remain == 0) {
183
0
        *buf   = safe_erealloc(*buf, size, 2, 0);
184
0
        remain = size;
185
0
      }
186
0
    }
187
0
    if (read < 0) {
188
0
      efree(*buf);
189
0
      return FAILURE;
190
0
    }
191
192
0
    file_handle->len = size;
193
0
    if (size && remain < ZEND_MMAP_AHEAD) {
194
0
      *buf = safe_erealloc(*buf, size, 1, ZEND_MMAP_AHEAD);
195
0
    }
196
0
    file_handle->buf = *buf;
197
0
  }
198
199
0
  if (file_handle->len == 0) {
200
0
    *buf = erealloc(*buf, ZEND_MMAP_AHEAD);
201
0
    file_handle->buf = *buf;
202
0
  }
203
204
0
  memset(file_handle->buf + file_handle->len, 0, ZEND_MMAP_AHEAD);
205
206
0
  *buf = file_handle->buf;
207
0
  *len = file_handle->len;
208
209
0
  return SUCCESS;
210
0
} /* }}} */
211
212
static void zend_file_handle_dtor(zend_file_handle *fh) /* {{{ */
213
0
{
214
0
  switch (fh->type) {
215
0
    case ZEND_HANDLE_FP:
216
0
      if (fh->handle.fp) {
217
0
        fclose(fh->handle.fp);
218
0
        fh->handle.fp = NULL;
219
0
      }
220
0
      break;
221
0
    case ZEND_HANDLE_STREAM:
222
0
      if (fh->handle.stream.closer && fh->handle.stream.handle) {
223
0
        fh->handle.stream.closer(fh->handle.stream.handle);
224
0
      }
225
0
      fh->handle.stream.handle = NULL;
226
0
      break;
227
0
    case ZEND_HANDLE_FILENAME:
228
      /* We're only supposed to get here when destructing the used_files hash,
229
       * which doesn't really contain open files, but references to their names/paths
230
       */
231
0
      break;
232
0
  }
233
0
  if (fh->opened_path) {
234
0
    zend_string_release_ex(fh->opened_path, 0);
235
0
    fh->opened_path = NULL;
236
0
  }
237
0
  if (fh->buf) {
238
0
    efree(fh->buf);
239
0
    fh->buf = NULL;
240
0
  }
241
0
  if (fh->filename) {
242
0
    zend_string_release(fh->filename);
243
0
    fh->filename = NULL;
244
0
  }
245
0
}
246
/* }}} */
247
248
/* return int to be compatible with Zend linked list API */
249
static int zend_compare_file_handles(zend_file_handle *fh1, zend_file_handle *fh2) /* {{{ */
250
0
{
251
0
  if (fh1->type != fh2->type) {
252
0
    return 0;
253
0
  }
254
0
  switch (fh1->type) {
255
0
    case ZEND_HANDLE_FILENAME:
256
0
      return zend_string_equals(fh1->filename, fh2->filename);
257
0
    case ZEND_HANDLE_FP:
258
0
      return fh1->handle.fp == fh2->handle.fp;
259
0
    case ZEND_HANDLE_STREAM:
260
0
      return fh1->handle.stream.handle == fh2->handle.stream.handle;
261
0
    default:
262
0
      return 0;
263
0
  }
264
0
  return 0;
265
0
} /* }}} */
266
267
ZEND_API void zend_destroy_file_handle(zend_file_handle *file_handle) /* {{{ */
268
0
{
269
0
  if (file_handle->in_list) {
270
0
    zend_llist_del_element(&CG(open_files), file_handle, (int (*)(void *, void *)) zend_compare_file_handles);
271
    /* zend_file_handle_dtor() operates on the copy, so we have to NULLify the original here */
272
0
    file_handle->opened_path = NULL;
273
0
    file_handle->filename = NULL;
274
0
  } else {
275
0
    zend_file_handle_dtor(file_handle);
276
0
  }
277
0
} /* }}} */
278
279
void zend_stream_init(void) /* {{{ */
280
1.99k
{
281
1.99k
  zend_llist_init(&CG(open_files), sizeof(zend_file_handle), (void (*)(void *)) zend_file_handle_dtor, 0);
282
1.99k
} /* }}} */
283
284
void zend_stream_shutdown(void) /* {{{ */
285
1.99k
{
286
1.99k
  zend_llist_destroy(&CG(open_files));
287
1.99k
} /* }}} */