From ab55a26c95b03cae88bfad025c276d54bea3b01a Mon Sep 17 00:00:00 2001 From: Levi Morrison Date: Fri, 27 Feb 2026 15:42:04 +0100 Subject: [PATCH 01/11] perf(config): cache sys getenv In rinit, calling system getenv when a SAPI env var isn't found is slow enough it shows up in profiles. glibc's getenv does a linear scan on the environ and does strncmp on the members, so we can definitely do better. We save it in minit so it's available for SYSTEM_INI variables and again on the first request. This is also arguably more correct: system environment variables map to a system INI setting, which shouldn't change at runtime. --- ext/otel_config.c | 2 +- zend_abstract_interface/config/config.c | 75 +++++++++++++++++++-- zend_abstract_interface/config/config.h | 2 + zend_abstract_interface/config/config_ini.c | 3 +- zend_abstract_interface/env/env.c | 4 +- zend_abstract_interface/env/env.h | 6 +- 6 files changed, 80 insertions(+), 12 deletions(-) diff --git a/ext/otel_config.c b/ext/otel_config.c index c9504a0cabb..9f7b2c371ff 100644 --- a/ext/otel_config.c +++ b/ext/otel_config.c @@ -21,7 +21,7 @@ static void report_otel_cfg_telemetry_invalid(const char *otel_cfg, const char * } static bool get_otel_value(zai_str str, zai_env_buffer buf, bool pre_rinit) { - if (zai_getenv_ex(str, buf, pre_rinit) == ZAI_ENV_SUCCESS) { + if (zai_getenv_ex(str, buf, pre_rinit, true) == ZAI_ENV_SUCCESS) { return true; } diff --git a/zend_abstract_interface/config/config.c b/zend_abstract_interface/config/config.c index 961e7765dd9..03bbb0bfdf4 100644 --- a/zend_abstract_interface/config/config.c +++ b/zend_abstract_interface/config/config.c @@ -12,11 +12,67 @@ HashTable zai_config_name_map = {0}; uint16_t zai_config_memoized_entries_count = 0; zai_config_memoized_entry zai_config_memoized_entries[ZAI_CONFIG_ENTRIES_COUNT_MAX]; -static bool zai_config_get_env_value(zai_str name, zai_env_buffer buf) { +/* + * File-local owning zai_option_str cache: + * - None (ptr == NULL): env var was unset. + * - Some with len == 0: env var was explicitly set to empty. + * - Some with len > 0: env var has a non-empty value. + * + * Each Some entry owns pemalloc(..., 1) memory and is freed in + * zai_config_clear_cached_env_values(). + */ +static zai_option_str zai_config_cached_env_values[ZAI_CONFIG_ENTRIES_COUNT_MAX][ZAI_CONFIG_NAMES_COUNT_MAX]; + +bool zai_config_get_cached_env_value(zai_config_id id, uint8_t name_index, zai_env_buffer buf) { + ZEND_ASSERT(buf.ptr != NULL); + ZEND_ASSERT(buf.len > 0); + ZEND_ASSERT(id < zai_config_memoized_entries_count); + ZEND_ASSERT(name_index < zai_config_memoized_entries[id].names_count); + zai_option_str cached = zai_config_cached_env_values[id][name_index]; + if (zai_option_str_is_some(cached) && cached.len < buf.len) { + memcpy(buf.ptr, cached.ptr, cached.len + 1); + return true; + } + return false; +} + +static void zai_config_cache_env_values(void) { + for (zai_config_id i = 0; i < zai_config_memoized_entries_count; i++) { + zai_config_memoized_entry *memoized = &zai_config_memoized_entries[i]; + for (uint8_t n = 0; n < memoized->names_count; n++) { + zai_str name = {.len = memoized->names[n].len, .ptr = memoized->names[n].ptr}; + char *value = getenv(name.ptr); + if (!value) { + continue; + } + size_t len = strlen(value); + char *copy = pemalloc(len + 1, 1); + memcpy(copy, value, len + 1); + zai_config_cached_env_values[i][n] = zai_option_str_from_raw_parts(copy, len); + } + } +} + +static void zai_config_clear_cached_env_values(void) { + for (zai_config_id i = 0; i < zai_config_memoized_entries_count; i++) { + zai_config_memoized_entry *memoized = &zai_config_memoized_entries[i]; + for (uint8_t n = 0; n < memoized->names_count; n++) { + zai_option_str *cached = &zai_config_cached_env_values[i][n]; + if (zai_option_str_is_some(*cached)) { + pefree((char *)cached->ptr, 1); + } + *cached = ZAI_OPTION_STR_NONE; + } + } +} + +static bool zai_config_get_env_value(zai_str name, zai_config_id id, uint8_t name_index, zai_env_buffer buf) { // TODO Handle other return codes - // We want to explicitly allow pre-RINIT access to env vars here. So that callers can have an early view at config. - // But in general allmost all configurations shall only be accessed after first RINIT. (the trivial getter will - return zai_getenv_ex(name, buf, true) == ZAI_ENV_SUCCESS; + // Request-time SAPI env overrides the cached process env value. + if (zai_getenv_ex(name, buf, false, false) == ZAI_ENV_SUCCESS) { + return true; + } + return zai_config_get_cached_env_value(id, name_index, buf); } static inline void zai_config_process_env(zai_config_memoized_entry *memoized, zai_env_buffer buf, zai_option_str *value) { @@ -48,7 +104,7 @@ static void zai_config_find_and_set_value(zai_config_memoized_entry *memoized, z name_index = ZAI_CONFIG_ORIGIN_FLEET_STABLE; memoized->config_id = (zai_str) ZAI_STR_FROM_ZSTR(entry->config_id); break; - } else if (zai_config_get_env_value(name, buf)) { + } else if (zai_config_get_env_value(name, id, (uint8_t)name_index, buf)) { zai_config_process_env(memoized, buf, &value); break; } else if (entry && entry->source == DDOG_LIBRARY_CONFIG_SOURCE_LOCAL_STABLE_CONFIG) { @@ -148,6 +204,7 @@ bool zai_config_minit(zai_config_entry entries[], size_t entries_count, zai_conf if (!entries || !entries_count) return false; if (!zai_json_setup_bindings()) return false; zai_config_entries_init(entries, entries_count); + zai_config_cache_env_values(); zai_config_ini_minit(env_to_ini, module_number); zai_config_stable_file_minit(); #if PHP_VERSION_ID >= 70300 && PHP_VERSION_ID < 70400 @@ -164,6 +221,7 @@ static void zai_config_dtor_memoized_zvals(void) { void zai_config_mshutdown(void) { zai_config_dtor_memoized_zvals(); + zai_config_clear_cached_env_values(); if (zai_config_name_map.nTableSize) { zend_hash_destroy(&zai_config_name_map); } @@ -237,6 +295,13 @@ void zai_config_first_time_rinit(bool in_request) { (void)in_request; #endif + if (in_request) { + // Refresh process env snapshot for SAPIs like FPM that materialize + // pool env values just before the first request. + zai_config_clear_cached_env_values(); + zai_config_cache_env_values(); + } + for (uint16_t i = 0; i < zai_config_memoized_entries_count; i++) { zai_config_memoized_entry *memoized = &zai_config_memoized_entries[i]; zai_config_find_and_set_value(memoized, i); diff --git a/zend_abstract_interface/config/config.h b/zend_abstract_interface/config/config.h index 3041136179e..a9533951794 100644 --- a/zend_abstract_interface/config/config.h +++ b/zend_abstract_interface/config/config.h @@ -6,6 +6,7 @@ #include #include +#include "../env/env.h" #include "../zai_string/string.h" #include "config_decode.h" @@ -114,5 +115,6 @@ bool zai_config_get_id_by_name(zai_str name, zai_config_id *id); void zai_config_register_config_id(zai_config_name *name, zai_config_id id); bool zai_config_is_initialized(void); +bool zai_config_get_cached_env_value(zai_config_id id, uint8_t name_index, zai_env_buffer buf); #endif // ZAI_CONFIG_H diff --git a/zend_abstract_interface/config/config_ini.c b/zend_abstract_interface/config/config_ini.c index 06f03f711c8..6cbd6487126 100644 --- a/zend_abstract_interface/config/config_ini.c +++ b/zend_abstract_interface/config/config_ini.c @@ -446,7 +446,8 @@ void zai_config_ini_rinit(void) { memoized->name_index = ZAI_CONFIG_ORIGIN_FLEET_STABLE; memoized->config_id = (zai_str) ZAI_STR_FROM_ZSTR(entry->config_id); goto next_entry; - } else if (zai_getenv_ex(name, buf, false) == ZAI_ENV_SUCCESS + } else if ((zai_getenv_ex(name, buf, false, false) == ZAI_ENV_SUCCESS + || zai_config_get_cached_env_value(i, name_index, buf)) && zai_config_process_runtime_env(memoized, buf, in_startup, i, name_index)) { goto next_entry; } else if (entry && entry->source == DDOG_LIBRARY_CONFIG_SOURCE_LOCAL_STABLE_CONFIG diff --git a/zend_abstract_interface/env/env.c b/zend_abstract_interface/env/env.c index 28a65a87a9e..30696af4d20 100644 --- a/zend_abstract_interface/env/env.c +++ b/zend_abstract_interface/env/env.c @@ -11,7 +11,7 @@ #define sapi_getenv_compat(name, name_len) sapi_getenv((char *)name, name_len) #endif -zai_env_result zai_getenv_ex(zai_str name, zai_env_buffer buf, bool pre_rinit) { +zai_env_result zai_getenv_ex(zai_str name, zai_env_buffer buf, bool pre_rinit, bool use_process_env) { if (!buf.ptr || !buf.len) return ZAI_ENV_ERROR; buf.ptr[0] = '\0'; @@ -34,7 +34,7 @@ zai_env_result zai_getenv_ex(zai_str name, zai_env_buffer buf, bool pre_rinit) { char *value = sapi_getenv_compat(name.ptr, name.len); if (value) { use_sapi_env = true; - } else { + } else if (use_process_env) { value = getenv(name.ptr); } diff --git a/zend_abstract_interface/env/env.h b/zend_abstract_interface/env/env.h index c8686e6748c..ecac19251fe 100644 --- a/zend_abstract_interface/env/env.h +++ b/zend_abstract_interface/env/env.h @@ -53,7 +53,7 @@ typedef struct zai_env_buffer_s { * which is as early as module RINIT. If the active SAPI has a custom * environment variable handler, the SAPI handler is used to access the * environment variable. If there is no custom handler, the environment variable - * is accessed from the host using getenv(). + * is accessed from the host using getenv(), unless use_process_env is false. * * For error conditions, a return value other than ZAI_ENV_SUCCESS is returned * and 'buf.ptr' is made an empty string. If the buffer size 'buf.len' is not @@ -61,9 +61,9 @@ typedef struct zai_env_buffer_s { * and 'buf.ptr' will be an empty string; e.g. this API does not attempt to * truncate the value to accommodate the buffer size. */ -zai_env_result zai_getenv_ex(zai_str name, zai_env_buffer buf, bool pre_rinit); +zai_env_result zai_getenv_ex(zai_str name, zai_env_buffer buf, bool pre_rinit, bool use_process_env); static inline zai_env_result zai_getenv(zai_str name, zai_env_buffer buf) { - return zai_getenv_ex(name, buf, false); + return zai_getenv_ex(name, buf, false, true); } #define zai_getenv_literal(name, buf) zai_getenv(ZAI_STRL(name), buf) From 95ce7cc6b63f62ed502f8151780b6693956b13d1 Mon Sep 17 00:00:00 2001 From: Levi Morrison Date: Fri, 27 Feb 2026 23:42:51 +0100 Subject: [PATCH 02/11] perf(config): avoid interim string copies --- ext/otel_config.c | 74 ++++++++++----------- ext/otel_config.h | 18 ++--- zend_abstract_interface/config/config.c | 30 ++++++--- zend_abstract_interface/config/config.h | 2 +- zend_abstract_interface/config/config_ini.c | 12 +++- zend_abstract_interface/config/config_ini.h | 2 +- zend_abstract_interface/env/env.c | 51 ++++++++------ zend_abstract_interface/env/env.h | 23 ++++--- zend_abstract_interface/env/tests/host.cc | 4 ++ zend_abstract_interface/env/tests/sapi.cc | 1 + 10 files changed, 124 insertions(+), 93 deletions(-) diff --git a/ext/otel_config.c b/ext/otel_config.c index 9f7b2c371ff..a15ed9ca95e 100644 --- a/ext/otel_config.c +++ b/ext/otel_config.c @@ -20,7 +20,7 @@ static void report_otel_cfg_telemetry_invalid(const char *otel_cfg, const char * } } -static bool get_otel_value(zai_str str, zai_env_buffer buf, bool pre_rinit) { +static bool get_otel_value(zai_str str, zai_env_buffer *buf, bool pre_rinit) { if (zai_getenv_ex(str, buf, pre_rinit, true) == ZAI_ENV_SUCCESS) { return true; } @@ -29,13 +29,13 @@ static bool get_otel_value(zai_str str, zai_env_buffer buf, bool pre_rinit) { if (cfg) { if (Z_TYPE_P(cfg) == IS_ARRAY) { zval *val; - char *off = buf.ptr; + char *off = buf->ptr; ZEND_HASH_FOREACH_VAL(Z_ARR_P(cfg), val) { if (Z_TYPE_P(val) == IS_STRING) { - if (off - buf.ptr + Z_STRLEN_P(val) + 2 >= ZAI_ENV_MAX_BUFSIZ) { + if (off - buf->ptr + Z_STRLEN_P(val) + 2 >= ZAI_ENV_MAX_BUFSIZ) { return false; } - if (off != buf.ptr) { + if (off != buf->ptr) { *off++ = ','; } memcpy(off, Z_STRVAL_P(val), Z_STRLEN_P(val)); @@ -46,7 +46,7 @@ static bool get_otel_value(zai_str str, zai_env_buffer buf, bool pre_rinit) { } else if (Z_STRLEN_P(cfg) == 0 || Z_STRLEN_P(cfg) + 1 >= ZAI_ENV_MAX_BUFSIZ) { return false; } else { - memcpy(buf.ptr, Z_STRVAL_P(cfg), Z_STRLEN_P(cfg) + 1); + memcpy(buf->ptr, Z_STRVAL_P(cfg), Z_STRLEN_P(cfg) + 1); } return true; } @@ -54,12 +54,12 @@ static bool get_otel_value(zai_str str, zai_env_buffer buf, bool pre_rinit) { return false; } -static bool ddtrace_conf_otel_resource_attributes_special(const char *tag, int len, zai_env_buffer buf, bool pre_rinit) { +static bool ddtrace_conf_otel_resource_attributes_special(const char *tag, int len, zai_env_buffer *buf, bool pre_rinit) { if (!get_otel_value((zai_str)ZAI_STRL("OTEL_RESOURCE_ATTRIBUTES"), buf, pre_rinit)) { return false; } - for (char *cur = buf.ptr, *key_start = cur; *cur; ++cur) { + for (char *cur = buf->ptr, *key_start = cur; *cur; ++cur) { if (*cur == '=') { char *key_end = cur++; while (*cur && *cur != ',') { @@ -67,8 +67,8 @@ static bool ddtrace_conf_otel_resource_attributes_special(const char *tag, int l } if (key_end - key_start == len && memcmp(key_start, tag, len) == 0 && key_end[1]) { size_t vallen = cur - (key_end + 1); - memcpy(buf.ptr, key_end + 1, vallen); - buf.ptr[vallen] = 0; + memcpy(buf->ptr, key_end + 1, vallen); + buf->ptr[vallen] = 0; return true; } key_start = cur-- + 1; @@ -78,92 +78,92 @@ static bool ddtrace_conf_otel_resource_attributes_special(const char *tag, int l return false; } -bool ddtrace_conf_otel_resource_attributes_env(zai_env_buffer buf, bool pre_rinit) { +bool ddtrace_conf_otel_resource_attributes_env(zai_env_buffer *buf, bool pre_rinit) { return ddtrace_conf_otel_resource_attributes_special(ZEND_STRL("deployment.environment"), buf, pre_rinit); } -bool ddtrace_conf_otel_resource_attributes_version(zai_env_buffer buf, bool pre_rinit) { +bool ddtrace_conf_otel_resource_attributes_version(zai_env_buffer *buf, bool pre_rinit) { return ddtrace_conf_otel_resource_attributes_special(ZEND_STRL("service.version"), buf, pre_rinit); } -bool ddtrace_conf_otel_service_name(zai_env_buffer buf, bool pre_rinit) { +bool ddtrace_conf_otel_service_name(zai_env_buffer *buf, bool pre_rinit) { return get_otel_value((zai_str)ZAI_STRL("OTEL_SERVICE_NAME"), buf, pre_rinit) || ddtrace_conf_otel_resource_attributes_special(ZEND_STRL("service.name"), buf, pre_rinit); } -bool ddtrace_conf_otel_log_level(zai_env_buffer buf, bool pre_rinit) { +bool ddtrace_conf_otel_log_level(zai_env_buffer *buf, bool pre_rinit) { return get_otel_value((zai_str)ZAI_STRL("OTEL_LOG_LEVEL"), buf, pre_rinit); } -bool ddtrace_conf_otel_propagators(zai_env_buffer buf, bool pre_rinit) { +bool ddtrace_conf_otel_propagators(zai_env_buffer *buf, bool pre_rinit) { if (!get_otel_value((zai_str)ZAI_STRL("OTEL_PROPAGATORS"), buf, pre_rinit)) { return false; } - char *off = (char *)zend_memnstr(buf.ptr, ZEND_STRL("b3"), buf.ptr + strlen(buf.ptr)); - if (off && (!off[strlen("b3")] || off[strlen("b3")] == ',') && strlen(buf.ptr) < buf.len - 100) { - memmove(off + strlen("b3 single header"), off + strlen("b3"), buf.ptr + strlen(buf.ptr) - (off + strlen("b3")) + 1); + char *off = (char *)zend_memnstr(buf->ptr, ZEND_STRL("b3"), buf->ptr + strlen(buf->ptr)); + if (off && (!off[strlen("b3")] || off[strlen("b3")] == ',') && strlen(buf->ptr) < buf->len - 100) { + memmove(off + strlen("b3 single header"), off + strlen("b3"), buf->ptr + strlen(buf->ptr) - (off + strlen("b3")) + 1); memcpy(off, "b3 single header", strlen("b3 single header")); } return true; } -bool ddtrace_conf_otel_sample_rate(zai_env_buffer buf, bool pre_rinit) { +bool ddtrace_conf_otel_sample_rate(zai_env_buffer *buf, bool pre_rinit) { if (!get_otel_value((zai_str)ZAI_STRL("OTEL_TRACES_SAMPLER"), buf, pre_rinit)) { return false; } - if (strcmp(buf.ptr, "always_on") == 0 || strcmp(buf.ptr, "parentbased_always_on") == 0) { - memcpy(buf.ptr, ZEND_STRS("1")); + if (strcmp(buf->ptr, "always_on") == 0 || strcmp(buf->ptr, "parentbased_always_on") == 0) { + memcpy(buf->ptr, ZEND_STRS("1")); return true; } - if (strcmp(buf.ptr, "always_off") == 0 || strcmp(buf.ptr, "parentbased_always_off") == 0) { - memcpy(buf.ptr, ZEND_STRS("0")); + if (strcmp(buf->ptr, "always_off") == 0 || strcmp(buf->ptr, "parentbased_always_off") == 0) { + memcpy(buf->ptr, ZEND_STRS("0")); return true; } - if (strcmp(buf.ptr, "traceidratio") == 0 || strcmp(buf.ptr, "parentbased_traceidratio") == 0) { + if (strcmp(buf->ptr, "traceidratio") == 0 || strcmp(buf->ptr, "parentbased_traceidratio") == 0) { if (get_otel_value((zai_str)ZAI_STRL("OTEL_TRACES_SAMPLER_ARG"), buf, pre_rinit)) { return true; } - LOG_ONCE(WARN, "OTEL_TRACES_SAMPLER is %s, but is missing OTEL_TRACES_SAMPLER_ARG", buf.ptr); + LOG_ONCE(WARN, "OTEL_TRACES_SAMPLER is %s, but is missing OTEL_TRACES_SAMPLER_ARG", buf->ptr); } else { - LOG_ONCE(WARN, "OTEL_TRACES_SAMPLER has invalid value: %s", buf.ptr); + LOG_ONCE(WARN, "OTEL_TRACES_SAMPLER has invalid value: %s", buf->ptr); } report_otel_cfg_telemetry_invalid("otel_traces_sampler", "dd_trace_sample_rate", pre_rinit); return false; } -bool ddtrace_conf_otel_traces_exporter(zai_env_buffer buf, bool pre_rinit) { +bool ddtrace_conf_otel_traces_exporter(zai_env_buffer *buf, bool pre_rinit) { if (get_otel_value((zai_str)ZAI_STRL("OTEL_TRACES_EXPORTER"), buf, pre_rinit)) { - if (strcmp(buf.ptr, "none") == 0) { - memcpy(buf.ptr, ZEND_STRS("0")); + if (strcmp(buf->ptr, "none") == 0) { + memcpy(buf->ptr, ZEND_STRS("0")); return true; } - LOG_ONCE(WARN, "OTEL_TRACES_EXPORTER has invalid value: %s", buf.ptr); + LOG_ONCE(WARN, "OTEL_TRACES_EXPORTER has invalid value: %s", buf->ptr); report_otel_cfg_telemetry_invalid("otel_traces_exporter", "dd_trace_enabled", pre_rinit); } return false; } -bool ddtrace_conf_otel_metrics_exporter(zai_env_buffer buf, bool pre_rinit) { +bool ddtrace_conf_otel_metrics_exporter(zai_env_buffer *buf, bool pre_rinit) { if (get_otel_value((zai_str)ZAI_STRL("OTEL_METRICS_EXPORTER"), buf, pre_rinit)) { - if (strcmp(buf.ptr, "none") == 0) { - memcpy(buf.ptr, ZEND_STRS("0")); + if (strcmp(buf->ptr, "none") == 0) { + memcpy(buf->ptr, ZEND_STRS("0")); return true; } - LOG_ONCE(WARN, "OTEL_METRICS_EXPORTER has invalid value: %s", buf.ptr); + LOG_ONCE(WARN, "OTEL_METRICS_EXPORTER has invalid value: %s", buf->ptr); report_otel_cfg_telemetry_invalid("otel_metrics_exporter", "dd_integration_metrics_enabled", pre_rinit); } return false; } -bool ddtrace_conf_otel_resource_attributes_tags(zai_env_buffer buf, bool pre_rinit) { +bool ddtrace_conf_otel_resource_attributes_tags(zai_env_buffer *buf, bool pre_rinit) { if (!get_otel_value((zai_str)ZAI_STRL("OTEL_RESOURCE_ATTRIBUTES"), buf, pre_rinit)) { return false; } - char *out = buf.ptr; + char *out = buf->ptr; int tags = 0; - for (char *cur = buf.ptr, *key_start = cur; *cur; ++cur) { + for (char *cur = buf->ptr, *key_start = cur; *cur; ++cur) { if (*cur == '=') { char *key = key_start, *key_end = cur++; while (*cur && *cur != ',') { @@ -190,7 +190,7 @@ bool ddtrace_conf_otel_resource_attributes_tags(zai_env_buffer buf, bool pre_rin --cur; } } - if (out != buf.ptr) { + if (out != buf->ptr) { --out; } *out = 0; diff --git a/ext/otel_config.h b/ext/otel_config.h index 41049b4e31e..a7840b6d78e 100644 --- a/ext/otel_config.h +++ b/ext/otel_config.h @@ -3,14 +3,14 @@ #include -bool ddtrace_conf_otel_resource_attributes_env(zai_env_buffer buf, bool pre_rinit); -bool ddtrace_conf_otel_resource_attributes_version(zai_env_buffer buf, bool pre_rinit); -bool ddtrace_conf_otel_service_name(zai_env_buffer buf, bool pre_rinit); -bool ddtrace_conf_otel_log_level(zai_env_buffer buf, bool pre_rinit); -bool ddtrace_conf_otel_propagators(zai_env_buffer buf, bool pre_rinit); -bool ddtrace_conf_otel_sample_rate(zai_env_buffer buf, bool pre_rinit); -bool ddtrace_conf_otel_traces_exporter(zai_env_buffer buf, bool pre_rinit); -bool ddtrace_conf_otel_metrics_exporter(zai_env_buffer buf, bool pre_rinit); -bool ddtrace_conf_otel_resource_attributes_tags(zai_env_buffer buf, bool pre_rinit); +bool ddtrace_conf_otel_resource_attributes_env(zai_env_buffer *buf, bool pre_rinit); +bool ddtrace_conf_otel_resource_attributes_version(zai_env_buffer *buf, bool pre_rinit); +bool ddtrace_conf_otel_service_name(zai_env_buffer *buf, bool pre_rinit); +bool ddtrace_conf_otel_log_level(zai_env_buffer *buf, bool pre_rinit); +bool ddtrace_conf_otel_propagators(zai_env_buffer *buf, bool pre_rinit); +bool ddtrace_conf_otel_sample_rate(zai_env_buffer *buf, bool pre_rinit); +bool ddtrace_conf_otel_traces_exporter(zai_env_buffer *buf, bool pre_rinit); +bool ddtrace_conf_otel_metrics_exporter(zai_env_buffer *buf, bool pre_rinit); +bool ddtrace_conf_otel_resource_attributes_tags(zai_env_buffer *buf, bool pre_rinit); #endif // DD_OTEL_CONFIG_H diff --git a/zend_abstract_interface/config/config.c b/zend_abstract_interface/config/config.c index 03bbb0bfdf4..f91277fa5a5 100644 --- a/zend_abstract_interface/config/config.c +++ b/zend_abstract_interface/config/config.c @@ -23,14 +23,16 @@ zai_config_memoized_entry zai_config_memoized_entries[ZAI_CONFIG_ENTRIES_COUNT_M */ static zai_option_str zai_config_cached_env_values[ZAI_CONFIG_ENTRIES_COUNT_MAX][ZAI_CONFIG_NAMES_COUNT_MAX]; -bool zai_config_get_cached_env_value(zai_config_id id, uint8_t name_index, zai_env_buffer buf) { - ZEND_ASSERT(buf.ptr != NULL); - ZEND_ASSERT(buf.len > 0); +bool zai_config_get_cached_env_value(zai_config_id id, uint8_t name_index, zai_env_buffer *buf) { + ZEND_ASSERT(buf != NULL); + ZEND_ASSERT(buf->ptr != NULL); + ZEND_ASSERT(buf->len > 0); ZEND_ASSERT(id < zai_config_memoized_entries_count); ZEND_ASSERT(name_index < zai_config_memoized_entries[id].names_count); zai_option_str cached = zai_config_cached_env_values[id][name_index]; - if (zai_option_str_is_some(cached) && cached.len < buf.len) { - memcpy(buf.ptr, cached.ptr, cached.len + 1); + if (zai_option_str_is_some(cached)) { + buf->ptr = (char *)cached.ptr; + buf->len = cached.len; return true; } return false; @@ -66,10 +68,9 @@ static void zai_config_clear_cached_env_values(void) { } } -static bool zai_config_get_env_value(zai_str name, zai_config_id id, uint8_t name_index, zai_env_buffer buf) { +static bool zai_config_get_env_value(zai_str name, zai_config_id id, uint8_t name_index, zai_env_buffer *buf) { // TODO Handle other return codes - // Request-time SAPI env overrides the cached process env value. - if (zai_getenv_ex(name, buf, false, false) == ZAI_ENV_SUCCESS) { + if (zai_getenv(name, buf) == ZAI_ENV_SUCCESS) { return true; } return zai_config_get_cached_env_value(id, name_index, buf); @@ -96,6 +97,9 @@ static void zai_config_find_and_set_value(zai_config_memoized_entry *memoized, z int16_t name_index = 0; for (; name_index < memoized->names_count; name_index++) { + buf.ptr = buf_storage; + buf.len = sizeof(buf_storage); + buf.ptr[0] = '\0'; zai_str name = {.len = memoized->names[name_index].len, .ptr = memoized->names[name_index].ptr}; zai_config_stable_file_entry *entry = zai_config_stable_file_get_value(name); if (entry && entry->source == DDOG_LIBRARY_CONFIG_SOURCE_FLEET_STABLE_CONFIG) { @@ -104,7 +108,7 @@ static void zai_config_find_and_set_value(zai_config_memoized_entry *memoized, z name_index = ZAI_CONFIG_ORIGIN_FLEET_STABLE; memoized->config_id = (zai_str) ZAI_STR_FROM_ZSTR(entry->config_id); break; - } else if (zai_config_get_env_value(name, id, (uint8_t)name_index, buf)) { + } else if (zai_config_get_env_value(name, id, (uint8_t)name_index, &buf)) { zai_config_process_env(memoized, buf, &value); break; } else if (entry && entry->source == DDOG_LIBRARY_CONFIG_SOURCE_LOCAL_STABLE_CONFIG) { @@ -115,7 +119,13 @@ static void zai_config_find_and_set_value(zai_config_memoized_entry *memoized, z break; } } - if (!value.len && memoized->env_config_fallback && memoized->env_config_fallback(buf, true)) { + + buf.ptr = buf_storage; + buf.len = sizeof(buf_storage); + if (zai_option_str_is_none(value) || value.ptr != buf_storage) { + buf.ptr[0] = '\0'; + } + if (!value.len && memoized->env_config_fallback && memoized->env_config_fallback(&buf, true)) { zai_config_process_env(memoized, buf, &value); name_index = ZAI_CONFIG_ORIGIN_MODIFIED; } diff --git a/zend_abstract_interface/config/config.h b/zend_abstract_interface/config/config.h index a9533951794..4bfa6df15cd 100644 --- a/zend_abstract_interface/config/config.h +++ b/zend_abstract_interface/config/config.h @@ -115,6 +115,6 @@ bool zai_config_get_id_by_name(zai_str name, zai_config_id *id); void zai_config_register_config_id(zai_config_name *name, zai_config_id id); bool zai_config_is_initialized(void); -bool zai_config_get_cached_env_value(zai_config_id id, uint8_t name_index, zai_env_buffer buf); +bool zai_config_get_cached_env_value(zai_config_id id, uint8_t name_index, zai_env_buffer *buf); #endif // ZAI_CONFIG_H diff --git a/zend_abstract_interface/config/config_ini.c b/zend_abstract_interface/config/config_ini.c index 6cbd6487126..01117240fcc 100644 --- a/zend_abstract_interface/config/config_ini.c +++ b/zend_abstract_interface/config/config_ini.c @@ -438,6 +438,9 @@ void zai_config_ini_rinit(void) { // makes only sense to update INIs once, avoid rereading env unnecessarily if (!env_to_ini_name || !memoized->original_on_modify) { for (uint8_t name_index = 0; name_index < memoized->names_count; name_index++) { + buf.ptr = buf_storage; + buf.len = sizeof(buf_storage); + buf.ptr[0] = '\0'; zai_str name = ZAI_STR_NEW(memoized->names[name_index].ptr, memoized->names[name_index].len); zai_config_stable_file_entry *entry = zai_config_stable_file_get_value(name); if (entry && entry->source == DDOG_LIBRARY_CONFIG_SOURCE_FLEET_STABLE_CONFIG @@ -446,8 +449,8 @@ void zai_config_ini_rinit(void) { memoized->name_index = ZAI_CONFIG_ORIGIN_FLEET_STABLE; memoized->config_id = (zai_str) ZAI_STR_FROM_ZSTR(entry->config_id); goto next_entry; - } else if ((zai_getenv_ex(name, buf, false, false) == ZAI_ENV_SUCCESS - || zai_config_get_cached_env_value(i, name_index, buf)) + } else if ((zai_getenv(name, &buf) == ZAI_ENV_SUCCESS + || zai_config_get_cached_env_value(i, name_index, &buf)) && zai_config_process_runtime_env(memoized, buf, in_startup, i, name_index)) { goto next_entry; } else if (entry && entry->source == DDOG_LIBRARY_CONFIG_SOURCE_LOCAL_STABLE_CONFIG @@ -459,7 +462,10 @@ void zai_config_ini_rinit(void) { } } - if (memoized->env_config_fallback && memoized->env_config_fallback(buf, false) && zai_config_process_runtime_env(memoized, buf, in_startup, i, 0)) { + buf.ptr = buf_storage; + buf.len = sizeof(buf_storage); + buf.ptr[0] = '\0'; + if (memoized->env_config_fallback && memoized->env_config_fallback(&buf, false) && zai_config_process_runtime_env(memoized, buf, in_startup, i, 0)) { goto next_entry; } } diff --git a/zend_abstract_interface/config/config_ini.h b/zend_abstract_interface/config/config_ini.h index fb253295a98..ce880792e1b 100644 --- a/zend_abstract_interface/config/config_ini.h +++ b/zend_abstract_interface/config/config_ini.h @@ -35,7 +35,7 @@ int16_t zai_config_initialize_ini_value(zend_ini_entry **entries, zai_config_id entry_id); typedef bool (*zai_config_apply_ini_change)(zval *old_value, zval *new_value, zend_string *new_str); -typedef bool (*zai_env_config_fallback)(zai_env_buffer buf, bool pre_rinit); +typedef bool (*zai_env_config_fallback)(zai_env_buffer *buf, bool pre_rinit); bool zai_config_system_ini_change(zval *old_value, zval *new_value, zend_string *new_str); diff --git a/zend_abstract_interface/env/env.c b/zend_abstract_interface/env/env.c index 30696af4d20..c6071dfca2b 100644 --- a/zend_abstract_interface/env/env.c +++ b/zend_abstract_interface/env/env.c @@ -11,14 +11,15 @@ #define sapi_getenv_compat(name, name_len) sapi_getenv((char *)name, name_len) #endif -zai_env_result zai_getenv_ex(zai_str name, zai_env_buffer buf, bool pre_rinit, bool use_process_env) { - if (!buf.ptr || !buf.len) return ZAI_ENV_ERROR; +zai_env_result zai_getenv_ex(zai_str name, zai_env_buffer *buf, bool pre_rinit, bool use_process_env) { + if (!buf || !buf->ptr || !buf->len) return ZAI_ENV_ERROR; - buf.ptr[0] = '\0'; + char *scratch = buf->ptr; + size_t scratch_len = buf->len; if (zai_str_is_empty(name)) return ZAI_ENV_ERROR; - if (buf.len > ZAI_ENV_MAX_BUFSIZ) return ZAI_ENV_BUFFER_TOO_BIG; + if (scratch_len > ZAI_ENV_MAX_BUFSIZ) return ZAI_ENV_BUFFER_TOO_BIG; /* Some SAPIs do not initialize the SAPI-controlled environment variables * until SAPI RINIT. It is for this reason we cannot reliably access @@ -28,28 +29,34 @@ zai_env_result zai_getenv_ex(zai_str name, zai_env_buffer buf, bool pre_rinit, b /* sapi_getenv may or may not include process environment variables. * It will return NULL when it is not found in the possibly synthetic SAPI environment. - * Hence we need to do a getenv() in any case. */ - bool use_sapi_env = false; - char *value = sapi_getenv_compat(name.ptr, name.len); - if (value) { - use_sapi_env = true; - } else if (use_process_env) { - value = getenv(name.ptr); + char *sapi_value = sapi_getenv_compat(name.ptr, name.len); + if (sapi_value) { + size_t sapi_value_len = strlen(sapi_value); + zai_env_result res; + if (sapi_value_len < scratch_len) { + memcpy(scratch, sapi_value, sapi_value_len + 1); + buf->ptr = scratch; + buf->len = sapi_value_len; + res = ZAI_ENV_SUCCESS; + } else { + res = ZAI_ENV_BUFFER_TOO_SMALL; + } + efree(sapi_value); + return res; } - if (!value) return ZAI_ENV_NOT_SET; + if (!use_process_env) return ZAI_ENV_NOT_SET; - zai_env_result res; + char *process_value = getenv(name.ptr); + if (!process_value) return ZAI_ENV_NOT_SET; - if (strlen(value) < buf.len) { - strcpy(buf.ptr, value); - res = ZAI_ENV_SUCCESS; - } else { - res = ZAI_ENV_BUFFER_TOO_SMALL; + size_t process_value_len = strlen(process_value); + if (process_value_len < scratch_len) { + memcpy(scratch, process_value, process_value_len + 1); + buf->ptr = scratch; + buf->len = process_value_len; + return ZAI_ENV_SUCCESS; } - - if (use_sapi_env) efree(value); - - return res; + return ZAI_ENV_BUFFER_TOO_SMALL; } diff --git a/zend_abstract_interface/env/env.h b/zend_abstract_interface/env/env.h index ecac19251fe..68f321dda03 100644 --- a/zend_abstract_interface/env/env.h +++ b/zend_abstract_interface/env/env.h @@ -48,24 +48,27 @@ typedef struct zai_env_buffer_s { char name##_storage[size]; \ zai_env_buffer name = {size, name##_storage} -/* Fills 'buf.ptr' with the value of a target environment variable identified by - * 'name'. Must be called after the SAPI envrionment variables are available +/* Resolves a target environment variable identified by 'name'. Must be called + * after the SAPI envrionment variables are available * which is as early as module RINIT. If the active SAPI has a custom * environment variable handler, the SAPI handler is used to access the * environment variable. If there is no custom handler, the environment variable * is accessed from the host using getenv(), unless use_process_env is false. * - * For error conditions, a return value other than ZAI_ENV_SUCCESS is returned - * and 'buf.ptr' is made an empty string. If the buffer size 'buf.len' is not - * big enough to contain the value, ZAI_ENV_BUFFER_TOO_SMALL will be returned - * and 'buf.ptr' will be an empty string; e.g. this API does not attempt to - * truncate the value to accommodate the buffer size. + * For SAPI values, this writes into the caller scratch buffer (`buf->ptr`). + * For process getenv() values, this may repoint `buf->ptr` to borrowed process + * env storage to avoid a temporary copy. + * + * For error conditions, a return value other than ZAI_ENV_SUCCESS is returned. + * No output-buffer contents are guaranteed on failure. If callers want an + * empty C-string on failure, they should initialize `buf->ptr[0] = '\\0'` + * before calling this API (when `buf->len > 0`). */ -zai_env_result zai_getenv_ex(zai_str name, zai_env_buffer buf, bool pre_rinit, bool use_process_env); -static inline zai_env_result zai_getenv(zai_str name, zai_env_buffer buf) { +zai_env_result zai_getenv_ex(zai_str name, zai_env_buffer *buf, bool pre_rinit, bool use_process_env); +static inline zai_env_result zai_getenv(zai_str name, zai_env_buffer *buf) { return zai_getenv_ex(name, buf, false, true); } -#define zai_getenv_literal(name, buf) zai_getenv(ZAI_STRL(name), buf) +#define zai_getenv_literal(name, buf) zai_getenv(ZAI_STRL(name), &(buf)) #endif // ZAI_ENV_H diff --git a/zend_abstract_interface/env/tests/host.cc b/zend_abstract_interface/env/tests/host.cc index 273bbe5b91e..46eb53acb51 100644 --- a/zend_abstract_interface/env/tests/host.cc +++ b/zend_abstract_interface/env/tests/host.cc @@ -36,6 +36,7 @@ TEA_TEST_CASE_WITH_PROLOGUE("env/host", "not set", { REQUIRE_UNSETENV("FOO"); ZAI_ENV_BUFFER_INIT(buf, 64); + buf.ptr[0] = '\0'; zai_env_result res = zai_getenv_literal("FOO", buf); REQUIRE(res == ZAI_ENV_NOT_SET); @@ -60,6 +61,7 @@ TEA_TEST_CASE_WITH_PROLOGUE("env/host", "buffer too small", { REQUIRE_SETENV("FOO", "bar"); ZAI_ENV_BUFFER_INIT(buf, 3); // No room for null terminator + buf.ptr[0] = '\0'; zai_env_result res = zai_getenv_literal("FOO", buf); REQUIRE(res == ZAI_ENV_BUFFER_TOO_SMALL); @@ -72,6 +74,7 @@ TEA_TEST_CASE_WITH_PROLOGUE("env/host", "buffer too big", { REQUIRE_SETENV("FOO", "bar"); ZAI_ENV_BUFFER_INIT(buf, ZAI_ENV_MAX_BUFSIZ + 1); + buf.ptr[0] = '\0'; zai_env_result res = zai_getenv_literal("FOO", buf); REQUIRE(res == ZAI_ENV_BUFFER_TOO_BIG); @@ -86,6 +89,7 @@ TEA_TEST_CASE_BARE("env/host", "outside request context", { REQUIRE_SETENV("FOO", "bar"); ZAI_ENV_BUFFER_INIT(buf, 64); + buf.ptr[0] = '\0'; zai_env_result res = zai_getenv_literal("FOO", buf); REQUIRE(res == ZAI_ENV_NOT_READY); diff --git a/zend_abstract_interface/env/tests/sapi.cc b/zend_abstract_interface/env/tests/sapi.cc index bb40968e8b1..093ecb9e0fe 100644 --- a/zend_abstract_interface/env/tests/sapi.cc +++ b/zend_abstract_interface/env/tests/sapi.cc @@ -49,6 +49,7 @@ TEA_TEST_CASE_WITH_PROLOGUE("env/sapi", "not set", { },{ REQUIRE_UNSETENV("FOO"); ZAI_ENV_BUFFER_INIT(buf, 64); + buf.ptr[0] = '\0'; zai_env_result res = zai_getenv_literal("FOO", buf); REQUIRE(res == ZAI_ENV_NOT_SET); From f2bfaa3de6615eab8d3d804aa89ec78306419b9c Mon Sep 17 00:00:00 2001 From: Levi Morrison Date: Sat, 28 Feb 2026 06:09:36 +0100 Subject: [PATCH 03/11] fix: zero name len test --- zend_abstract_interface/config/config.c | 4 ---- zend_abstract_interface/config/config_ini.c | 2 -- zend_abstract_interface/env/env.h | 2 +- zend_abstract_interface/env/tests/error.cc | 3 ++- zend_abstract_interface/env/tests/host.cc | 4 ---- zend_abstract_interface/env/tests/sapi.cc | 1 - 6 files changed, 3 insertions(+), 13 deletions(-) diff --git a/zend_abstract_interface/config/config.c b/zend_abstract_interface/config/config.c index f91277fa5a5..898c58f2a6c 100644 --- a/zend_abstract_interface/config/config.c +++ b/zend_abstract_interface/config/config.c @@ -99,7 +99,6 @@ static void zai_config_find_and_set_value(zai_config_memoized_entry *memoized, z for (; name_index < memoized->names_count; name_index++) { buf.ptr = buf_storage; buf.len = sizeof(buf_storage); - buf.ptr[0] = '\0'; zai_str name = {.len = memoized->names[name_index].len, .ptr = memoized->names[name_index].ptr}; zai_config_stable_file_entry *entry = zai_config_stable_file_get_value(name); if (entry && entry->source == DDOG_LIBRARY_CONFIG_SOURCE_FLEET_STABLE_CONFIG) { @@ -122,9 +121,6 @@ static void zai_config_find_and_set_value(zai_config_memoized_entry *memoized, z buf.ptr = buf_storage; buf.len = sizeof(buf_storage); - if (zai_option_str_is_none(value) || value.ptr != buf_storage) { - buf.ptr[0] = '\0'; - } if (!value.len && memoized->env_config_fallback && memoized->env_config_fallback(&buf, true)) { zai_config_process_env(memoized, buf, &value); name_index = ZAI_CONFIG_ORIGIN_MODIFIED; diff --git a/zend_abstract_interface/config/config_ini.c b/zend_abstract_interface/config/config_ini.c index 01117240fcc..d9e153613ff 100644 --- a/zend_abstract_interface/config/config_ini.c +++ b/zend_abstract_interface/config/config_ini.c @@ -440,7 +440,6 @@ void zai_config_ini_rinit(void) { for (uint8_t name_index = 0; name_index < memoized->names_count; name_index++) { buf.ptr = buf_storage; buf.len = sizeof(buf_storage); - buf.ptr[0] = '\0'; zai_str name = ZAI_STR_NEW(memoized->names[name_index].ptr, memoized->names[name_index].len); zai_config_stable_file_entry *entry = zai_config_stable_file_get_value(name); if (entry && entry->source == DDOG_LIBRARY_CONFIG_SOURCE_FLEET_STABLE_CONFIG @@ -464,7 +463,6 @@ void zai_config_ini_rinit(void) { buf.ptr = buf_storage; buf.len = sizeof(buf_storage); - buf.ptr[0] = '\0'; if (memoized->env_config_fallback && memoized->env_config_fallback(&buf, false) && zai_config_process_runtime_env(memoized, buf, in_startup, i, 0)) { goto next_entry; } diff --git a/zend_abstract_interface/env/env.h b/zend_abstract_interface/env/env.h index 68f321dda03..4e18ff092d3 100644 --- a/zend_abstract_interface/env/env.h +++ b/zend_abstract_interface/env/env.h @@ -45,7 +45,7 @@ typedef struct zai_env_buffer_s { } zai_env_buffer; #define ZAI_ENV_BUFFER_INIT(name, size) \ - char name##_storage[size]; \ + char name##_storage[size] = {0}; \ zai_env_buffer name = {size, name##_storage} /* Resolves a target environment variable identified by 'name'. Must be called diff --git a/zend_abstract_interface/env/tests/error.cc b/zend_abstract_interface/env/tests/error.cc index e301ba7d4e4..aca1273a677 100644 --- a/zend_abstract_interface/env/tests/error.cc +++ b/zend_abstract_interface/env/tests/error.cc @@ -12,10 +12,11 @@ TEA_TEST_CASE_WITH_PROLOGUE("env/error", "zero name len", { REQUIRE_UNSETENV("FOO"); ZAI_ENV_BUFFER_INIT(buf, 64); + strcpy(buf.ptr, "foo"); zai_env_result res = zai_getenv_literal("", buf); REQUIRE(res == ZAI_ENV_ERROR); - REQUIRE_BUF_EQ("", buf); + REQUIRE_BUF_EQ("foo", buf); }) TEA_TEST_CASE_WITH_PROLOGUE("env/error", "NULL buffer", { diff --git a/zend_abstract_interface/env/tests/host.cc b/zend_abstract_interface/env/tests/host.cc index 46eb53acb51..273bbe5b91e 100644 --- a/zend_abstract_interface/env/tests/host.cc +++ b/zend_abstract_interface/env/tests/host.cc @@ -36,7 +36,6 @@ TEA_TEST_CASE_WITH_PROLOGUE("env/host", "not set", { REQUIRE_UNSETENV("FOO"); ZAI_ENV_BUFFER_INIT(buf, 64); - buf.ptr[0] = '\0'; zai_env_result res = zai_getenv_literal("FOO", buf); REQUIRE(res == ZAI_ENV_NOT_SET); @@ -61,7 +60,6 @@ TEA_TEST_CASE_WITH_PROLOGUE("env/host", "buffer too small", { REQUIRE_SETENV("FOO", "bar"); ZAI_ENV_BUFFER_INIT(buf, 3); // No room for null terminator - buf.ptr[0] = '\0'; zai_env_result res = zai_getenv_literal("FOO", buf); REQUIRE(res == ZAI_ENV_BUFFER_TOO_SMALL); @@ -74,7 +72,6 @@ TEA_TEST_CASE_WITH_PROLOGUE("env/host", "buffer too big", { REQUIRE_SETENV("FOO", "bar"); ZAI_ENV_BUFFER_INIT(buf, ZAI_ENV_MAX_BUFSIZ + 1); - buf.ptr[0] = '\0'; zai_env_result res = zai_getenv_literal("FOO", buf); REQUIRE(res == ZAI_ENV_BUFFER_TOO_BIG); @@ -89,7 +86,6 @@ TEA_TEST_CASE_BARE("env/host", "outside request context", { REQUIRE_SETENV("FOO", "bar"); ZAI_ENV_BUFFER_INIT(buf, 64); - buf.ptr[0] = '\0'; zai_env_result res = zai_getenv_literal("FOO", buf); REQUIRE(res == ZAI_ENV_NOT_READY); diff --git a/zend_abstract_interface/env/tests/sapi.cc b/zend_abstract_interface/env/tests/sapi.cc index 093ecb9e0fe..bb40968e8b1 100644 --- a/zend_abstract_interface/env/tests/sapi.cc +++ b/zend_abstract_interface/env/tests/sapi.cc @@ -49,7 +49,6 @@ TEA_TEST_CASE_WITH_PROLOGUE("env/sapi", "not set", { },{ REQUIRE_UNSETENV("FOO"); ZAI_ENV_BUFFER_INIT(buf, 64); - buf.ptr[0] = '\0'; zai_env_result res = zai_getenv_literal("FOO", buf); REQUIRE(res == ZAI_ENV_NOT_SET); From 851505d613751ff39c0c0ac0211f9c99da60172d Mon Sep 17 00:00:00 2001 From: Levi Morrison Date: Tue, 10 Mar 2026 11:22:44 -0600 Subject: [PATCH 04/11] fix: missed a getenv -> getenv_ex conversion --- zend_abstract_interface/config/config_ini.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zend_abstract_interface/config/config_ini.c b/zend_abstract_interface/config/config_ini.c index d9e153613ff..f4158152a2e 100644 --- a/zend_abstract_interface/config/config_ini.c +++ b/zend_abstract_interface/config/config_ini.c @@ -448,7 +448,7 @@ void zai_config_ini_rinit(void) { memoized->name_index = ZAI_CONFIG_ORIGIN_FLEET_STABLE; memoized->config_id = (zai_str) ZAI_STR_FROM_ZSTR(entry->config_id); goto next_entry; - } else if ((zai_getenv(name, &buf) == ZAI_ENV_SUCCESS + } else if ((zai_getenv_ex(name, &buf, false, false) == ZAI_ENV_SUCCESS || zai_config_get_cached_env_value(i, name_index, &buf)) && zai_config_process_runtime_env(memoized, buf, in_startup, i, name_index)) { goto next_entry; From c475ec36bc9875afef1cafa80e7f9ac719578f0c Mon Sep 17 00:00:00 2001 From: Levi Morrison Date: Tue, 10 Mar 2026 11:35:29 -0600 Subject: [PATCH 05/11] perf: skip getenv cache refresh on CLI --- zend_abstract_interface/config/config.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/zend_abstract_interface/config/config.c b/zend_abstract_interface/config/config.c index 898c58f2a6c..60bab995785 100644 --- a/zend_abstract_interface/config/config.c +++ b/zend_abstract_interface/config/config.c @@ -1,5 +1,6 @@ #include "./config.h" +#include #include #include #include
@@ -301,9 +302,10 @@ void zai_config_first_time_rinit(bool in_request) { (void)in_request; #endif - if (in_request) { - // Refresh process env snapshot for SAPIs like FPM that materialize - // pool env values just before the first request. + // Refresh process env snapshot for SAPIs like FPM that materialize pool + // env values just before the first request. Skip on CLI since we know it + // doesn't need it and we can avoid the extra work. + if (in_request && strcmp(sapi_module.name, "cli") != 0) { zai_config_clear_cached_env_values(); zai_config_cache_env_values(); } From 183a5c578600096db5b0f9df02d0f2f248c47bfd Mon Sep 17 00:00:00 2001 From: Levi Morrison Date: Tue, 10 Mar 2026 11:56:02 -0600 Subject: [PATCH 06/11] test: sapi precedence and cached sys env --- zend_abstract_interface/config/tests/env.cc | 5 +- zend_abstract_interface/config/tests/ini.cc | 56 ++++++++++++++++++--- 2 files changed, 51 insertions(+), 10 deletions(-) diff --git a/zend_abstract_interface/config/tests/env.cc b/zend_abstract_interface/config/tests/env.cc index 6fe9b63d913..f839ba2462e 100644 --- a/zend_abstract_interface/config/tests/env.cc +++ b/zend_abstract_interface/config/tests/env.cc @@ -193,14 +193,15 @@ TEA_TEST_CASE_BARE("config/env", "change after memoization", { REQUEST_BEGIN(); + // Should be false still because it's cached on minit/first_rinit. zval *value = zai_config_get_value(EXT_CFG_FOO_BOOL); REQUIRE(value != NULL); #if PHP_VERSION_ID > 70000 - REQUIRE(Z_TYPE_P(value) == IS_TRUE); + REQUIRE(Z_TYPE_P(value) == IS_FALSE); #else REQUIRE(Z_TYPE_P(value) == IS_BOOL); - REQUIRE(Z_BVAL_P(value) == 1); + REQUIRE(Z_BVAL_P(value) == 0); #endif REQUEST_END(); diff --git a/zend_abstract_interface/config/tests/ini.cc b/zend_abstract_interface/config/tests/ini.cc index 32b141e6957..346ac66db0f 100644 --- a/zend_abstract_interface/config/tests/ini.cc +++ b/zend_abstract_interface/config/tests/ini.cc @@ -522,6 +522,7 @@ TEST_INI("change before request startup", { static ZEND_INI_MH(dummy) { return SUCCESS; } + TEST_INI("setting perdir INI setting for multiple ZAI config users", { REQUIRE(tea_sapi_append_system_ini_entry("zai_config.INI_FOO_STRING", "another")); }, { @@ -556,13 +557,38 @@ TEST_INI("setting an env value after memoization for multiple ZAI config users", REQUIRE_SETENV("INI_FOO_STRING", "value2"); - // Something else inits zai config first - zai_config_rinit(); - zai_config_rshutdown(); + REQUEST_BEGIN() - // now we init it - zai_config_memoized_entry *entry = &zai_config_memoized_entries[EXT_CFG_INI_FOO_STRING]; - entry->original_on_modify = dummy; + // The value should be cached on minit/first_rinit, so we still get the + // "value" back, not "value2". + zval *value = zai_config_get_value(EXT_CFG_INI_FOO_STRING); + + REQUIRE(value != NULL); + REQUIRE(Z_TYPE_P(value) == IS_STRING); + REQUIRE(zval_string_equals(value, "value")); + + REQUEST_END() +}) + +#if PHP_VERSION_ID >= 80000 +#define TEA_SAPI_GETENV_FUNCTION(fn) static char *fn(const char *name, size_t name_len) +#else +#define TEA_SAPI_GETENV_FUNCTION(fn) static char *fn(char *name, size_t name_len) +#endif + +static char sapi_getenv_test_buf[64]; + +// Returns "from_sapi" only for INI_FOO_STRING; otherwise NULL (so config falls back to cache). +TEA_SAPI_GETENV_FUNCTION(ini_sapi_getenv_from_sapi) { + if (name_len == 15 && strncmp(name, "INI_FOO_STRING", 15) == 0) { + strcpy(sapi_getenv_test_buf, "sapi env val"); + return sapi_getenv_test_buf; + } + return NULL; +} + +TEST_INI("SAPI env takes priority over cache", {}, { + REQUIRE_SETENV("INI_FOO_STRING", "system env val"); REQUEST_BEGIN() @@ -570,7 +596,21 @@ TEST_INI("setting an env value after memoization for multiple ZAI config users", REQUIRE(value != NULL); REQUIRE(Z_TYPE_P(value) == IS_STRING); - REQUIRE(zval_string_equals(value, "value2")); + REQUIRE(zval_string_equals(value, "system env val")); REQUEST_END() -}) \ No newline at end of file + + tea_sapi_module.getenv = ini_sapi_getenv_from_sapi; + + REQUEST_BEGIN() + + zval *value = zai_config_get_value(EXT_CFG_INI_FOO_STRING); + + REQUIRE(value != NULL); + REQUIRE(Z_TYPE_P(value) == IS_STRING); + REQUIRE(zval_string_equals(value, "sapi env val")); + + REQUEST_END() + + tea_sapi_module.getenv = NULL; +}) From 3bfaaac5c001d11641a31e05f9f3d9e0ff700bdd Mon Sep 17 00:00:00 2001 From: Levi Morrison Date: Tue, 10 Mar 2026 12:23:41 -0600 Subject: [PATCH 07/11] test: better output on zval str failure --- .../config/tests/config_test_helpers.h | 12 +++++ zend_abstract_interface/config/tests/ini.cc | 44 ++++--------------- 2 files changed, 21 insertions(+), 35 deletions(-) diff --git a/zend_abstract_interface/config/tests/config_test_helpers.h b/zend_abstract_interface/config/tests/config_test_helpers.h index 26c49c9f945..420c21ae303 100644 --- a/zend_abstract_interface/config/tests/config_test_helpers.h +++ b/zend_abstract_interface/config/tests/config_test_helpers.h @@ -7,6 +7,18 @@ static inline bool zval_string_equals(zval *value, const char *str) { return Z_STRLEN_P(value) == strlen(str) && !strcmp(Z_STRVAL_P(value), str); } +#ifdef __cplusplus +#include + +/* Use Catch2's expression decomposition: REQUIRE(a == b) prints both values on failure (like assert_eq! in Rust). */ +#define REQUIRE_ZVAL_STRING_EQ(zv, expected) \ + do { \ + REQUIRE(zv != NULL); \ + REQUIRE(Z_TYPE_P(zv) == IS_STRING); \ + REQUIRE(std::string(Z_STRVAL_P(zv), Z_STRLEN_P(zv)) == std::string(expected)); \ + } while (0) +#endif + #define REQUIRE_SETENV(key, val) REQUIRE(0 == setenv(key, val, /* overwrite */ 1)) #define REQUEST_BEGIN() \ diff --git a/zend_abstract_interface/config/tests/ini.cc b/zend_abstract_interface/config/tests/ini.cc index 346ac66db0f..267dc406510 100644 --- a/zend_abstract_interface/config/tests/ini.cc +++ b/zend_abstract_interface/config/tests/ini.cc @@ -245,13 +245,8 @@ TEST_INI("map INI: user value", {}, { TEST_INI("string INI: default value", {}, { REQUEST_BEGIN() - zval *value = zai_config_get_value(EXT_CFG_INI_FOO_STRING); - - REQUIRE(value != NULL); - REQUIRE(Z_TYPE_P(value) == IS_STRING); - REQUIRE(zval_string_equals(value, "foo string")); - + REQUIRE_ZVAL_STRING_EQ(value, "foo string"); REQUEST_END() }) @@ -259,27 +254,16 @@ TEST_INI("string INI: system value", { REQUIRE(tea_sapi_append_system_ini_entry("zai_config.INI_FOO_STRING", "system string")); }, { REQUEST_BEGIN() - zval *value = zai_config_get_value(EXT_CFG_INI_FOO_STRING); - - REQUIRE(value != NULL); - REQUIRE(Z_TYPE_P(value) == IS_STRING); - REQUIRE(zval_string_equals(value, "system string")); - + REQUIRE_ZVAL_STRING_EQ(value, "system string"); REQUEST_END() }) TEST_INI("string INI: user value", {}, { REQUEST_BEGIN() - REQUIRE_SET_INI("zai_config.INI_FOO_STRING", "user string"); - zval *value = zai_config_get_value(EXT_CFG_INI_FOO_STRING); - - REQUIRE(value != NULL); - REQUIRE(Z_TYPE_P(value) == IS_STRING); - REQUIRE(zval_string_equals(value, "user string")); - + REQUIRE_ZVAL_STRING_EQ(value, "user string"); REQUEST_END() }) @@ -534,9 +518,7 @@ TEST_INI("setting perdir INI setting for multiple ZAI config users", { zval *value = zai_config_get_value(EXT_CFG_INI_FOO_STRING); - REQUIRE(value != NULL); - REQUIRE(Z_TYPE_P(value) == IS_STRING); - REQUIRE(zval_string_equals(value, "another")); + REQUIRE_ZVAL_STRING_EQ(value, "another"); REQUEST_END(); }) @@ -549,9 +531,7 @@ TEST_INI("setting an env value after memoization for multiple ZAI config users", zval *value = zai_config_get_value(EXT_CFG_INI_FOO_STRING); - REQUIRE(value != NULL); - REQUIRE(Z_TYPE_P(value) == IS_STRING); - REQUIRE(zval_string_equals(value, "value")); + REQUIRE_ZVAL_STRING_EQ(value, "value"); REQUEST_END() @@ -563,9 +543,7 @@ TEST_INI("setting an env value after memoization for multiple ZAI config users", // "value" back, not "value2". zval *value = zai_config_get_value(EXT_CFG_INI_FOO_STRING); - REQUIRE(value != NULL); - REQUIRE(Z_TYPE_P(value) == IS_STRING); - REQUIRE(zval_string_equals(value, "value")); + REQUIRE_ZVAL_STRING_EQ(value, "value"); REQUEST_END() }) @@ -578,7 +556,7 @@ TEST_INI("setting an env value after memoization for multiple ZAI config users", static char sapi_getenv_test_buf[64]; -// Returns "from_sapi" only for INI_FOO_STRING; otherwise NULL (so config falls back to cache). +// Returns "sapi env val" only for INI_FOO_STRING; otherwise NULL (so config falls back to cache). TEA_SAPI_GETENV_FUNCTION(ini_sapi_getenv_from_sapi) { if (name_len == 15 && strncmp(name, "INI_FOO_STRING", 15) == 0) { strcpy(sapi_getenv_test_buf, "sapi env val"); @@ -594,9 +572,7 @@ TEST_INI("SAPI env takes priority over cache", {}, { zval *value = zai_config_get_value(EXT_CFG_INI_FOO_STRING); - REQUIRE(value != NULL); - REQUIRE(Z_TYPE_P(value) == IS_STRING); - REQUIRE(zval_string_equals(value, "system env val")); + REQUIRE_ZVAL_STRING_EQ(value, "system env val"); REQUEST_END() @@ -606,9 +582,7 @@ TEST_INI("SAPI env takes priority over cache", {}, { zval *value = zai_config_get_value(EXT_CFG_INI_FOO_STRING); - REQUIRE(value != NULL); - REQUIRE(Z_TYPE_P(value) == IS_STRING); - REQUIRE(zval_string_equals(value, "sapi env val")); + REQUIRE_ZVAL_STRING_EQ(value, "sapi env val"); REQUEST_END() From a4c8f3027e8c2086bfabffb9b445c0262a111513 Mon Sep 17 00:00:00 2001 From: Levi Morrison Date: Tue, 10 Mar 2026 12:32:22 -0600 Subject: [PATCH 08/11] test: fix headers/build for zai (hopefully) --- .../config/tests/config_test_helpers.h | 12 ------------ zend_abstract_interface/config/tests/ini.cc | 10 ++++++++++ 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/zend_abstract_interface/config/tests/config_test_helpers.h b/zend_abstract_interface/config/tests/config_test_helpers.h index 420c21ae303..26c49c9f945 100644 --- a/zend_abstract_interface/config/tests/config_test_helpers.h +++ b/zend_abstract_interface/config/tests/config_test_helpers.h @@ -7,18 +7,6 @@ static inline bool zval_string_equals(zval *value, const char *str) { return Z_STRLEN_P(value) == strlen(str) && !strcmp(Z_STRVAL_P(value), str); } -#ifdef __cplusplus -#include - -/* Use Catch2's expression decomposition: REQUIRE(a == b) prints both values on failure (like assert_eq! in Rust). */ -#define REQUIRE_ZVAL_STRING_EQ(zv, expected) \ - do { \ - REQUIRE(zv != NULL); \ - REQUIRE(Z_TYPE_P(zv) == IS_STRING); \ - REQUIRE(std::string(Z_STRVAL_P(zv), Z_STRLEN_P(zv)) == std::string(expected)); \ - } while (0) -#endif - #define REQUIRE_SETENV(key, val) REQUIRE(0 == setenv(key, val, /* overwrite */ 1)) #define REQUEST_BEGIN() \ diff --git a/zend_abstract_interface/config/tests/ini.cc b/zend_abstract_interface/config/tests/ini.cc index 267dc406510..bee941cd2ba 100644 --- a/zend_abstract_interface/config/tests/ini.cc +++ b/zend_abstract_interface/config/tests/ini.cc @@ -7,6 +7,16 @@ extern "C" { #include "zai_tests_common.hpp" +#include + +/* Use Catch2's expression decomposition: REQUIRE(a == b) prints both values on failure (like assert_eq! in Rust). */ +#define REQUIRE_ZVAL_STRING_EQ(zv, expected) \ + do { \ + REQUIRE(zv != NULL); \ + REQUIRE(Z_TYPE_P(zv) == IS_STRING); \ + REQUIRE(std::string(Z_STRVAL_P(zv), Z_STRLEN_P(zv)) == std::string(expected)); \ + } while (0) + typedef enum { EXT_CFG_INI_FOO_BOOL, EXT_CFG_INI_FOO_DOUBLE, From 583f0af8cf43bdd1accb584f82304c508ab69ce2 Mon Sep 17 00:00:00 2001 From: Levi Morrison Date: Tue, 10 Mar 2026 12:52:43 -0600 Subject: [PATCH 09/11] test: fix sapi_getenv mock --- zend_abstract_interface/config/tests/ini.cc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/zend_abstract_interface/config/tests/ini.cc b/zend_abstract_interface/config/tests/ini.cc index bee941cd2ba..60b58117c96 100644 --- a/zend_abstract_interface/config/tests/ini.cc +++ b/zend_abstract_interface/config/tests/ini.cc @@ -568,7 +568,9 @@ static char sapi_getenv_test_buf[64]; // Returns "sapi env val" only for INI_FOO_STRING; otherwise NULL (so config falls back to cache). TEA_SAPI_GETENV_FUNCTION(ini_sapi_getenv_from_sapi) { - if (name_len == 15 && strncmp(name, "INI_FOO_STRING", 15) == 0) { + zai_str key = ZAI_STR_NEW(name, name_len); + if (zai_str_eq(key, ZAI_STRL("INI_FOO_STRING"))) { + memset(sapi_getenv_test_buf, 0, sizeof(sapi_getenv_test_buf)); strcpy(sapi_getenv_test_buf, "sapi env val"); return sapi_getenv_test_buf; } From 41fc47c31e2744580c273efa699ab3fb2e8a55e8 Mon Sep 17 00:00:00 2001 From: Levi Morrison Date: Wed, 11 Mar 2026 14:13:45 -0600 Subject: [PATCH 10/11] test: try to fix sapi env priority --- zend_abstract_interface/config/tests/ini.cc | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/zend_abstract_interface/config/tests/ini.cc b/zend_abstract_interface/config/tests/ini.cc index 60b58117c96..25cb21256c6 100644 --- a/zend_abstract_interface/config/tests/ini.cc +++ b/zend_abstract_interface/config/tests/ini.cc @@ -7,6 +7,7 @@ extern "C" { #include "zai_tests_common.hpp" +#include #include /* Use Catch2's expression decomposition: REQUIRE(a == b) prints both values on failure (like assert_eq! in Rust). */ @@ -565,11 +566,16 @@ TEST_INI("setting an env value after memoization for multiple ZAI config users", #endif static char sapi_getenv_test_buf[64]; +static std::atomic sapi_getenv_request_count{0}; -// Returns "sapi env val" only for INI_FOO_STRING; otherwise NULL (so config falls back to cache). +// For INI_FOO_STRING: first request (call) returns NULL (fall back to cache), second returns "sapi env val". TEA_SAPI_GETENV_FUNCTION(ini_sapi_getenv_from_sapi) { zai_str key = ZAI_STR_NEW(name, name_len); if (zai_str_eq(key, ZAI_STRL("INI_FOO_STRING"))) { + int call = sapi_getenv_request_count.fetch_add(1); + if (call == 0) { + return NULL; + } memset(sapi_getenv_test_buf, 0, sizeof(sapi_getenv_test_buf)); strcpy(sapi_getenv_test_buf, "sapi env val"); return sapi_getenv_test_buf; @@ -577,9 +583,11 @@ TEA_SAPI_GETENV_FUNCTION(ini_sapi_getenv_from_sapi) { return NULL; } -TEST_INI("SAPI env takes priority over cache", {}, { +TEST_INI("SAPI env takes priority over cache", { + sapi_getenv_request_count.store(0); REQUIRE_SETENV("INI_FOO_STRING", "system env val"); - + tea_sapi_module.getenv = ini_sapi_getenv_from_sapi; +}, { REQUEST_BEGIN() zval *value = zai_config_get_value(EXT_CFG_INI_FOO_STRING); @@ -588,8 +596,6 @@ TEST_INI("SAPI env takes priority over cache", {}, { REQUEST_END() - tea_sapi_module.getenv = ini_sapi_getenv_from_sapi; - REQUEST_BEGIN() zval *value = zai_config_get_value(EXT_CFG_INI_FOO_STRING); From a12cb93f039cad4a30e21682f4ca863d9a1b4ced Mon Sep 17 00:00:00 2001 From: Levi Morrison Date: Fri, 13 Mar 2026 09:36:04 -0600 Subject: [PATCH 11/11] refactor(env): replace buffer-based getenv with zai_option_str API Replace zai_getenv_ex/zai_getenv with two focused functions: - zai_sapi_getenv: borrows value from the SAPI environment - zai_sys_getenv: borrows value from the process environment Both return zai_option_str instead of writing into a caller-supplied buffer, eliminating the ZAI_ENV_BUFFER_* error codes and the scratch buffer indirection throughout callers. Update config, config_ini, and otel_config to use the new API. Rename zai_config_get_cached_env_value to zai_config_sys_getenv_cached to return zai_option_str directly. Add ZAI_OPTION_STRL macro and zai_option_str_eq helper to string.h. Remove now-obsolete error.cc env tests. --- ext/otel_config.c | 14 ++- ext/otel_config.h | 2 + zend_abstract_interface/config/config.c | 97 ++++++++++--------- zend_abstract_interface/config/config.h | 2 +- zend_abstract_interface/config/config_ini.c | 36 ++++--- zend_abstract_interface/config/tests/ini.cc | 21 +++- zend_abstract_interface/env/env.c | 55 +---------- zend_abstract_interface/env/env.h | 39 ++++---- .../env/tests/CMakeLists.txt | 2 +- zend_abstract_interface/env/tests/error.cc | 45 --------- zend_abstract_interface/env/tests/host.cc | 86 +++++++--------- zend_abstract_interface/env/tests/sapi.cc | 73 ++++++++------ zend_abstract_interface/zai_string/string.h | 9 ++ 13 files changed, 215 insertions(+), 266 deletions(-) delete mode 100644 zend_abstract_interface/env/tests/error.cc diff --git a/ext/otel_config.c b/ext/otel_config.c index a15ed9ca95e..c3911ca4f49 100644 --- a/ext/otel_config.c +++ b/ext/otel_config.c @@ -1,5 +1,6 @@ #include "otel_config.h" #include +#include #include "ddtrace.h" #include #include "sidecar.h" @@ -20,8 +21,19 @@ static void report_otel_cfg_telemetry_invalid(const char *otel_cfg, const char * } } +/** + * Borrows the value from the SAPI or the system env vars, or falls back on the + * INI entry, which copies the value into the buffer. + */ static bool get_otel_value(zai_str str, zai_env_buffer *buf, bool pre_rinit) { - if (zai_getenv_ex(str, buf, pre_rinit, true) == ZAI_ENV_SUCCESS) { + zai_option_str opt = zai_sapi_getenv(str); + if (zai_option_str_is_none(opt)) { + opt = zai_sys_getenv(str); + } + zai_str val; + if (zai_option_str_get(opt, &val)) { + buf->ptr = val.ptr; + buf->len = val.len; return true; } diff --git a/ext/otel_config.h b/ext/otel_config.h index a7840b6d78e..360d727b84d 100644 --- a/ext/otel_config.h +++ b/ext/otel_config.h @@ -3,6 +3,8 @@ #include +#include + bool ddtrace_conf_otel_resource_attributes_env(zai_env_buffer *buf, bool pre_rinit); bool ddtrace_conf_otel_resource_attributes_version(zai_env_buffer *buf, bool pre_rinit); bool ddtrace_conf_otel_service_name(zai_env_buffer *buf, bool pre_rinit); diff --git a/zend_abstract_interface/config/config.c b/zend_abstract_interface/config/config.c index 60bab995785..57044102464 100644 --- a/zend_abstract_interface/config/config.c +++ b/zend_abstract_interface/config/config.c @@ -24,19 +24,10 @@ zai_config_memoized_entry zai_config_memoized_entries[ZAI_CONFIG_ENTRIES_COUNT_M */ static zai_option_str zai_config_cached_env_values[ZAI_CONFIG_ENTRIES_COUNT_MAX][ZAI_CONFIG_NAMES_COUNT_MAX]; -bool zai_config_get_cached_env_value(zai_config_id id, uint8_t name_index, zai_env_buffer *buf) { - ZEND_ASSERT(buf != NULL); - ZEND_ASSERT(buf->ptr != NULL); - ZEND_ASSERT(buf->len > 0); +zai_option_str zai_config_sys_getenv_cached(zai_config_id id, uint8_t name_index) { ZEND_ASSERT(id < zai_config_memoized_entries_count); ZEND_ASSERT(name_index < zai_config_memoized_entries[id].names_count); - zai_option_str cached = zai_config_cached_env_values[id][name_index]; - if (zai_option_str_is_some(cached)) { - buf->ptr = (char *)cached.ptr; - buf->len = cached.len; - return true; - } - return false; + return zai_config_cached_env_values[id][name_index]; } static void zai_config_cache_env_values(void) { @@ -44,14 +35,18 @@ static void zai_config_cache_env_values(void) { zai_config_memoized_entry *memoized = &zai_config_memoized_entries[i]; for (uint8_t n = 0; n < memoized->names_count; n++) { zai_str name = {.len = memoized->names[n].len, .ptr = memoized->names[n].ptr}; - char *value = getenv(name.ptr); - if (!value) { + + zai_option_str val = zai_sys_getenv(name); + if (zai_option_str_is_none(val)) { continue; } - size_t len = strlen(value); - char *copy = pemalloc(len + 1, 1); - memcpy(copy, value, len + 1); - zai_config_cached_env_values[i][n] = zai_option_str_from_raw_parts(copy, len); + + size_t len = val.len; + // +1 to hold the null terminator. + char *dst = pemalloc(len + 1, 1); + // +1 to include the null terminator in the copy. + memcpy(dst, val.ptr, len + 1); + zai_config_cached_env_values[i][n] = zai_option_str_from_raw_parts(dst, len); } } } @@ -69,61 +64,69 @@ static void zai_config_clear_cached_env_values(void) { } } -static bool zai_config_get_env_value(zai_str name, zai_config_id id, uint8_t name_index, zai_env_buffer *buf) { - // TODO Handle other return codes - if (zai_getenv(name, buf) == ZAI_ENV_SUCCESS) { - return true; +static zai_option_str zai_config_getenv(zai_str name, zai_config_id id, uint8_t name_index, bool in_request) { + zai_option_str val = ZAI_OPTION_STR_NONE; + if (in_request) { + val = zai_sapi_getenv(name); + if (zai_option_str_is_some(val)) { + return val; + } } - return zai_config_get_cached_env_value(id, name_index, buf); + + return zai_config_sys_getenv_cached(id, name_index); } -static inline void zai_config_process_env(zai_config_memoized_entry *memoized, zai_env_buffer buf, zai_option_str *value) { +/** + * Decodes the environment variable value and returns the decoded value, or + * None if the decoding fails. + */ +static inline zai_option_str zai_config_process_env(zai_config_memoized_entry *memoized, zai_str val) { zval tmp; ZVAL_UNDEF(&tmp); - zai_str env_value = ZAI_STR_FROM_CSTR(buf.ptr); - if (!zai_config_decode_value(env_value, memoized->type, memoized->parser, &tmp, /* persistent */ true)) { + zai_option_str value = ZAI_OPTION_STR_NONE; + if (!zai_config_decode_value(val, memoized->type, memoized->parser, &tmp, /* persistent */ true)) { // TODO Log decoding error } else { zai_json_dtor_pzval(&tmp); - *value = zai_option_str_from_str(env_value); + value = zai_option_str_from_str(val); } + return value; } -static void zai_config_find_and_set_value(zai_config_memoized_entry *memoized, zai_config_id id) { - // TODO Use less buffer space - // TODO Make a more generic zai_string_buffer - ZAI_ENV_BUFFER_INIT(buf, ZAI_ENV_MAX_BUFSIZ); - +static void zai_config_find_and_set_value(zai_config_memoized_entry *memoized, zai_config_id id, bool in_request) { zai_option_str value = ZAI_OPTION_STR_NONE; int16_t name_index = 0; for (; name_index < memoized->names_count; name_index++) { - buf.ptr = buf_storage; - buf.len = sizeof(buf_storage); zai_str name = {.len = memoized->names[name_index].len, .ptr = memoized->names[name_index].ptr}; zai_config_stable_file_entry *entry = zai_config_stable_file_get_value(name); if (entry && entry->source == DDOG_LIBRARY_CONFIG_SOURCE_FLEET_STABLE_CONFIG) { - strcpy(buf.ptr, ZSTR_VAL(entry->value)); - zai_config_process_env(memoized, buf, &value); + zai_str val = zai_str_from_zstr(entry->value); + value = zai_config_process_env(memoized, val); name_index = ZAI_CONFIG_ORIGIN_FLEET_STABLE; memoized->config_id = (zai_str) ZAI_STR_FROM_ZSTR(entry->config_id); break; - } else if (zai_config_get_env_value(name, id, (uint8_t)name_index, &buf)) { - zai_config_process_env(memoized, buf, &value); - break; - } else if (entry && entry->source == DDOG_LIBRARY_CONFIG_SOURCE_LOCAL_STABLE_CONFIG) { - strcpy(buf.ptr, ZSTR_VAL(entry->value)); - zai_config_process_env(memoized, buf, &value); + }; + { + zai_str val; + zai_option_str maybe_val = zai_config_getenv(name, id, (uint8_t)name_index, in_request); + if (zai_option_str_get(maybe_val, &val)) { + value = zai_config_process_env(memoized, val); + break; + } + } + if (entry && entry->source == DDOG_LIBRARY_CONFIG_SOURCE_LOCAL_STABLE_CONFIG) { + zai_str val = zai_str_from_zstr(entry->value); + value = zai_config_process_env(memoized, val); name_index = ZAI_CONFIG_ORIGIN_LOCAL_STABLE; memoized->config_id = (zai_str) ZAI_STR_FROM_ZSTR(entry->config_id); break; } } - buf.ptr = buf_storage; - buf.len = sizeof(buf_storage); - if (!value.len && memoized->env_config_fallback && memoized->env_config_fallback(&buf, true)) { - zai_config_process_env(memoized, buf, &value); + ZAI_ENV_BUFFER_INIT(buf, ZAI_ENV_MAX_BUFSIZ); + if (zai_option_str_is_none(value) && memoized->env_config_fallback && memoized->env_config_fallback(&buf, true)) { + value = zai_config_process_env(memoized, (zai_str){buf.ptr, buf.len}); name_index = ZAI_CONFIG_ORIGIN_MODIFIED; } @@ -132,7 +135,7 @@ static void zai_config_find_and_set_value(zai_config_memoized_entry *memoized, z zai_str value_view; if (zai_option_str_get(value, &value_view)) { - if (value_view.ptr != buf.ptr && ini_name_index >= 0) { + if (ini_name_index >= 0) { name_index = ini_name_index; } // TODO If name_index > 0, log deprecation notice @@ -312,7 +315,7 @@ void zai_config_first_time_rinit(bool in_request) { for (uint16_t i = 0; i < zai_config_memoized_entries_count; i++) { zai_config_memoized_entry *memoized = &zai_config_memoized_entries[i]; - zai_config_find_and_set_value(memoized, i); + zai_config_find_and_set_value(memoized, i, in_request); #if PHP_VERSION_ID >= 70300 zai_config_intern_zval(&memoized->decoded_value); #else diff --git a/zend_abstract_interface/config/config.h b/zend_abstract_interface/config/config.h index 4bfa6df15cd..73986191153 100644 --- a/zend_abstract_interface/config/config.h +++ b/zend_abstract_interface/config/config.h @@ -115,6 +115,6 @@ bool zai_config_get_id_by_name(zai_str name, zai_config_id *id); void zai_config_register_config_id(zai_config_name *name, zai_config_id id); bool zai_config_is_initialized(void); -bool zai_config_get_cached_env_value(zai_config_id id, uint8_t name_index, zai_env_buffer *buf); +zai_option_str zai_config_sys_getenv_cached(zai_config_id id, uint8_t name_index); #endif // ZAI_CONFIG_H diff --git a/zend_abstract_interface/config/config_ini.c b/zend_abstract_interface/config/config_ini.c index f4158152a2e..cf76b1d3454 100644 --- a/zend_abstract_interface/config/config_ini.c +++ b/zend_abstract_interface/config/config_ini.c @@ -342,14 +342,14 @@ void zai_config_ini_minit(zai_config_env_to_ini_name env_to_ini, int module_numb #endif } -static inline bool zai_config_process_runtime_env(zai_config_memoized_entry *memoized, zai_env_buffer buf, bool in_startup, uint16_t config_index, uint16_t name_index) { +static inline bool zai_config_process_runtime_env(zai_config_memoized_entry *memoized, zai_str value, bool in_startup, uint16_t config_index, uint16_t name_index) { /* * we unconditionally decode the value because we do not store the in-use encoded value * so we cannot compare the current environment value to the current configuration value * for the purposes of short circuiting decode */ if (env_to_ini_name) { - zend_string *str = zend_string_init(buf.ptr, strlen(buf.ptr), in_startup); + zend_string *str = zend_string_init(value.ptr, value.len, in_startup); zend_ini_entry *ini = memoized->ini_entries[name_index]; if (zend_alter_ini_entry_ex(ini->name, str, PHP_INI_USER, PHP_INI_STAGE_RUNTIME, 0) == SUCCESS) { @@ -358,11 +358,9 @@ static inline bool zai_config_process_runtime_env(zai_config_memoized_entry *mem } zend_string_release(str); } else { - zai_str rte_value = ZAI_STR_FROM_CSTR(buf.ptr); - zval new_zv; ZVAL_UNDEF(&new_zv); - if (zai_config_decode_value(rte_value, memoized->type, memoized->parser, &new_zv, /* persistent */ false)) { + if (zai_config_decode_value(value, memoized->type, memoized->parser, &new_zv, /* persistent */ false)) { zai_config_replace_runtime_config(config_index, &new_zv); zval_ptr_dtor(&new_zv); return true; @@ -443,27 +441,33 @@ void zai_config_ini_rinit(void) { zai_str name = ZAI_STR_NEW(memoized->names[name_index].ptr, memoized->names[name_index].len); zai_config_stable_file_entry *entry = zai_config_stable_file_get_value(name); if (entry && entry->source == DDOG_LIBRARY_CONFIG_SOURCE_FLEET_STABLE_CONFIG - && strcpy(buf.ptr, ZSTR_VAL(entry->value)) - && zai_config_process_runtime_env(memoized, buf, in_startup, i, name_index)) { + && zai_config_process_runtime_env(memoized, (zai_str)ZAI_STR_FROM_ZSTR(entry->value), in_startup, i, name_index)) { memoized->name_index = ZAI_CONFIG_ORIGIN_FLEET_STABLE; memoized->config_id = (zai_str) ZAI_STR_FROM_ZSTR(entry->config_id); goto next_entry; - } else if ((zai_getenv_ex(name, &buf, false, false) == ZAI_ENV_SUCCESS - || zai_config_get_cached_env_value(i, name_index, &buf)) - && zai_config_process_runtime_env(memoized, buf, in_startup, i, name_index)) { + } + zai_str view; + zai_option_str opt = zai_sapi_getenv(name); + if (zai_option_str_get(opt, &view)) { + if (zai_config_process_runtime_env(memoized, view, in_startup, i, name_index)) { + goto next_entry; + } + } + + zai_option_str cached = zai_config_sys_getenv_cached(i, name_index); + if (zai_option_str_get(cached, &view) && zai_config_process_runtime_env(memoized, view, in_startup, i, name_index)) { goto next_entry; - } else if (entry && entry->source == DDOG_LIBRARY_CONFIG_SOURCE_LOCAL_STABLE_CONFIG - && strcpy(buf.ptr, ZSTR_VAL(entry->value)) - && zai_config_process_runtime_env(memoized, buf, in_startup, i, name_index)) { + } + if (entry && entry->source == DDOG_LIBRARY_CONFIG_SOURCE_LOCAL_STABLE_CONFIG + && zai_config_process_runtime_env(memoized, (zai_str)ZAI_STR_FROM_ZSTR(entry->value), in_startup, i, name_index)) { memoized->name_index = ZAI_CONFIG_ORIGIN_LOCAL_STABLE; memoized->config_id = (zai_str) ZAI_STR_FROM_ZSTR(entry->config_id); goto next_entry; } } - buf.ptr = buf_storage; - buf.len = sizeof(buf_storage); - if (memoized->env_config_fallback && memoized->env_config_fallback(&buf, false) && zai_config_process_runtime_env(memoized, buf, in_startup, i, 0)) { + if (memoized->env_config_fallback && memoized->env_config_fallback(&buf, false) + && zai_config_process_runtime_env(memoized, (zai_str){buf.ptr, buf.len}, in_startup, i, 0)) { goto next_entry; } } diff --git a/zend_abstract_interface/config/tests/ini.cc b/zend_abstract_interface/config/tests/ini.cc index 25cb21256c6..49bb38a3cb2 100644 --- a/zend_abstract_interface/config/tests/ini.cc +++ b/zend_abstract_interface/config/tests/ini.cc @@ -566,14 +566,21 @@ TEST_INI("setting an env value after memoization for multiple ZAI config users", #endif static char sapi_getenv_test_buf[64]; -static std::atomic sapi_getenv_request_count{0}; +// Tracks which request number we are in (incremented once per RINIT via pre_rinit callback). +// Both zai_config_first_time_rinit and zai_config_ini_rinit call zai_sapi_getenv within the +// same RINIT, so we use a per-request counter (not a per-call counter) so all calls within +// a single request see the same SAPI answer. +static std::atomic sapi_getenv_request_num{0}; + +static void sapi_getenv_advance_request() { + sapi_getenv_request_num.fetch_add(1); +} -// For INI_FOO_STRING: first request (call) returns NULL (fall back to cache), second returns "sapi env val". +// For INI_FOO_STRING: request 1 returns NULL (fall back to sys env cache), request 2+ returns "sapi env val". TEA_SAPI_GETENV_FUNCTION(ini_sapi_getenv_from_sapi) { zai_str key = ZAI_STR_NEW(name, name_len); if (zai_str_eq(key, ZAI_STRL("INI_FOO_STRING"))) { - int call = sapi_getenv_request_count.fetch_add(1); - if (call == 0) { + if (sapi_getenv_request_num.load() < 2) { return NULL; } memset(sapi_getenv_test_buf, 0, sizeof(sapi_getenv_test_buf)); @@ -584,12 +591,14 @@ TEA_SAPI_GETENV_FUNCTION(ini_sapi_getenv_from_sapi) { } TEST_INI("SAPI env takes priority over cache", { - sapi_getenv_request_count.store(0); + sapi_getenv_request_num.store(0); + ext_zai_config_pre_rinit = sapi_getenv_advance_request; REQUIRE_SETENV("INI_FOO_STRING", "system env val"); tea_sapi_module.getenv = ini_sapi_getenv_from_sapi; }, { REQUEST_BEGIN() + // Request 1: pre_rinit bumped request_num to 1; SAPI returns NULL → falls back to cached sys env. zval *value = zai_config_get_value(EXT_CFG_INI_FOO_STRING); REQUIRE_ZVAL_STRING_EQ(value, "system env val"); @@ -598,6 +607,7 @@ TEST_INI("SAPI env takes priority over cache", { REQUEST_BEGIN() + // Request 2: pre_rinit bumped request_num to 2; SAPI returns "sapi env val" → overrides cache. zval *value = zai_config_get_value(EXT_CFG_INI_FOO_STRING); REQUIRE_ZVAL_STRING_EQ(value, "sapi env val"); @@ -605,4 +615,5 @@ TEST_INI("SAPI env takes priority over cache", { REQUEST_END() tea_sapi_module.getenv = NULL; + ext_zai_config_pre_rinit = NULL; }) diff --git a/zend_abstract_interface/env/env.c b/zend_abstract_interface/env/env.c index c6071dfca2b..374ee7261a7 100644 --- a/zend_abstract_interface/env/env.c +++ b/zend_abstract_interface/env/env.c @@ -1,7 +1,4 @@ -#include "../tsrmls_cache.h" #include
-#include
-#include #include "env.h" @@ -11,52 +8,10 @@ #define sapi_getenv_compat(name, name_len) sapi_getenv((char *)name, name_len) #endif -zai_env_result zai_getenv_ex(zai_str name, zai_env_buffer *buf, bool pre_rinit, bool use_process_env) { - if (!buf || !buf->ptr || !buf->len) return ZAI_ENV_ERROR; - - char *scratch = buf->ptr; - size_t scratch_len = buf->len; - - if (zai_str_is_empty(name)) return ZAI_ENV_ERROR; - - if (scratch_len > ZAI_ENV_MAX_BUFSIZ) return ZAI_ENV_BUFFER_TOO_BIG; - - /* Some SAPIs do not initialize the SAPI-controlled environment variables - * until SAPI RINIT. It is for this reason we cannot reliably access - * environment variables until module RINIT. - */ - if (!pre_rinit && !PG(modules_activated) && !PG(during_request_startup)) return ZAI_ENV_NOT_READY; - - /* sapi_getenv may or may not include process environment variables. - * It will return NULL when it is not found in the possibly synthetic SAPI environment. - */ - char *sapi_value = sapi_getenv_compat(name.ptr, name.len); - if (sapi_value) { - size_t sapi_value_len = strlen(sapi_value); - zai_env_result res; - if (sapi_value_len < scratch_len) { - memcpy(scratch, sapi_value, sapi_value_len + 1); - buf->ptr = scratch; - buf->len = sapi_value_len; - res = ZAI_ENV_SUCCESS; - } else { - res = ZAI_ENV_BUFFER_TOO_SMALL; - } - efree(sapi_value); - return res; - } - - if (!use_process_env) return ZAI_ENV_NOT_SET; - - char *process_value = getenv(name.ptr); - if (!process_value) return ZAI_ENV_NOT_SET; - - size_t process_value_len = strlen(process_value); - if (process_value_len < scratch_len) { - memcpy(scratch, process_value, process_value_len + 1); - buf->ptr = scratch; - buf->len = process_value_len; - return ZAI_ENV_SUCCESS; +zai_option_str zai_sapi_getenv(zai_str name) { + char *value = sapi_getenv_compat(name.ptr, name.len); + if (value) { + return zai_option_str_from_raw_parts(value, strlen(value)); } - return ZAI_ENV_BUFFER_TOO_SMALL; + return ZAI_OPTION_STR_NONE; } diff --git a/zend_abstract_interface/env/env.h b/zend_abstract_interface/env/env.h index 4e18ff092d3..b894226bc5f 100644 --- a/zend_abstract_interface/env/env.h +++ b/zend_abstract_interface/env/env.h @@ -3,8 +3,8 @@ #include -#include -#include +#include +#include /* The upper-bounds limit on the buffer size to hold the value of an arbitrary * environment variable. @@ -48,27 +48,24 @@ typedef struct zai_env_buffer_s { char name##_storage[size] = {0}; \ zai_env_buffer name = {size, name##_storage} -/* Resolves a target environment variable identified by 'name'. Must be called - * after the SAPI envrionment variables are available - * which is as early as module RINIT. If the active SAPI has a custom - * environment variable handler, the SAPI handler is used to access the - * environment variable. If there is no custom handler, the environment variable - * is accessed from the host using getenv(), unless use_process_env is false. +/** + * Borrows the environment variable from the SAPI--it will not check the + * system environment variables. * - * For SAPI values, this writes into the caller scratch buffer (`buf->ptr`). - * For process getenv() values, this may repoint `buf->ptr` to borrowed process - * env storage to avoid a temporary copy. - * - * For error conditions, a return value other than ZAI_ENV_SUCCESS is returned. - * No output-buffer contents are guaranteed on failure. If callers want an - * empty C-string on failure, they should initialize `buf->ptr[0] = '\\0'` - * before calling this API (when `buf->len > 0`). + * This mostly only makes sense to use during a request. */ -zai_env_result zai_getenv_ex(zai_str name, zai_env_buffer *buf, bool pre_rinit, bool use_process_env); -static inline zai_env_result zai_getenv(zai_str name, zai_env_buffer *buf) { - return zai_getenv_ex(name, buf, false, true); -} +zai_option_str zai_sapi_getenv(zai_str name); -#define zai_getenv_literal(name, buf) zai_getenv(ZAI_STRL(name), &(buf)) +/** + * Borrows the environment variable from the system--it will not check the + * SAPI environment variables. + */ +static inline zai_option_str zai_sys_getenv(zai_str name) { + char *value = getenv(name.ptr); + if (value) { + return zai_option_str_from_raw_parts(value, strlen(value)); + } + return ZAI_OPTION_STR_NONE; +} #endif // ZAI_ENV_H diff --git a/zend_abstract_interface/env/tests/CMakeLists.txt b/zend_abstract_interface/env/tests/CMakeLists.txt index edaef7c1971..e0aa5891121 100644 --- a/zend_abstract_interface/env/tests/CMakeLists.txt +++ b/zend_abstract_interface/env/tests/CMakeLists.txt @@ -1,4 +1,4 @@ -add_executable(env host.cc error.cc sapi.cc) +add_executable(env host.cc sapi.cc) target_link_libraries(env PUBLIC catch2_main Tea::Tea Zai::Env) diff --git a/zend_abstract_interface/env/tests/error.cc b/zend_abstract_interface/env/tests/error.cc deleted file mode 100644 index aca1273a677..00000000000 --- a/zend_abstract_interface/env/tests/error.cc +++ /dev/null @@ -1,45 +0,0 @@ -extern "C" { -#include "env/env.h" -#include "tea/sapi.h" -#include "tea/extension.h" -} - -#include "zai_tests_common.hpp" - -TEA_TEST_CASE_WITH_PROLOGUE("env/error", "zero name len", { - REQUIRE(tea_sapi_module.getenv == NULL); -},{ - REQUIRE_UNSETENV("FOO"); - - ZAI_ENV_BUFFER_INIT(buf, 64); - strcpy(buf.ptr, "foo"); - zai_env_result res = zai_getenv_literal("", buf); - - REQUIRE(res == ZAI_ENV_ERROR); - REQUIRE_BUF_EQ("foo", buf); -}) - -TEA_TEST_CASE_WITH_PROLOGUE("env/error", "NULL buffer", { - REQUIRE(tea_sapi_module.getenv == NULL); -},{ - REQUIRE_UNSETENV("FOO"); - - ZAI_ENV_BUFFER_INIT(buf, 64); - buf.ptr = NULL; - zai_env_result res = zai_getenv_literal("FOO", buf); - - REQUIRE(res == ZAI_ENV_ERROR); -}) - -TEA_TEST_CASE_WITH_PROLOGUE("env/error", "zero buffer size", { - REQUIRE(tea_sapi_module.getenv == NULL); -},{ - REQUIRE_UNSETENV("FOO"); - - ZAI_ENV_BUFFER_INIT(buf, 64); - buf.len = 0; - zai_env_result res = zai_getenv_literal("FOO", buf); - - REQUIRE(res == ZAI_ENV_ERROR); -}) - diff --git a/zend_abstract_interface/env/tests/host.cc b/zend_abstract_interface/env/tests/host.cc index 273bbe5b91e..1509fecda93 100644 --- a/zend_abstract_interface/env/tests/host.cc +++ b/zend_abstract_interface/env/tests/host.cc @@ -5,17 +5,35 @@ extern "C" { } #include "zai_tests_common.hpp" +#include + +static std::string zai_option_str_format(zai_option_str opt) { + zai_str view; + if (zai_option_str_get(opt, &view)) { + return std::string("Some(\"") + std::string(view.ptr, view.len) + "\")"; + } else { + return "None"; + } +} + +#define REQUIRE_OPTION_STR_EQ(actual, expected) \ + do { \ + zai_option_str _req_opt_actual = (actual); \ + zai_option_str _req_opt_expected = (expected); \ + INFO("comparing " << zai_option_str_format(_req_opt_expected) << " and " << zai_option_str_format(_req_opt_actual)); \ + REQUIRE(zai_option_str_eq(_req_opt_actual, _req_opt_expected)); \ + } while (0) TEA_TEST_CASE_WITH_PROLOGUE("env/host", "non-empty string", { REQUIRE(tea_sapi_module.getenv == NULL); },{ REQUIRE_SETENV("FOO", "bar"); - ZAI_ENV_BUFFER_INIT(buf, 64); - zai_env_result res = zai_getenv_literal("FOO", buf); + zai_option_str opt = zai_sys_getenv(ZAI_STRL("FOO")); + REQUIRE_OPTION_STR_EQ(opt, ZAI_OPTION_STRL("bar")); - REQUIRE(res == ZAI_ENV_SUCCESS); - REQUIRE_BUF_EQ("bar", buf); + opt = zai_sapi_getenv(ZAI_STRL("FOO")); + REQUIRE_OPTION_STR_EQ(opt, ZAI_OPTION_STR_NONE); }) TEA_TEST_CASE_WITH_PROLOGUE("env/host", "empty string", { @@ -23,11 +41,11 @@ TEA_TEST_CASE_WITH_PROLOGUE("env/host", "empty string", { },{ REQUIRE_SETENV("FOO", ""); - ZAI_ENV_BUFFER_INIT(buf, 64); - zai_env_result res = zai_getenv_literal("FOO", buf); + zai_option_str opt = zai_sys_getenv(ZAI_STRL("FOO")); + REQUIRE_OPTION_STR_EQ(opt, ZAI_OPTION_STRL("")); - REQUIRE(res == ZAI_ENV_SUCCESS); - REQUIRE_BUF_EQ("", buf); + opt = zai_sapi_getenv(ZAI_STRL("FOO")); + REQUIRE_OPTION_STR_EQ(opt, ZAI_OPTION_STR_NONE); }) TEA_TEST_CASE_WITH_PROLOGUE("env/host", "not set", { @@ -35,47 +53,11 @@ TEA_TEST_CASE_WITH_PROLOGUE("env/host", "not set", { },{ REQUIRE_UNSETENV("FOO"); - ZAI_ENV_BUFFER_INIT(buf, 64); - zai_env_result res = zai_getenv_literal("FOO", buf); - - REQUIRE(res == ZAI_ENV_NOT_SET); - REQUIRE_BUF_EQ("", buf); -}) - -TEA_TEST_CASE_WITH_PROLOGUE("env/host", "max buffer size", { - REQUIRE(tea_sapi_module.getenv == NULL); -},{ - REQUIRE_SETENV("FOO", "bar"); - - ZAI_ENV_BUFFER_INIT(buf, ZAI_ENV_MAX_BUFSIZ); - zai_env_result res = zai_getenv_literal("FOO", buf); - - REQUIRE(res == ZAI_ENV_SUCCESS); - REQUIRE_BUF_EQ("bar", buf); -}) - -TEA_TEST_CASE_WITH_PROLOGUE("env/host", "buffer too small", { - REQUIRE(tea_sapi_module.getenv == NULL); -},{ - REQUIRE_SETENV("FOO", "bar"); - - ZAI_ENV_BUFFER_INIT(buf, 3); // No room for null terminator - zai_env_result res = zai_getenv_literal("FOO", buf); - - REQUIRE(res == ZAI_ENV_BUFFER_TOO_SMALL); - REQUIRE_BUF_EQ("", buf); -}) - -TEA_TEST_CASE_WITH_PROLOGUE("env/host", "buffer too big", { - REQUIRE(tea_sapi_module.getenv == NULL); -},{ - REQUIRE_SETENV("FOO", "bar"); - - ZAI_ENV_BUFFER_INIT(buf, ZAI_ENV_MAX_BUFSIZ + 1); - zai_env_result res = zai_getenv_literal("FOO", buf); + zai_option_str opt = zai_sys_getenv(ZAI_STRL("FOO")); + REQUIRE_OPTION_STR_EQ(opt, ZAI_OPTION_STR_NONE); - REQUIRE(res == ZAI_ENV_BUFFER_TOO_BIG); - REQUIRE_BUF_EQ("", buf); + opt = zai_sapi_getenv(ZAI_STRL("FOO")); + REQUIRE_OPTION_STR_EQ(opt, ZAI_OPTION_STR_NONE); }) TEA_TEST_CASE_BARE("env/host", "outside request context", { @@ -85,11 +67,11 @@ TEA_TEST_CASE_BARE("env/host", "outside request context", { REQUIRE_SETENV("FOO", "bar"); - ZAI_ENV_BUFFER_INIT(buf, 64); - zai_env_result res = zai_getenv_literal("FOO", buf); + zai_option_str opt = zai_sys_getenv(ZAI_STRL("FOO")); + REQUIRE_OPTION_STR_EQ(opt, ZAI_OPTION_STRL("bar")); - REQUIRE(res == ZAI_ENV_NOT_READY); - REQUIRE_BUF_EQ("", buf); + opt = zai_sapi_getenv(ZAI_STRL("FOO")); + REQUIRE_OPTION_STR_EQ(opt, ZAI_OPTION_STR_NONE); TEA_TEST_CASE_WITHOUT_BAILOUT_END() tea_sapi_mshutdown(); diff --git a/zend_abstract_interface/env/tests/sapi.cc b/zend_abstract_interface/env/tests/sapi.cc index bb40968e8b1..1dc0a648918 100644 --- a/zend_abstract_interface/env/tests/sapi.cc +++ b/zend_abstract_interface/env/tests/sapi.cc @@ -5,6 +5,7 @@ extern "C" { } #include "zai_tests_common.hpp" +#include #if PHP_VERSION_ID >= 80000 #define TEA_SAPI_GETENV_FUNCTION(fn) static char *fn(const char *name, size_t name_len) @@ -12,6 +13,22 @@ extern "C" { #define TEA_SAPI_GETENV_FUNCTION(fn) static char *fn(char *name, size_t name_len) #endif +static std::string zai_option_str_format(zai_option_str opt) { + zai_str view; + if (zai_option_str_get(opt, &view)) { + return std::string("Some(\"") + std::string(view.ptr, view.len) + "\")"; + } + return "None"; +} + +#define REQUIRE_OPTION_STR_EQ(actual, expected) \ + do { \ + zai_option_str _req_opt_actual = (actual); \ + zai_option_str _req_opt_expected = (expected); \ + INFO("comparing " << zai_option_str_format(_req_opt_expected) << " and " << zai_option_str_format(_req_opt_actual)); \ + REQUIRE(zai_option_str_eq(_req_opt_actual, _req_opt_expected)); \ + } while (0) + static char zai_str_buf[64]; // Returns the name of the env var as the value @@ -23,11 +40,8 @@ TEA_SAPI_GETENV_FUNCTION(tea_sapi_getenv_non_empty) { TEA_TEST_CASE_WITH_PROLOGUE("env/sapi", "non-empty string", { tea_sapi_module.getenv = tea_sapi_getenv_non_empty; },{ - ZAI_ENV_BUFFER_INIT(buf, 64); - zai_env_result res = zai_getenv_literal("FOO", buf); - - REQUIRE(res == ZAI_ENV_SUCCESS); - REQUIRE_BUF_EQ("FOO", buf); + zai_option_str opt = zai_sapi_getenv(ZAI_STRL("FOO")); + REQUIRE_OPTION_STR_EQ(opt, ZAI_OPTION_STRL("FOO")); }) TEA_TEST_CASE_WITH_PROLOGUE("env/sapi", "non-empty string (no host env fallback)", { @@ -35,11 +49,8 @@ TEA_TEST_CASE_WITH_PROLOGUE("env/sapi", "non-empty string (no host env fallback) },{ REQUIRE_SETENV("FOO", "bar"); - ZAI_ENV_BUFFER_INIT(buf, 64); - zai_env_result res = zai_getenv_literal("FOO", buf); - - REQUIRE(res == ZAI_ENV_SUCCESS); - REQUIRE_BUF_EQ("FOO", buf); + zai_option_str opt = zai_sapi_getenv(ZAI_STRL("FOO")); + REQUIRE_OPTION_STR_EQ(opt, ZAI_OPTION_STRL("FOO")); }) TEA_SAPI_GETENV_FUNCTION(tea_sapi_getenv_null) { return NULL; } @@ -48,11 +59,9 @@ TEA_TEST_CASE_WITH_PROLOGUE("env/sapi", "not set", { tea_sapi_module.getenv = tea_sapi_getenv_null; },{ REQUIRE_UNSETENV("FOO"); - ZAI_ENV_BUFFER_INIT(buf, 64); - zai_env_result res = zai_getenv_literal("FOO", buf); - REQUIRE(res == ZAI_ENV_NOT_SET); - REQUIRE_BUF_EQ("", buf); + zai_option_str opt = zai_sapi_getenv(ZAI_STRL("FOO")); + REQUIRE_OPTION_STR_EQ(opt, ZAI_OPTION_STR_NONE); }) TEA_TEST_CASE_WITH_PROLOGUE("env/sapi", "not set (with host env fallback)", { @@ -60,17 +69,17 @@ TEA_TEST_CASE_WITH_PROLOGUE("env/sapi", "not set (with host env fallback)", { },{ REQUIRE_SETENV("FOO", "bar"); - ZAI_ENV_BUFFER_INIT(buf, 64); - zai_env_result res = zai_getenv_literal("FOO", buf); + zai_option_str opt = zai_sapi_getenv(ZAI_STRL("FOO")); + REQUIRE_OPTION_STR_EQ(opt, ZAI_OPTION_STR_NONE); - REQUIRE(res == ZAI_ENV_SUCCESS); - REQUIRE_BUF_EQ("bar", buf); + opt = zai_sys_getenv(ZAI_STRL("FOO")); + REQUIRE_OPTION_STR_EQ(opt, ZAI_OPTION_STRL("bar")); }) /****************************** Access from RINIT *****************************/ -zai_env_result zai_rinit_last_res; static char zai_rinit_str_buf[64]; +static bool zai_rinit_got_value; static PHP_RINIT_FUNCTION(zai_env) { #if PHP_VERSION_ID >= 80000 @@ -80,8 +89,18 @@ static PHP_RINIT_FUNCTION(zai_env) { #endif zend_try { - zai_env_buffer buf = {sizeof zai_rinit_str_buf, zai_rinit_str_buf}; - zai_rinit_last_res = zai_getenv_literal("FROM_RINIT", buf); + zai_option_str opt = zai_sapi_getenv(ZAI_STRL("FROM_RINIT")); + if (zai_option_str_is_none(opt)) { + opt = zai_sys_getenv(ZAI_STRL("FROM_RINIT")); + } + zai_rinit_got_value = zai_option_str_is_some(opt); + if (zai_rinit_got_value) { + zai_str v; + zai_option_str_get(opt, &v); + size_t n = v.len < sizeof(zai_rinit_str_buf) - 1 ? v.len : sizeof(zai_rinit_str_buf) - 1; + memcpy(zai_rinit_str_buf, v.ptr, n); + zai_rinit_str_buf[n] = '\0'; + } } zend_catch { result = FAILURE; } zend_end_try(); @@ -92,23 +111,23 @@ static PHP_RINIT_FUNCTION(zai_env) { TEA_TEST_CASE_WITH_PROLOGUE("env/sapi", "rinit non-empty string", { tea_sapi_module.getenv = tea_sapi_getenv_non_empty; - zai_rinit_last_res = ZAI_ENV_ERROR; + zai_rinit_got_value = false; zai_rinit_str_buf[0] = '\0'; tea_extension_rinit(PHP_RINIT(zai_env)); },{ - REQUIRE(zai_rinit_last_res == ZAI_ENV_SUCCESS); + REQUIRE(zai_rinit_got_value); REQUIRE(0 == strcmp("FROM_RINIT", zai_rinit_str_buf)); }) TEA_TEST_CASE_WITH_TAGS_WITH_PROLOGUE("env/host", "rinit non-empty string", "[adopted][env/sapi]", { tea_sapi_module.getenv = NULL; - zai_rinit_last_res = ZAI_ENV_ERROR; + REQUIRE_SETENV("FROM_RINIT", "bar"); + + zai_rinit_got_value = false; zai_rinit_str_buf[0] = '\0'; tea_extension_rinit(PHP_RINIT(zai_env)); - - REQUIRE_SETENV("FROM_RINIT", "bar"); },{ - REQUIRE(zai_rinit_last_res == ZAI_ENV_SUCCESS); + REQUIRE(zai_rinit_got_value); REQUIRE(0 == strcmp("bar", zai_rinit_str_buf)); }) diff --git a/zend_abstract_interface/zai_string/string.h b/zend_abstract_interface/zai_string/string.h index 4680314b69b..894500e17cc 100644 --- a/zend_abstract_interface/zai_string/string.h +++ b/zend_abstract_interface/zai_string/string.h @@ -129,6 +129,8 @@ typedef struct zai_option_str_s { #define ZAI_OPTION_STR_NONE \ (zai_option_str) {.ptr = NULL, .len = 0} +#define ZAI_OPTION_STRL(literal) \ + zai_option_str_from_raw_parts("" literal, sizeof(literal) - 1) /** * Creates a zai_option_str from the given `ptr` and `len`. If `ptr` is null, * then it will be a None. @@ -181,6 +183,13 @@ bool zai_option_str_get(zai_option_str self, zai_str *view) { return self.ptr; } +static inline bool zai_option_str_eq(zai_option_str a, zai_option_str b) { + zai_str x, y; + bool ba = zai_option_str_get(a, &x); + bool bb = zai_option_str_get(b, &y); + return ba == bb && (ba ? zai_str_eq(x, y) : true); +} + /** * Represents a owned string. *