diff --git a/ext/otel_config.c b/ext/otel_config.c index c9504a0cabb..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 * } } -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) { +/** + * 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) { + 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; } @@ -29,13 +41,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 +58,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 +66,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 +79,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 +90,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 +202,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..360d727b84d 100644 --- a/ext/otel_config.h +++ b/ext/otel_config.h @@ -3,14 +3,16 @@ #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); +#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); #endif // DD_OTEL_CONFIG_H diff --git a/zend_abstract_interface/config/config.c b/zend_abstract_interface/config/config.c index 961e7765dd9..57044102464 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
@@ -12,30 +13,87 @@ 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; +/* + * 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]; + +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); + return zai_config_cached_env_values[id][name_index]; } -static inline void zai_config_process_env(zai_config_memoized_entry *memoized, zai_env_buffer buf, zai_option_str *value) { +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}; + + zai_option_str val = zai_sys_getenv(name); + if (zai_option_str_is_none(val)) { + continue; + } + + 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); + } + } +} + +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 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_sys_getenv_cached(id, name_index); +} + +/** + * 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; @@ -43,24 +101,32 @@ static void zai_config_find_and_set_value(zai_config_memoized_entry *memoized, z 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, 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; } } - 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; } @@ -69,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 @@ -148,6 +214,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 +231,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,9 +305,17 @@ void zai_config_first_time_rinit(bool in_request) { (void)in_request; #endif + // 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(); + } + 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..73986191153 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); +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 06f03f711c8..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; @@ -438,27 +436,38 @@ 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); 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) == ZAI_ENV_SUCCESS - && 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; } } - 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/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/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..49bb38a3cb2 100644 --- a/zend_abstract_interface/config/tests/ini.cc +++ b/zend_abstract_interface/config/tests/ini.cc @@ -7,6 +7,17 @@ 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). */ +#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, @@ -245,13 +256,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 +265,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() }) @@ -522,6 +517,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")); }, { @@ -533,9 +529,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(); }) @@ -548,29 +542,78 @@ 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() 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_ZVAL_STRING_EQ(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]; +// 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: 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"))) { + if (sapi_getenv_request_num.load() < 2) { + 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; + } + return NULL; +} + +TEST_INI("SAPI env takes priority over cache", { + 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(value != NULL); - REQUIRE(Z_TYPE_P(value) == IS_STRING); - REQUIRE(zval_string_equals(value, "value2")); + REQUIRE_ZVAL_STRING_EQ(value, "system env val"); + + REQUEST_END() + + 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"); REQUEST_END() -}) \ No newline at end of file + + 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 28a65a87a9e..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,45 +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) { - if (!buf.ptr || !buf.len) return ZAI_ENV_ERROR; - - buf.ptr[0] = '\0'; - - if (zai_str_is_empty(name)) return ZAI_ENV_ERROR; - - if (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; +zai_option_str zai_sapi_getenv(zai_str name) { char *value = sapi_getenv_compat(name.ptr, name.len); if (value) { - use_sapi_env = true; - } else { - value = getenv(name.ptr); + return zai_option_str_from_raw_parts(value, strlen(value)); } - - if (!value) return ZAI_ENV_NOT_SET; - - zai_env_result res; - - if (strlen(value) < buf.len) { - strcpy(buf.ptr, value); - res = ZAI_ENV_SUCCESS; - } else { - res = ZAI_ENV_BUFFER_TOO_SMALL; - } - - if (use_sapi_env) efree(value); - - return res; + return ZAI_OPTION_STR_NONE; } diff --git a/zend_abstract_interface/env/env.h b/zend_abstract_interface/env/env.h index c8686e6748c..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. @@ -45,27 +45,27 @@ 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} -/* 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(). +/** + * Borrows the environment variable from the SAPI--it will not check the + * system environment variables. * - * 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. + * 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); -static inline zai_env_result zai_getenv(zai_str name, zai_env_buffer buf) { - return zai_getenv_ex(name, buf, false); -} +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 e301ba7d4e4..00000000000 --- a/zend_abstract_interface/env/tests/error.cc +++ /dev/null @@ -1,44 +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); - 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 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. *