Coverage Report

Created: 2026-09-14 06:25

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/php-src/main/streams/stream_errors.c
Line
Count
Source
1
/*
2
   +----------------------------------------------------------------------+
3
   | Copyright (c) The PHP Group                                          |
4
   +----------------------------------------------------------------------+
5
   | This source file is subject to the Modified BSD License that is      |
6
   | bundled with this package in the file LICENSE, and is available      |
7
   | through the World Wide Web at <https://www.php.net/license/>.        |
8
   |                                                                      |
9
   | SPDX-License-Identifier: BSD-3-Clause                                |
10
   +----------------------------------------------------------------------+
11
   | Authors: Jakub Zelenka <bukka@php.net>                               |
12
   +----------------------------------------------------------------------+
13
 */
14
15
#define ZEND_ENUM_StreamErrorCode_USE_NAME_TABLE
16
#include "php.h"
17
#include "php_globals.h"
18
#include "php_streams.h"
19
#include "php_stream_errors.h"
20
#include "zend_enum.h"
21
#include "zend_exceptions.h"
22
#include "ext/standard/file.h"
23
#include "stream_errors_arginfo.h"
24
25
/* Class entries */
26
static zend_class_entry *php_ce_stream_error_code;
27
static zend_class_entry *php_ce_stream_error_mode;
28
static zend_class_entry *php_ce_stream_error_store;
29
static zend_class_entry *php_ce_stream_error;
30
static zend_class_entry *php_ce_stream_exception;
31
32
/* Forward declarations */
33
static void php_stream_error_entry_free(php_stream_error_entry *entry);
34
35
/* Helper to create a single StreamError object from an entry */
36
static void php_stream_error_create_object(zval *zv, const php_stream_error_entry *entry)
37
0
{
38
0
  object_init_ex(zv, php_ce_stream_error);
39
40
0
  const char *case_name = NULL;
41
0
  if (entry->code > 0 && entry->code <= ZEND_ENUM_StreamErrorCode_CASE_COUNT) {
42
0
    case_name = zend_enum_StreamErrorCode_case_names[entry->code];
43
0
  }
44
0
  if (!case_name) {
45
0
    case_name = "Generic";
46
0
  }
47
48
  /* TODO: migrate to zend_enum_get_case_by_id() */
49
0
  zend_object *enum_obj = zend_enum_get_case_cstr(php_ce_stream_error_code, case_name);
50
0
  ZEND_ASSERT(enum_obj != NULL);
51
52
0
  zval code_enum;
53
0
  ZVAL_OBJ_COPY(&code_enum, enum_obj);
54
55
0
  zend_update_property(php_ce_stream_error, Z_OBJ_P(zv), ZEND_STRL("code"), &code_enum);
56
0
  zval_ptr_dtor(&code_enum);
57
58
0
  zend_update_property_str(
59
0
      php_ce_stream_error, Z_OBJ_P(zv), ZEND_STRL("message"), entry->message);
60
61
0
  zend_update_property_string(php_ce_stream_error, Z_OBJ_P(zv), ZEND_STRL("wrapperName"),
62
0
      entry->wrapper_name ? entry->wrapper_name : "");
63
64
0
  zend_update_property_long(
65
0
      php_ce_stream_error, Z_OBJ_P(zv), ZEND_STRL("severity"), entry->severity);
66
67
0
  zend_update_property_bool(
68
0
      php_ce_stream_error, Z_OBJ_P(zv), ZEND_STRL("terminating"), entry->terminating);
69
0
}
70
71
/* Create array of StreamError objects from error chain */
72
static void php_stream_error_create_array(zval *zv, const php_stream_error_entry *first)
73
0
{
74
0
  array_init(zv);
75
76
0
  const php_stream_error_entry *entry = first;
77
0
  while (entry) {
78
0
    zval error_obj;
79
0
    php_stream_error_create_object(&error_obj, entry);
80
0
    zend_hash_next_index_insert_new(Z_ARRVAL_P(zv), &error_obj);
81
0
    entry = entry->next;
82
0
  }
83
0
}
84
85
/* Context option helpers */
86
/* Error mode context options (internal C constants) */
87
C23_ENUM(php_stream_error_mode, uint8_t) {
88
  PHP_STREAM_ERROR_MODE_ERROR = 0,
89
  PHP_STREAM_ERROR_MODE_EXCEPTION = 1,
90
  PHP_STREAM_ERROR_MODE_SILENT = 2
91
};
92
93
/* Error store context options (internal C constants) */
94
C23_ENUM(php_stream_error_store, uint8_t) {
95
  PHP_STREAM_ERROR_STORE_AUTO = 0,
96
  PHP_STREAM_ERROR_STORE_NONE = 1,
97
  PHP_STREAM_ERROR_STORE_NON_TERM = 2,
98
  PHP_STREAM_ERROR_STORE_TERMINAL = 3,
99
  PHP_STREAM_ERROR_STORE_ALL = 4
100
};
101
102
static php_stream_error_store php_stream_auto_decide_error_store_mode(php_stream_error_mode error_mode)
103
2.25k
{
104
2.25k
  switch (error_mode) {
105
2.25k
    case PHP_STREAM_ERROR_MODE_ERROR:
106
2.25k
      return PHP_STREAM_ERROR_STORE_NONE;
107
0
    case PHP_STREAM_ERROR_MODE_EXCEPTION:
108
0
      return PHP_STREAM_ERROR_STORE_NON_TERM;
109
0
    case PHP_STREAM_ERROR_MODE_SILENT:
110
0
      return PHP_STREAM_ERROR_STORE_ALL;
111
0
    default:
112
0
      return PHP_STREAM_ERROR_STORE_NONE;
113
2.25k
  }
114
2.25k
}
115
116
static php_stream_error_mode php_stream_get_error_mode(const php_stream_context *context)
117
2.25k
{
118
2.25k
  if (!context) {
119
2.19k
    return PHP_STREAM_ERROR_MODE_ERROR;
120
2.19k
  }
121
122
60
  zval *option = php_stream_context_get_option(context, "stream", "error_mode");
123
60
  if (!option) {
124
60
    return PHP_STREAM_ERROR_MODE_ERROR;
125
60
  }
126
127
0
  if (Z_TYPE_P(option) != IS_OBJECT
128
0
      || !instanceof_function(Z_OBJCE_P(option), php_ce_stream_error_mode)) {
129
0
    zend_type_error("stream context option 'error_mode' must be of type StreamErrorMode");
130
0
    return PHP_STREAM_ERROR_MODE_ERROR;
131
0
  }
132
133
0
  switch ((zend_enum_StreamErrorMode) zend_enum_fetch_case_id(Z_OBJ_P(option))) {
134
0
    case ZEND_ENUM_StreamErrorMode_Error:
135
0
      return PHP_STREAM_ERROR_MODE_ERROR;
136
0
    case ZEND_ENUM_StreamErrorMode_Exception:
137
0
      return PHP_STREAM_ERROR_MODE_EXCEPTION;
138
0
    case ZEND_ENUM_StreamErrorMode_Silent:
139
0
      return PHP_STREAM_ERROR_MODE_SILENT;
140
0
  }
141
142
0
  return PHP_STREAM_ERROR_MODE_ERROR;
143
0
}
144
145
static php_stream_error_store php_stream_get_error_store_mode(
146
  const php_stream_context *context, php_stream_error_mode error_mode)
