chiark / gitweb /
rough work in progress; may not build
[tripe-android] / jni.c
diff --git a/jni.c b/jni.c
index 9f9e44d5b4f05ca5c958e0f8b56a5147a9603e94..f65e464c7a883d42141ea3b920bc62c8340494c6 100644 (file)
--- a/jni.c
+++ b/jni.c
@@ -1,7 +1,37 @@
+/* -*-c-*-
+ *
+ * Native-code portions of the project
+ *
+ * (c) 2018 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the Trivial IP Encryption (TrIPE) Android app.
+ *
+ * TrIPE is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 3 of the License, or (at your
+ * option) any later version.
+ *
+ * TrIPE is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with TrIPE.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#define _FILE_OFFSET_BITS 64
+
 #include <assert.h>
+#include <ctype.h>
 #include <errno.h>
 #include <inttypes.h>
-#include <stdint.h>
+#include <stdarg.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 
 #include <sys/types.h>
 #include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/sysmacros.h>
 #include <sys/un.h>
+#include <fcntl.h>
 #include <unistd.h>
+#include <dirent.h>
+
+#include <mLib/align.h>
+#include <mLib/bits.h>
+#include <mLib/dstr.h>
+#include <mLib/macros.h>
+
+#include <catacomb/ghash.h>
 
 #undef sun
 
-union align {
-  int i;
-  long l;
-  double d;
-  void *p;
-  void (*f)(void *);
-  struct notexist *s;
-};
+/*----- Magic class names and similar -------------------------------------*/
+
+/* The name decoration is horrific.  Hide it. */
+#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/sys/package$ErrorEntry"
+
+/* The `stat' class. */
+#define STAT "uk/org/distorted/tripe/sys/package$FileInfo"
+
+/* Exception class names. */
+#define NULLERR "java/lang/NullPointerException"
+#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"
+
+/*----- Miscellaneous utilities -------------------------------------------*/
+
+static void put_cstring(JNIEnv *jni, jbyteArray v, const char *p)
+  { if (p) (*jni)->ReleaseByteArrayElements(jni, v, (jbyte *)p, JNI_ABORT); }
+
+static void vexcept(JNIEnv *jni, const char *clsname,
+                   const char *msg, va_list *ap)
+{
+  jclass cls;
+  int rc;
+  dstr d = DSTR_INIT;
+
+  cls = (*jni)->FindClass(jni, clsname); assert(cls);
+  if (!msg)
+    rc = (*jni)->ThrowNew(jni, cls, 0);
+  else {
+    dstr_vputf(&d, msg, ap);
+    rc = (*jni)->ThrowNew(jni, cls, d.buf);
+    assert(!rc);
+    dstr_destroy(&d);
+  }
+  assert(!rc);
+}
+
+static void except(JNIEnv *jni, const char *clsname, const char *msg, ...)
+{
+  va_list ap;
+
+  va_start(ap, msg);
+  vexcept(jni, clsname, msg, &ap);
+  va_end(ap);
+}
+
+#ifdef DEBUG
+static void dump_bytes(const void *p, size_t n, size_t o)
+{
+  const unsigned char *q = p;
+  size_t i;
+
+  if (!n) return;
+  for (;;) {
+    fprintf(stderr, ";;   %08zx\n", o);
+    for (i = 0; i < 8; i++)
+      if (i < n) fprintf(stderr, "%02x ", q[i]);
+      else fprintf(stderr, "** ");
+    fprintf(stderr, ": ");
+    for (i = 0; i < 8; i++)
+      fputc(i >= n ? '*' : isprint(q[i]) ? q[i] : '.', stderr);
+    fputc('\n', stderr);
+    if (n <= 8) break;
+    q += 8; n -= 8;
+  }
+}
+
+static void dump_byte_array(JNIEnv *jni, const char *what, jbyteArray v)
+{
+  jsize n;
+  jbyte *p;
+
+  fprintf(stderr, ";; %s\n", what);
+  if (!v) { fprintf(stderr, ";;   <null>\n"); return; }
+  n = (*jni)->GetArrayLength(jni, v);
+  p = (*jni)->GetByteArrayElements(jni, v, 0);
+  dump_bytes(p, n, 0);
+  (*jni)->ReleaseByteArrayElements(jni, v, p, JNI_ABORT);
+}
+#endif
+
+static jbyteArray wrap_cstring(JNIEnv *jni, const char *p)
+{
+  size_t n;
+  jbyteArray v;
+  jbyte *q;
+
+  if (!p) return (0);
+  n = strlen(p) + 1;
+  v = (*jni)->NewByteArray(jni, n); if (!v) return (0);
+  q = (*jni)->GetByteArrayElements(jni, v, 0); if (!q) return (0);
+  memcpy(q, p, n);
+  (*jni)->ReleaseByteArrayElements(jni, v, q, 0);
+  return (v);
+}
+
+static const char *get_cstring(JNIEnv *jni, jbyteArray v)
+{
+  if (!v) { except(jni, NULLERR, 0); return (0); }
+  return ((const char *)(*jni)->GetByteArrayElements(jni, v, 0));
+}
+
+static void vexcept_syserror(JNIEnv *jni, const char *clsname,
+                            int err, const char *msg, va_list *ap)
+{
+  jclass cls;
+  int rc;
+  dstr d = DSTR_INIT;
+  jbyteArray msgstr;
+  jthrowable e;
+  jmethodID init;
+
+  cls = (*jni)->FindClass(jni, clsname); assert(cls);
+  init = (*jni)->GetMethodID(jni, cls, "<init>", "(I[B)V"); assert(init);
+  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);
+  rc = (*jni)->Throw(jni, e); assert(!rc);
+}
+
+static void except_syserror(JNIEnv *jni, const char *clsname,
+                           int err, const char *msg, ...)
+{
+  va_list ap;
+
+  va_start(ap, msg);
+  vexcept_syserror(jni, clsname, err, msg, &ap);
+  va_end(ap);
+}
+
+/*----- Wrapping native types ---------------------------------------------*/
+
+/* There's no way defined in the JNI to stash a C pointer in a Java object.
+ * It seems that the usual approach is to cast to `jlong', but this is
+ * clearly unsatisfactory.  Instead, we store structures as Java byte arrays,
+ * with a 32-bit tag on the front.
+ */
 
 struct native_type {
   const char *name;
   size_t sz;
-  uint32_t tag;
+  uint32 tag;
 };
 
