#define _GNU_SOURCE #include #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, Ctrlmask = 1<<2, Phaseignore, Phasequiet, Phaserecover, }; typedef struct Test Test; struct Test { pid_t first; pid_t firstpid; pid_t second; pid_t helper; int firsterrfd; int seconderrfd; int helpererrfd; int notifyfd; int runtimewd; int buswd; int ipcfirst; int ipcnew; int busfirstfd; int phase; int socketdeleted; int socketcreated; int bustempcreated; int bustempmoved; int buspublished; uint32_t buscookie; 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 missing[384]; char addrfile[512]; char addrbase[256]; char firstaddress[512]; char secondaddress[512]; char firstcontents[2048]; size_t nfirstcontents; char secondcontents[2048]; size_t nsecondcontents; char bustemp[256]; struct stat firstsocketst; struct stat firstaddrst; struct stat secondsocketst; struct stat secondaddrst; char firsterr[8192]; size_t nfirsterr; char seconderr[8192]; size_t nseconderr; char helpererr[2048]; size_t nhelpererr; }; static uint32_t watchmask = IN_CREATE|IN_DELETE|IN_ATTRIB|IN_MOVED_FROM| IN_MOVED_TO|IN_CLOSE_WRITE|IN_DELETE_SELF|IN_MOVE_SELF; 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; t->firstpid = -1; t->second = -1; t->helper = -1; t->firsterrfd = -1; t->seconderrfd = -1; t->helpererrfd = -1; t->notifyfd = -1; t->runtimewd = -1; t->buswd = -1; t->ipcfirst = -1; t->ipcnew = -1; t->busfirstfd = -1; t->phase = Phaseignore; 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)); 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 || snprintf(t->missing, sizeof t->missing, "%s/missing-font.ttf", t->root) >= (int)sizeof t->missing) 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; t->notifyfd = inotify_init1(IN_CLOEXEC|IN_NONBLOCK); if(t->notifyfd < 0) return fail("inotify_init1: %s", strerror(errno)); t->runtimewd = inotify_add_watch(t->notifyfd, t->runtime, watchmask); if(t->runtimewd < 0) return fail("watch runtime directory: %s", strerror(errno)); t->buswd = inotify_add_watch(t->notifyfd, t->bus, watchmask); if(t->buswd < 0) return fail("watch IBus directory: %s", strerror(errno)); 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){ dprintf(STDERR_FILENO, "set daemon environment: %s\n", strerror(errno)); _exit(126); } execl(program, program, mapdir, t->missing, (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 runtimeevent(Test *t, struct inotify_event *ev) { uint32_t mask; if(ev->len == 0 || strcmp(ev->name, "strans.sock") != 0) return fail("unexpected runtime entry event %#x for %s", ev->mask, ev->len != 0 ? ev->name : "directory"); mask = ev->mask & watchmask; if(mask & IN_DELETE){ if(t->socketdeleted || t->socketcreated) return fail("IPC socket deletion occurred out of order"); t->socketdeleted = 1; mask &= ~IN_DELETE; } if(mask & IN_CREATE){ if(!t->socketdeleted || t->socketcreated) return fail("IPC socket creation occurred before stale deletion"); t->socketcreated = 1; mask &= ~IN_CREATE; } if(mask & IN_ATTRIB){ if(!t->socketcreated) return fail("IPC socket protection occurred before recreation"); mask &= ~IN_ATTRIB; } if(mask != 0) return fail("unexpected IPC recovery event %#x", ev->mask); return 1; } static int tempname(Test *t, char *name) { size_t n, nn; n = strlen(t->addrbase); nn = strlen(name); if(nn <= n + 5) return 0; return strncmp(name, t->addrbase, n) == 0 && strncmp(name + n, ".tmp.", 5) == 0; } static int busevent(Test *t, struct inotify_event *ev) { uint32_t mask; if(ev->len == 0) return fail("unexpected unnamed IBus directory event %#x", ev->mask); mask = ev->mask & watchmask; if(tempname(t, ev->name)){ if(mask & IN_CREATE){ if(t->bustempcreated || snprintf(t->bustemp, sizeof t->bustemp, "%s", ev->name) >= (int)sizeof t->bustemp) return fail("invalid replacement IBus temporary file"); t->bustempcreated = 1; mask &= ~IN_CREATE; } if(!t->bustempcreated || strcmp(ev->name, t->bustemp) != 0) return fail("unowned IBus temporary file event for %s", ev->name); if(mask & IN_ATTRIB) mask &= ~IN_ATTRIB; if(mask & IN_CLOSE_WRITE) mask &= ~IN_CLOSE_WRITE; if(mask & IN_MOVED_FROM){ if(t->bustempmoved || ev->cookie == 0) return fail("invalid IBus temporary-file rename"); t->bustempmoved = 1; t->buscookie = ev->cookie; mask &= ~IN_MOVED_FROM; } if(mask != 0) return fail("unexpected IBus temporary-file event %#x", ev->mask); return 1; } if(strcmp(ev->name, t->addrbase) != 0) return fail("unexpected IBus recovery entry %s", ev->name); if(mask == IN_MOVED_TO && t->bustempmoved && ev->cookie == t->buscookie){ t->buspublished = 1; return 1; } return fail("IBus address replacement was not one atomic rename: %#x", ev->mask); } static int drainnotify(Test *t) { char buf[4096]; struct inotify_event *ev; ssize_t n; size_t off; int ok; ok = 1; for(;;){ n = read(t->notifyfd, buf, sizeof buf); if(n < 0 && errno == EINTR) continue; if(n < 0 && errno == EAGAIN) return ok; if(n < 0) return fail("read endpoint watches: %s", strerror(errno)); if(n == 0) return ok; for(off = 0; off + sizeof *ev <= (size_t)n; off += sizeof *ev + ev->len){ ev = (struct inotify_event*)(buf + off); if(off + sizeof *ev + ev->len > (size_t)n) return fail("truncated inotify event"); if(ev->mask & IN_Q_OVERFLOW){ fail("endpoint watch queue overflowed"); ok = 0; continue; } if(ev->mask & (IN_IGNORED|IN_UNMOUNT)){ fail("endpoint watch became invalid: %#x", ev->mask); ok = 0; continue; } if(ev->wd != t->runtimewd && ev->wd != t->buswd) continue; if(t->phase == Phaseignore) continue; if(t->phase == Phasequiet){ fail("hard crash changed %s endpoint %s with event %#x", ev->wd == t->runtimewd ? "IPC" : "IBus", ev->len != 0 ? ev->name : "directory", ev->mask); ok = 0; continue; } if(ev->wd == t->runtimewd){ if(!runtimeevent(t, ev)) ok = 0; }else if(!busevent(t, ev)) ok = 0; } if(off != (size_t)n) return fail("truncated inotify event buffer"); } } 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; } 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 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[2]; struct stat st; char contents[2048], address[512]; 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; 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, sizeof address, &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[0].fd = t->notifyfd; pfd[0].events = POLLIN; pfd[0].revents = 0; pfd[1].fd = errfd; pfd[1].events = POLLIN|POLLHUP; pfd[1].revents = 0; n = poll(pfd, 2, timeout); if(n < 0 && errno == EINTR) continue; if(n < 0) return fail("poll %s daemon readiness: %s", which, strerror(errno)); if(pfd[0].revents & (POLLERR|POLLHUP|POLLNVAL)) return fail("endpoint watch became unusable: %#x", pfd[0].revents); if(pfd[1].revents & (POLLERR|POLLNVAL)) return fail("%s stderr pipe became unusable: %#x", which, pfd[1].revents); if(pfd[0].revents & POLLIN) if(!drainnotify(t)) return 0; if(pfd[1].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 snapshotfirst(Test *t) { char *base; pid_t declared; if(lstat(t->socket, &t->firstsocketst) < 0) return fail("stat first IPC socket: %s", strerror(errno)); if(!S_ISSOCK(t->firstsocketst.st_mode) || (t->firstsocketst.st_mode & 0777) != 0600) return fail("first IPC endpoint is not a protected socket"); if(lstat(t->addrfile, &t->firstaddrst) < 0) return fail("stat first IBus address file: %s", strerror(errno)); if(!S_ISREG(t->firstaddrst.st_mode) || (t->firstaddrst.st_mode & 0777) != 0600) return fail("first IBus address is not a protected regular file"); if(!readfile(t->addrfile, t->firstcontents, sizeof t->firstcontents, &t->nfirstcontents) || !parseaddress(t->firstcontents, t->nfirstcontents, t->firstaddress, sizeof t->firstaddress, &declared)) return 0; if(declared != t->firstpid) return fail("first IBus address PID %ld, expected %ld", (long)declared, (long)t->firstpid); if(!privateaddress(t->firstaddress, declared)) return 0; base = strrchr(t->addrfile, '/'); if(base == NULL || snprintf(t->addrbase, sizeof t->addrbase, "%s", base+1) >= (int)sizeof t->addrbase) return fail("first IBus address filename is invalid"); return 1; } 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"), "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 = sendcall(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) { char name[32], path[96]; int64_t deadline; deadline = nowms() + Calltimeout; t->ipcfirst = connectsocket(t->socket, deadline); 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")) return 0; if(!dbus_connection_get_unix_fd(t->busfirst, &t->busfirstfd)) return fail("persistent IBus connection has no Unix descriptor"); return 1; } static int hardcrash(Test *t) { struct pollfd pfd; pid_t pid; int e, n, status, timeout; int64_t deadline; if(!childalive(&t->first, "first")) 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); } 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; } 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 onlyaddress(Test *t, char *where) { DIR *dir; struct dirent *de; int count, ok; dir = opendir(t->bus); if(dir == NULL) return fail("open IBus directory %s %s: %s", t->bus, where, strerror(errno)); count = 0; ok = 1; errno = 0; while((de = readdir(dir)) != NULL){ if(strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0) continue; count++; if(strcmp(de->d_name, t->addrbase) != 0){ fail("unexpected IBus entry %s %s", de->d_name, where); ok = 0; } errno = 0; } if(errno != 0){ fail("read IBus directory %s %s: %s", t->bus, where, strerror(errno)); ok = 0; } if(closedir(dir) < 0){ fail("close IBus directory %s %s: %s", t->bus, where, strerror(errno)); ok = 0; } if(count != 1){ fail("IBus directory has %d entries %s", count, where); ok = 0; } return ok; } static int checkstale(Test *t) { struct stat st; char contents[2048], address[512]; size_t ncontents; pid_t declared; if(lstat(t->socket, &st) < 0) return fail("stale IPC socket disappeared: %s", strerror(errno)); if(!S_ISSOCK(st.st_mode) || st.st_mode != t->firstsocketst.st_mode || st.st_dev != t->firstsocketst.st_dev || st.st_ino != t->firstsocketst.st_ino) return fail("stale IPC socket identity or protection changed"); if(lstat(t->addrfile, &st) < 0) return fail("stale IBus address file disappeared: %s", strerror(errno)); if(st.st_mode != t->firstaddrst.st_mode || st.st_dev != t->firstaddrst.st_dev || st.st_ino != t->firstaddrst.st_ino) return fail("stale IBus address identity or protection changed"); if(!readfile(t->addrfile, contents, sizeof contents, &ncontents) || !parseaddress(contents, ncontents, address, sizeof address, &declared)) return 0; if(ncontents != t->nfirstcontents || memcmp(contents, t->firstcontents, ncontents) != 0) return fail("stale IBus address contents changed after hard crash"); if(declared != t->firstpid || strcmp(address, t->firstaddress) != 0) return fail("stale IBus address no longer names the dead first daemon"); return onlyaddress(t, "after hard crash"); } static int helpermain(char *address) { DBusConnection *conn; DBusError err; dbus_error_init(&err); 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); dbus_error_free(&err); fprintf(stderr, "old private address unexpectedly accepted a connection\n"); return 1; } if(!dbus_error_is_set(&err)){ fprintf(stderr, "old private address failed without a D-Bus error\n"); dbus_error_free(&err); return 2; } dbus_error_free(&err); 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) { struct pollfd pfd; int errpipe[2], fd, n, status, timeout; long maxfd; pid_t pid; int64_t deadline; if(pipe2(errpipe, O_CLOEXEC|O_NONBLOCK) < 0) return fail("pipe old-address helper: %s", strerror(errno)); pid = fork(); if(pid < 0){ close(errpipe[0]); close(errpipe[1]); return fail("fork old-address helper: %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); } execl(self, self, "--reject-address", t->firstaddress, (char*)0); dprintf(STDERR_FILENO, "exec %s: %s\n", self, strerror(errno)); _exit(127); } close(errpipe[1]); t->helper = pid; t->helpererrfd = errpipe[0]; t->nhelpererr = 0; t->helpererr[0] = '\0'; deadline = nowms() + Calltimeout; for(;;){ do n = waitpid(pid, &status, WNOHANG); while(n < 0 && errno == EINTR); if(n == pid){ t->helper = -1; break; } if(n < 0){ fail("wait old-address helper %ld: %s", (long)pid, strerror(errno)); if(errno == ECHILD) t->helper = -1; else killreap(&t->helper, t->helpererrfd, "old-address helper"); return 0; } timeout = leftms(deadline); if(timeout <= 0){ fail("old private address open did not fail promptly %s", where); killreap(&t->helper, t->helpererrfd, "old-address helper"); return 0; } pfd.fd = t->helpererrfd; pfd.events = POLLIN|POLLHUP; pfd.revents = 0; n = poll(&pfd, 1, timeout); if(n < 0 && errno == EINTR) continue; if(n < 0){ fail("poll old-address helper: %s", strerror(errno)); killreap(&t->helper, t->helpererrfd, "old-address helper"); 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"); return 0; } if(pfd.revents & (POLLIN|POLLHUP)) readpipe(t->helpererrfd, t->helpererr, sizeof t->helpererr, &t->nhelpererr); } 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(!WIFEXITED(status) || WEXITSTATUS(status) != 0) return fail("old private address helper failed %s with status %#x", where, status); return 1; } static int recoveryevents(Test *t) { if(!t->socketdeleted || !t->socketcreated) return fail("replacement did not delete and recreate the stale IPC path"); if(!t->bustempcreated || !t->bustempmoved || !t->buspublished) return fail("replacement IBus address was not atomically published"); return 1; } static int snapshotsecond(Test *t) { pid_t declared; if(lstat(t->socket, &t->secondsocketst) < 0) return fail("stat replacement IPC socket: %s", strerror(errno)); if(!S_ISSOCK(t->secondsocketst.st_mode) || (t->secondsocketst.st_mode & 0777) != 0600) return fail("replacement IPC endpoint is not a protected socket"); if(lstat(t->addrfile, &t->secondaddrst) < 0) return fail("stat replacement IBus address file: %s", strerror(errno)); if(!S_ISREG(t->secondaddrst.st_mode) || (t->secondaddrst.st_mode & 0777) != 0600) return fail("replacement IBus address is not a protected regular file"); if(t->secondaddrst.st_dev == t->firstaddrst.st_dev && t->secondaddrst.st_ino == t->firstaddrst.st_ino) return fail("replacement IBus address retained the stale inode"); if(!readfile(t->addrfile, t->secondcontents, sizeof t->secondcontents, &t->nsecondcontents) || !parseaddress(t->secondcontents, t->nsecondcontents, t->secondaddress, sizeof t->secondaddress, &declared)) return 0; if(declared != t->second) return fail("replacement IBus address PID %ld, expected %ld", (long)declared, (long)t->second); if(!privateaddress(t->secondaddress, declared)) return 0; if(t->nsecondcontents == t->nfirstcontents && memcmp(t->secondcontents, t->firstcontents, t->nfirstcontents) == 0) return fail("replacement IBus address contents did not change"); if(strcmp(t->secondaddress, t->firstaddress) == 0) return fail("replacement reused the dead private IBus address"); return onlyaddress(t, "after replacement"); } static int 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]; t->ipcnew = connectsocket(t->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, name, sizeof name, "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; } static int cleanup(Test *t) { int ok; ok = 1; 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; } t->ipcnew = -1; } if(t->ipcfirst >= 0){ if(close(t->ipcfirst) < 0){ fail("close persistent IPC client: %s", strerror(errno)); ok = 0; } t->ipcfirst = -1; } if(t->helper > 0 && !killreap(&t->helper, t->helpererrfd, "old-address helper")) ok = 0; if(t->first > 0 && !killreap(&t->first, t->firsterrfd, "first daemon")) ok = 0; t->phase = Phaseignore; if(!stopsecond(t)) 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(t->notifyfd >= 0){ if(t->runtimewd >= 0 && inotify_rm_watch(t->notifyfd, t->runtimewd) < 0 && errno != EINVAL){ fail("remove runtime watch: %s", strerror(errno)); ok = 0; } if(t->buswd >= 0 && inotify_rm_watch(t->notifyfd, t->buswd) < 0 && errno != EINVAL){ fail("remove IBus watch: %s", strerror(errno)); ok = 0; } if(close(t->notifyfd) < 0){ fail("close endpoint watches: %s", strerror(errno)); ok = 0; } t->notifyfd = -1; } if(!removeroot(t)) ok = 0; return ok; } static int runrestart(Test *t, char *self, char *program, char *mapdir) { if(!startchild(t, program, mapdir, &t->first, &t->firsterrfd)) return 0; t->firstpid = t->first; if(!waitready(t, &t->first, t->firsterrfd, "first") || !snapshotfirst(t) || !openpersistent(t) || !drainnotify(t)) return 0; t->phase = Phasequiet; if(!hardcrash(t) || !waitipcclosed(t) || !waitbusclosed(t) || !drainnotify(t) || !checkstale(t) || !rejectold(t, self, "after hard crash") || !drainnotify(t)) return 0; closebus(&t->busfirst); if(close(t->ipcfirst) < 0) return fail("close disconnected persistent IPC client: %s", strerror(errno)); t->ipcfirst = -1; t->phase = Phaserecover; if(!startchild(t, program, mapdir, &t->second, &t->seconderrfd) || !waitready(t, &t->second, t->seconderrfd, "second") || !drainnotify(t) || !recoveryevents(t) || !snapshotsecond(t) || !rejectold(t, self, "after replacement") || !openreplacement(t)) return 0; return childalive(&t->second, "second"); } int main(int argc, char **argv) { Test test; int ok; if(argc == 3 && strcmp(argv[1], "--reject-address") == 0) return helpermain(argv[2]); if(argc != 3){ fprintf(stderr, "usage: daemon_restart_test strans mapdir\n"); return 2; } ok = setup(&test); if(ok) ok = runrestart(&test, argv[0], argv[1], argv[2]); if(!cleanup(&test)) ok = 0; if(!ok){ showerrors(&test); return 1; } printf("daemon hard-crash endpoint recovery: ok\n"); return 0; }