Files
strans/tests/ipc_live_test.c
Hojun-Cho bab6077c75 srv, ibus: remove the endpoints on a signal too
A stopped daemon left its socket and IBus address file behind, since
plan9port ends a process on SIGTERM without running exit handlers. Both
files now go on a note as well as on exit, and only while they are still
the ones this daemon made, so a successor is never robbed of its own.
2026-08-16 21:40:42 +09:00

349 lines
7.5 KiB
C

#define _GNU_SOURCE
#include <errno.h>
#include <poll.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include "ipc.h"
#include "live.h"
enum
{
Maxclients = 64,
Cappreedit = 1,
};
typedef struct Response Response;
struct Response
{
int eaten;
char commit[Ipcfieldmax+1];
char preedit[Ipcfieldmax+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
sendcap(int fd, int cap)
{
unsigned char req[Ipcreqsz];
ipcpackcap(req, cap);
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 *commit, char *preedit,
char *where)
{
if(!sendreset(fd, want))
return fail("send %s: %s", where, strerror(errno));
return expectresponse(fd, want, eaten, commit, 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 = connectsocket(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 = connectsocket(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
runsmoke(char *path)
{
int client, ok;
client = connectsocket(path, nowms() + Calltimeout);
if(client < 0)
return fail("connect client: %s", strerror(errno));
ok = sendcap(client, Cappreedit) &&
expectresponse(client, 1, 1, "", "", "negotiate preedit") &&
requestkey(client, 1, Mctrl, 'n', 1, "", "",
"select Japanese") &&
requestkey(client, 1, 0, 'k', 1, "", "k", "preedit") &&
requestreset(client, 1, 1, "k", "", "reset");
close(client);
return ok;
}
static int
runcapacity(char *path)
{
int client[Maxclients];
int i, ok;
for(i = 0; i < Maxclients; i++)
client[i] = -1;
ok = 0;
client[0] = connectsocket(path, 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"))
goto out;
for(i = 1; i < Maxclients; i++){
client[i] = connectsocket(path, 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(path))
goto out;
close(client[Maxclients-1]);
client[Maxclients-1] = -1;
if(!waitslot(path, &client[Maxclients-1], "inactive slot recovery"))
goto out;
close(client[0]);
client[0] = -1;
if(!waitslot(path, &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;
Live live;
int capacity, ok;
testname = "ipc_live_test";
capacity = argc == 4 && strcmp(argv[1], "--capacity") == 0;
if((!capacity && argc != 3) || (capacity && argc != 4)){
fprintf(stderr,
"usage: ipc_live_test [--capacity] strans mapdir\n");
return 2;
}
daemoninit(&daemon, "daemon");
ok = livesetup(&live, "ipc") &&
startdaemon(&live, &daemon, argv[1+capacity], argv[2+capacity]) &&
waitready(&live, &daemon, NULL, 0);
if(ok)
ok = capacity ? runcapacity(live.socket) : runsmoke(live.socket);
if(!ok)
showerrors(&daemon);
if(!stopdaemon(&daemon))
ok = 0;
/* SIGTERM must leave no socket behind. */
if(access(live.socket, F_OK) == 0)
ok = fail("socket %s survived the daemon", live.socket);
if(!closeerrors(&daemon))
ok = 0;
if(!liveclean(&live))
ok = 0;
if(!ok)
return 1;
printf("ipc %s: ok\n", capacity ? "connection capacity" : "live smoke");
return 0;
}