147
2.25k
{
148
2.25k
  if (!context) {
149
2.19k
    return php_stream_auto_decide_error_store_mode(error_mode);
150
2.19k
  }
151
152
60
  zval *option = php_stream_context_get_option(context, "stream", "error_store");
153
60
  if (!option) {
154
60
    return php_stream_auto_decide_error_store_mode(error_mode);
155
60
  }
156
157
0
  if (Z_TYPE_P(option) != IS_OBJECT
158
0
      || !instanceof_function(Z_OBJCE_P(option), php_ce_stream_error_store)) {
159
0
    zend_type_error("stream context option 'error_store' must be of type StreamErrorStore");
160
0
    return php_stream_auto_decide_error_store_mode(error_mode);
161
0
  }
162
163
0
  switch ((zend_enum_StreamErrorStore) zend_enum_fetch_case_id(Z_OBJ_P(option))) {
164
0
    case ZEND_ENUM_StreamErrorStore_Auto:
165
0
      return php_stream_auto_decide_error_store_mode(error_mode);
166
0
    case ZEND_ENUM_StreamErrorStore_None:
167
0
      return PHP_STREAM_ERROR_STORE_NONE;
168
0
    case ZEND_ENUM_StreamErrorStore_NonTerminating:
169
0
      return PHP_STREAM_ERROR_STORE_NON_TERM;
170
0
    case ZEND_ENUM_StreamErrorStore_Terminating:
171
0
      return PHP_STREAM_ERROR_STORE_TERMINAL;
172
0
    case ZEND_ENUM_StreamErrorStore_All:
173
0
      return PHP_STREAM_ERROR_STORE_ALL;
174
0
  }
175
176
0
  return php_stream_auto_decide_error_store_mode(error_mode);
177
0
}
178
179
/* Helper functions */
180
181
static bool php_stream_has_terminating_error(const php_stream_error_operation *op)
182
2.25k
{
183
2.25k
  const php_stream_error_entry *entry = op->first_error;
184
2.37k
  while (entry) {
185
2.25k
    if (entry->terminating) {
186
2.14k
      return true;
187
2.14k
    }
188
112
    entry = entry->next;
189
112
  }
190
112
  return false;
191
2.25k
}
192
193
static inline php_stream_error_operation *php_stream_get_operation_at_depth(uint32_t depth)
194
0
{
195
0
  php_stream_error_state *state = &FG(stream_error_state);
196
197
0
  if (depth < PHP_STREAM_ERROR_OPERATION_POOL_SIZE) {
198
0
    return &state->operation_pool[depth];
199
0
  } else {
200
0
    uint32_t overflow_index = depth - PHP_STREAM_ERROR_OPERATION_POOL_SIZE;
201
0
    ZEND_ASSERT(overflow_index < state->overflow_capacity);
202
0
    return &state->overflow_operations[overflow_index];
203
0
  }
204
0
}
205
206
static inline php_stream_error_operation *php_stream_get_parent_operation(void)
207
2.45k
{
208
2.45k
  const php_stream_error_state *state = &FG(stream_error_state);
209
210
2.45k
  if (state->operation_depth <= 1) {
211
2.45k
    return NULL;
212
2.45k
  }
213
214
0
  return php_stream_get_operation_at_depth(state->operation_depth - 2);
215
2.45k
}
216
217
/* Clean up functions */
218
219
static void php_stream_error_entry_free(php_stream_error_entry *entry)
220
2.25k
{
221
4.51k
  while (entry) {
222
2.25k
    php_stream_error_entry *next = entry->next;
223
2.25k
    zend_string_release(entry->message);
224
2.25k
    efree(entry->wrapper_name);
225
2.25k
    efree(entry->docref);
226
2.25k
    efree(entry);
227
2.25k
    entry = next;
228
2.25k
  }
229
2.25k
}
230
231
PHPAPI void php_stream_error_state_cleanup(void)
232
295k
{
233
295k
  php_stream_error_state *state = &FG(stream_error_state);
234
235
295k
  while (state->current_operation) {
236
0
    php_stream_error_operation *op = state->current_operation;
237
0
    state->operation_depth--;
238
0
    state->current_operation = php_stream_get_parent_operation();
239
240
0
    php_stream_error_entry_free(op->first_error);
241
242
0
    op->first_error = NULL;
243
0
    op->last_error = NULL;
244
0
    op->error_count = 0;
245
0
  }
246
247
295k
  php_stream_stored_error *stored = state->stored_errors;
248
295k
  while (stored) {
249
0
    php_stream_stored_error *next = stored->next;
250
0
    php_stream_error_entry_free(stored->first_error);
251
0
    efree(stored);
252
0
    stored = next;
253
0
  }
254
255
295k
  state->stored_errors = NULL;
256
295k
  state->stored_count = 0;
257
295k
  state->operation_depth = 0;
258
259
295k
  if (state->overflow_operations) {
260
0
    efree(state->overflow_operations);
261
0
    state->overflow_operations = NULL;
262
0
    state->overflow_capacity = 0;
263
0
  }
264
295k
}
265
266
PHPAPI void php_stream_error_get_last(zval *return_value)
267
0
{
268
0
  const php_stream_error_state *state = &FG(stream_error_state);
269
270
0
  if (!state->stored_errors) {
271
0
    ZVAL_EMPTY_ARRAY(return_value);
272
0
    return;
273
0
  }
274
275
0
  php_stream_error_create_array(return_value, state->stored_errors->first_error);
276
0
}
277
278
PHPAPI void php_stream_error_clear_stored(void)
279
0
{
280
0
  php_stream_error_state *state = &FG(stream_error_state);
281
282
0
  php_stream_stored_error *stored = state->stored_errors;
283
0
  while (stored) {
284
0
    php_stream_stored_error *next = stored->next;
285
0
    php_stream_error_entry_free(stored->first_error);
286
0
    efree(stored);
287
0
    stored = next;
288
0
  }
289
290
0
  state->stored_errors = NULL;
291
0
  state->stored_count = 0;
292
0
}
293
294
/* Error operation stack management */
295
296
PHPAPI php_stream_error_operation *php_stream_error_operation_begin(void)
297
2.45k
{
298
2.45k
  php_stream_error_state *state = &FG(stream_error_state);
299
300
2.45k
  if (state->operation_depth >= PHP_STREAM_ERROR_MAX_DEPTH) {
301
0
    php_error_docref(NULL, E_WARNING,
302
0
        "Stream error operation depth exceeded (%"PRIu32"), possible infinite recursion",
303
0
        state->operation_depth);
304
0
    return NULL;
305
0
  }
306
307
2.45k
  php_stream_error_operation *op;
308
309
2.45k
  if (state->operation_depth < PHP_STREAM_ERROR_OPERATION_POOL_SIZE) {
310
2.45k
    op = &state->operation_pool[state->operation_depth];
311
2.45k
  } else {
312
0
    uint32_t overflow_index = state->operation_depth - PHP_STREAM_ERROR_OPERATION_POOL_SIZE;
313
314
0
    if (overflow_index >= state->overflow_capacity) {
315
0
      uint32_t new_capacity
316
0
          = state->overflow_capacity == 0 ? 8 : state->overflow_capacity * 2;
317
0
      php_stream_error_operation *new_overflow = erealloc(
318
0
          state->overflow_operations, sizeof(php_stream_error_operation) * new_capacity);
319
0
      state->overflow_operations = new_overflow;
320
0
      state->overflow_capacity = new_capacity;
321
0
    }
322
323
0
    op = &state->overflow_operations[overflow_index];
324
0
  }
325
326
2.45k
  op->first_error = NULL;
327
2.45k
  op->last_error = NULL;
328
2.45k
  op->error_count = 0;
329
330
2.45k
  state->current_operation = op;
331
2.45k
  state->operation_depth++;
332
333
2.45k
  return op;
334
2.45k
}
335
336
static void php_stream_error_add(zend_enum_StreamErrorCode code, const char *wrapper_name,
337
    zend_string *message, const char *docref, int severity, bool terminating)
