wip
[tripe-android] / sys.scala
diff --git a/sys.scala b/sys.scala
new file mode 100644 (file)
index 0000000..a6c00c3
--- /dev/null
+++ b/sys.scala
@@ -0,0 +1,327 @@
+/* -*-scala-*-
+ *
+ * System calls and errors
+ *
+ * (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/>.
+ */
+
+package uk.org.distorted.tripe; package object sys {
+
+/*----- Imports -----------------------------------------------------------*/
+
+import scala.collection.mutable.HashSet;
+
+import java.io.File;
+
+import Magic._;
+
+/*----- Error codes -------------------------------------------------------*/
+
+object Errno extends Enumeration {
+  private[this] val tagmap = {
+    val b = Map.newBuilder[String, Int];
+    for (jni.ErrorEntry(tag, err) <- jni.errtab) b += tag -> err;
+    b.result
+  }
+  private[this] var wrong = -255;
+  private[this] val seen = HashSet[Int]();
+
+  class ErrnoVal private[Errno](tag: String, val code: Int, id: Int)
+       extends Val(id, tag) {
+    def message: String = jni.strerror(code).toJString;
+  }
+
+  private[this] def err(tag: String, code: Int): ErrnoVal = {
+    if (seen contains code) { wrong -= 1; new ErrnoVal(tag, code, wrong) }
+    else { seen += code; new ErrnoVal(tag, code, code) }
+  }
+  private[this] def err(tag: String): ErrnoVal = err(tag, tagmap(tag));
+
+  val OK = err("OK", 0);
+
+  /*
+     ;;; 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 "  val %s = err(\"%s\");\n" err err)))))
+  */
+  /***BEGIN errtab***/
+  val EPERM = err("EPERM");
+  val ENOENT = err("ENOENT");
+  val ESRCH = err("ESRCH");
+  val EINTR = err("EINTR");
+  val EIO = err("EIO");
+  val ENXIO = err("ENXIO");
+  val E2BIG = err("E2BIG");
+  val ENOEXEC = err("ENOEXEC");
+  val EBADF = err("EBADF");
+  val ECHILD = err("ECHILD");
+  val EAGAIN = err("EAGAIN");
+  val ENOMEM = err("ENOMEM");
+  val EACCES = err("EACCES");
+  val EFAULT = err("EFAULT");
+  val ENOTBLK = err("ENOTBLK");
+  val EBUSY = err("EBUSY");
+  val EEXIST = err("EEXIST");
+  val EXDEV = err("EXDEV");
+  val ENODEV = err("ENODEV");
+  val ENOTDIR = err("ENOTDIR");
+  val EISDIR = err("EISDIR");
+  val EINVAL = err("EINVAL");
+  val ENFILE = err("ENFILE");
+  val EMFILE = err("EMFILE");
+  val ENOTTY = err("ENOTTY");
+  val ETXTBSY = err("ETXTBSY");
+  val EFBIG = err("EFBIG");
+  val ENOSPC = err("ENOSPC");
+  val ESPIPE = err("ESPIPE");
+  val EROFS = err("EROFS");
+  val EMLINK = err("EMLINK");
+  val EPIPE = err("EPIPE");
+  val EDOM = err("EDOM");
+  val ERANGE = err("ERANGE");
+  val EDEADLK = err("EDEADLK");
+  val ENAMETOOLONG = err("ENAMETOOLONG");
+  val ENOLCK = err("ENOLCK");
+  val ENOSYS = err("ENOSYS");
+  val ENOTEMPTY = err("ENOTEMPTY");
+  val ELOOP = err("ELOOP");
+  val EWOULDBLOCK = err("EWOULDBLOCK");
+  val ENOMSG = err("ENOMSG");
+  val EIDRM = err("EIDRM");
+  val ECHRNG = err("ECHRNG");
+  val EL2NSYNC = err("EL2NSYNC");
+  val EL3HLT = err("EL3HLT");
+  val EL3RST = err("EL3RST");
+  val ELNRNG = err("ELNRNG");
+  val EUNATCH = err("EUNATCH");
+  val ENOCSI = err("ENOCSI");
+  val EL2HLT = err("EL2HLT");
+  val EBADE = err("EBADE");
+  val EBADR = err("EBADR");
+  val EXFULL = err("EXFULL");
+  val ENOANO = err("ENOANO");
+  val EBADRQC = err("EBADRQC");
+  val EBADSLT = err("EBADSLT");
+  val EDEADLOCK = err("EDEADLOCK");
+  val EBFONT = err("EBFONT");
+  val ENOSTR = err("ENOSTR");
+  val ENODATA = err("ENODATA");
+  val ETIME = err("ETIME");
+  val ENOSR = err("ENOSR");
+  val ENONET = err("ENONET");
+  val ENOPKG = err("ENOPKG");
+  val EREMOTE = err("EREMOTE");
+  val ENOLINK = err("ENOLINK");
+  val EADV = err("EADV");
+  val ESRMNT = err("ESRMNT");
+  val ECOMM = err("ECOMM");
+  val EPROTO = err("EPROTO");
+  val EMULTIHOP = err("EMULTIHOP");
+  val EDOTDOT = err("EDOTDOT");
+  val EBADMSG = err("EBADMSG");
+  val EOVERFLOW = err("EOVERFLOW");
+  val ENOTUNIQ = err("ENOTUNIQ");
+  val EBADFD = err("EBADFD");
+  val EREMCHG = err("EREMCHG");
+  val ELIBACC = err("ELIBACC");
+  val ELIBBAD = err("ELIBBAD");
+  val ELIBSCN = err("ELIBSCN");
+  val ELIBMAX = err("ELIBMAX");
+  val ELIBEXEC = err("ELIBEXEC");
+  val EILSEQ = err("EILSEQ");
+  val ERESTART = err("ERESTART");
+  val ESTRPIPE = err("ESTRPIPE");
+  val EUSERS = err("EUSERS");
+  val ENOTSOCK = err("ENOTSOCK");
+  val EDESTADDRREQ = err("EDESTADDRREQ");
+  val EMSGSIZE = err("EMSGSIZE");
+  val EPROTOTYPE = err("EPROTOTYPE");
+  val ENOPROTOOPT = err("ENOPROTOOPT");
+  val EPROTONOSUPPORT = err("EPROTONOSUPPORT");
+  val ESOCKTNOSUPPORT = err("ESOCKTNOSUPPORT");
+  val EOPNOTSUPP = err("EOPNOTSUPP");
+  val EPFNOSUPPORT = err("EPFNOSUPPORT");
+  val EAFNOSUPPORT = err("EAFNOSUPPORT");
+  val EADDRINUSE = err("EADDRINUSE");
+  val EADDRNOTAVAIL = err("EADDRNOTAVAIL");
+  val ENETDOWN = err("ENETDOWN");
+  val ENETUNREACH = err("ENETUNREACH");
+  val ENETRESET = err("ENETRESET");
+  val ECONNABORTED = err("ECONNABORTED");
+  val ECONNRESET = err("ECONNRESET");
+  val ENOBUFS = err("ENOBUFS");
+  val EISCONN = err("EISCONN");
+  val ENOTCONN = err("ENOTCONN");
+  val ESHUTDOWN = err("ESHUTDOWN");
+  val ETOOMANYREFS = err("ETOOMANYREFS");
+  val ETIMEDOUT = err("ETIMEDOUT");
+  val ECONNREFUSED = err("ECONNREFUSED");
+  val EHOSTDOWN = err("EHOSTDOWN");
+  val EHOSTUNREACH = err("EHOSTUNREACH");
+  val EALREADY = err("EALREADY");
+  val EINPROGRESS = err("EINPROGRESS");
+  val ESTALE = err("ESTALE");
+  val EUCLEAN = err("EUCLEAN");
+  val ENOTNAM = err("ENOTNAM");
+  val ENAVAIL = err("ENAVAIL");
+  val EISNAM = err("EISNAM");
+  val EREMOTEIO = err("EREMOTEIO");
+  val EDQUOT = err("EDQUOT");
+  val ENOMEDIUM = err("ENOMEDIUM");
+  val EMEDIUMTYPE = err("EMEDIUMTYPE");
+  val ECANCELED = err("ECANCELED");
+  val ENOKEY = err("ENOKEY");
+  val EKEYEXPIRED = err("EKEYEXPIRED");
+  val EKEYREVOKED = err("EKEYREVOKED");
+  val EKEYREJECTED = err("EKEYREJECTED");
+  val EOWNERDEAD = err("EOWNERDEAD");
+  val ENOTRECOVERABLE = err("ENOTRECOVERABLE");
+  val ERFKILL = err("ERFKILL");
+  val EHWPOISON = err("EHWPOISON");
+  /***end***/
+}
+import Errno.{Value => _, _};
+
+object SystemError {
+  def apply(err: Errno.Value, what: String): SystemError =
+    new SystemError(err, what);
+  def unapply(e: Exception): Option[(Errno.Value, String)] = e match {
+    case e: SystemError => Some((e.err, e.what))
+    case _ => None
+  }
+}
+
+class SystemError private[this](val err: Errno.ErrnoVal, val what: String)
+       extends Exception {
+  def this(err: Errno.Value, what: String)
+    { this(err.asInstanceOf[Errno.ErrnoVal], what); }
+  private[tripe] def this(err: Int, what: CString)
+    { this(Errno(err), what.toJString); }
+  override def getMessage(): String = s"$what: ${err.message}";
+}
+
+/*----- Filesystem hacks --------------------------------------------------*/
+
+def freshFile(d: File): File = {
+  /* Return the name of a freshly created file in directory D. */
+
+  val buf = new Array[Byte](6);
+  val b = new StringBuilder;
+
+  while (true) {
+    /* Keep going until we find a fresh one. */
+
+    /* Provide a prefix.  Mostly this is to prevent the file starting with
+     * an unfortunate character like `-'.
+     */
+    b ++= "tmp.";
+
+    /* Generate some random bytes. */
+    rng.nextBytes(buf);
+
+    /* Now turn the bytes into a filename.  This is a cheesy implementation
+     * of Base64 encoding.
+     */
+    var a = 0;
+    var n = 0;
+
+    for (x <- buf) {
+      a = (a << 8) | x; n += 8;
+      while (n >= 6) {
+       val y = (a >> n - 6)&0x3f; n -= 6;
+       b += (if (y < 26) 'A' + y
+             else if (y < 52) 'a' + (y - 26)
+             else if (y < 62) '0' + (y - 52)
+             else if (y == 62) '+'
+             else '-').toChar;
+      }
+    }
+
+    /* Make the filename, and try to create the file.  If we succeed, we
+     * win.
+     */
+    val f = new File(d, b.result); b.clear();
+    try { jni.mkfile(f); return f; }
+    catch { case SystemError(EEXIST, _) => (); }
+  }
+
+  /* We shouldn't get here, but the type checker needs placating. */
+  unreachable("unreachable");
+}
+
+def rmTree(f: File) {
+  def walk(f: File) {
+    if (jni.stat(f).isdir) {
+      closing(new jni.DirFilesIterator(f)) { _ foreach(walk _) }
+      try { jni.rmdir(f); }
+      catch { case SystemError(ENOENT, _) => (); }
+    } else {
+      try { jni.unlink(f); }
+      catch { case SystemError(ENOENT, _) => (); }
+    }
+  }
+  walk(f);
+}
+def rmTree(path: String) { rmTree(new File(path)); }
+
+def fileExists(path: String): Boolean =
+  try { jni.stat(path); true }
+  catch { case SystemError(ENOENT, _) => false };
+def fileExists(file: File): Boolean = fileExists(file.getPath);
+
+/*----- That's all, folks -------------------------------------------------*/
+
+}