-typedef jbyteArray wrapped;
+typedef jbyteArray wrapper;
 
-struct open {
-  wrapped obj;
-  jbyte *arr;
+struct native_base {
+  uint32 tag;
 };
 
-struct base {
-  uint32_t tag;
+static int unwrap(JNIEnv *jni, void *p,
+                 const struct native_type *ty, wrapper w)
+{
+  jbyte *q;
+  jclass cls;
+  struct native_base *b = p;
+  jsize n;
+
+  if (!w) { except(jni, NULLERR, 0); return (-1); }
+  cls = (*jni)->FindClass(jni, "[B"); assert(cls);
+  if (!(*jni)->IsInstanceOf(jni, w, cls)) {
+    except(jni, TYPEERR,
+          "corrupted native object wrapper: expected a byte array");
+    return (-1);
+  }
+  n = (*jni)->GetArrayLength(jni, w);
+  if (n != ty->sz) {
+    except(jni, TYPEERR,
+          "corrupted native object wrapper: wrong size for `%s'",
+          ty->name);
+    return (-1);
+  }
+  q = (*jni)->GetByteArrayElements(jni, w, 0); if (!q) return (-1);
+  memcpy(b, q, ty->sz);
+  (*jni)->ReleaseByteArrayElements(jni, w, q, JNI_ABORT);
+  if (b->tag != ty->tag) {
+    except(jni, TYPEERR,
+          "corrupted native object wrapper: expected tag for `%s'",
+          ty->name);
+    return (-1);
+  }
+  return (0);
+}
+
+static int update_wrapper(JNIEnv *jni, const struct native_type *ty,
+                         wrapper w, const void *p)
+{
+  jbyte *q;
+
+  q = (*jni)->GetByteArrayElements(jni, w, 0); if (!q) return (-1);
+  memcpy(q, p, ty->sz);
+  (*jni)->ReleaseByteArrayElements(jni, w, q, 0);
+  return (0);
+}
+
+static wrapper wrap(JNIEnv *jni, const struct native_type *ty, const void *p)
+{
+  wrapper w;
+
+  w = (*jni)->NewByteArray(jni, ty->sz); if (!w) return (0);
+  if (update_wrapper(jni, ty, w, p)) return (0);
+  return (w);
+}
+
+#define INIT_NATIVE(type, p) do (p)->_base.tag = type##_type.tag; while (0)
+
+/*----- Crypto information ------------------------------------------------*/
+
+JNIEXPORT jint JNICALL JNIFUNC(hashsz)(JNIEnv *jni, jobject cls,
+                                      jstring hnamestr)
+{
+  jint rc = -1;
+  const char *hname;
+  const gchash *hc;
+
+  hname = (*jni)->GetStringUTFChars(jni, hnamestr, 0);
+  if (!hname) goto end;
+  hc = ghash_byname(hname); if (!hc) goto end;
+  rc = hc->hashsz;
+
+end:
+  if (hname) (*jni)->ReleaseStringUTFChars(jni, hnamestr, hname);
+  return (rc);
+}
+
+/*----- System errors -----------------------------------------------------*/
+
+static const struct errtab { const char *tag; int err; } errtab[] = {
+  /*
+     ;;; The errno name table is very boring to type.  To make life less
+     ;;; awful, put the errno names in this list and evaluate the code to
+     ;;; get Emacs to regenerate it.
+
+     (let ((errors '(EPERM ENOENT ESRCH EINTR EIO ENXIO E2BIG ENOEXEC EBADF
+                    ECHILD EAGAIN ENOMEM EACCES EFAULT ENOTBLK EBUSY EEXIST
+                    EXDEV ENODEV ENOTDIR EISDIR EINVAL ENFILE EMFILE ENOTTY
+                    ETXTBSY EFBIG ENOSPC ESPIPE EROFS EMLINK EPIPE EDOM
+                    ERANGE
+
+                    EDEADLK ENAMETOOLONG ENOLCK ENOSYS ENOTEMPTY ELOOP
+                    EWOULDBLOCK ENOMSG EIDRM ECHRNG EL2NSYNC EL3HLT EL3RST
+                    ELNRNG EUNATCH ENOCSI EL2HLT EBADE EBADR EXFULL ENOANO
+                    EBADRQC EBADSLT EDEADLOCK EBFONT ENOSTR ENODATA ETIME
+                    ENOSR ENONET ENOPKG EREMOTE ENOLINK EADV ESRMNT ECOMM
+                    EPROTO EMULTIHOP EDOTDOT EBADMSG EOVERFLOW ENOTUNIQ
+                    EBADFD EREMCHG ELIBACC ELIBBAD ELIBSCN ELIBMAX ELIBEXEC
+                    EILSEQ ERESTART ESTRPIPE EUSERS ENOTSOCK EDESTADDRREQ
+                    EMSGSIZE EPROTOTYPE ENOPROTOOPT EPROTONOSUPPORT
+                    ESOCKTNOSUPPORT EOPNOTSUPP EPFNOSUPPORT EAFNOSUPPORT
+                    EADDRINUSE EADDRNOTAVAIL ENETDOWN ENETUNREACH ENETRESET
+                    ECONNABORTED ECONNRESET ENOBUFS EISCONN ENOTCONN
+                    ESHUTDOWN ETOOMANYREFS ETIMEDOUT ECONNREFUSED EHOSTDOWN
+                    EHOSTUNREACH EALREADY EINPROGRESS ESTALE EUCLEAN ENOTNAM
+                    ENAVAIL EISNAM EREMOTEIO EDQUOT ENOMEDIUM EMEDIUMTYPE
+                    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)))))
+  */
+  /***BEGIN errtab***/
+#ifdef EPERM
+  { "EPERM", EPERM },
+#endif
+#ifdef ENOENT
+  { "ENOENT", ENOENT },
+#endif
+#ifdef ESRCH
+  { "ESRCH", ESRCH },
+#endif
+#ifdef EINTR
+  { "EINTR", EINTR },
+#endif
+#ifdef EIO
+  { "EIO", EIO },
+#endif
+#ifdef ENXIO
+  { "ENXIO", ENXIO },
+#endif
+#ifdef E2BIG
+  { "E2BIG", E2BIG },
+#endif
+#ifdef ENOEXEC
+  { "ENOEXEC", ENOEXEC },
+#endif
+#ifdef EBADF
+  { "EBADF", EBADF },
+#endif
+#ifdef ECHILD
+  { "ECHILD", ECHILD },
+#endif
+#ifdef EAGAIN
+  { "EAGAIN", EAGAIN },
+#endif
+#ifdef ENOMEM
+  { "ENOMEM", ENOMEM },
+#endif
+#ifdef EACCES
+  { "EACCES", EACCES },
+#endif
+#ifdef EFAULT
+  { "EFAULT", EFAULT },
+#endif
+#ifdef ENOTBLK
+  { "ENOTBLK", ENOTBLK },
+#endif
+#ifdef EBUSY
+  { "EBUSY", EBUSY },
+#endif
+#ifdef EEXIST
+  { "EEXIST", EEXIST },
+#endif
+#ifdef EXDEV
+  { "EXDEV", EXDEV },
+#endif
+#ifdef ENODEV
+  { "ENODEV", ENODEV },
+#endif
+#ifdef ENOTDIR
+  { "ENOTDIR", ENOTDIR },
+#endif
+#ifdef EISDIR
+  { "EISDIR", EISDIR },
+#endif
+#ifdef EINVAL
+  { "EINVAL", EINVAL },
+#endif
+#ifdef ENFILE
+  { "ENFILE", ENFILE },
+#endif
+#ifdef EMFILE
+  { "EMFILE", EMFILE },
+#endif
+#ifdef ENOTTY
+  { "ENOTTY", ENOTTY },
+#endif
+#ifdef ETXTBSY
+  { "ETXTBSY", ETXTBSY },
+#endif
+#ifdef EFBIG
+  { "EFBIG", EFBIG },
+#endif
+#ifdef ENOSPC
+  { "ENOSPC", ENOSPC },
+#endif
+#ifdef ESPIPE
+  { "ESPIPE", ESPIPE },
+#endif
+#ifdef EROFS
+  { "EROFS", EROFS },
+#endif
+#ifdef EMLINK
+  { "EMLINK", EMLINK },
+#endif
+#ifdef EPIPE
+  { "EPIPE", EPIPE },
+#endif
+#ifdef EDOM
+  { "EDOM", EDOM },
+#endif
+#ifdef ERANGE
+  { "ERANGE", ERANGE },
+#endif
+#ifdef EDEADLK
+  { "EDEADLK", EDEADLK },
+#endif
+#ifdef ENAMETOOLONG
+  { "ENAMETOOLONG", ENAMETOOLONG },
+#endif
+#ifdef ENOLCK
+  { "ENOLCK", ENOLCK },
+#endif
+#ifdef ENOSYS
+  { "ENOSYS", ENOSYS },
+#endif
+#ifdef ENOTEMPTY
+  { "ENOTEMPTY", ENOTEMPTY },
+#endif
+#ifdef ELOOP
+  { "ELOOP", ELOOP },
+#endif
+#ifdef EWOULDBLOCK
+  { "EWOULDBLOCK", EWOULDBLOCK },
+#endif
+#ifdef ENOMSG
+  { "ENOMSG", ENOMSG },
+#endif
+#ifdef EIDRM
+  { "EIDRM", EIDRM },
+#endif
+#ifdef ECHRNG
+  { "ECHRNG", ECHRNG },
+#endif
+#ifdef EL2NSYNC
+  { "EL2NSYNC", EL2NSYNC },
+#endif
+#ifdef EL3HLT
+  { "EL3HLT", EL3HLT },
+#endif
+#ifdef EL3RST
+  { "EL3RST", EL3RST },
+#endif
+#ifdef ELNRNG
+  { "ELNRNG", ELNRNG },
+#endif
+#ifdef EUNATCH
+  { "EUNATCH", EUNATCH },
+#endif
+#ifdef ENOCSI
+  { "ENOCSI", ENOCSI },
+#endif
+#ifdef EL2HLT
+  { "EL2HLT", EL2HLT },
+#endif
+#ifdef EBADE
+  { "EBADE", EBADE },
+#endif
+#ifdef EBADR
+  { "EBADR", EBADR },
+#endif
+#ifdef EXFULL
+  { "EXFULL", EXFULL },
+#endif
+#ifdef ENOANO
+  { "ENOANO", ENOANO },
+#endif
+#ifdef EBADRQC
+  { "EBADRQC", EBADRQC },
+#endif
+#ifdef EBADSLT
+  { "EBADSLT", EBADSLT },
+#endif
+#ifdef EDEADLOCK
+  { "EDEADLOCK", EDEADLOCK },
+#endif
+#ifdef EBFONT
+  { "EBFONT", EBFONT },
+#endif
+#ifdef ENOSTR
+  { "ENOSTR", ENOSTR },
+#endif
+#ifdef ENODATA
+  { "ENODATA", ENODATA },
+#endif
+#ifdef ETIME
+  { "ETIME", ETIME },
+#endif
+#ifdef ENOSR
+  { "ENOSR", ENOSR },
+#endif
+#ifdef ENONET
+  { "ENONET", ENONET },
+#endif
+#ifdef ENOPKG
+  { "ENOPKG", ENOPKG },
+#endif
+#ifdef EREMOTE
+  { "EREMOTE", EREMOTE },
+#endif
+#ifdef ENOLINK
+  { "ENOLINK", ENOLINK },
+#endif
+#ifdef EADV
+  { "EADV", EADV },
+#endif
+#ifdef ESRMNT
+  { "ESRMNT", ESRMNT },
+#endif
+#ifdef ECOMM
+  { "ECOMM", ECOMM },
+#endif
+#ifdef EPROTO
+  { "EPROTO", EPROTO },
+#endif
+#ifdef EMULTIHOP
+  { "EMULTIHOP", EMULTIHOP },
+#endif
+#ifdef EDOTDOT
+  { "EDOTDOT", EDOTDOT },
+#endif
+#ifdef EBADMSG
+  { "EBADMSG", EBADMSG },
+#endif
+#ifdef EOVERFLOW
+  { "EOVERFLOW", EOVERFLOW },
+#endif
+#ifdef ENOTUNIQ
+  { "ENOTUNIQ", ENOTUNIQ },
+#endif
+#ifdef EBADFD
+  { "EBADFD", EBADFD },
+#endif
+#ifdef EREMCHG
+  { "EREMCHG", EREMCHG },
+#endif
+#ifdef ELIBACC
+  { "ELIBACC", ELIBACC },
+#endif
+#ifdef ELIBBAD
+  { "ELIBBAD", ELIBBAD },
+#endif
+#ifdef ELIBSCN
+  { "ELIBSCN", ELIBSCN },
+#endif
+#ifdef ELIBMAX
+  { "ELIBMAX", ELIBMAX },
+#endif
+#ifdef ELIBEXEC
+  { "ELIBEXEC", ELIBEXEC },
+#endif
+#ifdef EILSEQ
+  { "EILSEQ", EILSEQ },
+#endif
+#ifdef ERESTART
+  { "ERESTART", ERESTART },
+#endif
+#ifdef ESTRPIPE
+  { "ESTRPIPE", ESTRPIPE },
+#endif
+#ifdef EUSERS
+  { "EUSERS", EUSERS },
+#endif
+#ifdef ENOTSOCK
+  { "ENOTSOCK", ENOTSOCK },
+#endif
+#ifdef EDESTADDRREQ
+  { "EDESTADDRREQ", EDESTADDRREQ },
+#endif
+#ifdef EMSGSIZE
+  { "EMSGSIZE", EMSGSIZE },
+#endif
+#ifdef EPROTOTYPE
+  { "EPROTOTYPE", EPROTOTYPE },
+#endif
+#ifdef ENOPROTOOPT
+  { "ENOPROTOOPT", ENOPROTOOPT },
+#endif
+#ifdef EPROTONOSUPPORT
+  { "EPROTONOSUPPORT", EPROTONOSUPPORT },
+#endif
+#ifdef ESOCKTNOSUPPORT
+  { "ESOCKTNOSUPPORT", ESOCKTNOSUPPORT },
+#endif
+#ifdef EOPNOTSUPP
+  { "EOPNOTSUPP", EOPNOTSUPP },
+#endif
+#ifdef EPFNOSUPPORT
+  { "EPFNOSUPPORT", EPFNOSUPPORT },
+#endif
+#ifdef EAFNOSUPPORT
+  { "EAFNOSUPPORT", EAFNOSUPPORT },
+#endif
+#ifdef EADDRINUSE
+  { "EADDRINUSE", EADDRINUSE },
+#endif
+#ifdef EADDRNOTAVAIL
+  { "EADDRNOTAVAIL", EADDRNOTAVAIL },
+#endif
+#ifdef ENETDOWN
+  { "ENETDOWN", ENETDOWN },
+#endif
+#ifdef ENETUNREACH
+  { "ENETUNREACH", ENETUNREACH },
+#endif
+#ifdef ENETRESET
+  { "ENETRESET", ENETRESET },
+#endif
+#ifdef ECONNABORTED
+  { "ECONNABORTED", ECONNABORTED },
+#endif
+#ifdef ECONNRESET
+  { "ECONNRESET", ECONNRESET },
+#endif
+#ifdef ENOBUFS
+  { "ENOBUFS", ENOBUFS },
+#endif
+#ifdef EISCONN
+  { "EISCONN", EISCONN },
+#endif
+#ifdef ENOTCONN
+  { "ENOTCONN", ENOTCONN },
+#endif
+#ifdef ESHUTDOWN
+  { "ESHUTDOWN", ESHUTDOWN },
+#endif
+#ifdef ETOOMANYREFS
+  { "ETOOMANYREFS", ETOOMANYREFS },
+#endif
+#ifdef ETIMEDOUT
+  { "ETIMEDOUT", ETIMEDOUT },
+#endif
+#ifdef ECONNREFUSED
+  { "ECONNREFUSED", ECONNREFUSED },
+#endif
+#ifdef EHOSTDOWN
+  { "EHOSTDOWN", EHOSTDOWN },
+#endif
+#ifdef EHOSTUNREACH
+  { "EHOSTUNREACH", EHOSTUNREACH },
+#endif
+#ifdef EALREADY
+  { "EALREADY", EALREADY },
+#endif
+#ifdef EINPROGRESS
+  { "EINPROGRESS", EINPROGRESS },
+#endif
+#ifdef ESTALE
+  { "ESTALE", ESTALE },
+#endif
+#ifdef EUCLEAN
+  { "EUCLEAN", EUCLEAN },
+#endif
+#ifdef ENOTNAM
+  { "ENOTNAM", ENOTNAM },
+#endif
+#ifdef ENAVAIL
+  { "ENAVAIL", ENAVAIL },
+#endif
+#ifdef EISNAM
+  { "EISNAM", EISNAM },
+#endif
+#ifdef EREMOTEIO
+  { "EREMOTEIO", EREMOTEIO },
+#endif
+#ifdef EDQUOT
+  { "EDQUOT", EDQUOT },
+#endif
+#ifdef ENOMEDIUM
+  { "ENOMEDIUM", ENOMEDIUM },
+#endif
+#ifdef EMEDIUMTYPE
+  { "EMEDIUMTYPE", EMEDIUMTYPE },
+#endif
+#ifdef ECANCELED
+  { "ECANCELED", ECANCELED },
+#endif
+#ifdef ENOKEY
+  { "ENOKEY", ENOKEY },
+#endif
+#ifdef EKEYEXPIRED
+  { "EKEYEXPIRED", EKEYEXPIRED },
+#endif
+#ifdef EKEYREVOKED
+  { "EKEYREVOKED", EKEYREVOKED },
+#endif
+#ifdef EKEYREJECTED
+  { "EKEYREJECTED", EKEYREJECTED },
+#endif
+#ifdef EOWNERDEAD
+  { "EOWNERDEAD", EOWNERDEAD },
+#endif
+#ifdef ENOTRECOVERABLE
+  { "ENOTRECOVERABLE", ENOTRECOVERABLE },
+#endif
+#ifdef ERFKILL
+  { "ERFKILL", ERFKILL },
+#endif
+#ifdef EHWPOISON
+  { "EHWPOISON", EHWPOISON },
+#endif
+  /***END***/
 };
 
-static void except(JNIEnv *jni, const char *clsname, const char *msg)
+JNIEXPORT jobject JNIFUNC(errtab)(JNIEnv *jni, jobject cls)
+{
+  size_t i;
+  jclass eltcls;
+  jarray v;
+  jmethodID init;
+  jobject e;
+
+  eltcls =
+    (*jni)->FindClass(jni, ERRENTRY);
+  assert(eltcls);
+  v = (*jni)->NewObjectArray(jni, N(errtab), eltcls, 0); if (!v) return (0);
+  init = (*jni)->GetMethodID(jni, eltcls, "<init>",
+                            "(Ljava/lang/String;I)V");
+  assert(init);
+
+  for (i = 0; i < N(errtab); i++) {
+    e = (*jni)->NewObject(jni, eltcls, init,
+                         (*jni)->NewStringUTF(jni, errtab[i].tag),
+                         errtab[i].err);
+    (*jni)->SetObjectArrayElement(jni, v, i, e);
+  }
+  return (v);
+}
+
+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;
-  int rc;
+  jfieldID fid;
 
-  cls = (*jni)->FindClass(jni, clsname); assert(cls);
-  rc = (*jni)->ThrowNew(jni, cls, msg); assert(!rc);
+  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);
 }
 
