Overlay autocompile: compiler scratch files beside the overlay cache (#714)

This commit is contained in:
yacineolivier-maker
2026-10-21 23:45:21 +02:00
committed by GitHub
parent 5c07d42f6a
commit 0d1389e7d3
4 changed files with 129 additions and 1 deletions
+4
View File
@@ -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
+12 -1
View File
@@ -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);
+53
View File
@@ -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 <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
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
@@ -0,0 +1,60 @@
/* Real child launch: private TEMP/TMP, inherited variables, parent isolation. */
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
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 <cache>/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;
}