#define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #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; }