338
2.25k
{
339
2.25k
  php_stream_error_operation *op = FG(stream_error_state).current_operation;
340
2.25k
  ZEND_ASSERT(op != NULL);
341
342
2.25k
  php_stream_error_entry *entry = emalloc(sizeof(php_stream_error_entry));
343
2.25k
  entry->message = message;
344
2.25k
  entry->code = code;
345
2.25k
  entry->wrapper_name = wrapper_name ? estrdup(wrapper_name) : NULL;
346
2.25k
  entry->docref = docref ? estrdup(docref) : NULL;
347
2.25k
  entry->severity = severity;
348
2.25k
  entry->terminating = terminating;
349
2.25k
  entry->next = NULL;
350
351
2.25k
  if (op->last_error) {
352
0
    op->last_error->next = entry;
353
2.25k
  } else {
354
2.25k
    op->first_error = entry;
355
2.25k
  }
356
2.25k
  op->last_error = entry;
357
2.25k
  op->error_count++;
358
2.25k
}
359
360
/* Error reporting */
361
362
static void php_stream_call_error_handler(const zval *handler, zval *errors_array)
363
0
{
364
0
  zend_fcall_info_cache fcc;
365
0
  char *is_callable_error = NULL;
366
367
0
  if (!zend_is_callable_ex(handler, NULL, 0, NULL, &fcc, &is_callable_error)) {
368
0
    if (is_callable_error) {
369
0
      zend_type_error("stream error handler must be a valid callback, %s", is_callable_error);
370
0
      efree(is_callable_error);
371
0
    }
372
0
    return;
373
0
  }
374
375
0
  zend_call_known_fcc(&fcc, NULL, 1, errors_array, NULL);
376
0
}
377
378
static void php_stream_throw_exception_with_errors(const php_stream_error_operation *op)
379
0
{
380
0
  if (!op->first_error) {
381
0
    return;
382
0
  }
383
384
0
  zval ex;
385
0
  object_init_ex(&ex, php_ce_stream_exception);
386
387
  /* Set message from first error */
388
0
  zend_update_property_str(php_ce_stream_exception, Z_OBJ(ex), ZEND_STRL("message"),
389
0
      op->first_error->message);
390
391
  /* Set code from first error */
392
0
  zend_update_property_long(php_ce_stream_exception, Z_OBJ(ex), ZEND_STRL("code"),
393
0
      (zend_long) op->first_error->code);
394
395
  /* Build errors array and set it */
396
0
  zval errors_array;
397
0
  php_stream_error_create_array(&errors_array, op->first_error);
398
0
  zend_update_property(php_ce_stream_exception, Z_OBJ(ex), ZEND_STRL("errors"), &errors_array);
399
0
  zval_ptr_dtor(&errors_array);
400
401
0
  zend_throw_exception_object(&ex);
402
0
}
403
404
static void php_stream_report_errors(const php_stream_context *context, const php_stream_error_operation *op,
405
    php_stream_error_mode error_mode, bool is_terminating)
