#define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include #include #include #include #include #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 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(waitfd(fd, POLLIN, until) < 0) return -1; r = recv(fd, p, n, MSG_DONTWAIT); if(r < 0 && errno == EINTR) continue; if(r < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) 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 && errno != EAGAIN && errno != EWOULDBLOCK) 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(waitfd(fd, POLLOUT, 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)) 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); }