/* * 975_dirs_toolong_run — F14 #69: dirs silently truncated an over-long * composed path and then mkdir'd the WRONG directory, returning it rc=0. * The fix (dirs.ww build()) precomputes the composition length and * rt_aborts loudly when it won't fit the 256B pathbuf — mirroring Hare's * path::push too_long → `!` abort (ref/hare/dirs/xdg.ha). Both stages * share the lib, so this is a stdlib behavior fix, not a stage divergence; * the test still runs both driver twins for parity. * * One fixture program (dirs.config("prog"), print len) built per stage, * then run under two HOME states: * - a short HOME → exit 0 (normal path intact, no regression); * - a ~260-byte HOME → non-zero exit (loud abort) AND no stray * directory created on disk. */ #include #include #include #include #include #include static int runwait(const char *cmd) { int rc = system(cmd); if (rc == -1) return -1; if (WIFEXITED(rc)) return WEXITSTATUS(rc); return -1; } static const char *PROG = "package main;\n" "import dirs;\n" "import fmt;\n" "export fn main() int = {\n" " let d = dirs.config(\"prog\");\n" " fmt.println(d.len: i64);\n" " return 0;\n" "};\n"; /* run_stage — build PROG with `driver`, then exercise the two HOME states. * Returns 0 on success, non-zero on any mismatch. */ static int run_stage(const char *name, const char *driver) { char tmpdir[64] = "/tmp/dtl_XXXXXX"; if (mkdtemp(tmpdir) == NULL) { perror("dirs_toolong: mkdtemp"); return 1; } char src[128], outbin[128], sepwork[160], rmcmd[192], cmd[2048]; char shorthome[128], shortcfg[160], shortprog[192]; snprintf(src, sizeof src, "%s/dtl_%d.ww", tmpdir, getpid()); snprintf(outbin, sizeof outbin, "%s/dtl_%d", tmpdir, getpid()); snprintf(sepwork, sizeof sepwork, "%s.sepwork", outbin); snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", sepwork); snprintf(shorthome, sizeof shorthome, "%s/home", tmpdir); snprintf(shortcfg, sizeof shortcfg, "%s/.config", shorthome); snprintf(shortprog, sizeof shortprog, "%s/prog", shortcfg); int fail = 0; FILE *f = fopen(src, "wb"); if (!f) { fail = 1; goto cleanup; } fputs(PROG, f); fclose(f); snprintf(cmd, sizeof cmd, "%s build -o %s %s 2>/dev/null", driver, outbin, src); if (runwait(cmd) != 0) { fprintf(stderr, "dirs_toolong[%s]: build failed\n", name); fail = 1; goto cleanup; } /* short HOME — the normal path, must still exit 0. */ snprintf(cmd, sizeof cmd, "env -u XDG_CONFIG_HOME HOME=%s %s >/dev/null 2>&1", shorthome, outbin); if (runwait(cmd) != 0) { fprintf(stderr, "dirs_toolong[%s]: short HOME did not exit 0\n", name); fail = 1; } /* ~260-byte HOME — must abort loudly (non-zero) and create no dir. */ char longhome[512]; int p = snprintf(longhome, sizeof longhome, "%s/long_", tmpdir); for (int i = 0; i < 260 && p < (int)sizeof longhome - 1; i++) longhome[p++] = 'a'; longhome[p] = '\0'; snprintf(cmd, sizeof cmd, "env -u XDG_CONFIG_HOME HOME=%s %s >/dev/null 2>&1", longhome, outbin); if (runwait(cmd) == 0) { fprintf(stderr, "dirs_toolong[%s]: long HOME exited 0, " "expected a loud abort\n", name); fail = 1; } /* the truncated wrong directory must NOT have been created. */ snprintf(cmd, sizeof cmd, "test -e %s", longhome); if (runwait(cmd) == 0) { fprintf(stderr, "dirs_toolong[%s]: stray directory created\n", name); fail = 1; } cleanup: { int cleanbad = 0; if (unlink(src) != 0 && errno != ENOENT) cleanbad = 1; if (unlink(outbin) != 0 && errno != ENOENT) cleanbad = 1; if (runwait(rmcmd) != 0) cleanbad = 1; if (rmdir(shortprog) != 0 && errno != ENOENT) cleanbad = 1; if (rmdir(shortcfg) != 0 && errno != ENOENT) cleanbad = 1; if (rmdir(shorthome) != 0 && errno != ENOENT) cleanbad = 1; if (rmdir(tmpdir) != 0) cleanbad = 1; if (cleanbad) { fprintf(stderr, "dirs_toolong[%s]: temporary cleanup failed\n", name); fail = 1; } } return fail; } int main(void) { const char *bin = getenv("BIN"); if (!bin) bin = "out/bin"; char absbin[1024]; if (bin[0] != '/') { char cwd[1024]; if (getcwd(cwd, sizeof cwd) == NULL) return 1; snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin); bin = absbin; } char cdrv[1024], wdrv[1024]; snprintf(cdrv, sizeof cdrv, "%s/ww", bin); snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin); int fail = 0; fail += run_stage("cstage", cdrv); if (access(wdrv, X_OK) == 0) fail += run_stage("wwstage", wdrv); else fprintf(stderr, "dirs_toolong: skip wwstage (no %s)\n", wdrv); if (fail) { fprintf(stderr, "dirs_toolong: %d stage(s) failed\n", fail); return 1; } printf("dirs_toolong: ok\n"); return 0; }