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;