406
2.25k
{
407
2.25k
  switch (error_mode) {
408
2.25k
    case PHP_STREAM_ERROR_MODE_ERROR: {
409
2.25k
      const php_stream_error_entry *entry = op->first_error;
410
4.51k
      while (entry) {
411
2.25k
        php_error_docref(entry->docref, entry->severity, "%s", ZSTR_VAL(entry->message));
412
2.25k
        entry = entry->next;
413
2.25k
      }
414
2.25k
      break;
415
0
    }
416
417
0
    case PHP_STREAM_ERROR_MODE_EXCEPTION: {
418
0
      if (is_terminating) {
419
0
        php_stream_throw_exception_with_errors(op);
420
0
      }
421
0
      break;
422
0
    }
423
424
0
    case PHP_STREAM_ERROR_MODE_SILENT:
425
0
      break;
426
2.25k
  }
427
428
  /* Call user error handler if set */
429
2.25k
  const zval *handler
430
2.25k
      = context ? php_stream_context_get_option(context, "stream", "error_handler") : NULL;
431
432
2.25k
  if (handler) {
433
0
    zval errors_array;
434
0
    php_stream_error_create_array(&errors_array, op->first_error);
435
436
0
    php_stream_call_error_handler(handler, &errors_array);
437
438
0
    zval_ptr_dtor(&errors_array);
439
0
  }
440
2.25k
}
441
442
/* Error storage */
443
444
PHPAPI void php_stream_error_operation_end(const php_stream_context *context)
445
2.26k
{
446
2.26k
  php_stream_error_state *state = &FG(stream_error_state);
447
2.26k
  php_stream_error_operation *op = state->current_operation;
448
449
2.26k
  if (!op) {
450
0
    return;
451
0
  }
452
453
2.26k
  if (op->error_count > 0) {
454
2.25k
    if (context == NULL) {
455
2.20k
      context = FG(default_context);
456
2.20k
    }
457
458
2.25k
    php_stream_error_mode error_mode = php_stream_get_error_mode(context);
459
2.25k
    php_stream_error_store store_mode = php_stream_get_error_store_mode(context, error_mode);
460
461
2.25k
    bool is_terminating = php_stream_has_terminating_error(op);
462
463
2.25k
    php_stream_report_errors(context, op, error_mode, is_terminating);
464
465
2.25k
    if (store_mode == PHP_STREAM_ERROR_STORE_NONE) {
466
2.25k
      php_stream_error_entry_free(op->first_error);
467
2.25k
      op->first_error = NULL;
468
2.25k
    } else {
469
0
      php_stream_error_entry *entry = op->first_error;
470
0
      php_stream_error_entry *prev = NULL;
471
0
      php_stream_error_entry *to_store_first = NULL;
472
0
      php_stream_error_entry *to_store_last = NULL;
473
0
      uint32_t to_store_count = 0;
474
0
      php_stream_error_entry *remaining_first = NULL;
475
476
0
      while (entry) {
477
0
        php_stream_error_entry *next = entry->next;
478
0
        bool should_store = false;
479
480
0
        if (store_mode == PHP_STREAM_ERROR_STORE_ALL) {
481
0
          should_store = true;
482
0
        } else if (store_mode == PHP_STREAM_ERROR_STORE_NON_TERM && !entry->terminating) {
483
0
          should_store = true;
484
0
        } else if (store_mode == PHP_STREAM_ERROR_STORE_TERMINAL && entry->terminating) {
485
0
          should_store = true;
486
0
        }
487
488
0
        if (should_store) {
489
0
          entry->next = NULL;
490
0
          if (to_store_last) {
491
0
            to_store_last->next = entry;
492
0
          } else {
493
0
            to_store_first = entry;
494
0
          }
495
0
          to_store_last = entry;
496
0
          to_store_count++;
497
0
        } else {
498
0
          entry->next = NULL;
499
0
          if (prev) {
500
0
            prev->next = entry;
501
0
          } else {
502
0
            remaining_first = entry;
503
0
          }
504
0
          prev = entry;
505
0
        }
506
507
0
        entry = next;
508
0
      }
509
510
0
      if (to_store_first) {
511
0
        php_stream_stored_error *stored = emalloc(sizeof(php_stream_stored_error));
512
0
        stored->first_error = to_store_first;
513
0
        stored->error_count = to_store_count;
514
0
        stored->next = state->stored_errors;
515
516
0
        state->stored_errors = stored;
517
0
        state->stored_count++;
518
0
      }
519
520
0
      if (remaining_first) {
521
0
        php_stream_error_entry_free(remaining_first);
522
0
      }
523
524
0
      op->first_error = NULL;
525
0
    }
526
2.25k
  }
527
528
2.26k
  state->operation_depth--;
529
2.26k
  state->current_operation = php_stream_get_parent_operation();
530
531
2.26k
  op->first_error = NULL;
532
2.26k
  op->last_error = NULL;
533
2.26k
  op->error_count = 0;
534
2.26k
}
535
536
PHPAPI void php_stream_error_operation_end_for_stream(const php_stream *stream)
537
193
{
538
193
  php_stream_error_state *state = &FG(stream_error_state);
539
193
  php_stream_error_operation *op = state->current_operation;
540
541
193
  if (!op) {
542
0
    return;
543
0
  }
544
545
193
  if (op->error_count == 0) {
546
193
    state->operation_depth--;
547
193
    state->current_operation = php_stream_get_parent_operation();
548
549
193
    op->first_error = NULL;
550
193
    op->last_error = NULL;
551
193
    return;
552
193
  }
553
554
0
  const php_stream_context *context = PHP_STREAM_CONTEXT(stream);
555
0
  php_stream_error_operation_end(context);
556
0
}
557
558
PHPAPI void php_stream_error_operation_abort(void)
559
0
{
560
0
  php_stream_error_state *state = &FG(stream_error_state);
561
0
  php_stream_error_operation *op = state->current_operation;
562
563
0
  if (!op) {
564
0
    return;
565
0
  }
566
567
0
  state->operation_depth--;
568
0
  state->current_operation = php_stream_get_parent_operation();
569
570
0
  php_stream_error_entry_free(op->first_error);
571
0
  op->first_error = NULL;
572
0
  op->last_error = NULL;
573
0
  op->error_count = 0;
574
0
}
575
576
/* Wrapper error reporting */
577
578
static void php_stream_wrapper_error_internal(const char *wrapper_name, const php_stream_context *context,
579
    const char *docref, int severity, bool terminating,
580
    zend_enum_StreamErrorCode code, zend_string *message)
