#include <jni.h>
#include <sys/types.h>
+#include <sys/select.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/sysmacros.h>
/*----- Magic class names and similar -------------------------------------*/
/* The name decoration is horrific. Hide it. */
-#define JNIFUNC(f) Java_uk_org_distorted_tripe_jni_package_00024_##f
+#define JNIFUNC(f) Java_uk_org_distorted_tripe_sys_package_00024_##f
/* The little class for bundling up error codes. */
-#define ERRENTRY "uk/org/distorted/tripe/jni/package$ErrorEntry"
+#define ERRENTRY "uk/org/distorted/tripe/sys/package$ErrorEntry"
/* The `stat' class. */
-#define STAT "uk/org/distorted/tripe/jni/package$FileInfo"
+#define STAT "uk/org/distorted/tripe/sys/package$FileInfo"
/* Exception class names. */
#define NULLERR "java/lang/NullPointerException"
-#define TYPEERR "uk/org/distorted/tripe/jni/package$NativeObjectTypeException"
+#define TYPEERR "uk/org/distorted/tripe/sys/package$NativeObjectTypeException"
#define SYSERR "uk/org/distorted/tripe/sys/package$SystemError"
#define ARGERR "java/lang/IllegalArgumentException"
#define BOUNDSERR "java/lang/IndexOutOfBoundsException"
{ if (p) (*jni)->ReleaseByteArrayElements(jni, v, (jbyte *)p, JNI_ABORT); }
static void vexcept(JNIEnv *jni, const char *clsname,
- const char *msg, va_list ap)
+ const char *msg, va_list *ap)
{
jclass cls;
int rc;
if (!msg)
rc = (*jni)->ThrowNew(jni, cls, 0);
else {
- dstr_vputf(&d, msg, &ap);
+ dstr_vputf(&d, msg, ap);
rc = (*jni)->ThrowNew(jni, cls, d.buf);
assert(!rc);
dstr_destroy(&d);
va_list ap;
va_start(ap, msg);
- vexcept(jni, clsname, msg, ap);
+ vexcept(jni, clsname, msg, &ap);
va_end(ap);
}
}
static void vexcept_syserror(JNIEnv *jni, const char *clsname,
- int err, const char *msg, va_list ap)
+ int err, const char *msg, va_list *ap)
{
jclass cls;
int rc;
cls = (*jni)->FindClass(jni, clsname); assert(cls);
init = (*jni)->GetMethodID(jni, cls, "<init>", "(I[B)V"); assert(init);
- dstr_vputf(&d, msg, &ap);
+ dstr_vputf(&d, msg, ap);
msgstr = wrap_cstring(jni, d.buf); assert(msgstr);
dstr_destroy(&d);
e = (*jni)->NewObject(jni, cls, init, err, msgstr); assert(e);
va_list ap;
va_start(ap, msg);
- vexcept_syserror(jni, clsname, err, msg, ap);
+ vexcept_syserror(jni, clsname, err, msg, &ap);
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.
ECANCELED ENOKEY EKEYEXPIRED EKEYREVOKED EKEYREJECTED
EOWNERDEAD ENOTRECOVERABLE ERFKILL EHWPOISON)))
(save-excursion
- (goto-char (point-min))
- (search-forward (concat "***" "BEGIN errtab" "***"))
- (beginning-of-line 2)
- (delete-region (point)
- (progn
- (search-forward "***END***")
- (beginning-of-line)
- (point)))
- (dolist (err errors)
- (insert (format "#ifdef %s\n { \"%s\", %s },\n#endif\n"
- err err err)))))
+ (goto-char (point-min))
+ (search-forward (concat "***" "BEGIN errtab" "***"))
+ (beginning-of-line 2)
+ (delete-region (point)
+ (progn
+ (search-forward "***END***")
+ (beginning-of-line)
+ (point)))
+ (dolist (err errors)
+ (insert (format "#ifdef %s\n { \"%s\", %s },\n#endif\n"
+ err err err)))))
*/
/***BEGIN errtab***/
#ifdef EPERM
JNIEXPORT jobject JNIFUNC(strerror)(JNIEnv *jni, jobject cls, jint err)
{ return (wrap_cstring(jni, strerror(err))); }
+/*----- Messing with file descriptors -------------------------------------*/
+
+static void fdguts(JNIEnv *jni, jclass *cls, jfieldID *fid)
+{
+ *cls = (*jni)->FindClass(jni, "java/io/FileDescriptor"); assert(cls);
+ *fid = (*jni)->GetFieldID(jni, *cls, "fd", "I"); // OpenJDK
+ if (!*fid) *fid = (*jni)->GetFieldID(jni, *cls, "descriptor", "I"); // Android
+ assert(*fid);
+}
+
+static int fdint(JNIEnv *jni, jobject jfd)
+{
+ jclass cls;
+ jfieldID fid;
+
+ fdguts(jni, &cls, &fid);
+ return ((*jni)->GetIntField(jni, jfd, fid));
+}
+
+static jobject newfd(JNIEnv *jni, int fd)
+{
+ jobject jfd;
+ jclass cls;
+ jmethodID init;
+ jfieldID fid;
+
+ fdguts(jni, &cls, &fid);
+ init = (*jni)->GetMethodID(jni, cls, "<init>", "()V"); assert(init);
+ jfd = (*jni)->NewObject(jni, cls, init);
+ (*jni)->SetIntField(jni, jfd, fid, fd);
+ return (jfd);
+}
+
+JNIEXPORT jint JNIFUNC(fdint)(JNIEnv *jni, jobject cls, jobject jfd)
+ { return (fdint(jni, jfd)); }
+
+JNIEXPORT jobject JNIFUNC(newfd)(JNIEnv *jni, jobject cls, jint fd)
+ { return (newfd(jni, fd)); }
+
+JNIEXPORT jboolean JNIFUNC(isatty)(JNIEnv *jni, jobject cls, jobject jfd)
+ { return (isatty(fdint(jni, jfd))); }
+
/*----- Low-level file operations -----------------------------------------*/
/* 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
put_cstring(jni, to, tostr);
}
-#define LKF_EXCL 1u
-#define LKF_WAIT 2u
+#define LKF_EXCL 0x1000u
+#define LKF_WAIT 0x2000u
struct lockf {
struct native_base _base;
int fd;
pathstr = get_cstring(jni, path); if (!pathstr) goto end;
again:
- fd = open(pathstr, O_RDWR | O_CREAT); if (fd < 0) goto err;
+ fd = open(pathstr, O_RDWR | O_CREAT, flags&07777); if (fd < 0) goto err;
if (fstat(fd, &st0)) goto err;
f = fcntl(fd, F_GETFD); if (f < 0) goto err;
if (fcntl(fd, F_SETFD, f | FD_CLOEXEC)) goto err;
jclass cls;
jmethodID init;
jint modehack;
-
+
modehack = st->st_mode&07777;
if (S_ISFIFO(st->st_mode)) modehack |= 0010000;
else if (S_ISCHR(st->st_mode)) modehack |= 0020000;
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 {
{ "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,
}
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);
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);
}
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;
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);
}
-JNIEXPORT void JNICALL JNIFUNC(close)(JNIEnv *jni, jobject cls,
- wrapper wconn, jint how)
+JNIEXPORT void JNICALL JNIFUNC(closeconn)(JNIEnv *jni, jobject cls,
+ wrapper wconn, jint how)
{
struct conn conn;
int rc;