From 7220286ab505d7503a91f0c9e154def59f6356bf Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Sun, 16 Aug 2026 16:42:50 +0900 Subject: [PATCH] test: share one daemon and D-Bus harness across live tests Six live tests carried private copies of the same daemon harness: the private XDG_RUNTIME_DIR, the fork/exec with captured stderr, the readiness waits, the SIGTERM and SIGKILL paths, the IBus address file reader, the socket connect, and the IPC probe. Three of them also carried the same libdbus helpers. Extract one implementation into tests/live.c and, so that the binaries that do not link dbus-1 keep not linking it, the D-Bus half into tests/livebus.c. Both are compiled into each binary the way ../ipc.c already is. gtk_live_test keeps its own fake-server harness and only takes fail, nowms and leftms; xim_live_test keeps its own Xvfb and process-group daemon spawn, which must inherit DISPLAY, and takes the temporary directory, timing and cleanup halves. The copies had drifted; the harness keeps the stricter behaviour. - nowms reports a broken clock (-1) instead of pretending it read zero, and leftms turns that into an expired deadline, so a loop ends in a timeout failure rather than spinning. livesetup checks the clock once up front, as daemon_restart_test did. - Timeouts compare with <= 0, not == 0. - readuntil keeps the three-way result (complete, peer closed, error) from ipc_live_test and daemon_restart_test rather than folding peer closure into ECONNRESET. - The daemon's stdout and stderr are both captured, and a failing setenv is reported, for every daemon; daemon_failure_test captured stderr only and said nothing about setenv. - The child keeps daemon_restart_test's careful redirect that also works when the pipe lands on fd 1 or 2. - parseaddress requires a positive declared PID. - killdaemon reports ECHILD after SIGKILL as a failure; one copy accepted it. It now reaps with a blocking waitpid, which SIGKILL guarantees will return, instead of daemon_restart_test's polled wait with its own timeout diagnostic. - stopdaemon tolerates ESRCH on SIGTERM, a benign race two copies reported as an error. - liveclean removes the socket and address files and then rmdirs each directory, reporting leftovers, rather than deleting the temporary root recursively. - ibus_live_test now waits for the IPC socket and the address file by polling, dropping its inotify variant; it asserted only the address file before. --- tests/Makefile | 44 +- tests/daemon_collision_test.c | 916 ++----------------------- tests/daemon_failure_test.c | 256 +------ tests/daemon_restart_test.c | 1183 ++------------------------------- tests/gtk_live_test.c | 39 +- tests/ibus_live_test.c | 620 ++--------------- tests/ipc_live_test.c | 555 +--------------- tests/live.c | 695 +++++++++++++++++++ tests/live.h | 73 ++ tests/livebus.c | 168 +++++ tests/livebus.h | 17 + tests/xim_live_test.c | 144 +--- 12 files changed, 1223 insertions(+), 3487 deletions(-) create mode 100644 tests/live.c create mode 100644 tests/live.h create mode 100644 tests/livebus.c create mode 100644 tests/livebus.h diff --git a/tests/Makefile b/tests/Makefile index 53a1c13..19f341b 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -24,6 +24,8 @@ UNIT_LDLIBS = -lthread -lbio $(TEXT_LIBS) $(IBUS_LIBS) $(XIM_LIBS) PROG = unit_test STRESS = stress_test +LIVESRC = live.c live.h +LIVEBUSSRC = livebus.c livebus.h SMOKE = ibus_live_test ibus_client_smoke gtk_live_test xim_live_test \ ipc_live_test FAULT = daemon_collision_test daemon_failure_test daemon_restart_test @@ -71,18 +73,19 @@ stress_test.o: unit_test.c $(CC) $(CPPFLAGS) $(UNIT_CPPFLAGS) $(UNIT_CFLAGS) $(CFLAGS) -DSTRESS \ -c -o $@ $< -ibus_live_test: ibus_live_test.c - $(HOSTCC) $(CPPFLAGS) $(DBUS_CFLAGS) $(HOST_CFLAGS) $(CFLAGS) \ - $(LDFLAGS) -o $@ $< $(DBUS_LIBS) $(LDLIBS) +ibus_live_test: ibus_live_test.c $(LIVESRC) $(LIVEBUSSRC) ../ipc.c ../ipc.h + $(HOSTCC) $(CPPFLAGS) -I.. $(DBUS_CFLAGS) $(HOST_CFLAGS) $(CFLAGS) \ + $(LDFLAGS) -o $@ ibus_live_test.c live.c livebus.c ../ipc.c \ + $(DBUS_LIBS) $(LDLIBS) ibus_client_smoke: ibus_client_smoke.c $(HOSTCC) $(CPPFLAGS) $(IBUS_CLIENT_CFLAGS) $(HOST_CFLAGS) $(CFLAGS) \ $(LDFLAGS) -o $@ $< $(IBUS_CLIENT_LIBS) $(LDLIBS) -gtk_live_test: gtk_live_test.c ../ipc.c ../ipc.h +gtk_live_test: gtk_live_test.c $(LIVESRC) ../ipc.c ../ipc.h $(HOSTCC) $(CPPFLAGS) -I.. $(GTK_CFLAGS) $(HOST_CFLAGS) $(CFLAGS) \ - $(LDFLAGS) -o $@ gtk_live_test.c ../ipc.c $(GTK_LIBS) -pthread \ - $(LDLIBS) + $(LDFLAGS) -o $@ gtk_live_test.c live.c ../ipc.c $(GTK_LIBS) \ + -pthread $(LDLIBS) ../gtk/im-strans.so: ../gtk/main.c ../ipc.c ../ipc.h $(MAKE) -C ../gtk @@ -90,27 +93,30 @@ gtk_live_test: gtk_live_test.c ../ipc.c ../ipc.h ../strans: $(MAKE) -C .. strans -xim_live_test: xim_live_test.c ../ipc.c ../ipc.h +xim_live_test: xim_live_test.c $(LIVESRC) ../ipc.c ../ipc.h $(HOSTCC) $(CPPFLAGS) -I.. $(X11_CFLAGS) $(HOST_CFLAGS) $(CFLAGS) \ - $(LDFLAGS) -o $@ xim_live_test.c ../ipc.c $(X11_LIBS) $(LDLIBS) + $(LDFLAGS) -o $@ xim_live_test.c live.c ../ipc.c $(X11_LIBS) \ + $(LDLIBS) -ipc_live_test: ipc_live_test.c ../ipc.c ../ipc.h +ipc_live_test: ipc_live_test.c $(LIVESRC) ../ipc.c ../ipc.h $(HOSTCC) $(CPPFLAGS) -I.. $(HOST_CFLAGS) $(CFLAGS) $(LDFLAGS) \ - -o $@ ipc_live_test.c ../ipc.c $(LDLIBS) + -o $@ ipc_live_test.c live.c ../ipc.c $(LDLIBS) -daemon_collision_test: daemon_collision_test.c ../ipc.c ../ipc.h +daemon_collision_test: daemon_collision_test.c $(LIVESRC) $(LIVEBUSSRC) \ + ../ipc.c ../ipc.h $(HOSTCC) $(CPPFLAGS) -I.. $(DBUS_CFLAGS) $(HOST_CFLAGS) $(CFLAGS) \ - $(LDFLAGS) -o $@ daemon_collision_test.c ../ipc.c $(DBUS_LIBS) \ - $(LDLIBS) + $(LDFLAGS) -o $@ daemon_collision_test.c live.c livebus.c \ + ../ipc.c $(DBUS_LIBS) $(LDLIBS) -daemon_failure_test: daemon_failure_test.c - $(HOSTCC) $(CPPFLAGS) $(HOST_CFLAGS) $(CFLAGS) $(LDFLAGS) -o $@ $< \ - $(LDLIBS) +daemon_failure_test: daemon_failure_test.c $(LIVESRC) ../ipc.c ../ipc.h + $(HOSTCC) $(CPPFLAGS) -I.. $(HOST_CFLAGS) $(CFLAGS) $(LDFLAGS) -o $@ \ + daemon_failure_test.c live.c ../ipc.c $(LDLIBS) -daemon_restart_test: daemon_restart_test.c ../ipc.c ../ipc.h +daemon_restart_test: daemon_restart_test.c $(LIVESRC) $(LIVEBUSSRC) \ + ../ipc.c ../ipc.h $(HOSTCC) $(CPPFLAGS) -I.. $(DBUS_CFLAGS) $(HOST_CFLAGS) $(CFLAGS) \ - $(LDFLAGS) -o $@ daemon_restart_test.c ../ipc.c $(DBUS_LIBS) \ - $(LDLIBS) + $(LDFLAGS) -o $@ daemon_restart_test.c live.c livebus.c ../ipc.c \ + $(DBUS_LIBS) $(LDLIBS) unit_test.o stress_test.o $(TESTOBJ): test.h ../dat.h ../fn.h ../ipc.h \ ../cutest/cutest.h diff --git a/tests/daemon_collision_test.c b/tests/daemon_collision_test.c index bffa628..74ffb0d 100644 --- a/tests/daemon_collision_test.c +++ b/tests/daemon_collision_test.c @@ -1,335 +1,39 @@ #define _GNU_SOURCE -#include -#include #include -#include -#include #include -#include -#include #include #include -#include #include -#include -#include #include -#include #include -#include #include -#include "ipc.h" - -enum -{ - Calltimeout = 4000, - Starttimeout = 8000, - Stoptimeout = 3000, -}; +#include "live.h" +#include "livebus.h" typedef struct Test Test; struct Test { - pid_t first; - pid_t second; - int firsterrfd; - int seconderrfd; + Live l; + Daemon first; + Daemon second; int ipcfirst; int ipcnew; DBusConnection *busfirst; DBusConnection *busnew; - char root[256]; - char runtime[320]; - char config[320]; - char ibus[384]; - char bus[448]; - char home[320]; - char socket[384]; - char addrfile[512]; char address[512]; - char firsterr[8192]; - size_t nfirsterr; - char seconderr[8192]; - size_t nseconderr; }; -static int -fail(char *fmt, ...) -{ - va_list ap; - - fprintf(stderr, "daemon_collision_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 0; - return (int64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000; -} - -static int -leftms(int64_t deadline) -{ - int64_t n; - - n = deadline - nowms(); - if(n <= 0) - return 0; - if(n > 0x7fffffff) - return 0x7fffffff; - return n; -} - -static int -makedir(char *path) -{ - if(mkdir(path, 0700) == 0) - return 1; - return fail("mkdir %s: %s", path, strerror(errno)); -} - -static void -readpipe(int fd, char *buf, size_t cap, size_t *used) -{ - ssize_t n; - - if(fd < 0 || cap == 0) - return; - while(*used + 1 < cap){ - n = read(fd, buf + *used, cap - *used - 1); - if(n > 0){ - *used += n; - continue; - } - if(n < 0 && errno == EINTR) - continue; - break; - } - buf[*used] = '\0'; -} - -static void -showerrors(Test *t) -{ - readpipe(t->firsterrfd, t->firsterr, sizeof t->firsterr, - &t->nfirsterr); - readpipe(t->seconderrfd, t->seconderr, sizeof t->seconderr, - &t->nseconderr); - if(t->nfirsterr != 0) - fprintf(stderr, "daemon_collision_test: first daemon stderr:\n%s", - t->firsterr); - if(t->nseconderr != 0) - fprintf(stderr, "daemon_collision_test: second daemon stderr:\n%s", - t->seconderr); -} - static int setup(Test *t) { memset(t, 0, sizeof *t); - t->first = -1; - t->second = -1; - t->firsterrfd = -1; - t->seconderrfd = -1; + daemoninit(&t->first, "first daemon"); + daemoninit(&t->second, "second daemon"); t->ipcfirst = -1; t->ipcnew = -1; - snprintf(t->root, sizeof t->root, "/tmp/strans-collision.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) - return fail("temporary path is too long"); - if(!makedir(t->runtime) || !makedir(t->config) || !makedir(t->ibus) || - !makedir(t->bus) || !makedir(t->home)) - return 0; - return 1; -} - -static int -startchild(Test *t, char *program, char *mapdir, pid_t *child, int *errfd) -{ - 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], STDOUT_FILENO) < 0 || - 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){ - dprintf(STDERR_FILENO, "set daemon environment: %s\n", - strerror(errno)); - _exit(126); - } - execl(program, program, mapdir, (char*)0); - dprintf(STDERR_FILENO, "exec %s: %s\n", program, strerror(errno)); - _exit(127); - } - close(errpipe[1]); - *child = pid; - *errfd = errpipe[0]; - return 1; -} - -static int -findaddress(Test *t) -{ - DIR *dir; - struct dirent *de; - int count, ok; - - dir = opendir(t->bus); - if(dir == NULL){ - fail("open IBus directory %s: %s", t->bus, strerror(errno)); - return -1; - } - count = 0; - ok = 1; - errno = 0; - while((de = readdir(dir)) != NULL){ - if(strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0 || - strstr(de->d_name, ".tmp.") != NULL) - continue; - count++; - if(snprintf(t->addrfile, sizeof t->addrfile, "%s/%s", t->bus, - de->d_name) >= (int)sizeof t->addrfile){ - fail("IBus address path is too long"); - ok = 0; - break; - } - } - if(errno != 0){ - fail("read IBus directory %s: %s", t->bus, strerror(errno)); - ok = 0; - } - if(closedir(dir) < 0){ - fail("close IBus directory %s: %s", t->bus, strerror(errno)); - ok = 0; - } - if(!ok) - return -1; - if(count > 1){ - fail("found %d IBus address files", count); - return -1; - } - return count; -} - -static int -readfile(char *path, char *buf, size_t cap, size_t *nread) -{ - struct stat st; - ssize_t n; - size_t off; - char extra; - int fd, ok; - - fd = open(path, O_RDONLY|O_CLOEXEC); - if(fd < 0) - return fail("open %s: %s", path, strerror(errno)); - ok = 0; - if(fstat(fd, &st) < 0){ - fail("stat open address file %s: %s", path, strerror(errno)); - goto out; - } - if(st.st_size < 0 || (uintmax_t)st.st_size >= cap){ - fail("address file %s is too large", path); - goto out; - } - off = 0; - while(off < (size_t)st.st_size){ - n = read(fd, buf + off, (size_t)st.st_size - off); - if(n < 0 && errno == EINTR) - continue; - if(n <= 0){ - fail("read %s: %s", path, - n == 0 ? "unexpected EOF" : strerror(errno)); - goto out; - } - off += n; - } - do - n = read(fd, &extra, 1); - while(n < 0 && errno == EINTR); - if(n != 0){ - fail("address file %s changed while being read", path); - goto out; - } - buf[off] = '\0'; - *nread = off; - ok = 1; -out: - if(close(fd) < 0){ - fail("close %s: %s", path, strerror(errno)); - ok = 0; - } - return ok; -} - -static int -parseaddress(char *contents, size_t ncontents, char *address, size_t naddress, - pid_t expected) -{ - char found[512]; - long pid; - int consumed; - - consumed = -1; - if(strlen(contents) != ncontents || - sscanf(contents, "IBUS_ADDRESS=%511[^\n]\nIBUS_DAEMON_PID=%ld\n%n", - found, &pid, &consumed) != 2 || consumed != (int)ncontents) - return fail("invalid IBus address file contents"); - if(pid != expected) - return fail("IBus address PID %ld, expected %ld", pid, - (long)expected); - if(snprintf(address, naddress, "%s", found) >= (int)naddress) - return fail("private IBus address is too long"); - return 1; + return livesetup(&t->l, "collision"); } static int @@ -337,368 +41,18 @@ addressunchanged(Test *t) { char contents[2048], address[512]; size_t ncontents; + pid_t declared; - if(!readfile(t->addrfile, contents, sizeof contents, &ncontents) || - !parseaddress(contents, ncontents, address, sizeof address, t->first)) + if(!readfile(t->l.addrfile, contents, sizeof contents, &ncontents) || + !parseaddress(contents, ncontents, address, sizeof address, &declared)) return 0; + if(declared != t->first.pid) + return fail("IBus address PID %ld, expected %ld", (long)declared, + (long)t->first.pid); return strcmp(address, t->address) == 0 || fail("first private IBus address changed after collision"); } -static int -childalive(pid_t *pid, char *which) -{ - int n, status; - - do - n = waitpid(*pid, &status, WNOHANG); - while(n < 0 && errno == EINTR); - if(n == 0) - return 1; - if(n == *pid){ - fail("%s daemon exited unexpectedly with wait status %#x", which, - status); - *pid = -1; - return 0; - } - if(n < 0){ - fail("check %s daemon %ld: %s", which, (long)*pid, - strerror(errno)); - if(errno == ECHILD) - *pid = -1; - return 0; - } - return fail("waitpid returned the wrong %s child", which); -} - -static int -waitready(Test *t) -{ - struct pollfd pfd; - struct stat st; - char contents[2048]; - size_t ncontents; - int n, count, socketready, timeout; - int64_t deadline; - - deadline = nowms() + Starttimeout; - for(;;){ - socketready = lstat(t->socket, &st) == 0 && S_ISSOCK(st.st_mode) && - (st.st_mode & 0777) == 0600; - count = findaddress(t); - if(count < 0) - return 0; - if(socketready && count == 1 && - readfile(t->addrfile, contents, sizeof contents, &ncontents) && - parseaddress(contents, ncontents, t->address, sizeof t->address, - t->first)) - return 1; - if(!childalive(&t->first, "first")) - return 0; - timeout = leftms(deadline); - if(timeout == 0) - return fail("timed out waiting for both daemon endpoints"); - pfd.fd = t->firsterrfd; - pfd.events = POLLIN|POLLHUP; - pfd.revents = 0; - n = poll(&pfd, 1, timeout > 20 ? 20 : timeout); - if(n < 0 && errno == EINTR) - continue; - if(n < 0) - return fail("poll daemon readiness: %s", strerror(errno)); - if(pfd.revents != 0) - readpipe(t->firsterrfd, t->firsterr, sizeof t->firsterr, - &t->nfirsterr); - } -} - -static int -connectsocket(char *path, int64_t deadline) -{ - struct sockaddr_un addr; - struct pollfd pfd; - socklen_t nerr; - int err, fd, flags, n; - - memset(&addr, 0, sizeof addr); - addr.sun_family = AF_UNIX; - if(snprintf(addr.sun_path, sizeof addr.sun_path, "%s", path) - >= (int)sizeof addr.sun_path){ - errno = ENAMETOOLONG; - return -1; - } - fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); - if(fd < 0) - return -1; - if(connect(fd, (struct sockaddr*)&addr, sizeof addr) < 0 && - errno != EINPROGRESS){ - err = errno; - close(fd); - errno = err; - return -1; - } - for(;;){ - n = leftms(deadline); - if(n == 0){ - close(fd); - errno = ETIMEDOUT; - return -1; - } - pfd.fd = fd; - pfd.events = POLLOUT; - pfd.revents = 0; - n = poll(&pfd, 1, n); - if(n < 0 && errno == EINTR) - continue; - if(n <= 0){ - err = n == 0 ? ETIMEDOUT : errno; - close(fd); - errno = err; - return -1; - } - break; - } - err = 0; - nerr = sizeof err; - if(getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &nerr) < 0 || err != 0){ - if(err == 0) - err = errno; - close(fd); - errno = err; - return -1; - } - flags = fcntl(fd, F_GETFL); - if(flags < 0 || fcntl(fd, F_SETFL, flags & ~O_NONBLOCK) < 0){ - err = errno; - close(fd); - errno = err; - return -1; - } - return fd; -} - -static int -readuntil(int fd, void *buf, size_t n, int64_t deadline) -{ - struct pollfd pfd; - unsigned char *p; - ssize_t r; - int timeout; - - p = buf; - while(n > 0){ - timeout = leftms(deadline); - if(timeout == 0){ - errno = ETIMEDOUT; - return -1; - } - pfd.fd = fd; - pfd.events = POLLIN; - pfd.revents = 0; - r = poll(&pfd, 1, timeout); - if(r < 0 && errno == EINTR) - continue; - if(r <= 0){ - if(r == 0) - errno = ETIMEDOUT; - return -1; - } - r = recv(fd, p, n, 0); - if(r < 0 && errno == EINTR) - continue; - if(r <= 0){ - if(r == 0) - errno = ECONNRESET; - return -1; - } - p += r; - n -= r; - } - return 0; -} - -static int -ipcprobe(int fd, char *where) -{ - unsigned char req[Ipcreqsz], reply[Ipcresphdrsz+Ipclensz]; - - ipcpackreq(req, 1, 0, Kmodfirst); - if(ipcsend(fd, req, sizeof req) < 0) - return fail("send %s IPC request: %s", where, strerror(errno)); - if(readuntil(fd, reply, sizeof reply, nowms() + Calltimeout) < 0) - return fail("read %s IPC response: %s", where, strerror(errno)); - if(reply[0] != 0 || reply[1] != 0 || reply[2] != 0 || - reply[3] != 0 || reply[4] != 0) - return fail("%s IPC response was not an empty modifier reply", where); - return 1; -} - -static void -closebus(DBusConnection **conn) -{ - if(*conn == NULL) - return; - dbus_connection_close(*conn); - dbus_connection_unref(*conn); - *conn = NULL; -} - -static DBusMessage* -sendcall(DBusConnection *conn, DBusMessage *m, char *where) -{ - DBusMessage *reply; - DBusPendingCall *pending; - int timeout; - int64_t deadline; - - if(m == NULL){ - fail("allocate %s call", where); - return NULL; - } - pending = NULL; - if(!dbus_connection_send_with_reply(conn, m, &pending, Calltimeout) || - pending == NULL){ - dbus_message_unref(m); - fail("queue %s call", where); - return NULL; - } - dbus_message_unref(m); - deadline = nowms() + Calltimeout; - while(!dbus_pending_call_get_completed(pending)){ - timeout = leftms(deadline); - if(timeout == 0){ - fail("timed out waiting for %s", where); - goto fail; - } - if(!dbus_connection_read_write_dispatch(conn, timeout)){ - fail("connection closed waiting for %s", where); - goto fail; - } - } - reply = dbus_pending_call_steal_reply(pending); - dbus_pending_call_unref(pending); - if(reply == NULL) - fail("%s completed without a reply", where); - return reply; -fail: - dbus_pending_call_cancel(pending); - dbus_pending_call_unref(pending); - return NULL; -} - -static DBusConnection* -openbus(char *address) -{ - DBusConnection *conn; - DBusError err; - - dbus_error_init(&err); - conn = dbus_connection_open_private(address, &err); - if(conn == NULL){ - fail("open private IBus connection: %s", - err.message != NULL ? err.message : "D-Bus error"); - dbus_error_free(&err); - return NULL; - } - dbus_error_free(&err); - dbus_connection_set_exit_on_disconnect(conn, FALSE); - return conn; -} - -static int -hello(DBusConnection *conn, char *name, size_t nname, char *where) -{ - DBusMessage *m, *reply; - DBusError err; - const char *s; - - m = dbus_message_new_method_call("org.freedesktop.DBus", - "/org/freedesktop/DBus", "org.freedesktop.DBus", "Hello"); - reply = sendcall(conn, m, where); - if(reply == NULL) - return 0; - dbus_error_init(&err); - if(!dbus_message_has_signature(reply, "s") || - !dbus_message_get_args(reply, &err, DBUS_TYPE_STRING, &s, - DBUS_TYPE_INVALID)){ - dbus_message_unref(reply); - dbus_error_free(&err); - return fail("%s returned an invalid Hello reply", where); - } - if(snprintf(name, nname, "%s", s) >= (int)nname){ - dbus_error_free(&err); - dbus_message_unref(reply); - return fail("%s Hello name is too long", where); - } - dbus_error_free(&err); - dbus_message_unref(reply); - return name[0] == ':' || fail("%s returned invalid Hello name %s", - where, name); -} - -static int -createcontext(DBusConnection *conn, char *path, size_t npath, char *where) -{ - DBusMessage *m, *reply; - DBusError err; - const char *client, *p; - - client = "daemon-collision-test"; - m = dbus_message_new_method_call("org.freedesktop.IBus", - "/org/freedesktop/IBus", "org.freedesktop.IBus", - "CreateInputContext"); - if(m == NULL || !dbus_message_append_args(m, DBUS_TYPE_STRING, &client, - DBUS_TYPE_INVALID)){ - if(m != NULL) - dbus_message_unref(m); - return fail("build %s CreateInputContext call", where); - } - reply = sendcall(conn, m, where); - if(reply == NULL) - return 0; - dbus_error_init(&err); - if(!dbus_message_has_signature(reply, "o") || - !dbus_message_get_args(reply, &err, DBUS_TYPE_OBJECT_PATH, &p, - DBUS_TYPE_INVALID)){ - dbus_message_unref(reply); - dbus_error_free(&err); - return fail("%s returned an invalid context reply", where); - } - if(snprintf(path, npath, "%s", p) >= (int)npath){ - dbus_error_free(&err); - dbus_message_unref(reply); - return fail("%s context path is too long", where); - } - dbus_error_free(&err); - dbus_message_unref(reply); - return path[0] == '/' || fail("%s returned invalid context path %s", - where, path); -} - -static int -killowned(pid_t *pid, int *status) -{ - int n, ok; - - ok = 1; - if(kill(*pid, SIGKILL) < 0 && errno != ESRCH){ - fail("kill -9 %ld: %s", (long)*pid, strerror(errno)); - ok = 0; - } - do - n = waitpid(*pid, status, 0); - while(n < 0 && errno == EINTR); - if(n != *pid){ - fail("reap %ld after SIGKILL: %s", (long)*pid, - n < 0 ? strerror(errno) : "wrong child"); - if(n < 0 && errno == ECHILD) - *pid = -1; - ok = 0; - }else - *pid = -1; - return ok; -} - static int waitsecond(Test *t) { @@ -709,36 +63,34 @@ waitsecond(Test *t) deadline = nowms() + Starttimeout; for(;;){ do - n = waitpid(t->second, &status, WNOHANG); + n = waitpid(t->second.pid, &status, WNOHANG); while(n < 0 && errno == EINTR); - if(n == t->second){ - t->second = -1; - readpipe(t->seconderrfd, t->seconderr, sizeof t->seconderr, - &t->nseconderr); + if(n == t->second.pid){ + t->second.pid = -1; + readerrors(&t->second); if(!WIFEXITED(status) || WEXITSTATUS(status) == 0) return fail("second daemon did not exit unsuccessfully: %#x", status); - if(strstr(t->seconderr, "IPC endpoint is already in use") == NULL) + if(strstr(t->second.err, "IPC endpoint is already in use") == NULL) return fail("second daemon did not report the IPC collision"); return 1; } if(n < 0){ - fail("waitpid second daemon %ld: %s", (long)t->second, + fail("waitpid second daemon %ld: %s", (long)t->second.pid, strerror(errno)); if(errno == ECHILD) - t->second = -1; + t->second.pid = -1; return 0; } - if(!childalive(&t->first, "first")) + if(!daemonalive(&t->first)) return 0; timeout = leftms(deadline); if(timeout == 0){ fail("second daemon did not exit after the endpoint collision"); - if(!killowned(&t->second, &status)) - return 0; + killdaemon(&t->second, NULL); return 0; } - pfd.fd = t->seconderrfd; + pfd.fd = t->second.errfd; pfd.events = POLLIN|POLLHUP; pfd.revents = 0; n = poll(&pfd, 1, timeout > 20 ? 20 : timeout); @@ -747,167 +99,45 @@ waitsecond(Test *t) if(n < 0) return fail("poll second daemon: %s", strerror(errno)); if(pfd.revents != 0) - readpipe(t->seconderrfd, t->seconderr, sizeof t->seconderr, - &t->nseconderr); + readerrors(&t->second); } } static int runcollision(Test *t, char *program, char *mapdir) { - char firsthello[32], newhello[32]; - char persistentpath[96], newpath[96]; - - if(!startchild(t, program, mapdir, &t->first, &t->firsterrfd) || - !waitready(t)) + if(!startdaemon(&t->l, &t->first, program, mapdir) || + !waitready(&t->l, &t->first, t->address, sizeof t->address)) return 0; - t->ipcfirst = connectsocket(t->socket, nowms() + Calltimeout); + t->ipcfirst = connectsocket(t->l.socket, nowms() + Calltimeout); if(t->ipcfirst < 0) return fail("connect persistent IPC client: %s", strerror(errno)); if(!ipcprobe(t->ipcfirst, "persistent pre-collision")) return 0; - t->busfirst = openbus(t->address); - if(t->busfirst == NULL || !hello(t->busfirst, firsthello, - sizeof firsthello, "persistent pre-collision Hello")) + t->busfirst = openbus(t->address, "persistent pre-collision"); + if(t->busfirst == NULL || + !hello(t->busfirst, NULL, 0, "persistent pre-collision Hello")) return 0; - if(!startchild(t, program, mapdir, &t->second, &t->seconderrfd) || - !waitsecond(t) || !childalive(&t->first, "first") || - !addressunchanged(t)) + if(!startdaemon(&t->l, &t->second, program, mapdir) || + !waitsecond(t) || !daemonalive(&t->first) || !addressunchanged(t)) return 0; if(!ipcprobe(t->ipcfirst, "persistent post-collision")) return 0; - if(!createcontext(t->busfirst, persistentpath, sizeof persistentpath, + if(!createcontext(t->busfirst, NULL, 0, "persistent post-collision context")) return 0; - t->ipcnew = connectsocket(t->socket, nowms() + Calltimeout); + t->ipcnew = connectsocket(t->l.socket, nowms() + Calltimeout); if(t->ipcnew < 0) return fail("connect new IPC client after collision: %s", strerror(errno)); if(!ipcprobe(t->ipcnew, "new post-collision")) return 0; - t->busnew = openbus(t->address); + t->busnew = openbus(t->address, "new post-collision"); if(t->busnew == NULL || - !hello(t->busnew, newhello, sizeof newhello, - "new post-collision Hello") || - !createcontext(t->busnew, newpath, sizeof newpath, - "new post-collision context")) + !hello(t->busnew, NULL, 0, "new post-collision Hello") || + !createcontext(t->busnew, NULL, 0, "new post-collision context")) return 0; - return childalive(&t->first, "first"); -} - -static int -stopfirst(Test *t) -{ - struct pollfd pfd; - int n, ok, reaped, status; - int64_t deadline; - - if(t->first <= 0) - return 1; - ok = 1; - if(!childalive(&t->first, "first")) - return 0; - if(kill(t->first, SIGTERM) < 0){ - fail("kill first daemon %ld: %s", (long)t->first, strerror(errno)); - ok = 0; - } - reaped = 0; - deadline = nowms() + Stoptimeout; - while(t->first > 0){ - n = waitpid(t->first, &status, WNOHANG); - if(n == t->first){ - reaped = 1; - t->first = -1; - break; - } - if(n < 0 && errno == EINTR) - continue; - if(n < 0){ - fail("waitpid first daemon %ld: %s", (long)t->first, - strerror(errno)); - if(errno == ECHILD) - t->first = -1; - ok = 0; - break; - } - n = leftms(deadline); - if(n == 0){ - fail("first daemon %ld did not stop after SIGTERM", - (long)t->first); - ok = 0; - if(!killowned(&t->first, &status)) - ok = 0; - break; - } - pfd.fd = t->firsterrfd; - pfd.events = POLLIN|POLLHUP; - pfd.revents = 0; - if(poll(&pfd, 1, n) < 0 && errno != EINTR){ - fail("poll first daemon termination: %s", strerror(errno)); - ok = 0; - if(!killowned(&t->first, &status)) - ok = 0; - break; - } - readpipe(t->firsterrfd, t->firsterr, sizeof t->firsterr, - &t->nfirsterr); - } - if(reaped && - !((WIFSIGNALED(status) && WTERMSIG(status) == SIGTERM) || - (WIFEXITED(status) && WEXITSTATUS(status) == 1))){ - fail("first daemon exited with unexpected wait status %#x", status); - ok = 0; - } - return ok; -} - -static int -clearbus(Test *t) -{ - DIR *dir; - struct dirent *de; - char path[576]; - int ok; - - dir = opendir(t->bus); - if(dir == NULL) - return errno == ENOENT || fail("open cleanup directory %s: %s", - t->bus, strerror(errno)); - ok = 1; - errno = 0; - while((de = readdir(dir)) != NULL){ - if(strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0) - continue; - if(snprintf(path, sizeof path, "%s/%s", t->bus, de->d_name) - >= (int)sizeof path){ - fail("cleanup path is too long: %s", de->d_name); - ok = 0; - continue; - } - if(unlink(path) < 0 && errno != ENOENT){ - fail("remove IBus cleanup file %s: %s", de->d_name, - strerror(errno)); - ok = 0; - } - errno = 0; - } - if(errno != 0){ - fail("read cleanup directory %s: %s", t->bus, strerror(errno)); - ok = 0; - } - if(closedir(dir) < 0){ - fail("close cleanup directory %s: %s", t->bus, strerror(errno)); - ok = 0; - } - return ok; -} - -static int -rmdirknown(char *path) -{ - if(path[0] == '\0' || rmdir(path) == 0 || errno == ENOENT) - return 1; - return fail("rmdir %s: %s", path, strerror(errno)); + return daemonalive(&t->first); } static int @@ -919,70 +149,42 @@ cleanup(Test *t) closebus(&t->busnew); closebus(&t->busfirst); if(t->ipcnew >= 0){ - if(close(t->ipcnew) < 0){ - fail("close new IPC client: %s", strerror(errno)); - ok = 0; - } + if(close(t->ipcnew) < 0) + ok = fail("close new IPC client: %s", strerror(errno)); t->ipcnew = -1; } if(t->ipcfirst >= 0){ - if(close(t->ipcfirst) < 0){ - fail("close persistent IPC client: %s", strerror(errno)); - ok = 0; - } + if(close(t->ipcfirst) < 0) + ok = fail("close persistent IPC client: %s", strerror(errno)); t->ipcfirst = -1; } - if(t->second > 0){ + if(t->second.pid > 0){ do - n = waitpid(t->second, &status, WNOHANG); + n = waitpid(t->second.pid, &status, WNOHANG); while(n < 0 && errno == EINTR); - if(n == t->second) - t->second = -1; + if(n == t->second.pid) + t->second.pid = -1; else if(n == 0){ - fail("second daemon still running during cleanup"); - ok = 0; - if(!killowned(&t->second, &status)) + ok = fail("second daemon still running during cleanup"); + if(!killdaemon(&t->second, NULL)) ok = 0; }else{ fail("check second daemon during cleanup: %s", strerror(errno)); ok = 0; if(errno == ECHILD) - t->second = -1; - else if(!killowned(&t->second, &status)) + t->second.pid = -1; + else if(!killdaemon(&t->second, NULL)) ok = 0; } } - if(!stopfirst(t)) + if(!stopdaemon(&t->first)) ok = 0; - readpipe(t->firsterrfd, t->firsterr, sizeof t->firsterr, - &t->nfirsterr); - readpipe(t->seconderrfd, t->seconderr, sizeof t->seconderr, - &t->nseconderr); - if(t->firsterrfd >= 0){ - if(close(t->firsterrfd) < 0){ - fail("close first daemon stderr: %s", strerror(errno)); - ok = 0; - } - t->firsterrfd = -1; - } - if(t->seconderrfd >= 0){ - if(close(t->seconderrfd) < 0){ - fail("close second daemon stderr: %s", strerror(errno)); - ok = 0; - } - t->seconderrfd = -1; - } - if(t->socket[0] != '\0' && unlink(t->socket) < 0 && errno != ENOENT){ - fail("remove IPC socket %s: %s", t->socket, strerror(errno)); + if(!closeerrors(&t->first)) + ok = 0; + if(!closeerrors(&t->second)) + ok = 0; + if(!liveclean(&t->l)) ok = 0; - } - if(t->bus[0] != '\0' && !clearbus(t)) ok = 0; - if(!rmdirknown(t->bus)) ok = 0; - if(!rmdirknown(t->ibus)) ok = 0; - if(!rmdirknown(t->config)) ok = 0; - if(!rmdirknown(t->runtime)) ok = 0; - if(!rmdirknown(t->home)) ok = 0; - if(!rmdirknown(t->root)) ok = 0; return ok; } @@ -992,6 +194,7 @@ main(int argc, char **argv) Test test; int ok; + testname = "daemon_collision_test"; if(argc != 3){ fprintf(stderr, "usage: daemon_collision_test strans mapdir\n"); return 2; @@ -1002,7 +205,8 @@ main(int argc, char **argv) if(!cleanup(&test)) ok = 0; if(!ok){ - showerrors(&test); + showerrors(&test.first); + showerrors(&test.second); return 1; } printf("daemon endpoint collision ownership: ok\n"); diff --git a/tests/daemon_failure_test.c b/tests/daemon_failure_test.c index 128ea9e..18749e0 100644 --- a/tests/daemon_failure_test.c +++ b/tests/daemon_failure_test.c @@ -1,212 +1,49 @@ #define _GNU_SOURCE #include #include -#include -#include #include -#include -#include #include #include -#include #include #include #include #include -#include #include +#include "live.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) +waitfailed(Daemon *d, char *badmap) { struct pollfd pfd; int n, status; int64_t deadline; - deadline = nowms(); - if(deadline < 0) - return fail("read monotonic clock: %s", strerror(errno)); - deadline += Failtimeout; + status = 0; + deadline = nowms() + Failtimeout; for(;;){ - n = waitpid(t->child, &status, WNOHANG); - if(n == t->child){ - t->child = -1; - readerrors(t); + n = waitpid(d->pid, &status, WNOHANG); + if(n == d->pid){ + d->pid = -1; + readerrors(d); break; } if(n < 0 && errno == EINTR) continue; if(n < 0){ if(errno == ECHILD) - t->child = -1; + d->pid = -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.fd = d->errfd; pfd.events = POLLIN|POLLHUP; pfd.revents = 0; n = poll(&pfd, 1, n); @@ -215,11 +52,11 @@ waitfailed(Test *t) if(n < 0) return fail("poll daemon: %s", strerror(errno)); if(n > 0) - readerrors(t); + readerrors(d); } 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) + if(strstr(d->err, "can't open") == NULL || strstr(d->err, badmap) == NULL) return fail("daemon did not report the missing map directory"); return 1; } @@ -253,72 +90,47 @@ emptydir(char *path) } static int -checkendpoints(Test *t) +checkendpoints(Live *l) { struct stat st; - if(lstat(t->socket, &st) == 0) - return fail("IPC endpoint exists after failed startup: %s", t->socket); + if(lstat(l->socket, &st) == 0) + return fail("IPC endpoint exists after failed startup: %s", l->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; + return emptydir(l->runtime) && emptydir(l->bus); } int main(int argc, char **argv) { - Test test; + Daemon daemon; + Live live; + char badmap[320]; int ok; + testname = "daemon_failure_test"; if(argc != 2){ fprintf(stderr, "usage: daemon_failure_test strans\n"); return 2; } - ok = setup(&test); + daemoninit(&daemon, "daemon"); + ok = livesetup(&live, "failure"); + if(ok && snprintf(badmap, sizeof badmap, "%s/missing-map", live.root) + >= (int)sizeof badmap) + ok = fail("temporary path is too long"); if(ok) - ok = startchild(&test, argv[1]); + ok = startdaemon(&live, &daemon, argv[1], badmap); if(ok) - ok = waitfailed(&test) && checkendpoints(&test); - if(!cleanup(&test)) + ok = waitfailed(&daemon, badmap) && checkendpoints(&live); + if(!killdaemon(&daemon, NULL)) + ok = 0; + if(!closeerrors(&daemon)) + ok = 0; + if(!liveclean(&live)) ok = 0; if(!ok){ - if(test.nerr != 0) - fprintf(stderr, "daemon_failure_test: daemon stderr:\n%s", test.err); + showerrors(&daemon); return 1; } printf("failed daemon startup leaves no endpoints: ok\n"); diff --git a/tests/daemon_restart_test.c b/tests/daemon_restart_test.c index e6c2cdb..edfd4ae 100644 --- a/tests/daemon_restart_test.c +++ b/tests/daemon_restart_test.c @@ -1,31 +1,24 @@ #define _GNU_SOURCE -#include -#include #include #include #include #include #include -#include #include #include -#include #include #include #include #include -#include #include -#include #include #include "ipc.h" +#include "live.h" +#include "livebus.h" enum { - Calltimeout = 4000, - Starttimeout = 8000, - Stoptimeout = 3000, Ctrlmask = 1<<2, }; @@ -33,136 +26,30 @@ typedef struct Test Test; struct Test { - pid_t first; + Live l; + Daemon first; + Daemon second; + Daemon helper; pid_t firstpid; - pid_t second; - pid_t helper; - int firsterrfd; - int seconderrfd; - int helpererrfd; int ipcfirst; int ipcnew; int busfirstfd; DBusConnection *busfirst; DBusConnection *busnew; - char root[256]; - char runtime[320]; - char config[320]; - char ibus[384]; - char bus[448]; - char home[320]; - char socket[384]; - char addrfile[512]; char firstaddress[512]; char secondaddress[512]; - char firsterr[8192]; - size_t nfirsterr; - char seconderr[8192]; - size_t nseconderr; - char helpererr[2048]; - size_t nhelpererr; }; -static int -fail(char *fmt, ...) -{ - va_list ap; - - fprintf(stderr, "daemon_restart_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; - if(n > 0x7fffffff) - return 0x7fffffff; - return n; -} - -static int -makedir(char *path) -{ - if(mkdir(path, 0700) == 0) - return 1; - return fail("mkdir %s: %s", path, strerror(errno)); -} - -static void -readpipe(int fd, char *buf, size_t cap, size_t *used) -{ - ssize_t n; - - if(fd < 0 || cap == 0) - return; - while(*used + 1 < cap){ - n = read(fd, buf + *used, cap - *used - 1); - if(n > 0){ - *used += n; - continue; - } - if(n < 0 && errno == EINTR) - continue; - break; - } - buf[*used] = '\0'; -} - -static void -showerrors(Test *t) -{ - readpipe(t->firsterrfd, t->firsterr, sizeof t->firsterr, - &t->nfirsterr); - readpipe(t->seconderrfd, t->seconderr, sizeof t->seconderr, - &t->nseconderr); - readpipe(t->helpererrfd, t->helpererr, sizeof t->helpererr, - &t->nhelpererr); - if(t->nfirsterr != 0) - fprintf(stderr, "daemon_restart_test: first daemon stderr:\n%s", - t->firsterr); - if(t->nseconderr != 0) - fprintf(stderr, "daemon_restart_test: second daemon stderr:\n%s", - t->seconderr); - if(t->nhelpererr != 0) - fprintf(stderr, "daemon_restart_test: address helper stderr:\n%s", - t->helpererr); -} - static int setup(Test *t) { struct sigaction sa; memset(t, 0, sizeof *t); - t->first = -1; + daemoninit(&t->first, "first daemon"); + daemoninit(&t->second, "second daemon"); + daemoninit(&t->helper, "old-address helper"); t->firstpid = -1; - t->second = -1; - t->helper = -1; - t->firsterrfd = -1; - t->seconderrfd = -1; - t->helpererrfd = -1; t->ipcfirst = -1; t->ipcnew = -1; t->busfirstfd = -1; @@ -170,207 +57,7 @@ setup(Test *t) sa.sa_handler = SIG_DFL; if(sigemptyset(&sa.sa_mask) < 0 || sigaction(SIGCHLD, &sa, NULL) < 0) return fail("establish exact child ownership: %s", strerror(errno)); - if(nowms() < 0) - return fail("read monotonic clock: %s", strerror(errno)); - snprintf(t->root, sizeof t->root, "/tmp/strans-restart.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) - return fail("temporary path is too long"); - if(!makedir(t->runtime) || !makedir(t->config) || !makedir(t->ibus) || - !makedir(t->bus) || !makedir(t->home)) - return 0; - return 1; -} - -static int -childstderr(int fd) -{ - int flags; - - if(fd != STDERR_FILENO){ - if(dup2(fd, STDERR_FILENO) < 0) - return 0; - close(fd); - return 1; - } - flags = fcntl(fd, F_GETFD); - if(flags < 0 || fcntl(fd, F_SETFD, flags & ~FD_CLOEXEC) < 0) - return 0; - return 1; -} - -static int -startchild(Test *t, char *program, char *mapdir, pid_t *child, int *errfd) -{ - 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(!childstderr(errpipe[1])) - _exit(126); - 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){ - dprintf(STDERR_FILENO, "set daemon environment: %s\n", - strerror(errno)); - _exit(126); - } - execl(program, program, mapdir, (char*)0); - dprintf(STDERR_FILENO, "exec %s: %s\n", program, strerror(errno)); - _exit(127); - } - close(errpipe[1]); - *child = pid; - *errfd = errpipe[0]; - return 1; -} - -static int -findaddress(Test *t) -{ - DIR *dir; - struct dirent *de; - int count, ok; - - dir = opendir(t->bus); - if(dir == NULL){ - fail("open IBus directory %s: %s", t->bus, strerror(errno)); - return -1; - } - count = 0; - ok = 1; - errno = 0; - while((de = readdir(dir)) != NULL){ - if(strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0 || - strstr(de->d_name, ".tmp.") != NULL) - continue; - count++; - if(snprintf(t->addrfile, sizeof t->addrfile, "%s/%s", t->bus, - de->d_name) >= (int)sizeof t->addrfile){ - fail("IBus address path is too long"); - ok = 0; - break; - } - } - if(errno != 0){ - fail("read IBus directory %s: %s", t->bus, strerror(errno)); - ok = 0; - } - if(closedir(dir) < 0){ - fail("close IBus directory %s: %s", t->bus, strerror(errno)); - ok = 0; - } - if(!ok) - return -1; - if(count > 1){ - fail("found %d IBus address files", count); - return -1; - } - return count; -} - -static int -readfile(char *path, char *buf, size_t cap, size_t *nread) -{ - struct stat st; - ssize_t n; - size_t off; - char extra; - int fd, ok; - - fd = open(path, O_RDONLY|O_CLOEXEC); - if(fd < 0) - return fail("open %s: %s", path, strerror(errno)); - ok = 0; - if(fstat(fd, &st) < 0){ - fail("stat open address file %s: %s", path, strerror(errno)); - goto out; - } - if(st.st_size < 0 || (uintmax_t)st.st_size >= cap){ - fail("address file %s is too large", path); - goto out; - } - off = 0; - while(off < (size_t)st.st_size){ - n = read(fd, buf + off, (size_t)st.st_size - off); - if(n < 0 && errno == EINTR) - continue; - if(n <= 0){ - fail("read %s: %s", path, - n == 0 ? "unexpected EOF" : strerror(errno)); - goto out; - } - off += n; - } - do - n = read(fd, &extra, 1); - while(n < 0 && errno == EINTR); - if(n != 0){ - fail("address file %s changed while being read", path); - goto out; - } - buf[off] = '\0'; - *nread = off; - ok = 1; -out: - if(close(fd) < 0){ - fail("close %s: %s", path, strerror(errno)); - ok = 0; - } - return ok; -} - -static int -parseaddress(char *contents, size_t ncontents, char *address, size_t naddress, - pid_t *declared) -{ - char found[512]; - long pid; - int consumed; - - consumed = -1; - if(strlen(contents) != ncontents || - sscanf(contents, "IBUS_ADDRESS=%511[^\n]\nIBUS_DAEMON_PID=%ld\n%n", - found, &pid, &consumed) != 2 || consumed != (int)ncontents || pid <= 0) - return fail("invalid IBus address file contents"); - if(snprintf(address, naddress, "%s", found) >= (int)naddress) - return fail("private IBus address is too long"); - *declared = (pid_t)pid; - return 1; + return livesetup(&t->l, "restart"); } static int @@ -387,388 +74,12 @@ privateaddress(char *address, pid_t pid) fail("IBus address is not the daemon's private abstract address"); } -static int -childalive(pid_t *pid, char *which) -{ - int n, status; - - do - n = waitpid(*pid, &status, WNOHANG); - while(n < 0 && errno == EINTR); - if(n == 0) - return 1; - if(n == *pid){ - fail("%s daemon exited unexpectedly with wait status %#x", which, - status); - *pid = -1; - return 0; - } - if(n < 0){ - fail("check %s daemon %ld: %s", which, (long)*pid, - strerror(errno)); - if(errno == ECHILD) - *pid = -1; - return 0; - } - return fail("waitpid returned the wrong %s child", which); -} - -static int -waitready(Test *t, pid_t *child, int errfd, char *which) -{ - struct pollfd pfd; - struct stat st; - char contents[2048], *address; - size_t ncontents; - pid_t declared; - int count, n, socketready, timeout; - int64_t deadline; - - deadline = nowms(); - if(deadline < 0) - return fail("read monotonic clock before %s startup: %s", which, - strerror(errno)); - deadline += Starttimeout; - address = child == &t->first ? t->firstaddress : t->secondaddress; - for(;;){ - socketready = lstat(t->socket, &st) == 0 && S_ISSOCK(st.st_mode) && - (st.st_mode & 0777) == 0600; - count = findaddress(t); - if(count < 0) - return 0; - if(socketready && count == 1 && - readfile(t->addrfile, contents, sizeof contents, &ncontents) && - parseaddress(contents, ncontents, address, 512, - &declared) && declared == *child && privateaddress(address, declared)) - return 1; - if(!childalive(child, which)) - return 0; - timeout = leftms(deadline); - if(timeout < 0) - return fail("read monotonic clock during %s startup: %s", which, - strerror(errno)); - if(timeout == 0) - return fail("timed out waiting for both %s daemon endpoints", which); - pfd.fd = errfd; - pfd.events = POLLIN|POLLHUP; - pfd.revents = 0; - n = poll(&pfd, 1, timeout > 20 ? 20 : timeout); - if(n < 0 && errno == EINTR) - continue; - if(n < 0) - return fail("poll %s daemon readiness: %s", which, - strerror(errno)); - if(pfd.revents & (POLLERR|POLLNVAL)) - return fail("%s stderr pipe became unusable: %#x", which, - pfd.revents); - if(pfd.revents & (POLLIN|POLLHUP)){ - if(child == &t->first) - readpipe(errfd, t->firsterr, sizeof t->firsterr, - &t->nfirsterr); - else - readpipe(errfd, t->seconderr, sizeof t->seconderr, - &t->nseconderr); - } - } -} - -static int -connectsocket(char *path, int64_t deadline) -{ - struct sockaddr_un addr; - struct pollfd pfd; - socklen_t nerr; - int err, fd, flags, n; - - memset(&addr, 0, sizeof addr); - addr.sun_family = AF_UNIX; - if(snprintf(addr.sun_path, sizeof addr.sun_path, "%s", path) - >= (int)sizeof addr.sun_path){ - errno = ENAMETOOLONG; - return -1; - } - fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); - if(fd < 0) - return -1; - if(connect(fd, (struct sockaddr*)&addr, sizeof addr) < 0 && - errno != EINPROGRESS){ - err = errno; - close(fd); - errno = err; - return -1; - } - for(;;){ - n = leftms(deadline); - if(n <= 0){ - close(fd); - errno = n < 0 ? EIO : ETIMEDOUT; - return -1; - } - pfd.fd = fd; - pfd.events = POLLOUT; - pfd.revents = 0; - n = poll(&pfd, 1, n); - if(n < 0 && errno == EINTR) - continue; - if(n <= 0){ - err = n == 0 ? ETIMEDOUT : errno; - close(fd); - errno = err; - return -1; - } - break; - } - err = 0; - nerr = sizeof err; - if(getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &nerr) < 0 || err != 0){ - if(err == 0) - err = errno; - close(fd); - errno = err; - return -1; - } - flags = fcntl(fd, F_GETFL); - if(flags < 0 || fcntl(fd, F_SETFL, flags & ~O_NONBLOCK) < 0){ - err = errno; - close(fd); - errno = err; - return -1; - } - return fd; -} - -/* 1 is complete, 0 is peer closure, and -1 is a timeout or I/O error. */ -static int -readuntil(int fd, void *buf, size_t n, int64_t deadline) -{ - struct pollfd pfd; - unsigned char *p; - ssize_t r; - int timeout; - - p = buf; - while(n > 0){ - timeout = leftms(deadline); - if(timeout <= 0){ - errno = timeout < 0 ? EIO : ETIMEDOUT; - return -1; - } - pfd.fd = fd; - pfd.events = POLLIN; - pfd.revents = 0; - r = poll(&pfd, 1, timeout); - if(r < 0 && errno == EINTR) - continue; - if(r <= 0){ - if(r == 0) - errno = ETIMEDOUT; - return -1; - } - r = recv(fd, p, n, 0); - if(r < 0 && errno == EINTR) - continue; - if(r < 0) - return -1; - if(r == 0) - return 0; - p += r; - n -= r; - } - return 1; -} - -static int -ipcrequest(int fd, uint32_t mod, uint32_t key, unsigned char *want, - size_t nwant, char *where) -{ - unsigned char req[Ipcreqsz], got[16]; - int rv; - - if(nwant > sizeof got) - return fail("%s expected IPC response is too large", where); - ipcpackreq(req, 1, mod, key); - if(ipcsend(fd, req, sizeof req) < 0) - return fail("send %s IPC request: %s", where, strerror(errno)); - rv = readuntil(fd, got, nwant, nowms() + Calltimeout); - if(rv != 1) - return fail("read %s IPC response: %s", where, - rv == 0 ? "peer closed" : strerror(errno)); - if(memcmp(got, want, nwant) != 0) - return fail("%s IPC response did not match", where); - return 1; -} - -static int -ipcprobe(int fd, char *where) -{ - unsigned char empty[] = {0, 0, 0, 0, 0}; - - return ipcrequest(fd, 0, Kmodfirst, empty, sizeof empty, where); -} - -static void -closebus(DBusConnection **conn) -{ - if(*conn == NULL) - return; - dbus_connection_close(*conn); - dbus_connection_unref(*conn); - *conn = NULL; -} - -static DBusMessage* -sendcall(DBusConnection *conn, DBusMessage *m, char *where) -{ - DBusMessage *reply; - DBusPendingCall *pending; - int timeout; - int64_t deadline; - - if(m == NULL){ - fail("allocate %s call", where); - return NULL; - } - pending = NULL; - if(!dbus_connection_send_with_reply(conn, m, &pending, Calltimeout) || - pending == NULL){ - dbus_message_unref(m); - fail("queue %s call", where); - return NULL; - } - dbus_message_unref(m); - deadline = nowms() + Calltimeout; - while(!dbus_pending_call_get_completed(pending)){ - timeout = leftms(deadline); - if(timeout <= 0){ - fail("timed out waiting for %s", where); - goto fail; - } - if(!dbus_connection_read_write_dispatch(conn, timeout)){ - fail("connection closed waiting for %s", where); - goto fail; - } - } - reply = dbus_pending_call_steal_reply(pending); - dbus_pending_call_unref(pending); - if(reply == NULL){ - fail("%s completed without a reply", where); - return NULL; - } - if(dbus_message_get_type(reply) != DBUS_MESSAGE_TYPE_METHOD_RETURN){ - fail("%s returned D-Bus message type %d", where, - dbus_message_get_type(reply)); - dbus_message_unref(reply); - return NULL; - } - return reply; -fail: - dbus_pending_call_cancel(pending); - dbus_pending_call_unref(pending); - return NULL; -} - -static DBusConnection* -openbus(char *address, char *where) -{ - DBusConnection *conn; - DBusError err; - - dbus_error_init(&err); - conn = dbus_connection_open_private(address, &err); - if(conn == NULL){ - fail("open %s private IBus connection: %s", where, - err.message != NULL ? err.message : "D-Bus error"); - dbus_error_free(&err); - return NULL; - } - dbus_error_free(&err); - dbus_connection_set_exit_on_disconnect(conn, FALSE); - return conn; -} - -static int -hello(DBusConnection *conn, char *name, size_t nname, char *where) -{ - DBusMessage *m, *reply; - DBusError err; - const char *s; - - m = dbus_message_new_method_call("org.freedesktop.DBus", - "/org/freedesktop/DBus", "org.freedesktop.DBus", "Hello"); - reply = sendcall(conn, m, where); - if(reply == NULL) - return 0; - dbus_error_init(&err); - if(!dbus_message_has_signature(reply, "s") || - !dbus_message_get_args(reply, &err, DBUS_TYPE_STRING, &s, - DBUS_TYPE_INVALID)){ - dbus_message_unref(reply); - dbus_error_free(&err); - return fail("%s returned an invalid Hello reply", where); - } - if(snprintf(name, nname, "%s", s) >= (int)nname){ - dbus_error_free(&err); - dbus_message_unref(reply); - return fail("%s Hello name is too long", where); - } - dbus_error_free(&err); - dbus_message_unref(reply); - return name[0] == ':' || fail("%s returned invalid Hello name %s", - where, name); -} - -static int -createcontext(DBusConnection *conn, char *path, size_t npath, char *where) -{ - DBusMessage *m, *reply; - DBusError err; - const char *client, *p; - - client = "daemon-restart-test"; - m = dbus_message_new_method_call("org.freedesktop.IBus", - "/org/freedesktop/IBus", "org.freedesktop.IBus", - "CreateInputContext"); - if(m == NULL || !dbus_message_append_args(m, DBUS_TYPE_STRING, &client, - DBUS_TYPE_INVALID)){ - if(m != NULL) - dbus_message_unref(m); - return fail("build %s CreateInputContext call", where); - } - reply = sendcall(conn, m, where); - if(reply == NULL) - return 0; - dbus_error_init(&err); - if(!dbus_message_has_signature(reply, "o") || - !dbus_message_get_args(reply, &err, DBUS_TYPE_OBJECT_PATH, &p, - DBUS_TYPE_INVALID)){ - dbus_message_unref(reply); - dbus_error_free(&err); - return fail("%s returned an invalid context reply", where); - } - if(snprintf(path, npath, "%s", p) >= (int)npath){ - dbus_error_free(&err); - dbus_message_unref(reply); - return fail("%s context path is too long", where); - } - dbus_error_free(&err); - dbus_message_unref(reply); - return path[0] == '/' || fail("%s returned invalid context path %s", - where, path); -} - -static DBusMessage* -contextcall(char *path, char *member) -{ - return dbus_message_new_method_call("org.freedesktop.IBus", path, - "org.freedesktop.IBus.InputContext", member); -} - static int focusin(DBusConnection *conn, char *path) { DBusMessage *reply; - reply = sendcall(conn, contextcall(path, "FocusIn"), + reply = callret(conn, contextcall(path, "FocusIn"), "replacement FocusIn"); if(reply == NULL) return 0; @@ -798,7 +109,7 @@ keycall(DBusConnection *conn, char *path, dbus_uint32_t sym, dbus_message_unref(m); return fail("build %s ProcessKeyEvent call", where); } - reply = sendcall(conn, m, where); + reply = callret(conn, m, where); if(reply == NULL) return 0; dbus_error_init(&err); @@ -818,20 +129,15 @@ keycall(DBusConnection *conn, char *path, dbus_uint32_t sym, static int openpersistent(Test *t) { - char name[32], path[96]; - int64_t deadline; - - deadline = nowms() + Calltimeout; - t->ipcfirst = connectsocket(t->socket, deadline); + t->ipcfirst = connectsocket(t->l.socket, nowms() + Calltimeout); if(t->ipcfirst < 0) return fail("connect persistent IPC client: %s", strerror(errno)); if(!ipcprobe(t->ipcfirst, "persistent pre-crash")) return 0; t->busfirst = openbus(t->firstaddress, "persistent pre-crash"); if(t->busfirst == NULL || - !hello(t->busfirst, name, sizeof name, "persistent pre-crash Hello") || - !createcontext(t->busfirst, path, sizeof path, - "persistent pre-crash context")) + !hello(t->busfirst, NULL, 0, "persistent pre-crash Hello") || + !createcontext(t->busfirst, NULL, 0, "persistent pre-crash context")) return 0; if(!dbus_connection_get_unix_fd(t->busfirst, &t->busfirstfd)) return fail("persistent IBus connection has no Unix descriptor"); @@ -841,73 +147,14 @@ openpersistent(Test *t) static int hardcrash(Test *t) { - struct pollfd pfd; - pid_t pid; - int e, n, status, timeout; - int64_t deadline; + int status; - if(!childalive(&t->first, "first")) + status = 0; + if(!daemonalive(&t->first) || !killdaemon(&t->first, &status)) return 0; - pid = t->first; - if(kill(pid, SIGKILL) < 0){ - e = errno; - do - n = waitpid(pid, &status, WNOHANG); - while(n < 0 && errno == EINTR); - if(n == pid){ - t->first = -1; - return fail("first daemon exited before planned SIGKILL with status %#x", - status); - } - if(n < 0 && errno == ECHILD) - t->first = -1; - return fail("kill -9 first daemon %ld: %s", (long)pid, strerror(e)); - } - deadline = nowms(); - if(deadline < 0) - return fail("read monotonic clock before first daemon crash: %s", - strerror(errno)); - deadline += Stoptimeout; - for(;;){ - do - n = waitpid(pid, &status, WNOHANG); - while(n < 0 && errno == EINTR); - if(n == pid){ - t->first = -1; - break; - } - if(n < 0){ - e = errno; - if(e == ECHILD) - t->first = -1; - return fail("reap first daemon %ld after SIGKILL: %s", (long)pid, - strerror(e)); - } - timeout = leftms(deadline); - if(timeout < 0) - return fail("read monotonic clock during first daemon crash: %s", - strerror(errno)); - if(timeout == 0) - return fail("first daemon %ld was not reaped after SIGKILL", - (long)pid); - pfd.fd = t->firsterrfd; - pfd.events = POLLIN|POLLHUP; - pfd.revents = 0; - n = poll(&pfd, 1, timeout); - if(n < 0 && errno == EINTR) - continue; - if(n < 0) - return fail("poll first daemon crash: %s", strerror(errno)); - if(pfd.revents & (POLLERR|POLLNVAL)) - return fail("first daemon stderr pipe became unusable: %#x", - pfd.revents); - if(pfd.revents & (POLLIN|POLLHUP)) - readpipe(t->firsterrfd, t->firsterr, sizeof t->firsterr, - &t->nfirsterr); - } + readerrors(&t->first); if(!WIFSIGNALED(status) || WTERMSIG(status) != SIGKILL) return fail("first daemon hard crash had wait status %#x", status); - readpipe(t->firsterrfd, t->firsterr, sizeof t->firsterr, &t->nfirsterr); return 1; } @@ -923,7 +170,7 @@ waitipcclosed(Test *t) deadline = nowms() + Calltimeout; for(;;){ timeout = leftms(deadline); - if(timeout <= 0) + if(timeout == 0) return fail("persistent IPC connection did not disconnect promptly"); pfd.fd = t->ipcfirst; pfd.events = POLLIN|POLLHUP|POLLERR; @@ -959,7 +206,7 @@ waitbusclosed(Test *t) if(!dbus_connection_get_is_connected(t->busfirst)) return 1; timeout = leftms(deadline); - if(timeout <= 0) + if(timeout == 0) return fail("persistent IBus connection did not disconnect promptly"); pfd.fd = t->busfirstfd; pfd.events = POLLIN|POLLHUP|POLLERR; @@ -984,13 +231,13 @@ checkstale(Test *t) size_t ncontents; pid_t declared; - if(lstat(t->socket, &st) < 0) + if(lstat(t->l.socket, &st) < 0) return fail("stale IPC socket disappeared: %s", strerror(errno)); if(!S_ISSOCK(st.st_mode)) return fail("stale IPC endpoint is not a socket"); - if(lstat(t->addrfile, &st) < 0) + if(lstat(t->l.addrfile, &st) < 0) return fail("stale IBus address file disappeared: %s", strerror(errno)); - if(!readfile(t->addrfile, contents, sizeof contents, &ncontents) || + if(!readfile(t->l.addrfile, contents, sizeof contents, &ncontents) || !parseaddress(contents, ncontents, address, sizeof address, &declared)) return 0; if(declared != t->firstpid || strcmp(address, t->firstaddress) != 0) @@ -1008,8 +255,7 @@ helpermain(char *address) conn = dbus_connection_open_private(address, &err); if(conn != NULL){ dbus_connection_set_exit_on_disconnect(conn, FALSE); - dbus_connection_close(conn); - dbus_connection_unref(conn); + closebus(&conn); dbus_error_free(&err); fprintf(stderr, "old private address unexpectedly accepted a connection\n"); return 1; @@ -1023,94 +269,6 @@ helpermain(char *address) return 0; } -static int -killreap(pid_t *child, int errfd, char *which) -{ - struct pollfd pfd; - pid_t pid; - int e, n, ok, status, timeout; - int64_t deadline; - - if(*child <= 0) - return 1; - pid = *child; - ok = 1; - if(kill(pid, SIGKILL) < 0 && errno != ESRCH){ - fail("kill -9 %s %ld: %s", which, (long)pid, strerror(errno)); - ok = 0; - } - deadline = nowms(); - if(deadline < 0){ - fail("read monotonic clock before reaping %s %ld: %s", which, - (long)pid, strerror(errno)); - ok = 0; - }else - deadline += Stoptimeout; - while(deadline >= 0){ - do - n = waitpid(pid, &status, WNOHANG); - while(n < 0 && errno == EINTR); - if(n == pid){ - *child = -1; - return ok; - } - if(n < 0){ - e = errno; - if(e == ECHILD) - *child = -1; - fail("reap %s %ld after SIGKILL: %s", which, (long)pid, - strerror(e)); - return 0; - } - timeout = leftms(deadline); - if(timeout < 0){ - fail("read monotonic clock while reaping %s %ld: %s", which, - (long)pid, strerror(errno)); - ok = 0; - break; - } - if(timeout == 0){ - fail("%s %ld was not reaped promptly after SIGKILL", which, - (long)pid); - ok = 0; - break; - } - pfd.fd = errfd; - pfd.events = 0; - pfd.revents = 0; - n = poll(&pfd, 1, timeout); - if(n < 0 && errno == EINTR) - continue; - if(n < 0){ - fail("poll %s %ld after SIGKILL: %s", which, (long)pid, - strerror(errno)); - ok = 0; - break; - } - if(pfd.revents & (POLLERR|POLLNVAL)){ - fail("%s %ld stderr pipe became unusable: %#x", which, - (long)pid, pfd.revents); - ok = 0; - break; - } - } - /* Keep exact child ownership until the mandatory post-SIGKILL reap. */ - do - n = waitpid(pid, &status, 0); - while(n < 0 && errno == EINTR); - if(n == pid) - *child = -1; - else{ - e = errno; - if(n < 0 && e == ECHILD) - *child = -1; - fail("final reap of %s %ld after SIGKILL: %s", which, (long)pid, - n < 0 ? strerror(e) : "wrong child"); - ok = 0; - } - return ok; -} - static int rejectold(Test *t, char *self, char *where) { @@ -1130,8 +288,9 @@ rejectold(Test *t, char *self, char *where) } if(pid == 0){ close(errpipe[0]); - if(!childstderr(errpipe[1])) + 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) @@ -1144,36 +303,32 @@ rejectold(Test *t, char *self, char *where) _exit(127); } close(errpipe[1]); - t->helper = pid; - t->helpererrfd = errpipe[0]; - t->nhelpererr = 0; - t->helpererr[0] = '\0'; + t->helper.pid = pid; + t->helper.errfd = errpipe[0]; deadline = nowms() + Calltimeout; for(;;){ do n = waitpid(pid, &status, WNOHANG); while(n < 0 && errno == EINTR); if(n == pid){ - t->helper = -1; + t->helper.pid = -1; break; } if(n < 0){ - fail("wait old-address helper %ld: %s", (long)pid, - strerror(errno)); + fail("wait old-address helper %ld: %s", (long)pid, strerror(errno)); if(errno == ECHILD) - t->helper = -1; + t->helper.pid = -1; else - killreap(&t->helper, t->helpererrfd, - "old-address helper"); + killdaemon(&t->helper, NULL); return 0; } timeout = leftms(deadline); - if(timeout <= 0){ + if(timeout == 0){ fail("old private address open did not fail promptly %s", where); - killreap(&t->helper, t->helpererrfd, "old-address helper"); + killdaemon(&t->helper, NULL); return 0; } - pfd.fd = t->helpererrfd; + pfd.fd = t->helper.errfd; pfd.events = POLLIN|POLLHUP; pfd.revents = 0; n = poll(&pfd, 1, timeout); @@ -1181,25 +336,19 @@ rejectold(Test *t, char *self, char *where) continue; if(n < 0){ fail("poll old-address helper: %s", strerror(errno)); - killreap(&t->helper, t->helpererrfd, "old-address helper"); + killdaemon(&t->helper, NULL); return 0; } if(pfd.revents & (POLLERR|POLLNVAL)){ fail("old-address helper pipe became unusable: %#x", pfd.revents); - killreap(&t->helper, t->helpererrfd, "old-address helper"); + killdaemon(&t->helper, NULL); return 0; } if(pfd.revents & (POLLIN|POLLHUP)) - readpipe(t->helpererrfd, t->helpererr, sizeof t->helpererr, - &t->nhelpererr); + readerrors(&t->helper); } - readpipe(t->helpererrfd, t->helpererr, sizeof t->helpererr, - &t->nhelpererr); - if(close(t->helpererrfd) < 0){ - t->helpererrfd = -1; - return fail("close old-address helper pipe: %s", strerror(errno)); - } - t->helpererrfd = -1; + if(!closeerrors(&t->helper)) + return 0; if(!WIFEXITED(status) || WEXITSTATUS(status) != 0) return fail("old private address helper failed %s with status %#x", where, status); @@ -1211,9 +360,9 @@ openreplacement(Test *t) { unsigned char selectjp[] = {1, 0, 0, 0, 0}; unsigned char keyk[] = {1, 0, 0, 1, 0, 'k'}; - char name[32], path[96]; + char path[96]; - t->ipcnew = connectsocket(t->socket, nowms() + Calltimeout); + t->ipcnew = connectsocket(t->l.socket, nowms() + Calltimeout); if(t->ipcnew < 0) return fail("connect replacement IPC client: %s", strerror(errno)); if(!ipcrequest(t->ipcnew, Mctrl, 'n', selectjp, sizeof selectjp, @@ -1223,182 +372,14 @@ openreplacement(Test *t) return 0; t->busnew = openbus(t->secondaddress, "replacement"); if(t->busnew == NULL || - !hello(t->busnew, name, sizeof name, "replacement Hello") || + !hello(t->busnew, NULL, 0, "replacement Hello") || !createcontext(t->busnew, path, sizeof path, "replacement context") || !focusin(t->busnew, path) || !keycall(t->busnew, path, 'n', Ctrlmask, 1, "replacement select Japanese") || !keycall(t->busnew, path, 'k', 0, 1, "replacement real key")) return 0; - return childalive(&t->second, "second"); -} - -static int -stopsecond(Test *t) -{ - struct pollfd pfd; - pid_t pid; - int n, ok, reaped, status, timeout; - int64_t deadline; - - if(t->second <= 0) - return 1; - if(!childalive(&t->second, "second")) - return 0; - pid = t->second; - ok = 1; - if(kill(pid, SIGTERM) < 0){ - fail("kill second daemon %ld: %s", (long)pid, strerror(errno)); - ok = 0; - } - reaped = 0; - deadline = nowms() + Stoptimeout; - while(t->second > 0){ - do - n = waitpid(pid, &status, WNOHANG); - while(n < 0 && errno == EINTR); - if(n == pid){ - reaped = 1; - t->second = -1; - break; - } - if(n < 0){ - fail("waitpid second daemon %ld: %s", (long)pid, - strerror(errno)); - if(errno == ECHILD) - t->second = -1; - else if(!killreap(&t->second, t->seconderrfd, "second daemon")) - ok = 0; - ok = 0; - break; - } - timeout = leftms(deadline); - if(timeout <= 0){ - fail("second daemon %ld did not stop after SIGTERM", (long)pid); - ok = 0; - if(!killreap(&t->second, t->seconderrfd, "second daemon")) - ok = 0; - break; - } - pfd.fd = t->seconderrfd; - pfd.events = POLLIN|POLLHUP; - pfd.revents = 0; - n = poll(&pfd, 1, timeout); - if(n < 0 && errno == EINTR) - continue; - if(n < 0 || (pfd.revents & (POLLERR|POLLNVAL))){ - fail("poll second daemon termination: %s", - n < 0 ? strerror(errno) : "stderr pipe failure"); - ok = 0; - if(!killreap(&t->second, t->seconderrfd, "second daemon")) - ok = 0; - break; - } - if(pfd.revents & (POLLIN|POLLHUP)) - readpipe(t->seconderrfd, t->seconderr, sizeof t->seconderr, - &t->nseconderr); - } - if(reaped && - !((WIFSIGNALED(status) && WTERMSIG(status) == SIGTERM) || - (WIFEXITED(status) && WEXITSTATUS(status) == 1))){ - fail("second daemon exited with unexpected wait status %#x", status); - ok = 0; - } - return ok; -} - -static int clearfd(int); - -static int -removeentry(int dirfd, char *name) -{ - struct stat st; - int fd, ok; - - if(fstatat(dirfd, name, &st, AT_SYMLINK_NOFOLLOW) < 0) - return fail("stat cleanup entry %s: %s", name, strerror(errno)); - if(!S_ISDIR(st.st_mode)){ - if(unlinkat(dirfd, name, 0) == 0 || errno == ENOENT) - return 1; - return fail("remove cleanup entry %s: %s", name, strerror(errno)); - } - fd = openat(dirfd, name, O_RDONLY|O_CLOEXEC|O_DIRECTORY|O_NOFOLLOW); - if(fd < 0) - return fail("open cleanup directory %s: %s", name, strerror(errno)); - ok = clearfd(fd); - if(close(fd) < 0){ - fail("close cleanup directory %s: %s", name, strerror(errno)); - ok = 0; - } - if(unlinkat(dirfd, name, AT_REMOVEDIR) < 0 && errno != ENOENT){ - fail("remove cleanup directory %s: %s", name, strerror(errno)); - ok = 0; - } - return ok; -} - -static int -clearfd(int fd) -{ - DIR *dir; - struct dirent *de; - int copy, ok; - - copy = dup(fd); - if(copy < 0) - return fail("duplicate cleanup directory: %s", strerror(errno)); - dir = fdopendir(copy); - if(dir == NULL){ - close(copy); - return fail("open cleanup directory stream: %s", strerror(errno)); - } - ok = 1; - errno = 0; - while((de = readdir(dir)) != NULL){ - if(strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0) - continue; - if(!removeentry(fd, de->d_name)) - ok = 0; - errno = 0; - } - if(errno != 0){ - fail("read cleanup directory: %s", strerror(errno)); - ok = 0; - } - if(closedir(dir) < 0){ - fail("close cleanup directory stream: %s", strerror(errno)); - ok = 0; - } - return ok; -} - -static int -removeroot(Test *t) -{ - struct stat st; - int fd, ok; - - if(t->root[0] == '\0') - return 1; - fd = open(t->root, O_RDONLY|O_CLOEXEC|O_DIRECTORY|O_NOFOLLOW); - if(fd < 0) - return errno == ENOENT || fail("open cleanup root %s: %s", t->root, - strerror(errno)); - ok = clearfd(fd); - if(close(fd) < 0){ - fail("close cleanup root %s: %s", t->root, strerror(errno)); - ok = 0; - } - if(rmdir(t->root) < 0 && errno != ENOENT){ - fail("rmdir %s: %s", t->root, strerror(errno)); - ok = 0; - } - errno = 0; - if(lstat(t->root, &st) == 0 || errno != ENOENT){ - fail("temporary root still exists after cleanup"); - ok = 0; - } - return ok; + return daemonalive(&t->second); } static int @@ -1410,53 +391,28 @@ cleanup(Test *t) closebus(&t->busnew); closebus(&t->busfirst); if(t->ipcnew >= 0){ - if(close(t->ipcnew) < 0){ - fail("close replacement IPC client: %s", strerror(errno)); - ok = 0; - } + if(close(t->ipcnew) < 0) + ok = fail("close replacement IPC client: %s", strerror(errno)); t->ipcnew = -1; } if(t->ipcfirst >= 0){ - if(close(t->ipcfirst) < 0){ - fail("close persistent IPC client: %s", strerror(errno)); - ok = 0; - } + if(close(t->ipcfirst) < 0) + ok = fail("close persistent IPC client: %s", strerror(errno)); t->ipcfirst = -1; } - if(t->helper > 0 && !killreap(&t->helper, t->helpererrfd, - "old-address helper")) + if(!killdaemon(&t->helper, NULL)) ok = 0; - if(t->first > 0 && !killreap(&t->first, t->firsterrfd, "first daemon")) + if(!killdaemon(&t->first, NULL)) ok = 0; - if(!stopsecond(t)) + if(!stopdaemon(&t->second)) ok = 0; - readpipe(t->firsterrfd, t->firsterr, sizeof t->firsterr, &t->nfirsterr); - readpipe(t->seconderrfd, t->seconderr, sizeof t->seconderr, - &t->nseconderr); - readpipe(t->helpererrfd, t->helpererr, sizeof t->helpererr, - &t->nhelpererr); - if(t->firsterrfd >= 0){ - if(close(t->firsterrfd) < 0){ - fail("close first daemon stderr: %s", strerror(errno)); - ok = 0; - } - t->firsterrfd = -1; - } - if(t->seconderrfd >= 0){ - if(close(t->seconderrfd) < 0){ - fail("close second daemon stderr: %s", strerror(errno)); - ok = 0; - } - t->seconderrfd = -1; - } - if(t->helpererrfd >= 0){ - if(close(t->helpererrfd) < 0){ - fail("close old-address helper stderr: %s", strerror(errno)); - ok = 0; - } - t->helpererrfd = -1; - } - if(!removeroot(t)) + if(!closeerrors(&t->first)) + ok = 0; + if(!closeerrors(&t->second)) + ok = 0; + if(!closeerrors(&t->helper)) + ok = 0; + if(!liveclean(&t->l)) ok = 0; return ok; } @@ -1464,11 +420,11 @@ cleanup(Test *t) static int runrestart(Test *t, char *self, char *program, char *mapdir) { - if(!startchild(t, program, mapdir, &t->first, &t->firsterrfd)) + if(!startdaemon(&t->l, &t->first, program, mapdir)) return 0; - t->firstpid = t->first; - if(!waitready(t, &t->first, t->firsterrfd, "first") || - !openpersistent(t)) + t->firstpid = t->first.pid; + if(!waitready(&t->l, &t->first, t->firstaddress, sizeof t->firstaddress) || + !privateaddress(t->firstaddress, t->firstpid) || !openpersistent(t)) return 0; if(!hardcrash(t) || !waitipcclosed(t) || !waitbusclosed(t) || !checkstale(t) || !rejectold(t, self, "after hard crash")) @@ -1478,11 +434,13 @@ runrestart(Test *t, char *self, char *program, char *mapdir) return fail("close disconnected persistent IPC client: %s", strerror(errno)); t->ipcfirst = -1; - if(!startchild(t, program, mapdir, &t->second, &t->seconderrfd) || - !waitready(t, &t->second, t->seconderrfd, "second") || + if(!startdaemon(&t->l, &t->second, program, mapdir) || + !waitready(&t->l, &t->second, t->secondaddress, + sizeof t->secondaddress) || + !privateaddress(t->secondaddress, t->second.pid) || !openreplacement(t)) return 0; - return childalive(&t->second, "second"); + return daemonalive(&t->second); } int @@ -1491,6 +449,7 @@ main(int argc, char **argv) Test test; int ok; + testname = "daemon_restart_test"; if(argc == 3 && strcmp(argv[1], "--reject-address") == 0) return helpermain(argv[2]); if(argc != 3){ @@ -1503,7 +462,9 @@ main(int argc, char **argv) if(!cleanup(&test)) ok = 0; if(!ok){ - showerrors(&test); + showerrors(&test.first); + showerrors(&test.second); + showerrors(&test.helper); return 1; } printf("daemon hard-crash endpoint recovery: ok\n"); diff --git a/tests/gtk_live_test.c b/tests/gtk_live_test.c index b926629..0f15d30 100644 --- a/tests/gtk_live_test.c +++ b/tests/gtk_live_test.c @@ -5,7 +5,6 @@ #include #include #include -#include #include #include #include @@ -19,6 +18,7 @@ #include #include "ipc.h" +#include "live.h" enum { @@ -85,42 +85,6 @@ struct Siglog int prechanged; }; -static int -fail(char *fmt, ...) -{ - va_list ap; - - fprintf(stderr, "gtk_live_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 0; - return (int64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000; -} - -static int -leftms(int64_t deadline) -{ - int64_t n; - - n = deadline - nowms(); - if(n <= 0) - return 0; - if(n > 0x7fffffff) - return 0x7fffffff; - return n; -} - static void record(Server *s, Event *e) { @@ -698,6 +662,7 @@ main(int argc, char **argv) int first, i, ox, oy, scale, closes, ok, rootmade, serverup; int64_t stalledms, started; + testname = "gtk_live_test"; ctx = oldctx = badctx = NULL; top = NULL; xvfb = -1; diff --git a/tests/ibus_live_test.c b/tests/ibus_live_test.c index a0b5cea..fd1572e 100644 --- a/tests/ibus_live_test.c +++ b/tests/ibus_live_test.c @@ -1,574 +1,23 @@ #define _GNU_SOURCE -#include #include -#include -#include #include #include -#include #include #include #include #include -#include -#include #include #include -#include #include +#include "live.h" +#include "livebus.h" + enum { Maxconnections = 64, - Calltimeout = 4000, - Starttimeout = 8000, - Stoptimeout = 3000, }; -typedef struct Daemon Daemon; -struct Daemon -{ - pid_t pid; - int errfd; - char root[256]; - char runtime[320]; - char config[320]; - char ibus[384]; - char bus[448]; - char home[320]; - char socket[384]; - char addrfile[512]; - char address[512]; - char err[4096]; - size_t nerr; -}; - -static int -fail(char *fmt, ...) -{ - va_list ap; - - fprintf(stderr, "ibus_live_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 0; - return (int64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000; -} - -static int -leftms(int64_t deadline) -{ - int64_t n; - - n = deadline - nowms(); - if(n <= 0) - return 0; - if(n > 0x7fffffff) - return 0x7fffffff; - return n; -} - -static int -makedir(char *path) -{ - if(mkdir(path, 0700) == 0) - return 1; - return fail("mkdir %s: %s", path, strerror(errno)); -} - -static void -readerrors(Daemon *d) -{ - ssize_t n; - - if(d->errfd < 0) - return; - while(d->nerr + 1 < sizeof d->err){ - n = read(d->errfd, d->err + d->nerr, - sizeof d->err - d->nerr - 1); - if(n > 0){ - d->nerr += n; - continue; - } - if(n < 0 && errno == EINTR) - continue; - break; - } - d->err[d->nerr] = '\0'; -} - -static void -showerrors(Daemon *d) -{ - readerrors(d); - if(d->nerr != 0) - fprintf(stderr, "ibus_live_test: daemon stderr:\n%s", d->err); -} - -static int -readaddress(Daemon *d) -{ - FILE *fp; - char line[768], *p; - long pid; - int haveaddr, havepid; - - fp = fopen(d->addrfile, "r"); - if(fp == NULL) - return fail("open %s: %s", d->addrfile, strerror(errno)); - haveaddr = havepid = 0; - pid = -1; - while(fgets(line, sizeof line, fp) != NULL){ - p = strchr(line, '\n'); - if(p != NULL) - *p = '\0'; - if(strncmp(line, "IBUS_ADDRESS=", 13) == 0){ - if(snprintf(d->address, sizeof d->address, "%s", line + 13) - >= (int)sizeof d->address) - break; - haveaddr = d->address[0] != '\0'; - }else if(strncmp(line, "IBUS_DAEMON_PID=", 16) == 0){ - errno = 0; - pid = strtol(line + 16, &p, 10); - havepid = errno == 0 && *p == '\0'; - } - } - if(fclose(fp) != 0) - return fail("close %s: %s", d->addrfile, strerror(errno)); - if(!haveaddr || !havepid) - return fail("invalid IBus address file %s", d->addrfile); - if(pid != d->pid) - return fail("address file PID %ld, expected %ld", pid, (long)d->pid); - return 1; -} - -static int -waitaddress(Daemon *d, int notifyfd) -{ - struct pollfd pfd[2]; - char buf[4096]; - struct inotify_event *ev; - ssize_t n; - int i, status, timeout; - int64_t deadline; - - deadline = nowms() + Starttimeout; - for(;;){ - if(waitpid(d->pid, &status, WNOHANG) == d->pid){ - d->pid = -1; - readerrors(d); - return fail("daemon exited before publishing IBus address"); - } - timeout = leftms(deadline); - if(timeout == 0) - return fail("timed out waiting for IBus address file"); - pfd[0].fd = notifyfd; - pfd[0].events = POLLIN; - pfd[0].revents = 0; - pfd[1].fd = d->errfd; - pfd[1].events = POLLIN; - pfd[1].revents = 0; - i = poll(pfd, 2, timeout); - if(i < 0 && errno == EINTR) - continue; - if(i < 0) - return fail("poll for IBus address: %s", strerror(errno)); - if(i == 0) - continue; - if(pfd[1].revents != 0) - readerrors(d); - if(!(pfd[0].revents & POLLIN)) - continue; - n = read(notifyfd, buf, sizeof buf); - if(n < 0 && (errno == EINTR || errno == EAGAIN)) - continue; - if(n < 0) - return fail("read IBus directory watch: %s", strerror(errno)); - for(i = 0; i < n; i += sizeof *ev + ev->len){ - ev = (struct inotify_event*)(buf + i); - if(!(ev->mask & IN_MOVED_TO) || ev->len == 0) - continue; - if(snprintf(d->addrfile, sizeof d->addrfile, "%s/%s", - d->bus, ev->name) >= (int)sizeof d->addrfile) - return fail("IBus address path is too long"); - return readaddress(d); - } - } -} - -static int -startdaemon(Daemon *d, char *program, char *mapdir) -{ - int fd, notifyfd, watch, errpipe[2]; - long maxfd; - pid_t pid; - - memset(d, 0, sizeof *d); - d->pid = -1; - d->errfd = -1; - snprintf(d->root, sizeof d->root, "/tmp/strans-ibus.XXXXXX"); - if(mkdtemp(d->root) == NULL) - return fail("mkdtemp: %s", strerror(errno)); - if(snprintf(d->runtime, sizeof d->runtime, "%s/runtime", d->root) - >= (int)sizeof d->runtime || - snprintf(d->config, sizeof d->config, "%s/config", d->root) - >= (int)sizeof d->config || - snprintf(d->ibus, sizeof d->ibus, "%s/ibus", d->config) - >= (int)sizeof d->ibus || - snprintf(d->bus, sizeof d->bus, "%s/bus", d->ibus) - >= (int)sizeof d->bus || - snprintf(d->home, sizeof d->home, "%s/home", d->root) - >= (int)sizeof d->home || - snprintf(d->socket, sizeof d->socket, "%s/strans.sock", d->runtime) - >= (int)sizeof d->socket) - return fail("temporary path is too long"); - if(!makedir(d->runtime) || !makedir(d->config) || !makedir(d->ibus) || - !makedir(d->bus) || !makedir(d->home)) - return 0; - notifyfd = inotify_init1(IN_CLOEXEC|IN_NONBLOCK); - if(notifyfd < 0) - return fail("inotify_init1: %s", strerror(errno)); - watch = inotify_add_watch(notifyfd, d->bus, IN_MOVED_TO); - if(watch < 0){ - close(notifyfd); - return fail("inotify_add_watch: %s", strerror(errno)); - } - if(pipe2(errpipe, O_CLOEXEC|O_NONBLOCK) < 0){ - close(notifyfd); - return fail("pipe2: %s", strerror(errno)); - } - pid = fork(); - if(pid < 0){ - close(errpipe[0]); - close(errpipe[1]); - close(notifyfd); - return fail("fork: %s", strerror(errno)); - } - if(pid == 0){ - close(errpipe[0]); - close(notifyfd); - if(dup2(errpipe[1], STDOUT_FILENO) < 0 || - 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", d->runtime, 1) < 0 || - setenv("XDG_CONFIG_HOME", d->config, 1) < 0 || - setenv("HOME", d->home, 1) < 0 || unsetenv("DISPLAY") < 0 || - unsetenv("DBUS_SESSION_BUS_ADDRESS") < 0 || - unsetenv("IBUS_ADDRESS") < 0 || - unsetenv("IBUS_ADDRESS_FILE") < 0){ - dprintf(STDERR_FILENO, "set daemon environment: %s\n", - strerror(errno)); - _exit(126); - } - execl(program, program, mapdir, (char*)0); - dprintf(STDERR_FILENO, "exec %s: %s\n", program, strerror(errno)); - _exit(127); - } - close(errpipe[1]); - d->pid = pid; - d->errfd = errpipe[0]; - if(!waitaddress(d, notifyfd)){ - inotify_rm_watch(notifyfd, watch); - close(notifyfd); - return 0; - } - inotify_rm_watch(notifyfd, watch); - close(notifyfd); - return 1; -} - -static int -rmdirknown(char *path) -{ - if(path[0] == '\0' || rmdir(path) == 0 || errno == ENOENT) - return 1; - return fail("rmdir %s: %s", path, strerror(errno)); -} - -static int -killdaemon(Daemon *d, int *status) -{ - int err, n, ok; - - ok = 1; - if(kill(d->pid, SIGKILL) < 0 && errno != ESRCH){ - fail("kill -9 %ld: %s", (long)d->pid, strerror(errno)); - ok = 0; - } - do - n = waitpid(d->pid, status, 0); - while(n < 0 && errno == EINTR); - if(n < 0 && errno == ECHILD){ - d->pid = -1; - return ok; - } - if(n != d->pid){ - err = errno; - fail("reap %ld after SIGKILL: %s", (long)d->pid, - n < 0 ? strerror(err) : "wrong child"); - ok = 0; - }else - d->pid = -1; - return ok; -} - -static int -stopdaemon(Daemon *d) -{ - struct timespec pause; - int err, n, ok, reaped, status; - int64_t deadline; - - ok = 1; - reaped = 0; - if(d->pid > 0){ - do - n = waitpid(d->pid, &status, WNOHANG); - while(n < 0 && errno == EINTR); - if(n == d->pid){ - fail("daemon exited before test termination with wait status %#x", - status); - d->pid = -1; - ok = 0; - }else if(n < 0){ - err = errno; - fail("check daemon %ld: %s", (long)d->pid, strerror(err)); - if(err == ECHILD) - d->pid = -1; - ok = 0; - } - } - if(d->pid > 0){ - if(kill(d->pid, SIGTERM) < 0 && errno != ESRCH){ - fail("kill %ld: %s", (long)d->pid, strerror(errno)); - ok = 0; - } - pause.tv_sec = 0; - pause.tv_nsec = 10000000; - deadline = nowms() + Stoptimeout; - for(;;){ - n = waitpid(d->pid, &status, WNOHANG); - if(n == d->pid){ - reaped = 1; - d->pid = -1; - break; - } - if(n < 0 && errno == EINTR) - continue; - if(n < 0){ - err = errno; - fail("waitpid %ld: %s", (long)d->pid, strerror(err)); - if(err == ECHILD) - d->pid = -1; - ok = 0; - break; - } - if(leftms(deadline) == 0){ - fail("daemon %ld did not stop after SIGTERM", (long)d->pid); - ok = 0; - if(!killdaemon(d, &status)) - ok = 0; - break; - } - nanosleep(&pause, NULL); - readerrors(d); - } - if(reaped && - !((WIFSIGNALED(status) && WTERMSIG(status) == SIGTERM) || - (WIFEXITED(status) && WEXITSTATUS(status) == 1))){ - fail("daemon exited with unexpected wait status %#x", status); - ok = 0; - } - } - readerrors(d); - if(d->errfd >= 0){ - close(d->errfd); - d->errfd = -1; - } - if(d->socket[0] != '\0' && unlink(d->socket) < 0 && errno != ENOENT){ - fail("remove IPC socket %s: %s", d->socket, strerror(errno)); - ok = 0; - } - if(d->addrfile[0] != '\0' && unlink(d->addrfile) < 0 && errno != ENOENT){ - fail("remove IBus address file %s: %s", d->addrfile, - strerror(errno)); - ok = 0; - } - if(!rmdirknown(d->bus)) ok = 0; - if(!rmdirknown(d->ibus)) ok = 0; - if(!rmdirknown(d->config)) ok = 0; - if(!rmdirknown(d->runtime)) ok = 0; - if(!rmdirknown(d->home)) ok = 0; - if(!rmdirknown(d->root)) ok = 0; - return ok; -} - -static DBusConnection* -openbus(char *address) -{ - DBusConnection *conn; - DBusError err; - - dbus_error_init(&err); - conn = dbus_connection_open_private(address, &err); - if(conn == NULL){ - fail("connect to private IBus address: %s", err.message); - dbus_error_free(&err); - return NULL; - } - dbus_connection_set_exit_on_disconnect(conn, FALSE); - return conn; -} - -static void -closebus(DBusConnection **conn) -{ - if(*conn == NULL) - return; - dbus_connection_close(*conn); - dbus_connection_unref(*conn); - *conn = NULL; -} - -static DBusMessage* -method(char *path, char *iface, char *member) -{ - DBusMessage *m; - - m = dbus_message_new_method_call("org.freedesktop.IBus", path, - iface, member); - if(m == NULL) - fail("allocate %s call: out of memory", member); - return m; -} - -static DBusMessage* -sendcall(DBusConnection *conn, DBusMessage *m) -{ - DBusMessage *reply; - DBusPendingCall *pending; - char member[64], path[128]; - const char *s; - int timeout; - int64_t deadline; - - if(m == NULL) - return NULL; - s = dbus_message_get_member(m); - snprintf(member, sizeof member, "%s", s != NULL ? s : "method"); - s = dbus_message_get_path(m); - snprintf(path, sizeof path, "%s", s != NULL ? s : "path"); - pending = NULL; - if(!dbus_connection_send_with_reply(conn, m, &pending, Calltimeout) || - pending == NULL){ - dbus_message_unref(m); - fail("queue %s on %s: out of memory", member, path); - return NULL; - } - dbus_message_unref(m); - deadline = nowms() + Calltimeout; - while(!dbus_pending_call_get_completed(pending)){ - timeout = leftms(deadline); - if(timeout == 0){ - fail("timed out waiting for %s on %s", member, path); - goto fail; - } - if(!dbus_connection_read_write_dispatch(conn, timeout)){ - fail("connection closed waiting for %s on %s", member, path); - goto fail; - } - } - reply = dbus_pending_call_steal_reply(pending); - dbus_pending_call_unref(pending); - if(reply == NULL){ - fail("%s on %s completed without a reply", member, path); - return NULL; - } - return reply; -fail: - dbus_pending_call_cancel(pending); - dbus_pending_call_unref(pending); - return NULL; -} - -static int -hello(DBusConnection *conn) -{ - DBusMessage *m, *reply; - DBusError err; - const char *name; - int ok; - - m = dbus_message_new_method_call("org.freedesktop.DBus", - "/org/freedesktop/DBus", "org.freedesktop.DBus", "Hello"); - reply = sendcall(conn, m); - if(reply == NULL) - return 0; - dbus_error_init(&err); - ok = dbus_message_get_type(reply) == DBUS_MESSAGE_TYPE_METHOD_RETURN && - dbus_message_has_signature(reply, "s") && - dbus_message_get_args(reply, &err, DBUS_TYPE_STRING, &name, - DBUS_TYPE_INVALID) && name[0] == ':'; - if(!ok) - fail("Hello returned an invalid reply"); - dbus_error_free(&err); - dbus_message_unref(reply); - return ok; -} - -static int -createcontext(DBusConnection *conn, char *buf, size_t nbuf) -{ - DBusMessage *m, *reply; - DBusError err; - const char *client, *path; - int ok; - - client = "ibus-live-test"; - m = method("/org/freedesktop/IBus", "org.freedesktop.IBus", - "CreateInputContext"); - if(m == NULL || !dbus_message_append_args(m, DBUS_TYPE_STRING, &client, - DBUS_TYPE_INVALID)){ - if(m != NULL) - dbus_message_unref(m); - return fail("build CreateInputContext call"); - } - reply = sendcall(conn, m); - if(reply == NULL) - return 0; - dbus_error_init(&err); - ok = dbus_message_get_type(reply) == DBUS_MESSAGE_TYPE_METHOD_RETURN && - dbus_message_has_signature(reply, "o") && - dbus_message_get_args(reply, &err, DBUS_TYPE_OBJECT_PATH, &path, - DBUS_TYPE_INVALID) && path[0] == '/'; - if(ok && buf != NULL && snprintf(buf, nbuf, "%s", path) >= (int)nbuf) - ok = 0; - if(!ok) - fail("CreateInputContext returned an invalid reply"); - dbus_error_free(&err); - dbus_message_unref(reply); - return ok; -} - static int invalidcall(DBusConnection *conn, char *path, char *member) { @@ -576,8 +25,7 @@ invalidcall(DBusConnection *conn, char *path, char *member) const char *name; int ok; - reply = sendcall(conn, method(path, - "org.freedesktop.IBus.InputContext", member)); + reply = sendcall(conn, contextcall(path, member), member); if(reply == NULL) return 0; name = dbus_message_get_error_name(reply); @@ -585,7 +33,8 @@ invalidcall(DBusConnection *conn, char *path, char *member) name != NULL && strcmp(name, DBUS_ERROR_INVALID_ARGS) == 0; if(!ok) fail("malformed %s returned %s, expected %s", member, - name != NULL ? name : "a non-error reply", DBUS_ERROR_INVALID_ARGS); + name != NULL ? name : "a non-error reply", + DBUS_ERROR_INVALID_ARGS); dbus_message_unref(reply); return ok; } @@ -647,7 +96,7 @@ overflowrejected(char *address) } static int -runcontract(Daemon *d) +runcontract(char *address) { static char *bad[] = { "ProcessKeyEvent", @@ -659,10 +108,11 @@ runcontract(Daemon *d) char path[96]; int i, ok; - conn = openbus(d->address); + conn = openbus(address, "contract"); if(conn == NULL) return 0; - ok = hello(conn) && createcontext(conn, path, sizeof path); + ok = hello(conn, NULL, 0, "contract Hello") && + createcontext(conn, path, sizeof path, "contract context"); for(i = 0; ok && i < (int)(sizeof bad / sizeof bad[0]); i++) ok = invalidcall(conn, path, bad[i]); closebus(&conn); @@ -670,7 +120,7 @@ runcontract(Daemon *d) } static int -runcapacity(Daemon *d) +runcapacity(char *address) { DBusConnection *conn[Maxconnections]; int i, ok; @@ -679,19 +129,19 @@ runcapacity(Daemon *d) conn[i] = NULL; ok = 0; for(i = 0; i < Maxconnections; i++){ - conn[i] = openbus(d->address); - if(conn[i] == NULL || !hello(conn[i])) + conn[i] = openbus(address, "capacity"); + if(conn[i] == NULL || !hello(conn[i], NULL, 0, "capacity Hello")) goto out; } - if(!overflowrejected(d->address)) + if(!overflowrejected(address)) goto out; closebus(&conn[Maxconnections-1]); - if(!createcontext(conn[0], NULL, 0)) + if(!createcontext(conn[0], NULL, 0, "capacity context")) goto out; - conn[Maxconnections-1] = openbus(d->address); + conn[Maxconnections-1] = openbus(address, "replacement"); if(conn[Maxconnections-1] == NULL || - !hello(conn[Maxconnections-1]) || - !createcontext(conn[Maxconnections-1], NULL, 0)) + !hello(conn[Maxconnections-1], NULL, 0, "replacement Hello") || + !createcontext(conn[Maxconnections-1], NULL, 0, "replacement context")) goto out; ok = 1; out: @@ -701,9 +151,8 @@ out: } static int -runclient(Daemon *d, char *program) +runclient(Live *l, char *address, char *program) { - struct timespec pause; pid_t pid, n; int status; int64_t deadline; @@ -712,17 +161,15 @@ runclient(Daemon *d, char *program) if(pid < 0) return fail("fork official libibus client: %s", strerror(errno)); if(pid == 0){ - if(setenv("XDG_RUNTIME_DIR", d->runtime, 1) < 0 || - setenv("XDG_CONFIG_HOME", d->config, 1) < 0 || - setenv("HOME", d->home, 1) < 0 || + if(setenv("XDG_RUNTIME_DIR", l->runtime, 1) < 0 || + setenv("XDG_CONFIG_HOME", l->config, 1) < 0 || + setenv("HOME", l->home, 1) < 0 || unsetenv("IBUS_ADDRESS_FILE") < 0) _exit(126); - execl(program, program, d->address, (char*)0); + execl(program, program, address, (char*)0); dprintf(STDERR_FILENO, "exec %s: %s\n", program, strerror(errno)); _exit(127); } - pause.tv_sec = 0; - pause.tv_nsec = 10000000; deadline = nowms() + Starttimeout; for(;;){ n = waitpid(pid, &status, WNOHANG); @@ -740,7 +187,7 @@ runclient(Daemon *d, char *program) while(n < 0 && errno == EINTR); return 0; } - nanosleep(&pause, NULL); + pausems(10); } if(!WIFEXITED(status) || WEXITSTATUS(status) != 0) return fail("official libibus client exited with wait status %#x", @@ -752,24 +199,33 @@ int main(int argc, char **argv) { Daemon daemon; + Live live; + char address[512]; int capacity, ok; + testname = "ibus_live_test"; capacity = argc == 4 && strcmp(argv[1], "--capacity") == 0; if(argc != 4){ fprintf(stderr, "usage: ibus_live_test strans mapdir libibus-client\n"); fprintf(stderr, " ibus_live_test --capacity strans mapdir\n"); return 2; } - ok = startdaemon(&daemon, argv[capacity ? 2 : 1], - argv[capacity ? 3 : 2]); + daemoninit(&daemon, "daemon"); + ok = livesetup(&live, "ibus") && + startdaemon(&live, &daemon, argv[capacity ? 2 : 1], + argv[capacity ? 3 : 2]) && + waitready(&live, &daemon, address, sizeof address); if(ok) - ok = capacity ? runcapacity(&daemon) : runclient(&daemon, argv[3]); - if(ok && !capacity) - ok = runcontract(&daemon); + ok = capacity ? runcapacity(address) : + runclient(&live, address, argv[3]) && runcontract(address); if(!ok) showerrors(&daemon); if(!stopdaemon(&daemon)) ok = 0; + if(!closeerrors(&daemon)) + ok = 0; + if(!liveclean(&live)) + ok = 0; if(!ok) return 1; printf(capacity ? "ibus connection capacity: ok\n" : diff --git a/tests/ipc_live_test.c b/tests/ipc_live_test.c index 6766801..7179885 100644 --- a/tests/ipc_live_test.c +++ b/tests/ipc_live_test.c @@ -1,53 +1,25 @@ #define _GNU_SOURCE -#include #include -#include -#include #include -#include -#include #include #include #include #include -#include #include -#include #include -#include -#include -#include #include #include "ipc.h" +#include "live.h" enum { Maxclients = 64, - Calltimeout = 4000, - Starttimeout = 8000, - Stoptimeout = 3000, Cappreedit = 1, }; -typedef struct Daemon Daemon; typedef struct Response Response; -struct Daemon -{ - pid_t pid; - int errfd; - char root[256]; - char runtime[320]; - char config[320]; - char ibus[384]; - char bus[448]; - char home[320]; - char socket[384]; - char err[4096]; - size_t nerr; -}; - struct Response { int eaten; @@ -55,495 +27,6 @@ struct Response char preedit[Ipcfieldmax+1]; }; -static int -fail(char *fmt, ...) -{ - va_list ap; - - fprintf(stderr, "ipc_live_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 0; - return (int64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000; -} - -static int -leftms(int64_t deadline) -{ - int64_t n; - - n = deadline - nowms(); - if(n <= 0) - return 0; - if(n > 0x7fffffff) - return 0x7fffffff; - return n; -} - -static int -makedir(char *path) -{ - if(mkdir(path, 0700) == 0) - return 1; - return fail("mkdir %s: %s", path, strerror(errno)); -} - -static void -readerrors(Daemon *d) -{ - ssize_t n; - - if(d->errfd < 0) - return; - while(d->nerr + 1 < sizeof d->err){ - n = read(d->errfd, d->err + d->nerr, - sizeof d->err - d->nerr - 1); - if(n > 0){ - d->nerr += n; - continue; - } - if(n < 0 && errno == EINTR) - continue; - break; - } - d->err[d->nerr] = '\0'; -} - -static void -showerrors(Daemon *d) -{ - readerrors(d); - if(d->nerr != 0) - fprintf(stderr, "ipc_live_test: daemon stderr:\n%s", d->err); -} - -static int -waitsocket(Daemon *d, int notifyfd) -{ - struct pollfd pfd[2]; - struct stat st; - char buf[4096]; - int n, status, timeout; - int64_t deadline; - - deadline = nowms() + Starttimeout; - for(;;){ - if(lstat(d->socket, &st) == 0 && S_ISSOCK(st.st_mode) && - (st.st_mode & 0777) == 0600) - return 1; - do - n = waitpid(d->pid, &status, WNOHANG); - while(n < 0 && errno == EINTR); - if(n == d->pid){ - d->pid = -1; - readerrors(d); - return fail("daemon exited before creating IPC socket"); - } - if(n < 0){ - if(errno == ECHILD) - d->pid = -1; - return fail("wait for daemon startup: %s", strerror(errno)); - } - timeout = leftms(deadline); - if(timeout == 0) - return fail("timed out waiting for protected IPC socket %s", - d->socket); - pfd[0].fd = notifyfd; - pfd[0].events = POLLIN; - pfd[0].revents = 0; - pfd[1].fd = d->errfd; - pfd[1].events = POLLIN; - pfd[1].revents = 0; - n = poll(pfd, 2, timeout); - if(n < 0 && errno == EINTR) - continue; - if(n < 0) - return fail("poll for IPC socket: %s", strerror(errno)); - if(n == 0) - continue; - if(pfd[1].revents != 0) - readerrors(d); - if(pfd[0].revents & POLLIN) - while(read(notifyfd, buf, sizeof buf) < 0 && errno == EINTR) - ; - } -} - -static int -startdaemon(Daemon *d, char *program, char *mapdir) -{ - int fd, notifyfd, watch, errpipe[2]; - long maxfd; - pid_t pid; - - memset(d, 0, sizeof *d); - d->pid = -1; - d->errfd = -1; - snprintf(d->root, sizeof d->root, "/tmp/strans-ipc.XXXXXX"); - if(mkdtemp(d->root) == NULL){ - d->root[0] = '\0'; - return fail("mkdtemp: %s", strerror(errno)); - } - if(snprintf(d->runtime, sizeof d->runtime, "%s/runtime", d->root) - >= (int)sizeof d->runtime || - snprintf(d->config, sizeof d->config, "%s/config", d->root) - >= (int)sizeof d->config || - snprintf(d->ibus, sizeof d->ibus, "%s/ibus", d->config) - >= (int)sizeof d->ibus || - snprintf(d->bus, sizeof d->bus, "%s/bus", d->ibus) - >= (int)sizeof d->bus || - snprintf(d->home, sizeof d->home, "%s/home", d->root) - >= (int)sizeof d->home || - snprintf(d->socket, sizeof d->socket, "%s/strans.sock", d->runtime) - >= (int)sizeof d->socket) - return fail("temporary path is too long"); - if(!makedir(d->runtime) || !makedir(d->config) || !makedir(d->ibus) || - !makedir(d->bus) || !makedir(d->home)) - return 0; - notifyfd = inotify_init1(IN_CLOEXEC|IN_NONBLOCK); - if(notifyfd < 0) - return fail("inotify_init1: %s", strerror(errno)); - watch = inotify_add_watch(notifyfd, d->runtime, - IN_CREATE|IN_MOVED_TO|IN_ATTRIB); - if(watch < 0){ - close(notifyfd); - return fail("inotify_add_watch: %s", strerror(errno)); - } - if(pipe2(errpipe, O_CLOEXEC|O_NONBLOCK) < 0){ - close(notifyfd); - return fail("pipe2: %s", strerror(errno)); - } - pid = fork(); - if(pid < 0){ - close(errpipe[0]); - close(errpipe[1]); - close(notifyfd); - return fail("fork: %s", strerror(errno)); - } - if(pid == 0){ - close(errpipe[0]); - close(notifyfd); - if(dup2(errpipe[1], STDOUT_FILENO) < 0 || - 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", d->runtime, 1) < 0 || - setenv("XDG_CONFIG_HOME", d->config, 1) < 0 || - setenv("HOME", d->home, 1) < 0 || unsetenv("DISPLAY") < 0 || - unsetenv("DBUS_SESSION_BUS_ADDRESS") < 0 || - unsetenv("IBUS_ADDRESS") < 0 || - unsetenv("IBUS_ADDRESS_FILE") < 0){ - dprintf(STDERR_FILENO, "set daemon environment: %s\n", - strerror(errno)); - _exit(126); - } - execl(program, program, mapdir, (char*)0); - dprintf(STDERR_FILENO, "exec %s: %s\n", program, strerror(errno)); - _exit(127); - } - close(errpipe[1]); - d->pid = pid; - d->errfd = errpipe[0]; - if(!waitsocket(d, notifyfd)){ - inotify_rm_watch(notifyfd, watch); - close(notifyfd); - return 0; - } - inotify_rm_watch(notifyfd, watch); - close(notifyfd); - return 1; -} - -static int -clearbus(Daemon *d) -{ - DIR *dir; - struct dirent *de; - char path[576]; - int ok; - - dir = opendir(d->bus); - if(dir == NULL) - return errno == ENOENT || fail("open cleanup directory %s: %s", - d->bus, strerror(errno)); - ok = 1; - errno = 0; - while((de = readdir(dir)) != NULL){ - if(strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0) - continue; - if(snprintf(path, sizeof path, "%s/%s", d->bus, de->d_name) - >= (int)sizeof path){ - fail("cleanup path is too long: %s", de->d_name); - ok = 0; - continue; - } - if(unlink(path) < 0 && errno != ENOENT){ - fail("remove IBus address file %s: %s", de->d_name, - strerror(errno)); - ok = 0; - } - errno = 0; - } - if(errno != 0){ - fail("read cleanup directory %s: %s", d->bus, strerror(errno)); - ok = 0; - } - closedir(dir); - return ok; -} - -static int -rmdirknown(char *path) -{ - if(path[0] == '\0' || rmdir(path) == 0 || errno == ENOENT) - return 1; - return fail("rmdir %s: %s", path, strerror(errno)); -} - -static int -killdaemon(Daemon *d, int *status) -{ - int n, ok; - - ok = 1; - if(kill(d->pid, SIGKILL) < 0 && errno != ESRCH){ - fail("kill -9 %ld: %s", (long)d->pid, strerror(errno)); - ok = 0; - } - do - n = waitpid(d->pid, status, 0); - while(n < 0 && errno == EINTR); - if(n != d->pid){ - fail("reap %ld after SIGKILL: %s", (long)d->pid, - n < 0 ? strerror(errno) : "wrong child"); - if(n < 0 && errno == ECHILD) - d->pid = -1; - ok = 0; - }else - d->pid = -1; - return ok; -} - -static int -stopdaemon(Daemon *d) -{ - struct pollfd pfd; - int ok, status, n, reaped; - int64_t deadline; - - ok = 1; - if(d->pid > 0){ - do - n = waitpid(d->pid, &status, WNOHANG); - while(n < 0 && errno == EINTR); - if(n == d->pid){ - fail("daemon exited before test termination with wait status %#x", - status); - d->pid = -1; - ok = 0; - }else if(n < 0){ - fail("check daemon %ld before termination: %s", - (long)d->pid, strerror(errno)); - if(errno == ECHILD) - d->pid = -1; - ok = 0; - } - } - if(d->pid > 0){ - if(kill(d->pid, SIGTERM) < 0){ - fail("kill %ld: %s", (long)d->pid, strerror(errno)); - ok = 0; - } - reaped = 0; - deadline = nowms() + Stoptimeout; - for(;;){ - n = waitpid(d->pid, &status, WNOHANG); - if(n == d->pid){ - reaped = 1; - d->pid = -1; - break; - } - if(n < 0 && errno == EINTR) - continue; - if(n < 0){ - fail("waitpid %ld: %s", (long)d->pid, strerror(errno)); - ok = 0; - if(errno == ECHILD) - d->pid = -1; - break; - } - n = leftms(deadline); - if(n == 0){ - fail("daemon %ld did not stop after SIGTERM", (long)d->pid); - ok = 0; - if(!killdaemon(d, &status)) - ok = 0; - break; - } - pfd.fd = d->errfd; - pfd.events = POLLIN|POLLHUP; - pfd.revents = 0; - if(poll(&pfd, 1, n) < 0 && errno != EINTR){ - fail("poll daemon %ld: %s", (long)d->pid, strerror(errno)); - ok = 0; - if(!killdaemon(d, &status)) - ok = 0; - break; - } - readerrors(d); - } - /* plan9port turns a caught termination note into exit status 1. */ - if(reaped && - !((WIFSIGNALED(status) && WTERMSIG(status) == SIGTERM) || - (WIFEXITED(status) && WEXITSTATUS(status) == 1))){ - fail("daemon exited with unexpected wait status %#x", status); - ok = 0; - } - } - readerrors(d); - if(d->errfd >= 0){ - close(d->errfd); - d->errfd = -1; - } - if(d->socket[0] != '\0' && unlink(d->socket) < 0 && errno != ENOENT){ - fail("remove IPC socket %s: %s", d->socket, strerror(errno)); - ok = 0; - } - if(!clearbus(d)) ok = 0; - if(!rmdirknown(d->bus)) ok = 0; - if(!rmdirknown(d->ibus)) ok = 0; - if(!rmdirknown(d->config)) ok = 0; - if(!rmdirknown(d->runtime)) ok = 0; - if(!rmdirknown(d->home)) ok = 0; - if(!rmdirknown(d->root)) ok = 0; - return ok; -} - -static int -connectuntil(char *path, int64_t deadline) -{ - struct sockaddr_un addr; - struct pollfd pfd; - socklen_t nerr; - int err, fd, flags, n; - - memset(&addr, 0, sizeof addr); - addr.sun_family = AF_UNIX; - if(snprintf(addr.sun_path, sizeof addr.sun_path, "%s", path) - >= (int)sizeof addr.sun_path){ - errno = ENAMETOOLONG; - return -1; - } - fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); - if(fd < 0) - return -1; - if(connect(fd, (struct sockaddr*)&addr, sizeof addr) < 0 && - errno != EINPROGRESS){ - err = errno; - close(fd); - errno = err; - return -1; - } - pfd.fd = fd; - pfd.events = POLLOUT; - pfd.revents = 0; - for(;;){ - n = leftms(deadline); - if(n == 0){ - close(fd); - errno = ETIMEDOUT; - return -1; - } - n = poll(&pfd, 1, n); - if(n < 0 && errno == EINTR) - continue; - if(n <= 0){ - err = n == 0 ? ETIMEDOUT : errno; - close(fd); - errno = err; - return -1; - } - break; - } - err = 0; - nerr = sizeof err; - if(getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &nerr) < 0 || err != 0){ - if(err == 0) - err = errno; - close(fd); - errno = err; - return -1; - } - flags = fcntl(fd, F_GETFL); - if(flags < 0 || fcntl(fd, F_SETFL, flags & ~O_NONBLOCK) < 0){ - err = errno; - close(fd); - errno = err; - return -1; - } - return fd; -} - -/* 1 is complete, 0 is peer closure, and -1 is a timeout or I/O error. */ -static int -readuntil(int fd, void *buf, size_t n, int64_t deadline) -{ - struct pollfd pfd; - unsigned char *p; - ssize_t r; - int timeout; - - p = buf; - while(n > 0){ - timeout = leftms(deadline); - if(timeout == 0){ - errno = ETIMEDOUT; - return -1; - } - pfd.fd = fd; - pfd.events = POLLIN; - pfd.revents = 0; - r = poll(&pfd, 1, timeout); - if(r < 0){ - if(errno == EINTR) - continue; - return -1; - } - if(r == 0){ - errno = ETIMEDOUT; - return -1; - } - r = recv(fd, p, n, 0); - if(r < 0 && errno == EINTR) - continue; - if(r < 0) - return -1; - if(r == 0) - return 0; - p += r; - n -= r; - } - return 1; -} - static size_t getlen(unsigned char p[Ipclensz]) { @@ -674,7 +157,7 @@ tryclient(char *path, int64_t deadline, int *client) Response res; int fd, rv; - fd = connectuntil(path, deadline); + fd = connectsocket(path, deadline); if(fd < 0) return errno == ETIMEDOUT ? -1 : 0; if(!sendkey(fd, 1, 0, Kmodfirst)){ @@ -721,7 +204,7 @@ overflowrejected(char *path) int64_t deadline; deadline = nowms() + Calltimeout; - fd = connectuntil(path, deadline); + fd = connectsocket(path, deadline); if(fd < 0) return fail("overflow connect: %s", strerror(errno)); if(!sendkey(fd, 1, 0, Kmodfirst)){ @@ -762,11 +245,11 @@ overflowrejected(char *path) } static int -runsmoke(Daemon *d) +runsmoke(char *path) { int client, ok; - client = connectuntil(d->socket, nowms() + Calltimeout); + client = connectsocket(path, nowms() + Calltimeout); if(client < 0) return fail("connect client: %s", strerror(errno)); ok = sendcap(client, Cappreedit) && @@ -780,7 +263,7 @@ runsmoke(Daemon *d) } static int -runcapacity(Daemon *d) +runcapacity(char *path) { int client[Maxclients]; int i, ok; @@ -788,7 +271,7 @@ runcapacity(Daemon *d) for(i = 0; i < Maxclients; i++) client[i] = -1; ok = 0; - client[0] = connectuntil(d->socket, nowms() + Calltimeout); + client[0] = connectsocket(path, nowms() + Calltimeout); if(client[0] < 0){ fail("connect first client: %s", strerror(errno)); goto out; @@ -797,7 +280,7 @@ runcapacity(Daemon *d) "select Japanese")) goto out; for(i = 1; i < Maxclients; i++){ - client[i] = connectuntil(d->socket, nowms() + Calltimeout); + client[i] = connectsocket(path, nowms() + Calltimeout); if(client[i] < 0){ fail("connect capacity client %d: %s", i, strerror(errno)); goto out; @@ -806,15 +289,15 @@ runcapacity(Daemon *d) "capacity modifier")) goto out; } - if(!overflowrejected(d->socket)) + if(!overflowrejected(path)) goto out; close(client[Maxclients-1]); client[Maxclients-1] = -1; - if(!waitslot(d->socket, &client[Maxclients-1], "inactive slot recovery")) + if(!waitslot(path, &client[Maxclients-1], "inactive slot recovery")) goto out; close(client[0]); client[0] = -1; - if(!waitslot(d->socket, &client[0], "active slot recovery") || + if(!waitslot(path, &client[0], "active slot recovery") || !requestkey(client[0], 1, 0, 'a', 1, "", "あ", "post-disconnect composition")) goto out; @@ -830,24 +313,30 @@ int main(int argc, char **argv) { Daemon daemon; - char *program, *mapdir; + Live live; int capacity, ok; + testname = "ipc_live_test"; capacity = argc == 4 && strcmp(argv[1], "--capacity") == 0; if((!capacity && argc != 3) || (capacity && argc != 4)){ fprintf(stderr, "usage: ipc_live_test [--capacity] strans mapdir\n"); return 2; } - program = argv[1+capacity]; - mapdir = argv[2+capacity]; - ok = startdaemon(&daemon, program, mapdir); + daemoninit(&daemon, "daemon"); + ok = livesetup(&live, "ipc") && + startdaemon(&live, &daemon, argv[1+capacity], argv[2+capacity]) && + waitready(&live, &daemon, NULL, 0); if(ok) - ok = capacity ? runcapacity(&daemon) : runsmoke(&daemon); + ok = capacity ? runcapacity(live.socket) : runsmoke(live.socket); if(!ok) showerrors(&daemon); if(!stopdaemon(&daemon)) ok = 0; + if(!closeerrors(&daemon)) + ok = 0; + if(!liveclean(&live)) + ok = 0; if(!ok) return 1; printf("ipc %s: ok\n", capacity ? "connection capacity" : "live smoke"); diff --git a/tests/live.c b/tests/live.c new file mode 100644 index 0000000..2395147 --- /dev/null +++ b/tests/live.c @@ -0,0 +1,695 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ipc.h" +#include "live.h" + +char *testname = "live_test"; + +int +fail(char *fmt, ...) +{ + va_list ap; + + fprintf(stderr, "%s: ", testname); + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + fputc('\n', stderr); + return 0; +} + +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; +} + +/* A clock that cannot be read counts as an expired deadline. */ +int +leftms(int64_t deadline) +{ + int64_t n; + + n = nowms(); + if(n < 0) + return 0; + n = deadline - n; + if(n <= 0) + return 0; + return n > INT_MAX ? INT_MAX : (int)n; +} + +void +pausems(int ms) +{ + struct timespec ts; + + ts.tv_sec = ms / 1000; + ts.tv_nsec = (ms % 1000) * 1000000L; + while(nanosleep(&ts, &ts) < 0 && errno == EINTR) + ; +} + +int +makedir(char *path) +{ + if(mkdir(path, 0700) == 0) + return 1; + return fail("mkdir %s: %s", path, strerror(errno)); +} + +int +cleardir(char *path) +{ + DIR *dir; + struct dirent *de; + char name[576]; + int ok; + + if(path[0] == '\0') + return 1; + dir = opendir(path); + if(dir == NULL) + return errno == ENOENT || + fail("open %s: %s", path, strerror(errno)); + ok = 1; + errno = 0; + while((de = readdir(dir)) != NULL){ + if(strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0) + continue; + if(snprintf(name, sizeof name, "%s/%s", path, de->d_name) + >= (int)sizeof name) + ok = fail("cleanup path is too long: %s", de->d_name); + else if(unlink(name) < 0 && errno != ENOENT) + ok = fail("remove %s: %s", name, strerror(errno)); + errno = 0; + } + if(errno != 0) + ok = fail("read %s: %s", path, strerror(errno)); + if(closedir(dir) < 0) + ok = fail("close %s: %s", path, strerror(errno)); + return ok; +} + +static int +rmdirknown(char *path) +{ + if(path[0] == '\0' || rmdir(path) == 0 || errno == ENOENT) + return 1; + return fail("rmdir %s: %s", path, strerror(errno)); +} + +int +livesetup(Live *l, char *prefix) +{ + memset(l, 0, sizeof *l); + if(nowms() < 0) + return fail("read monotonic clock: %s", strerror(errno)); + if(snprintf(l->root, sizeof l->root, "/tmp/strans-%s.XXXXXX", prefix) + >= (int)sizeof l->root){ + l->root[0] = '\0'; + return fail("temporary root name is too long"); + } + if(mkdtemp(l->root) == NULL){ + l->root[0] = '\0'; + return fail("mkdtemp: %s", strerror(errno)); + } + if(snprintf(l->runtime, sizeof l->runtime, "%s/runtime", l->root) + >= (int)sizeof l->runtime || + snprintf(l->config, sizeof l->config, "%s/config", l->root) + >= (int)sizeof l->config || + snprintf(l->ibus, sizeof l->ibus, "%s/ibus", l->config) + >= (int)sizeof l->ibus || + snprintf(l->bus, sizeof l->bus, "%s/bus", l->ibus) + >= (int)sizeof l->bus || + snprintf(l->home, sizeof l->home, "%s/home", l->root) + >= (int)sizeof l->home || + snprintf(l->socket, sizeof l->socket, "%s/strans.sock", l->runtime) + >= (int)sizeof l->socket) + return fail("temporary path is too long"); + return makedir(l->runtime) && makedir(l->config) && makedir(l->ibus) && + makedir(l->bus) && makedir(l->home); +} + +/* The daemon owns its endpoints, so leftovers elsewhere are a failure. */ +int +liveclean(Live *l) +{ + int ok; + + ok = 1; + if(l->socket[0] != '\0' && unlink(l->socket) < 0 && errno != ENOENT) + ok = fail("remove IPC socket %s: %s", l->socket, strerror(errno)); + if(!cleardir(l->bus)) ok = 0; + if(!rmdirknown(l->bus)) ok = 0; + if(!rmdirknown(l->ibus)) ok = 0; + if(!rmdirknown(l->config)) ok = 0; + if(!rmdirknown(l->runtime)) ok = 0; + if(!rmdirknown(l->home)) ok = 0; + if(!rmdirknown(l->root)) ok = 0; + return ok; +} + +void +daemoninit(Daemon *d, char *name) +{ + memset(d, 0, sizeof *d); + d->pid = -1; + d->errfd = -1; + d->name = name; +} + +void +readerrors(Daemon *d) +{ + ssize_t n; + + if(d->errfd < 0) + return; + while(d->nerr + 1 < sizeof d->err){ + n = read(d->errfd, d->err + d->nerr, sizeof d->err - d->nerr - 1); + if(n > 0){ + d->nerr += n; + continue; + } + if(n < 0 && errno == EINTR) + continue; + break; + } + d->err[d->nerr] = '\0'; +} + +void +showerrors(Daemon *d) +{ + readerrors(d); + if(d->nerr != 0) + fprintf(stderr, "%s: %s stderr:\n%s", testname, d->name, d->err); +} + +int +closeerrors(Daemon *d) +{ + int fd; + + readerrors(d); + fd = d->errfd; + d->errfd = -1; + if(fd >= 0 && close(fd) < 0) + return fail("close %s stderr: %s", d->name, strerror(errno)); + return 1; +} + +/* Point both standard streams at the capture pipe, even if it is 1 or 2. */ +static int +childfds(int fd) +{ + int flags; + + if(fd > STDERR_FILENO){ + if(dup2(fd, STDOUT_FILENO) < 0 || dup2(fd, STDERR_FILENO) < 0) + return 0; + close(fd); + return 1; + } + flags = fcntl(fd, F_GETFD); + if(flags < 0 || fcntl(fd, F_SETFD, flags & ~FD_CLOEXEC) < 0) + return 0; + return dup2(fd, STDOUT_FILENO) >= 0 && dup2(fd, STDERR_FILENO) >= 0; +} + +int +startdaemon(Live *l, Daemon *d, char *program, char *arg) +{ + 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(!childfds(errpipe[1])) + _exit(126); + 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", l->runtime, 1) < 0 || + setenv("XDG_CONFIG_HOME", l->config, 1) < 0 || + setenv("HOME", l->home, 1) < 0 || unsetenv("DISPLAY") < 0 || + unsetenv("DBUS_SESSION_BUS_ADDRESS") < 0 || + unsetenv("IBUS_ADDRESS") < 0 || + unsetenv("IBUS_ADDRESS_FILE") < 0){ + dprintf(STDERR_FILENO, "set daemon environment: %s\n", + strerror(errno)); + _exit(126); + } + execl(program, program, arg, (char*)0); + dprintf(STDERR_FILENO, "exec %s: %s\n", program, strerror(errno)); + _exit(127); + } + close(errpipe[1]); + d->pid = pid; + d->errfd = errpipe[0]; + return 1; +} + +int +daemonalive(Daemon *d) +{ + int n, status; + + if(d->pid <= 0) + return fail("%s is no longer running", d->name); + do + n = waitpid(d->pid, &status, WNOHANG); + while(n < 0 && errno == EINTR); + if(n == 0) + return 1; + if(n == d->pid){ + d->pid = -1; + return fail("%s exited unexpectedly with wait status %#x", d->name, + status); + } + if(n < 0){ + fail("check %s %ld: %s", d->name, (long)d->pid, strerror(errno)); + if(errno == ECHILD) + d->pid = -1; + return 0; + } + return fail("waitpid returned the wrong child for %s", d->name); +} + +int +killdaemon(Daemon *d, int *status) +{ + int n, ok, wait; + + if(d->pid <= 0) + return 1; + ok = 1; + if(kill(d->pid, SIGKILL) < 0 && errno != ESRCH) + ok = fail("kill -9 %s %ld: %s", d->name, (long)d->pid, + strerror(errno)); + do + n = waitpid(d->pid, &wait, 0); + while(n < 0 && errno == EINTR); + if(n != d->pid) + ok = fail("reap %s %ld after SIGKILL: %s", d->name, (long)d->pid, + n < 0 ? strerror(errno) : "wrong child"); + else if(status != NULL) + *status = wait; + d->pid = -1; + return ok; +} + +int +stopdaemon(Daemon *d) +{ + struct pollfd pfd; + int n, ok, reaped, status; + int64_t deadline; + + if(d->pid <= 0) + return 1; + if(!daemonalive(d)) + return 0; + ok = 1; + status = 0; + reaped = 0; + if(kill(d->pid, SIGTERM) < 0 && errno != ESRCH) + ok = fail("kill %s %ld: %s", d->name, (long)d->pid, strerror(errno)); + deadline = nowms() + Stoptimeout; + while(d->pid > 0){ + do + n = waitpid(d->pid, &status, WNOHANG); + while(n < 0 && errno == EINTR); + if(n == d->pid){ + reaped = 1; + d->pid = -1; + break; + } + if(n < 0){ + fail("waitpid %s %ld: %s", d->name, (long)d->pid, + strerror(errno)); + ok = 0; + if(errno == ECHILD) + d->pid = -1; + else if(!killdaemon(d, NULL)) + ok = 0; + break; + } + n = leftms(deadline); + if(n == 0){ + fail("%s %ld did not stop after SIGTERM", d->name, (long)d->pid); + ok = 0; + if(!killdaemon(d, NULL)) + ok = 0; + break; + } + pfd.fd = d->errfd; + pfd.events = POLLIN|POLLHUP; + pfd.revents = 0; + if(poll(&pfd, 1, n) < 0 && errno != EINTR){ + fail("poll %s %ld: %s", d->name, (long)d->pid, strerror(errno)); + ok = 0; + if(!killdaemon(d, NULL)) + ok = 0; + break; + } + readerrors(d); + } + /* plan9port turns a caught termination note into exit status 1. */ + if(reaped && !((WIFSIGNALED(status) && WTERMSIG(status) == SIGTERM) || + (WIFEXITED(status) && WEXITSTATUS(status) == 1))) + ok = fail("%s exited with unexpected wait status %#x", d->name, + status); + readerrors(d); + return ok; +} + +int +findaddress(Live *l) +{ + DIR *dir; + struct dirent *de; + int count, ok; + + dir = opendir(l->bus); + if(dir == NULL){ + fail("open IBus directory %s: %s", l->bus, strerror(errno)); + return -1; + } + count = 0; + ok = 1; + errno = 0; + while((de = readdir(dir)) != NULL){ + if(strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0 || + strstr(de->d_name, ".tmp.") != NULL) + continue; + count++; + if(snprintf(l->addrfile, sizeof l->addrfile, "%s/%s", l->bus, + de->d_name) >= (int)sizeof l->addrfile){ + ok = fail("IBus address path is too long"); + break; + } + } + if(errno != 0) + ok = fail("read IBus directory %s: %s", l->bus, strerror(errno)); + if(closedir(dir) < 0) + ok = fail("close IBus directory %s: %s", l->bus, strerror(errno)); + if(!ok) + return -1; + if(count > 1){ + fail("found %d IBus address files", count); + return -1; + } + return count; +} + +int +readfile(char *path, char *buf, size_t cap, size_t *nread) +{ + struct stat st; + ssize_t n; + size_t off; + char extra; + int fd, ok; + + fd = open(path, O_RDONLY|O_CLOEXEC); + if(fd < 0) + return fail("open %s: %s", path, strerror(errno)); + ok = 0; + if(fstat(fd, &st) < 0){ + fail("stat open file %s: %s", path, strerror(errno)); + goto out; + } + if(st.st_size < 0 || (uintmax_t)st.st_size >= cap){ + fail("file %s is too large", path); + goto out; + } + off = 0; + while(off < (size_t)st.st_size){ + n = read(fd, buf + off, (size_t)st.st_size - off); + if(n < 0 && errno == EINTR) + continue; + if(n <= 0){ + fail("read %s: %s", path, + n == 0 ? "unexpected EOF" : strerror(errno)); + goto out; + } + off += n; + } + do + n = read(fd, &extra, 1); + while(n < 0 && errno == EINTR); + if(n != 0){ + fail("file %s changed while being read", path); + goto out; + } + buf[off] = '\0'; + *nread = off; + ok = 1; +out: + if(close(fd) < 0){ + fail("close %s: %s", path, strerror(errno)); + ok = 0; + } + return ok; +} + +int +parseaddress(char *contents, size_t ncontents, char *address, size_t naddress, + pid_t *declared) +{ + char found[512]; + long pid; + int consumed; + + consumed = -1; + if(strlen(contents) != ncontents || + sscanf(contents, "IBUS_ADDRESS=%511[^\n]\nIBUS_DAEMON_PID=%ld\n%n", + found, &pid, &consumed) != 2 || consumed != (int)ncontents || pid <= 0) + return fail("invalid IBus address file contents"); + if(snprintf(address, naddress, "%s", found) >= (int)naddress) + return fail("private IBus address is too long"); + *declared = (pid_t)pid; + return 1; +} + +/* + * Wait for the protected IPC socket and, when addr is not null, for the + * IBus address file the daemon publishes for itself. + */ +int +waitready(Live *l, Daemon *d, char *addr, size_t naddr) +{ + struct pollfd pfd; + struct stat st; + char contents[2048]; + size_t ncontents; + pid_t declared; + int count, n, timeout; + int64_t deadline; + + deadline = nowms() + Starttimeout; + for(;;){ + if(lstat(l->socket, &st) == 0 && S_ISSOCK(st.st_mode) && + (st.st_mode & 0777) == 0600){ + if(addr == NULL) + return 1; + count = findaddress(l); + if(count < 0) + return 0; + if(count == 1 && + readfile(l->addrfile, contents, sizeof contents, &ncontents) && + parseaddress(contents, ncontents, addr, naddr, &declared) && + declared == d->pid) + return 1; + } + if(!daemonalive(d)) + return 0; + timeout = leftms(deadline); + if(timeout == 0) + return fail("timed out waiting for the %s endpoints", d->name); + pfd.fd = d->errfd; + pfd.events = POLLIN|POLLHUP; + pfd.revents = 0; + n = poll(&pfd, 1, timeout > 20 ? 20 : timeout); + if(n < 0 && errno == EINTR) + continue; + if(n < 0) + return fail("poll %s readiness: %s", d->name, strerror(errno)); + if(pfd.revents & (POLLERR|POLLNVAL)) + return fail("%s stderr pipe became unusable: %#x", d->name, + pfd.revents); + if(pfd.revents & (POLLIN|POLLHUP)) + readerrors(d); + } +} + +int +connectsocket(char *path, int64_t deadline) +{ + struct sockaddr_un addr; + struct pollfd pfd; + socklen_t nerr; + int err, fd, flags, n; + + memset(&addr, 0, sizeof addr); + addr.sun_family = AF_UNIX; + if(snprintf(addr.sun_path, sizeof addr.sun_path, "%s", path) + >= (int)sizeof addr.sun_path){ + errno = ENAMETOOLONG; + return -1; + } + fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); + if(fd < 0) + return -1; + if(connect(fd, (struct sockaddr*)&addr, sizeof addr) < 0 && + errno != EINPROGRESS){ + err = errno; + close(fd); + errno = err; + return -1; + } + for(;;){ + n = leftms(deadline); + if(n == 0){ + close(fd); + errno = ETIMEDOUT; + return -1; + } + pfd.fd = fd; + pfd.events = POLLOUT; + pfd.revents = 0; + n = poll(&pfd, 1, n); + if(n < 0 && errno == EINTR) + continue; + if(n <= 0){ + err = n == 0 ? ETIMEDOUT : errno; + close(fd); + errno = err; + return -1; + } + break; + } + err = 0; + nerr = sizeof err; + if(getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &nerr) < 0 || err != 0){ + if(err == 0) + err = errno; + close(fd); + errno = err; + return -1; + } + flags = fcntl(fd, F_GETFL); + if(flags < 0 || fcntl(fd, F_SETFL, flags & ~O_NONBLOCK) < 0){ + err = errno; + close(fd); + errno = err; + return -1; + } + return fd; +} + +/* 1 is complete, 0 is peer closure, and -1 is a timeout or I/O error. */ +int +readuntil(int fd, void *buf, size_t n, int64_t deadline) +{ + struct pollfd pfd; + unsigned char *p; + ssize_t r; + int timeout; + + p = buf; + while(n > 0){ + timeout = leftms(deadline); + if(timeout == 0){ + errno = ETIMEDOUT; + return -1; + } + pfd.fd = fd; + pfd.events = POLLIN; + pfd.revents = 0; + r = poll(&pfd, 1, timeout); + if(r < 0 && errno == EINTR) + continue; + if(r <= 0){ + if(r == 0) + errno = ETIMEDOUT; + return -1; + } + r = recv(fd, p, n, 0); + if(r < 0 && errno == EINTR) + continue; + if(r < 0) + return -1; + if(r == 0) + return 0; + p += r; + n -= r; + } + return 1; +} + +int +ipcrequest(int fd, uint32_t mod, uint32_t key, unsigned char *want, + size_t nwant, char *where) +{ + unsigned char req[Ipcreqsz], got[16]; + int rv; + + if(nwant > sizeof got) + return fail("%s expected IPC response is too large", where); + ipcpackreq(req, 1, mod, key); + if(ipcsend(fd, req, sizeof req) < 0) + return fail("send %s IPC request: %s", where, strerror(errno)); + rv = readuntil(fd, got, nwant, nowms() + Calltimeout); + if(rv != 1) + return fail("read %s IPC response: %s", where, + rv == 0 ? "peer closed" : strerror(errno)); + if(memcmp(got, want, nwant) != 0) + return fail("%s IPC response did not match", where); + return 1; +} + +int +ipcprobe(int fd, char *where) +{ + unsigned char empty[] = {0, 0, 0, 0, 0}; + + return ipcrequest(fd, 0, Kmodfirst, empty, sizeof empty, where); +} diff --git a/tests/live.h b/tests/live.h new file mode 100644 index 0000000..dc6154f --- /dev/null +++ b/tests/live.h @@ -0,0 +1,73 @@ +#include +#include +#include + +/* + * Harness shared by the live tests: a strans daemon started in a private + * XDG_RUNTIME_DIR with its output captured, the endpoints it publishes, + * and the IPC probe used to prove the daemon answers. + * Set testname before anything else; fail prefixes it to every message. + */ + +typedef struct Daemon Daemon; +typedef struct Live Live; + +enum +{ + Calltimeout = 4000, + Starttimeout = 8000, + Stoptimeout = 3000, + Errmax = 8192, +}; + +struct Daemon +{ + pid_t pid; + int errfd; + char *name; + char err[Errmax]; + size_t nerr; +}; + +struct Live +{ + char root[256]; + char runtime[320]; + char config[320]; + char ibus[384]; + char bus[448]; + char home[320]; + char socket[384]; + char addrfile[512]; +}; + +extern char *testname; + +int fail(char*, ...); +int64_t nowms(void); +int leftms(int64_t); +void pausems(int); +int makedir(char*); +int cleardir(char*); + +int livesetup(Live*, char*); +int liveclean(Live*); + +void daemoninit(Daemon*, char*); +int startdaemon(Live*, Daemon*, char*, char*); +int waitready(Live*, Daemon*, char*, size_t); +int daemonalive(Daemon*); +int stopdaemon(Daemon*); +int killdaemon(Daemon*, int*); +void readerrors(Daemon*); +void showerrors(Daemon*); +int closeerrors(Daemon*); + +int findaddress(Live*); +int readfile(char*, char*, size_t, size_t*); +int parseaddress(char*, size_t, char*, size_t, pid_t*); + +int connectsocket(char*, int64_t); +int readuntil(int, void*, size_t, int64_t); +int ipcrequest(int, uint32_t, uint32_t, unsigned char*, size_t, char*); +int ipcprobe(int, char*); diff --git a/tests/livebus.c b/tests/livebus.c new file mode 100644 index 0000000..dbfdbb2 --- /dev/null +++ b/tests/livebus.c @@ -0,0 +1,168 @@ +#define _GNU_SOURCE +#include +#include +#include +#include + +#include "live.h" +#include "livebus.h" + +DBusConnection* +openbus(char *address, char *where) +{ + DBusConnection *conn; + DBusError err; + + dbus_error_init(&err); + conn = dbus_connection_open_private(address, &err); + if(conn == NULL){ + fail("open %s private IBus connection: %s", where, + err.message != NULL ? err.message : "D-Bus error"); + dbus_error_free(&err); + return NULL; + } + dbus_error_free(&err); + dbus_connection_set_exit_on_disconnect(conn, FALSE); + return conn; +} + +void +closebus(DBusConnection **conn) +{ + if(*conn == NULL) + return; + dbus_connection_close(*conn); + dbus_connection_unref(*conn); + *conn = NULL; +} + +DBusMessage* +contextcall(char *path, char *member) +{ + return dbus_message_new_method_call("org.freedesktop.IBus", path, + "org.freedesktop.IBus.InputContext", member); +} + +DBusMessage* +sendcall(DBusConnection *conn, DBusMessage *m, char *where) +{ + DBusMessage *reply; + DBusPendingCall *pending; + int timeout; + int64_t deadline; + + if(m == NULL){ + fail("allocate %s call", where); + return NULL; + } + pending = NULL; + if(!dbus_connection_send_with_reply(conn, m, &pending, Calltimeout) || + pending == NULL){ + dbus_message_unref(m); + fail("queue %s call", where); + return NULL; + } + dbus_message_unref(m); + deadline = nowms() + Calltimeout; + while(!dbus_pending_call_get_completed(pending)){ + timeout = leftms(deadline); + if(timeout == 0){ + fail("timed out waiting for %s", where); + goto out; + } + if(!dbus_connection_read_write_dispatch(conn, timeout)){ + fail("connection closed waiting for %s", where); + goto out; + } + } + reply = dbus_pending_call_steal_reply(pending); + dbus_pending_call_unref(pending); + if(reply == NULL) + fail("%s completed without a reply", where); + return reply; +out: + dbus_pending_call_cancel(pending); + dbus_pending_call_unref(pending); + return NULL; +} + +DBusMessage* +callret(DBusConnection *conn, DBusMessage *m, char *where) +{ + DBusMessage *reply; + + reply = sendcall(conn, m, where); + if(reply == NULL) + return NULL; + if(dbus_message_get_type(reply) != DBUS_MESSAGE_TYPE_METHOD_RETURN){ + fail("%s returned D-Bus message type %d", where, + dbus_message_get_type(reply)); + dbus_message_unref(reply); + return NULL; + } + return reply; +} + +int +hello(DBusConnection *conn, char *name, size_t nname, char *where) +{ + DBusMessage *m, *reply; + DBusError err; + const char *s; + int ok; + + m = dbus_message_new_method_call("org.freedesktop.DBus", + "/org/freedesktop/DBus", "org.freedesktop.DBus", "Hello"); + reply = callret(conn, m, where); + if(reply == NULL) + return 0; + dbus_error_init(&err); + ok = dbus_message_has_signature(reply, "s") && + dbus_message_get_args(reply, &err, DBUS_TYPE_STRING, &s, + DBUS_TYPE_INVALID); + if(!ok) + fail("%s returned an invalid Hello reply", where); + else if(s[0] != ':') + ok = fail("%s returned invalid Hello name %s", where, s); + else if(name != NULL && snprintf(name, nname, "%s", s) >= (int)nname) + ok = fail("%s Hello name is too long", where); + dbus_error_free(&err); + dbus_message_unref(reply); + return ok; +} + +int +createcontext(DBusConnection *conn, char *path, size_t npath, char *where) +{ + DBusMessage *m, *reply; + DBusError err; + const char *client, *p; + int ok; + + client = testname; + m = dbus_message_new_method_call("org.freedesktop.IBus", + "/org/freedesktop/IBus", "org.freedesktop.IBus", + "CreateInputContext"); + if(m == NULL || !dbus_message_append_args(m, DBUS_TYPE_STRING, &client, + DBUS_TYPE_INVALID)){ + if(m != NULL) + dbus_message_unref(m); + return fail("build %s CreateInputContext call", where); + } + reply = callret(conn, m, where); + if(reply == NULL) + return 0; + dbus_error_init(&err); + ok = dbus_message_has_signature(reply, "o") && + dbus_message_get_args(reply, &err, DBUS_TYPE_OBJECT_PATH, &p, + DBUS_TYPE_INVALID); + if(!ok) + fail("%s returned an invalid context reply", where); + else if(p[0] != '/') + ok = fail("%s returned invalid context path %s", where, p); + else if(path != NULL && snprintf(path, npath, "%s", p) >= (int)npath) + ok = fail("%s context path is too long", where); + dbus_error_free(&err); + dbus_message_unref(reply); + return ok; +} diff --git a/tests/livebus.h b/tests/livebus.h new file mode 100644 index 0000000..61c8e76 --- /dev/null +++ b/tests/livebus.h @@ -0,0 +1,17 @@ +#include + +#include + +/* + * D-Bus half of the live harness: private connections to the IBus address + * a strans daemon publishes, and the calls every IBus client makes first. + * sendcall returns any reply, callret only a method return. + */ + +DBusConnection* openbus(char*, char*); +void closebus(DBusConnection**); +DBusMessage* contextcall(char*, char*); +DBusMessage* sendcall(DBusConnection*, DBusMessage*, char*); +DBusMessage* callret(DBusConnection*, DBusMessage*, char*); +int hello(DBusConnection*, char*, size_t, char*); +int createcontext(DBusConnection*, char*, size_t, char*); diff --git a/tests/xim_live_test.c b/tests/xim_live_test.c index 883279f..e23cb09 100644 --- a/tests/xim_live_test.c +++ b/tests/xim_live_test.c @@ -2,31 +2,26 @@ #include #include #include -#include #include #include #include #include #include -#include #include #include #include #include -#include #include #include #include -#include #include #include "ipc.h" +#include "live.h" enum { - Starttimeout = 8000, Eventtimeout = 4000, - Stoptimeout = 3000, }; typedef struct Child Child; @@ -42,15 +37,9 @@ struct Child struct Run { + Live l; Child xvfb; Child daemon; - char root[256]; - char runtime[320]; - char config[320]; - char ibus[352]; - char bus[384]; - char home[320]; - char socket[384]; char display[32]; }; @@ -66,61 +55,6 @@ struct Prelog static int xerror; -static int -fail(char *fmt, ...) -{ - va_list ap; - - fprintf(stderr, "xim_live_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 0; - return (int64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000; -} - -static int -leftms(int64_t deadline) -{ - int64_t n; - - n = deadline - nowms(); - if(n <= 0) - return 0; - if(n > 0x7fffffff) - return 0x7fffffff; - return n; -} - -static void -pausems(int n) -{ - struct timespec ts; - - ts.tv_sec = n / 1000; - ts.tv_nsec = (n % 1000) * 1000000L; - while(nanosleep(&ts, &ts) < 0 && errno == EINTR) - ; -} - -static int -makedir(char *path) -{ - if(mkdir(path, 0700) == 0) - return 1; - return fail("mkdir %s: %s", path, strerror(errno)); -} - static int childlog(char *path) { @@ -220,7 +154,7 @@ spawnxvfb(Run *r, char *program) } static int -startdaemon(Run *r, char *program, char *mapdir) +spawndaemon(Run *r, char *program, char *mapdir) { int errfd; pid_t pid; @@ -263,7 +197,7 @@ waitsocket(Run *r) deadline = nowms() + Starttimeout; while(leftms(deadline) > 0){ - if(lstat(r->socket, &st) == 0 && S_ISSOCK(st.st_mode) && + if(lstat(r->l.socket, &st) == 0 && S_ISSOCK(st.st_mode) && (st.st_mode & 0777) == 0600) return 1; if(waitpid(r->daemon.pid, &status, WNOHANG) == r->daemon.pid){ @@ -281,40 +215,24 @@ start(Run *r, char *daemon, char *mapdir, char *xvfb) memset(r, 0, sizeof *r); r->xvfb.pid = r->daemon.pid = -1; r->xvfb.pgid = r->daemon.pgid = -1; - snprintf(r->root, sizeof r->root, "/tmp/strans-xim.XXXXXX"); - if(mkdtemp(r->root) == NULL) - return fail("mkdtemp: %s", strerror(errno)); - if(snprintf(r->runtime, sizeof r->runtime, "%s/runtime", r->root) - >= (int)sizeof r->runtime || - snprintf(r->config, sizeof r->config, "%s/config", r->root) - >= (int)sizeof r->config || - snprintf(r->ibus, sizeof r->ibus, "%s/ibus", r->config) - >= (int)sizeof r->ibus || - snprintf(r->bus, sizeof r->bus, "%s/bus", r->ibus) - >= (int)sizeof r->bus || - snprintf(r->home, sizeof r->home, "%s/home", r->root) - >= (int)sizeof r->home || - snprintf(r->socket, sizeof r->socket, "%s/strans.sock", r->runtime) - >= (int)sizeof r->socket || - snprintf(r->xvfb.log, sizeof r->xvfb.log, "%s/xvfb.log", r->root) + if(!livesetup(&r->l, "xim")) + return 0; + if(snprintf(r->xvfb.log, sizeof r->xvfb.log, "%s/xvfb.log", r->l.root) >= (int)sizeof r->xvfb.log || - snprintf(r->daemon.log, sizeof r->daemon.log, "%s/daemon.log", r->root) + snprintf(r->daemon.log, sizeof r->daemon.log, "%s/daemon.log", r->l.root) >= (int)sizeof r->daemon.log) return fail("temporary path is too long"); - if(!makedir(r->runtime) || !makedir(r->config) || - !makedir(r->ibus) || !makedir(r->bus) || !makedir(r->home)) - return 0; if(!spawnxvfb(r, xvfb)) return 0; if(setenv("DISPLAY", r->display, 1) < 0 || setenv("XMODIFIERS", "@im=strans", 1) < 0 || - setenv("XDG_RUNTIME_DIR", r->runtime, 1) < 0 || - setenv("XDG_CONFIG_HOME", r->config, 1) < 0 || - setenv("HOME", r->home, 1) < 0 || + setenv("XDG_RUNTIME_DIR", r->l.runtime, 1) < 0 || + setenv("XDG_CONFIG_HOME", r->l.config, 1) < 0 || + setenv("HOME", r->l.home, 1) < 0 || unsetenv("DBUS_SESSION_BUS_ADDRESS") < 0 || unsetenv("IBUS_ADDRESS") < 0) return fail("set private environment: %s", strerror(errno)); - if(!startdaemon(r, daemon, mapdir)) + if(!spawndaemon(r, daemon, mapdir)) return 0; return waitsocket(r); } @@ -364,50 +282,22 @@ stopchild(Child *c) c->pgid = -1; } -static void -cleardir(char *path) -{ - char name[512]; - struct dirent *de; - DIR *dir; - - dir = opendir(path); - if(dir == NULL) - return; - while((de = readdir(dir)) != NULL){ - if(strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0) - continue; - if(snprintf(name, sizeof name, "%s/%s", path, de->d_name) - < (int)sizeof name) - unlink(name); - } - closedir(dir); -} - static void cleanup(Run *r, int noisy) { stopchild(&r->daemon); stopchild(&r->xvfb); if(noisy){ - fprintf(stderr, "xim_live_test: daemon log:\n"); + fprintf(stderr, "%s: daemon log:\n", testname); showlog(r->daemon.log); - fprintf(stderr, "xim_live_test: Xvfb log:\n"); + fprintf(stderr, "%s: Xvfb log:\n", testname); showlog(r->xvfb.log); } - if(r->socket[0] != '\0') - unlink(r->socket); - cleardir(r->bus); if(r->daemon.log[0] != '\0') unlink(r->daemon.log); if(r->xvfb.log[0] != '\0') unlink(r->xvfb.log); - if(r->bus[0] != '\0') rmdir(r->bus); - if(r->ibus[0] != '\0') rmdir(r->ibus); - if(r->config[0] != '\0') rmdir(r->config); - if(r->runtime[0] != '\0') rmdir(r->runtime); - if(r->home[0] != '\0') rmdir(r->home); - if(r->root[0] != '\0') rmdir(r->root); + liveclean(&r->l); } static int @@ -417,8 +307,7 @@ xerr(Display *dpy, XErrorEvent *e) xerror++; XGetErrorText(dpy, e->error_code, msg, sizeof msg); - fprintf(stderr, "xim_live_test: X error: %s (request %d.%d)\n", - msg, e->request_code, e->minor_code); + fail("X error: %s (request %d.%d)", msg, e->request_code, e->minor_code); return 0; } @@ -835,6 +724,7 @@ main(int argc, char **argv) int ok; char *xvfb; + testname = "xim_live_test"; if(argc != 3 && argc != 4){ fprintf(stderr, "usage: xim_live_test daemon mapdir [Xvfb]\n"); return 2;