581
2.25k
{
582
2.25k
  bool implicit_operation = (FG(stream_error_state).current_operation == NULL);
583
2.25k
  if (implicit_operation) {
584
2.22k
    php_stream_error_operation_begin();
585
2.22k
  }
586
587
2.25k
  php_stream_error_add(code, wrapper_name, message, docref, severity, terminating);
588
589
2.25k
  if (implicit_operation) {
590
2.22k
    php_stream_error_operation_end(context);
591
2.22k
  }
592
2.25k
}
593
594
PHPAPI void php_stream_wrapper_error_with_name(const char *wrapper_name,
595
    const php_stream_context *context, const char *docref, int options, int severity,
596
    bool terminating, zend_enum_StreamErrorCode code, const char *fmt, ...)
597
49
{
598
49
  if (!(options & REPORT_ERRORS)) {
599
0
    return;
600
0
  }
601
602
49
  va_list args;
603
49
  va_start(args, fmt);
604
49
  zend_string *message = vstrpprintf(0, fmt, args);
605
49
  va_end(args);
606
607
49
  php_stream_wrapper_error_internal(
608
49
      wrapper_name, context, docref, severity, terminating, code, message);
609
49
}
610
611
PHPAPI void php_stream_wrapper_error(
612
    const php_stream_wrapper *wrapper,
613
    const php_stream_context *context,
614
    const char *docref, int options, int severity, bool terminating,
615
    zend_enum_StreamErrorCode code, const char *fmt, ...)
