X-Git-Url: https://git.distorted.org.uk/~mdw/tripe-android/blobdiff_plain/68df6e8f5b575d7b737733339339b3b05ecc72a3..04a5abaece151705e9bd7026653f79938a7a2fbc:/jni.c diff --git a/jni.c b/jni.c index f65e464..9aa79f8 100644 --- a/jni.c +++ b/jni.c @@ -39,6 +39,7 @@ #include #include +#include #include #include #include @@ -192,6 +193,31 @@ static void except_syserror(JNIEnv *jni, const char *clsname, va_end(ap); } +static int set_nonblocking(JNIEnv *jni, int fd, int nb) +{ + int f0 = fcntl(fd, F_GETFL), f1; + if (f0 < 0) goto err; + if (nb) f1 = f0 | O_NONBLOCK; + else f1 = f0&~O_NONBLOCK; + if (fcntl(fd, F_SETFL, f1)) goto err; + return (f0 & O_NONBLOCK); +err: + except_syserror(jni, SYSERR, errno, + "failed to set descriptor nonblocking"); + return (-1); +} + +static int set_closeonexec(JNIEnv *jni, int fd) +{ + int f = fcntl(fd, F_GETFD); + if (f < 0 || fcntl(fd, F_SETFD, f | FD_CLOEXEC)) { + except_syserror(jni, SYSERR, errno, + "failed to set descriptor close-on-exec"); + return (-1); + } + return (0); +} + /*----- Wrapping native types ---------------------------------------------*/ /* There's no way defined in the JNI to stash a C pointer in a Java object. @@ -808,7 +834,7 @@ JNIEXPORT jboolean JNIFUNC(isatty)(JNIEnv *jni, jobject cls, jobject jfd) /* Java has these already, as methods on `java.io.File' objects. Alas, these * methods are useless at reporting errors: they tend to return a `boolean' - * success/ fail indicator, and throw away any more detailed information. + * success/fail indicator, and throw away any more detailed information. * There's better functionality in `java.nio.file.Files', but that only turns * up in Android API 26 (in 7.0 Nougat). There's `android.system.Os', which * has a bunch of POSIX-shaped functions -- but they're only in Android API @@ -1102,6 +1128,96 @@ end: put_cstring(jni, path, pathstr); } +/*----- Triggers ----------------------------------------------------------*/ + +/* A trigger is a gadget for waking up a thread which is blocking on I/O, + * and it's used to implement interruptability. + * + * Really, a trigger is a pipe. A `blocking' I/O operation secretly uses + * select(2) to block on the descriptor of interest /and/ the read side of + * the trigger pipe. To wake up a thread that's blocked, we just write a + * byte (nobody cares /which/ byte) to the write end. + */ + +struct trigger { + struct native_base _base; + int rfd, wfd; +}; +static const struct native_type trigger_type = + { "trigger", sizeof(struct trigger), 0x65ffd8b4 }; + +JNIEXPORT wrapper JNICALL JNIFUNC(makeTrigger)(JNIEnv *jni, jobject cls) +{ + struct trigger trig; + int fd[2]; + int i; + wrapper ret = 0; + + fd[0] = fd[1] = -1; + if (pipe(fd)) { + except_syserror(jni, SYSERR, errno, "failed to create pipe"); + goto end; + } + for (i = 0; i < 2; i++) { + if (set_nonblocking(jni, fd[i], 1) < 0 || set_closeonexec(jni, fd[i])) + goto end; + } + + INIT_NATIVE(trigger, &trig); + trig.rfd = fd[0]; fd[0] = -1; + trig.wfd = fd[1]; fd[1] = -1; + ret = wrap(jni, &trigger_type, &trig); + +end: + for (i = 0; i < 2; i++) + if (fd[i] != -1) close(fd[i]); + return (ret); +} + +JNIEXPORT void JNICALL JNIFUNC(destroyTrigger)(JNIEnv *jni, jobject cls, + wrapper wtrig) +{ + struct trigger trig; + + if (unwrap(jni, &trig, &trigger_type, wtrig)) return; + if (trig.rfd != -1) { close(trig.rfd); trig.rfd = -1; } + if (trig.wfd != -1) { close(trig.wfd); trig.wfd = -1; } + update_wrapper(jni, &trigger_type, wtrig, &trig); +} + +JNIEXPORT void JNICALL JNIFUNC(resetTrigger)(JNIEnv *jni, jobject cls, + wrapper wtrig) +{ + struct trigger trig; + char buf[64]; + ssize_t n; + + if (unwrap(jni, &trig, &trigger_type, wtrig)) return; + for (;;) { + n = read(trig.rfd, buf, sizeof(buf)); + if (n > 0) continue; + assert(n < 0); + if (errno == EAGAIN || errno == EWOULDBLOCK) break; + else { + except_syserror(jni, SYSERR, errno, "failed to reset trigger"); + break; + } + } +} + +JNIEXPORT void JNICALL JNIFUNC(trigger)(JNIEnv *jni, jobject cls, + wrapper wtrig) +{ + struct trigger trig; + ssize_t n; + char c = 0; + + if (unwrap(jni, &trig, &trigger_type, wtrig)) return; + n = write(trig.wfd, &c, 1); + if (n < 0 && errno != EAGAIN && errno != EWOULDBLOCK) + except_syserror(jni, SYSERR, errno, "failed to pull trigger"); +} + /*----- A server connection, using a Unix-domain socket -------------------*/ struct conn { @@ -1116,14 +1232,21 @@ static const struct native_type conn_type = { "conn", sizeof(struct conn), 0xed030167 }; JNIEXPORT wrapper JNICALL JNIFUNC(connect)(JNIEnv *jni, jobject cls, - jobject path) + jobject path, wrapper wtrig) { struct conn conn; + struct trigger trig; struct sockaddr_un sun; + int rc, maxfd; + fd_set rfds, wfds; const char *pathstr = 0; - jobject ret = 0; + int err; + socklen_t sz; + wrapper ret = 0; + int nb; int fd = -1; + if (unwrap(jni, &trig, &trigger_type, wtrig)) goto end; pathstr = get_cstring(jni, path); if (!pathstr) goto end; if (strlen(pathstr) >= sizeof(sun.sun_path)) { except(jni, ARGERR, @@ -1132,12 +1255,33 @@ JNIEXPORT wrapper JNICALL JNIFUNC(connect)(JNIEnv *jni, jobject cls, } INIT_NATIVE(conn, &conn); - fd = socket(SOCK_STREAM, PF_UNIX, 0); if (fd < 0) goto err; + fd = socket(PF_UNIX, SOCK_STREAM, 0); if (fd < 0) goto err; + nb = set_nonblocking(jni, fd, 1); if (nb < 0) goto end; sun.sun_family = AF_UNIX; strcpy(sun.sun_path, (char *)pathstr); - if (connect(fd, (struct sockaddr *)&sun, sizeof(sun))) goto err; + if (!connect(fd, (struct sockaddr *)&sun, sizeof(sun))) goto connected; + else if (errno != EINPROGRESS) goto err; + + maxfd = trig.rfd; + if (maxfd < fd) maxfd = fd; + for (;;) { + FD_ZERO(&rfds); FD_SET(trig.rfd, &rfds); + FD_ZERO(&wfds); FD_SET(fd, &wfds); + rc = select(maxfd + 1, &rfds, &wfds, 0, 0); if (rc < 0) goto err; + if (FD_ISSET(trig.rfd, &rfds)) goto end; + if (FD_ISSET(fd, &wfds)) { + sz = sizeof(sun); + if (!getpeername(fd, (struct sockaddr *)&sun, &sz)) goto connected; + else if (errno != ENOTCONN) goto err; + sz = sizeof(err); + if (!getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &sz)) errno = err; + goto err; + } + } +connected: + if (set_nonblocking(jni, fd, nb) < 0) goto end; conn.fd = fd; fd = -1; conn.f = 0; ret = wrap(jni, &conn_type, &conn); @@ -1147,7 +1291,7 @@ err: except_syserror(jni, SYSERR, errno, "failed to connect to Unix-domain socket `%s'", pathstr); end: - if (fd == -1) close(fd); + if (fd != -1) close(fd); put_cstring(jni, path, pathstr); return (ret); } @@ -1181,28 +1325,40 @@ static int check_buffer_bounds(JNIEnv *jni, const char *what, JNIEXPORT void JNICALL JNIFUNC(send)(JNIEnv *jni, jobject cls, wrapper wconn, jbyteArray buf, - jint start, jint len) + jint start, jint len, + wrapper wtrig) { struct conn conn; + struct trigger trig; + int rc, maxfd; ssize_t n; + fd_set rfds, wfds; jbyte *p = 0; if (unwrap(jni, &conn, &conn_type, wconn)) goto end; + if (unwrap(jni, &trig, &trigger_type, wtrig)) goto end; if (check_buffer_bounds(jni, "send", buf, start, len)) goto end; p = (*jni)->GetByteArrayElements(jni, buf, 0); if (!p) goto end; + maxfd = trig.rfd; + if (maxfd < conn.fd) maxfd = conn.fd; while (len) { - n = send(conn.fd, p + start, len, 0); - if (n < 0) { - except_syserror(jni, SYSERR, - errno, "failed to send on connection"); - goto end; + FD_ZERO(&rfds); FD_SET(trig.rfd, &rfds); + FD_ZERO(&wfds); FD_SET(conn.fd, &wfds); + rc = select(maxfd + 1, &rfds, &wfds, 0, 0); if (rc < 0) goto err; + if (FD_ISSET(trig.rfd, &rfds)) break; + if (FD_ISSET(conn.fd, &wfds)) { + n = send(conn.fd, p + start, len, 0); + if (n >= 0) { start += n; len -= n; } + else if (errno != EAGAIN && errno != EWOULDBLOCK) goto err; } - start += n; len -= n; } + goto end; +err: + except_syserror(jni, SYSERR, errno, "failed to send on connection"); end: if (p) (*jni)->ReleaseByteArrayElements(jni, buf, p, JNI_ABORT); return; @@ -1210,26 +1366,42 @@ end: JNIEXPORT jint JNICALL JNIFUNC(recv)(JNIEnv *jni, jobject cls, wrapper wconn, jbyteArray buf, - jint start, jint len) + jint start, jint len, + wrapper wtrig) { struct conn conn; + struct trigger trig; + int maxfd; + fd_set rfds; jbyte *p = 0; jint rc = -1; if (unwrap(jni, &conn, &conn_type, wconn)) goto end; + if (unwrap(jni, &trig, &trigger_type, wtrig)) goto end; if (check_buffer_bounds(jni, "send", buf, start, len)) goto end; p = (*jni)->GetByteArrayElements(jni, buf, 0); if (!p) goto end; - rc = recv(conn.fd, p + start, len, 0); - if (rc < 0) { - except_syserror(jni, SYSERR, - errno, "failed to read from connection"); - goto end; + maxfd = trig.rfd; + if (maxfd < conn.fd) maxfd = conn.fd; + for (;;) { + FD_ZERO(&rfds); FD_SET(trig.rfd, &rfds); FD_SET(conn.fd, &rfds); + rc = select(maxfd + 1, &rfds, 0, 0, 0); if (rc < 0) goto err; + if (FD_ISSET(trig.rfd, &rfds)) { + break; + } + if (FD_ISSET(conn.fd, &rfds)) { + rc = recv(conn.fd, p + start, len, 0); + if (rc >= 0) break; + else if (errno != EAGAIN && errno != EWOULDBLOCK) goto err; + } } if (!rc) rc = -1; + goto end; +err: + except_syserror(jni, SYSERR, errno, "failed to read from connection"); end: if (p) (*jni)->ReleaseByteArrayElements(jni, buf, p, 0); return (rc);