#define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include #include #include #include #include "ipc.h" static int64_t nowms(void) { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); return (int64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000; } static int64_t deadline(void) { return nowms() + Ipcwaitms; } static int waitfd(int fd, short events, int64_t until) { struct pollfd pfd; int64_t left; int n; pfd.fd = fd; pfd.events = events; for(;;){ left = until - nowms(); if(left <= 0){ errno = ETIMEDOUT; return -1; } n = poll(&pfd, 1, left); if(n < 0 && errno == EINTR) continue; if(n < 0) return -1; if(n == 0){ errno = ETIMEDOUT; return -1; } 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){ 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) { return (int32_t)((uint32_t)p[0] | ((uint32_t)p[1] << 8) | ((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24)); } int ipcpath(char *dst, size_t cap) { const char *dir, *sep; int n; 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; } /* * A nonblocking AF_UNIX connect completes at once or fails with EAGAIN * when the daemon's backlog is full; the socket stays nonblocking because * every transfer below polls with a deadline. */ int ipcconnect(void) { struct sockaddr_un addr; int e, fd; 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|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); if(fd < 0) return -1; if(connect(fd, (struct sockaddr*)&addr, sizeof addr) < 0){ e = errno; close(fd); errno = e; return -1; } return fd; } /* * Engine key for an X keysym and its Unicode value: printable characters * as themselves, the function keysyms 0xff00-0xffff as Kspec+offset. * The keypad and Shift+Tab (ISO_Left_Tab) variants fold onto Enter and Tab. */ uint32_t ipckeysym(uint32_t sym, uint32_t unicode) { if(unicode >= ' ' && unicode != 0x7f) return unicode; switch(sym){ case 0xfe20: case 0xff89: return Ktab; case 0xff8d: return Kret; } if(sym >= 0xff00 && sym <= 0xffff) return Kspec + (sym - 0xff00); return unicode; } uint32_t ipcmod(uint32_t state) { uint32_t m; m = state & Mmask; if(state & Msupervirt) m |= Msuper; return m; } 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; } void ipcpackcaret(unsigned char req[Ipccaretsz], int valid, int32_t x, int32_t y, int32_t h) { memset(req, 0, Ipccaretsz); req[0] = Ipcext; req[1] = Ipcversion; req[2] = Ipcopcaret; req[3] = valid != 0; if(valid){ put32(req + 4, x); put32(req + 8, y); put32(req + 12, h); } } 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 Ipckey; 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) { if(ipcreqtype(req) != Ipccaret || get32(req + 12) < 0) return -1; *valid = req[3]; *x = *valid ? get32(req + 4) : 0; *y = *valid ? get32(req + 8) : 0; *h = *valid ? get32(req + 12) : 0; 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; n = Ipcresphdrsz + ncommit + (want ? Ipclensz + npreedit : 0); if(cap < n) return -1; dst[0] = eaten != 0; putlen(dst + 1, ncommit); memcpy(dst + Ipcresphdrsz, commit, ncommit); if(want){ putlen(dst + Ipcresphdrsz + ncommit, npreedit); 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; until = deadline(); while(n > 0){ 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; } /* Reads a length-prefixed field into dst[Ipcfieldmax+1]. */ static int readfield(int fd, char *dst, int64_t until) { unsigned char len[Ipclensz]; size_t n; if(readwait(fd, len, sizeof len, until) < 0) return -1; n = getlen(len); if(n > Ipcfieldmax){ errno = EPROTO; return -1; } if(readwait(fd, dst, n, until) < 0) return -1; dst[n] = '\0'; return n; } int ipcreadresp(int fd, int want, char *commit, char *preedit, Ipcresp *resp) { unsigned char eaten; int64_t until; int n; commit[0] = '\0'; preedit[0] = '\0'; memset(resp, 0, sizeof *resp); until = deadline(); if(readwait(fd, &eaten, 1, until) < 0) return -1; if(eaten > 1){ errno = EPROTO; return -1; } resp->eaten = eaten; n = readfield(fd, commit, until); if(n < 0) return -1; resp->commitlen = n; if(!want) return 0; n = readfield(fd, preedit, until); if(n < 0) return -1; resp->preeditlen = n; return 0; }