616
190
{
617
190
  if (!(options & REPORT_ERRORS)) {
618
35
    return;
619
35
  }
620
621
155
  va_list args;
622
155
  va_start(args, fmt);
623
155
  zend_string *message = vstrpprintf(0, fmt, args);
624
155
  va_end(args);
625
626
155
  const char *wrapper_name = PHP_STREAM_ERROR_WRAPPER_NAME(wrapper);
627
628
155
  php_stream_wrapper_error_internal(
629
155
      wrapper_name, context, docref, severity, terminating, code, message);
630
155
}
631
632
/* Stream error reporting */
633
634
PHPAPI void php_stream_error(const php_stream *stream, const char *docref, int severity,
635
    bool terminating, zend_enum_StreamErrorCode code, const char *fmt, ...)
636
34
{
637
34
  va_list args;
638
34
  va_start(args, fmt);
639
640
34
  zend_string *message = vstrpprintf(0, fmt, args);
641
34
  va_end(args);
642
643
34
  const char *wrapper_name = stream->wrapper ? stream->wrapper->wops->label : "stream";
644
645
34
  const php_stream_context *context = PHP_STREAM_CONTEXT(stream);
646
647
34
  php_stream_wrapper_error_internal(wrapper_name, context, docref, severity,
648
34
      terminating, code, message);
649
34
}
650
651
/* Legacy wrapper error logging */
652
653
static void php_stream_error_entry_dtor_legacy(void *error)
654
682
{
655
682
  php_stream_error_entry *entry = *(php_stream_error_entry **) error;
656
682
  zend_string_release(entry->message);
657
682
  efree(entry->wrapper_name);
658
682
  efree(entry->docref);
659
682
  efree(entry);
660
682
}
661
662
static void php_stream_error_list_dtor(zval *item)
663
682
{
664
682
  zend_llist *list = (zend_llist *) Z_PTR_P(item);
665
682
  zend_llist_destroy(list);
666
682
  efree(list);
667
682
}
668
669
static void php_stream_wrapper_log_store_error(zend_string *message, zend_enum_StreamErrorCode code,
670
    const char *wrapper_name, int severity, bool terminating)
