tests: cover ipc listener capacity and recovery
This commit is contained in:
759
tests/ipc_live_test.c
Normal file
759
tests/ipc_live_test.c
Normal file
@@ -0,0 +1,759 @@
|
||||
#define _GNU_SOURCE
|
||||
#include <dirent.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.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
|
||||
{
|
||||
Maxclients = 64,
|
||||
Calltimeout = 4000,
|
||||
Starttimeout = 8000,
|
||||
Stoptimeout = 3000,
|
||||
};
|
||||
|
||||
typedef struct Daemon Daemon;
|
||||
typedef struct Response Response;
|
||||
|
||||
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 err[4096];
|
||||
size_t nerr;
|
||||
};
|
||||
|
||||
struct Response
|
||||
{
|
||||
int eaten;
|
||||
char commit[Ipcfieldmax+1];
|
||||
char preedit[Ipcfieldmax+1];
|
||||
};
|
||||
|
||||
static int
|
||||
fail(char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
fprintf(stderr, "ipc_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, "ipc_live_test: daemon stderr:\n%s", d->err);
|
||||
}
|
||||
|
||||
static int
|
||||
waitsocket(Daemon *d, int notifyfd)
|
||||
{
|
||||
struct pollfd pfd[2];
|
||||
struct stat st;
|
||||
char buf[4096];
|
||||
int n, status, timeout;
|
||||
int64_t deadline;
|
||||
|
||||
deadline = nowms() + Starttimeout;
|
||||
for(;;){
|
||||
if(lstat(d->socket, &st) == 0 && S_ISSOCK(st.st_mode) &&
|
||||
(st.st_mode & 0777) == 0600)
|
||||
return 1;
|
||||
if(waitpid(d->pid, &status, WNOHANG) == d->pid){
|
||||
d->pid = -1;
|
||||
readerrors(d);
|
||||
return fail("daemon exited before creating IPC socket");
|
||||
}
|
||||
timeout = leftms(deadline);
|
||||
if(timeout == 0)
|
||||
return fail("timed out waiting for protected IPC socket %s",
|
||||
d->socket);
|
||||
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;
|
||||
n = poll(pfd, 2, timeout);
|
||||
if(n < 0 && errno == EINTR)
|
||||
continue;
|
||||
if(n < 0)
|
||||
return fail("poll for IPC socket: %s", strerror(errno));
|
||||
if(n == 0)
|
||||
continue;
|
||||
if(pfd[1].revents != 0)
|
||||
readerrors(d);
|
||||
if(pfd[0].revents & POLLIN)
|
||||
while(read(notifyfd, buf, sizeof buf) < 0 && errno == EINTR)
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
startdaemon(Daemon *d, char *program, char *mapdir)
|
||||
{
|
||||
char missing[384];
|
||||
int notifyfd, watch, errpipe[2];
|
||||
pid_t pid;
|
||||
|
||||
memset(d, 0, sizeof *d);
|
||||
d->pid = -1;
|
||||
d->errfd = -1;
|
||||
snprintf(d->root, sizeof d->root, "/tmp/strans-ipc.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 ||
|
||||
snprintf(missing, sizeof missing, "%s/no-such-font.ttf", d->root)
|
||||
>= (int)sizeof missing)
|
||||
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->runtime,
|
||||
IN_CREATE|IN_MOVED_TO|IN_ATTRIB);
|
||||
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]);
|
||||
setenv("XDG_RUNTIME_DIR", d->runtime, 1);
|
||||
setenv("XDG_CONFIG_HOME", d->config, 1);
|
||||
setenv("HOME", d->home, 1);
|
||||
unsetenv("DISPLAY");
|
||||
unsetenv("WAYLAND_DISPLAY");
|
||||
unsetenv("DBUS_SESSION_BUS_ADDRESS");
|
||||
unsetenv("IBUS_ADDRESS");
|
||||
execl(program, program, mapdir, missing, (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(!waitsocket(d, notifyfd)){
|
||||
inotify_rm_watch(notifyfd, watch);
|
||||
close(notifyfd);
|
||||
return 0;
|
||||
}
|
||||
inotify_rm_watch(notifyfd, watch);
|
||||
close(notifyfd);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int
|
||||
clearbus(Daemon *d)
|
||||
{
|
||||
DIR *dir;
|
||||
struct dirent *de;
|
||||
char path[576];
|
||||
int ok;
|
||||
|
||||
dir = opendir(d->bus);
|
||||
if(dir == NULL)
|
||||
return errno == ENOENT || fail("open cleanup directory %s: %s",
|
||||
d->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", d->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 address file %s: %s", de->d_name,
|
||||
strerror(errno));
|
||||
ok = 0;
|
||||
}
|
||||
errno = 0;
|
||||
}
|
||||
if(errno != 0){
|
||||
fail("read cleanup directory %s: %s", d->bus, strerror(errno));
|
||||
ok = 0;
|
||||
}
|
||||
closedir(dir);
|
||||
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
|
||||
stopdaemon(Daemon *d)
|
||||
{
|
||||
struct pollfd pfd;
|
||||
int ok, status, n;
|
||||
int64_t deadline;
|
||||
|
||||
ok = 1;
|
||||
if(d->pid > 0){
|
||||
if(kill(d->pid, SIGTERM) < 0 && errno != ESRCH){
|
||||
fail("kill %ld: %s", (long)d->pid, strerror(errno));
|
||||
ok = 0;
|
||||
}
|
||||
deadline = nowms() + Stoptimeout;
|
||||
for(;;){
|
||||
n = waitpid(d->pid, &status, WNOHANG);
|
||||
if(n == d->pid)
|
||||
break;
|
||||
if(n < 0 && errno != EINTR){
|
||||
fail("waitpid %ld: %s", (long)d->pid, strerror(errno));
|
||||
ok = 0;
|
||||
break;
|
||||
}
|
||||
n = leftms(deadline);
|
||||
if(n == 0){
|
||||
fail("daemon %ld did not stop after SIGTERM", (long)d->pid);
|
||||
ok = 0;
|
||||
kill(d->pid, SIGKILL);
|
||||
while(waitpid(d->pid, &status, 0) < 0 && errno == EINTR)
|
||||
;
|
||||
break;
|
||||
}
|
||||
pfd.fd = d->errfd;
|
||||
pfd.events = POLLIN|POLLHUP;
|
||||
pfd.revents = 0;
|
||||
if(poll(&pfd, 1, n) < 0 && errno != EINTR){
|
||||
fail("poll daemon %ld: %s", (long)d->pid, strerror(errno));
|
||||
ok = 0;
|
||||
kill(d->pid, SIGKILL);
|
||||
while(waitpid(d->pid, &status, 0) < 0 && errno == EINTR)
|
||||
;
|
||||
break;
|
||||
}
|
||||
readerrors(d);
|
||||
}
|
||||
d->pid = -1;
|
||||
}
|
||||
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(!clearbus(d)) 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 int
|
||||
connectuntil(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;
|
||||
}
|
||||
pfd.fd = fd;
|
||||
pfd.events = POLLOUT;
|
||||
pfd.revents = 0;
|
||||
for(;;){
|
||||
n = leftms(deadline);
|
||||
if(n == 0){
|
||||
close(fd);
|
||||
errno = ETIMEDOUT;
|
||||
return -1;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
/* 1 is complete, 0 is peer closure, and -1 is a timeout or I/O error. */
|
||||
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){
|
||||
if(errno == EINTR)
|
||||
continue;
|
||||
return -1;
|
||||
}
|
||||
if(r == 0){
|
||||
errno = ETIMEDOUT;
|
||||
return -1;
|
||||
}
|
||||
r = recv(fd, p, n, 0);
|
||||
if(r < 0 && errno == EINTR)
|
||||
continue;
|
||||
if(r < 0)
|
||||
return -1;
|
||||
if(r == 0)
|
||||
return 0;
|
||||
p += r;
|
||||
n -= r;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static size_t
|
||||
getlen(unsigned char p[Ipclensz])
|
||||
{
|
||||
return p[0] | (p[1] << 8);
|
||||
}
|
||||
|
||||
static int
|
||||
readresponseuntil(int fd, int want, Response *res, int64_t deadline,
|
||||
int quiet)
|
||||
{
|
||||
unsigned char hdr[Ipcresphdrsz], len[Ipclensz];
|
||||
size_t n;
|
||||
int rv;
|
||||
|
||||
memset(res, 0, sizeof *res);
|
||||
rv = readuntil(fd, hdr, sizeof hdr, deadline);
|
||||
if(rv <= 0){
|
||||
if(!quiet)
|
||||
fail("read response header: %s",
|
||||
rv == 0 ? "peer closed" : strerror(errno));
|
||||
return rv;
|
||||
}
|
||||
res->eaten = hdr[0] != 0;
|
||||
n = getlen(hdr + 1);
|
||||
if(n > Ipcfieldmax){
|
||||
fail("invalid commit length %zu", n);
|
||||
return -1;
|
||||
}
|
||||
if(n != 0){
|
||||
rv = readuntil(fd, res->commit, n, deadline);
|
||||
if(rv <= 0){
|
||||
if(!quiet)
|
||||
fail("read commit: %s",
|
||||
rv == 0 ? "peer closed" : strerror(errno));
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
res->commit[n] = '\0';
|
||||
if(!want)
|
||||
return 1;
|
||||
rv = readuntil(fd, len, sizeof len, deadline);
|
||||
if(rv <= 0){
|
||||
if(!quiet)
|
||||
fail("read preedit length: %s",
|
||||
rv == 0 ? "peer closed" : strerror(errno));
|
||||
return rv;
|
||||
}
|
||||
n = getlen(len);
|
||||
if(n > Ipcfieldmax){
|
||||
fail("invalid preedit length %zu", n);
|
||||
return -1;
|
||||
}
|
||||
if(n != 0){
|
||||
rv = readuntil(fd, res->preedit, n, deadline);
|
||||
if(rv <= 0){
|
||||
if(!quiet)
|
||||
fail("read preedit: %s",
|
||||
rv == 0 ? "peer closed" : strerror(errno));
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
res->preedit[n] = '\0';
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int
|
||||
sendkey(int fd, int want, uint32_t mod, uint32_t key)
|
||||
{
|
||||
unsigned char req[Ipcreqsz];
|
||||
|
||||
ipcpackreq(req, want, mod, key);
|
||||
return ipcsend(fd, req, sizeof req) == 0;
|
||||
}
|
||||
|
||||
static int
|
||||
sendreset(int fd, int want)
|
||||
{
|
||||
unsigned char req[Ipcreqsz];
|
||||
|
||||
ipcpackreset(req, want);
|
||||
return ipcsend(fd, req, sizeof req) == 0;
|
||||
}
|
||||
|
||||
static int
|
||||
expectresponse(int fd, int want, int eaten, char *commit, char *preedit,
|
||||
char *where)
|
||||
{
|
||||
Response res;
|
||||
|
||||
if(readresponseuntil(fd, want, &res, nowms() + Calltimeout, 0) != 1)
|
||||
return 0;
|
||||
if(res.eaten != eaten || strcmp(res.commit, commit) != 0 ||
|
||||
(want && strcmp(res.preedit, preedit) != 0))
|
||||
return fail("%s: eaten=%d commit=%s preedit=%s",
|
||||
where, res.eaten, res.commit, res.preedit);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int
|
||||
requestreset(int fd, int want, int eaten, char *preedit, char *where)
|
||||
{
|
||||
if(!sendreset(fd, want))
|
||||
return fail("send %s: %s", where, strerror(errno));
|
||||
return expectresponse(fd, want, eaten, "", preedit, where);
|
||||
}
|
||||
|
||||
static int
|
||||
requestkey(int fd, int want, uint32_t mod, uint32_t key, int eaten,
|
||||
char *commit, char *preedit, char *where)
|
||||
{
|
||||
if(!sendkey(fd, want, mod, key))
|
||||
return fail("send %s: %s", where, strerror(errno));
|
||||
return expectresponse(fd, want, eaten, commit, preedit, where);
|
||||
}
|
||||
|
||||
static int
|
||||
tryclient(char *path, int64_t deadline, int *client)
|
||||
{
|
||||
Response res;
|
||||
int fd, rv;
|
||||
|
||||
fd = connectuntil(path, deadline);
|
||||
if(fd < 0)
|
||||
return errno == ETIMEDOUT ? -1 : 0;
|
||||
if(!sendkey(fd, 1, 0, Kmodfirst)){
|
||||
close(fd);
|
||||
return 0;
|
||||
}
|
||||
rv = readresponseuntil(fd, 1, &res, deadline, 1);
|
||||
if(rv != 1){
|
||||
close(fd);
|
||||
return rv < 0 && errno == ETIMEDOUT ? -1 : 0;
|
||||
}
|
||||
if(res.eaten || res.commit[0] != '\0' || res.preedit[0] != '\0'){
|
||||
close(fd);
|
||||
fail("replacement client returned invalid modifier response");
|
||||
return -1;
|
||||
}
|
||||
*client = fd;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int
|
||||
waitslot(char *path, int *client, char *where)
|
||||
{
|
||||
int rv;
|
||||
int64_t deadline;
|
||||
|
||||
deadline = nowms() + Calltimeout;
|
||||
for(;;){
|
||||
rv = tryclient(path, deadline, client);
|
||||
if(rv > 0)
|
||||
return 1;
|
||||
if(rv < 0 || leftms(deadline) == 0)
|
||||
return fail("%s: timed out waiting for a worker slot", where);
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
overflowrejected(char *path)
|
||||
{
|
||||
struct pollfd pfd;
|
||||
unsigned char byte;
|
||||
ssize_t n;
|
||||
int fd, timeout;
|
||||
int64_t deadline;
|
||||
|
||||
deadline = nowms() + Calltimeout;
|
||||
fd = connectuntil(path, deadline);
|
||||
if(fd < 0)
|
||||
return fail("overflow connect: %s", strerror(errno));
|
||||
if(!sendkey(fd, 1, 0, Kmodfirst)){
|
||||
close(fd);
|
||||
return 1;
|
||||
}
|
||||
for(;;){
|
||||
timeout = leftms(deadline);
|
||||
if(timeout == 0){
|
||||
close(fd);
|
||||
return fail("overflow client was not rejected");
|
||||
}
|
||||
pfd.fd = fd;
|
||||
pfd.events = POLLIN;
|
||||
pfd.revents = 0;
|
||||
n = poll(&pfd, 1, timeout);
|
||||
if(n < 0 && errno == EINTR)
|
||||
continue;
|
||||
if(n <= 0){
|
||||
close(fd);
|
||||
return fail("poll overflow client: %s",
|
||||
n == 0 ? "timed out" : strerror(errno));
|
||||
}
|
||||
n = recv(fd, &byte, 1, 0);
|
||||
if(n < 0 && errno == EINTR)
|
||||
continue;
|
||||
if(n == 0 || (n < 0 && (errno == ECONNRESET || errno == EPIPE))){
|
||||
close(fd);
|
||||
return 1;
|
||||
}
|
||||
if(n < 0){
|
||||
close(fd);
|
||||
return fail("read overflow rejection: %s", strerror(errno));
|
||||
}
|
||||
close(fd);
|
||||
return fail("overflow client received a response");
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
runlistener(Daemon *d)
|
||||
{
|
||||
int client[Maxclients];
|
||||
int i, ok;
|
||||
|
||||
for(i = 0; i < Maxclients; i++)
|
||||
client[i] = -1;
|
||||
ok = 0;
|
||||
client[0] = connectuntil(d->socket, nowms() + Calltimeout);
|
||||
if(client[0] < 0){
|
||||
fail("connect first client: %s", strerror(errno));
|
||||
goto out;
|
||||
}
|
||||
if(!requestkey(client[0], 1, Mctrl, 'n', 1, "", "", "select Japanese") ||
|
||||
!requestkey(client[0], 1, 0, 'k', 1, "", "k", "GTK preedit") ||
|
||||
!requestreset(client[0], 1, 1, "", "active reset"))
|
||||
goto out;
|
||||
if(!sendkey(client[0], 0, 0, 'k') ||
|
||||
!sendkey(client[0], 1, 0, Kmodfirst)){
|
||||
fail("send pipelined XIM/GTK requests: %s", strerror(errno));
|
||||
goto out;
|
||||
}
|
||||
if(!expectresponse(client[0], 0, 1, "", "", "XIM framing") ||
|
||||
!expectresponse(client[0], 1, 0, "", "k", "GTK after XIM framing"))
|
||||
goto out;
|
||||
for(i = 1; i < Maxclients; i++){
|
||||
client[i] = connectuntil(d->socket, nowms() + Calltimeout);
|
||||
if(client[i] < 0){
|
||||
fail("connect capacity client %d: %s", i, strerror(errno));
|
||||
goto out;
|
||||
}
|
||||
if(!requestkey(client[i], 1, 0, Kmodfirst, 0, "", "",
|
||||
"capacity modifier"))
|
||||
goto out;
|
||||
}
|
||||
if(!overflowrejected(d->socket))
|
||||
goto out;
|
||||
close(client[Maxclients-1]);
|
||||
client[Maxclients-1] = -1;
|
||||
if(!waitslot(d->socket, &client[Maxclients-1], "inactive slot recovery"))
|
||||
goto out;
|
||||
close(client[0]);
|
||||
client[0] = -1;
|
||||
if(!waitslot(d->socket, &client[0], "active slot recovery") ||
|
||||
!requestkey(client[0], 1, 0, 'a', 1, "", "あ",
|
||||
"post-disconnect composition"))
|
||||
goto out;
|
||||
ok = 1;
|
||||
out:
|
||||
for(i = 0; i < Maxclients; i++)
|
||||
if(client[i] >= 0)
|
||||
close(client[i]);
|
||||
return ok;
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
Daemon daemon;
|
||||
int ok;
|
||||
|
||||
if(argc != 3){
|
||||
fprintf(stderr, "usage: ipc_live_test strans mapdir\n");
|
||||
return 2;
|
||||
}
|
||||
ok = startdaemon(&daemon, argv[1], argv[2]);
|
||||
if(ok)
|
||||
ok = runlistener(&daemon);
|
||||
if(!ok)
|
||||
showerrors(&daemon);
|
||||
if(!stopdaemon(&daemon))
|
||||
ok = 0;
|
||||
if(!ok)
|
||||
return 1;
|
||||
printf("ipc live listener: ok\n");
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user