#define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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; }