Coverage Report

Created: 2026-07-25 06:39

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