Files
strans/tests/daemon_collision_test.c

1182 lines
28 KiB
C

#define _GNU_SOURCE
#include <dbus/dbus.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <poll.h>
#include <signal.h>
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/inotify.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/un.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>
#include "ipc.h"
enum
{
Calltimeout = 4000,
Starttimeout = 8000,
Stoptimeout = 3000,
};
typedef struct Test Test;
struct Test
{
pid_t first;
pid_t second;
int firsterrfd;
int seconderrfd;
int notifyfd;
int buswd;
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 addrcontents[2048];
size_t naddrcontents;
struct stat socketst;
struct stat addrst;
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;
t->notifyfd = -1;
t->buswd = -1;
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;
t->notifyfd = inotify_init1(IN_CLOEXEC|IN_NONBLOCK);
if(t->notifyfd < 0)
return fail("inotify_init1: %s", strerror(errno));
t->buswd = inotify_add_watch(t->notifyfd, t->bus,
IN_CREATE|IN_MOVED_TO|IN_ATTRIB|IN_DELETE);
if(t->buswd < 0)
return fail("watch IBus directory: %s", strerror(errno));
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){
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
drainnotify(Test *t, int trackbus, int *changed)
{
char buf[4096];
struct inotify_event *ev;
ssize_t n;
size_t off;
for(;;){
n = read(t->notifyfd, buf, sizeof buf);
if(n < 0 && errno == EINTR)
continue;
if(n < 0 && errno == EAGAIN)
return 1;
if(n < 0)
return fail("read endpoint watches: %s", strerror(errno));
if(n == 0)
return 1;
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)
return fail("endpoint watch queue overflowed");
if(trackbus && changed != NULL && ev->wd == t->buswd &&
ev->len != 0 &&
(ev->mask & (IN_CREATE|IN_MOVED_TO|IN_DELETE|IN_ATTRIB))){
*changed = 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;
}
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[2];
struct stat st;
char contents[2048], address[512];
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, address, sizeof 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[0].fd = t->notifyfd;
pfd[0].events = POLLIN;
pfd[0].revents = 0;
pfd[1].fd = t->firsterrfd;
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 daemon readiness: %s", strerror(errno));
if(pfd[1].revents != 0)
readpipe(t->firsterrfd, t->firsterr, sizeof t->firsterr,
&t->nfirsterr);
if((pfd[0].revents & POLLIN) && !drainnotify(t, 0, NULL))
return 0;
}
}
static int
snapshot(Test *t)
{
if(lstat(t->socket, &t->socketst) < 0)
return fail("stat first IPC socket: %s", strerror(errno));
if(!S_ISSOCK(t->socketst.st_mode) || (t->socketst.st_mode & 0777) != 0600)
return fail("first IPC endpoint is not a protected socket");
if(lstat(t->addrfile, &t->addrst) < 0)
return fail("stat first IBus address file: %s", strerror(errno));
if(!S_ISREG(t->addrst.st_mode) || (t->addrst.st_mode & 0777) != 0600)
return fail("first IBus address is not a protected regular file");
if(!readfile(t->addrfile, t->addrcontents, sizeof t->addrcontents,
&t->naddrcontents))
return 0;
return parseaddress(t->addrcontents, t->naddrcontents, t->address,
sizeof t->address, t->first);
}
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");
ok = 0;
}else
*pid = -1;
return ok;
}
static int
waitsecond(Test *t, int *changed)
{
struct pollfd pfd[2];
int n, status, timeout;
int64_t deadline;
deadline = nowms() + Starttimeout;
for(;;){
do
n = waitpid(t->second, &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(!drainnotify(t, 1, changed))
return 0;
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)
return fail("second daemon did not report the IPC collision");
return 1;
}
if(n < 0){
fail("waitpid second daemon %ld: %s", (long)t->second,
strerror(errno));
if(errno == ECHILD)
t->second = -1;
return 0;
}
if(!childalive(&t->first, "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;
return 0;
}
pfd[0].fd = t->notifyfd;
pfd[0].events = POLLIN;
pfd[0].revents = 0;
pfd[1].fd = t->seconderrfd;
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 second daemon: %s", strerror(errno));
if(pfd[1].revents != 0)
readpipe(t->seconderrfd, t->seconderr, sizeof t->seconderr,
&t->nseconderr);
if((pfd[0].revents & POLLIN) &&
!drainnotify(t, 1, changed))
return 0;
}
}
static int
checksocket(Test *t)
{
struct stat st;
if(lstat(t->socket, &st) < 0)
return fail("first IPC socket disappeared: %s", strerror(errno));
if(!S_ISSOCK(st.st_mode) || st.st_mode != t->socketst.st_mode ||
st.st_dev != t->socketst.st_dev || st.st_ino != t->socketst.st_ino)
return fail("first IPC socket identity or protection changed");
return 1;
}
static int
checkaddress(Test *t)
{
struct stat st;
char contents[2048], address[512];
size_t ncontents;
if(lstat(t->addrfile, &st) < 0)
return fail("first IBus address file disappeared: %s",
strerror(errno));
if(st.st_mode != t->addrst.st_mode || st.st_dev != t->addrst.st_dev ||
st.st_ino != t->addrst.st_ino)
return fail("first IBus address file identity or protection changed");
if(!readfile(t->addrfile, contents, sizeof contents, &ncontents))
return 0;
if(ncontents != t->naddrcontents ||
memcmp(contents, t->addrcontents, ncontents) != 0)
return fail("first IBus address file contents changed");
if(!parseaddress(contents, ncontents, address, sizeof address, t->first))
return 0;
return strcmp(address, t->address) == 0 ||
fail("first private IBus address changed");
}
static int
onlyaddress(Test *t)
{
DIR *dir;
struct dirent *de;
char expected[512];
char *base;
int count, ok;
base = strrchr(t->addrfile, '/');
if(base == NULL)
return fail("invalid recorded IBus address path");
if(snprintf(expected, sizeof expected, "%s", base + 1)
>= (int)sizeof expected)
return fail("recorded IBus address name is too long");
dir = opendir(t->bus);
if(dir == NULL)
return fail("open IBus directory after collision: %s",
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, expected) != 0){
fail("unexpected IBus directory entry %s", de->d_name);
ok = 0;
}
}
if(errno != 0){
fail("read IBus directory after collision: %s", strerror(errno));
ok = 0;
}
if(closedir(dir) < 0){
fail("close IBus directory after collision: %s", strerror(errno));
ok = 0;
}
if(count != 1){
fail("IBus directory contains %d entries after collision", count);
ok = 0;
}
return ok;
}
static int
runcollision(Test *t, char *program, char *mapdir)
{
char firsthello[32], newhello[32];
char firstpath[96], persistentpath[96], newpath[96];
int changed;
if(!startchild(t, program, mapdir, &t->first, &t->firsterrfd) ||
!waitready(t) || !snapshot(t))
return 0;
t->ipcfirst = connectsocket(t->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") ||
!createcontext(t->busfirst, firstpath, sizeof firstpath,
"persistent pre-collision context"))
return 0;
if(!drainnotify(t, 0, NULL))
return 0;
changed = 0;
if(!startchild(t, program, mapdir, &t->second, &t->seconderrfd) ||
!waitsecond(t, &changed))
return 0;
if(changed)
return fail("second daemon touched the first IBus address directory");
if(!childalive(&t->first, "first") || !checksocket(t) ||
!checkaddress(t) || !onlyaddress(t))
return 0;
if(!ipcprobe(t->ipcfirst, "persistent post-collision"))
return 0;
if(!createcontext(t->busfirst, persistentpath, sizeof persistentpath,
"persistent post-collision context"))
return 0;
t->ipcnew = connectsocket(t->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);
if(t->busnew == NULL ||
!hello(t->busnew, newhello, sizeof newhello,
"new post-collision Hello") ||
!createcontext(t->busnew, newpath, sizeof newpath,
"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));
}
static int
cleanup(Test *t)
{
int n, ok, status;
ok = 1;
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;
}
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->second > 0){
do
n = waitpid(t->second, &status, WNOHANG);
while(n < 0 && errno == EINTR);
if(n == t->second)
t->second = -1;
else if(n == 0){
fail("second daemon still running during cleanup");
ok = 0;
if(!killowned(&t->second, &status))
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))
ok = 0;
}
}
if(!stopfirst(t))
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->notifyfd >= 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(t->socket[0] != '\0' && unlink(t->socket) < 0 && errno != ENOENT){
fail("remove IPC socket %s: %s", t->socket, strerror(errno));
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;
}
int
main(int argc, char **argv)
{
Test test;
int ok;
if(argc != 3){
fprintf(stderr, "usage: daemon_collision_test strans mapdir\n");
return 2;
}
ok = setup(&test);
if(ok)
ok = runcollision(&test, argv[1], argv[2]);
if(!cleanup(&test))
ok = 0;
if(!ok){
showerrors(&test);
return 1;
}
printf("daemon endpoint collision ownership: ok\n");
return 0;
}