libibus destroys an input context with Destroy on org.freedesktop.IBus.Service, not on the InputContext interface, so the call went unanswered and the context slot stayed taken until the whole connection closed. A long-lived GTK process that opens and closes text widgets exhausted the 1024 slots and then got LimitsExceeded for every new field. The live contract test now destroys a context that way and checks the path is gone.
261 lines
6.1 KiB
C
261 lines
6.1 KiB
C
#define _GNU_SOURCE
|
|
#include <errno.h>
|
|
#include <poll.h>
|
|
#include <signal.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/types.h>
|
|
#include <sys/wait.h>
|
|
#include <unistd.h>
|
|
|
|
#include "live.h"
|
|
#include "livebus.h"
|
|
|
|
enum
|
|
{
|
|
Maxconnections = 64,
|
|
};
|
|
|
|
static int
|
|
invalidcall(DBusConnection *conn, char *path, char *member)
|
|
{
|
|
DBusMessage *reply;
|
|
const char *name;
|
|
int ok;
|
|
|
|
reply = sendcall(conn, contextcall(path, member), 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;
|
|
}
|
|
|
|
/* libibus destroys through the Service interface; the path must go away. */
|
|
static int
|
|
destroyed(DBusConnection *conn, char *path)
|
|
{
|
|
DBusMessage *m, *reply;
|
|
const char *name;
|
|
int ok;
|
|
|
|
m = dbus_message_new_method_call("org.freedesktop.IBus", path,
|
|
"org.freedesktop.IBus.Service", "Destroy");
|
|
reply = callret(conn, m, "Service.Destroy");
|
|
if(reply == NULL)
|
|
return 0;
|
|
dbus_message_unref(reply);
|
|
reply = sendcall(conn, contextcall(path, "FocusIn"), "FocusIn");
|
|
if(reply == NULL)
|
|
return 0;
|
|
name = dbus_message_get_error_name(reply);
|
|
ok = name != NULL && strcmp(name, DBUS_ERROR_UNKNOWN_OBJECT) == 0;
|
|
if(!ok)
|
|
fail("context survived Service.Destroy");
|
|
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(char *address)
|
|
{
|
|
static char *bad[] = {
|
|
"ProcessKeyEvent",
|
|
"SetEngine",
|
|
"SetCapabilities",
|
|
"SetCursorLocation",
|
|
};
|
|
DBusConnection *conn;
|
|
char path[96];
|
|
int i, ok;
|
|
|
|
conn = openbus(address, "contract");
|
|
if(conn == NULL)
|
|
return 0;
|
|
ok = hello(conn, NULL, 0, "contract Hello") &&
|
|
createcontext(conn, path, sizeof path, "contract context");
|
|
for(i = 0; ok && i < (int)(sizeof bad / sizeof bad[0]); i++)
|
|
ok = invalidcall(conn, path, bad[i]);
|
|
ok = ok && destroyed(conn, path);
|
|
closebus(&conn);
|
|
return ok;
|
|
}
|
|
|
|
static int
|
|
runcapacity(char *address)
|
|
{
|
|
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(address, "capacity");
|
|
if(conn[i] == NULL || !hello(conn[i], NULL, 0, "capacity Hello"))
|
|
goto out;
|
|
}
|
|
if(!overflowrejected(address))
|
|
goto out;
|
|
closebus(&conn[Maxconnections-1]);
|
|
if(!createcontext(conn[0], NULL, 0, "capacity context"))
|
|
goto out;
|
|
conn[Maxconnections-1] = openbus(address, "replacement");
|
|
if(conn[Maxconnections-1] == NULL ||
|
|
!hello(conn[Maxconnections-1], NULL, 0, "replacement Hello") ||
|
|
!createcontext(conn[Maxconnections-1], NULL, 0, "replacement context"))
|
|
goto out;
|
|
ok = 1;
|
|
out:
|
|
for(i = 0; i < Maxconnections; i++)
|
|
closebus(&conn[i]);
|
|
return ok;
|
|
}
|
|
|
|
static int
|
|
runclient(Live *l, char *address, char *program)
|
|
{
|
|
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", l->runtime, 1) < 0 ||
|
|
setenv("XDG_CONFIG_HOME", l->config, 1) < 0 ||
|
|
setenv("HOME", l->home, 1) < 0 ||
|
|
unsetenv("IBUS_ADDRESS_FILE") < 0)
|
|
_exit(126);
|
|
execl(program, program, address, (char*)0);
|
|
dprintf(STDERR_FILENO, "exec %s: %s\n", program, strerror(errno));
|
|
_exit(127);
|
|
}
|
|
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;
|
|
}
|
|
pausems(10);
|
|
}
|
|
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;
|
|
Live live;
|
|
char address[512];
|
|
int capacity, ok;
|
|
|
|
testname = "ibus_live_test";
|
|
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;
|
|
}
|
|
daemoninit(&daemon, "daemon");
|
|
ok = livesetup(&live, "ibus") &&
|
|
startdaemon(&live, &daemon, argv[capacity ? 2 : 1],
|
|
argv[capacity ? 3 : 2]) &&
|
|
waitready(&live, &daemon, address, sizeof address);
|
|
if(ok)
|
|
ok = capacity ? runcapacity(address) :
|
|
runclient(&live, address, argv[3]) && runcontract(address);
|
|
if(!ok)
|
|
showerrors(&daemon);
|
|
if(!stopdaemon(&daemon))
|
|
ok = 0;
|
|
if(!closeerrors(&daemon))
|
|
ok = 0;
|
|
if(!liveclean(&live))
|
|
ok = 0;
|
|
if(!ok)
|
|
return 1;
|
|
printf(capacity ? "ibus connection capacity: ok\n" :
|
|
"ibus official client and malformed call: ok\n");
|
|
return 0;
|
|
}
|