1182 lines
28 KiB
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;
|
|
}
|