ipc.c: an AF_UNIX nonblocking connect completes at once or fails with EAGAIN, so the EINPROGRESS/poll/SO_ERROR path and the fcntl juggling were dead; the deadline plumbing checked a clock that cannot fail and re-tested the deadline before every transfer although only waitfd blocks; readfield's truncation and discard loop served the unit test, since every caller owns char[Ipcfieldmax+1]; NULL-argument checks on in-tree encoders are gone (peer validation stays). The primary key frame is Ipckey, not 'legacy'; ipckeysym names the keysym mapping. srv.c: the per-connection capability and caret already lived in the persistent Keyreq; the mirror locals and 'negotiated' flag guarded a protocol rule no client relied on. proccreate never fails in libthread. gtk: focus-out no longer round-trips a reset before closing the socket that releases the engine; the caret dedup compares the packed frame; srvconnect's preedit no-ops and the insimple flag are gone.
438 lines
12 KiB
C
438 lines
12 KiB
C
#define _POSIX_C_SOURCE 200809L
|
|
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <stdlib.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/un.h>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
#include "test.h"
|
|
|
|
#undef accept
|
|
#undef listen
|
|
#undef send
|
|
|
|
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 void ipcclientdeadlines(struct ct*);
|
|
static void ipcconnectcloexec(struct ct*);
|
|
|
|
void
|
|
ipc_masks_modifiers(struct ct *t)
|
|
{
|
|
uchar buf[Ipcreqsz];
|
|
u32int key, mod;
|
|
int want;
|
|
|
|
ipcpackreq(buf, 1, Mctrl|Malt|(1<<1)|(1<<4)|(1<<7), 0x1f642);
|
|
ipcunpackreq(buf, &want, &mod, &key);
|
|
CT_EQ_INT(t, 1, want);
|
|
CT_EQ_UINT(t, Mctrl|Malt, mod);
|
|
CT_EQ_UINT(t, 0x1f642, key);
|
|
CT_CHECK(t, !ipcreqreset(buf));
|
|
|
|
ipcpackreset(buf, 1);
|
|
ipcunpackreq(buf, &want, &mod, &key);
|
|
CT_EQ_INT(t, 1, want);
|
|
CT_EQ_UINT(t, 0, mod);
|
|
CT_EQ_UINT(t, 0, key);
|
|
CT_CHECK(t, ipcreqreset(buf));
|
|
|
|
/* Frontends share one keysym and modifier mapping. */
|
|
CT_EQ_UINT(t, 'a', ipckeysym('a', 'a'));
|
|
CT_EQ_UINT(t, '1', ipckeysym(0xffb1, '1')); /* KP_1 */
|
|
CT_EQ_UINT(t, Kret, ipckeysym(0xff0d, '\r'));
|
|
CT_EQ_UINT(t, Kback, ipckeysym(0xff08, 8));
|
|
CT_EQ_UINT(t, Kspec + 0xff, ipckeysym(0xffff, 0x7f)); /* Delete */
|
|
CT_EQ_UINT(t, 0x1f642, ipckeysym(0x101f642, 0x1f642));
|
|
CT_EQ_UINT(t, 0, ipckeysym(0xfe03, 0)); /* ISO_Level3_Shift */
|
|
CT_EQ_UINT(t, Mshift|Mctrl, ipcmod(Mshift|Mctrl|(1<<1)|(1<<4)));
|
|
CT_EQ_UINT(t, Msuper, ipcmod(1<<6));
|
|
CT_EQ_UINT(t, Msuper, ipcmod(1<<26));
|
|
}
|
|
|
|
void
|
|
ipc_control_and_caret_frames(struct ct *t)
|
|
{
|
|
static const uchar capon[Ipcreqsz] = {
|
|
Ipcext|Cclientpreedit, Ipcversion, Ipcopcap, 0, 0, 0,
|
|
};
|
|
static const uchar capoff[Ipcreqsz] = {
|
|
Ipcext, Ipcversion, Ipcopcap, 0, 0, 0,
|
|
};
|
|
static const uchar caret[Ipccaretsz] = {
|
|
Ipcext, Ipcversion, Ipcopcaret, 1,
|
|
0xfe, 0xff, 0xff, 0xff,
|
|
0x04, 0x03, 0x02, 0x01,
|
|
0x06, 0x05, 0x00, 0x00,
|
|
};
|
|
static const uchar nocaret[Ipccaretsz] = {
|
|
Ipcext, Ipcversion, Ipcopcaret, 0,
|
|
};
|
|
uchar buf[Ipccaretsz], bad[Ipccaretsz];
|
|
u32int key, mod;
|
|
int valid, want;
|
|
int32_t x, y, h;
|
|
|
|
ipcpackcap(buf, Cclientpreedit);
|
|
CT_EQ_MEM(t, capon, buf, sizeof capon);
|
|
CT_EQ_INT(t, Ipccap, ipcreqtype(buf));
|
|
/* The probe remains a harmless key-zero request to an old daemon. */
|
|
ipcunpackreq(buf, &want, &mod, &key);
|
|
CT_EQ_INT(t, 1, want);
|
|
CT_EQ_UINT(t, 0, key);
|
|
ipcpackcap(buf, 0);
|
|
CT_EQ_MEM(t, capoff, buf, sizeof capoff);
|
|
ipcunpackreq(buf, &want, &mod, &key);
|
|
CT_EQ_INT(t, 0, want);
|
|
CT_EQ_UINT(t, 0, key);
|
|
|
|
ipcpackcaret(buf, 1, -2, 0x01020304, 0x506);
|
|
CT_EQ_MEM(t, caret, buf, sizeof caret);
|
|
CT_EQ_INT(t, Ipccaret, ipcreqtype(buf));
|
|
if(CT_EQ_INT(t, 0, ipcunpackcaret(buf, &valid, &x, &y, &h))){
|
|
CT_EQ_INT(t, 1, valid);
|
|
CT_EQ_INT(t, -2, x);
|
|
CT_EQ_INT(t, 0x01020304, y);
|
|
CT_EQ_INT(t, 0x506, h);
|
|
}
|
|
ipcpackcaret(buf, 1, INT32_MIN, INT32_MAX, 0);
|
|
if(CT_EQ_INT(t, 0, ipcunpackcaret(buf, &valid, &x, &y, &h))){
|
|
CT_EQ_INT(t, INT32_MIN, x);
|
|
CT_EQ_INT(t, INT32_MAX, y);
|
|
CT_EQ_INT(t, 0, h);
|
|
}
|
|
ipcpackcaret(buf, 0, -2, 3, 4);
|
|
CT_EQ_MEM(t, nocaret, buf, sizeof nocaret);
|
|
if(CT_EQ_INT(t, 0, ipcunpackcaret(buf, &valid, &x, &y, &h)))
|
|
CT_EQ_INT(t, 0, valid);
|
|
|
|
memcpy(bad, caret, sizeof bad);
|
|
bad[1]++;
|
|
CT_EQ_INT(t, Ipcunknown, ipcreqtype(bad));
|
|
CT_EQ_INT(t, -1, ipcunpackcaret(bad, &valid, &x, &y, &h));
|
|
memcpy(bad, caret, sizeof bad);
|
|
bad[2]++;
|
|
CT_EQ_INT(t, Ipcunknown, ipcreqtype(bad));
|
|
CT_EQ_INT(t, -1, ipcunpackcaret(bad, &valid, &x, &y, &h));
|
|
memcpy(bad, caret, sizeof bad);
|
|
bad[3] = 2;
|
|
CT_EQ_INT(t, -1, ipcunpackcaret(bad, &valid, &x, &y, &h));
|
|
memcpy(bad, caret, sizeof bad);
|
|
bad[0] |= Ipcreqwant;
|
|
CT_EQ_INT(t, Ipcunknown, ipcreqtype(bad));
|
|
memcpy(bad, caret, sizeof bad);
|
|
bad[15] = 0x80;
|
|
CT_EQ_INT(t, -1, ipcunpackcaret(bad, &valid, &x, &y, &h));
|
|
memcpy(bad, capon, sizeof capon);
|
|
bad[3] = 1;
|
|
CT_EQ_INT(t, Ipcunknown, ipcreqtype(bad));
|
|
|
|
ipcpackreq(buf, 1, 0, 'a');
|
|
CT_EQ_INT(t, Ipckey, ipcreqtype(buf));
|
|
ipcpackreset(buf, 1);
|
|
CT_EQ_INT(t, Ipckey, ipcreqtype(buf));
|
|
}
|
|
|
|
void
|
|
ipc_runtime_path(struct ct *t)
|
|
{
|
|
char buf[128], fallback[128], *old, *saved;
|
|
|
|
old = getenv("XDG_RUNTIME_DIR");
|
|
saved = old == nil ? nil : strdup(old);
|
|
setenv("XDG_RUNTIME_DIR", "/tmp/strans-runtime-test", 1);
|
|
CT_EQ_INT(t, 0, ipcpath(buf, sizeof buf));
|
|
CT_EQ_STR(t, "/tmp/strans-runtime-test/strans.sock", buf);
|
|
setenv("XDG_RUNTIME_DIR", "/tmp/strans-runtime-test/", 1);
|
|
CT_EQ_INT(t, 0, ipcpath(buf, sizeof buf));
|
|
CT_EQ_STR(t, "/tmp/strans-runtime-test/strans.sock", buf);
|
|
setenv("XDG_RUNTIME_DIR", "relative", 1);
|
|
CT_EQ_INT(t, 0, ipcpath(buf, sizeof buf));
|
|
snprint(fallback, sizeof fallback, "/tmp/strans.%d", getuid());
|
|
CT_EQ_STR(t, fallback, buf);
|
|
CT_EQ_INT(t, -1, ipcpath(buf, 4));
|
|
if(saved != nil){
|
|
setenv("XDG_RUNTIME_DIR", saved, 1);
|
|
free(saved);
|
|
}else
|
|
unsetenv("XDG_RUNTIME_DIR");
|
|
}
|
|
|
|
void
|
|
ipc_response_pack_boundaries(struct ct *t)
|
|
{
|
|
static const uchar withpre[] = { 1, 1, 0, 'A', 2, 0, 'x', 'y' };
|
|
static const uchar withoutpre[] = { 1, 1, 0, 'A' };
|
|
uchar out[Ipcmaxresp+1], field[Ipcfieldmax];
|
|
int n;
|
|
|
|
n = ipcpackresp(out, sizeof out, 7, "A", 1, "xy", 2, 1);
|
|
CT_EQ_INT(t, sizeof withpre, n);
|
|
CT_EQ_MEM(t, withpre, out, sizeof withpre);
|
|
n = ipcpackresp(out, sizeof out, 1, "A", 1, "xy", 2, 0);
|
|
CT_EQ_INT(t, sizeof withoutpre, n);
|
|
CT_EQ_MEM(t, withoutpre, out, sizeof withoutpre);
|
|
n = ipcpackresp(out, sizeof out, 0, nil, 0, nil, 0, 1);
|
|
CT_EQ_INT(t, Ipcresphdrsz + Ipclensz, n);
|
|
|
|
memset(field, 'x', sizeof field);
|
|
memset(out, 0xa5, sizeof out);
|
|
n = ipcpackresp(out, Ipcmaxresp, 1,
|
|
(char*)field, sizeof field, (char*)field, sizeof field, 1);
|
|
CT_EQ_INT(t, Ipcmaxresp, n);
|
|
CT_EQ_INT(t, 0, out[1]);
|
|
CT_EQ_INT(t, 1, out[2]);
|
|
CT_EQ_INT(t, 0, out[3+Ipcfieldmax]);
|
|
CT_EQ_INT(t, 1, out[4+Ipcfieldmax]);
|
|
CT_EQ_INT(t, 0xa5, out[Ipcmaxresp]);
|
|
CT_EQ_INT(t, -1, ipcpackresp(out, Ipcmaxresp-1, 1,
|
|
(char*)field, sizeof field, (char*)field, sizeof field, 1));
|
|
CT_EQ_INT(t, -1, ipcpackresp(out, sizeof out, 0,
|
|
(char*)field, Ipcfieldmax+1, "", 0, 0));
|
|
}
|
|
|
|
void
|
|
ipc_response_empty_and_preedit(struct ct *t)
|
|
{
|
|
uchar first[Ipcmaxresp], second[Ipcmaxresp];
|
|
char commit[Ipcfieldmax+1], preedit[Ipcfieldmax+1];
|
|
Ipcresp resp;
|
|
int fd[2], nfirst, nsecond;
|
|
|
|
nfirst = ipcpackresp(first, sizeof first, 0, "", 0, "", 0, 0);
|
|
nsecond = ipcpackresp(second, sizeof second, 1,
|
|
"go", 2, "kana", 4, 1);
|
|
if(!CT_CHECK(t, nfirst > 0 && nsecond > 0))
|
|
return;
|
|
if(socketpair(AF_UNIX, SOCK_STREAM, 0, fd) < 0){
|
|
CT_ERRORF(t, "socketpair failed");
|
|
return;
|
|
}
|
|
if(CT_EQ_INT(t, 0, ipcsend(fd[0], first, nfirst)) &&
|
|
CT_EQ_INT(t, 0, ipcsend(fd[0], second, nsecond)) &&
|
|
CT_EQ_INT(t, 0, ipcreadresp(fd[1], 0, commit, preedit, &resp))){
|
|
CT_EQ_INT(t, 0, resp.eaten);
|
|
CT_EQ_SIZE(t, 0, resp.commitlen);
|
|
CT_EQ_SIZE(t, 0, resp.preeditlen);
|
|
CT_EQ_STR(t, "", commit);
|
|
}
|
|
if(CT_EQ_INT(t, 0, ipcreadresp(fd[1], 1, commit, preedit, &resp))){
|
|
CT_EQ_INT(t, 1, resp.eaten);
|
|
CT_EQ_SIZE(t, 2, resp.commitlen);
|
|
CT_EQ_SIZE(t, 4, resp.preeditlen);
|
|
CT_EQ_STR(t, "go", commit);
|
|
CT_EQ_STR(t, "kana", preedit);
|
|
}
|
|
close(fd[0]);
|
|
close(fd[1]);
|
|
}
|
|
|
|
void
|
|
ipc_response_max_and_drain(struct ct *t)
|
|
{
|
|
uchar first[Ipcmaxresp], second[Ipcmaxresp], field[Ipcfieldmax];
|
|
char commit[Ipcfieldmax+1], preedit[Ipcfieldmax+1];
|
|
Ipcresp resp;
|
|
int fd[2], nfirst, nsecond;
|
|
|
|
memset(field, 'x', sizeof field);
|
|
nfirst = ipcpackresp(first, sizeof first, 1,
|
|
(char*)field, sizeof field, (char*)field, sizeof field, 1);
|
|
nsecond = ipcpackresp(second, sizeof second, 0, "ok", 2, "", 0, 0);
|
|
if(!CT_CHECK(t, nfirst == Ipcmaxresp && nsecond > 0))
|
|
return;
|
|
if(socketpair(AF_UNIX, SOCK_STREAM, 0, fd) < 0){
|
|
CT_ERRORF(t, "socketpair failed");
|
|
return;
|
|
}
|
|
if(CT_EQ_INT(t, 0, ipcsend(fd[0], first, nfirst)) &&
|
|
CT_EQ_INT(t, 0, ipcsend(fd[0], second, nsecond)) &&
|
|
CT_EQ_INT(t, 0, ipcreadresp(fd[1], 1, commit, preedit, &resp))){
|
|
CT_EQ_SIZE(t, Ipcfieldmax, resp.commitlen);
|
|
CT_EQ_SIZE(t, Ipcfieldmax, resp.preeditlen);
|
|
CT_EQ_MEM(t, field, commit, Ipcfieldmax);
|
|
CT_EQ_MEM(t, field, preedit, Ipcfieldmax);
|
|
CT_EQ_INT(t, 0, commit[Ipcfieldmax]);
|
|
CT_EQ_INT(t, 0, preedit[Ipcfieldmax]);
|
|
}
|
|
if(CT_EQ_INT(t, 0, ipcreadresp(fd[1], 0, commit, preedit, &resp)))
|
|
CT_EQ_STR(t, "ok", commit);
|
|
close(fd[0]);
|
|
close(fd[1]);
|
|
}
|
|
|
|
void
|
|
ipc_response_fragmented_and_truncated(struct ct *t)
|
|
{
|
|
uchar frame[Ipcmaxresp];
|
|
char commit[Ipcfieldmax+1], preedit[Ipcfieldmax+1];
|
|
Ipcresp resp;
|
|
int fd[2], i, n;
|
|
|
|
n = ipcpackresp(frame, sizeof frame, 1, "abc", 3, "xy", 2, 1);
|
|
if(!CT_CHECK(t, n > 0))
|
|
return;
|
|
if(socketpair(AF_UNIX, SOCK_STREAM, 0, fd) < 0){
|
|
CT_ERRORF(t, "socketpair failed");
|
|
return;
|
|
}
|
|
for(i = 0; i < n; i++)
|
|
if(ipcsend(fd[0], frame+i, 1) < 0){
|
|
CT_ERRORF(t, "fragment send failed");
|
|
break;
|
|
}
|
|
if(i == n && CT_EQ_INT(t, 0, ipcreadresp(fd[1], 1, commit, preedit,
|
|
&resp))){
|
|
CT_EQ_STR(t, "abc", commit);
|
|
CT_EQ_STR(t, "xy", preedit);
|
|
}
|
|
close(fd[0]);
|
|
close(fd[1]);
|
|
|
|
if(socketpair(AF_UNIX, SOCK_STREAM, 0, fd) < 0){
|
|
CT_ERRORF(t, "socketpair failed");
|
|
return;
|
|
}
|
|
CT_EQ_INT(t, 0, ipcsend(fd[0], frame, n-1));
|
|
shutdown(fd[0], SHUT_WR);
|
|
CT_EQ_INT(t, -1, ipcreadresp(fd[1], 1, commit, preedit, &resp));
|
|
close(fd[0]);
|
|
close(fd[1]);
|
|
|
|
if(socketpair(AF_UNIX, SOCK_STREAM, 0, fd) < 0){
|
|
CT_ERRORF(t, "socketpair failed");
|
|
return;
|
|
}
|
|
frame[0] = 2;
|
|
CT_EQ_INT(t, 0, ipcsend(fd[0], frame, n));
|
|
errno = 0;
|
|
CT_EQ_INT(t, -1, ipcreadresp(fd[1], 1, commit, preedit, &resp));
|
|
CT_EQ_INT(t, EPROTO, errno);
|
|
close(fd[0]);
|
|
close(fd[1]);
|
|
}
|
|
|
|
void
|
|
ipc_broken_peer_send(struct ct *t)
|
|
{
|
|
int fd[2];
|
|
|
|
if(socketpair(AF_UNIX, SOCK_STREAM, 0, fd) < 0){
|
|
CT_ERRORF(t, "socketpair failed");
|
|
return;
|
|
}
|
|
close(fd[1]);
|
|
CT_EQ_INT(t, -1, ipcsend(fd[0], "x", 1));
|
|
close(fd[0]);
|
|
ipcclientdeadlines(t);
|
|
ipcconnectcloexec(t);
|
|
}
|
|
|
|
static void
|
|
ipcclientdeadlines(struct ct *t)
|
|
{
|
|
char fill[4096], commit[Ipcfieldmax+1], preedit[Ipcfieldmax+1];
|
|
Ipcresp resp;
|
|
int fd[2];
|
|
int64_t start, elapsed;
|
|
ssize_t n;
|
|
|
|
if(socketpair(AF_UNIX, SOCK_STREAM, 0, fd) < 0){
|
|
CT_ERRORF(t, "socketpair failed");
|
|
return;
|
|
}
|
|
start = nowms();
|
|
errno = 0;
|
|
CT_EQ_INT(t, -1, ipcreadresp(fd[0], 0, commit, preedit, &resp));
|
|
elapsed = nowms() - start;
|
|
CT_EQ_INT(t, ETIMEDOUT, errno);
|
|
CT_CHECK(t, elapsed >= 0 && elapsed < 4*Ipcwaitms);
|
|
|
|
memset(fill, 'x', sizeof fill);
|
|
for(;;){
|
|
n = send(fd[0], fill, sizeof fill, MSG_DONTWAIT|MSG_NOSIGNAL);
|
|
if(n < 0 && (errno == EAGAIN || errno == EWOULDBLOCK))
|
|
break;
|
|
if(n <= 0){
|
|
CT_ERRORF(t, "fill send failed: %s", strerror(errno));
|
|
close(fd[0]);
|
|
close(fd[1]);
|
|
return;
|
|
}
|
|
}
|
|
start = nowms();
|
|
errno = 0;
|
|
CT_EQ_INT(t, -1, ipcsend(fd[0], "x", 1));
|
|
elapsed = nowms() - start;
|
|
CT_EQ_INT(t, ETIMEDOUT, errno);
|
|
CT_CHECK(t, elapsed >= 0 && elapsed < 4*Ipcwaitms);
|
|
close(fd[0]);
|
|
close(fd[1]);
|
|
}
|
|
|
|
static void
|
|
ipcconnectcloexec(struct ct *t)
|
|
{
|
|
struct sockaddr_un addr;
|
|
char root[] = "/tmp/strans-ipc.XXXXXX";
|
|
char *old, *saved;
|
|
int accepted, client, flags, full, listener;
|
|
|
|
accepted = client = full = listener = -1;
|
|
old = getenv("XDG_RUNTIME_DIR");
|
|
saved = old == nil ? nil : strdup(old);
|
|
if(mkdtemp(root) == nil){
|
|
CT_ERRORF(t, "mkdtemp failed: %s", strerror(errno));
|
|
free(saved);
|
|
return;
|
|
}
|
|
setenv("XDG_RUNTIME_DIR", root, 1);
|
|
memset(&addr, 0, sizeof addr);
|
|
addr.sun_family = AF_UNIX;
|
|
if(ipcpath(addr.sun_path, sizeof addr.sun_path) < 0 ||
|
|
(listener = socket(AF_UNIX, SOCK_STREAM, 0)) < 0 ||
|
|
bind(listener, (struct sockaddr*)&addr, sizeof addr) < 0 ||
|
|
listen(listener, 0) < 0){
|
|
CT_ERRORF(t, "listen failed: %s", strerror(errno));
|
|
goto Out;
|
|
}
|
|
client = ipcconnect();
|
|
if(!CT_CHECK(t, client >= 0))
|
|
goto Out;
|
|
flags = fcntl(client, F_GETFD);
|
|
CT_CHECK(t, flags >= 0 && (flags & FD_CLOEXEC) != 0);
|
|
errno = 0;
|
|
full = ipcconnect();
|
|
CT_EQ_INT(t, -1, full);
|
|
CT_CHECK(t, errno == EAGAIN || errno == EWOULDBLOCK);
|
|
accepted = accept(listener, nil, nil);
|
|
if(!CT_CHECK(t, accepted >= 0))
|
|
goto Out;
|
|
Out:
|
|
if(full >= 0)
|
|
close(full);
|
|
if(accepted >= 0)
|
|
close(accepted);
|
|
if(client >= 0)
|
|
close(client);
|
|
if(listener >= 0)
|
|
close(listener);
|
|
unlink(addr.sun_path);
|
|
rmdir(root);
|
|
if(saved != nil){
|
|
setenv("XDG_RUNTIME_DIR", saved, 1);
|
|
free(saved);
|
|
}else
|
|
unsetenv("XDG_RUNTIME_DIR");
|
|
}
|