-static void except_errno(JNIEnv *jni, const char *clsname, int err)
-  { except(jni, clsname, strerror(err)); }
+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))); }
 
-static void *open_struct_unchecked(JNIEnv *jni, wrapped obj, struct open *op)
+/*----- 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.
+ * 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
+ * 21 (in 5.0 Lollipop), and there's nothing in the support library to help.
+ *
+ * So the other option is to implement them ourselves.
+ */
+
+JNIEXPORT void JNIFUNC(unlink)(JNIEnv *jni, jobject cls, jobject path)
 {
-  jboolean copyp;
-  uintptr_t p, q;
+  const char *pathstr = 0;
 
-  op->obj = obj;
-  op->arr = (*jni)->GetByteArrayElements(jni, obj, &copyp);
-  if (!op->arr) return (0);
-  p = (uintptr_t)op->arr;
-  q = p + sizeof(union align) - 1;
-  q -= q%sizeof(union align);
-  fprintf(stderr, ";; offset = %"PRIuPTR"\n", q - p);
-  return (op->arr + (q - p));
+  pathstr = get_cstring(jni, path); if (!pathstr) goto end;
+  if (unlink(pathstr)) {
+    except_syserror(jni, SYSERR, errno,
+                   "failed to delete file `%s'", pathstr);
+    goto end;
+  }
+end:
+  put_cstring(jni, path, pathstr);
 }
 
