#define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include #include #include #include #include "test.h" #undef waitpid enum { Childtimeout = 2000, }; typedef struct Childres Childres; struct Childres { int rc; unsigned int want; unsigned int mod; unsigned int key; }; static long long nowms(void) { struct timespec ts; if(clock_gettime(CLOCK_MONOTONIC, &ts) < 0) return -1; return (long long)ts.tv_sec * 1000 + ts.tv_nsec / 1000000; } static int waitreadable(int fd, int timeout) { struct pollfd pfd; long long end, now; int n, left; end = nowms() + timeout; pfd.fd = fd; pfd.events = POLLIN; for(;;){ now = nowms(); if(now < 0 || now >= end) return -1; left = end - now; n = poll(&pfd, 1, left); if(n > 0) return (pfd.revents & (POLLIN|POLLHUP)) != 0 ? 0 : -1; if(n == 0) return -1; if(errno != EINTR) return -1; } } static int readdeadline(int fd, void *buf, size_t n, int timeout) { unsigned char *p; long long end, now; ssize_t got; p = buf; end = nowms() + timeout; while(n > 0){ now = nowms(); if(now < 0 || now >= end || waitreadable(fd, end - now) < 0) return -1; got = read(fd, p, n); if(got < 0 && errno == EINTR) continue; if(got <= 0) return -1; p += got; n -= got; } return 0; } static int waitqueueempty(int fd, int timeout) { long long end; int n; end = nowms() + timeout; for(;;){ if(ioctl(fd, FIONREAD, &n) < 0) return -1; if(n == 0) return 0; if(nowms() >= end) return -1; sched_yield(); } } static int reapchild(pid_t pid, int *status, int timeout) { long long end; pid_t got; end = nowms() + timeout; for(;;){ got = waitpid(pid, status, WNOHANG); if(got == pid) return 0; if(got < 0 && errno != EINTR) return -1; if(nowms() >= end) break; poll(nil, 0, 1); } if(kill(pid, SIGKILL) < 0 && errno != ESRCH) return -1; end = nowms() + timeout; for(;;){ got = waitpid(pid, status, WNOHANG); if(got == pid) return 0; if(got < 0 && errno != EINTR) return -1; if(nowms() >= end) return -1; poll(nil, 0, 1); } } void ipc_request_codec(struct ct *t) { static const struct { int want; unsigned int mod, key; unsigned char bytes[Ipcreqsz]; } cases[] = { { 0, 0, 0, { 0, 0, 0, 0, 0, 0 } }, { 1, Mctrl|Mshift, 0x1234, { 1, Mctrl|Mshift, 0x34, 0x12, 0, 0 } }, { 1, Malt, 0x1f600, { 1, Malt, 0x00, 0xf6, 0x01, 0x00 } }, { 1, 0, 0xf008, { 1, 0, 0x08, 0xf0, 0x00, 0x00 } }, { 1, 0, Kback, { 1, 0, 0x08, 0x00, 0x11, 0x00 } }, { 1, Msuper, 0x89abcdefU, { 1, Msuper, 0xef, 0xcd, 0xab, 0x89 } }, }; unsigned char got[Ipcreqsz]; unsigned int mod, key; int i, want; for(i = 0; i < nelem(cases); i++){ ipcpackreq(got, cases[i].want, cases[i].mod, cases[i].key); if(!CT_EQ_MEM(t, cases[i].bytes, got, sizeof got)) continue; ipcunpackreq(got, &want, &mod, &key); CT_EQ_INT(t, cases[i].want != 0, want); CT_EQ_UINT(t, cases[i].mod, mod); CT_EQ_UINT(t, cases[i].key, key); } } void ipc_response_pack_boundaries(struct ct *t) { static const unsigned char want[] = { 1, 1, 0, 'A', 2, 0, 'x', 'y' }; static const unsigned char want0[] = { 1, 1, 0, 'A' }; unsigned char out[Ipcmaxresp+1], field[Ipcfieldmax]; int n; n = ipcpackresp(out, sizeof out, 7, "A", 1, "xy", 2, 1); CT_EQ_INT(t, sizeof want, n); CT_EQ_MEM(t, want, out, sizeof want); n = ipcpackresp(out, sizeof out, 1, "A", 1, "xy", 2, 0); CT_EQ_INT(t, sizeof want0, n); CT_EQ_MEM(t, want0, out, sizeof want0); 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, 'x', out[Ipcmaxresp-1]); memset(out, 0xa5, sizeof out); CT_EQ_INT(t, -1, ipcpackresp(out, Ipcmaxresp-1, 1, (char*)field, sizeof field, (char*)field, sizeof field, 1)); CT_EQ_INT(t, 0xa5, out[0]); CT_EQ_INT(t, -1, ipcpackresp(out, sizeof out, 0, nil, 1, nil, 0, 0)); CT_EQ_INT(t, -1, ipcpackresp(out, sizeof out, 0, nil, 0, nil, 1, 1)); CT_EQ_INT(t, -1, ipcpackresp(out, sizeof out, 0, (char*)field, Ipcfieldmax+1, nil, 0, 0)); } void ipc_response_codec_and_drain(struct ct *t) { unsigned char first[Ipcmaxresp]; static const unsigned char second[] = { 0, 2, 0, 'o', 'k' }; unsigned char field[Ipcfieldmax]; char commit[8], preedit[8]; Ipcresp resp; int fd[2], n; memset(field, 'x', sizeof field); n = ipcpackresp(first, sizeof first, 1, (char*)field, sizeof field, (char*)field, sizeof field, 1); if(!CT_EQ_INT(t, Ipcmaxresp, n)) 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, n))) goto cleanup; if(!CT_EQ_INT(t, 0, ipcsend(fd[0], second, sizeof second))) goto cleanup; if(!CT_EQ_INT(t, 0, shutdown(fd[0], SHUT_WR))) goto cleanup; if(!CT_EQ_INT(t, 0, ipcreadresp(fd[1], 1, commit, sizeof commit, preedit, sizeof preedit, &resp))) goto cleanup; CT_EQ_INT(t, 1, resp.eaten); CT_EQ_SIZE(t, Ipcfieldmax, resp.ncommit); CT_EQ_SIZE(t, Ipcfieldmax, resp.npreedit); CT_EQ_STR(t, "xxxxxxx", commit); CT_EQ_STR(t, "xxxxxxx", preedit); if(!CT_EQ_INT(t, 0, ipcreadresp(fd[1], 0, commit, sizeof commit, preedit, sizeof preedit, &resp))) goto cleanup; CT_EQ_INT(t, 0, resp.eaten); CT_EQ_STR(t, "ok", commit); CT_EQ_STR(t, "", preedit); cleanup: close(fd[0]); close(fd[1]); } void ipc_truncated_frame(struct ct *t) { unsigned char b = 1, req[Ipcreqsz]; int fd[2]; if(pipe(fd) < 0){ CT_ERRORF(t, "pipe failed"); return; } if(!CT_EQ_INT(t, 1, write(fd[1], &b, 1))){ close(fd[0]); close(fd[1]); return; } close(fd[1]); CT_EQ_INT(t, -1, ipcreadn(fd[0], req, sizeof req)); close(fd[0]); } void ipc_fragmented_request(struct ct *t) { unsigned char req[Ipcreqsz]; Childres res; Keyreq got; pid_t pid; int data[2], result[2], status; pid = -1; data[0] = data[1] = result[0] = result[1] = -1; ipcpackreq(req, 1, Mctrl|Mshift, 0x1f600); if(socketpair(AF_UNIX, SOCK_STREAM, 0, data) < 0){ CT_ERRORF(t, "data socketpair failed"); return; } if(socketpair(AF_UNIX, SOCK_STREAM, 0, result) < 0){ CT_ERRORF(t, "result socketpair failed"); goto cleanup; } pid = fork(); if(pid < 0){ CT_ERRORF(t, "fork failed"); goto cleanup; } if(pid == 0){ close(data[0]); close(result[0]); memset(&res, 0, sizeof res); res.rc = srvreadkey(data[1], &got); if(res.rc == 0){ res.want = got.want; res.mod = got.mod; res.key = got.ks; } _exit(ipcsend(result[1], &res, sizeof res) < 0 ? 1 : 0); } close(result[1]); result[1] = -1; if(ipcsend(data[0], req, Ipcreqsz/2) < 0){ CT_ERRORF(t, "first request fragment failed"); goto cleanup; } if(waitqueueempty(data[1], Childtimeout) < 0){ CT_ERRORF(t, "child did not consume first request fragment"); goto cleanup; } if(ipcsend(data[0], req+Ipcreqsz/2, Ipcreqsz-Ipcreqsz/2) < 0){ CT_ERRORF(t, "second request fragment failed"); goto cleanup; } if(readdeadline(result[0], &res, sizeof res, Childtimeout) < 0){ CT_ERRORF(t, "timed out waiting for decoded request"); goto cleanup; } CT_EQ_INT(t, 0, res.rc); CT_EQ_INT(t, 1, res.want); CT_EQ_UINT(t, Mctrl|Mshift, res.mod); CT_EQ_UINT(t, 0x1f600, res.key); cleanup: if(data[0] >= 0) close(data[0]); if(data[1] >= 0) close(data[1]); if(result[0] >= 0) close(result[0]); if(result[1] >= 0) close(result[1]); if(pid > 0){ if(reapchild(pid, &status, Childtimeout) < 0) CT_ERRORF(t, "could not reap request reader"); else if(!WIFEXITED(status) || WEXITSTATUS(status) != 0) CT_ERRORF(t, "request reader failed"); } } void ipc_closed_peer(struct ct *t) { unsigned char b; pid_t pid; int data[2], syncfd[2], status; pid = -1; data[0] = data[1] = syncfd[0] = syncfd[1] = -1; if(socketpair(AF_UNIX, SOCK_STREAM, 0, data) < 0){ CT_ERRORF(t, "data socketpair failed"); return; } if(socketpair(AF_UNIX, SOCK_STREAM, 0, syncfd) < 0){ CT_ERRORF(t, "synchronization socketpair failed"); goto cleanup; } pid = fork(); if(pid < 0){ CT_ERRORF(t, "fork failed"); goto cleanup; } if(pid == 0){ signal(SIGPIPE, SIG_DFL); close(data[1]); close(syncfd[0]); if(ipcsend(syncfd[1], "r", 1) < 0 || readdeadline(syncfd[1], &b, 1, Childtimeout) < 0) _exit(2); _exit(ipcsend(data[0], "x", 1) < 0 ? 0 : 1); } close(data[0]); data[0] = -1; close(data[1]); data[1] = -1; close(syncfd[1]); syncfd[1] = -1; if(readdeadline(syncfd[0], &b, 1, Childtimeout) < 0 || ipcsend(syncfd[0], "g", 1) < 0) CT_ERRORF(t, "child synchronization failed"); cleanup: if(data[0] >= 0) close(data[0]); if(data[1] >= 0) close(data[1]); if(syncfd[0] >= 0) close(syncfd[0]); if(syncfd[1] >= 0) close(syncfd[1]); if(pid > 0){ if(reapchild(pid, &status, Childtimeout) < 0) CT_ERRORF(t, "could not reap closed-peer writer"); else if(!WIFEXITED(status) || WEXITSTATUS(status) != 0) CT_ERRORF(t, "write to closed peer did not return -1"); } }