#define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include "ipc.h" #include "live.h" #include "livebus.h" enum { Ctrlmask = 1<<2, }; typedef struct Test Test; struct Test { Live l; Daemon first; Daemon second; pid_t firstpid; int ipcfirst; int ipcnew; int busfirstfd; DBusConnection *busfirst; DBusConnection *busnew; char firstaddress[512]; char secondaddress[512]; }; static int setup(Test *t) { struct sigaction sa; memset(t, 0, sizeof *t); daemoninit(&t->first, "first daemon"); daemoninit(&t->second, "second daemon"); t->firstpid = -1; t->ipcfirst = -1; t->ipcnew = -1; t->busfirstfd = -1; memset(&sa, 0, sizeof sa); 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)); return livesetup(&t->l, "restart"); } static int privateaddress(char *address, pid_t pid) { char prefix[96]; size_t n; if(snprintf(prefix, sizeof prefix, "unix:abstract=strans-%ld", (long)pid) >= (int)sizeof prefix) return fail("private IBus prefix is too long"); n = strlen(prefix); return (strncmp(address, prefix, n) == 0 && address[n] == ',') || fail("IBus address is not the daemon's private abstract address"); } static int focusin(DBusConnection *conn, char *path) { DBusMessage *reply; reply = callret(conn, contextcall(path, "FocusIn"), "replacement FocusIn"); if(reply == NULL) return 0; if(!dbus_message_has_signature(reply, "")){ dbus_message_unref(reply); return fail("replacement FocusIn returned a nonempty reply"); } dbus_message_unref(reply); return 1; } static int keycall(DBusConnection *conn, char *path, dbus_uint32_t sym, dbus_uint32_t state, int expected, char *where) { DBusMessage *m, *reply; DBusError err; dbus_uint32_t code; dbus_bool_t eaten; code = 0; m = contextcall(path, "ProcessKeyEvent"); if(m == NULL || !dbus_message_append_args(m, DBUS_TYPE_UINT32, &sym, DBUS_TYPE_UINT32, &code, DBUS_TYPE_UINT32, &state, DBUS_TYPE_INVALID)){ if(m != NULL) dbus_message_unref(m); return fail("build %s ProcessKeyEvent call", where); } reply = callret(conn, m, where); if(reply == NULL) return 0; dbus_error_init(&err); if(!dbus_message_has_signature(reply, "b") || !dbus_message_get_args(reply, &err, DBUS_TYPE_BOOLEAN, &eaten, DBUS_TYPE_INVALID)){ dbus_message_unref(reply); dbus_error_free(&err); return fail("%s returned an invalid ProcessKeyEvent reply", where); } dbus_error_free(&err); dbus_message_unref(reply); return (eaten != FALSE) == (expected != 0) || fail("%s eaten=%d, expected %d", where, eaten != FALSE, expected); } static int openpersistent(Test *t) { 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, 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"); return 1; } /* * plan9port ignores a broken pipe: the note handlers run and the daemon * goes on serving, so the endpoints it announced must still be there. */ static int survivenote(Test *t) { struct stat st; int fd, ok; if(kill(t->firstpid, SIGPIPE) < 0) return fail("send a broken pipe to the first daemon: %s", strerror(errno)); pausems(200); if(!daemonalive(&t->first)) return fail("first daemon died of a broken pipe"); if(lstat(t->l.socket, &st) < 0) return fail("IPC socket taken by a note the daemon survived: %s", strerror(errno)); if(lstat(t->l.addrfile, &st) < 0) return fail("IBus address taken by a note the daemon survived: %s", strerror(errno)); fd = connectsocket(t->l.socket, nowms() + Calltimeout); if(fd < 0) return fail("connect after a broken pipe: %s", strerror(errno)); ok = ipcprobe(fd, "after a broken pipe"); if(close(fd) < 0) return fail("close the client opened after a broken pipe: %s", strerror(errno)); return ok; } static int hardcrash(Test *t) { int status; status = 0; if(!daemonalive(&t->first) || !killdaemon(&t->first, &status)) return 0; readerrors(&t->first); if(!WIFSIGNALED(status) || WTERMSIG(status) != SIGKILL) return fail("first daemon hard crash had wait status %#x", status); return 1; } static int waitipcclosed(Test *t) { struct pollfd pfd; unsigned char byte; ssize_t n; int timeout; int64_t deadline; deadline = nowms() + Calltimeout; for(;;){ timeout = leftms(deadline); if(timeout == 0) return fail("persistent IPC connection did not disconnect promptly"); pfd.fd = t->ipcfirst; pfd.events = POLLIN|POLLHUP|POLLERR; pfd.revents = 0; n = poll(&pfd, 1, timeout); if(n < 0 && errno == EINTR) continue; if(n <= 0) return fail("poll persistent IPC disconnection: %s", n == 0 ? "timed out" : strerror(errno)); n = recv(t->ipcfirst, &byte, 1, MSG_PEEK); if(n == 0 || (n < 0 && (errno == ECONNRESET || errno == ENOTCONN || errno == EPIPE))) return 1; if(n < 0 && (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK)) continue; if(n < 0) return fail("read persistent IPC disconnection: %s", strerror(errno)); return fail("persistent IPC connection retained unread data after crash"); } } static int waitbusclosed(Test *t) { struct pollfd pfd; int n, timeout; int64_t deadline; deadline = nowms() + Calltimeout; for(;;){ if(!dbus_connection_get_is_connected(t->busfirst)) return 1; timeout = leftms(deadline); if(timeout == 0) return fail("persistent IBus connection did not disconnect promptly"); pfd.fd = t->busfirstfd; pfd.events = POLLIN|POLLHUP|POLLERR; pfd.revents = 0; n = poll(&pfd, 1, timeout); if(n < 0 && errno == EINTR) continue; if(n <= 0) return fail("poll persistent IBus disconnection: %s", n == 0 ? "timed out" : strerror(errno)); if(pfd.revents & POLLNVAL) return fail("persistent IBus descriptor became invalid"); dbus_connection_read_write_dispatch(t->busfirst, 0); } } static int checkstale(Test *t) { struct stat st; char contents[2048], address[512]; size_t ncontents; pid_t declared; 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->l.addrfile, &st) < 0) return fail("stale IBus address file disappeared: %s", strerror(errno)); 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) return fail("stale IBus address no longer names the dead first daemon"); return 1; } /* The dead daemon's abstract socket has no listener, so this must fail. */ static int rejectold(Test *t, char *where) { DBusConnection *conn; DBusError err; int ok; dbus_error_init(&err); conn = dbus_connection_open_private(t->firstaddress, &err); if(conn != NULL){ dbus_connection_set_exit_on_disconnect(conn, FALSE); closebus(&conn); ok = fail("old private address accepted a connection %s", where); }else ok = dbus_error_is_set(&err) || fail("old private address failed without a D-Bus error %s", where); dbus_error_free(&err); return ok; } static int openreplacement(Test *t) { unsigned char selectjp[] = {1, 0, 0, 0, 0, 0}; unsigned char keyk[] = {1, 0, 0, 0, 1, 0, 'k'}; char path[96]; 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, "replacement select Japanese") || !ipcrequest(t->ipcnew, 0, 'k', keyk, sizeof keyk, "replacement real key")) return 0; t->busnew = openbus(t->secondaddress, "replacement"); if(t->busnew == NULL || !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 daemonalive(&t->second); } static int cleanup(Test *t) { int ok; ok = 1; closebus(&t->busnew); closebus(&t->busfirst); if(t->ipcnew >= 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) ok = fail("close persistent IPC client: %s", strerror(errno)); t->ipcfirst = -1; } if(!killdaemon(&t->first, NULL)) ok = 0; if(!stopdaemon(&t->second)) ok = 0; if(!closeerrors(&t->first)) ok = 0; if(!closeerrors(&t->second)) ok = 0; if(!liveclean(&t->l)) ok = 0; return ok; } static int runrestart(Test *t, char *program, char *mapdir) { if(!startdaemon(&t->l, &t->first, program, mapdir)) return 0; 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(!survivenote(t)) return 0; if(!hardcrash(t) || !waitipcclosed(t) || !waitbusclosed(t) || !checkstale(t) || !rejectold(t, "after hard crash")) return 0; closebus(&t->busfirst); if(close(t->ipcfirst) < 0) return fail("close disconnected persistent IPC client: %s", strerror(errno)); t->ipcfirst = -1; 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 daemonalive(&t->second); } int main(int argc, char **argv) { Test test; int ok; testname = "daemon_restart_test"; if(argc != 3){ fprintf(stderr, "usage: daemon_restart_test strans mapdir\n"); return 2; } ok = setup(&test); if(ok) ok = runrestart(&test, argv[1], argv[2]); if(!cleanup(&test)) ok = 0; if(!ok){ showerrors(&test.first); showerrors(&test.second); return 1; } printf("daemon endpoints across a note and a hard crash: ok\n"); return 0; }