779 lines
18 KiB
C
779 lines
18 KiB
C
#define _GNU_SOURCE
|
|
#include <dbus/dbus.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/stat.h>
|
|
#include <sys/types.h>
|
|
#include <sys/wait.h>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
|
|
enum
|
|
{
|
|
Maxconnections = 64,
|
|
Calltimeout = 4000,
|
|
Starttimeout = 8000,
|
|
Stoptimeout = 3000,
|
|
};
|
|
|
|
typedef struct Daemon Daemon;
|
|
struct Daemon
|
|
{
|
|
pid_t pid;
|
|
int errfd;
|
|
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 err[4096];
|
|
size_t nerr;
|
|
};
|
|
|
|
static int
|
|
fail(char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
|
|
fprintf(stderr, "ibus_live_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
|
|
readerrors(Daemon *d)
|
|
{
|
|
ssize_t n;
|
|
|
|
if(d->errfd < 0)
|
|
return;
|
|
while(d->nerr + 1 < sizeof d->err){
|
|
n = read(d->errfd, d->err + d->nerr,
|
|
sizeof d->err - d->nerr - 1);
|
|
if(n > 0){
|
|
d->nerr += n;
|
|
continue;
|
|
}
|
|
if(n < 0 && errno == EINTR)
|
|
continue;
|
|
break;
|
|
}
|
|
d->err[d->nerr] = '\0';
|
|
}
|
|
|
|
static void
|
|
showerrors(Daemon *d)
|
|
{
|
|
readerrors(d);
|
|
if(d->nerr != 0)
|
|
fprintf(stderr, "ibus_live_test: daemon stderr:\n%s", d->err);
|
|
}
|
|
|
|
static int
|
|
readaddress(Daemon *d)
|
|
{
|
|
FILE *fp;
|
|
char line[768], *p;
|
|
long pid;
|
|
int haveaddr, havepid;
|
|
|
|
fp = fopen(d->addrfile, "r");
|
|
if(fp == NULL)
|
|
return fail("open %s: %s", d->addrfile, strerror(errno));
|
|
haveaddr = havepid = 0;
|
|
pid = -1;
|
|
while(fgets(line, sizeof line, fp) != NULL){
|
|
p = strchr(line, '\n');
|
|
if(p != NULL)
|
|
*p = '\0';
|
|
if(strncmp(line, "IBUS_ADDRESS=", 13) == 0){
|
|
if(snprintf(d->address, sizeof d->address, "%s", line + 13)
|
|
>= (int)sizeof d->address)
|
|
break;
|
|
haveaddr = d->address[0] != '\0';
|
|
}else if(strncmp(line, "IBUS_DAEMON_PID=", 16) == 0){
|
|
errno = 0;
|
|
pid = strtol(line + 16, &p, 10);
|
|
havepid = errno == 0 && *p == '\0';
|
|
}
|
|
}
|
|
if(fclose(fp) != 0)
|
|
return fail("close %s: %s", d->addrfile, strerror(errno));
|
|
if(!haveaddr || !havepid)
|
|
return fail("invalid IBus address file %s", d->addrfile);
|
|
if(pid != d->pid)
|
|
return fail("address file PID %ld, expected %ld", pid, (long)d->pid);
|
|
return 1;
|
|
}
|
|
|
|
static int
|
|
waitaddress(Daemon *d, int notifyfd)
|
|
{
|
|
struct pollfd pfd[2];
|
|
char buf[4096];
|
|
struct inotify_event *ev;
|
|
ssize_t n;
|
|
int i, status, timeout;
|
|
int64_t deadline;
|
|
|
|
deadline = nowms() + Starttimeout;
|
|
for(;;){
|
|
if(waitpid(d->pid, &status, WNOHANG) == d->pid){
|
|
d->pid = -1;
|
|
readerrors(d);
|
|
return fail("daemon exited before publishing IBus address");
|
|
}
|
|
timeout = leftms(deadline);
|
|
if(timeout == 0)
|
|
return fail("timed out waiting for IBus address file");
|
|
pfd[0].fd = notifyfd;
|
|
pfd[0].events = POLLIN;
|
|
pfd[0].revents = 0;
|
|
pfd[1].fd = d->errfd;
|
|
pfd[1].events = POLLIN;
|
|
pfd[1].revents = 0;
|
|
i = poll(pfd, 2, timeout);
|
|
if(i < 0 && errno == EINTR)
|
|
continue;
|
|
if(i < 0)
|
|
return fail("poll for IBus address: %s", strerror(errno));
|
|
if(i == 0)
|
|
continue;
|
|
if(pfd[1].revents != 0)
|
|
readerrors(d);
|
|
if(!(pfd[0].revents & POLLIN))
|
|
continue;
|
|
n = read(notifyfd, buf, sizeof buf);
|
|
if(n < 0 && (errno == EINTR || errno == EAGAIN))
|
|
continue;
|
|
if(n < 0)
|
|
return fail("read IBus directory watch: %s", strerror(errno));
|
|
for(i = 0; i < n; i += sizeof *ev + ev->len){
|
|
ev = (struct inotify_event*)(buf + i);
|
|
if(!(ev->mask & IN_MOVED_TO) || ev->len == 0)
|
|
continue;
|
|
if(snprintf(d->addrfile, sizeof d->addrfile, "%s/%s",
|
|
d->bus, ev->name) >= (int)sizeof d->addrfile)
|
|
return fail("IBus address path is too long");
|
|
return readaddress(d);
|
|
}
|
|
}
|
|
}
|
|
|
|
static int
|
|
startdaemon(Daemon *d, char *program, char *mapdir)
|
|
{
|
|
int fd, notifyfd, watch, errpipe[2];
|
|
long maxfd;
|
|
pid_t pid;
|
|
|
|
memset(d, 0, sizeof *d);
|
|
d->pid = -1;
|
|
d->errfd = -1;
|
|
snprintf(d->root, sizeof d->root, "/tmp/strans-ibus.XXXXXX");
|
|
if(mkdtemp(d->root) == NULL)
|
|
return fail("mkdtemp: %s", strerror(errno));
|
|
if(snprintf(d->runtime, sizeof d->runtime, "%s/runtime", d->root)
|
|
>= (int)sizeof d->runtime ||
|
|
snprintf(d->config, sizeof d->config, "%s/config", d->root)
|
|
>= (int)sizeof d->config ||
|
|
snprintf(d->ibus, sizeof d->ibus, "%s/ibus", d->config)
|
|
>= (int)sizeof d->ibus ||
|
|
snprintf(d->bus, sizeof d->bus, "%s/bus", d->ibus)
|
|
>= (int)sizeof d->bus ||
|
|
snprintf(d->home, sizeof d->home, "%s/home", d->root)
|
|
>= (int)sizeof d->home ||
|
|
snprintf(d->socket, sizeof d->socket, "%s/strans.sock", d->runtime)
|
|
>= (int)sizeof d->socket)
|
|
return fail("temporary path is too long");
|
|
if(!makedir(d->runtime) || !makedir(d->config) || !makedir(d->ibus) ||
|
|
!makedir(d->bus) || !makedir(d->home))
|
|
return 0;
|
|
notifyfd = inotify_init1(IN_CLOEXEC|IN_NONBLOCK);
|
|
if(notifyfd < 0)
|
|
return fail("inotify_init1: %s", strerror(errno));
|
|
watch = inotify_add_watch(notifyfd, d->bus, IN_MOVED_TO);
|
|
if(watch < 0){
|
|
close(notifyfd);
|
|
return fail("inotify_add_watch: %s", strerror(errno));
|
|
}
|
|
if(pipe2(errpipe, O_CLOEXEC|O_NONBLOCK) < 0){
|
|
close(notifyfd);
|
|
return fail("pipe2: %s", strerror(errno));
|
|
}
|
|
pid = fork();
|
|
if(pid < 0){
|
|
close(errpipe[0]);
|
|
close(errpipe[1]);
|
|
close(notifyfd);
|
|
return fail("fork: %s", strerror(errno));
|
|
}
|
|
if(pid == 0){
|
|
close(errpipe[0]);
|
|
close(notifyfd);
|
|
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", d->runtime, 1) < 0 ||
|
|
setenv("XDG_CONFIG_HOME", d->config, 1) < 0 ||
|
|
setenv("HOME", d->home, 1) < 0 || unsetenv("DISPLAY") < 0 ||
|
|
unsetenv("DBUS_SESSION_BUS_ADDRESS") < 0 ||
|
|
unsetenv("IBUS_ADDRESS") < 0 ||
|
|
unsetenv("IBUS_ADDRESS_FILE") < 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]);
|
|
d->pid = pid;
|
|
d->errfd = errpipe[0];
|
|
if(!waitaddress(d, notifyfd)){
|
|
inotify_rm_watch(notifyfd, watch);
|
|
close(notifyfd);
|
|
return 0;
|
|
}
|
|
inotify_rm_watch(notifyfd, watch);
|
|
close(notifyfd);
|
|
return 1;
|
|
}
|
|
|
|
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
|
|
killdaemon(Daemon *d, int *status)
|
|
{
|
|
int err, n, ok;
|
|
|
|
ok = 1;
|
|
if(kill(d->pid, SIGKILL) < 0 && errno != ESRCH){
|
|
fail("kill -9 %ld: %s", (long)d->pid, strerror(errno));
|
|
ok = 0;
|
|
}
|
|
do
|
|
n = waitpid(d->pid, status, 0);
|
|
while(n < 0 && errno == EINTR);
|
|
if(n < 0 && errno == ECHILD){
|
|
d->pid = -1;
|
|
return ok;
|
|
}
|
|
if(n != d->pid){
|
|
err = errno;
|
|
fail("reap %ld after SIGKILL: %s", (long)d->pid,
|
|
n < 0 ? strerror(err) : "wrong child");
|
|
ok = 0;
|
|
}else
|
|
d->pid = -1;
|
|
return ok;
|
|
}
|
|
|
|
static int
|
|
stopdaemon(Daemon *d)
|
|
{
|
|
struct timespec pause;
|
|
int err, n, ok, reaped, status;
|
|
int64_t deadline;
|
|
|
|
ok = 1;
|
|
reaped = 0;
|
|
if(d->pid > 0){
|
|
do
|
|
n = waitpid(d->pid, &status, WNOHANG);
|
|
while(n < 0 && errno == EINTR);
|
|
if(n == d->pid){
|
|
fail("daemon exited before test termination with wait status %#x",
|
|
status);
|
|
d->pid = -1;
|
|
ok = 0;
|
|
}else if(n < 0){
|
|
err = errno;
|
|
fail("check daemon %ld: %s", (long)d->pid, strerror(err));
|
|
if(err == ECHILD)
|
|
d->pid = -1;
|
|
ok = 0;
|
|
}
|
|
}
|
|
if(d->pid > 0){
|
|
if(kill(d->pid, SIGTERM) < 0 && errno != ESRCH){
|
|
fail("kill %ld: %s", (long)d->pid, strerror(errno));
|
|
ok = 0;
|
|
}
|
|
pause.tv_sec = 0;
|
|
pause.tv_nsec = 10000000;
|
|
deadline = nowms() + Stoptimeout;
|
|
for(;;){
|
|
n = waitpid(d->pid, &status, WNOHANG);
|
|
if(n == d->pid){
|
|
reaped = 1;
|
|
d->pid = -1;
|
|
break;
|
|
}
|
|
if(n < 0 && errno == EINTR)
|
|
continue;
|
|
if(n < 0){
|
|
err = errno;
|
|
fail("waitpid %ld: %s", (long)d->pid, strerror(err));
|
|
if(err == ECHILD)
|
|
d->pid = -1;
|
|
ok = 0;
|
|
break;
|
|
}
|
|
if(leftms(deadline) == 0){
|
|
fail("daemon %ld did not stop after SIGTERM", (long)d->pid);
|
|
ok = 0;
|
|
if(!killdaemon(d, &status))
|
|
ok = 0;
|
|
break;
|
|
}
|
|
nanosleep(&pause, NULL);
|
|
readerrors(d);
|
|
}
|
|
if(reaped &&
|
|
!((WIFSIGNALED(status) && WTERMSIG(status) == SIGTERM) ||
|
|
(WIFEXITED(status) && WEXITSTATUS(status) == 1))){
|
|
fail("daemon exited with unexpected wait status %#x", status);
|
|
ok = 0;
|
|
}
|
|
}
|
|
readerrors(d);
|
|
if(d->errfd >= 0){
|
|
close(d->errfd);
|
|
d->errfd = -1;
|
|
}
|
|
if(d->socket[0] != '\0' && unlink(d->socket) < 0 && errno != ENOENT){
|
|
fail("remove IPC socket %s: %s", d->socket, strerror(errno));
|
|
ok = 0;
|
|
}
|
|
if(d->addrfile[0] != '\0' && unlink(d->addrfile) < 0 && errno != ENOENT){
|
|
fail("remove IBus address file %s: %s", d->addrfile,
|
|
strerror(errno));
|
|
ok = 0;
|
|
}
|
|
if(!rmdirknown(d->bus)) ok = 0;
|
|
if(!rmdirknown(d->ibus)) ok = 0;
|
|
if(!rmdirknown(d->config)) ok = 0;
|
|
if(!rmdirknown(d->runtime)) ok = 0;
|
|
if(!rmdirknown(d->home)) ok = 0;
|
|
if(!rmdirknown(d->root)) ok = 0;
|
|
return ok;
|
|
}
|
|
|
|
static DBusConnection*
|
|
openbus(char *address)
|
|
{
|
|
DBusConnection *conn;
|
|
DBusError err;
|
|
|
|
dbus_error_init(&err);
|
|
conn = dbus_connection_open_private(address, &err);
|
|
if(conn == NULL){
|
|
fail("connect to private IBus address: %s", err.message);
|
|
dbus_error_free(&err);
|
|
return NULL;
|
|
}
|
|
dbus_connection_set_exit_on_disconnect(conn, FALSE);
|
|
return conn;
|
|
}
|
|
|
|
static void
|
|
closebus(DBusConnection **conn)
|
|
{
|
|
if(*conn == NULL)
|
|
return;
|
|
dbus_connection_close(*conn);
|
|
dbus_connection_unref(*conn);
|
|
*conn = NULL;
|
|
}
|
|
|
|
static DBusMessage*
|
|
method(char *path, char *iface, char *member)
|
|
{
|
|
DBusMessage *m;
|
|
|
|
m = dbus_message_new_method_call("org.freedesktop.IBus", path,
|
|
iface, member);
|
|
if(m == NULL)
|
|
fail("allocate %s call: out of memory", member);
|
|
return m;
|
|
}
|
|
|
|
static DBusMessage*
|
|
sendcall(DBusConnection *conn, DBusMessage *m)
|
|
{
|
|
DBusMessage *reply;
|
|
DBusPendingCall *pending;
|
|
char member[64], path[128];
|
|
const char *s;
|
|
int timeout;
|
|
int64_t deadline;
|
|
|
|
if(m == NULL)
|
|
return NULL;
|
|
s = dbus_message_get_member(m);
|
|
snprintf(member, sizeof member, "%s", s != NULL ? s : "method");
|
|
s = dbus_message_get_path(m);
|
|
snprintf(path, sizeof path, "%s", s != NULL ? s : "path");
|
|
pending = NULL;
|
|
if(!dbus_connection_send_with_reply(conn, m, &pending, Calltimeout) ||
|
|
pending == NULL){
|
|
dbus_message_unref(m);
|
|
fail("queue %s on %s: out of memory", member, path);
|
|
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 on %s", member, path);
|
|
goto fail;
|
|
}
|
|
if(!dbus_connection_read_write_dispatch(conn, timeout)){
|
|
fail("connection closed waiting for %s on %s", member, path);
|
|
goto fail;
|
|
}
|
|
}
|
|
reply = dbus_pending_call_steal_reply(pending);
|
|
dbus_pending_call_unref(pending);
|
|
if(reply == NULL){
|
|
fail("%s on %s completed without a reply", member, path);
|
|
return NULL;
|
|
}
|
|
return reply;
|
|
fail:
|
|
dbus_pending_call_cancel(pending);
|
|
dbus_pending_call_unref(pending);
|
|
return NULL;
|
|
}
|
|
|
|
static int
|
|
hello(DBusConnection *conn)
|
|
{
|
|
DBusMessage *m, *reply;
|
|
DBusError err;
|
|
const char *name;
|
|
int ok;
|
|
|
|
m = dbus_message_new_method_call("org.freedesktop.DBus",
|
|
"/org/freedesktop/DBus", "org.freedesktop.DBus", "Hello");
|
|
reply = sendcall(conn, m);
|
|
if(reply == NULL)
|
|
return 0;
|
|
dbus_error_init(&err);
|
|
ok = dbus_message_get_type(reply) == DBUS_MESSAGE_TYPE_METHOD_RETURN &&
|
|
dbus_message_has_signature(reply, "s") &&
|
|
dbus_message_get_args(reply, &err, DBUS_TYPE_STRING, &name,
|
|
DBUS_TYPE_INVALID) && name[0] == ':';
|
|
if(!ok)
|
|
fail("Hello returned an invalid reply");
|
|
dbus_error_free(&err);
|
|
dbus_message_unref(reply);
|
|
return ok;
|
|
}
|
|
|
|
static int
|
|
createcontext(DBusConnection *conn, char *buf, size_t nbuf)
|
|
{
|
|
DBusMessage *m, *reply;
|
|
DBusError err;
|
|
const char *client, *path;
|
|
int ok;
|
|
|
|
client = "ibus-live-test";
|
|
m = method("/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 CreateInputContext call");
|
|
}
|
|
reply = sendcall(conn, m);
|
|
if(reply == NULL)
|
|
return 0;
|
|
dbus_error_init(&err);
|
|
ok = dbus_message_get_type(reply) == DBUS_MESSAGE_TYPE_METHOD_RETURN &&
|
|
dbus_message_has_signature(reply, "o") &&
|
|
dbus_message_get_args(reply, &err, DBUS_TYPE_OBJECT_PATH, &path,
|
|
DBUS_TYPE_INVALID) && path[0] == '/';
|
|
if(ok && buf != NULL && snprintf(buf, nbuf, "%s", path) >= (int)nbuf)
|
|
ok = 0;
|
|
if(!ok)
|
|
fail("CreateInputContext returned an invalid reply");
|
|
dbus_error_free(&err);
|
|
dbus_message_unref(reply);
|
|
return ok;
|
|
}
|
|
|
|
static int
|
|
invalidcall(DBusConnection *conn, char *path, char *member)
|
|
{
|
|
DBusMessage *reply;
|
|
const char *name;
|
|
int ok;
|
|
|
|
reply = sendcall(conn, method(path,
|
|
"org.freedesktop.IBus.InputContext", member));
|
|
if(reply == NULL)
|
|
return 0;
|
|
name = dbus_message_get_error_name(reply);
|
|
ok = dbus_message_get_type(reply) == DBUS_MESSAGE_TYPE_ERROR &&
|
|
name != NULL && strcmp(name, DBUS_ERROR_INVALID_ARGS) == 0;
|
|
if(!ok)
|
|
fail("malformed %s returned %s, expected %s", member,
|
|
name != NULL ? name : "a non-error reply", DBUS_ERROR_INVALID_ARGS);
|
|
dbus_message_unref(reply);
|
|
return ok;
|
|
}
|
|
|
|
static int
|
|
overflowrejected(char *address)
|
|
{
|
|
DBusConnection *conn;
|
|
DBusError err;
|
|
struct pollfd pfd;
|
|
int fd, n, ok, timeout;
|
|
int64_t deadline;
|
|
|
|
dbus_error_init(&err);
|
|
conn = dbus_connection_open_private(address, &err);
|
|
if(conn == NULL){
|
|
ok = dbus_error_is_set(&err);
|
|
dbus_error_free(&err);
|
|
return ok || fail("overflow connection failed without a D-Bus error");
|
|
}
|
|
dbus_error_free(&err);
|
|
dbus_connection_set_exit_on_disconnect(conn, FALSE);
|
|
if(!dbus_connection_get_unix_fd(conn, &fd)){
|
|
closebus(&conn);
|
|
return fail("overflow connection has no Unix descriptor");
|
|
}
|
|
ok = 0;
|
|
deadline = nowms() + Calltimeout;
|
|
for(;;){
|
|
if(!dbus_connection_get_is_connected(conn)){
|
|
ok = 1;
|
|
break;
|
|
}
|
|
timeout = leftms(deadline);
|
|
if(timeout == 0){
|
|
fail("65th IBus connection was not rejected");
|
|
break;
|
|
}
|
|
pfd.fd = fd;
|
|
pfd.events = POLLIN;
|
|
pfd.revents = 0;
|
|
n = poll(&pfd, 1, timeout);
|
|
if(n < 0 && errno == EINTR)
|
|
continue;
|
|
if(n < 0){
|
|
fail("poll overflow IBus connection: %s", strerror(errno));
|
|
break;
|
|
}
|
|
if(n == 0)
|
|
continue;
|
|
if(pfd.revents & POLLNVAL){
|
|
fail("overflow IBus descriptor became invalid");
|
|
break;
|
|
}
|
|
dbus_connection_read_write_dispatch(conn, 0);
|
|
}
|
|
closebus(&conn);
|
|
return ok;
|
|
}
|
|
|
|
static int
|
|
runcontract(Daemon *d)
|
|
{
|
|
static char *bad[] = {
|
|
"ProcessKeyEvent",
|
|
"SetEngine",
|
|
"SetCapabilities",
|
|
"SetCursorLocation",
|
|
};
|
|
DBusConnection *conn;
|
|
char path[96];
|
|
int i, ok;
|
|
|
|
conn = openbus(d->address);
|
|
if(conn == NULL)
|
|
return 0;
|
|
ok = hello(conn) && createcontext(conn, path, sizeof path);
|
|
for(i = 0; ok && i < (int)(sizeof bad / sizeof bad[0]); i++)
|
|
ok = invalidcall(conn, path, bad[i]);
|
|
closebus(&conn);
|
|
return ok;
|
|
}
|
|
|
|
static int
|
|
runcapacity(Daemon *d)
|
|
{
|
|
DBusConnection *conn[Maxconnections];
|
|
int i, ok;
|
|
|
|
for(i = 0; i < Maxconnections; i++)
|
|
conn[i] = NULL;
|
|
ok = 0;
|
|
for(i = 0; i < Maxconnections; i++){
|
|
conn[i] = openbus(d->address);
|
|
if(conn[i] == NULL || !hello(conn[i]))
|
|
goto out;
|
|
}
|
|
if(!overflowrejected(d->address))
|
|
goto out;
|
|
closebus(&conn[Maxconnections-1]);
|
|
if(!createcontext(conn[0], NULL, 0))
|
|
goto out;
|
|
conn[Maxconnections-1] = openbus(d->address);
|
|
if(conn[Maxconnections-1] == NULL ||
|
|
!hello(conn[Maxconnections-1]) ||
|
|
!createcontext(conn[Maxconnections-1], NULL, 0))
|
|
goto out;
|
|
ok = 1;
|
|
out:
|
|
for(i = 0; i < Maxconnections; i++)
|
|
closebus(&conn[i]);
|
|
return ok;
|
|
}
|
|
|
|
static int
|
|
runclient(Daemon *d, char *program)
|
|
{
|
|
struct timespec pause;
|
|
pid_t pid, n;
|
|
int status;
|
|
int64_t deadline;
|
|
|
|
pid = fork();
|
|
if(pid < 0)
|
|
return fail("fork official libibus client: %s", strerror(errno));
|
|
if(pid == 0){
|
|
if(setenv("XDG_RUNTIME_DIR", d->runtime, 1) < 0 ||
|
|
setenv("XDG_CONFIG_HOME", d->config, 1) < 0 ||
|
|
setenv("HOME", d->home, 1) < 0 ||
|
|
unsetenv("IBUS_ADDRESS_FILE") < 0)
|
|
_exit(126);
|
|
execl(program, program, d->address, (char*)0);
|
|
dprintf(STDERR_FILENO, "exec %s: %s\n", program, strerror(errno));
|
|
_exit(127);
|
|
}
|
|
pause.tv_sec = 0;
|
|
pause.tv_nsec = 10000000;
|
|
deadline = nowms() + Starttimeout;
|
|
for(;;){
|
|
n = waitpid(pid, &status, WNOHANG);
|
|
if(n == pid)
|
|
break;
|
|
if(n < 0 && errno == EINTR)
|
|
continue;
|
|
if(n < 0)
|
|
return fail("wait official libibus client: %s", strerror(errno));
|
|
if(leftms(deadline) == 0){
|
|
fail("official libibus client timed out");
|
|
kill(pid, SIGKILL);
|
|
do
|
|
n = waitpid(pid, &status, 0);
|
|
while(n < 0 && errno == EINTR);
|
|
return 0;
|
|
}
|
|
nanosleep(&pause, NULL);
|
|
}
|
|
if(!WIFEXITED(status) || WEXITSTATUS(status) != 0)
|
|
return fail("official libibus client exited with wait status %#x",
|
|
status);
|
|
return 1;
|
|
}
|
|
|
|
int
|
|
main(int argc, char **argv)
|
|
{
|
|
Daemon daemon;
|
|
int capacity, ok;
|
|
|
|
capacity = argc == 4 && strcmp(argv[1], "--capacity") == 0;
|
|
if(argc != 4){
|
|
fprintf(stderr, "usage: ibus_live_test strans mapdir libibus-client\n");
|
|
fprintf(stderr, " ibus_live_test --capacity strans mapdir\n");
|
|
return 2;
|
|
}
|
|
ok = startdaemon(&daemon, argv[capacity ? 2 : 1],
|
|
argv[capacity ? 3 : 2]);
|
|
if(ok)
|
|
ok = capacity ? runcapacity(&daemon) : runclient(&daemon, argv[3]);
|
|
if(ok && !capacity)
|
|
ok = runcontract(&daemon);
|
|
if(!ok)
|
|
showerrors(&daemon);
|
|
if(!stopdaemon(&daemon))
|
|
ok = 0;
|
|
if(!ok)
|
|
return 1;
|
|
printf(capacity ? "ibus connection capacity: ok\n" :
|
|
"ibus official client and malformed call: ok\n");
|
|
return 0;
|
|
}
|