diff --git a/ext/otel_config.c b/ext/otel_config.c index c9504a0cabb..d9bfae54888 100644 --- a/ext/otel_config.c +++ b/ext/otel_config.c @@ -20,22 +20,22 @@ 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) { - return true; - } +static bool get_otel_value(zai_str str, zai_env_buffer *buf, bool pre_rinit) { + if (!pre_rinit && zai_sapi_getenv(str, buf) == ZAI_ENV_SUCCESS) return true; + zai_option_str sys = zai_sys_getenv(str); + if (zai_option_str_is_some(sys)) { buf->ptr = sys.ptr; buf->len = sys.len; return true; } zval *cfg = cfg_get_entry(str.ptr, str.len); 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,13 @@ 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) { - if (!get_otel_value((zai_str)ZAI_STRL("OTEL_RESOURCE_ATTRIBUTES"), buf, pre_rinit)) { +static bool ddtrace_conf_otel_resource_attributes_special(const char *tag, int len, zai_env_buffer *buf, bool pre_rinit) { + ZAI_ENV_BUFFER_INIT(local, ZAI_ENV_MAX_BUFSIZ); + if (!get_otel_value((zai_str)ZAI_STRL("OTEL_RESOURCE_ATTRIBUTES"), &local, pre_rinit)) { return false; } - for (char *cur = buf.ptr, *key_start = cur; *cur; ++cur) { + for (char *cur = local.ptr, *key_start = cur; *cur; ++cur) { if (*cur == '=') { char *key_end = cur++; while (*cur && *cur != ',') { @@ -67,8 +68,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 +79,95 @@ 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) { - if (!get_otel_value((zai_str)ZAI_STRL("OTEL_PROPAGATORS"), buf, pre_rinit)) { +bool ddtrace_conf_otel_propagators(zai_env_buffer *buf, bool pre_rinit) { + ZAI_ENV_BUFFER_INIT(local, ZAI_ENV_MAX_BUFSIZ); + if (!get_otel_value((zai_str)ZAI_STRL("OTEL_PROPAGATORS"), &local, 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); + memcpy(buf->ptr, local.ptr, strlen(local.ptr) + 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) { + buf->ptr = "1"; buf->len = 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) { + buf->ptr = "0"; buf->len = 1; 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) { + buf->ptr = "0"; buf->len = 1; 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) { + buf->ptr = "0"; buf->len = 1; 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) { - if (!get_otel_value((zai_str)ZAI_STRL("OTEL_RESOURCE_ATTRIBUTES"), buf, pre_rinit)) { +bool ddtrace_conf_otel_resource_attributes_tags(zai_env_buffer *buf, bool pre_rinit) { + ZAI_ENV_BUFFER_INIT(local, ZAI_ENV_MAX_BUFSIZ); + if (!get_otel_value((zai_str)ZAI_STRL("OTEL_RESOURCE_ATTRIBUTES"), &local, 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 = local.ptr, *key_start = cur; *cur; ++cur) { if (*cur == '=') { char *key = key_start, *key_end = cur++; while (*cur && *cur != ',') { @@ -190,7 +194,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 961e7765dd9..02355bf6bd9 100644 --- a/zend_abstract_interface/config/config.c +++ b/zend_abstract_interface/config/config.c @@ -3,6 +3,7 @@ #include #include #include
+#include
#include #include #include @@ -12,11 +13,34 @@ 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) { - // 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; +// Indexed [config_id][name_index]; NULL means not set. +static char *zai_config_cached_sys_env[ZAI_CONFIG_ENTRIES_COUNT_MAX][ZAI_CONFIG_NAMES_COUNT_MAX]; + +const char *zai_config_sys_env_cached(zai_config_id id, uint8_t name_index) { + return zai_config_cached_sys_env[id][name_index]; +} + +static void zai_config_cache_sys_env(void) { + for (zai_config_id i = 0; i < zai_config_memoized_entries_count; i++) { + zai_config_memoized_entry *m = &zai_config_memoized_entries[i]; + for (uint8_t n = 0; n < m->names_count; n++) { + zai_str name = ZAI_STR_NEW(m->names[n].ptr, m->names[n].len); + zai_option_str val = zai_sys_getenv(name); + zai_config_cached_sys_env[i][n] = zai_option_str_is_some(val) + ? pestrdup(val.ptr, 1) : NULL; + } + } +} + +static void zai_config_clear_sys_env_cache(void) { + for (zai_config_id i = 0; i < zai_config_memoized_entries_count; i++) { + for (uint8_t n = 0; n < zai_config_memoized_entries[i].names_count; n++) { + if (zai_config_cached_sys_env[i][n]) { + pefree(zai_config_cached_sys_env[i][n], 1); + zai_config_cached_sys_env[i][n] = NULL; + } + } + } } static inline void zai_config_process_env(zai_config_memoized_entry *memoized, zai_env_buffer buf, zai_option_str *value) { @@ -31,7 +55,7 @@ static inline void zai_config_process_env(zai_config_memoized_entry *memoized, z } } -static void zai_config_find_and_set_value(zai_config_memoized_entry *memoized, zai_config_id id) { +static void zai_config_find_and_set_value(zai_config_memoized_entry *memoized, zai_config_id id, bool in_request) { // TODO Use less buffer space // TODO Make a more generic zai_string_buffer ZAI_ENV_BUFFER_INIT(buf, ZAI_ENV_MAX_BUFSIZ); @@ -48,10 +72,26 @@ 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)) { - zai_config_process_env(memoized, buf, &value); - break; - } else if (entry && entry->source == DDOG_LIBRARY_CONFIG_SOURCE_LOCAL_STABLE_CONFIG) { + } else { + // SAPI env (e.g. Apache SetEnv) takes priority over the sys env + // cache and must be checked here at first RINIT, not only in + // zai_config_ini_rinit. Code that runs between first_time_rinit + // and zai_config_ini_rinit--such as the signal handler setup that + // reads DD_TRACE_HEALTH_METRICS_ENABLED--relies on SAPI-provided + // values being present in the decoded config. + if (in_request && zai_sapi_getenv(name, &buf) == ZAI_ENV_SUCCESS) { + zai_config_process_env(memoized, buf, &value); + break; + } + const char *cached = zai_config_sys_env_cached(id, name_index); + if (cached) { + buf.ptr = (char *)cached; + buf.len = strlen(cached); + zai_config_process_env(memoized, buf, &value); + break; + } + } + 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); name_index = ZAI_CONFIG_ORIGIN_LOCAL_STABLE; @@ -59,7 +99,7 @@ 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)) { + 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; } @@ -153,6 +193,7 @@ bool zai_config_minit(zai_config_entry entries[], size_t entries_count, zai_conf #if PHP_VERSION_ID >= 70300 && PHP_VERSION_ID < 70400 zai_persistent_new_interned_string = zend_new_interned_string; #endif + zai_config_cache_sys_env(); return true; } @@ -164,11 +205,13 @@ static void zai_config_dtor_memoized_zvals(void) { void zai_config_mshutdown(void) { zai_config_dtor_memoized_zvals(); + zai_config_memoized_entries_count = 0; if (zai_config_name_map.nTableSize) { zend_hash_destroy(&zai_config_name_map); } zai_config_ini_mshutdown(); zai_config_stable_file_mshutdown(); + zai_config_clear_sys_env_cache(); } void zai_config_runtime_config_ctor(void); @@ -237,9 +280,16 @@ void zai_config_first_time_rinit(bool in_request) { (void)in_request; #endif + // Non-CLI SAPIs (CGI/FPM/mod_php) may inject env vars before the first + // request, so refresh the cache to pick them up. + if (in_request && strcmp(sapi_module.name, "cli") != 0) { + zai_config_clear_sys_env_cache(); + zai_config_cache_sys_env(); + } + 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 3041136179e..8bbc08d60aa 100644 --- a/zend_abstract_interface/config/config.h +++ b/zend_abstract_interface/config/config.h @@ -110,6 +110,9 @@ zval *zai_config_get_value(zai_config_id id); bool zai_config_get_id_by_name(zai_str name, zai_config_id *id); +// Returns NULL if not set; persistent allocation, must not be freed. +const char *zai_config_sys_env_cached(zai_config_id id, uint8_t name_index); + // Adds name to name<->id mapping. Id may be present multiple times. void zai_config_register_config_id(zai_config_name *name, zai_config_id id); diff --git a/zend_abstract_interface/config/config_ini.c b/zend_abstract_interface/config/config_ini.c index 06f03f711c8..888271399cd 100644 --- a/zend_abstract_interface/config/config_ini.c +++ b/zend_abstract_interface/config/config_ini.c @@ -427,9 +427,8 @@ void zai_config_ini_rinit(void) { } #endif - ZAI_ENV_BUFFER_INIT(buf, ZAI_ENV_MAX_BUFSIZ); - for (uint16_t i = 0; i < zai_config_memoized_entries_count; ++i) { + ZAI_ENV_BUFFER_INIT(buf, ZAI_ENV_MAX_BUFSIZ); zai_config_memoized_entry *memoized = &zai_config_memoized_entries[i]; if (memoized->ini_change == zai_config_system_ini_change) { continue; @@ -446,10 +445,19 @@ 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_sapi_getenv(name, &buf) == ZAI_ENV_SUCCESS && 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 + } else { + const char *cached = zai_config_sys_env_cached(i, name_index); + if (cached) { + zai_env_buffer cached_buf = {strlen(cached), (char *)cached}; + if (zai_config_process_runtime_env(memoized, cached_buf, in_startup, i, name_index)) { + goto next_entry; + } + } + } + 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)) { memoized->name_index = ZAI_CONFIG_ORIGIN_LOCAL_STABLE; @@ -458,7 +466,7 @@ 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)) { + 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/config/tests/CMakeLists.txt b/zend_abstract_interface/config/tests/CMakeLists.txt index 40b98909b5d..15cebf8391e 100644 --- a/zend_abstract_interface/config/tests/CMakeLists.txt +++ b/zend_abstract_interface/config/tests/CMakeLists.txt @@ -1,8 +1,67 @@ -add_executable(config ext_zai_config.cc default.cc env.cc id.cc decode.cc ini.cc) +# --- Second consumer as an isolated shared library --- +# Compiles all zai_config sources into the .so so it gets its own independent globals. +# PHP/Tea symbols are NOT linked (not PIC); they resolve from the main binary at load time. +add_library(second_consumer_ext MODULE + ext_second_consumer.cc + ${PROJECT_SOURCE_DIR}/config/config.c + ${PROJECT_SOURCE_DIR}/config/config_decode.c + ${PROJECT_SOURCE_DIR}/config/config_ini.c + ${PROJECT_SOURCE_DIR}/config/config_stable_file.c + ${PROJECT_SOURCE_DIR}/config/config_runtime.c +) + +set_target_properties(second_consumer_ext PROPERTIES + POSITION_INDEPENDENT_CODE ON + PREFIX "" +) + +# Headers only — do not link any static library (they aren't PIC). +target_include_directories(second_consumer_ext PRIVATE + ${PROJECT_SOURCE_DIR} + ${PROJECT_SOURCE_DIR}/.. + $ + $ + $ + $ + $ +) + +# PHP 7.0 headers (e.g. zend_string.h) use the `register` storage class specifier, +# which was removed in C++17. Cap at C++14 where it is only deprecated, not an error. +set_target_properties(second_consumer_ext PROPERTIES + CXX_STANDARD 14 + CXX_STANDARD_REQUIRED YES +) +target_compile_features(second_consumer_ext PRIVATE c_std_11) + +# Compile definitions from Tea::Php (e.g. ZEND_DEBUG, ZTS) must match the main binary's build. +target_compile_definitions(second_consumer_ext PRIVATE + $ +) + +# Allow PHP/Tea/Zai symbols to remain undefined at link time; resolved at load time. +# macOS ld uses -undefined dynamic_lookup; GNU ld uses --unresolved-symbols=ignore-all. +target_link_options(second_consumer_ext PRIVATE + $<$:-undefined dynamic_lookup> + $<$:-Wl,--unresolved-symbols=ignore-all> +) + +add_executable(config + ext_zai_config.cc + # ext_second_consumer.cc removed — it is now the second_consumer_ext MODULE + default.cc env.cc id.cc decode.cc ini.cc +) set(THREADS_PREFER_PTHREAD_FLAG ON) find_package(Threads REQUIRED) target_link_libraries(config PUBLIC catch2_main Tea::Tea Zai::Config Threads::Threads) +# Make the .so path available at compile time. +target_compile_definitions(config PRIVATE + SECOND_CONSUMER_SO_PATH="$" +) + +add_dependencies(config second_consumer_ext) + catch_discover_tests(config) diff --git a/zend_abstract_interface/config/tests/env.cc b/zend_abstract_interface/config/tests/env.cc index 6fe9b63d913..8e26f500329 100644 --- a/zend_abstract_interface/config/tests/env.cc +++ b/zend_abstract_interface/config/tests/env.cc @@ -169,7 +169,7 @@ TEST_ENV("alias", { REQUEST_END() }) -TEA_TEST_CASE_BARE("config/env", "change after memoization", { +TEA_TEST_CASE_BARE("config/env", "sys env change after memoization is not reflected", { REQUIRE(tea_sapi_sinit()); ext_zai_config_ctor(PHP_MINIT(zai_config_env)); REQUIRE_SETENV("FOO_BOOL", "false"); @@ -195,12 +195,13 @@ TEA_TEST_CASE_BARE("config/env", "change after memoization", { zval *value = zai_config_get_value(EXT_CFG_FOO_BOOL); + // Sys env is cached at MINIT; changes between requests are not picked up. 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/ext_second_consumer.cc b/zend_abstract_interface/config/tests/ext_second_consumer.cc new file mode 100644 index 00000000000..f37e000165b --- /dev/null +++ b/zend_abstract_interface/config/tests/ext_second_consumer.cc @@ -0,0 +1,65 @@ +extern "C" { +#include "config/config.h" +#include "tea/extension.h" +} + +#include + +static void ext_second_consumer_env_to_ini_name(zai_str env_name, zai_config_name *ini_name) { + int len = snprintf(ini_name->ptr, ZAI_CONFIG_NAME_BUFSIZ, "zai_config.%s", env_name.ptr); + ini_name->len = (len > 0 && len < ZAI_CONFIG_NAME_BUFSIZ) ? (size_t)len : 0; +} + +typedef enum { + EXT_CFG_SC_INI_FOO_STRING, + EXT_CFG_SC_INI_FOO_INT, +} ext_second_consumer_cfg_id; + +static std::once_flag sc_first_rinit_once; + +static PHP_MINIT_FUNCTION(second_consumer) { + new (&sc_first_rinit_once) std::once_flag{}; + zai_config_entry entries[] = { + ZAI_CONFIG_ENTRY(EXT_CFG_SC_INI_FOO_STRING, INI_FOO_STRING, STRING, ""), + ZAI_CONFIG_ENTRY(EXT_CFG_SC_INI_FOO_INT, INI_FOO_INT, INT, "0"), + }; + if (!zai_config_minit(entries, sizeof(entries) / sizeof(entries[0]), ext_second_consumer_env_to_ini_name, module_number)) { + return FAILURE; + } + return SUCCESS; +} + +static PHP_RINIT_FUNCTION(second_consumer) { + (void)type; (void)module_number; + std::call_once(sc_first_rinit_once, zai_config_first_time_rinit, true); + zai_config_rinit(); + return SUCCESS; +} + +static PHP_RSHUTDOWN_FUNCTION(second_consumer) { + (void)type; (void)module_number; + zai_config_rshutdown(); + return SUCCESS; +} + +static PHP_MSHUTDOWN_FUNCTION(second_consumer) { + zai_config_mshutdown(); + return SUCCESS; +} + +static zend_module_entry second_consumer_module_entry = { + STANDARD_MODULE_HEADER, + "second_consumer", + NULL, /* functions */ + PHP_MINIT(second_consumer), + PHP_MSHUTDOWN(second_consumer), + PHP_RINIT(second_consumer), + PHP_RSHUTDOWN(second_consumer), + NULL, /* PHP_MINFO */ + PHP_VERSION, + STANDARD_MODULE_PROPERTIES +}; + +extern "C" ZEND_DLEXPORT zend_module_entry *get_module(void) { + return &second_consumer_module_entry; +} diff --git a/zend_abstract_interface/config/tests/ext_zai_config.cc b/zend_abstract_interface/config/tests/ext_zai_config.cc index cd0928e1bb0..429974077fd 100644 --- a/zend_abstract_interface/config/tests/ext_zai_config.cc +++ b/zend_abstract_interface/config/tests/ext_zai_config.cc @@ -4,14 +4,14 @@ extern "C" { #include "tea/extension.h" } -#include +#include -std::atomic ext_first_rinit; +static std::once_flag ext_first_rinit_once; static ext_zai_config_minit_fn ext_orig_minit; void (*ext_zai_config_pre_rinit)(); static PHP_MINIT_FUNCTION(zai_config) { - atomic_init(&ext_first_rinit, 1); + new (&ext_first_rinit_once) std::once_flag{}; return ext_orig_minit(INIT_FUNC_ARGS_PASSTHRU); } @@ -44,10 +44,7 @@ static PHP_RINIT_FUNCTION(zai_config) { ext_zai_config_pre_rinit(); } - int expected_first_rinit = 1; - if (atomic_compare_exchange_strong(&ext_first_rinit, &expected_first_rinit, 0)) { - zai_config_first_time_rinit(true); - } + std::call_once(ext_first_rinit_once, zai_config_first_time_rinit, true); zai_config_rinit(); } zend_catch { diff --git a/zend_abstract_interface/config/tests/ini.cc b/zend_abstract_interface/config/tests/ini.cc index 32b141e6957..5f44167645e 100644 --- a/zend_abstract_interface/config/tests/ini.cc +++ b/zend_abstract_interface/config/tests/ini.cc @@ -528,7 +528,6 @@ TEST_INI("setting perdir INI setting for multiple ZAI config users", { zai_config_memoized_entry *entry = &zai_config_memoized_entries[EXT_CFG_INI_FOO_STRING]; entry->original_on_modify = dummy; - zai_config_first_time_rinit(true); REQUEST_BEGIN(); zval *value = zai_config_get_value(EXT_CFG_INI_FOO_STRING); @@ -541,36 +540,95 @@ TEST_INI("setting perdir INI setting for multiple ZAI config users", { }) -TEST_INI("setting an env value after memoization for multiple ZAI config users", {}, { - REQUIRE_SETENV("INI_FOO_STRING", "value"); +static int second_consumer_sapi_phase; +#if PHP_VERSION_ID >= 80000 +static char *second_consumer_sapi_getenv(const char *name, size_t name_len) { +#else +static char *second_consumer_sapi_getenv(char *name, size_t name_len) { +#endif + (void)name_len; + if (second_consumer_sapi_phase == 0) { + if (strcmp(name, "INI_FOO_STRING") == 0) return estrdup("sapi_val"); + if (strcmp(name, "INI_FOO_INT") == 0) return estrdup("2"); + } else if (second_consumer_sapi_phase == 1) { + if (strcmp(name, "INI_FOO_INT") == 0) return estrdup("3"); + } + return NULL; +} + +// Verify three properties when a second extension has registered the same INI name (causing +// original_on_modify to be set on INI_FOO_STRING): +// 1. SAPI is consulted on every rinit, including the first; sys env cache (set at minit) is the +// fallback when SAPI returns NULL — consulting SAPI on first rinit is required for backwards +// compatibility with health metrics +// 2. SAPI env takes priority over sys env for ALL entries, including those with original_on_modify — +// the presence of a second consumer does not affect SAPI precedence +// 3. Sys env changes between requests are NOT reflected — the cache is immutable after minit (request 3) +TEA_TEST_CASE_BARE("config/ini", "second consumer extension causes original_on_modify to be set", { + REQUIRE(tea_sapi_sinit()); + tea_sapi_module.getenv = second_consumer_sapi_getenv; + // second consumer must be loaded first so its zai_config_minit registers INI entries + // before the first consumer's zai_config_minit runs; zai_config_add_ini_entry will + // then find the pre-existing entries and set original_on_modify. + // PHP loads the .so via extension= INI during php_module_startup and calls MINIT automatically. + REQUIRE(tea_sapi_append_system_ini_entry("extension", SECOND_CONSUMER_SO_PATH)); + ext_zai_config_ctor(PHP_MINIT(zai_config_ini)); + REQUIRE_SETENV("INI_FOO_STRING", "sys_val"); + REQUIRE_SETENV("INI_FOO_INT", "1"); + second_consumer_sapi_phase = 0; + REQUIRE(tea_sapi_minit()); + + // --- Request 1: SAPI consulted on first rinit; "sapi_val"/"2" win over sys cache "sys_val"/"1" --- REQUEST_BEGIN() - zval *value = zai_config_get_value(EXT_CFG_INI_FOO_STRING); + zval *str_val = zai_config_get_value(EXT_CFG_INI_FOO_STRING); + REQUIRE(str_val != NULL); + REQUIRE(Z_TYPE_P(str_val) == IS_STRING); + INFO("str_val = " << Z_STRVAL_P(str_val)); + REQUIRE(zval_string_equals(str_val, "sapi_val")); // SAPI "sapi_val" overrides sys cache "sys_val" - REQUIRE(value != NULL); - REQUIRE(Z_TYPE_P(value) == IS_STRING); - REQUIRE(zval_string_equals(value, "value")); + zval *int_val = zai_config_get_value(EXT_CFG_INI_FOO_INT); + REQUIRE(int_val != NULL); + REQUIRE(Z_TYPE_P(int_val) == IS_LONG); + REQUIRE(Z_LVAL_P(int_val) == 2); // SAPI "2" overrides sys cache "1" REQUEST_END() - REQUIRE_SETENV("INI_FOO_STRING", "value2"); + // Change sys env between requests — cache is immutable after minit, this is ignored. + REQUIRE_SETENV("INI_FOO_INT", "99"); + // SAPI phase 1: INI_FOO_INT → "3", INI_FOO_STRING → NULL + second_consumer_sapi_phase = 1; - // Something else inits zai config first - zai_config_rinit(); - zai_config_rshutdown(); + // --- Request 2: SAPI returns NULL for INI_FOO_STRING → sys cache used; SAPI "3" for INI_FOO_INT --- + 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; + zval *str_val = zai_config_get_value(EXT_CFG_INI_FOO_STRING); + REQUIRE(str_val != NULL); + REQUIRE(Z_TYPE_P(str_val) == IS_STRING); + INFO("str_val = " << Z_STRVAL_P(str_val)); + REQUIRE(zval_string_equals(str_val, "sys_val")); // SAPI returned NULL; sys cache "sys_val" used - REQUEST_BEGIN() + zval *int_val = zai_config_get_value(EXT_CFG_INI_FOO_INT); + REQUIRE(int_val != NULL); + REQUIRE(Z_TYPE_P(int_val) == IS_LONG); + REQUIRE(Z_LVAL_P(int_val) == 3); // SAPI changed to "3" - zval *value = zai_config_get_value(EXT_CFG_INI_FOO_STRING); + REQUEST_END() - REQUIRE(value != NULL); - REQUIRE(Z_TYPE_P(value) == IS_STRING); - REQUIRE(zval_string_equals(value, "value2")); + // SAPI phase 2: nothing from SAPI + second_consumer_sapi_phase = 2; + + // --- Request 3: SAPI NULL; sys change ("99") ignored; cache ("1") used --- + REQUEST_BEGIN() + + zval *int_val = zai_config_get_value(EXT_CFG_INI_FOO_INT); + REQUIRE(int_val != NULL); + REQUIRE(Z_TYPE_P(int_val) == IS_LONG); + REQUIRE(Z_LVAL_P(int_val) == 1); // SAPI NULL; cache has "1" (set at minit), not live sys "99" REQUEST_END() -}) \ No newline at end of file + + tea_sapi_mshutdown(); + tea_sapi_sshutdown(); +}) diff --git a/zend_abstract_interface/env/env.c b/zend_abstract_interface/env/env.c index 28a65a87a9e..875b5754d46 100644 --- a/zend_abstract_interface/env/env.c +++ b/zend_abstract_interface/env/env.c @@ -1,7 +1,9 @@ #include "../tsrmls_cache.h" #include
#include
+ #include +#include #include "env.h" @@ -11,45 +13,29 @@ #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) { - if (!buf.ptr || !buf.len) return ZAI_ENV_ERROR; - - buf.ptr[0] = '\0'; +zai_env_result zai_sapi_getenv(zai_str name, zai_env_buffer *buf) { + ZAI_ASSERT(buf && "zai_sapi_getenv: buf must be non-NULL"); + ZAI_ASSERT((PG(modules_activated) | PG(during_request_startup)) && "zai_sapi_getenv: must be called during or after RINIT"); - if (zai_str_is_empty(name)) return ZAI_ENV_ERROR; + // Optimize for the happy-path where the caller has valid inputs. On every + // request, every config is likely to check for a sapi-provided env var, + // so this is reasonably hot. + // Use bitwise-or and bitwise-and as appropriate to avoid excess branches + // that aren't going to happen in production. - if (buf.len > ZAI_ENV_MAX_BUFSIZ) return ZAI_ENV_BUFFER_TOO_BIG; + if (UNEXPECTED(zai_str_is_empty(name) | !buf->ptr | !buf->len)) return ZAI_ENV_ERROR; + if (UNEXPECTED(buf->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. - * 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 { - value = getenv(name.ptr); - } - if (!value) return ZAI_ENV_NOT_SET; - zai_env_result res; - - if (strlen(value) < buf.len) { - strcpy(buf.ptr, value); - res = ZAI_ENV_SUCCESS; + zai_env_result res = ZAI_ENV_SUCCESS; + if (EXPECTED(strlen(value) < buf->len)) { + strcpy(buf->ptr, value); } else { res = ZAI_ENV_BUFFER_TOO_SMALL; } - if (use_sapi_env) efree(value); - + efree(value); return res; } diff --git a/zend_abstract_interface/env/env.h b/zend_abstract_interface/env/env.h index c8686e6748c..42f5d47fd1e 100644 --- a/zend_abstract_interface/env/env.h +++ b/zend_abstract_interface/env/env.h @@ -3,8 +3,9 @@ #include -#include #include +#include +#include /* The upper-bounds limit on the buffer size to hold the value of an arbitrary * environment variable. @@ -23,10 +24,6 @@ typedef enum { * environment variable 'name'. */ ZAI_ENV_SUCCESS, - /* The function is being called before the SAPI environment variables are - * available. - */ - ZAI_ENV_NOT_READY, /* The environment variable is not set. */ ZAI_ENV_NOT_SET, /* The buffer is not large enough to accommodate the length of the value. */ @@ -48,24 +45,30 @@ 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 - * 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(). - * - * 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. +/** + * SAPI-only. Copies sapi_module.getenv() result into buf->ptr. Handles the + * efree of the emalloc'd SAPI result internally — caller never frees. + * buf must be non-NULL. buf->ptr must point to caller-owned writable storage + * of buf->len bytes. + * Returns ZAI_ENV_SUCCESS, ZAI_ENV_NOT_SET, etc. + * Precondition: must be called during or after RINIT — either + * PG(modules_activated) or PG(during_request_startup) must be non-zero. + * Violating this (calling before any request has started) is a programming + * error and will trigger ZAI_ASSERT. + * On failure, there's no guarantee about the contents of buf->ptr. */ -zai_env_result zai_getenv_ex(zai_str name, zai_env_buffer buf, bool pre_rinit); -static inline zai_env_result zai_getenv(zai_str name, zai_env_buffer buf) { - return zai_getenv_ex(name, buf, false); -} +zai_env_result zai_sapi_getenv(zai_str name, zai_env_buffer *buf); -#define zai_getenv_literal(name, buf) zai_getenv(ZAI_STRL(name), buf) +/** + * System-only. Returns a zai_option_str pointing directly into process memory. + * The pointer must not be freed or written to, and should be copied if held. + * Should be called pre-RINIT, but it can also be called at request time if the + * cache needs to be bypassed e.g. for OTEL env vars. + */ +static inline zai_option_str zai_sys_getenv(zai_str name) { + ZAI_ASSERT(name.ptr && "zai_sys_getenv: detected null zai_str.ptr"); + char *value = getenv(name.ptr); + return value ? zai_option_str_from_raw_parts(value, strlen(value)) : ZAI_OPTION_STR_NONE; +} #endif // ZAI_ENV_H diff --git a/zend_abstract_interface/env/tests/error.cc b/zend_abstract_interface/env/tests/error.cc index e301ba7d4e4..0fff9e733c2 100644 --- a/zend_abstract_interface/env/tests/error.cc +++ b/zend_abstract_interface/env/tests/error.cc @@ -6,39 +6,6 @@ extern "C" { #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); - zai_env_result res = zai_getenv_literal("", buf); - - REQUIRE(res == ZAI_ENV_ERROR); - REQUIRE_BUF_EQ("", 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 name len (sys)", {}, { + REQUIRE(zai_option_str_is_none(zai_sys_getenv(ZAI_STRL("")))); }) - -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..b17db7f64f8 100644 --- a/zend_abstract_interface/env/tests/host.cc +++ b/zend_abstract_interface/env/tests/host.cc @@ -11,11 +11,7 @@ TEA_TEST_CASE_WITH_PROLOGUE("env/host", "non-empty string", { },{ 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("bar", buf); + REQUIRE(zai_option_str_is_some(zai_sys_getenv(ZAI_STRL("FOO")))); }) TEA_TEST_CASE_WITH_PROLOGUE("env/host", "empty string", { @@ -23,11 +19,9 @@ 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); - - REQUIRE(res == ZAI_ENV_SUCCESS); - REQUIRE_BUF_EQ("", buf); + zai_option_str result = zai_sys_getenv(ZAI_STRL("FOO")); + REQUIRE(zai_option_str_is_some(result)); + REQUIRE(result.len == 0); }) TEA_TEST_CASE_WITH_PROLOGUE("env/host", "not set", { @@ -35,47 +29,7 @@ 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); - - REQUIRE(res == ZAI_ENV_BUFFER_TOO_BIG); - REQUIRE_BUF_EQ("", buf); + REQUIRE(zai_option_str_is_none(zai_sys_getenv(ZAI_STRL("FOO")))); }) TEA_TEST_CASE_BARE("env/host", "outside request context", { @@ -85,11 +39,8 @@ 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); - - REQUIRE(res == ZAI_ENV_NOT_READY); - REQUIRE_BUF_EQ("", buf); + // zai_sys_getenv works outside request context (no readiness check) + REQUIRE(zai_option_str_is_some(zai_sys_getenv(ZAI_STRL("FOO")))); 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..a8acd213b0a 100644 --- a/zend_abstract_interface/env/tests/sapi.cc +++ b/zend_abstract_interface/env/tests/sapi.cc @@ -12,19 +12,16 @@ extern "C" { #define TEA_SAPI_GETENV_FUNCTION(fn) static char *fn(char *name, size_t name_len) #endif -static char zai_str_buf[64]; - -// Returns the name of the env var as the value +// Returns the name of the env var as the value (emalloc'd so efree inside zai_sapi_getenv works) TEA_SAPI_GETENV_FUNCTION(tea_sapi_getenv_non_empty) { - strcpy(zai_str_buf, name); - return zai_str_buf; + return estrdup(name); } 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); + zai_env_result res = zai_sapi_getenv(ZAI_STRL("FOO"), &buf); REQUIRE(res == ZAI_ENV_SUCCESS); REQUIRE_BUF_EQ("FOO", buf); @@ -36,7 +33,7 @@ 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); + zai_env_result res = zai_sapi_getenv(ZAI_STRL("FOO"), &buf); REQUIRE(res == ZAI_ENV_SUCCESS); REQUIRE_BUF_EQ("FOO", buf); @@ -44,27 +41,41 @@ TEA_TEST_CASE_WITH_PROLOGUE("env/sapi", "non-empty string (no host env fallback) TEA_SAPI_GETENV_FUNCTION(tea_sapi_getenv_null) { return NULL; } +TEA_TEST_CASE_WITH_PROLOGUE("env/sapi", "fallback to host env when sapi not set", { + tea_sapi_module.getenv = tea_sapi_getenv_null; + REQUIRE_SETENV("FOO", "bar"); +},{ + ZAI_ENV_BUFFER_INIT(buf, 64); + + zai_env_result res = zai_sapi_getenv(ZAI_STRL("FOO"), &buf); + REQUIRE(res == ZAI_ENV_NOT_SET); + + zai_option_str sys = zai_sys_getenv(ZAI_STRL("FOO")); + REQUIRE(zai_option_str_is_some(sys)); + REQUIRE(sys.len == strlen("bar")); + REQUIRE(0 == memcmp(sys.ptr, "bar", sys.len)); +}) + 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); + zai_env_result res = zai_sapi_getenv(ZAI_STRL("FOO"), &buf); REQUIRE(res == ZAI_ENV_NOT_SET); - REQUIRE_BUF_EQ("", buf); }) -TEA_TEST_CASE_WITH_PROLOGUE("env/sapi", "not set (with host env fallback)", { +TEA_TEST_CASE_WITH_PROLOGUE("env/sapi", "not set (sapi returns null regardless of host env)", { tea_sapi_module.getenv = tea_sapi_getenv_null; },{ REQUIRE_SETENV("FOO", "bar"); ZAI_ENV_BUFFER_INIT(buf, 64); - zai_env_result res = zai_getenv_literal("FOO", buf); + zai_env_result res = zai_sapi_getenv(ZAI_STRL("FOO"), &buf); - REQUIRE(res == ZAI_ENV_SUCCESS); - REQUIRE_BUF_EQ("bar", buf); + // zai_sapi_getenv only consults SAPI, not host env + REQUIRE(res == ZAI_ENV_NOT_SET); }) /****************************** Access from RINIT *****************************/ @@ -81,7 +92,7 @@ static PHP_RINIT_FUNCTION(zai_env) { 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_rinit_last_res = zai_sapi_getenv(ZAI_STRL("FROM_RINIT"), &buf); } zend_catch { result = FAILURE; } zend_end_try(); @@ -99,16 +110,3 @@ TEA_TEST_CASE_WITH_PROLOGUE("env/sapi", "rinit non-empty string", { REQUIRE(zai_rinit_last_res == ZAI_ENV_SUCCESS); 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; - 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(0 == strcmp("bar", zai_rinit_str_buf)); -})