Files
strans/tests/ibus_live_test.c
Hojun-Cho cdd3e38195 fix(ibus): drop a context on the Service.Destroy libibus really sends
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.
2026-08-16 20:24:26 +09:00

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;
}