/* os_temp_dir() falls back when $TMPDIR is missing or read-only (Termux, * Android) and os_temp_dir_export() hands the choice to child processes. * Each case runs in a fresh fork because the choice is made once. */ #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #include "os_compat.h" #include #include #include #ifndef _WIN32 #include #include #include static int nfail; #define EXPECT(cond, msg) \ do { \ if (!(cond)) { \ fprintf(stderr, "FAIL %s:%d: %s\n", __FILE__, __LINE__, msg); \ nfail++; \ } \ } while (0) /* Runs in a child: set the environment, then compare os_temp_dir(). */ static int pick_is(const char *tmpdir, const char *prefix, const char *want) { pid_t pid = fork(); if (pid < 0) return 0; if (pid == 0) { if (tmpdir) setenv("TMPDIR", tmpdir, 1); else unsetenv("TMPDIR"); if (prefix) setenv("PREFIX", prefix, 1); else unsetenv("PREFIX"); char got[4096]; if (os_temp_dir(got, sizeof(got)) != 0 || strcmp(got, want) != 0) { fprintf(stderr, " TMPDIR=%s PREFIX=%s: got %s, want %s\n", tmpdir ? tmpdir : "(unset)", prefix ? prefix : "(unset)", got, want); _exit(1); } os_temp_dir_export(); const char *t = getenv("TMPDIR"); if (!t || strcmp(t, want) != 0) _exit(2); char f[4096]; int fd = os_mkstemp(f, sizeof(f), "cst-t"); if (fd < 0 || strncmp(f, want, strlen(want)) != 0) _exit(3); close(fd); unlink(f); _exit(0); } int st = 0; waitpid(pid, &st, 0); return WIFEXITED(st) && WEXITSTATUS(st) == 0; } int main(void) { const char *base = getenv("TMPDIR"); char root[4096]; snprintf(root, sizeof(root), "%s/cst-tmpdir-XXXXXX", base && base[0] ? base : "/tmp"); if (!mkdtemp(root)) { puts("test_tmpdir: skipped (no writable temp dir to set up in)"); return 0; } char ok[4200], prefix[4200], ptmp[4300], ro[4200], slash[4300]; snprintf(ok, sizeof(ok), "%s/ok", root); snprintf(prefix, sizeof(prefix), "%s/usr", root); snprintf(ptmp, sizeof(ptmp), "%s/tmp", prefix); snprintf(ro, sizeof(ro), "%s/ro", root); snprintf(slash, sizeof(slash), "%s//", ok); mkdir(ok, 0700); mkdir(prefix, 0700); mkdir(ptmp, 0700); mkdir(ro, 0500); EXPECT(pick_is(ok, NULL, ok), "usable TMPDIR wins"); EXPECT(pick_is(slash, NULL, ok), "trailing slashes trimmed"); EXPECT(pick_is("/nonexistent/cst", prefix, ptmp), "missing TMPDIR falls back to $PREFIX/tmp"); EXPECT(pick_is(NULL, prefix, ptmp), "unset TMPDIR uses $PREFIX/tmp"); if (geteuid() != 0) { EXPECT(pick_is(ro, prefix, ptmp), "read-only TMPDIR is skipped"); char tmp_ok[64] = "/tmp"; if (access("/tmp", W_OK) == 0) EXPECT(pick_is(ro, NULL, tmp_ok), "read-only TMPDIR falls to /tmp"); } rmdir(ro); rmdir(ptmp); rmdir(prefix); rmdir(ok); rmdir(root); if (nfail) { fprintf(stderr, "test_tmpdir: %d failure(s)\n", nfail); return 1; } puts("test_tmpdir: OK"); return 0; } #else int main(void) { puts("test_tmpdir: skipped on Windows"); return 0; } #endif