From b69e5c152559d31ccffa89fe68648d3c1c2a095b Mon Sep 17 00:00:00 2001 From: henderkes Date: Sun, 8 Mar 2026 11:57:07 +0700 Subject: [PATCH 1/5] fix php startup errors when ini files contain environment variables --- frankenphp.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/frankenphp.c b/frankenphp.c index 419959f3d3..a781b9ab93 100644 --- a/frankenphp.c +++ b/frankenphp.c @@ -974,6 +974,11 @@ static void frankenphp_log_message(const char *message, int syslog_type_int) { static char *frankenphp_getenv(const char *name, size_t name_len) { HashTable *ht = sandboxed_env ? sandboxed_env : main_thread_env; + /* main_thread_env is not yet available during PHP startup, but .ini parsing may call sapi_getenv */ + if (ht == NULL) { + return getenv(name); + } + zval *env_val = zend_hash_str_find(ht, name, name_len); if (env_val && Z_TYPE_P(env_val) == IS_STRING) { zend_string *str = Z_STR_P(env_val); From db86d932439f834798111a99396695f2f2a67f74 Mon Sep 17 00:00:00 2001 From: henderkes Date: Sun, 8 Mar 2026 12:30:53 +0700 Subject: [PATCH 2/5] add test --- frankenphp.c | 3 ++- phpmainthread_test.go | 22 ++++++++++++++++++---- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/frankenphp.c b/frankenphp.c index a781b9ab93..e9d1955567 100644 --- a/frankenphp.c +++ b/frankenphp.c @@ -974,7 +974,8 @@ static void frankenphp_log_message(const char *message, int syslog_type_int) { static char *frankenphp_getenv(const char *name, size_t name_len) { HashTable *ht = sandboxed_env ? sandboxed_env : main_thread_env; - /* main_thread_env is not yet available during PHP startup, but .ini parsing may call sapi_getenv */ + /* main_thread_env is not yet available during PHP startup, + * but .ini parsing may call sapi_getenv */ if (ht == NULL) { return getenv(name); } diff --git a/phpmainthread_test.go b/phpmainthread_test.go index c26f2cde51..06a8530b5a 100644 --- a/phpmainthread_test.go +++ b/phpmainthread_test.go @@ -25,6 +25,22 @@ func setupGlobals(t *testing.T) { resetGlobals() } +func TestPhpIniEnvVarExpansion(t *testing.T) { + t.Setenv("OPCACHE_ENABLE", "0") + t.Cleanup(Shutdown) + + resetGlobals() + isRunning = true + + _, err := initPHPThreads(1, 1, map[string]string{"opcache.enable": "${OPCACHE_ENABLE}"}) + assert.NoError(t, err) + + regularRequestChan = make(chan contextHolder) + convertToRegularThread(phpThreads[0]) + + assertRequestBody(t, "http://example.com/ini.php?key=opcache.enable", "opcache.enable:0") +} + func TestStartAndStopTheMainThreadWithOneInactiveThread(t *testing.T) { _, err := initPHPThreads(1, 1, nil) // boot 1 thread assert.NoError(t, err) @@ -188,12 +204,11 @@ func TestReturnAnErrorIf2WorkersHaveTheSameFileName(t *testing.T) { workersByName = map[string]*worker{} workersByPath = map[string]*worker{} w, err1 := newWorker(workerOpt{fileName: testDataPath + "/index.php"}) + assert.NoError(t, err1) workers = append(workers, w) workersByName[w.name] = w workersByPath[w.fileName] = w _, err2 := newWorker(workerOpt{fileName: testDataPath + "/index.php"}) - - assert.NoError(t, err1) assert.Error(t, err2, "two workers cannot have the same filename") } @@ -202,12 +217,11 @@ func TestReturnAnErrorIf2ModuleWorkersHaveTheSameName(t *testing.T) { workersByName = map[string]*worker{} workersByPath = map[string]*worker{} w, err1 := newWorker(workerOpt{fileName: testDataPath + "/index.php", name: "workername"}) + assert.NoError(t, err1) workers = append(workers, w) workersByName[w.name] = w workersByPath[w.fileName] = w _, err2 := newWorker(workerOpt{fileName: testDataPath + "/hello.php", name: "workername"}) - - assert.NoError(t, err1) assert.Error(t, err2, "two workers cannot have the same name") } From 47d865510f71c60942678d9119d52cd26dc2ebc1 Mon Sep 17 00:00:00 2001 From: henderkes Date: Sun, 8 Mar 2026 16:56:51 +0700 Subject: [PATCH 3/5] update test by suggestion --- frankenphp_test.go | 14 ++++++++++++++ phpmainthread_test.go | 16 ---------------- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/frankenphp_test.go b/frankenphp_test.go index 0301b20276..61e257e35a 100644 --- a/frankenphp_test.go +++ b/frankenphp_test.go @@ -160,6 +160,20 @@ func testHelloWorld(t *testing.T, opts *testOptions) { }, opts) } +func TestEnvVarsInPhpIni(t *testing.T) { + t.Setenv("OPCACHE_ENABLE", "0") + + runTest(t, func(handler func(http.ResponseWriter, *http.Request), _ *httptest.Server, _ int) { + body, _ := testGet("http://example.com/ini.php?key=opcache.enable", handler, t) + assert.Equal(t, "opcache.enable:0", body) + }, &testOptions{ + nbParallelRequests: 1, + phpIni: map[string]string{ + "opcache.enable": "${OPCACHE_ENABLE}", + }, + }) +} + func TestFinishRequest_module(t *testing.T) { testFinishRequest(t, nil) } func TestFinishRequest_worker(t *testing.T) { testFinishRequest(t, &testOptions{workerScript: "finish-request.php"}) diff --git a/phpmainthread_test.go b/phpmainthread_test.go index 06a8530b5a..d274991863 100644 --- a/phpmainthread_test.go +++ b/phpmainthread_test.go @@ -25,22 +25,6 @@ func setupGlobals(t *testing.T) { resetGlobals() } -func TestPhpIniEnvVarExpansion(t *testing.T) { - t.Setenv("OPCACHE_ENABLE", "0") - t.Cleanup(Shutdown) - - resetGlobals() - isRunning = true - - _, err := initPHPThreads(1, 1, map[string]string{"opcache.enable": "${OPCACHE_ENABLE}"}) - assert.NoError(t, err) - - regularRequestChan = make(chan contextHolder) - convertToRegularThread(phpThreads[0]) - - assertRequestBody(t, "http://example.com/ini.php?key=opcache.enable", "opcache.enable:0") -} - func TestStartAndStopTheMainThreadWithOneInactiveThread(t *testing.T) { _, err := initPHPThreads(1, 1, nil) // boot 1 thread assert.NoError(t, err) From 03f5b884d3864091aaaf655ef634ecaa4114bc1c Mon Sep 17 00:00:00 2001 From: henderkes Date: Sun, 8 Mar 2026 17:00:45 +0700 Subject: [PATCH 4/5] move sandboxed env initialization before ini parsing --- frankenphp.c | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/frankenphp.c b/frankenphp.c index e9d1955567..9277560e90 100644 --- a/frankenphp.c +++ b/frankenphp.c @@ -974,12 +974,6 @@ static void frankenphp_log_message(const char *message, int syslog_type_int) { static char *frankenphp_getenv(const char *name, size_t name_len) { HashTable *ht = sandboxed_env ? sandboxed_env : main_thread_env; - /* main_thread_env is not yet available during PHP startup, - * but .ini parsing may call sapi_getenv */ - if (ht == NULL) { - return getenv(name); - } - zval *env_val = zend_hash_str_find(ht, name, name_len); if (env_val && Z_TYPE_P(env_val) == IS_STRING) { zend_string *str = Z_STR_P(env_val); @@ -1141,6 +1135,13 @@ static void *php_main(void *arg) { frankenphp_init_interned_strings(); + /* take a snapshot of the environment for sandboxing */ + if (main_thread_env == NULL) { + main_thread_env = pemalloc(sizeof(HashTable), 1); + zend_hash_init(main_thread_env, 8, NULL, NULL, 1); + go_init_os_env(main_thread_env); + } + frankenphp_sapi_module.startup(&frankenphp_sapi_module); /* check if a default filter is set in php.ini and only filter if @@ -1150,13 +1151,6 @@ static void *php_main(void *arg) { should_filter_var = default_filter != NULL; original_user_abort_setting = PG(ignore_user_abort); - /* take a snapshot of the environment for sandboxing */ - if (main_thread_env == NULL) { - main_thread_env = pemalloc(sizeof(HashTable), 1); - zend_hash_init(main_thread_env, 8, NULL, NULL, 1); - go_init_os_env(main_thread_env); - } - go_frankenphp_main_thread_is_ready(); /* channel closed, shutdown gracefully */ From fd22487b6433121f2f789fb3e959da28eacaf57a Mon Sep 17 00:00:00 2001 From: Marc Date: Sun, 8 Mar 2026 20:36:37 +0700 Subject: [PATCH 5/5] Update frankenphp_test.go MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Kévin Dunglas Signed-off-by: Marc --- frankenphp_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/frankenphp_test.go b/frankenphp_test.go index 61e257e35a..3b8c274d99 100644 --- a/frankenphp_test.go +++ b/frankenphp_test.go @@ -167,7 +167,6 @@ func TestEnvVarsInPhpIni(t *testing.T) { body, _ := testGet("http://example.com/ini.php?key=opcache.enable", handler, t) assert.Equal(t, "opcache.enable:0", body) }, &testOptions{ - nbParallelRequests: 1, phpIni: map[string]string{ "opcache.enable": "${OPCACHE_ENABLE}", },