X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~mdw/git/tripe-android/blobdiff_plain/3a2f1a4ba4cc824e192c9d144b317a215e79c8d6..c8292b34485a2e00e676023d4164dd5841e4659f:/jni.c diff --git a/jni.c b/jni.c index 9f9e44d..f65e464 100644 --- 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 . + */ + +/*----- Header files ------------------------------------------------------*/ + +#define _FILE_OFFSET_BITS 64 + #include +#include #include #include -#include +#include #include #include #include @@ -10,207 +40,1164 @@ #include #include +#include +#include #include +#include #include +#include + +#include +#include +#include +#include + +#include #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, ";; \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, "", "(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, "", + "(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, "", "()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, ©p); - 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, "", "(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, ©p); +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, ©p); + 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 -------------------------------------------------*/