diff --git a/ext/otel_config.c b/ext/otel_config.c index c9504a0cabb..a15ed9ca95e 100644 --- a/ext/otel_config.c +++ b/ext/otel_config.c @@ -20,8 +20,8 @@ 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) { +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 961e7765dd9..898c58f2a6c 100644 --- a/zend_abstract_interface/config/config.c +++ b/zend_abstract_interface/config/config.c @@ -12,11 +12,68 @@ 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 != 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)) { + buf->ptr = (char *)cached.ptr; + buf->len = cached.len; + 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; + if (zai_getenv(name, buf) == 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) { @@ -40,6 +97,8 @@ 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); 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) { @@ -48,7 +107,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) { @@ -59,7 +118,10 @@ 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 (!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; } @@ -148,6 +210,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 +227,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 +301,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..4bfa6df15cd 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..d9e153613ff 100644 --- a/zend_abstract_interface/config/config_ini.c +++ b/zend_abstract_interface/config/config_ini.c @@ -438,6 +438,8 @@ 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 @@ -446,7 +448,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(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 @@ -458,7 +461,9 @@ 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); + 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 28a65a87a9e..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) { - 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) { /* 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); + 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 c8686e6748c..4e18ff092d3 100644 --- a/zend_abstract_interface/env/env.h +++ b/zend_abstract_interface/env/env.h @@ -45,27 +45,30 @@ 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 +/* 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(). + * 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); -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_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/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", {