671
682
{
672
682
  php_stream_error_entry *entry = ecalloc(1, sizeof(php_stream_error_entry));
673
682
  entry->message = message;
674
682
  entry->code = code;
675
682
  entry->wrapper_name = wrapper_name ? estrdup(wrapper_name) : NULL;
676
682
  entry->severity = severity;
677
682
  entry->terminating = terminating;
678
679
682
  if (!FG(wrapper_logged_errors)) {
680
22
    ALLOC_HASHTABLE(FG(wrapper_logged_errors));
681
22
    zend_hash_init(FG(wrapper_logged_errors), 8, NULL, php_stream_error_list_dtor, 0);
682
22
  }
683
684
682
  zend_llist *list
685
682
      = zend_hash_str_find_ptr(FG(wrapper_logged_errors), wrapper_name, strlen(wrapper_name));
686
687
682
  if (!list) {
688
682
    zend_llist new_list;
689
682
    zend_llist_init(
690
682
        &new_list, sizeof(php_stream_error_entry *), php_stream_error_entry_dtor_legacy, 0);
691
682
    list = zend_hash_str_update_mem(FG(wrapper_logged_errors), wrapper_name,
692
682
        strlen(wrapper_name), &new_list, sizeof(new_list));
693
682
  }
694
695
682
  zend_llist_add_element(list, &entry);
696
682
}
697
698
PHPAPI void php_stream_wrapper_log_error(const php_stream_wrapper *wrapper,
699
    const php_stream_context *context, int options, int severity, bool terminating,
700
    zend_enum_StreamErrorCode code, const char *fmt, ...)
701
682
{
702
682
  va_list args;
703
682
  va_start(args, fmt);
704
682
  zend_string *message = vstrpprintf(0, fmt, args);
705
682
  const char *wrapper_name = PHP_STREAM_ERROR_WRAPPER_NAME(wrapper);
706
707
682
  if (options & REPORT_ERRORS) {
708
0
    php_stream_wrapper_error_internal(
709
0
        wrapper_name, context, NULL, severity, terminating, code, message);
710
682
  } else {
711
682
    php_stream_wrapper_log_store_error(
712
682
        message, code, wrapper_name, severity, terminating);
713
682
  }
714
682
  va_end(args);
715
682
}
716
717
static zend_llist *php_stream_get_wrapper_errors_list(const char *wrapper_name)
718
2.02k
{
719
2.02k
  if (!FG(wrapper_logged_errors)) {
720
1.33k
    return NULL;
721
1.33k
  }
722
682
  return (zend_llist *) zend_hash_str_find_ptr(
723
682
      FG(wrapper_logged_errors), wrapper_name, strlen(wrapper_name));
724
2.02k
}
725
726
PHPAPI void php_stream_display_wrapper_name_errors(const char *wrapper_name,
727
    const php_stream_context *context, zend_enum_StreamErrorCode code,
728
    const char *caption)
