From 0d1389e7d3f90b753fde9f9376da501ebd35c0f5 Mon Sep 17 00:00:00 2001 From: yacineolivier-maker Date: Wed, 21 Oct 2026 23:45:21 +0200 Subject: [PATCH] Overlay autocompile: compiler scratch files beside the overlay cache (#714) --- runtime/CMakeLists.txt | 4 ++ runtime/src/autocompile.c | 13 ++++- runtime/src/autocompile_child_env.h | 53 +++++++++++++++++++ runtime/tests/test_autocompile_child_env.c | 60 ++++++++++++++++++++++ 4 files changed, 129 insertions(+), 1 deletion(-) create mode 100644 runtime/src/autocompile_child_env.h create mode 100644 runtime/tests/test_autocompile_child_env.c diff --git a/runtime/CMakeLists.txt b/runtime/CMakeLists.txt index 11e7afbc..f1c45888 100644 --- a/runtime/CMakeLists.txt +++ b/runtime/CMakeLists.txt @@ -1516,6 +1516,10 @@ if(BUILD_TESTING) add_test(NAME mod_resident_test COMMAND mod_resident_test) if(WIN32) + add_executable(autocompile_child_env_test tests/test_autocompile_child_env.c) + target_include_directories(autocompile_child_env_test PRIVATE src) + add_test(NAME autocompile_child_env_test COMMAND autocompile_child_env_test) + add_executable(autocompile_publication_test tests/test_autocompile_publication.c src/autocompile.c diff --git a/runtime/src/autocompile.c b/runtime/src/autocompile.c index 22842246..3300923f 100644 --- a/runtime/src/autocompile.c +++ b/runtime/src/autocompile.c @@ -46,6 +46,9 @@ static char s_cwd[512]; * the loader's READ location, regardless of what the configured command says. */ static char s_cache_dir[512]; static char s_captures[512]; +#ifdef _WIN32 +#include "autocompile_child_env.h" +#endif enum { AC_IDLE = 0, AC_RUNNING = 1, AC_DONE = 2 }; #ifdef _WIN32 @@ -1191,10 +1194,18 @@ static int autocompile_request_impl(void) { /* Compilation is opportunistic: it must never outrank the interpreter that * keeps the current frame alive. cmd -> python -> gcc inherit below-normal * priority, while the live compile script also defaults to one worker. */ + char *child_env = s_cache_dir[0] ? ac_child_environment(s_cache_dir) : NULL; + if (s_cache_dir[0] && !child_env) { + if (job) CloseHandle(job); + CloseHandle(rd); CloseHandle(wr); + autocompile_note_failure(); + return 0; + } BOOL ok = CreateProcessA(NULL, full, NULL, NULL, TRUE, CREATE_NO_WINDOW | BELOW_NORMAL_PRIORITY_CLASS | CREATE_SUSPENDED, - NULL, s_cwd[0] ? s_cwd : NULL, &si, &pi); + child_env, s_cwd[0] ? s_cwd : NULL, &si, &pi); + free(child_env); CloseHandle(wr); if (!ok) { if (job) CloseHandle(job); diff --git a/runtime/src/autocompile_child_env.h b/runtime/src/autocompile_child_env.h new file mode 100644 index 00000000..3e0a7087 --- /dev/null +++ b/runtime/src/autocompile_child_env.h @@ -0,0 +1,53 @@ +/* Windows compiler scratch is private to the child, never the game process. */ +#ifndef PSX_AUTOCOMPILE_CHILD_ENV_H +#define PSX_AUTOCOMPILE_CHILD_ENV_H +#include +#include +#include +#include +static int ac_env_compare(const void *a, const void *b) { + return _stricmp(*(const char *const *)a, *(const char *const *)b); +} +/* cache is the canonical existing cache directory. Caller frees the returned + * sorted ANSI environment block after CreateProcessA. NULL is a hard failure; + * do not silently inherit a system-drive TEMP after a scratch setup failure. */ +static char *ac_child_environment(const char *cache) { + char temp_dir[sizeof(s_cache_dir) + 5]; + char temp_var[sizeof(temp_dir) + 6], tmp_var[sizeof(temp_dir) + 5]; + int n = snprintf(temp_dir, sizeof(temp_dir), "%s\\tmp", cache); + if (n < 0 || (size_t)n >= sizeof(temp_dir)) return NULL; + if (!CreateDirectoryA(temp_dir, NULL) && GetLastError() != ERROR_ALREADY_EXISTS) + return NULL; + DWORD attr = GetFileAttributesA(temp_dir); + if (attr == INVALID_FILE_ATTRIBUTES || !(attr & FILE_ATTRIBUTE_DIRECTORY)) return NULL; + snprintf(temp_var, sizeof(temp_var), "TEMP=%s", temp_dir); + snprintf(tmp_var, sizeof(tmp_var), "TMP=%s", temp_dir); + char *inherited = GetEnvironmentStringsA(); + if (!inherited) return NULL; + size_t count = 0; + for (char *p = inherited; *p; p += strlen(p) + 1) ++count; + const char **entries = (const char **)malloc((count + 2) * sizeof(*entries)); + if (!entries) { FreeEnvironmentStringsA(inherited); return NULL; } + size_t used = 0, bytes = 1; + for (char *p = inherited; *p; p += strlen(p) + 1) { + if (!_strnicmp(p, "TEMP=", 5) || !_strnicmp(p, "TMP=", 4)) continue; + entries[used++] = p; + } + entries[used++] = temp_var; + entries[used++] = tmp_var; + qsort(entries, used, sizeof(*entries), ac_env_compare); + for (size_t i = 0; i < used; ++i) bytes += strlen(entries[i]) + 1; + char *block = (char *)malloc(bytes); + if (block) { + char *out = block; + for (size_t i = 0; i < used; ++i) { + size_t length = strlen(entries[i]) + 1; + memcpy(out, entries[i], length); out += length; + } + *out = 0; + } + free(entries); + FreeEnvironmentStringsA(inherited); + return block; +} +#endif diff --git a/runtime/tests/test_autocompile_child_env.c b/runtime/tests/test_autocompile_child_env.c new file mode 100644 index 00000000..c56627da --- /dev/null +++ b/runtime/tests/test_autocompile_child_env.c @@ -0,0 +1,60 @@ +/* Real child launch: private TEMP/TMP, inherited variables, parent isolation. */ +#define WIN32_LEAN_AND_MEAN +#include +#include +#include +#include +static char s_cache_dir[512]; /* same input capacity as autocompile.c */ +#include "autocompile_child_env.h" +#define CHECK(x) do { if (!(x)) { fprintf(stderr,"FAIL line %d: %s\n",__LINE__,#x); return 1; } } while (0) +int main(int argc, char **argv) { + char temp[1024], tmp[1024]; + if (argc == 3 && !strcmp(argv[1], "--child")) { + CHECK(GetEnvironmentVariableA("TEMP", temp, sizeof(temp)) > 0); + CHECK(GetEnvironmentVariableA("TMP", tmp, sizeof(tmp)) > 0); + CHECK(!strcmp(temp, argv[2]) && !strcmp(tmp, argv[2])); + char marker[32]; + CHECK(GetEnvironmentVariableA("PSX_AC_TEMP_TEST", marker, sizeof(marker)) > 0); + CHECK(!strcmp(marker, "preserved")); + return 0; + } + char cwd[512], cache[600], expected[610], exe[1024], cmd[2300]; + CHECK(GetCurrentDirectoryA(sizeof(cwd), cwd) > 0); + CHECK(GetEnvironmentVariableA("TEMP", temp, sizeof(temp)) > 0); + CHECK(GetEnvironmentVariableA("TMP", tmp, sizeof(tmp)) > 0); + CHECK(SetEnvironmentVariableA("PSX_AC_TEMP_TEST", "preserved")); + snprintf(cache, sizeof(cache), "%s\\autocompile-child-cache", cwd); + CHECK(CreateDirectoryA(cache, NULL) || GetLastError() == ERROR_ALREADY_EXISTS); + char *env = ac_child_environment(cache); + CHECK(env != NULL); + int temps = 0, tmps = 0; + const char *previous = NULL; + for (char *p = env; *p; p += strlen(p) + 1) { + CHECK(!previous || _stricmp(previous, p) <= 0); + if (!_strnicmp(p, "TEMP=", 5)) ++temps; + if (!_strnicmp(p, "TMP=", 4)) ++tmps; + previous = p; + } + CHECK(temps == 1 && tmps == 1); + snprintf(expected, sizeof(expected), "%s\\tmp", cache); + CHECK(GetModuleFileNameA(NULL, exe, sizeof(exe)) > 0); + snprintf(cmd, sizeof(cmd), "\"%s\" --child \"%s\"", exe, expected); + STARTUPINFOA si = {0}; PROCESS_INFORMATION pi = {0}; si.cb = sizeof(si); + CHECK(CreateProcessA(NULL, cmd, NULL, NULL, FALSE, CREATE_NO_WINDOW, + env, cwd, &si, &pi)); + free(env); + CHECK(WaitForSingleObject(pi.hProcess, 10000) == WAIT_OBJECT_0); + DWORD code = 1; CHECK(GetExitCodeProcess(pi.hProcess, &code)); + CloseHandle(pi.hThread); CloseHandle(pi.hProcess); CHECK(code == 0); + char after[1024]; + CHECK(GetEnvironmentVariableA("TEMP", after, sizeof(after)) > 0 && !strcmp(after,temp)); + CHECK(GetEnvironmentVariableA("TMP", after, sizeof(after)) > 0 && !strcmp(after,tmp)); + /* A file occupying /tmp must refuse, never inherit another TEMP. */ + snprintf(cache, sizeof(cache), "%s\\autocompile-blocked-cache", cwd); + CHECK(CreateDirectoryA(cache, NULL) || GetLastError() == ERROR_ALREADY_EXISTS); + snprintf(expected, sizeof(expected), "%s\\tmp", cache); + FILE *file = fopen(expected, "wb"); CHECK(file != NULL); fclose(file); + CHECK(ac_child_environment(cache) == NULL); + puts("PASS: compiler child gets cache scratch; parent TEMP/TMP stay unchanged; invalid scratch refuses"); + return 0; +}