rejectold forked and re-execed the test binary with --reject-address just to call dbus_connection_open_private once against the crashed daemon's abstract address, then waited on the helper, drained its stderr pipe and checked its exit status. An abstract socket with no listener refuses the connection immediately, so the call cannot block and needs no separate process. Open the address in-process and require a null connection with the D-Bus error set, freeing the error afterwards. This drops the helper process, its stderr capture, the Test.helper fields and the argv dispatch in main. killdaemon stays; the daemons still need it.
373 lines
9.4 KiB
C
373 lines
9.4 KiB
C
#define _GNU_SOURCE
|
|
#include <errno.h>
|
|
#include <poll.h>
|
|
#include <signal.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
#include <sys/wait.h>
|
|
#include <unistd.h>
|
|
|
|
#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;
|
|
}
|
|
|
|
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};
|
|
unsigned char keyk[] = {1, 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(!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 hard-crash endpoint recovery: ok\n");
|
|
return 0;
|
|
}
|