729
2.05k
{
730
2.05k
  char *msg;
731
2.05k
  char errstr[256];
732
2.05k
  bool free_msg = false;
733
734
2.05k
  if (EG(exception)) {
735
34
    return;
736
34
  }
737
738
2.02k
  if (strcmp(wrapper_name, PHP_STREAM_ERROR_WRAPPER_DEFAULT_NAME)) {
739
2.02k
    zend_llist *err_list = php_stream_get_wrapper_errors_list(wrapper_name);
740
2.02k
    if (err_list) {
741
682
      size_t l = 0;
742
682
      size_t brlen;
743
682
      const char *br;
744
682
      php_stream_error_entry **err_entry_p;
745
682
      zend_llist_position pos;
746
747
682
      if (PG(html_errors)) {
748
0
        brlen = 7;
749
0
        br = "<br />\n";
750
682
      } else {
751
682
        brlen = 1;
752
682
        br = "\n";
753
682
      }
754
755
682
      size_t i = 0;
756
682
      const size_t count = zend_llist_count(err_list);
757
1.36k
      for (i = 0, err_entry_p = zend_llist_get_first_ex(err_list, &pos); err_entry_p;
758
682
          err_entry_p = zend_llist_get_next_ex(err_list, &pos), i++) {
759
682
        l += ZSTR_LEN((*err_entry_p)->message);
760
682
        if (i < count - 1) {
761
0
          l += brlen;
762
0
        }
763
682
      }
764
682
      msg = emalloc(l + 1);
765
682
      msg[0] = '\0';
766
1.36k
      for (err_entry_p = zend_llist_get_first_ex(err_list, &pos), i = 0; err_entry_p;
767
682
          err_entry_p = zend_llist_get_next_ex(err_list, &pos), i++) {
768
682
        strcat(msg, ZSTR_VAL((*err_entry_p)->message));
769
682
        if (i < count - 1) {
770
0
          strcat(msg, br);
771
0
        }
772
682
      }
773
774
682
      free_msg = true;
775
1.33k
    } else {
776
1.33k
      if (!strcmp(wrapper_name, php_plain_files_wrapper.wops->label)) {
777
1.32k
        msg = php_socket_strerror_s(errno, errstr, sizeof(errstr));
778
1.32k
      } else {
779
14
        msg = "operation failed";
780
14
      }
781
1.33k
    }
782
2.02k
  } else {
783
0
    msg = "no suitable wrapper could be found";
784
0
  }
785
786
2.02k
  zend_string *message = strpprintf(0, "%s: %s", caption, msg);
787
788
2.02k
  php_stream_wrapper_error_internal(wrapper_name, context, NULL, E_WARNING, true,
789
2.02k
      code, message);
790
791
2.02k
  if (free_msg) {
792
682
    efree(msg);
793
682
  }
794
2.02k
}
795
796
PHPAPI void php_stream_display_wrapper_errors(const php_stream_wrapper *wrapper,
797
    const php_stream_context *context, zend_enum_StreamErrorCode code,
798
    const char *caption)
799
24
{
800
24
  if (wrapper) {
801
24
    const char *wrapper_name = PHP_STREAM_ERROR_WRAPPER_NAME(wrapper);
802
24
    php_stream_display_wrapper_name_errors(wrapper_name, context, code, caption);
803
24
  }
804
24
}
805
806
PHPAPI void php_stream_tidy_wrapper_name_error_log(const char *wrapper_name)
807
3.17k
{
808
3.17k
  if (FG(wrapper_logged_errors)) {
809
682
    zend_hash_str_del(FG(wrapper_logged_errors), wrapper_name, strlen(wrapper_name));
810
682
  }
811
3.17k
}
812
813
PHPAPI void php_stream_tidy_wrapper_error_log(const php_stream_wrapper *wrapper)
814
216
{
815
216
  if (wrapper) {
816
216
    const char *wrapper_name = PHP_STREAM_ERROR_WRAPPER_NAME(wrapper);
817
216
    php_stream_tidy_wrapper_name_error_log(wrapper_name);
818
216
  }
819
216
}
820
821
/* StreamException methods */
822
823
PHP_METHOD(StreamException, getErrors)
824
0
{
825
0
  ZEND_PARSE_PARAMETERS_NONE();
826
827
0
  const zval *errors = zend_read_property(
828
0
      php_ce_stream_exception, Z_OBJ_P(ZEND_THIS), ZEND_STRL("errors"), 1, NULL);
829
830
0
  RETURN_COPY(errors);
831
0
}
832
833
/* Module init */
834
835
PHP_MINIT_FUNCTION(stream_errors)
836
16
{
837
16
  php_ce_stream_error_code = register_class_StreamErrorCode();
838
16
  php_ce_stream_error_mode = register_class_StreamErrorMode();
839
16
  php_ce_stream_error_store = register_class_StreamErrorStore();
840
841
16
  php_ce_stream_error = register_class_StreamError();
842
16
  php_ce_stream_exception = register_class_StreamException(zend_ce_exception);
843
844
16
  return SUCCESS;
845
16
}
846
847
PHP_MSHUTDOWN_FUNCTION(stream_errors)
848
0
{
849
0
  return SUCCESS;
850
0
}