Temp dir falls back when TMPDIR or /tmp is unusable (Termux); v0.6.1
os_temp_dir() used $TMPDIR or "/tmp" without checking either. On Termux
/tmp usually does not exist (or SELinux denies it) unless TMPDIR points at
$PREFIX/tmp, so plugin and MCP temp files failed and shell children that
need a temp file (heredocs) broke.
* os_temp_dir() picks, once, the first of $TMPDIR, $PREFIX/tmp, /tmp,
/var/tmp and ~/.cache/cst-agent/tmp where a file can really be
created; if none works it keeps the old answer
* os_temp_dir_export() at startup writes the choice back to TMPDIR, so
shell children and the sandbox's writable temp dir agree
* tests/test_tmpdir.c covers the fallbacks in fresh forks
This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
/* 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 <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifndef _WIN32
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
|
||||
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
|
||||
Reference in New Issue
Block a user