Files
strans/ipc.c

493 lines
8.8 KiB
C

#define _POSIX_C_SOURCE 200809L
#include <errno.h>
#include <fcntl.h>
#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/un.h>
#include "ipc.h"
static int64_t
nowms(void)
{
struct timespec ts;
if(clock_gettime(CLOCK_MONOTONIC, &ts) < 0)
return -1;
return (int64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
}
static int64_t
deadline(void)
{
int64_t now;
now = nowms();
if(now < 0)
return -1;
return now + Ipcwaitms;
}
static int
checkdeadline(int64_t until)
{
int64_t now;
now = nowms();
if(now < 0)
return -1;
if(now >= until){
errno = ETIMEDOUT;
return -1;
}
return 0;
}
static int
waitfd(int fd, short events, int64_t until)
{
struct pollfd pfd;
int64_t now, left;
int n;
pfd.fd = fd;
pfd.events = events;
for(;;){
now = nowms();
if(now < 0)
return -1;
left = until - now;
if(left <= 0){
errno = ETIMEDOUT;
return -1;
}
pfd.revents = 0;
n = poll(&pfd, 1, left > INT32_MAX ? INT32_MAX : (int)left);
if(n < 0 && errno == EINTR)
continue;
if(n < 0)
return -1;
if(n == 0){
errno = ETIMEDOUT;
return -1;
}
if(pfd.revents & POLLNVAL){
errno = EBADF;
return -1;
}
if(pfd.revents & (events|POLLERR|POLLHUP))
return 0;
}
}
static int
readwait(int fd, void *buf, size_t n, int64_t until)
{
unsigned char *p;
ssize_t r;
p = buf;
while(n > 0){
if(checkdeadline(until) < 0)
return -1;
r = recv(fd, p, n, MSG_DONTWAIT);
if(r < 0 && errno == EINTR)
continue;
if(r < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)){
if(waitfd(fd, POLLIN, until) < 0)
return -1;
continue;
}
if(r < 0)
return -1;
if(r == 0){
errno = ECONNRESET;
return -1;
}
p += r;
n -= r;
}
return 0;
}
static void
putlen(unsigned char p[Ipclensz], size_t n)
{
p[0] = n;
p[1] = n >> 8;
}
static size_t
getlen(const unsigned char p[Ipclensz])
{
return p[0] | (p[1] << 8);
}
static void
put32(unsigned char *p, int32_t v)
{
uint32_t u;
u = v;
p[0] = u;
p[1] = u >> 8;
p[2] = u >> 16;
p[3] = u >> 24;
}
static int32_t
get32(const unsigned char *p)
{
uint32_t u;
u = (uint32_t)p[0] |
((uint32_t)p[1] << 8) |
((uint32_t)p[2] << 16) |
((uint32_t)p[3] << 24);
if(u <= INT32_MAX)
return u;
return -(int32_t)(~u) - 1;
}
int
ipcpath(char *dst, size_t cap)
{
const char *dir, *sep;
int n;
if(dst == NULL || cap == 0)
return -1;
dir = getenv("XDG_RUNTIME_DIR");
if(dir != NULL && dir[0] == '/'){
sep = dir[strlen(dir)-1] == '/' ? "" : "/";
n = snprintf(dst, cap, "%s%sstrans.sock", dir, sep);
}else
n = snprintf(dst, cap, "/tmp/strans.%lu",
(unsigned long)getuid());
if(n < 0 || (size_t)n >= cap)
return -1;
return 0;
}
int
ipcconnect(void)
{
struct sockaddr_un addr;
socklen_t nerr;
int e, err, fd, fdflags, flags;
int64_t until;
memset(&addr, 0, sizeof addr);
addr.sun_family = AF_UNIX;
if(ipcpath(addr.sun_path, sizeof addr.sun_path) < 0){
errno = ENAMETOOLONG;
return -1;
}
fd = socket(AF_UNIX, SOCK_STREAM, 0);
if(fd < 0)
return -1;
fdflags = fcntl(fd, F_GETFD);
if(fdflags < 0 || fcntl(fd, F_SETFD, fdflags|FD_CLOEXEC) < 0)
goto Bad;
flags = fcntl(fd, F_GETFL);
if(flags < 0 || fcntl(fd, F_SETFL, flags|O_NONBLOCK) < 0)
goto Bad;
until = deadline();
if(until < 0)
goto Bad;
if(connect(fd, (struct sockaddr*)&addr, sizeof addr) < 0){
if(errno != EINPROGRESS && errno != EALREADY && errno != EINTR)
goto Bad;
if(waitfd(fd, POLLOUT, until) < 0)
goto Bad;
nerr = sizeof err;
if(getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &nerr) < 0)
goto Bad;
if(err != 0){
errno = err;
goto Bad;
}
}
if(fcntl(fd, F_SETFL, flags) < 0)
goto Bad;
return fd;
Bad:
e = errno;
close(fd);
errno = e;
return -1;
}
void
ipcpackreq(unsigned char req[Ipcreqsz], int want, uint32_t mod,
uint32_t key)
{
req[0] = want ? Ipcreqwant : 0;
req[1] = mod;
req[2] = key;
req[3] = key >> 8;
req[4] = key >> 16;
req[5] = key >> 24;
}
void
ipcpackreset(unsigned char req[Ipcreqsz], int want)
{
memset(req, 0, Ipcreqsz);
req[0] = Ipcreqreset | (want ? Ipcreqwant : 0);
}
/* An old server sees this reserved frame as a key-zero request. */
void
ipcpackcap(unsigned char req[Ipcreqsz], int want)
{
memset(req, 0, Ipcreqsz);
req[0] = Ipcext | (want ? Ipcreqwant : 0);
req[1] = Ipcversion;
req[2] = Ipcopcap;
}
int
ipcpackcaret(unsigned char req[Ipccaretsz], int valid, int32_t x,
int32_t y, int32_t h)
{
if(valid != 0 && valid != 1)
return -1;
if(valid && h < 0)
return -1;
memset(req, 0, Ipccaretsz);
req[0] = Ipcext;
req[1] = Ipcversion;
req[2] = Ipcopcaret;
req[3] = valid;
if(!valid)
return 0;
put32(req + 4, x);
put32(req + 8, y);
put32(req + 12, h);
return 0;
}
void
ipcunpackreq(const unsigned char req[Ipcreqsz], int *want, uint32_t *mod,
uint32_t *key)
{
*want = (req[0] & Ipcreqwant) != 0;
*mod = req[1] & Mmask;
*key = (uint32_t)req[2] |
((uint32_t)req[3] << 8) |
((uint32_t)req[4] << 16) |
((uint32_t)req[5] << 24);
}
int
ipcreqtype(const unsigned char req[Ipcreqsz])
{
if((req[0] & Ipcext) == 0)
return Ipclegacy;
if(req[1] != Ipcversion)
return Ipcunknown;
switch(req[2]){
case Ipcopcap:
if((req[0] & ~(Ipcext|Ipcreqwant)) != 0 ||
req[3] != 0 || req[4] != 0 || req[5] != 0)
return Ipcunknown;
return Ipccap;
case Ipcopcaret:
if(req[0] != Ipcext || req[3] > 1)
return Ipcunknown;
return Ipccaret;
}
return Ipcunknown;
}
int
ipcunpackcaret(const unsigned char req[Ipccaretsz], int *valid,
int32_t *x, int32_t *y, int32_t *h)
{
int32_t wireh;
if(valid == NULL || x == NULL || y == NULL || h == NULL ||
ipcreqtype(req) != Ipccaret)
return -1;
wireh = get32(req + 12);
if(wireh < 0)
return -1;
*valid = req[3];
if(!*valid){
*x = 0;
*y = 0;
*h = 0;
return 0;
}
*x = get32(req + 4);
*y = get32(req + 8);
*h = wireh;
return 0;
}
int
ipcreqreset(const unsigned char req[Ipcreqsz])
{
return (req[0] & Ipcreqreset) != 0;
}
int
ipcpackresp(unsigned char *dst, size_t cap, int eaten,
const char *commit, size_t ncommit, const char *preedit, size_t npreedit,
int want)
{
size_t n;
if(ncommit > Ipcfieldmax || npreedit > Ipcfieldmax)
return -1;
if((ncommit > 0 && commit == NULL) ||
(want && npreedit > 0 && preedit == NULL))
return -1;
n = Ipcresphdrsz + ncommit + (want ? Ipclensz + npreedit : 0);
if(dst == NULL || cap < n)
return -1;
dst[0] = eaten != 0;
putlen(dst + 1, ncommit);
if(ncommit > 0)
memcpy(dst + Ipcresphdrsz, commit, ncommit);
if(want){
putlen(dst + Ipcresphdrsz + ncommit, npreedit);
if(npreedit > 0)
memcpy(dst + Ipcresphdrsz + ncommit + Ipclensz,
preedit, npreedit);
}
return n;
}
int
ipcreadn(int fd, void *buf, size_t n)
{
unsigned char *p;
ssize_t r;
p = buf;
while(n > 0){
r = read(fd, p, n);
if(r < 0 && errno == EINTR)
continue;
if(r <= 0)
return -1;
p += r;
n -= r;
}
return 0;
}
int
ipcsend(int fd, const void *buf, size_t n)
{
const unsigned char *p;
ssize_t r;
int64_t until;
p = buf;
if(n == 0)
return 0;
until = deadline();
if(until < 0)
return -1;
while(n > 0){
if(checkdeadline(until) < 0)
return -1;
r = send(fd, p, n, MSG_NOSIGNAL|MSG_DONTWAIT);
if(r < 0 && errno == EINTR)
continue;
if(r < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)){
if(waitfd(fd, POLLOUT, until) < 0)
return -1;
continue;
}
if(r < 0)
return -1;
if(r == 0){
errno = EPIPE;
return -1;
}
p += r;
n -= r;
}
return 0;
}
static int
readfield(int fd, size_t n, char *dst, size_t cap, size_t *copied,
int64_t until)
{
unsigned char discard[128];
size_t keep, part;
if(cap > 0 && dst == NULL)
return -1;
keep = 0;
if(cap > 0)
keep = n >= cap ? cap - 1 : n;
if(keep > 0 && readwait(fd, dst, keep, until) < 0)
return -1;
if(cap > 0)
dst[keep] = '\0';
n -= keep;
while(n > 0){
part = n < sizeof discard ? n : sizeof discard;
if(readwait(fd, discard, part, until) < 0)
return -1;
n -= part;
}
*copied = keep;
return 0;
}
int
ipcreadresp(int fd, int want, char *commit, size_t ccap,
char *preedit, size_t pcap, Ipcresp *resp)
{
unsigned char hdr[Ipcresphdrsz], npreedit[Ipclensz];
size_t wirelen;
int64_t until;
if(resp == NULL || (ccap > 0 && commit == NULL) ||
(pcap > 0 && preedit == NULL))
return -1;
if(ccap > 0)
commit[0] = '\0';
if(pcap > 0)
preedit[0] = '\0';
memset(resp, 0, sizeof *resp);
until = deadline();
if(until < 0 || readwait(fd, hdr, sizeof hdr, until) < 0)
return -1;
if(hdr[0] > 1){
errno = EPROTO;
return -1;
}
resp->eaten = hdr[0];
wirelen = getlen(hdr + 1);
if(wirelen > Ipcfieldmax){
errno = EPROTO;
return -1;
}
if(readfield(fd, wirelen, commit, ccap, &resp->commitlen, until) < 0)
return -1;
if(!want)
return 0;
if(readwait(fd, npreedit, sizeof npreedit, until) < 0)
return -1;
wirelen = getlen(npreedit);
if(wirelen > Ipcfieldmax){
errno = EPROTO;
return -1;
}
return readfield(fd, wirelen, preedit, pcap, &resp->preeditlen,
until);
}