327 lines
7.4 KiB
C
327 lines
7.4 KiB
C
#define _GNU_SOURCE
|
|
#include <dirent.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <limits.h>
|
|
#include <poll.h>
|
|
#include <signal.h>
|
|
#include <stdarg.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
#include <sys/wait.h>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
|
|
enum
|
|
{
|
|
Failtimeout = 4000,
|
|
};
|
|
|
|
typedef struct Test Test;
|
|
struct Test
|
|
{
|
|
pid_t child;
|
|
int errfd;
|
|
char root[256];
|
|
char runtime[320];
|
|
char config[320];
|
|
char ibus[384];
|
|
char bus[448];
|
|
char home[320];
|
|
char socket[384];
|
|
char badmap[320];
|
|
char err[4096];
|
|
size_t nerr;
|
|
};
|
|
|
|
static int
|
|
fail(char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
|
|
fprintf(stderr, "daemon_failure_test: ");
|
|
va_start(ap, fmt);
|
|
vfprintf(stderr, fmt, ap);
|
|
va_end(ap);
|
|
fputc('\n', stderr);
|
|
return 0;
|
|
}
|
|
|
|
static int64_t
|
|
nowms(void)
|
|
{
|
|
struct timespec ts;
|
|
|
|
if(clock_gettime(CLOCK_MONOTONIC, &ts) < 0)
|
|
return -1;
|
|
return (int64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
|
|
}
|
|
|
|
static int
|
|
leftms(int64_t deadline)
|
|
{
|
|
int64_t n;
|
|
|
|
n = nowms();
|
|
if(n < 0)
|
|
return -1;
|
|
n = deadline - n;
|
|
if(n <= 0)
|
|
return 0;
|
|
return n > INT_MAX ? INT_MAX : n;
|
|
}
|
|
|
|
static int
|
|
makedir(char *path)
|
|
{
|
|
if(mkdir(path, 0700) == 0)
|
|
return 1;
|
|
return fail("mkdir %s: %s", path, strerror(errno));
|
|
}
|
|
|
|
static void
|
|
readerrors(Test *t)
|
|
{
|
|
ssize_t n;
|
|
|
|
while(t->errfd >= 0 && t->nerr + 1 < sizeof t->err){
|
|
n = read(t->errfd, t->err + t->nerr,
|
|
sizeof t->err - t->nerr - 1);
|
|
if(n > 0){
|
|
t->nerr += n;
|
|
continue;
|
|
}
|
|
if(n < 0 && errno == EINTR)
|
|
continue;
|
|
break;
|
|
}
|
|
t->err[t->nerr] = '\0';
|
|
}
|
|
|
|
static int
|
|
setup(Test *t)
|
|
{
|
|
memset(t, 0, sizeof *t);
|
|
t->child = -1;
|
|
t->errfd = -1;
|
|
snprintf(t->root, sizeof t->root, "/tmp/strans-failure.XXXXXX");
|
|
if(mkdtemp(t->root) == NULL){
|
|
t->root[0] = '\0';
|
|
return fail("mkdtemp: %s", strerror(errno));
|
|
}
|
|
if(snprintf(t->runtime, sizeof t->runtime, "%s/runtime", t->root)
|
|
>= (int)sizeof t->runtime ||
|
|
snprintf(t->config, sizeof t->config, "%s/config", t->root)
|
|
>= (int)sizeof t->config ||
|
|
snprintf(t->ibus, sizeof t->ibus, "%s/ibus", t->config)
|
|
>= (int)sizeof t->ibus ||
|
|
snprintf(t->bus, sizeof t->bus, "%s/bus", t->ibus)
|
|
>= (int)sizeof t->bus ||
|
|
snprintf(t->home, sizeof t->home, "%s/home", t->root)
|
|
>= (int)sizeof t->home ||
|
|
snprintf(t->socket, sizeof t->socket, "%s/strans.sock", t->runtime)
|
|
>= (int)sizeof t->socket ||
|
|
snprintf(t->badmap, sizeof t->badmap, "%s/missing-map", t->root)
|
|
>= (int)sizeof t->badmap)
|
|
return fail("temporary path is too long");
|
|
return makedir(t->runtime) && makedir(t->config) && makedir(t->ibus) &&
|
|
makedir(t->bus) && makedir(t->home);
|
|
}
|
|
|
|
static int
|
|
startchild(Test *t, char *program)
|
|
{
|
|
int errpipe[2], fd;
|
|
long maxfd;
|
|
pid_t pid;
|
|
|
|
if(pipe2(errpipe, O_CLOEXEC|O_NONBLOCK) < 0)
|
|
return fail("pipe2: %s", strerror(errno));
|
|
pid = fork();
|
|
if(pid < 0){
|
|
close(errpipe[0]);
|
|
close(errpipe[1]);
|
|
return fail("fork: %s", strerror(errno));
|
|
}
|
|
if(pid == 0){
|
|
close(errpipe[0]);
|
|
if(dup2(errpipe[1], STDERR_FILENO) < 0)
|
|
_exit(126);
|
|
close(errpipe[1]);
|
|
if(close_range(3, UINT_MAX, 0) < 0){
|
|
maxfd = sysconf(_SC_OPEN_MAX);
|
|
if(maxfd < 0)
|
|
maxfd = 1024;
|
|
for(fd = 3; fd < maxfd; fd++)
|
|
close(fd);
|
|
}
|
|
if(setenv("XDG_RUNTIME_DIR", t->runtime, 1) < 0 ||
|
|
setenv("XDG_CONFIG_HOME", t->config, 1) < 0 ||
|
|
setenv("HOME", t->home, 1) < 0 || unsetenv("DISPLAY") < 0 ||
|
|
unsetenv("DBUS_SESSION_BUS_ADDRESS") < 0 ||
|
|
unsetenv("IBUS_ADDRESS") < 0 ||
|
|
unsetenv("IBUS_ADDRESS_FILE") < 0)
|
|
_exit(126);
|
|
execl(program, program, t->badmap, (char*)0);
|
|
dprintf(STDERR_FILENO, "exec %s: %s\n", program, strerror(errno));
|
|
_exit(127);
|
|
}
|
|
close(errpipe[1]);
|
|
t->child = pid;
|
|
t->errfd = errpipe[0];
|
|
return 1;
|
|
}
|
|
|
|
static int
|
|
waitfailed(Test *t)
|
|
{
|
|
struct pollfd pfd;
|
|
int n, status;
|
|
int64_t deadline;
|
|
|
|
deadline = nowms();
|
|
if(deadline < 0)
|
|
return fail("read monotonic clock: %s", strerror(errno));
|
|
deadline += Failtimeout;
|
|
for(;;){
|
|
n = waitpid(t->child, &status, WNOHANG);
|
|
if(n == t->child){
|
|
t->child = -1;
|
|
readerrors(t);
|
|
break;
|
|
}
|
|
if(n < 0 && errno == EINTR)
|
|
continue;
|
|
if(n < 0){
|
|
if(errno == ECHILD)
|
|
t->child = -1;
|
|
return fail("waitpid: %s", strerror(errno));
|
|
}
|
|
n = leftms(deadline);
|
|
if(n < 0)
|
|
return fail("read monotonic clock: %s", strerror(errno));
|
|
if(n == 0)
|
|
return fail("daemon did not exit after map initialization failure");
|
|
pfd.fd = t->errfd;
|
|
pfd.events = POLLIN|POLLHUP;
|
|
pfd.revents = 0;
|
|
n = poll(&pfd, 1, n);
|
|
if(n < 0 && errno == EINTR)
|
|
continue;
|
|
if(n < 0)
|
|
return fail("poll daemon: %s", strerror(errno));
|
|
if(n > 0)
|
|
readerrors(t);
|
|
}
|
|
if(!WIFEXITED(status) || WEXITSTATUS(status) == 0)
|
|
return fail("daemon map failure had wait status %#x", status);
|
|
if(strstr(t->err, "can't open") == NULL || strstr(t->err, t->badmap) == NULL)
|
|
return fail("daemon did not report the missing map directory");
|
|
return 1;
|
|
}
|
|
|
|
static int
|
|
emptydir(char *path)
|
|
{
|
|
DIR *dir;
|
|
struct dirent *de;
|
|
int empty;
|
|
|
|
dir = opendir(path);
|
|
if(dir == NULL)
|
|
return fail("open %s: %s", path, strerror(errno));
|
|
empty = 1;
|
|
errno = 0;
|
|
while((de = readdir(dir)) != NULL)
|
|
if(strcmp(de->d_name, ".") != 0 && strcmp(de->d_name, "..") != 0){
|
|
fail("unexpected endpoint %s/%s", path, de->d_name);
|
|
empty = 0;
|
|
}
|
|
if(errno != 0){
|
|
fail("read %s: %s", path, strerror(errno));
|
|
empty = 0;
|
|
}
|
|
if(closedir(dir) < 0){
|
|
fail("close %s: %s", path, strerror(errno));
|
|
empty = 0;
|
|
}
|
|
return empty;
|
|
}
|
|
|
|
static int
|
|
checkendpoints(Test *t)
|
|
{
|
|
struct stat st;
|
|
|
|
if(lstat(t->socket, &st) == 0)
|
|
return fail("IPC endpoint exists after failed startup: %s", t->socket);
|
|
if(errno != ENOENT)
|
|
return fail("lstat IPC endpoint: %s", strerror(errno));
|
|
return emptydir(t->runtime) && emptydir(t->bus);
|
|
}
|
|
|
|
static int
|
|
cleanup(Test *t)
|
|
{
|
|
int n, ok, status;
|
|
|
|
ok = 1;
|
|
if(t->child > 0){
|
|
kill(t->child, SIGKILL);
|
|
do
|
|
n = waitpid(t->child, &status, 0);
|
|
while(n < 0 && errno == EINTR);
|
|
if(n != t->child)
|
|
ok = fail("reap child: %s", strerror(errno));
|
|
t->child = -1;
|
|
}
|
|
readerrors(t);
|
|
if(t->errfd >= 0 && close(t->errfd) < 0)
|
|
ok = fail("close stderr: %s", strerror(errno));
|
|
if(t->socket[0] != '\0' && unlink(t->socket) < 0 && errno != ENOENT)
|
|
ok = fail("remove socket: %s", strerror(errno));
|
|
if(t->bus[0] != '\0' && rmdir(t->bus) < 0 && errno != ENOENT)
|
|
ok = fail("remove bus directory: %s", strerror(errno));
|
|
if(t->ibus[0] != '\0' && rmdir(t->ibus) < 0 && errno != ENOENT)
|
|
ok = fail("remove IBus directory: %s", strerror(errno));
|
|
if(t->config[0] != '\0' && rmdir(t->config) < 0 && errno != ENOENT)
|
|
ok = fail("remove config directory: %s", strerror(errno));
|
|
if(t->runtime[0] != '\0' && rmdir(t->runtime) < 0 && errno != ENOENT)
|
|
ok = fail("remove runtime directory: %s", strerror(errno));
|
|
if(t->home[0] != '\0' && rmdir(t->home) < 0 && errno != ENOENT)
|
|
ok = fail("remove home directory: %s", strerror(errno));
|
|
if(t->root[0] != '\0' && rmdir(t->root) < 0 && errno != ENOENT)
|
|
ok = fail("remove root directory: %s", strerror(errno));
|
|
return ok;
|
|
}
|
|
|
|
int
|
|
main(int argc, char **argv)
|
|
{
|
|
Test test;
|
|
int ok;
|
|
|
|
if(argc != 2){
|
|
fprintf(stderr, "usage: daemon_failure_test strans\n");
|
|
return 2;
|
|
}
|
|
ok = setup(&test);
|
|
if(ok)
|
|
ok = startchild(&test, argv[1]);
|
|
if(ok)
|
|
ok = waitfailed(&test) && checkendpoints(&test);
|
|
if(!cleanup(&test))
|
|
ok = 0;
|
|
if(!ok){
|
|
if(test.nerr != 0)
|
|
fprintf(stderr, "daemon_failure_test: daemon stderr:\n%s", test.err);
|
|
return 1;
|
|
}
|
|
printf("failed daemon startup leaves no endpoints: ok\n");
|
|
return 0;
|
|
}
|