-static void *open_struct(JNIEnv *jni, wrapped obj,
-                        const struct native_type *ty, struct open *op)
+JNIEXPORT void JNIFUNC(rmdir)(JNIEnv *jni, jobject cls, jobject path)
 {
-  struct base *p;
-  jsize n;
+  const char *pathstr = 0;
 
-  if (!obj) { except(jni, "java/lang/NullPointerException", 0); return (0); }
-  n = (*jni)->GetArrayLength(jni, obj);
-  if ((*jni)->ExceptionOccurred(jni)) return (0);
-  p = open_struct_unchecked(jni, obj, op);
-  if (!p) return (0);
-  if (n < ty->sz + sizeof(union align) - 1 || p->tag != ty->tag)
-  {
-    (*jni)->ReleaseByteArrayElements(jni, obj, op->arr, JNI_ABORT);
-    except(jni, "uk/org/distorted/tripe/JNI$NativeObjectTypeException", 0);
-    return (0);
+  pathstr = get_cstring(jni, path); if (!pathstr) goto end;
+  if (rmdir(pathstr)) {
+    except_syserror(jni, SYSERR, errno,
+                   "failed to delete directory `%s'", pathstr);
+    goto end;
+  }
+end:
+  put_cstring(jni, path, pathstr);
+}
+
+JNIEXPORT void JNIFUNC(mkdir)(JNIEnv *jni, jobject cls,
+                             jobject path, jint mode)
+{
+  const char *pathstr = 0;
+
+  pathstr = get_cstring(jni, path); if (!pathstr) goto end;
+  if (mkdir(pathstr, mode)) {
+    except_syserror(jni, SYSERR, errno,
+                   "failed to create directory `%s'", pathstr);
+    goto end;
+  }
+end:
+  put_cstring(jni, path, pathstr);
+}
+
+JNIEXPORT void JNIFUNC(mkfile)(JNIEnv *jni, jobject cls,
+                           jobject path, jint mode)
+{
+  const char *pathstr = 0;
+  int fd = -1;
+
+  pathstr = get_cstring(jni, path); if (!pathstr) goto end;
+  fd = open(pathstr, O_WRONLY | O_CREAT | O_EXCL, mode);
+  if (fd < 0) {
+    except_syserror(jni, SYSERR, errno,
+                   "failed to create fresh file `%s'", pathstr);
+    goto end;
   }
-  return (p);
+end:
+  if (fd != -1) close(fd);
+  put_cstring(jni, path, pathstr);
+}
+
+JNIEXPORT void JNIFUNC(rename)(JNIEnv *jni, jobject cls,
+                              jobject from, jobject to)
+{
+  const char *fromstr = 0, *tostr = 0;
+
+  fromstr = get_cstring(jni, from); if (!fromstr) goto end;
+  tostr = get_cstring(jni, to); if (!tostr) goto end;
+  if (rename(fromstr, tostr)) {
+    except_syserror(jni, SYSERR, errno,
+                   "failed to rename `%s' as `%s'", fromstr, tostr);
+    goto end;
+  }
+end:
+  put_cstring(jni, from, fromstr);
+  put_cstring(jni, to, tostr);
+}
+
+#define LKF_EXCL 0x1000u
+#define LKF_WAIT 0x2000u
+struct lockf {
+  struct native_base _base;
+  int fd;
+};
+static struct native_type lockf_type =
+       { "lock", sizeof(struct lockf), 0xb2648926};
+JNIEXPORT wrapper JNIFUNC(lock)(JNIEnv *jni, jobject cls,
+                               jobject path, jint flags)
+{
+  const char *pathstr = 0;
+  int fd = -1;
+  struct flock l;
+  struct lockf lk;
+  struct stat st0, st1;
+  int f;
+  wrapper r = 0;
+
+  pathstr = get_cstring(jni, path); if (!pathstr) goto end;
+
+again:
+  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;
+  l.l_type = (flags&LKF_EXCL) ? F_WRLCK : F_RDLCK;
+  l.l_whence = SEEK_SET;
+  l.l_start = 0;
+  l.l_len = 0;
+  if (fcntl(fd, (flags&LKF_WAIT) ? F_SETLKW : F_SETLK, &l)) goto err;
+  if (stat(pathstr, &st1))
+    { if (errno == ENOENT) goto again; else goto err; }
+  if (st0.st_dev != st1.st_dev || st0.st_ino != st1.st_ino)
+    { close(fd); fd = -1; goto again; }
+
+  INIT_NATIVE(lockf, &lk); lk.fd = fd; fd = -1;
+  r = wrap(jni, &lockf_type, &lk);
+  goto end;
+
+err:
+  except_syserror(jni, SYSERR, errno, "failed to lock file `%s'", pathstr);
+end:
+  if (fd != -1) close(fd);
+  put_cstring(jni, path, pathstr);
+  return (r);
+}
+
+JNIEXPORT void JNIFUNC(unlock)(JNIEnv *jni, jobject cls, wrapper wlk)
+{
+  struct lockf lk;
+  struct flock l;
+  int rc;
+
+  if (unwrap(jni, &lk, &lockf_type, wlk)) goto end;
+  if (lk.fd == -1) goto end;
+  l.l_type = F_UNLCK;
+  l.l_whence = SEEK_SET;
+  l.l_start = 0;
+  l.l_len = 0;
+  if (fcntl(lk.fd, F_SETLK, &l)) goto end;
+  close(lk.fd); lk.fd = -1;
+  rc = update_wrapper(jni, &lockf_type, wlk, &lk); assert(!rc);
+end:;
 }
 
-static wrapped close_struct(JNIEnv *jni, struct open *op)
+static jlong xlttimespec(const struct timespec *ts)
+  { return (1000*(jlong)ts->tv_sec + ts->tv_nsec/1000000); }
+
+static jobject xltstat(JNIEnv *jni, const struct stat *st)
 {
-  (*jni)->ReleaseByteArrayElements(jni, op->obj, op->arr, 0);
-  return (op->obj);
+  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;
+  else if (S_ISDIR(st->st_mode)) modehack |= 0040000;
+  else if (S_ISBLK(st->st_mode)) modehack |= 0060000;
+  else if (S_ISREG(st->st_mode)) modehack |= 0100000;
+  else if (S_ISLNK(st->st_mode)) modehack |= 0120000;
+  else if (S_ISSOCK(st->st_mode)) modehack |= 0140000;
+
+  cls = (*jni)->FindClass(jni, STAT); assert(cls);
+  init = (*jni)->GetMethodID(jni, cls, "<init>", "(IIJIIIIIIJIJJJJ)V");
+  assert(init);
+  return ((*jni)->NewObject(jni, cls, init,
+                           (jint)major(st->st_dev), (jint)minor(st->st_dev),
+                           (jlong)st->st_ino,
+                           modehack,
+                           (jint)st->st_nlink,
+                           (jint)st->st_uid, (jint)st->st_gid,
+                           (jint)major(st->st_rdev), (jint)minor(st->st_rdev),
+                           (jlong)st->st_size,
+                           (jint)st->st_blksize, (jlong)st->st_blocks,
+                           xlttimespec(&st->st_atim),
+                           xlttimespec(&st->st_mtim),
+                           xlttimespec(&st->st_ctim)));
 }
 
-static void *alloc_struct(JNIEnv *jni, const struct native_type *ty,
-                         struct open *op)
+JNIEXPORT jobject JNIFUNC(stat)(JNIEnv *jni, jobject cls, jobject path)
 {
-  wrapped obj;
-  struct base *p;
+  jobject r = 0;
+  const char *pathstr = 0;
+  struct stat st;
 
-  obj = (*jni)->NewByteArray(jni, ty->sz + sizeof(union align) - 1);
-  if (!obj) return (0);
-  p = open_struct_unchecked(jni, obj, op);
-  if (!p) { (*jni)->DeleteLocalRef(jni, obj); return (0); }
-  p->tag = ty->tag;
-  return (p);
+  pathstr = get_cstring(jni, path); if (!pathstr) goto end;
+  if (stat(pathstr, &st)) {
+    except_syserror(jni, SYSERR, errno,
+                   "failed to read information about `%s'", pathstr);
+    goto end;
+  }
+  r = xltstat(jni, &st);
+end:
+  put_cstring(jni, path, pathstr);
+  return (r);
 }
 
-JNIEXPORT void JNICALL Java_uk_org_distorted_tripe_JNI_test
-       (JNIEnv *jni, jobject cls)
-  { printf("Hello from C!\n"); }
+JNIEXPORT jobject JNIFUNC(lstat)(JNIEnv *jni, jobject cls, jobject path)
+{
+  jobject r = 0;
+  const char *pathstr = 0;
+  struct stat st;
+
+  pathstr = get_cstring(jni, path); if (!pathstr) goto end;
+  if (lstat(pathstr, &st)) {
+    except_syserror(jni, SYSERR, errno,
+                   "failed to read information about `%s'", pathstr);
+    goto end;
+  }
+  r = xltstat(jni, &st);
+end:
+  put_cstring(jni, path, pathstr);
+  return (r);
+}
 
-struct toy {
-  struct base _base;
-  const char *p;
+struct dir {
+  struct native_base _base;
+  DIR *d;
 };
-static const struct native_type toy_type =
-       { "toy", sizeof(struct toy), 0x58008918 };
+static const struct native_type dir_type =
+       { "dir", sizeof(struct dir), 0x0f5ca477 };
 
-JNIEXPORT wrapped JNICALL Java_uk_org_distorted_tripe_JNI_make
-       (JNIEnv *jni, jobject cls)
+JNIEXPORT jobject JNIFUNC(opendir)(JNIEnv *jni, jobject cls, jobject path)
 {
-  struct open op_toy;
-  struct toy *toy;
+  const char *pathstr = 0;
+  struct dir dir;
+  wrapper r = 0;
 
-  toy = alloc_struct(jni, &toy_type, &op_toy);
-  if (!toy) return (0);
-  toy->p = "A working thing";
-  return (close_struct(jni, &op_toy));
+  pathstr = get_cstring(jni, path); if (!pathstr) goto end;
+  INIT_NATIVE(dir, &dir);
+  dir.d = opendir(pathstr);
+  if (!dir.d) {
+    except_syserror(jni, SYSERR, errno,
+                   "failed to open directory `%s'", pathstr);
+    goto end;
+  }
+  r = wrap(jni, &dir_type, &dir);
+end:
+  put_cstring(jni, path, pathstr);
+  return (r);
 }
 
-JNIEXPORT void JNICALL Java_uk_org_distorted_tripe_JNI_check
-       (JNIEnv *jni, jobject cls, wrapped wtoy)
+JNIEXPORT jbyteArray JNIFUNC(readdir)(JNIEnv *jni, jobject cls,
+                                     jobject path, jobject wdir)
 {
-  struct toy *toy;
-  struct open op_toy;
+  const char *pathstr = 0;
+  struct dir dir;
+  struct dirent *d;
+  jbyteArray r = 0;
 
-  toy = open_struct(jni, wtoy, &toy_type, &op_toy);
-  if (!toy) return;
-  printf("Toy says: %s\n", toy->p);
-  close_struct(jni, &op_toy);
+  if (unwrap(jni, &dir, &dir_type, wdir)) goto end;
+  if (!dir.d) { except(jni, ARGERR, "directory has been closed"); goto end; }
+  errno = 0; d = readdir(dir.d);
+  if (errno) {
+    pathstr = get_cstring(jni, path); if (!pathstr) goto end;
+    except_syserror(jni, SYSERR, errno,
+                   "failed to read directory `%s'", pathstr);
+    goto end;
+  }
+  if (d) r = wrap_cstring(jni, d->d_name);
+end:
+  put_cstring(jni, path, pathstr);
+  return (r);
+}
+
+JNIEXPORT void JNIFUNC(closedir)(JNIEnv *jni, jobject cls,
+                                jobject path, jobject wdir)
+{
+  const char *pathstr = 0;
+  struct dir dir;
+
+  if (unwrap(jni, &dir, &dir_type, wdir)) goto end;
+  if (!dir.d) goto end;
+  if (closedir(dir.d)) {
+    pathstr = get_cstring(jni, path); if (!pathstr) goto end;
+    except_syserror(jni, SYSERR, errno,
+                   "failed to close directory `%s'", pathstr);
+    goto end;
+  }
+  dir.d = 0;
+  if (update_wrapper(jni, &dir_type, wdir, &dir)) goto end;
+end:
+  put_cstring(jni, path, pathstr);
 }
 
+/*----- A server connection, using a Unix-domain socket -------------------*/
+
 struct conn {
-  struct base _base;
+  struct native_base _base;
   int fd;
   unsigned f;
 #define CF_CLOSERD 1u
 #define CF_CLOSEWR 2u
 #define CF_CLOSEMASK (CF_CLOSERD | CF_CLOSEWR)
 };
-static const struct native_type conn_type
-       { "conn", sizeof(struct conn), 0xed030167 };
+static const struct native_type conn_type =
+       { "conn", sizeof(struct conn), 0xed030167 };
 
-JNIEXPORT wrapped JNICALL Java_uk_org_distorted_tripe_JNI_connect
-       (JNIEnv *jni, jobject cls)
+JNIEXPORT wrapper JNICALL JNIFUNC(connect)(JNIEnv *jni, jobject cls,
+                                          jobject path)
 {
-  struct conn *conn;
-  struct open op;
+  struct conn conn;
   struct sockaddr_un sun;
+  const char *pathstr = 0;
+  jobject ret = 0;
   int fd = -1;
 
-  conn = alloc_struct(jni, &conn_type, &op);
-  if (!conn) goto err;
+  pathstr = get_cstring(jni, path); if (!pathstr) goto end;
+  if (strlen(pathstr) >= sizeof(sun.sun_path)) {
+    except(jni, ARGERR,
+          "Unix-domain socket path `%s' too long", pathstr);
+    goto end;
+  }
 
-  fd = socket(SOCK_STREAM, PF_UNIX, 0);
-  if (!fd) goto err_except;
+  INIT_NATIVE(conn, &conn);
+  fd = socket(SOCK_STREAM, PF_UNIX, 0); if (fd < 0) goto err;
 
   sun.sun_family = AF_UNIX;
-  strcpy(sun.sun_path, "/tmp/mdw/sk");
-  if (connect(fd, (struct sockaddr *)&sun, sizeof(sun))) goto err_except;
+  strcpy(sun.sun_path, (char *)pathstr);
+  if (connect(fd, (struct sockaddr *)&sun, sizeof(sun))) goto err;
 
-  conn->fd = fd;
-  return (close_struct(jni, &op));
+  conn.fd = fd; fd = -1;
+  conn.f = 0;
+  ret = wrap(jni, &conn_type, &conn);
+  goto end;
 
-err_except:
-  except_errno(jni, "java/io/IOException", errno);
 err:
-  if (fd) close(fd);
-  return (0);
+  except_syserror(jni, SYSERR, errno,
+                 "failed to connect to Unix-domain socket `%s'", pathstr);
+end:
+  if (fd == -1) close(fd);
+  put_cstring(jni, path, pathstr);
+  return (ret);
 }
 
-JNIEXPORT void JNICALL Java_uk_org_distorted_tripe_JNI_send
-       (JNIEnv *jni, jobject cls, wrapped wconn, jbyteArray buf,
-        jint start, jint len)
+static int check_buffer_bounds(JNIEnv *jni, const char *what,
+                              jbyteArray buf, jint start, jint len)
 {
-  struct conn *conn = 0;
-  struct open op;
-  jboolean copyp;
   jsize bufsz;
-  ssize_t n;
-  jbyte *p = 0;
-
-  conn = open_struct(jni, wconn, &conn_type, &op);
-  if (!conn) goto end;
+  jclass cls;
 
+  cls = (*jni)->FindClass(jni, "[B"); assert(cls);
+  if (!(*jni)->IsInstanceOf(jni, buf, cls)) {
+    except(jni, ARGERR,
+          "expected a byte array");
+    return (-1);
+  }
   bufsz = (*jni)->GetArrayLength(jni, buf);
-  if ((*jni)->ExceptionOccurred(jni)) goto end;
-  if (bufsz < start || bufsz - start < len) {
-    except(jni, "java/lang/IndexOutOfBoundsException",
-          "bad send-buffer bounds");
-    goto end;
+  if (start > bufsz) {
+    except(jni, BOUNDSERR,
+          "bad %s buffer bounds: start %d > buffer size %d", start, bufsz);
+    return (-1);
+  }
+  if (len > bufsz - start) {
+    except(jni, BOUNDSERR,
+          "bad %s buffer bounds: length %d > remaining buffer size %d",
+          len, bufsz - start);
+    return (-1);
   }
+  return (0);
+}
 
-  p = (*jni)->GetByteArrayElements(jni, buf, &copyp);
+JNIEXPORT void JNICALL JNIFUNC(send)(JNIEnv *jni, jobject cls,
+                                    wrapper wconn, jbyteArray buf,
+                                    jint start, jint len)
+{
+  struct conn conn;
+  ssize_t n;
+  jbyte *p = 0;
+
+  if (unwrap(jni, &conn, &conn_type, wconn)) goto end;
+  if (check_buffer_bounds(jni, "send", buf, start, len)) goto end;
+
+  p = (*jni)->GetByteArrayElements(jni, buf, 0);
   if (!p) goto end;
 
   while (len) {
-    n = send(conn->fd, p + start, len, 0);
+    n = send(conn.fd, p + start, len, 0);
     if (n < 0) {
-      except_errno(jni, "java/io/IOException", errno);
+      except_syserror(jni, SYSERR,
+                     errno, "failed to send on connection");
       goto end;
     }
     start += n; len -= n;
@@ -218,69 +1205,58 @@ JNIEXPORT void JNICALL Java_uk_org_distorted_tripe_JNI_send
 
 end:
   if (p) (*jni)->ReleaseByteArrayElements(jni, buf, p, JNI_ABORT);
-  if (conn) close_struct(jni, &op);
   return;
 }
 
-JNIEXPORT jint JNICALL Java_uk_org_distorted_tripe_JNI_recv
-       (JNIEnv *jni, jobject cls, wrapped wconn, jbyteArray buf,
-        jint start, jint len)
+JNIEXPORT jint JNICALL JNIFUNC(recv)(JNIEnv *jni, jobject cls,
+                                    wrapper wconn, jbyteArray buf,
+                                    jint start, jint len)
 {
-  struct conn *conn = 0;
-  struct open op;
-  jboolean copyp;
-  jsize bufsz;
+  struct conn conn;
   jbyte *p = 0;
   jint rc = -1;
 
-  conn = open_struct(jni, wconn, &conn_type, &op);
-  if (!conn) goto end;
+  if (unwrap(jni, &conn, &conn_type, wconn)) goto end;
+  if (check_buffer_bounds(jni, "send", buf, start, len)) goto end;
 
-  bufsz = (*jni)->GetArrayLength(jni, buf);
-  if ((*jni)->ExceptionOccurred(jni)) goto end;
-  if (bufsz < start || bufsz - start < len) {
-    except(jni, "java/lang/IndexOutOfBoundsException",
-          "bad receive-buffer bounds");
-    goto end;
-  }
-
-  p = (*jni)->GetByteArrayElements(jni, buf, &copyp);
+  p = (*jni)->GetByteArrayElements(jni, buf, 0);
   if (!p) goto end;
 
-  rc = recv(conn->fd, p + start, len, 0);
+  rc = recv(conn.fd, p + start, len, 0);
   if (rc < 0) {
-    except_errno(jni, "java/io/IOException", errno);
+    except_syserror(jni, SYSERR,
+                   errno, "failed to read from connection");
     goto end;
   }
   if (!rc) rc = -1;
 
 end:
   if (p) (*jni)->ReleaseByteArrayElements(jni, buf, p, 0);
-  if (conn) close_struct(jni, &op);
   return (rc);
 }
 
-JNIEXPORT void JNICALL Java_uk_org_distorted_tripe_JNI_close
-       (JNIEnv *jni, jobject cls, wrapped wconn, jint how)
+JNIEXPORT void JNICALL JNIFUNC(closeconn)(JNIEnv *jni, jobject cls,
+                                         wrapper wconn, jint how)
 {
-  struct conn *conn = 0;
-  struct open op;
+  struct conn conn;
+  int rc;
 
-  conn = open_struct(jni, wconn, &conn_type, &op);
-  if (!conn || conn->fd == -1) goto end;
+  if (unwrap(jni, &conn, &conn_type, wconn)) goto end;
+  if (conn.fd == -1) goto end;
 
-  how &= CF_CLOSEMASK&~conn->f;
-  conn->f |= how;
-fprintf(stderr, ";; closing %u\n", how);
-  if ((conn->f&CF_CLOSEMASK) == CF_CLOSEMASK) {
-    close(conn->fd);
-    conn->fd = -1;
+  how &= CF_CLOSEMASK&~conn.f;
+  conn.f |= how;
+  if ((conn.f&CF_CLOSEMASK) == CF_CLOSEMASK) {
+    close(conn.fd);
+    conn.fd = -1;
   } else {
-    if (how&CF_CLOSERD) shutdown(conn->fd, SHUT_RD);
-    if (how&CF_CLOSEWR) shutdown(conn->fd, SHUT_WR);
+    if (how&CF_CLOSERD) shutdown(conn.fd, SHUT_RD);
+    if (how&CF_CLOSEWR) shutdown(conn.fd, SHUT_WR);
   }
+  rc = update_wrapper(jni, &conn_type, wconn, &conn); assert(!rc);
 
 end:
-  if (conn) close_struct(jni, &op);
   return;
 }
+
+/*----- That's all, folks -------------------------------------------------*/